Skip to content

Commit d6029b4

Browse files
committed
cpu working on mac
1 parent 5db9006 commit d6029b4

File tree

3 files changed

+82
-79
lines changed

3 files changed

+82
-79
lines changed

cli/stat.go

Lines changed: 48 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,28 @@ package cli
22

33
import (
44
"fmt"
5-
"math"
65
"runtime"
76
"strconv"
87
"strings"
98
"time"
109

1110
"github.com/elastic/go-sysinfo"
11+
sysinfotypes "github.com/elastic/go-sysinfo/types"
1212

1313
"github.com/coder/coder/cli/clibase"
1414
"github.com/coder/coder/cli/cliui"
1515
)
1616

1717
func (*RootCmd) stat() *clibase.Cmd {
18+
defaultCols := []string{"host_cpu", "host_memory", "disk"}
19+
if isContainerized() {
20+
// If running in a container, we assume that users want to see these first. Prepend.
21+
defaultCols = append([]string{"container_cpu", "container_memory"}, defaultCols...)
22+
}
1823
var (
1924
sampleInterval time.Duration
2025
formatter = cliui.NewOutputFormatter(
21-
cliui.TextFormat(),
26+
cliui.TableFormat([]statsRow{}, defaultCols),
2227
cliui.JSONFormat(),
2328
)
2429
)
@@ -35,11 +40,17 @@ func (*RootCmd) stat() *clibase.Cmd {
3540
},
3641
},
3742
Handler: func(inv *clibase.Invocation) error {
38-
stats, err := newStats(sampleInterval)
43+
hi, err := sysinfo.Host()
3944
if err != nil {
4045
return err
4146
}
42-
out, err := formatter.Format(inv.Context(), stats)
47+
sr := statsRow{}
48+
if cs, err := statCPU(hi, sampleInterval); err != nil {
49+
return err
50+
} else {
51+
sr.HostCPU = cs
52+
}
53+
out, err := formatter.Format(inv.Context(), []statsRow{sr})
4354
if err != nil {
4455
return err
4556
}
@@ -51,96 +62,63 @@ func (*RootCmd) stat() *clibase.Cmd {
5162
return cmd
5263
}
5364

54-
type stats struct {
55-
HostCPU stat `json:"cpu_host"`
56-
HostMemory stat `json:"mem_host"`
57-
Disk stat `json:"disk"`
58-
InContainer bool `json:"in_container,omitempty"`
59-
ContainerCPU stat `json:"cpu_container,omitempty"`
60-
ContainerMemory stat `json:"mem_container,omitempty"`
61-
}
62-
63-
func (s *stats) String() string {
64-
var sb strings.Builder
65-
sb.WriteString(s.HostCPU.String())
66-
sb.WriteString("\n")
67-
sb.WriteString(s.HostMemory.String())
68-
sb.WriteString("\n")
69-
sb.WriteString(s.Disk.String())
70-
sb.WriteString("\n")
71-
if s.InContainer {
72-
sb.WriteString(s.ContainerCPU.String())
73-
sb.WriteString("\n")
74-
sb.WriteString(s.ContainerMemory.String())
75-
sb.WriteString("\n")
76-
}
77-
return sb.String()
78-
}
79-
80-
func newStats(dur time.Duration) (stats, error) {
81-
var s stats
65+
func statCPU(hi sysinfotypes.Host, interval time.Duration) (*stat, error) {
8266
nproc := float64(runtime.NumCPU())
83-
// start := time.Now()
84-
// ticksPerDur := dur / tickInterval
85-
h1, err := sysinfo.Host()
86-
if err != nil {
87-
return s, err
88-
}
89-
<-time.After(dur)
90-
h2, err := sysinfo.Host()
91-
if err != nil {
92-
return s, err
67+
s := &stat{
68+
Unit: "cores",
9369
}
94-
// elapsed := time.Since(start)
95-
// numTicks := elapsed / tickInterval
96-
cts1, err := h1.CPUTime()
70+
c1, err := hi.CPUTime()
9771
if err != nil {
98-
return s, err
72+
return nil, err
9973
}
100-
cts2, err := h2.CPUTime()
74+
<-time.After(interval)
75+
c2, err := hi.CPUTime()
10176
if err != nil {
102-
return s, err
77+
return nil, err
10378
}
104-
// Assuming the total measured should add up to $(nproc) "cores",
105-
// we determine a scaling factor such that scaleFactor * total = nproc.
106-
// We then calculate used as the total time spent idle, and multiply
107-
// that by scaleFactor to give a rough approximation of how busy the
108-
// CPU(s) were.
109-
s.HostCPU.Total = nproc
110-
total := (cts2.Total() - cts1.Total())
111-
idle := (cts2.Idle - cts1.Idle)
79+
s.Total = nproc
80+
total := c2.Total() - c1.Total()
81+
idle := c2.Idle - c1.Idle
11282
used := total - idle
11383
scaleFactor := nproc / total.Seconds()
114-
s.HostCPU.Used = used.Seconds() * scaleFactor
115-
s.HostCPU.Unit = "cores"
116-
84+
s.Used = used.Seconds() * scaleFactor
11785
return s, nil
11886
}
11987

88+
type statsRow struct {
89+
HostCPU *stat `json:"host_cpu" table:"host_cpu,default_sort"`
90+
HostMemory *stat `json:"host_memory" table:"host_memory"`
91+
Disk *stat `json:"disk" table:"disk"`
92+
ContainerCPU *stat `json:"container_cpu" table:"container_cpu"`
93+
ContainerMemory *stat `json:"container_memory" table:"container_memory"`
94+
}
95+
12096
type stat struct {
121-
Used float64 `json:"used"`
12297
Total float64 `json:"total"`
12398
Unit string `json:"unit"`
99+
Used float64 `json:"used"`
124100
}
125101

126102
func (s *stat) String() string {
103+
if s == nil {
104+
return "-"
105+
}
127106
var sb strings.Builder
128107
_, _ = sb.WriteString(strconv.FormatFloat(s.Used, 'f', 1, 64))
129108
_, _ = sb.WriteString("/")
130109
_, _ = sb.WriteString(strconv.FormatFloat(s.Total, 'f', 1, 64))
131110
_, _ = sb.WriteString(" ")
132111
if s.Unit != "" {
133112
_, _ = sb.WriteString(s.Unit)
134-
_, _ = sb.WriteString(" ")
135-
}
136-
_, _ = sb.WriteString("(")
137-
var pct float64
138-
if s.Total == 0 {
139-
pct = math.NaN()
140-
} else {
141-
pct = s.Used / s.Total * 100
142113
}
143-
_, _ = sb.WriteString(strconv.FormatFloat(pct, 'f', 1, 64))
144-
_, _ = sb.WriteString("%)")
145114
return sb.String()
146115
}
116+
117+
func isContainerized() bool {
118+
hi, err := sysinfo.Host()
119+
if err != nil {
120+
// If we can't get the host info, we have other issues.
121+
panic(err)
122+
}
123+
return hi.Info().Containerized != nil && *hi.Info().Containerized
124+
}

cli/stat_internal_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ func TestStatString(t *testing.T) {
1212
Stat stat
1313
}{
1414
{
15-
Expected: "1.2/3.4 quatloos (50%)",
16-
Stat: stat{Used: 1.2, Total: 3.4, Unit: "quatloos"},
15+
Expected: "1.2/5.7 quatloos",
16+
Stat: stat{Used: 1.234, Total: 5.678, Unit: "quatloos"},
1717
},
1818
{
19-
Expected: "0/0 HP (NaN%)",
19+
Expected: "0.0/0.0 HP",
2020
Stat: stat{Used: 0, Total: 0, Unit: "HP"},
2121
},
2222
} {

cli/stat_test.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package cli_test
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
7+
"strings"
68
"testing"
79

810
"github.com/stretchr/testify/require"
@@ -11,8 +13,8 @@ import (
1113
"github.com/coder/coder/testutil"
1214
)
1315

14-
// This just tests that the stat command is recognized and does not output
15-
// an empty string. Actually testing the output of the stat command is
16+
// This just tests that the statRow command is recognized and does not output
17+
// an empty string. Actually testing the output of the stats command is
1618
// fraught with all sorts of fun.
1719
func TestStatCmd(t *testing.T) {
1820
t.Run("JSON", func(t *testing.T) {
@@ -24,17 +26,40 @@ func TestStatCmd(t *testing.T) {
2426
inv.Stdout = buf
2527
err := inv.WithContext(ctx).Run()
2628
require.NoError(t, err)
27-
require.NotEmpty(t, buf.String())
29+
s := buf.String()
30+
require.NotEmpty(t, s)
31+
// Must be valid JSON
32+
tmp := make([]struct{}, 0)
33+
require.NoError(t, json.NewDecoder(strings.NewReader(s)).Decode(&tmp))
2834
})
29-
t.Run("Text", func(t *testing.T) {
35+
t.Run("Table", func(t *testing.T) {
3036
t.Parallel()
3137
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
3238
t.Cleanup(cancel)
33-
inv, _ := clitest.New(t, "stat", "--output=text")
39+
inv, _ := clitest.New(t, "stat", "--output=table")
3440
buf := new(bytes.Buffer)
3541
inv.Stdout = buf
3642
err := inv.WithContext(ctx).Run()
3743
require.NoError(t, err)
38-
require.NotEmpty(t, buf.String())
44+
s := buf.String()
45+
require.NotEmpty(t, s)
46+
require.Contains(t, s, "HOST CPU")
47+
require.Contains(t, s, "HOST MEMORY")
48+
require.Contains(t, s, "DISK")
49+
})
50+
t.Run("Default", func(t *testing.T) {
51+
t.Parallel()
52+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
53+
t.Cleanup(cancel)
54+
inv, _ := clitest.New(t, "stat", "--output=table")
55+
buf := new(bytes.Buffer)
56+
inv.Stdout = buf
57+
err := inv.WithContext(ctx).Run()
58+
require.NoError(t, err)
59+
s := buf.String()
60+
require.NotEmpty(t, s)
61+
require.Contains(t, s, "HOST CPU")
62+
require.Contains(t, s, "HOST MEMORY")
63+
require.Contains(t, s, "DISK")
3964
})
4065
}

0 commit comments

Comments
 (0)