mirror of https://github.com/coder/coder.git
fix(cli): stat: explicitly specify resource SI prefix (#8206)
* fix(cli): move to explicitly specifying units * make gen
This commit is contained in:
parent
1558ef52f1
commit
80ef147060
|
@ -84,9 +84,10 @@ func (s *Statter) ContainerCPU() (*Result, error) {
|
|||
}
|
||||
|
||||
r := &Result{
|
||||
Unit: "cores",
|
||||
Used: used2 - used1,
|
||||
Total: ptr.To(total),
|
||||
Unit: "cores",
|
||||
Used: used2 - used1,
|
||||
Total: ptr.To(total),
|
||||
Prefix: PrefixDefault,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
@ -184,20 +185,20 @@ func (s *Statter) cGroupV1CPUUsed() (float64, error) {
|
|||
|
||||
// ContainerMemory returns the memory usage of the container cgroup.
|
||||
// If the system is not containerized, this always returns nil.
|
||||
func (s *Statter) ContainerMemory() (*Result, error) {
|
||||
func (s *Statter) ContainerMemory(p Prefix) (*Result, error) {
|
||||
if ok, err := IsContainerized(s.fs); err != nil || !ok {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
if s.isCGroupV2() {
|
||||
return s.cGroupV2Memory()
|
||||
return s.cGroupV2Memory(p)
|
||||
}
|
||||
|
||||
// Fall back to CGroupv1
|
||||
return s.cGroupV1Memory()
|
||||
return s.cGroupV1Memory(p)
|
||||
}
|
||||
|
||||
func (s *Statter) cGroupV2Memory() (*Result, error) {
|
||||
func (s *Statter) cGroupV2Memory(p Prefix) (*Result, error) {
|
||||
maxUsageBytes, err := readInt64(s.fs, cgroupV2MemoryMaxBytes)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("read memory total: %w", err)
|
||||
|
@ -214,13 +215,14 @@ func (s *Statter) cGroupV2Memory() (*Result, error) {
|
|||
}
|
||||
|
||||
return &Result{
|
||||
Total: ptr.To(float64(maxUsageBytes)),
|
||||
Used: float64(currUsageBytes - inactiveFileBytes),
|
||||
Unit: "B",
|
||||
Total: ptr.To(float64(maxUsageBytes)),
|
||||
Used: float64(currUsageBytes - inactiveFileBytes),
|
||||
Unit: "B",
|
||||
Prefix: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Statter) cGroupV1Memory() (*Result, error) {
|
||||
func (s *Statter) cGroupV1Memory(p Prefix) (*Result, error) {
|
||||
maxUsageBytes, err := readInt64(s.fs, cgroupV1MemoryMaxUsageBytes)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("read memory total: %w", err)
|
||||
|
@ -239,9 +241,10 @@ func (s *Statter) cGroupV1Memory() (*Result, error) {
|
|||
|
||||
// Total memory used is usage - total_inactive_file
|
||||
return &Result{
|
||||
Total: ptr.To(float64(maxUsageBytes)),
|
||||
Used: float64(usageBytes - totalInactiveFileBytes),
|
||||
Unit: "B",
|
||||
Total: ptr.To(float64(maxUsageBytes)),
|
||||
Used: float64(usageBytes - totalInactiveFileBytes),
|
||||
Unit: "B",
|
||||
Prefix: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
// Disk returns the disk usage of the given path.
|
||||
// If path is empty, it returns the usage of the root directory.
|
||||
func (*Statter) Disk(path string) (*Result, error) {
|
||||
func (*Statter) Disk(p Prefix, path string) (*Result, error) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
|
@ -22,5 +22,6 @@ func (*Statter) Disk(path string) (*Result, error) {
|
|||
r.Total = ptr.To(float64(stat.Blocks * uint64(stat.Bsize)))
|
||||
r.Used = float64(stat.Blocks-stat.Bfree) * float64(stat.Bsize)
|
||||
r.Unit = "B"
|
||||
r.Prefix = p
|
||||
return &r, nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
// Disk returns the disk usage of the given path.
|
||||
// If path is empty, it defaults to C:\
|
||||
func (*Statter) Disk(path string) (*Result, error) {
|
||||
func (*Statter) Disk(p Prefix, path string) (*Result, error) {
|
||||
if path == "" {
|
||||
path = `C:\`
|
||||
}
|
||||
|
@ -31,5 +31,6 @@ func (*Statter) Disk(path string) (*Result, error) {
|
|||
r.Total = ptr.To(float64(totalBytes))
|
||||
r.Used = float64(totalBytes - freeBytes)
|
||||
r.Unit = "B"
|
||||
r.Prefix = p
|
||||
return &r, nil
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package clistat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/elastic/go-sysinfo"
|
||||
"github.com/spf13/afero"
|
||||
"golang.org/x/xerrors"
|
||||
|
@ -17,15 +15,65 @@ import (
|
|||
sysinfotypes "github.com/elastic/go-sysinfo/types"
|
||||
)
|
||||
|
||||
// Prefix is a scale multiplier for a result.
|
||||
// Used when creating a human-readable representation.
|
||||
type Prefix float64
|
||||
|
||||
const (
|
||||
PrefixDefault = 1.0
|
||||
PrefixKibi = 1024.0
|
||||
PrefixMebi = PrefixKibi * 1024.0
|
||||
PrefixGibi = PrefixMebi * 1024.0
|
||||
PrefixTebi = PrefixGibi * 1024.0
|
||||
)
|
||||
|
||||
var (
|
||||
PrefixHumanKibi = "Ki"
|
||||
PrefixHumanMebi = "Mi"
|
||||
PrefixHumanGibi = "Gi"
|
||||
PrefixHumanTebi = "Ti"
|
||||
)
|
||||
|
||||
func (s *Prefix) String() string {
|
||||
switch *s {
|
||||
case PrefixKibi:
|
||||
return "Ki"
|
||||
case PrefixMebi:
|
||||
return "Mi"
|
||||
case PrefixGibi:
|
||||
return "Gi"
|
||||
case PrefixTebi:
|
||||
return "Ti"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func ParsePrefix(s string) Prefix {
|
||||
switch s {
|
||||
case PrefixHumanKibi:
|
||||
return PrefixKibi
|
||||
case PrefixHumanMebi:
|
||||
return PrefixMebi
|
||||
case PrefixHumanGibi:
|
||||
return PrefixGibi
|
||||
case PrefixHumanTebi:
|
||||
return PrefixTebi
|
||||
default:
|
||||
return PrefixDefault
|
||||
}
|
||||
}
|
||||
|
||||
// Result is a generic result type for a statistic.
|
||||
// Total is the total amount of the resource available.
|
||||
// It is nil if the resource is not a finite quantity.
|
||||
// Unit is the unit of the resource.
|
||||
// Used is the amount of the resource used.
|
||||
type Result struct {
|
||||
Total *float64 `json:"total"`
|
||||
Unit string `json:"unit"`
|
||||
Used float64 `json:"used"`
|
||||
Total *float64 `json:"total"`
|
||||
Unit string `json:"unit"`
|
||||
Used float64 `json:"used"`
|
||||
Prefix Prefix `json:"-"`
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of the result.
|
||||
|
@ -34,38 +82,29 @@ func (r *Result) String() string {
|
|||
return "-"
|
||||
}
|
||||
|
||||
var usedDisplay, totalDisplay string
|
||||
var usedScaled, totalScaled float64
|
||||
var usedPrefix, totalPrefix string
|
||||
usedScaled, usedPrefix = humanize.ComputeSI(r.Used)
|
||||
usedDisplay = humanizeFloat(usedScaled)
|
||||
if r.Total != (*float64)(nil) {
|
||||
totalScaled, totalPrefix = humanize.ComputeSI(*r.Total)
|
||||
totalDisplay = humanizeFloat(totalScaled)
|
||||
scale := 1.0
|
||||
if r.Prefix != 0.0 {
|
||||
scale = float64(r.Prefix)
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
_, _ = sb.WriteString(usedDisplay)
|
||||
|
||||
// If the unit prefixes of the used and total values are different,
|
||||
// display the used value's prefix to avoid confusion.
|
||||
if usedPrefix != totalPrefix || totalDisplay == "" {
|
||||
_, _ = sb.WriteString(" ")
|
||||
_, _ = sb.WriteString(usedPrefix)
|
||||
_, _ = sb.WriteString(r.Unit)
|
||||
}
|
||||
|
||||
if totalDisplay != "" {
|
||||
var usedScaled, totalScaled float64
|
||||
usedScaled = r.Used / scale
|
||||
_, _ = sb.WriteString(humanizeFloat(usedScaled))
|
||||
if r.Total != (*float64)(nil) {
|
||||
_, _ = sb.WriteString("/")
|
||||
_, _ = sb.WriteString(totalDisplay)
|
||||
_, _ = sb.WriteString(" ")
|
||||
_, _ = sb.WriteString(totalPrefix)
|
||||
_, _ = sb.WriteString(r.Unit)
|
||||
totalScaled = *r.Total / scale
|
||||
_, _ = sb.WriteString(humanizeFloat(totalScaled))
|
||||
}
|
||||
|
||||
if r.Total != nil && *r.Total != 0.0 {
|
||||
_, _ = sb.WriteString(" ")
|
||||
_, _ = sb.WriteString(r.Prefix.String())
|
||||
_, _ = sb.WriteString(r.Unit)
|
||||
|
||||
if r.Total != (*float64)(nil) && *r.Total > 0 {
|
||||
_, _ = sb.WriteString(" (")
|
||||
_, _ = sb.WriteString(fmt.Sprintf("%.0f", r.Used/(*r.Total)*100.0))
|
||||
pct := r.Used / *r.Total * 100.0
|
||||
_, _ = sb.WriteString(strconv.FormatFloat(pct, 'f', 0, 64))
|
||||
_, _ = sb.WriteString("%)")
|
||||
}
|
||||
|
||||
|
@ -153,8 +192,9 @@ func New(opts ...Option) (*Statter, error) {
|
|||
// Units are in "cores".
|
||||
func (s *Statter) HostCPU() (*Result, error) {
|
||||
r := &Result{
|
||||
Unit: "cores",
|
||||
Total: ptr.To(float64(s.nproc)),
|
||||
Unit: "cores",
|
||||
Total: ptr.To(float64(s.nproc)),
|
||||
Prefix: PrefixDefault,
|
||||
}
|
||||
c1, err := s.hi.CPUTime()
|
||||
if err != nil {
|
||||
|
@ -177,9 +217,10 @@ func (s *Statter) HostCPU() (*Result, error) {
|
|||
}
|
||||
|
||||
// HostMemory returns the memory usage of the host, in gigabytes.
|
||||
func (s *Statter) HostMemory() (*Result, error) {
|
||||
func (s *Statter) HostMemory(p Prefix) (*Result, error) {
|
||||
r := &Result{
|
||||
Unit: "B",
|
||||
Unit: "B",
|
||||
Prefix: p,
|
||||
}
|
||||
hm, err := s.hi.Memory()
|
||||
if err != nil {
|
||||
|
|
|
@ -33,19 +33,19 @@ func TestResultString(t *testing.T) {
|
|||
Result: Result{Used: 12.34, Total: nil, Unit: ""},
|
||||
},
|
||||
{
|
||||
Expected: "1.54 kB",
|
||||
Result: Result{Used: 1536, Total: nil, Unit: "B"},
|
||||
Expected: "1.5 KiB",
|
||||
Result: Result{Used: 1536, Total: nil, Unit: "B", Prefix: PrefixKibi},
|
||||
},
|
||||
{
|
||||
Expected: "1.23 things",
|
||||
Result: Result{Used: 1.234, Total: nil, Unit: "things"},
|
||||
},
|
||||
{
|
||||
Expected: "1 B/100 TB (0%)",
|
||||
Result: Result{Used: 1, Total: ptr.To(1000 * 1000 * 1000 * 1000 * 100.0), Unit: "B"},
|
||||
Expected: "0/100 TiB (0%)",
|
||||
Result: Result{Used: 1, Total: ptr.To(100.0 * float64(PrefixTebi)), Unit: "B", Prefix: PrefixTebi},
|
||||
},
|
||||
{
|
||||
Expected: "500 mcores/8 cores (6%)",
|
||||
Expected: "0.5/8 cores (6%)",
|
||||
Result: Result{Used: 0.5, Total: ptr.To(8.0), Unit: "cores"},
|
||||
},
|
||||
} {
|
||||
|
@ -76,7 +76,7 @@ func TestStatter(t *testing.T) {
|
|||
|
||||
t.Run("HostMemory", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mem, err := s.HostMemory()
|
||||
mem, err := s.HostMemory(PrefixDefault)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, mem.Used)
|
||||
assert.NotZero(t, mem.Total)
|
||||
|
@ -85,7 +85,7 @@ func TestStatter(t *testing.T) {
|
|||
|
||||
t.Run("HostDisk", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
disk, err := s.Disk("") // default to home dir
|
||||
disk, err := s.Disk(PrefixDefault, "") // default to home dir
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, disk.Used)
|
||||
assert.NotZero(t, disk.Total)
|
||||
|
@ -159,7 +159,7 @@ func TestStatter(t *testing.T) {
|
|||
fs := initFS(t, fsContainerCgroupV1)
|
||||
s, err := New(WithFS(fs), withNoWait)
|
||||
require.NoError(t, err)
|
||||
mem, err := s.ContainerMemory()
|
||||
mem, err := s.ContainerMemory(PrefixDefault)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, mem)
|
||||
assert.Equal(t, 268435456.0, mem.Used)
|
||||
|
@ -211,7 +211,7 @@ func TestStatter(t *testing.T) {
|
|||
fs := initFS(t, fsContainerCgroupV2)
|
||||
s, err := New(WithFS(fs), withNoWait)
|
||||
require.NoError(t, err)
|
||||
mem, err := s.ContainerMemory()
|
||||
mem, err := s.ContainerMemory(PrefixDefault)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, mem)
|
||||
assert.Equal(t, 268435456.0, mem.Used)
|
||||
|
|
45
cli/stat.go
45
cli/stat.go
|
@ -75,7 +75,7 @@ func (r *RootCmd) stat() *clibase.Cmd {
|
|||
}
|
||||
|
||||
// Host-level stats
|
||||
ms, err := st.HostMemory()
|
||||
ms, err := st.HostMemory(clistat.PrefixGibi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ func (r *RootCmd) stat() *clibase.Cmd {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ds, err := st.Disk(home)
|
||||
ds, err := st.Disk(clistat.PrefixGibi, home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func (r *RootCmd) stat() *clibase.Cmd {
|
|||
}
|
||||
sr.ContainerCPU = cs
|
||||
|
||||
ms, err := st.ContainerMemory()
|
||||
ms, err := st.ContainerMemory(clistat.PrefixGibi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -120,7 +120,6 @@ func (r *RootCmd) stat() *clibase.Cmd {
|
|||
|
||||
func (*RootCmd) statCPU(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
|
||||
var hostArg bool
|
||||
var prefixArg string
|
||||
formatter := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "cpu",
|
||||
|
@ -131,12 +130,6 @@ func (*RootCmd) statCPU(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
|
|||
Value: clibase.BoolOf(&hostArg),
|
||||
Description: "Force host CPU measurement.",
|
||||
},
|
||||
{
|
||||
Flag: "prefix",
|
||||
Value: clibase.StringOf(&prefixArg),
|
||||
Description: "Unit prefix.",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
var cs *clistat.Result
|
||||
|
@ -164,6 +157,7 @@ func (*RootCmd) statCPU(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
|
|||
|
||||
func (*RootCmd) statMem(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
|
||||
var hostArg bool
|
||||
var prefixArg string
|
||||
formatter := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "mem",
|
||||
|
@ -174,14 +168,26 @@ func (*RootCmd) statMem(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
|
|||
Value: clibase.BoolOf(&hostArg),
|
||||
Description: "Force host memory measurement.",
|
||||
},
|
||||
{
|
||||
Description: "SI Prefix for memory measurement.",
|
||||
Default: clistat.PrefixHumanGibi,
|
||||
Flag: "prefix",
|
||||
Value: clibase.EnumOf(&prefixArg,
|
||||
clistat.PrefixHumanKibi,
|
||||
clistat.PrefixHumanMebi,
|
||||
clistat.PrefixHumanGibi,
|
||||
clistat.PrefixHumanTebi,
|
||||
),
|
||||
},
|
||||
},
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
pfx := clistat.ParsePrefix(prefixArg)
|
||||
var ms *clistat.Result
|
||||
var err error
|
||||
if ok, _ := clistat.IsContainerized(fs); ok && !hostArg {
|
||||
ms, err = s.ContainerMemory()
|
||||
ms, err = s.ContainerMemory(pfx)
|
||||
} else {
|
||||
ms, err = s.HostMemory()
|
||||
ms, err = s.HostMemory(pfx)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -201,6 +207,7 @@ func (*RootCmd) statMem(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
|
|||
|
||||
func (*RootCmd) statDisk(s *clistat.Statter) *clibase.Cmd {
|
||||
var pathArg string
|
||||
var prefixArg string
|
||||
formatter := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "disk",
|
||||
|
@ -212,9 +219,21 @@ func (*RootCmd) statDisk(s *clistat.Statter) *clibase.Cmd {
|
|||
Description: "Path for which to check disk usage.",
|
||||
Default: "/",
|
||||
},
|
||||
{
|
||||
Flag: "prefix",
|
||||
Default: clistat.PrefixHumanGibi,
|
||||
Description: "SI Prefix for disk measurement.",
|
||||
Value: clibase.EnumOf(&prefixArg,
|
||||
clistat.PrefixHumanKibi,
|
||||
clistat.PrefixHumanMebi,
|
||||
clistat.PrefixHumanGibi,
|
||||
clistat.PrefixHumanTebi,
|
||||
),
|
||||
},
|
||||
},
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
ds, err := s.Disk(pathArg)
|
||||
pfx := clistat.ParsePrefix(prefixArg)
|
||||
ds, err := s.Disk(pfx, pathArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,8 +9,5 @@ Show CPU usage, in cores.
|
|||
-o, --output string (default: text)
|
||||
Output format. Available formats: text, json.
|
||||
|
||||
--prefix string
|
||||
Unit prefix.
|
||||
|
||||
---
|
||||
Run `coder --help` for a list of global options.
|
||||
|
|
|
@ -9,5 +9,8 @@ Show disk usage, in gigabytes.
|
|||
--path string (default: /)
|
||||
Path for which to check disk usage.
|
||||
|
||||
--prefix Ki|Mi|Gi|Ti (default: Gi)
|
||||
SI Prefix for disk measurement.
|
||||
|
||||
---
|
||||
Run `coder --help` for a list of global options.
|
||||
|
|
|
@ -9,5 +9,8 @@ Show memory usage, in gigabytes.
|
|||
-o, --output string (default: text)
|
||||
Output format. Available formats: text, json.
|
||||
|
||||
--prefix Ki|Mi|Gi|Ti (default: Gi)
|
||||
SI Prefix for memory measurement.
|
||||
|
||||
---
|
||||
Run `coder --help` for a list of global options.
|
||||
|
|
|
@ -28,11 +28,3 @@ Force host CPU measurement.
|
|||
| Default | <code>text</code> |
|
||||
|
||||
Output format. Available formats: text, json.
|
||||
|
||||
### --prefix
|
||||
|
||||
| | |
|
||||
| ---- | ------------------- |
|
||||
| Type | <code>string</code> |
|
||||
|
||||
Unit prefix.
|
||||
|
|
|
@ -29,3 +29,12 @@ Output format. Available formats: text, json.
|
|||
| Default | <code>/</code> |
|
||||
|
||||
Path for which to check disk usage.
|
||||
|
||||
### --prefix
|
||||
|
||||
| | |
|
||||
| ------- | --------------- | --- | --- | ---------- |
|
||||
| Type | <code>enum[Ki | Mi | Gi | Ti]</code> |
|
||||
| Default | <code>Gi</code> |
|
||||
|
||||
SI Prefix for disk measurement.
|
||||
|
|
|
@ -28,3 +28,12 @@ Force host memory measurement.
|
|||
| Default | <code>text</code> |
|
||||
|
||||
Output format. Available formats: text, json.
|
||||
|
||||
### --prefix
|
||||
|
||||
| | |
|
||||
| ------- | --------------- | --- | --- | ---------- |
|
||||
| Type | <code>enum[Ki | Mi | Gi | Ti]</code> |
|
||||
| Default | <code>Gi</code> |
|
||||
|
||||
SI Prefix for memory measurement.
|
||||
|
|
Loading…
Reference in New Issue