2017-06-13 20:01:41 +00:00
|
|
|
package humanize
|
|
|
|
|
|
|
|
import (
|
2017-06-13 20:02:00 +00:00
|
|
|
"fmt"
|
2017-06-13 20:01:41 +00:00
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2018-01-04 22:12:02 +00:00
|
|
|
"time"
|
2017-06-13 20:01:41 +00:00
|
|
|
"unicode"
|
|
|
|
|
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
Byte = 1 << (iota * 10)
|
|
|
|
Kibibyte
|
|
|
|
Mebibyte
|
|
|
|
Gibibyte
|
|
|
|
Tebibyte
|
|
|
|
Pebibyte
|
|
|
|
|
|
|
|
Kilobyte = 1000 * Byte
|
|
|
|
Megabyte = 1000 * Kilobyte
|
|
|
|
Gigabyte = 1000 * Megabyte
|
|
|
|
Terabyte = 1000 * Gigabyte
|
|
|
|
Petabyte = 1000 * Terabyte
|
2018-01-04 22:12:02 +00:00
|
|
|
|
|
|
|
// 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
|
2017-06-13 20:01:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var bytesTable = map[string]uint64{
|
2017-06-26 15:25:32 +00:00
|
|
|
"": Byte,
|
2017-06-13 20:01:41 +00:00
|
|
|
"b": Byte,
|
|
|
|
|
|
|
|
"kib": Kibibyte,
|
|
|
|
"mib": Mebibyte,
|
|
|
|
"gib": Gibibyte,
|
|
|
|
"tib": Tebibyte,
|
|
|
|
"pib": Pebibyte,
|
|
|
|
|
|
|
|
"kb": Kilobyte,
|
|
|
|
"mb": Megabyte,
|
|
|
|
"gb": Gigabyte,
|
|
|
|
"tb": Terabyte,
|
|
|
|
"pb": Petabyte,
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseBytes parses a given human-readable bytes or ibytes string into a number
|
|
|
|
// of bytes, or an error if the string was unable to be parsed.
|
|
|
|
func ParseBytes(str string) (uint64, error) {
|
|
|
|
var sep int
|
|
|
|
for _, r := range str {
|
|
|
|
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
sep = sep + 1
|
|
|
|
}
|
|
|
|
|
2017-07-31 17:46:34 +00:00
|
|
|
var f float64
|
|
|
|
|
|
|
|
if s := strings.Replace(str[:sep], ",", "", -1); len(s) > 0 {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
f, err = strconv.ParseFloat(s, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2017-06-13 20:01:41 +00:00
|
|
|
}
|
|
|
|
|
2017-06-15 20:33:34 +00:00
|
|
|
m, err := ParseByteUnit(str[sep:])
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2017-06-13 20:01:41 +00:00
|
|
|
|
2017-06-15 20:33:34 +00:00
|
|
|
f = f * float64(m)
|
|
|
|
if f >= math.MaxUint64 {
|
|
|
|
return 0, errors.New("number of bytes too large")
|
2017-06-13 20:01:41 +00:00
|
|
|
}
|
2017-06-15 20:33:34 +00:00
|
|
|
return uint64(f), nil
|
2017-06-13 20:01:41 +00:00
|
|
|
}
|
2017-06-13 20:02:00 +00:00
|
|
|
|
2017-06-15 20:32:06 +00:00
|
|
|
// ParseByteUnit returns the number of bytes in a given unit of storage, or an
|
|
|
|
// error, if that unit is unrecognized.
|
|
|
|
func ParseByteUnit(str string) (uint64, error) {
|
|
|
|
str = strings.TrimSpace(str)
|
|
|
|
str = strings.ToLower(str)
|
|
|
|
|
|
|
|
if u, ok := bytesTable[str]; ok {
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
return 0, errors.Errorf("unknown unit: %q", str)
|
|
|
|
}
|
|
|
|
|
2017-06-13 20:02:00 +00:00
|
|
|
var sizes = []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
|
|
|
|
|
|
|
// FormatBytes outputs the given number of bytes "s" as a human-readable string,
|
|
|
|
// rounding to the nearest half within .01.
|
|
|
|
func FormatBytes(s uint64) string {
|
2017-06-15 20:51:31 +00:00
|
|
|
var e float64
|
|
|
|
if s == 0 {
|
|
|
|
e = 0
|
|
|
|
} else {
|
|
|
|
e = math.Floor(log(float64(s), 1000))
|
2017-06-13 20:02:00 +00:00
|
|
|
}
|
|
|
|
|
2017-06-15 20:51:31 +00:00
|
|
|
unit := uint64(math.Pow(1000, e))
|
2017-06-13 20:02:00 +00:00
|
|
|
suffix := sizes[int(e)]
|
|
|
|
|
2017-06-15 20:51:31 +00:00
|
|
|
return fmt.Sprintf("%s %s",
|
|
|
|
FormatBytesUnit(s, unit), suffix)
|
2017-06-13 20:02:00 +00:00
|
|
|
}
|
|
|
|
|
2017-06-15 20:48:44 +00:00
|
|
|
// FormatBytesUnit outputs the given number of bytes "s" as a quantity of the
|
|
|
|
// given units "u" to the nearest half within .01.
|
|
|
|
func FormatBytesUnit(s, u uint64) string {
|
|
|
|
var rounded float64
|
|
|
|
if s < 10 {
|
|
|
|
rounded = float64(s)
|
|
|
|
} else {
|
|
|
|
rounded = math.Floor(float64(s)/float64(u)*10+.5) / 10
|
|
|
|
}
|
|
|
|
|
|
|
|
format := "%.0f"
|
|
|
|
if rounded < 10 && u > 1 {
|
|
|
|
format = "%.1f"
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf(format, rounded)
|
|
|
|
}
|
|
|
|
|
2018-01-04 22:12:02 +00:00
|
|
|
// 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 {
|
2018-10-24 14:57:05 +00:00
|
|
|
f = f / math.Max(time.Nanosecond.Seconds(), d.Seconds())
|
2018-01-04 22:12:02 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-06-13 20:02:00 +00:00
|
|
|
// log takes the log base "b" of "n" (\log_b{n})
|
|
|
|
func log(n, b float64) float64 {
|
|
|
|
return math.Log(n) / math.Log(b)
|
|
|
|
}
|