Skip to content

Commit 80ef147

Browse files
authored
fix(cli): stat: explicitly specify resource SI prefix (#8206)
* fix(cli): move to explicitly specifying units * make gen
1 parent 1558ef5 commit 80ef147

12 files changed

+161
-83
lines changed

cli/clistat/cgroup.go

+17-14
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ func (s *Statter) ContainerCPU() (*Result, error) {
8484
}
8585

8686
r := &Result{
87-
Unit: "cores",
88-
Used: used2 - used1,
89-
Total: ptr.To(total),
87+
Unit: "cores",
88+
Used: used2 - used1,
89+
Total: ptr.To(total),
90+
Prefix: PrefixDefault,
9091
}
9192
return r, nil
9293
}
@@ -184,20 +185,20 @@ func (s *Statter) cGroupV1CPUUsed() (float64, error) {
184185

185186
// ContainerMemory returns the memory usage of the container cgroup.
186187
// If the system is not containerized, this always returns nil.
187-
func (s *Statter) ContainerMemory() (*Result, error) {
188+
func (s *Statter) ContainerMemory(p Prefix) (*Result, error) {
188189
if ok, err := IsContainerized(s.fs); err != nil || !ok {
189190
return nil, nil //nolint:nilnil
190191
}
191192

192193
if s.isCGroupV2() {
193-
return s.cGroupV2Memory()
194+
return s.cGroupV2Memory(p)
194195
}
195196

196197
// Fall back to CGroupv1
197-
return s.cGroupV1Memory()
198+
return s.cGroupV1Memory(p)
198199
}
199200

200-
func (s *Statter) cGroupV2Memory() (*Result, error) {
201+
func (s *Statter) cGroupV2Memory(p Prefix) (*Result, error) {
201202
maxUsageBytes, err := readInt64(s.fs, cgroupV2MemoryMaxBytes)
202203
if err != nil {
203204
return nil, xerrors.Errorf("read memory total: %w", err)
@@ -214,13 +215,14 @@ func (s *Statter) cGroupV2Memory() (*Result, error) {
214215
}
215216

216217
return &Result{
217-
Total: ptr.To(float64(maxUsageBytes)),
218-
Used: float64(currUsageBytes - inactiveFileBytes),
219-
Unit: "B",
218+
Total: ptr.To(float64(maxUsageBytes)),
219+
Used: float64(currUsageBytes - inactiveFileBytes),
220+
Unit: "B",
221+
Prefix: p,
220222
}, nil
221223
}
222224

223-
func (s *Statter) cGroupV1Memory() (*Result, error) {
225+
func (s *Statter) cGroupV1Memory(p Prefix) (*Result, error) {
224226
maxUsageBytes, err := readInt64(s.fs, cgroupV1MemoryMaxUsageBytes)
225227
if err != nil {
226228
return nil, xerrors.Errorf("read memory total: %w", err)
@@ -239,9 +241,10 @@ func (s *Statter) cGroupV1Memory() (*Result, error) {
239241

240242
// Total memory used is usage - total_inactive_file
241243
return &Result{
242-
Total: ptr.To(float64(maxUsageBytes)),
243-
Used: float64(usageBytes - totalInactiveFileBytes),
244-
Unit: "B",
244+
Total: ptr.To(float64(maxUsageBytes)),
245+
Used: float64(usageBytes - totalInactiveFileBytes),
246+
Unit: "B",
247+
Prefix: p,
245248
}, nil
246249
}
247250

cli/clistat/disk.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
// Disk returns the disk usage of the given path.
1212
// If path is empty, it returns the usage of the root directory.
13-
func (*Statter) Disk(path string) (*Result, error) {
13+
func (*Statter) Disk(p Prefix, path string) (*Result, error) {
1414
if path == "" {
1515
path = "/"
1616
}
@@ -22,5 +22,6 @@ func (*Statter) Disk(path string) (*Result, error) {
2222
r.Total = ptr.To(float64(stat.Blocks * uint64(stat.Bsize)))
2323
r.Used = float64(stat.Blocks-stat.Bfree) * float64(stat.Bsize)
2424
r.Unit = "B"
25+
r.Prefix = p
2526
return &r, nil
2627
}

cli/clistat/disk_windows.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
// Disk returns the disk usage of the given path.
99
// If path is empty, it defaults to C:\
10-
func (*Statter) Disk(path string) (*Result, error) {
10+
func (*Statter) Disk(p Prefix, path string) (*Result, error) {
1111
if path == "" {
1212
path = `C:\`
1313
}
@@ -31,5 +31,6 @@ func (*Statter) Disk(path string) (*Result, error) {
3131
r.Total = ptr.To(float64(totalBytes))
3232
r.Used = float64(totalBytes - freeBytes)
3333
r.Unit = "B"
34+
r.Prefix = p
3435
return &r, nil
3536
}

cli/clistat/stat.go

+75-34
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package clistat
22

33
import (
4-
"fmt"
54
"math"
65
"runtime"
76
"strconv"
87
"strings"
98
"time"
109

11-
"github.com/dustin/go-humanize"
1210
"github.com/elastic/go-sysinfo"
1311
"github.com/spf13/afero"
1412
"golang.org/x/xerrors"
@@ -17,15 +15,65 @@ import (
1715
sysinfotypes "github.com/elastic/go-sysinfo/types"
1816
)
1917

18+
// Prefix is a scale multiplier for a result.
19+
// Used when creating a human-readable representation.
20+
type Prefix float64
21+
22+
const (
23+
PrefixDefault = 1.0
24+
PrefixKibi = 1024.0
25+
PrefixMebi = PrefixKibi * 1024.0
26+
PrefixGibi = PrefixMebi * 1024.0
27+
PrefixTebi = PrefixGibi * 1024.0
28+
)
29+
30+
var (
31+
PrefixHumanKibi = "Ki"
32+
PrefixHumanMebi = "Mi"
33+
PrefixHumanGibi = "Gi"
34+
PrefixHumanTebi = "Ti"
35+
)
36+
37+
func (s *Prefix) String() string {
38+
switch *s {
39+
case PrefixKibi:
40+
return "Ki"
41+
case PrefixMebi:
42+
return "Mi"
43+
case PrefixGibi:
44+
return "Gi"
45+
case PrefixTebi:
46+
return "Ti"
47+
default:
48+
return ""
49+
}
50+
}
51+
52+
func ParsePrefix(s string) Prefix {
53+
switch s {
54+
case PrefixHumanKibi:
55+
return PrefixKibi
56+
case PrefixHumanMebi:
57+
return PrefixMebi
58+
case PrefixHumanGibi:
59+
return PrefixGibi
60+
case PrefixHumanTebi:
61+
return PrefixTebi
62+
default:
63+
return PrefixDefault
64+
}
65+
}
66+
2067
// Result is a generic result type for a statistic.
2168
// Total is the total amount of the resource available.
2269
// It is nil if the resource is not a finite quantity.
2370
// Unit is the unit of the resource.
2471
// Used is the amount of the resource used.
2572
type Result struct {
26-
Total *float64 `json:"total"`
27-
Unit string `json:"unit"`
28-
Used float64 `json:"used"`
73+
Total *float64 `json:"total"`
74+
Unit string `json:"unit"`
75+
Used float64 `json:"used"`
76+
Prefix Prefix `json:"-"`
2977
}
3078

3179
// String returns a human-readable representation of the result.
@@ -34,38 +82,29 @@ func (r *Result) String() string {
3482
return "-"
3583
}
3684

37-
var usedDisplay, totalDisplay string
38-
var usedScaled, totalScaled float64
39-
var usedPrefix, totalPrefix string
40-
usedScaled, usedPrefix = humanize.ComputeSI(r.Used)
41-
usedDisplay = humanizeFloat(usedScaled)
42-
if r.Total != (*float64)(nil) {
43-
totalScaled, totalPrefix = humanize.ComputeSI(*r.Total)
44-
totalDisplay = humanizeFloat(totalScaled)
85+
scale := 1.0
86+
if r.Prefix != 0.0 {
87+
scale = float64(r.Prefix)
4588
}
4689

4790
var sb strings.Builder
48-
_, _ = sb.WriteString(usedDisplay)
49-
50-
// If the unit prefixes of the used and total values are different,
51-
// display the used value's prefix to avoid confusion.
52-
if usedPrefix != totalPrefix || totalDisplay == "" {
53-
_, _ = sb.WriteString(" ")
54-
_, _ = sb.WriteString(usedPrefix)
55-
_, _ = sb.WriteString(r.Unit)
56-
}
57-
58-
if totalDisplay != "" {
91+
var usedScaled, totalScaled float64
92+
usedScaled = r.Used / scale
93+
_, _ = sb.WriteString(humanizeFloat(usedScaled))
94+
if r.Total != (*float64)(nil) {
5995
_, _ = sb.WriteString("/")
60-
_, _ = sb.WriteString(totalDisplay)
61-
_, _ = sb.WriteString(" ")
62-
_, _ = sb.WriteString(totalPrefix)
63-
_, _ = sb.WriteString(r.Unit)
96+
totalScaled = *r.Total / scale
97+
_, _ = sb.WriteString(humanizeFloat(totalScaled))
6498
}
6599

66-
if r.Total != nil && *r.Total != 0.0 {
100+
_, _ = sb.WriteString(" ")
101+
_, _ = sb.WriteString(r.Prefix.String())
102+
_, _ = sb.WriteString(r.Unit)
103+
104+
if r.Total != (*float64)(nil) && *r.Total > 0 {
67105
_, _ = sb.WriteString(" (")
68-
_, _ = sb.WriteString(fmt.Sprintf("%.0f", r.Used/(*r.Total)*100.0))
106+
pct := r.Used / *r.Total * 100.0
107+
_, _ = sb.WriteString(strconv.FormatFloat(pct, 'f', 0, 64))
69108
_, _ = sb.WriteString("%)")
70109
}
71110

@@ -153,8 +192,9 @@ func New(opts ...Option) (*Statter, error) {
153192
// Units are in "cores".
154193
func (s *Statter) HostCPU() (*Result, error) {
155194
r := &Result{
156-
Unit: "cores",
157-
Total: ptr.To(float64(s.nproc)),
195+
Unit: "cores",
196+
Total: ptr.To(float64(s.nproc)),
197+
Prefix: PrefixDefault,
158198
}
159199
c1, err := s.hi.CPUTime()
160200
if err != nil {
@@ -177,9 +217,10 @@ func (s *Statter) HostCPU() (*Result, error) {
177217
}
178218

179219
// HostMemory returns the memory usage of the host, in gigabytes.
180-
func (s *Statter) HostMemory() (*Result, error) {
220+
func (s *Statter) HostMemory(p Prefix) (*Result, error) {
181221
r := &Result{
182-
Unit: "B",
222+
Unit: "B",
223+
Prefix: p,
183224
}
184225
hm, err := s.hi.Memory()
185226
if err != nil {

cli/clistat/stat_internal_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ func TestResultString(t *testing.T) {
3333
Result: Result{Used: 12.34, Total: nil, Unit: ""},
3434
},
3535
{
36-
Expected: "1.54 kB",
37-
Result: Result{Used: 1536, Total: nil, Unit: "B"},
36+
Expected: "1.5 KiB",
37+
Result: Result{Used: 1536, Total: nil, Unit: "B", Prefix: PrefixKibi},
3838
},
3939
{
4040
Expected: "1.23 things",
4141
Result: Result{Used: 1.234, Total: nil, Unit: "things"},
4242
},
4343
{
44-
Expected: "1 B/100 TB (0%)",
45-
Result: Result{Used: 1, Total: ptr.To(1000 * 1000 * 1000 * 1000 * 100.0), Unit: "B"},
44+
Expected: "0/100 TiB (0%)",
45+
Result: Result{Used: 1, Total: ptr.To(100.0 * float64(PrefixTebi)), Unit: "B", Prefix: PrefixTebi},
4646
},
4747
{
48-
Expected: "500 mcores/8 cores (6%)",
48+
Expected: "0.5/8 cores (6%)",
4949
Result: Result{Used: 0.5, Total: ptr.To(8.0), Unit: "cores"},
5050
},
5151
} {
@@ -76,7 +76,7 @@ func TestStatter(t *testing.T) {
7676

7777
t.Run("HostMemory", func(t *testing.T) {
7878
t.Parallel()
79-
mem, err := s.HostMemory()
79+
mem, err := s.HostMemory(PrefixDefault)
8080
require.NoError(t, err)
8181
assert.NotZero(t, mem.Used)
8282
assert.NotZero(t, mem.Total)
@@ -85,7 +85,7 @@ func TestStatter(t *testing.T) {
8585

8686
t.Run("HostDisk", func(t *testing.T) {
8787
t.Parallel()
88-
disk, err := s.Disk("") // default to home dir
88+
disk, err := s.Disk(PrefixDefault, "") // default to home dir
8989
require.NoError(t, err)
9090
assert.NotZero(t, disk.Used)
9191
assert.NotZero(t, disk.Total)
@@ -159,7 +159,7 @@ func TestStatter(t *testing.T) {
159159
fs := initFS(t, fsContainerCgroupV1)
160160
s, err := New(WithFS(fs), withNoWait)
161161
require.NoError(t, err)
162-
mem, err := s.ContainerMemory()
162+
mem, err := s.ContainerMemory(PrefixDefault)
163163
require.NoError(t, err)
164164
require.NotNil(t, mem)
165165
assert.Equal(t, 268435456.0, mem.Used)
@@ -211,7 +211,7 @@ func TestStatter(t *testing.T) {
211211
fs := initFS(t, fsContainerCgroupV2)
212212
s, err := New(WithFS(fs), withNoWait)
213213
require.NoError(t, err)
214-
mem, err := s.ContainerMemory()
214+
mem, err := s.ContainerMemory(PrefixDefault)
215215
require.NoError(t, err)
216216
require.NotNil(t, mem)
217217
assert.Equal(t, 268435456.0, mem.Used)

0 commit comments

Comments
 (0)