@@ -4,11 +4,14 @@ import (
4
4
"fmt"
5
5
"io"
6
6
"math"
7
+ "os"
7
8
"sort"
8
9
"strings"
9
10
11
+ "github.com/dustin/go-humanize"
10
12
"github.com/muesli/termenv"
11
13
"github.com/olekukonko/tablewriter"
14
+ "gopkg.in/inf.v0"
12
15
"k8s.io/apimachinery/pkg/api/resource"
13
16
"k8s.io/apimachinery/pkg/runtime/schema"
14
17
@@ -141,8 +144,7 @@ const (
141
144
142
145
// Print writes the table to w.
143
146
func (t table ) Print (w io.Writer , flags * Flags ) error {
144
- tw := tablewriter .NewWriter (w )
145
- setKubectlTableFormat (tw )
147
+ tw := newKubectlTableWriter (w )
146
148
147
149
if ! flags .NoHeaders {
148
150
var headers []string
@@ -168,23 +170,140 @@ func (t table) Print(w io.Writer, flags *Flags) error {
168
170
}
169
171
tw .Render ()
170
172
173
+ if flags .ShowStats {
174
+ _ , err := os .Stdout .WriteString ("\n " )
175
+ if err != nil {
176
+ return err
177
+ }
178
+ return t .printStats (w )
179
+ }
171
180
return nil
172
181
}
173
182
174
- // setKubectlTableFormat configures the given writer
175
- // to print according to the Kubectl output format.
176
- func setKubectlTableFormat (t * tablewriter.Table ) {
177
- t .SetAutoWrapText (false )
178
- t .SetAutoFormatHeaders (true )
179
- t .SetNoWhiteSpace (true )
180
- t .SetHeaderLine (false )
181
- t .SetHeaderAlignment (tablewriter .ALIGN_LEFT )
182
- t .SetAlignment (tablewriter .ALIGN_LEFT )
183
- t .SetCenterSeparator ("" )
184
- t .SetColumnSeparator ("" )
185
- t .SetRowSeparator ("" )
186
- t .SetTablePadding (" " )
187
- t .SetBorder (false )
183
+ type tableStatFn func (column func (i int ) * resource.Quantity ) * resource.Quantity
184
+
185
+ func (t table ) printStats (w io.Writer ) error {
186
+ tw := newKubectlTableWriter (w )
187
+
188
+ statFuncs := []tableStatFn {
189
+ t .sumQuantities ,
190
+ t .meanQuantities ,
191
+ t .medianQuantities ,
192
+ }
193
+ rows := []struct {
194
+ name string
195
+ getter func (i int ) * resource.Quantity
196
+ asBytes bool
197
+ }{
198
+ {"CPU Recommendations (# cores)" , func (i int ) * resource.Quantity { return t [i ].Recommendations .CPU }, false },
199
+ {"CPU Requests (# cores)" , func (i int ) * resource.Quantity { return t [i ].Requests .CPU }, false },
200
+ {"MEM Recommendations (IEC/SI)" , func (i int ) * resource.Quantity { return t [i ].Recommendations .Memory }, true },
201
+ {"MEM Requests (IEC/SI)" , func (i int ) * resource.Quantity { return t [i ].Requests .Memory }, true },
202
+ }
203
+ for _ , row := range rows {
204
+ values := make ([]string , 0 , len (statFuncs ))
205
+ for _ , fn := range statFuncs {
206
+ q := fn (row .getter )
207
+
208
+ var str string
209
+ if q == nil {
210
+ str = tableUnsetCell
211
+ } else {
212
+ if row .asBytes {
213
+ tmp := inf.Dec {}
214
+ tmp .Round (q .AsDec (), 0 , inf .RoundUp )
215
+ big := tmp .UnscaledBig ()
216
+ str = humanize .BigIBytes (big ) + "/" + humanize .BigBytes (big )
217
+ str = strings .ReplaceAll (str , " " , "" )
218
+ } else {
219
+ str = q .AsDec ().String ()
220
+ }
221
+ }
222
+ values = append (values , str )
223
+ }
224
+ tw .Append (append ([]string {row .name }, values ... ))
225
+ }
226
+ tw .SetHeader ([]string {"Description" , "Total" , "Mean" , "Median" })
227
+ tw .Render ()
228
+
229
+ return nil
230
+ }
231
+
232
+ func (t table ) sumQuantities (column func (i int ) * resource.Quantity ) * resource.Quantity {
233
+ var sum resource.Quantity
234
+ for i := range t {
235
+ v := column (i )
236
+ if v != nil {
237
+ sum .Add (* v )
238
+ }
239
+ }
240
+ return & sum
241
+ }
242
+
243
+ func (t table ) meanQuantities (column func (i int ) * resource.Quantity ) * resource.Quantity {
244
+ sum := t .sumQuantities (column )
245
+ dec := sum .AsDec ()
246
+ tmp := inf.Dec {}
247
+ tmp .QuoRound (dec , inf .NewDec (int64 (len (t )), 0 ), dec .Scale (), inf .RoundDown )
248
+
249
+ return resource .NewDecimalQuantity (tmp , resource .DecimalSI )
250
+ }
251
+
252
+ func (t table ) medianQuantities (column func (i int ) * resource.Quantity ) * resource.Quantity {
253
+ var values []* resource.Quantity
254
+
255
+ // Collect all values and sort them.
256
+ for i := range t {
257
+ v := column (i )
258
+ if v != nil {
259
+ values = append (values , v )
260
+ }
261
+ }
262
+ sort .Slice (values , func (i , j int ) bool {
263
+ b := compareQuantities (values [i ], values [j ])
264
+ switch b {
265
+ case - 1 :
266
+ return true
267
+ default :
268
+ return false
269
+ }
270
+ })
271
+ // No math is needed if there are no numbers.
272
+ // For even numbers we add the two middle values
273
+ // and divide by two.
274
+ // For odd numbers we just use the middle value.
275
+ l := len (values )
276
+ if l == 0 {
277
+ return nil
278
+ } else if l % 2 == 0 {
279
+ q := values [l / 2 - 1 ]
280
+ q .Add (* (values [l / 2 + 1 ]))
281
+ tmp := inf.Dec {}
282
+ tmp .QuoRound (q .AsDec (), inf .NewDec (2 , 0 ), 0 , inf .RoundDown )
283
+
284
+ return resource .NewDecimalQuantity (tmp , resource .DecimalSI )
285
+ }
286
+ return values [l / 2 ]
287
+ }
288
+
289
+ // newKubectlTableWriter returns a new table writer that writes
290
+ // to w and print according to the Kubectl output format.
291
+ func newKubectlTableWriter (w io.Writer ) * tablewriter.Table {
292
+ tw := tablewriter .NewWriter (w )
293
+
294
+ tw .SetAutoWrapText (false )
295
+ tw .SetAutoFormatHeaders (true )
296
+ tw .SetNoWhiteSpace (true )
297
+ tw .SetHeaderLine (false )
298
+ tw .SetHeaderAlignment (tablewriter .ALIGN_LEFT )
299
+ tw .SetAlignment (tablewriter .ALIGN_LEFT )
300
+ tw .SetCenterSeparator ("" )
301
+ tw .SetColumnSeparator ("" )
302
+ tw .SetRowSeparator ("" )
303
+ tw .SetTablePadding (" " )
304
+ tw .SetBorder (false )
305
+
306
+ return tw
188
307
}
189
308
190
309
// multiTableSorter implements the sort.Sort interface
0 commit comments