Skip to content

feat: add coder stat command #6938

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
r.scaletest(),
r.gitssh(),
r.vscodeSSH(),
r.stat(),
}
}

Expand Down Expand Up @@ -679,6 +680,15 @@ func (r *RootCmd) checkVersions(i *clibase.Invocation, client *codersdk.Client)
return nil
}

// verboseStderr returns the stderr writer if verbose is set, otherwise
// it returns a discard writer.
func (r *RootCmd) verboseStderr(inv *clibase.Invocation) io.Writer {
if r.verbose {
return inv.Stderr
}
return io.Discard
}

func (r *RootCmd) checkWarnings(i *clibase.Invocation, client *codersdk.Client) error {
if r.noFeatureWarning {
return nil
Expand Down
170 changes: 170 additions & 0 deletions cli/stat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package cli

import (
"bufio"
"bytes"
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/shirou/gopsutil/cpu"
"golang.org/x/xerrors"

"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/cli/cliui"
)

type statCmd struct {
*RootCmd
watch time.Duration
}

func (r *RootCmd) stat() *clibase.Cmd {
c := &clibase.Cmd{
Use: "stat <type> [flags...]",
Short: "Display local system resource usage statistics",
Long: "stat calls can be used as the script for agent metadata blocks.",
}
sc := statCmd{RootCmd: r}
c.Options.Add(
clibase.Option{
Flag: "watch",
FlagShorthand: "w",
Description: "Continuously display the statistic on the given interval.",
Value: clibase.DurationOf(&sc.watch),
},
)
c.AddSubcommands(
sc.cpu(),
)
sc.setWatchLoops(c)
return c
}

func (sc *statCmd) setWatchLoops(c *clibase.Cmd) {
for _, cmd := range c.Children {
innerHandler := cmd.Handler
cmd.Handler = func(inv *clibase.Invocation) error {
if sc.watch == 0 {
return innerHandler(inv)
}

ticker := time.NewTicker(sc.watch)
defer ticker.Stop()

for range ticker.C {
if err := innerHandler(inv); err != nil {
_, _ = fmt.Fprintf(inv.Stderr, "error: %v", err)
}
}
panic("unreachable")
}
}
}

func cpuUsageFromCgroup(interval time.Duration) (float64, error) {
cgroup, err := os.OpenFile("/proc/self/cgroup", os.O_RDONLY, 0)
if err != nil {
return 0, err
}
defer cgroup.Close()
sc := bufio.NewScanner(cgroup)

var groupDir string
for sc.Scan() {
fields := strings.Split(sc.Text(), ":")
if len(fields) != 3 {
continue
}
if fields[1] != "cpu,cpuacct" {
continue
}
groupDir = fields[2]
break
}

if groupDir == "" {
return 0, xerrors.New("no cpu cgroup found")
}

cpuAcct := func() (int64, error) {
path := fmt.Sprintf("/sys/fs/cgroup/cpu,cpuacct/%s/cpuacct.usage", groupDir)
Copy link
Member

Choose a reason for hiding this comment

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

@bpmct go pkg doesn't handle this on its own, and we need to specify the path of the cgroup, which is kernel/release dependent.


byt, err := os.ReadFile(
path,
)
if err != nil {
return 0, err
}

return strconv.ParseInt(string(bytes.TrimSpace(byt)), 10, 64)
}

stat1, err := cpuAcct()
if err != nil {
return 0, err
}

time.Sleep(interval)

stat2, err := cpuAcct()
if err != nil {
return 0, err
}

var (
cpuTime = time.Duration(stat2 - stat1)
realTime = interval
)

ncpu, err := cpu.Counts(true)
if err != nil {
return 0, err
}

return (cpuTime.Seconds() / realTime.Seconds()) * 100 / float64(ncpu), nil
}

//nolint:revive
func (sc *statCmd) cpu() *clibase.Cmd {
var interval time.Duration
c := &clibase.Cmd{
Use: "cpu-usage",
Aliases: []string{"cu"},
Short: "Display the system's cpu usage",
Long: "If inside a cgroup (e.g. docker container), the cpu usage is ",
Handler: func(inv *clibase.Invocation) error {
if sc.watch != 0 {
interval = sc.watch
}

r, err := cpuUsageFromCgroup(interval)
if err != nil {
cliui.Infof(sc.verboseStderr(inv), "cgroup error: %+v", err)

// Use standard methods if cgroup method fails.
rs, err := cpu.Percent(interval, false)
if err != nil {
return err
}
r = rs[0]
}

_, _ = fmt.Fprintf(inv.Stdout, "%02.0f\n", r)

return nil
},
Options: []clibase.Option{
{
Flag: "interval",
FlagShorthand: "i",
Description: `The sample collection interval. If --watch is set, it overrides this value.`,
Default: "0s",
Value: clibase.DurationOf(&interval),
},
},
}
return c
}
24 changes: 24 additions & 0 deletions cli/stat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cli_test

import (
"bytes"
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/cli/clitest"
)

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

t.Run("cpu", func(t *testing.T) {
t.Parallel()
inv, _ := clitest.New(t, "stat", "cpu")
var out bytes.Buffer
inv.Stdout = &out
clitest.Run(t, inv)

require.Regexp(t, `^[\d]{2}\n$`, out.String())
})
}
1 change: 1 addition & 0 deletions cli/testdata/coder_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +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 Display local system resource usage statistics
state Manually manage Terraform state to fix broken workspaces
stop Stop a workspace
templates Manage templates
Expand Down
15 changes: 15 additions & 0 deletions cli/testdata/coder_stat_--help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Usage: coder stat [flags] <type> [flags...]

Display local system resource usage statistics

stat calls can be used as the script for agent metadata blocks.

Subcommands
cpu Display the system's cpu usage

Options
-w, --watch duration
Continuously display the statistic on the given interval.

---
Run `coder --help` for a list of global options.
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]

