func (b *bytesValue) Set(s string) error { v, err := humanizeutil.ParseBytes(s) if err != nil { return err } *b.val = v b.isSet = true return nil }
// EnvOrDefaultBytes returns the value set by the specified environment // variable, if any, otherwise the specified default value. func EnvOrDefaultBytes(name string, value int64) int64 { if str, present := getEnv(name, 1); present { v, err := humanizeutil.ParseBytes(str) if err != nil { log.Errorf(context.Background(), "error parsing %s: %s", name, err) return value } return v } return value }
// newStoreSpec parses the string passed into a --store flag and returns a // StoreSpec if it is correctly parsed. // There are four possible fields that can be passed in, comma separated: // - path=xxx The directory in which to the rocks db instance should be // located, required unless using a in memory storage. // - type=mem This specifies that the store is an in memory storage instead of // an on disk one. mem is currently the only other type available. // - size=xxx The optional maximum size of the storage. This can be in one of a // few different formats. // - 10000000000 -> 10000000000 bytes // - 20GB -> 20000000000 bytes // - 20GiB -> 21474836480 bytes // - 0.02TiB -> 21474836480 bytes // - 20% -> 20% of the available space // - 0.2 -> 20% of the available space // - attrs=xxx:yyy:zzz A colon separated list of optional attributes. // Note that commas are forbidden within any field name or value. func newStoreSpec(value string) (StoreSpec, error) { if len(value) == 0 { return StoreSpec{}, fmt.Errorf("no value specified") } var ss StoreSpec used := make(map[string]struct{}) for _, split := range strings.Split(value, ",") { if len(split) == 0 { continue } subSplits := strings.SplitN(split, "=", 2) var field string var value string if len(subSplits) == 1 { field = "path" value = subSplits[0] } else { field = strings.ToLower(subSplits[0]) value = subSplits[1] } if _, ok := used[field]; ok { return StoreSpec{}, fmt.Errorf("%s field was used twice in store definition", field) } used[field] = struct{}{} if len(field) == 0 { continue } if len(value) == 0 { return StoreSpec{}, fmt.Errorf("no value specified for %s", field) } switch field { case "path": if len(value) == 0 { } ss.Path = value case "size": if len(value) == 0 { return StoreSpec{}, fmt.Errorf("no size specified") } if unicode.IsDigit(rune(value[len(value)-1])) && (strings.HasPrefix(value, "0.") || strings.HasPrefix(value, ".")) { // Value is a percentage without % sign. var err error ss.SizePercent, err = strconv.ParseFloat(value, 64) ss.SizePercent *= 100 if err != nil { return StoreSpec{}, fmt.Errorf("could not parse store size (%s) %s", value, err) } if ss.SizePercent > 100 || ss.SizePercent < 1 { return StoreSpec{}, fmt.Errorf("store size (%s) must be between 1%% and 100%%", value) } } else if strings.HasSuffix(value, "%") { // Value is a percentage. var err error ss.SizePercent, err = strconv.ParseFloat(value[:len(value)-1], 64) if err != nil { return StoreSpec{}, fmt.Errorf("could not parse store size (%s) %s", value, err) } if ss.SizePercent > 100 || ss.SizePercent < 1 { return StoreSpec{}, fmt.Errorf("store size (%s) must be between 1%% and 100%%", value) } } else { var err error ss.SizeInBytes, err = humanizeutil.ParseBytes(value) if err != nil { return StoreSpec{}, fmt.Errorf("could not parse store size (%s) %s", value, err) } if ss.SizeInBytes < MinimumStoreSize { return StoreSpec{}, fmt.Errorf("store size (%s) must be larger than %s", value, humanizeutil.IBytes(MinimumStoreSize)) } } case "attrs": if len(value) == 0 { return StoreSpec{}, fmt.Errorf("no attributes specified") } // Check to make sure there are no duplicate attributes. attrMap := make(map[string]struct{}) for _, attribute := range strings.Split(value, ":") { if _, ok := attrMap[attribute]; ok { return StoreSpec{}, fmt.Errorf("duplicate attribute given for store: %s", attribute) } attrMap[attribute] = struct{}{} } for attribute := range attrMap { ss.Attributes.Attrs = append(ss.Attributes.Attrs, attribute) } sort.Strings(ss.Attributes.Attrs) case "type": if value == "mem" { ss.InMemory = true } else { return StoreSpec{}, fmt.Errorf("%s is not a valid store type", value) } default: return StoreSpec{}, fmt.Errorf("%s is not a valid store field", field) } } if ss.InMemory { // Only in memory stores don't need a path and require a size. if ss.Path != "" { return StoreSpec{}, fmt.Errorf("path specified for in memory store") } if ss.SizePercent == 0 && ss.SizeInBytes == 0 { return StoreSpec{}, fmt.Errorf("size must be specified for an in memory store") } } else if ss.Path == "" { return StoreSpec{}, fmt.Errorf("no path specified") } return ss, nil }
// TestHumanizeBytes verifies both IBytes and ParseBytes. func TestBytes(t *testing.T) { defer leaktest.AfterTest(t)() testCases := []struct { value int64 exp string expNeg string parseExp int64 parseErr string parseErrNeg string }{ {0, "0 B", "0 B", 0, "", ""}, {1024, "1.0 KiB", "-1.0 KiB", 1024, "", ""}, {1024 << 10, "1.0 MiB", "-1.0 MiB", 1024 << 10, "", ""}, {1024 << 20, "1.0 GiB", "-1.0 GiB", 1024 << 20, "", ""}, {1024 << 30, "1.0 TiB", "-1.0 TiB", 1024 << 30, "", ""}, {1024 << 40, "1.0 PiB", "-1.0 PiB", 1024 << 40, "", ""}, {1024 << 50, "1.0 EiB", "-1.0 EiB", 1024 << 50, "", ""}, {int64(math.MaxInt64), "8.0 EiB", "-8.0 EiB", 0, "too large: 8.0 EiB", "too large: -8.0 EiB"}, } for i, testCase := range testCases { // Test IBytes. if actual := humanizeutil.IBytes(testCase.value); actual != testCase.exp { t.Errorf("%d: IBytes(%d) actual:%s does not match expected:%s", i, testCase.value, actual, testCase.exp) } // Test negative IBytes. if actual := humanizeutil.IBytes(-testCase.value); actual != testCase.expNeg { t.Errorf("%d: IBytes(%d) actual:%s does not match expected:%s", i, -testCase.value, actual, testCase.expNeg) } // Test ParseBytes. if actual, err := humanizeutil.ParseBytes(testCase.exp); err != nil { if len(testCase.parseErr) > 0 { if testCase.parseErr != err.Error() { t.Errorf("%d: ParseBytes(%s) caused an incorrect error actual:%s, expected:%s", i, testCase.exp, err, testCase.parseErr) } } else { t.Errorf("%d: ParseBytes(%s) caused an unexpected error:%s", i, testCase.exp, err) } } else if actual != testCase.parseExp { t.Errorf("%d: ParseBytes(%s) actual:%d does not match expected:%d", i, testCase.exp, actual, testCase.parseExp) } // Test negative ParseBytes. if actual, err := humanizeutil.ParseBytes(testCase.expNeg); err != nil { if len(testCase.parseErrNeg) > 0 { if testCase.parseErrNeg != err.Error() { t.Errorf("%d: ParseBytes(%s) caused an incorrect error actual:%s, expected:%s", i, testCase.expNeg, err, testCase.parseErrNeg) } } else { t.Errorf("%d: ParseBytes(%s) caused an unexpected error:%s", i, testCase.expNeg, err) } } else if actual != -testCase.parseExp { t.Errorf("%d: ParseBytes(%s) actual:%d does not match expected:%d", i, testCase.expNeg, actual, -testCase.parseExp) } } // Some extra error cases for good measure. testFailCases := []struct { value string expected string }{ {"", "parsing \"\": invalid syntax"}, // our error {"1 ZB", "unhandled size name: zb"}, // humanize's error {"-1 ZB", "unhandled size name: zb"}, // humanize's error {"1 ZiB", "unhandled size name: zib"}, // humanize's error {"-1 ZiB", "unhandled size name: zib"}, // humanize's error {"100 EiB", "too large: 100 EiB"}, // humanize's error {"-100 EiB", "too large: 100 EiB"}, // humanize's error {"10 EiB", "too large: 10 EiB"}, // our error {"-10 EiB", "too large: -10 EiB"}, // our error } for i, testCase := range testFailCases { if _, err := humanizeutil.ParseBytes(testCase.value); err.Error() != testCase.expected { t.Errorf("%d: ParseBytes(%s) caused an incorrect error actual:%s, expected:%s", i, testCase.value, err, testCase.expected) } } }