@@ -41,11 +41,12 @@ type statsOptions struct {
41
41
labels map [string ]string
42
42
// output format
43
43
output string
44
+ // live watch
45
+ watch bool
44
46
}
45
47
46
48
var statsCommand = cli.Command {
47
49
Name : "stats" ,
48
- // TODO(random-liu): Support live monitoring of resource usage.
49
50
Usage : "List container(s) resource usage statistics" ,
50
51
SkipArgReorder : true ,
51
52
UseShortOptionHandling : true ,
@@ -77,6 +78,10 @@ var statsCommand = cli.Command{
77
78
Value : 1 ,
78
79
Usage : "Sample duration for CPU usage in seconds" ,
79
80
},
81
+ cli.BoolFlag {
82
+ Name : "watch, w" ,
83
+ Usage : "Watch pod resources" ,
84
+ },
80
85
},
81
86
Action : func (context * cli.Context ) error {
82
87
var err error
@@ -90,6 +95,7 @@ var statsCommand = cli.Command{
90
95
podID : context .String ("pod" ),
91
96
sample : time .Duration (context .Int ("seconds" )) * time .Second ,
92
97
output : context .String ("output" ),
98
+ watch : context .Bool ("watch" ),
93
99
}
94
100
opts .labels , err = parseLabelStringSlice (context .StringSlice ("label" ))
95
101
if err != nil {
@@ -127,14 +133,39 @@ func ContainerStats(client pb.RuntimeServiceClient, opts statsOptions) error {
127
133
request := & pb.ListContainerStatsRequest {
128
134
Filter : filter ,
129
135
}
136
+
137
+ display := newTableDisplay (20 , 1 , 3 , ' ' , 0 )
138
+ if ! opts .watch {
139
+ if err := displayStats (client , request , display , opts ); err != nil {
140
+ return err
141
+ }
142
+ } else {
143
+ for range time .Tick (500 * time .Millisecond ) {
144
+ if err := displayStats (client , request , display , opts ); err != nil {
145
+ return err
146
+ }
147
+ }
148
+ }
149
+
150
+ return nil
151
+ }
152
+
153
+ func getContainerStats (client pb.RuntimeServiceClient , request * pb.ListContainerStatsRequest ) (* pb.ListContainerStatsResponse , error ) {
130
154
logrus .Debugf ("ListContainerStatsRequest: %v" , request )
131
155
r , err := client .ListContainerStats (context .Background (), request )
132
156
logrus .Debugf ("ListContainerResponse: %v" , r )
133
157
if err != nil {
134
- return err
158
+ return nil , err
135
159
}
136
160
sort .Sort (containerStatsByID (r .Stats ))
161
+ return r , nil
162
+ }
137
163
164
+ func displayStats (client pb.RuntimeServiceClient , request * pb.ListContainerStatsRequest , display * display , opts statsOptions ) error {
165
+ r , err := getContainerStats (client , request )
166
+ if err != nil {
167
+ return err
168
+ }
138
169
switch opts .output {
139
170
case "json" :
140
171
return outputProtobufObjAsJSON (r )
@@ -148,48 +179,42 @@ func ContainerStats(client pb.RuntimeServiceClient, opts statsOptions) error {
148
179
149
180
time .Sleep (opts .sample )
150
181
151
- logrus .Debugf ("ListContainerStatsRequest: %v" , request )
152
- r , err = client .ListContainerStats (context .Background (), request )
153
- logrus .Debugf ("ListContainerResponse: %v" , r )
182
+ r , err = getContainerStats (client , request )
154
183
if err != nil {
155
184
return err
156
185
}
157
- sort .Sort (containerStatsByID (r .Stats ))
158
186
159
- display := newTableDisplay (20 , 1 , 3 , ' ' , 0 )
160
- for range time .Tick (500 * time .Millisecond ) {
161
- display .AddRow ([]string {"CONTAINER" , "CPU %" , "MEM" , "DISK" , "INODES" })
162
- for _ , s := range r .GetStats () {
163
- id := getTruncatedID (s .Attributes .Id , "" )
164
- cpu := s .GetCpu ().GetUsageCoreNanoSeconds ().GetValue ()
165
- mem := s .GetMemory ().GetWorkingSetBytes ().GetValue ()
166
- disk := s .GetWritableLayer ().GetUsedBytes ().GetValue ()
167
- inodes := s .GetWritableLayer ().GetInodesUsed ().GetValue ()
168
- if ! opts .all && cpu == 0 && mem == 0 {
169
- // Skip non-running container
170
- continue
171
- }
172
- old , ok := oldStats [s .Attributes .Id ]
173
- if ! ok {
174
- // Skip new container
175
- continue
176
- }
177
- var cpuPerc float64
178
- if cpu != 0 {
179
- // Only generate cpuPerc for running container
180
- duration := s .GetCpu ().GetTimestamp () - old .GetCpu ().GetTimestamp ()
181
- if duration == 0 {
182
- return fmt .Errorf ("cpu stat is not updated during sample" )
183
- }
184
- cpuPerc = float64 (cpu - old .GetCpu ().GetUsageCoreNanoSeconds ().GetValue ()) / float64 (duration ) * 100
187
+ display .AddRow ([]string {columnContainer , columnCPU , columnMemory , columnDisk , columnInodes })
188
+ for _ , s := range r .GetStats () {
189
+ id := getTruncatedID (s .Attributes .Id , "" )
190
+ cpu := s .GetCpu ().GetUsageCoreNanoSeconds ().GetValue ()
191
+ mem := s .GetMemory ().GetWorkingSetBytes ().GetValue ()
192
+ disk := s .GetWritableLayer ().GetUsedBytes ().GetValue ()
193
+ inodes := s .GetWritableLayer ().GetInodesUsed ().GetValue ()
194
+ if ! opts .all && cpu == 0 && mem == 0 {
195
+ // Skip non-running container
196
+ continue
197
+ }
198
+ old , ok := oldStats [s .Attributes .Id ]
199
+ if ! ok {
200
+ // Skip new container
201
+ continue
202
+ }
203
+ var cpuPerc float64
204
+ if cpu != 0 {
205
+ // Only generate cpuPerc for running container
206
+ duration := s .GetCpu ().GetTimestamp () - old .GetCpu ().GetTimestamp ()
207
+ if duration == 0 {
208
+ return fmt .Errorf ("cpu stat is not updated during sample" )
185
209
}
186
- display .AddRow ([]string {id , fmt .Sprintf ("%.2f" , cpuPerc ), units .HumanSize (float64 (mem )),
187
- units .HumanSize (float64 (disk )), fmt .Sprintf ("%d" , inodes )})
188
-
210
+ cpuPerc = float64 (cpu - old .GetCpu ().GetUsageCoreNanoSeconds ().GetValue ()) / float64 (duration ) * 100
189
211
}
190
- display .ClearScreen ()
191
- display .Flush ()
212
+ display .AddRow ([]string {id , fmt .Sprintf ("%.2f" , cpuPerc ), units .HumanSize (float64 (mem )),
213
+ units .HumanSize (float64 (disk )), fmt .Sprintf ("%d" , inodes )})
214
+
192
215
}
216
+ display .ClearScreen ()
217
+ display .Flush ()
193
218
194
219
return nil
195
220
}
0 commit comments