- Notifications
You must be signed in to change notification settings - Fork1k
feat: addcoder 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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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()) | ||
}) | ||
} |
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. | ||
[1mSubcommands[0m | ||
cpu Display the system's cpu usage | ||
[1mOptions[0m | ||
-w, --watch duration | ||
Continuously display the statistic on the given interval. | ||
--- | ||
Run `coder --help` for a list of global options. |
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 | ||
[1mOptions[0m | ||
-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. |
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. |
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. |
Uh oh!
There was an error while loading.Please reload this page.