@@ -2,23 +2,28 @@ package cli
2
2
3
3
import (
4
4
"fmt"
5
- "math"
6
5
"runtime"
7
6
"strconv"
8
7
"strings"
9
8
"time"
10
9
11
10
"github.com/elastic/go-sysinfo"
11
+ sysinfotypes "github.com/elastic/go-sysinfo/types"
12
12
13
13
"github.com/coder/coder/cli/clibase"
14
14
"github.com/coder/coder/cli/cliui"
15
15
)
16
16
17
17
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
+ }
18
23
var (
19
24
sampleInterval time.Duration
20
25
formatter = cliui .NewOutputFormatter (
21
- cliui .TextFormat ( ),
26
+ cliui .TableFormat ([] statsRow {}, defaultCols ),
22
27
cliui .JSONFormat (),
23
28
)
24
29
)
@@ -35,11 +40,17 @@ func (*RootCmd) stat() *clibase.Cmd {
35
40
},
36
41
},
37
42
Handler : func (inv * clibase.Invocation ) error {
38
- stats , err := newStats ( sampleInterval )
43
+ hi , err := sysinfo . Host ( )
39
44
if err != nil {
40
45
return err
41
46
}
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 })
43
54
if err != nil {
44
55
return err
45
56
}
@@ -51,96 +62,63 @@ func (*RootCmd) stat() *clibase.Cmd {
51
62
return cmd
52
63
}
53
64
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 ) {
82
66
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" ,
93
69
}
94
- // elapsed := time.Since(start)
95
- // numTicks := elapsed / tickInterval
96
- cts1 , err := h1 .CPUTime ()
70
+ c1 , err := hi .CPUTime ()
97
71
if err != nil {
98
- return s , err
72
+ return nil , err
99
73
}
100
- cts2 , err := h2 .CPUTime ()
74
+ <- time .After (interval )
75
+ c2 , err := hi .CPUTime ()
101
76
if err != nil {
102
- return s , err
77
+ return nil , err
103
78
}
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
112
82
used := total - idle
113
83
scaleFactor := nproc / total .Seconds ()
114
- s .HostCPU .Used = used .Seconds () * scaleFactor
115
- s .HostCPU .Unit = "cores"
116
-
84
+ s .Used = used .Seconds () * scaleFactor
117
85
return s , nil
118
86
}
119
87
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
+
120
96
type stat struct {
121
- Used float64 `json:"used"`
122
97
Total float64 `json:"total"`
123
98
Unit string `json:"unit"`
99
+ Used float64 `json:"used"`
124
100
}
125
101
126
102
func (s * stat ) String () string {
103
+ if s == nil {
104
+ return "-"
105
+ }
127
106
var sb strings.Builder
128
107
_ , _ = sb .WriteString (strconv .FormatFloat (s .Used , 'f' , 1 , 64 ))
129
108
_ , _ = sb .WriteString ("/" )
130
109
_ , _ = sb .WriteString (strconv .FormatFloat (s .Total , 'f' , 1 , 64 ))
131
110
_ , _ = sb .WriteString (" " )
132
111
if s .Unit != "" {
133
112
_ , _ = 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
142
113
}
143
- _ , _ = sb .WriteString (strconv .FormatFloat (pct , 'f' , 1 , 64 ))
144
- _ , _ = sb .WriteString ("%)" )
145
114
return sb .String ()
146
115
}
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
+ }
0 commit comments