tools/humanize: add 'FormatByteRate' to format transfer speed

This commit is contained in:
Taylor Blau 2018-01-04 14:12:02 -08:00
parent 5058d0f983
commit d3afd10340
2 changed files with 81 additions and 0 deletions

@ -5,6 +5,7 @@ import (
"math"
"strconv"
"strings"
"time"
"unicode"
"github.com/git-lfs/git-lfs/errors"
@ -23,6 +24,10 @@ const (
Gigabyte = 1000 * Megabyte
Terabyte = 1000 * Gigabyte
Petabyte = 1000 * Terabyte
// eps is the machine epsilon, or a 64-bit floating point value
// reasonably close to zero.
eps float64 = 7.0/3 - 4.0/3 - 1.0
)
var bytesTable = map[string]uint64{
@ -126,6 +131,38 @@ func FormatBytesUnit(s, u uint64) string {
return fmt.Sprintf(format, rounded)
}
// FormatByteRate outputs the given rate of transfer "r" as the quotient of "s"
// (the number of bytes transferred) over "d" (the duration of time that those
// bytes were transferred in).
//
// It displays the output as a quantity of a "per-unit-time" unit (i.e., B/s,
// MiB/s) in the most representative fashion possible, as above.
func FormatByteRate(s uint64, d time.Duration) string {
// e is the index of the most representative unit of storage.
var e float64
// f is the floating-point equivalent of "s", so as to avoid more
// conversions than necessary.
f := float64(s)
if f != 0 {
f = f / d.Seconds()
e = math.Floor(log(f, 1000))
if e <= eps {
// The result of math.Floor(log(r, 1000)) can be
// "close-enough" to zero that it should be effectively
// considered zero.
e = 0
}
}
unit := uint64(math.Pow(1000, e))
suffix := sizes[int(e)]
return fmt.Sprintf("%s %s/s",
FormatBytesUnit(uint64(math.Ceil(f)), unit), suffix)
}
// log takes the log base "b" of "n" (\log_b{n})
func log(n, b float64) float64 {
return math.Log(n) / math.Log(b)

@ -3,6 +3,7 @@ package humanize_test
import (
"math"
"testing"
"time"
"github.com/git-lfs/git-lfs/tools/humanize"
"github.com/stretchr/testify/assert"
@ -59,6 +60,16 @@ func (c *FormatBytesUnitTestCase) Assert(t *testing.T) {
assert.Equal(t, c.Expected, humanize.FormatBytesUnit(c.Given, c.Unit))
}
type FormatByteRateTestCase struct {
Given uint64
Over time.Duration
Expected string
}
func (c *FormatByteRateTestCase) Assert(t *testing.T) {
assert.Equal(t, c.Expected, humanize.FormatByteRate(c.Given, c.Over))
}
func TestParseBytes(t *testing.T) {
for desc, c := range map[string]*ParseBytesTestCase{
"parse byte (zero, empty)": {"", uint64(0), nil},
@ -234,3 +245,36 @@ func TestFormatBytesUnit(t *testing.T) {
t.Run(desc, c.Assert)
}
}
func TestFormateByteRate(t *testing.T) {
for desc, c := range map[string]*FormatByteRateTestCase{
"format bytes": {uint64(1 * math.Pow(10, 0)), time.Second, "1 B/s"},
"format kilobytes": {uint64(1 * math.Pow(10, 3)), time.Second, "1.0 KB/s"},
"format megabytes": {uint64(1 * math.Pow(10, 6)), time.Second, "1.0 MB/s"},
"format gigabytes": {uint64(1 * math.Pow(10, 9)), time.Second, "1.0 GB/s"},
"format petabytes": {uint64(1 * math.Pow(10, 12)), time.Second, "1.0 TB/s"},
"format terabytes": {uint64(1 * math.Pow(10, 15)), time.Second, "1.0 PB/s"},
"format kilobytes under": {uint64(1.49 * math.Pow(10, 3)), time.Second, "1.5 KB/s"},
"format megabytes under": {uint64(1.49 * math.Pow(10, 6)), time.Second, "1.5 MB/s"},
"format gigabytes under": {uint64(1.49 * math.Pow(10, 9)), time.Second, "1.5 GB/s"},
"format petabytes under": {uint64(1.49 * math.Pow(10, 12)), time.Second, "1.5 TB/s"},
"format terabytes under": {uint64(1.49 * math.Pow(10, 15)), time.Second, "1.5 PB/s"},
"format kilobytes over": {uint64(1.51 * math.Pow(10, 3)), time.Second, "1.5 KB/s"},
"format megabytes over": {uint64(1.51 * math.Pow(10, 6)), time.Second, "1.5 MB/s"},
"format gigabytes over": {uint64(1.51 * math.Pow(10, 9)), time.Second, "1.5 GB/s"},
"format petabytes over": {uint64(1.51 * math.Pow(10, 12)), time.Second, "1.5 TB/s"},
"format terabytes over": {uint64(1.51 * math.Pow(10, 15)), time.Second, "1.5 PB/s"},
"format kilobytes exact": {uint64(1.3 * math.Pow(10, 3)), time.Second, "1.3 KB/s"},
"format megabytes exact": {uint64(1.3 * math.Pow(10, 6)), time.Second, "1.3 MB/s"},
"format gigabytes exact": {uint64(1.3 * math.Pow(10, 9)), time.Second, "1.3 GB/s"},
"format petabytes exact": {uint64(1.3 * math.Pow(10, 12)), time.Second, "1.3 TB/s"},
"format terabytes exact": {uint64(1.3 * math.Pow(10, 15)), time.Second, "1.3 PB/s"},
"format bytes (non-second)": {uint64(10 * math.Pow(10, 0)), 2 * time.Second, "5 B/s"},
} {
t.Run(desc, c.Assert)
}
}