Display the system's cpu usage

If inside a cgroup (e.g. docker container), the cpu usage is

Options
-i, --interval duration (default: 0s)
The sample collection interval. If --watch is set, it overrides this
value.

---
Run `coder --help` for a list of global options.
1 change: 1 addition & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
| [<code>speedtest</code>](./cli/speedtest) | Run upload and download tests from your machine to a workspace |
| [<code>ssh</code>](./cli/ssh) | Start a shell into a workspace |
| [<code>start</code>](./cli/start) | Start a workspace |
| [<code>stat</code>](./cli/stat) | Display local system resource usage statistics |
| [<code>state</code>](./cli/state) | Manually manage Terraform state to fix broken workspaces |
| [<code>stop</code>](./cli/stop) | Stop a workspace |
| [<code>templates</code>](./cli/templates) | Manage templates |
Expand Down
33 changes: 33 additions & 0 deletions docs/cli/stat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->

# stat

Display local system resource usage statistics

## Usage

```console
coder stat [flags] <type> [flags...]
```

## Description

```console
stat calls can be used as the script for agent metadata blocks.
```

## Subcommands

| Name | Purpose |
| ------------------------------ | ------------------------------ |
| [<code>cpu</code>](./stat_cpu) | Display the system's cpu usage |

## Options

### -w, --watch

| | |
| ---- | --------------------- |
| Type | <code>duration</code> |

Continuously display the statistic on the given interval.
28 changes: 28 additions & 0 deletions docs/cli/stat_cpu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->

# stat cpu

Display the system's cpu usage

## Usage

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

## Description

```console
If inside a cgroup (e.g. docker container), the cpu usage is
```

## Options

### -i, --interval

| | |
| ------- | --------------------- |
| Type | <code>duration</code> |
| Default | <code>0s</code> |

The sample collection interval. If --watch is set, it overrides this value.
10 changes: 10 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,16 @@
"description": "Start a workspace",
"path": "cli/start.md"
},
{
"title": "stat",
"description": "Display local system resource usage statistics",
"path": "cli/stat.md"
},
{
"title": "stat cpu",
"description": "Display the system's cpu usage",
"path": "cli/stat_cpu.md"
},
{
"title": "state",
"description": "Manually manage Terraform state to fix broken workspaces",
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ require (
github.com/prometheus/client_golang v1.14.0
github.com/quasilyte/go-ruleguard/dsl v0.3.21
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/afero v1.9.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
Expand Down Expand Up @@ -187,6 +188,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/text v0.8.0 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde // indirect
)
Expand Down
Loading