Skip to content

Commit bec527f

Browse files
committed
automatically scale precision, remove --prefix arg
1 parent ef7460a commit bec527f

File tree

3 files changed

+73
-51
lines changed

3 files changed

+73
-51
lines changed

cli/clistat/stat.go

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package clistat
22

33
import (
4+
"fmt"
5+
"math"
46
"runtime"
57
"strconv"
68
"strings"
@@ -32,37 +34,68 @@ func (r *Result) String() string {
3234
return "-"
3335
}
3436

35-
var sb strings.Builder
36-
usedScaled, scale := humanize.ComputeSI(r.Used)
37-
usedPrec := 1
38-
if usedScaled >= 100.0 {
39-
usedPrec = 0
40-
}
41-
_, _ = sb.WriteString(strconv.FormatFloat(usedScaled, 'f', usedPrec, 64))
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)
4242
if r.Total != (*float64)(nil) {
43-
// TODO(cian): handle case where scale of total is different to used
44-
totalScaled, _ := humanize.ComputeSI(*r.Total)
45-
totalPrec := 1
46-
if totalScaled >= 100.0 {
47-
totalPrec = 0
48-
}
49-
_, _ = sb.WriteString("/")
50-
_, _ = sb.WriteString(strconv.FormatFloat(totalScaled, 'f', totalPrec, 64))
43+
totalScaled, totalPrefix = humanize.ComputeSI(*r.Total)
44+
totalDisplay = humanizeFloat(totalScaled)
45+
}
46+
47+
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)
5156
}
5257

53-
if r.Unit != "" {
58+
if totalDisplay != "" {
59+
_, _ = sb.WriteString("/")
60+
_, _ = sb.WriteString(totalDisplay)
5461
_, _ = sb.WriteString(" ")
55-
_, _ = sb.WriteString(scale)
62+
_, _ = sb.WriteString(totalPrefix)
5663
_, _ = sb.WriteString(r.Unit)
5764
}
5865

5966
if r.Total != nil && *r.Total != 0.0 {
6067
_, _ = sb.WriteString(" (")
61-
_, _ = sb.WriteString(strconv.FormatFloat(r.Used/(*r.Total)*100, 'f', 0, 64))
68+
_, _ = sb.WriteString(fmt.Sprintf("%.0f", r.Used/(*r.Total)*100.0))
6269
_, _ = sb.WriteString("%)")
6370
}
6471

65-
return sb.String()
72+
return strings.TrimSpace(sb.String())
73+
}
74+
75+
func humanizeFloat(f float64) string {
76+
// humanize.FtoaWithDigits does not round correctly.
77+
prec := precision(f)
78+
rat := math.Pow(10, float64(prec))
79+
rounded := math.Round(f*rat) / rat
80+
return strconv.FormatFloat(rounded, 'f', -1, 64)
81+
}
82+
83+
// limit precision to 3 digits at most to preserve space
84+
func precision(f float64) int {
85+
fabs := math.Abs(f)
86+
if fabs == 0.0 {
87+
return 0
88+
}
89+
if fabs < 1.0 {
90+
return 3
91+
}
92+
if fabs < 10.0 {
93+
return 2
94+
}
95+
if fabs < 100.0 {
96+
return 1
97+
}
98+
return 0
6699
}
67100

68101
// Statter is a system statistics collector.

cli/clistat/stat_internal_test.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import (
44
"testing"
55
"time"
66

7-
"tailscale.com/types/ptr"
8-
97
"github.com/spf13/afero"
108
"github.com/stretchr/testify/assert"
119
"github.com/stretchr/testify/require"
10+
"tailscale.com/types/ptr"
1211
)
1312

