Skip to content

feat(cli): add coder stat command #8005

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

Merged
merged 48 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5db9006
add stat command
johnstcn May 29, 2023
d6029b4
cpu working on mac
johnstcn Jun 7, 2023
18f4942
add stat memory
johnstcn Jun 7, 2023
251fdda
support values with no total
johnstcn Jun 7, 2023
4c081dc
move clistats to its own package
johnstcn Jun 8, 2023
2ba7392
fix container detection to work with sysbox containers
johnstcn Jun 8, 2023
0e1c96a
add cross-platform declaration for IsContainerized()
johnstcn Jun 8, 2023
0f9859e
add a sync.Once to IsContainerized()
johnstcn Jun 8, 2023
a220c7f
make uptime minutes
johnstcn Jun 8, 2023
89f7e8d
lint
johnstcn Jun 8, 2023
c51e245
extract nproc to variable
johnstcn Jun 8, 2023
3528c00
add skeleton of cgroup stuff
johnstcn Jun 8, 2023
7108c6e
initial cgroupv2 cpu implementation
johnstcn Jun 8, 2023
4ef5f24
fix disk_windows
johnstcn Jun 8, 2023
f0f7b6a
add tests for clistats
johnstcn Jun 8, 2023
6a878b9
improve testing
johnstcn Jun 9, 2023
be7ba72
remove unnecessary os-specific implementations now that we have abstr…
johnstcn Jun 12, 2023
3643407
remove uptime stat as it is trivial to implement in bash
johnstcn Jun 12, 2023
1c8943e
implement cgroupv1 cpu
johnstcn Jun 12, 2023
95b8d1f
unskip container memory tests
johnstcn Jun 12, 2023
495b5b0
flesh out tests
johnstcn Jun 13, 2023
fa0c4c6
cgroupv1 memory
johnstcn Jun 13, 2023
70ef79b
improve tests to allow testing cpu used
johnstcn Jun 13, 2023
7eeefc1
refactor cpu usage calc
johnstcn Jun 13, 2023
305675f
fix tests
johnstcn Jun 13, 2023
d1bb322
fix off-by-10 error
johnstcn Jun 13, 2023
eb2bcf6
remove --sample-interval and collect CPU stats in parallel
johnstcn Jun 13, 2023
44edcf3
fmt; gen
johnstcn Jun 13, 2023
0f3254a
make default_cols consistent to avoid ci surprises
johnstcn Jun 13, 2023
edd99f4
fix race condition
johnstcn Jun 13, 2023
49b6861
remove UPTIME from test
johnstcn Jun 13, 2023
69b1904
update golden files
johnstcn Jun 13, 2023
7eb526d
add stat subcommands
johnstcn Jun 14, 2023
665bf7f
allow modifying unit prefixes
johnstcn Jun 14, 2023
6b11a5c
update docs and examples
johnstcn Jun 14, 2023
c1467f0
fix NaN issue for HostCPU
johnstcn Jun 14, 2023
789c6de
avoid blocking on err chan
johnstcn Jun 14, 2023
482db10
add percentages
johnstcn Jun 15, 2023
0775082
remove outdated comments
johnstcn Jun 15, 2023
73debf8
handle counter reset
johnstcn Jun 15, 2023
d0c992a
add test for large difference between used and total
johnstcn Jun 15, 2023
ef7460a
auto-scale precision, limiting to 3 digits
johnstcn Jun 15, 2023
bec527f
automatically scale precision, remove --prefix arg
johnstcn Jun 15, 2023
08adba7
make gen
johnstcn Jun 15, 2023
78f76e7
improve cli tests
johnstcn Jun 15, 2023
9a82882
update go.mod
johnstcn Jun 15, 2023
19c8a80
Merge remote-tracking branch 'origin/main' into cj/coder-stat
johnstcn Jun 15, 2023
eab2530
update go.sum
johnstcn Jun 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add stat subcommands
  • Loading branch information
johnstcn committed Jun 14, 2023
commit 7eb526d9d74a799d1b8f8cbd30d3555b7928041b
138 changes: 128 additions & 10 deletions cli/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,40 @@ import (
"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 (*RootCmd) stat() *clibase.Cmd {
func (r *RootCmd) stat() *clibase.Cmd {
fs := afero.NewReadOnlyFs(afero.NewOsFs())
defaultCols := []string{"host_cpu", "host_memory", "home_disk", "container_cpu", "container_memory"}
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 workspace resource usage.",
Options: clibase.OptionSet{},
Use: "stat",
Short: "Show resource usage for the current workspace.",
Children: []*clibase.Cmd{
r.statCPU(st, fs),
r.statMem(st, fs),
r.statDisk(st, fs),
},
Handler: func(inv *clibase.Invocation) error {
st, err := clistat.New(clistat.WithFS(fs))
if err != nil {
return err
}

var sr statsRow

// Get CPU measurements first.
Expand Down Expand Up @@ -108,6 +118,114 @@ func (*RootCmd) stat() *clibase.Cmd {
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
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.",
},
},
Handler: func(inv *clibase.Invocation) error {
var ms *clistat.Result
var err error
if ok, _ := clistat.IsContainerized(fs); ok && !hostArg {
ms, err = s.ContainerMemory()
} else {
ms, err = s.HostMemory()
}
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, fs afero.Fs) *clibase.Cmd {
var pathArg 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: "/",
},
},
Handler: func(inv *clibase.Invocation) error {
ds, err := s.Disk(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 {
Copy link
Member

Choose a reason for hiding this comment

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

nit: Please consider it to be more like a homework: Network TX, RX :)

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see an immediately obvious way to measure this; I'll address in a follow-up PR.

HostCPU *clistat.Result `json:"host_cpu" table:"host_cpu,default_sort"`
HostMemory *clistat.Result `json:"host_memory" table:"host_memory"`
Expand Down
99 changes: 96 additions & 3 deletions cli/stat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestStatCmd(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "--output=json")
inv, _ := clitest.New(t, "stat", "all", "--output=json")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
Expand All @@ -38,7 +38,7 @@ func TestStatCmd(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "--output=table")
inv, _ := clitest.New(t, "stat", "all", "--output=table")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
Expand All @@ -53,7 +53,7 @@ func TestStatCmd(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat")
inv, _ := clitest.New(t, "stat", "all")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
Expand All @@ -65,3 +65,96 @@ func TestStatCmd(t *testing.T) {
require.Contains(t, s, "HOME DISK")
})
}

func TestStatCPUCmd(t *testing.T) {
t.Parallel()

t.Run("Text", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "cpu", "--output=text")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
s := buf.String()
require.NotEmpty(t, s)
})

t.Run("JSON", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "cpu", "--output=json")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
s := buf.String()
tmp := struct{}{}
require.NoError(t, json.NewDecoder(strings.NewReader(s)).Decode(&tmp))
})
}

