Files
coder/cli/stat.go
Cian Johnston 80ef147060 fix(cli): stat: explicitly specify resource SI prefix (#8206)
* fix(cli): move to explicitly specifying units

* make gen
2023-06-26 18:06:38 +01:00

261 lines
6.0 KiB
Go

package cli
import (
"fmt"
"os"
"github.com/spf13/afero"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/cli/clistat"
"github.com/coder/coder/cli/cliui"
)
func (r *RootCmd) stat() *clibase.Cmd {
fs := afero.NewReadOnlyFs(afero.NewOsFs())
defaultCols := []string{
"host_cpu",
"host_memory",
"home_disk",
"container_cpu",
"container_memory",
}
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]statsRow{}, defaultCols),
cliui.JSONFormat(),
)
st, err := clistat.New(clistat.WithFS(fs))
if err != nil {
panic(xerrors.Errorf("initialize workspace stats collector: %w", err))
}
cmd := &clibase.Cmd{
Use: "stat",
Short: "Show resource usage for the current workspace.",
Children: []*clibase.Cmd{
r.statCPU(st, fs),
r.statMem(st, fs),
r.statDisk(st),
},
Handler: func(inv *clibase.Invocation) error {
var sr statsRow
// Get CPU measurements first.
hostErr := make(chan error, 1)
containerErr := make(chan error, 1)
go func() {
defer close(hostErr)
cs, err := st.HostCPU()
if err != nil {
hostErr <- err
return
}
sr.HostCPU = cs
}()
go func() {
defer close(containerErr)
if ok, _ := clistat.IsContainerized(fs); !ok {
// don't error if we're not in a container
return
}
cs, err := st.ContainerCPU()
if err != nil {
containerErr <- err
return
}
sr.ContainerCPU = cs
}()
if err := <-hostErr; err != nil {
return err
}
if err := <-containerErr; err != nil {
return err
}
// Host-level stats
ms, err := st.HostMemory(clistat.PrefixGibi)
if err != nil {
return err
}
sr.HostMemory = ms
home, err := os.UserHomeDir()
if err != nil {
return err
}
ds, err := st.Disk(clistat.PrefixGibi, home)
if err != nil {
return err
}
sr.Disk = ds
// Container-only stats.
if ok, err := clistat.IsContainerized(fs); err == nil && ok {
cs, err := st.ContainerCPU()
if err != nil {
return err
}
sr.ContainerCPU = cs
ms, err := st.ContainerMemory(clistat.PrefixGibi)
if err != nil {
return err
}
sr.ContainerMemory = ms
}
out, err := formatter.Format(inv.Context(), []statsRow{sr})
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return cmd
}
func (*RootCmd) statCPU(s *clistat.Statter, fs afero.Fs) *clibase.Cmd {
var hostArg bool
formatter := cliui.NewOutputFormatter(cliui.TextFormat(), cliui.JSONFormat())
cmd := &clibase.Cmd{
Use: "cpu",
Short: "Show CPU usage, in cores.",
Options: clibase.OptionSet{
{
Flag: "host",
Value: clibase.BoolOf(&hostArg),
Description: "Force host CPU measurement.",
},
},
Handler: func(inv *clibase.Invocation) error {
var cs *clistat.Result
var err error
if ok, _ := clistat.IsContainerized(fs); ok && !hostArg {
cs, err = s.ContainerCPU()
} else {
cs, err = s.HostCPU()
}
if err != nil {
return err
}
out, err := formatter.Format(inv.Context(), cs)
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return 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",
Short: "Show memory usage, in gigabytes.",
Options: clibase.OptionSet{
{
Flag: "host",
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(pfx)
} else {
ms, err = s.HostMemory(pfx)
}
if err != nil {
return err
}
out, err := formatter.Format(inv.Context(), ms)
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return 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",
Short: "Show disk usage, in gigabytes.",
Options: clibase.OptionSet{
{
Flag: "path",
Value: clibase.StringOf(&pathArg),
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 {
pfx := clistat.ParsePrefix(prefixArg)
ds, err := s.Disk(pfx, pathArg)
if err != nil {
return err
}
out, err := formatter.Format(inv.Context(), ds)
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return cmd
}
type statsRow struct {
HostCPU *clistat.Result `json:"host_cpu" table:"host_cpu,default_sort"`
HostMemory *clistat.Result `json:"host_memory" table:"host_memory"`
Disk *clistat.Result `json:"home_disk" table:"home_disk"`
ContainerCPU *clistat.Result `json:"container_cpu" table:"container_cpu"`
ContainerMemory *clistat.Result `json:"container_memory" table:"container_memory"`
}