1413
func TestResultString(t *testing.T) {
@@ -18,11 +17,11 @@ func TestResultString(t *testing.T) {
1817
Result Result
1918
}{
2019
{
21-
Expected: "1.2/5.7 quatloos (22%)",
20+
Expected: "1.23/5.68 quatloos (22%)",
2221
Result: Result{Used: 1.234, Total: ptr.To(5.678), Unit: "quatloos"},
2322
},
2423
{
25-
Expected: "0.0/0.0 HP",
24+
Expected: "0/0 HP",
2625
Result: Result{Used: 0.0, Total: ptr.To(0.0), Unit: "HP"},
2726
},
2827
{
@@ -34,17 +33,21 @@ func TestResultString(t *testing.T) {
3433
Result: Result{Used: 12.34, Total: nil, Unit: ""},
3534
},
3635
{
37-
Expected: "1.5 kB",
36+
Expected: "1.54 kB",
3837
Result: Result{Used: 1536, Total: nil, Unit: "B"},
3938
},
4039
{
41-
Expected: "1.2 things",
40+
Expected: "1.23 things",
4241
Result: Result{Used: 1.234, Total: nil, Unit: "things"},
4342
},
4443
{
45-
Expected: "0.0/100 TiB (0%)",
44+
Expected: "1 B/100 TB (0%)",
4645
Result: Result{Used: 1, Total: ptr.To(1000 * 1000 * 1000 * 1000 * 100.0), Unit: "B"},
4746
},
47+
{
48+
Expected: "500 mcores/8 cores (6%)",
49+
Result: Result{Used: 0.5, Total: ptr.To(8.0), Unit: "cores"},
50+
},
4851
} {
4952
assert.Equal(t, tt.Expected, tt.Result.String())
5053
}

cli/stat.go

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (r *RootCmd) stat() *clibase.Cmd {
4646
containerErr := make(chan error, 1)
4747
go func() {
4848
defer close(hostErr)
49-
cs, err := st.HostCPU("")
49+
cs, err := st.HostCPU()
5050
if err != nil {
5151
hostErr <- err
5252
return
@@ -59,7 +59,7 @@ func (r *RootCmd) stat() *clibase.Cmd {
5959
// don't error if we're not in a container
6060
return
6161
}
62-
cs, err := st.ContainerCPU(clistat.PrefixGibiShort)
62+
cs, err := st.ContainerCPU()
6363
if err != nil {
6464
containerErr <- err
6565
return
@@ -75,7 +75,7 @@ func (r *RootCmd) stat() *clibase.Cmd {
7575
}
7676

7777
// Host-level stats
78-
ms, err := st.HostMemory(clistat.PrefixGibiShort)
78+
ms, err := st.HostMemory()
7979
if err != nil {
8080
return err
8181
}
@@ -85,21 +85,21 @@ func (r *RootCmd) stat() *clibase.Cmd {
8585
if err != nil {
8686
return err
8787
}
88-
ds, err := st.Disk(home, clistat.PrefixGibiShort)
88+
ds, err := st.Disk(home)
8989
if err != nil {
9090
return err
9191
}
9292
sr.Disk = ds
9393

9494
// Container-only stats.
9595
if ok, err := clistat.IsContainerized(fs); err == nil && ok {
96-
cs, err := st.ContainerCPU("")
96+
cs, err := st.ContainerCPU()
9797
if err != nil {
9898
return err
9999
}
100100
sr.ContainerCPU = cs
101101

102-
ms, err := st.ContainerMemory(clistat.PrefixGibiShort)
102+
ms, err := st.ContainerMemory()
103103
if err != nil {
104104
return err
105105
}
@@ -142,9 +142,9 @@ func (*RootCmd) statCPU(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
142142
var cs *clistat.Result
143143
var err error
144144
if ok, _ := clistat.IsContainerized(fs); ok && !hostArg {
145-
cs, err = s.ContainerCPU(clistat.Prefix(prefixArg))
145+
cs, err = s.ContainerCPU()
146146
} else {
147-
cs, err = s.HostCPU(clistat.Prefix(prefixArg))
147+
cs, err = s.HostCPU()
148148
}
149149
if err != nil {
150150
return err
@@ -164,7 +164,6 @@ func (*RootCmd) statCPU(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
164164

165165
func (*RootCmd) statMem(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
166166
var hostArg bool
167-
var prefixArg string
168167
formatter := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
169168
cmd := &clibase.Cmd{
170169
Use: "mem",
@@ -175,20 +174,14 @@ func (*RootCmd) statMem(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
175174
Value: clibase.BoolOf(&hostArg),
176175
Description: "Force host memory measurement.",
177176
},
178-
{
179-
Flag: "prefix",
180-
Value: clibase.StringOf(&prefixArg),
181-
Description: "Unit prefix.",
182-
Default: string(clistat.PrefixGibiShort),
183-
},
184177
},
185178
Handler: func(inv *clibase.Invocation) error {
186179
var ms *clistat.Result
187180
var err error
188181
if ok, _ := clistat.IsContainerized(fs); ok && !hostArg {
189-
ms, err = s.ContainerMemory(clistat.Prefix(prefixArg))
182+
ms, err = s.ContainerMemory()
190183
} else {
191-
ms, err = s.HostMemory(clistat.Prefix(prefixArg))
184+
ms, err = s.HostMemory()
192185
}
193186
if err != nil {
194187
return err
@@ -208,7 +201,6 @@ func (*RootCmd) statMem(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
208201

209202
func (*RootCmd) statDisk(s *clistat.Statter) *clibase.Cmd {
210203
var pathArg string
211-
var prefixArg string
212204
formatter := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
213205
cmd := &clibase.Cmd{
214206
Use: "disk",
@@ -220,15 +212,9 @@ func (*RootCmd) statDisk(s *clistat.Statter) *clibase.Cmd {
220212
Description: "Path for which to check disk usage.",
221213
Default: "/",
222214
},
223-
{
224-
Flag: "prefix",
225-
Value: clibase.StringOf(&prefixArg),
226-
Description: "Unit prefix.",
227-
Default: string(clistat.PrefixGibiShort),
228-
},
229215
},
230216
Handler: func(inv *clibase.Invocation) error {
231-
ds, err := s.Disk(pathArg, clistat.Prefix(prefixArg))
217+
ds, err := s.Disk(pathArg)
232218
if err != nil {
233219
return err
234220
}

0 commit comments

Comments
 (0)