func TestStatMemCmd(t *testing.T) {
t.Parallel()

t.Run("Text", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "mem", "--output=text")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
s := buf.String()
require.NotEmpty(t, s)
})

t.Run("JSON", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "mem", "--output=json")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
s := buf.String()
tmp := struct{}{}
require.NoError(t, json.NewDecoder(strings.NewReader(s)).Decode(&tmp))
})
}

func TestStatDiskCmd(t *testing.T) {
t.Parallel()

t.Run("Text", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "disk", "--output=text")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
s := buf.String()
require.NotEmpty(t, s)
})

t.Run("JSON", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
inv, _ := clitest.New(t, "stat", "disk", "--output=json")
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
s := buf.String()
tmp := struct{}{}
require.NoError(t, json.NewDecoder(strings.NewReader(s)).Decode(&tmp))
})
}
2 changes: 1 addition & 1 deletion cli/testdata/coder_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Coder v0.0.0-devel — A tool for provisioning self-hosted development environme
workspace
ssh Start a shell into a workspace
start Start a workspace
stat Show workspace resource usage.
stat Show resource usage for the current workspace.
state Manually manage Terraform state to fix broken workspaces
stop Stop a workspace
templates Manage templates
Expand Down
7 changes: 6 additions & 1 deletion cli/testdata/coder_stat_--help.golden
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Usage: coder stat [flags]

Show workspace resource usage.
Show resource usage for the current workspace.

Subcommands
cpu Show CPU usage, in cores.
disk Show disk usage, in gigabytes.
mem Show memory usage, in gigabytes.

Options
-c, --column string-array (default: host_cpu,host_memory,home_disk,container_cpu,container_memory)
Expand Down
13 changes: 13 additions & 0 deletions cli/testdata/coder_stat_cpu_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Usage: coder stat cpu [flags]

Show CPU usage, in cores.

Options
--host bool
Force host CPU measurement.

-o, --output string (default: text)
Output format. Available formats: text, json.

---
Run `coder --help` for a list of global options.
13 changes: 13 additions & 0 deletions cli/testdata/coder_stat_disk_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Usage: coder stat disk [flags]

Show disk usage, in gigabytes.

Options
-o, --output string (default: text)
Output format. Available formats: text, json.

--path string (default: /)
Path for which to check disk usage.

---
Run `coder --help` for a list of global options.
13 changes: 13 additions & 0 deletions cli/testdata/coder_stat_mem_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Usage: coder stat mem [flags]

Show memory usage, in gigabytes.

Options
--host bool
Force host memory measurement.

-o, --output string (default: text)
Output format. Available formats: text, json.

---
Run `coder --help` for a list of global options.
2 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
| [<code>speedtest</code>](./cli/speedtest.md) | Run upload and download tests from your machine to a workspace |
| [<code>ssh</code>](./cli/ssh.md) | Start a shell into a workspace |
| [<code>start</code>](./cli/start.md) | Start a workspace |
| [<code>stat</code>](./cli/stat.md) | Show workspace resource usage. |
| [<code>stat</code>](./cli/stat.md) | Show resource usage for the current workspace. |
| [<code>state</code>](./cli/state.md) | Manually manage Terraform state to fix broken workspaces |
| [<code>stop</code>](./cli/stop.md) | Stop a workspace |
| [<code>templates</code>](./cli/templates.md) | Manage templates |
Expand Down
10 changes: 9 additions & 1 deletion docs/cli/stat.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

# stat

Show workspace resource usage.
Show resource usage for the current workspace.

## Usage

```console
coder stat [flags]
```

## Subcommands

| Name | Purpose |
| ----------------------------------- | -------------------------------- |
| [<code>cpu</code>](./stat_cpu.md) | Show CPU usage, in cores. |
| [<code>disk</code>](./stat_disk.md) | Show disk usage, in gigabytes. |
| [<code>mem</code>](./stat_mem.md) | Show memory usage, in gigabytes. |

## Options

### -c, --column
Expand Down
Loading