New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
strutil: add new ParseByteSize #5719
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one open question about kB vs KB
strutil/strutil.go
Outdated
func ParseValueWithUnit(inp string) (int64, error) { | ||
unitMultiplier := map[string]int{ | ||
"B": 1, | ||
"kB": 1000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we support KB
in case someone just types that out of a habit? (regardless if they meant 1024 or 1000)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KiB
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that lower case and upper case difference is pretty subtle and may be missed by people. For usability we should perhaps ignore case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was sitting on the fence about it but I think it does make sense to make the users life easier this way. I will make it ignore the case.
{"1MB", 1000 * 1000}, | ||
{"20MB", 20 * 1000 * 1000}, | ||
{"1GB", 1000 * 1000 * 1000}, | ||
{"31GB", 31 * 1000 * 1000 * 1000}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard drive vendors approve of this number ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks very nice and useful. One request for an extra test checks, pluse some suggestions/questions.
"GB": 1000 * 1000 * 1000, | ||
"TB": 1000 * 1000 * 1000 * 1000, | ||
"PB": 1000 * 1000 * 1000 * 1000 * 1000, | ||
"EB": 1000 * 1000 * 1000 * 1000 * 1000 * 1000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You got carried away, did't you? 😄 But that's fine.
strutil/strutil.go
Outdated
"PB": 1000 * 1000 * 1000 * 1000 * 1000, | ||
"EB": 1000 * 1000 * 1000 * 1000 * 1000 * 1000, | ||
} | ||
if len(inp) < 2 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to default to bytes if unit is omited, to simplify calling side? That of course means unit won't be mandatory, but I guess that's the case since we already have values around in the state without "B" suffix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My gut feeling is that we should enforce a unit. The error message tries to help steer the user into the right direction.
strutil/strutil.go
Outdated
return 0, fmt.Errorf("cannot parse %q: need a unit", inp) | ||
} | ||
// simple case, "1234B", read as byte | ||
if lastCh := inp[len(inp)-1]; lastCh == 'B' { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm missing something, we will always hit this case since every unit ends with 'B', so we will (usually) unnecessarily try convert the string to int, it will fail so we will carry on with two-char suffix check.
Since two-letter units will be used most of the time I suppose, would it make sense to reverse and re-organise the two conditions (unitMultiplier[unit]
lookup failure wouldn't be fatal in such case), or alternatively enhance this one to check if the inp[len(inp)-2] character is a digit to avoid ParseInt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but then we don't do anything when err != nil (we just continue and then hit the remaining cases)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, what I'm trying to say is we will hit ParseInt error case 99% of the time if we check for 'B' suffix first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And to be clear, the logic as currently is is perfectly correct, just suggesting to slightly re-organize the checks so that we only attempt ParseInt when we know unit is already correct (after two-character lookup, and then 'B' suffix check).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the order of the checks now.
{"1MB", 1000 * 1000}, | ||
{"20MB", 20 * 1000 * 1000}, | ||
{"1GB", 1000 * 1000 * 1000}, | ||
{"31GB", 31 * 1000 * 1000 * 1000}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add TB/EB/PB checks as well?
The new ParseValueWithUnit helper allows the user to write "500kB" which will be converted into 500*1000. This will be used in the rate-limit PR to support options like: snap set core refresh.rate-limit=500kB Split out to make the review simpler.
strutil/strutil.go
Outdated
@@ -111,3 +111,37 @@ func TruncateOutput(data []byte, maxLines, maxBytes int) []byte { | |||
} | |||
return data | |||
} | |||
|
|||
// ParseValueWithUnit parses a value like 500kB and returns the number | |||
// in byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bytes
strutil/strutil.go
Outdated
} | ||
// two char suffix | ||
unit := inp[len(inp)-2:] | ||
mul, ok := unitMultiplier[unit] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or the old-school method and you can drop the map:
val := uint64(123)
unit := "GB"
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
for i := 0; i < len(units) && unit != units[i]; i++ {
val *= 1000
}
Codecov Report
@@ Coverage Diff @@
## master #5719 +/- ##
==========================================
+ Coverage 78.97% 78.98% +<.01%
==========================================
Files 524 524
Lines 40007 40029 +22
==========================================
+ Hits 31596 31616 +20
- Misses 5843 5844 +1
- Partials 2568 2569 +1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the change! +1
Codecov Report
@@ Coverage Diff @@
## master #5719 +/- ##
==========================================
+ Coverage 78.96% 78.98% +0.01%
==========================================
Files 523 525 +2
Lines 40033 40073 +40
==========================================
+ Hits 31613 31651 +38
- Misses 5849 5850 +1
- Partials 2571 2572 +1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of suggestions, and LGTM.
strutil/strutil.go
Outdated
|
||
// ParseValueWithUnit parses a value like 500kB and returns the number | ||
// in bytes. The case of the unit will be ignored for user convenience. | ||
func ParseValueWithUnit(inp string) (int64, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name doesn't give a hint about what kind of value we have at hand (time, distance, length, etc). Perhaps ParseByteSize or ParseByteUnit or ParseSizeUnit?
strutil/strutil.go
Outdated
} | ||
asByte, err := strconv.ParseInt(inp[:len(inp)-1], 10, 64) | ||
if err != nil { | ||
return 0, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error will probably look awkward with a string like "200GiB".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, will fix.
Rename strutil.ParseValueWithUnit to strutil.ParseByteSize and improve error handling for cases like "200GiB" (and add extra tests). Thanks to Gustavo.
The new ParseValueWithUnit helper allows the user to write
"500kB" which will be converted into 500*1000. This will be
used in the rate-limit PR to support options like:
Split out to make the review simpler.