Skip to content
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

Merged
merged 8 commits into from Aug 29, 2018
Merged

strutil: add new ParseByteSize #5719

merged 8 commits into from Aug 29, 2018

Conversation

mvo5
Copy link
Contributor

@mvo5 mvo5 commented Aug 24, 2018

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.

Copy link
Collaborator

@zyga zyga left a 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

func ParseValueWithUnit(inp string) (int64, error) {
unitMultiplier := map[string]int{
"B": 1,
"kB": 1000,
Copy link
Collaborator

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)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KiB?

Copy link
Collaborator

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.

Copy link
Contributor Author

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},
Copy link
Collaborator

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 ;-)

Copy link
Contributor

@stolowski stolowski left a 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,
Copy link
Contributor

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.

"PB": 1000 * 1000 * 1000 * 1000 * 1000,
"EB": 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
}
if len(inp) < 2 {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

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' {
Copy link
Contributor

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?

Copy link
Collaborator

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)

Copy link
Contributor

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.

Copy link
Contributor

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).

Copy link
Contributor Author

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},
Copy link
Contributor

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.
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes

}
// two char suffix
unit := inp[len(inp)-2:]
mul, ok := unitMultiplier[unit]
Copy link
Collaborator

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-io
Copy link

Codecov Report

Merging #5719 into master will increase coverage by <.01%.
The diff coverage is 90.9%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
strutil/strutil.go 95.23% <90.9%> (-2.33%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0ca9c70...3002927. Read the comment docs.

Copy link
Contributor

@stolowski stolowski left a 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-io
Copy link

codecov-io commented Aug 27, 2018

Codecov Report

Merging #5719 into master will increase coverage by 0.01%.
The diff coverage is 87.09%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
strutil/strutil.go 93.05% <87.09%> (-4.51%) ⬇️
overlord/ifacestate/handlers.go 63.57% <0%> (-0.32%) ⬇️
httputil/redirect18.go 100% <0%> (ø)
httputil/transport17.go 100% <0%> (ø)
wrappers/binaries.go 83.33% <0%> (+5.55%) ⬆️
httputil/useragent.go 82.85% <0%> (+5.71%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4cbc187...8f5c0c4. Read the comment docs.

Copy link
Contributor

@niemeyer niemeyer left a 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.


// 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) {
Copy link
Contributor

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?

}
asByte, err := strconv.ParseInt(inp[:len(inp)-1], 10, 64)
if err != nil {
return 0, err
Copy link
Contributor

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".

Copy link
Contributor Author

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.
@mvo5 mvo5 changed the title strutil: add new ParseValueWithUnit strutil: add new ParseByteSize Aug 28, 2018
@mvo5 mvo5 merged commit db72d85 into snapcore:master Aug 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants