Skip to content

Commit 88dc6f0

Browse files
Merge pull request #146 from kubecost/mmd/predict-add-show-new-flag
Add --show-total to show full cost alongside diff and --hide-diff to hide diff info
2 parents 218bf80 + 5bdc389 commit 88dc6f0

File tree

2 files changed

+129
-48
lines changed

2 files changed

+129
-48
lines changed

pkg/cmd/display/predict.go

Lines changed: 127 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,28 @@ const (
1919
ColResourceUnit = "resource unit"
2020
ColMoDiffResource = "Δ qty"
2121
ColMoDiffCost = "Δ cost/mo"
22+
ColMoResource = "qty"
23+
ColMoCost = "cost/mo"
2224
ColCostPerUnit = "cost per unit"
2325
ColPctChange = "% change"
2426
)
2527

26-
type PredictDisplayOptions struct{}
28+
type PredictDisplayOptions struct {
29+
// ShowTotal determines if "After" cost info will be shown alongside the
30+
// diff
31+
ShowTotal bool
32+
33+
// HideDiff will disable diff information if true
34+
HideDiff bool
35+
}
2736

2837
func AddPredictDisplayOptionsFlags(cmd *cobra.Command, options *PredictDisplayOptions) {
2938
}
3039

3140
func (o *PredictDisplayOptions) Validate() error {
41+
if !o.ShowTotal && o.HideDiff {
42+
return fmt.Errorf("ShowTotal and HideDiff cannot be set such that no data will be shown")
43+
}
3244
return nil
3345
}
3446

@@ -42,9 +54,6 @@ func WritePredictionTable(out io.Writer, rowData []query.SpecCostDiff, currencyC
4254
// the decimal places.
4355
func fmtResourceFloat(x float64) string {
4456
s := fmt.Sprintf("%.2f", x)
45-
if x > 0 {
46-
s = fmt.Sprintf("+%s", s)
47-
}
4857

4958
// If formatted float ends in .000, remove
5059
s = strings.TrimRight(s, "0")
@@ -80,9 +89,6 @@ func fmtResourceCostFloat(x float64) string {
8089

8190
func fmtOverallCostFloat(x float64) string {
8291
s := fmt.Sprintf("%.2f", x)
83-
if x > 0 {
84-
s = fmt.Sprintf("+%s", s)
85-
}
8692
return s
8793
}
8894

@@ -114,8 +120,9 @@ func MakePredictionTable(specDiffs []query.SpecCostDiff, currencyCode string, op
114120
WidthMaxEnforcer: text.WrapSoft,
115121
},
116122
{
117-
Name: ColMoDiffResource,
118-
Align: text.AlignRight,
123+
Name: ColMoResource,
124+
Hidden: !opts.ShowTotal,
125+
Align: text.AlignRight,
119126
Transformer: func(val interface{}) string {
120127
if f, ok := val.(float64); ok {
121128
return fmtResourceFloat(f)
@@ -126,6 +133,24 @@ func MakePredictionTable(specDiffs []query.SpecCostDiff, currencyCode string, op
126133
return "invalid value"
127134
},
128135
},
136+
{
137+
Name: ColMoDiffResource,
138+
Hidden: opts.HideDiff,
139+
Align: text.AlignRight,
140+
Transformer: func(val interface{}) string {
141+
if f, ok := val.(float64); ok {
142+
s := fmtResourceFloat(f)
143+
if f > 0 {
144+
s = fmt.Sprintf("+%s", s)
145+
}
146+
return s
147+
}
148+
if s, ok := val.(string); ok {
149+
return s
150+
}
151+
return "invalid value"
152+
},
153+
},
129154
{
130155
Name: ColResourceUnit,
131156
Align: text.AlignLeft,
@@ -144,8 +169,9 @@ func MakePredictionTable(specDiffs []query.SpecCostDiff, currencyCode string, op
144169
},
145170
},
146171
{
147-
Name: ColMoDiffCost,
148-
Align: text.AlignRight,
172+
Name: ColMoCost,
173+
Hidden: !opts.ShowTotal,
174+
Align: text.AlignRight,
149175
Transformer: func(val interface{}) string {
150176
if f, ok := val.(float64); ok {
151177
return fmt.Sprintf("%s %s", fmtOverallCostFloat(f), currencyCode)
@@ -166,8 +192,40 @@ func MakePredictionTable(specDiffs []query.SpecCostDiff, currencyCode string, op
166192
},
167193
},
168194
{
169-
Name: ColPctChange,
170-
Align: text.AlignRight,
195+
Name: ColMoDiffCost,
196+
Hidden: opts.HideDiff,
197+
Align: text.AlignRight,
198+
Transformer: func(val interface{}) string {
199+
if f, ok := val.(float64); ok {
200+
s := fmt.Sprintf("%s %s", fmtOverallCostFloat(f), currencyCode)
201+
if f > 0 {
202+
s = fmt.Sprintf("+%s", s)
203+
}
204+
return s
205+
}
206+
if s, ok := val.(string); ok {
207+
return s
208+
}
209+
return "invalid value"
210+
},
211+
TransformerFooter: func(val interface{}) string {
212+
if f, ok := val.(float64); ok {
213+
s := fmt.Sprintf("%s %s", fmtOverallCostFloat(f), currencyCode)
214+
if f > 0 {
215+
s = fmt.Sprintf("+%s", s)
216+
}
217+
return s
218+
}
219+
if s, ok := val.(string); ok {
220+
return s
221+
}
222+
return "invalid value"
223+
},
224+
},
225+
{
226+
Name: ColPctChange,
227+
Hidden: opts.HideDiff,
228+
Align: text.AlignRight,
171229
Transformer: func(val interface{}) string {
172230
if f, ok := val.(float64); ok {
173231
prefix := ""
@@ -186,89 +244,110 @@ func MakePredictionTable(specDiffs []query.SpecCostDiff, currencyCode string, op
186244

187245
t.AppendHeader(table.Row{
188246
ColObject,
247+
ColMoResource,
189248
ColMoDiffResource,
190249
ColResourceUnit,
191250
ColCostPerUnit,
251+
ColMoCost,
192252
ColMoDiffCost,
193253
ColPctChange,
194254
})
195255

196256
totalCostImpact := 0.0
257+
totalCostNew := 0.0
197258
for _, specData := range specDiffs {
198259
totalCostImpact += specData.CostChange.TotalMonthlyRate
260+
totalCostNew += specData.CostAfter.TotalMonthlyRate
199261

200262
workloadName := fmt.Sprintf("%s %s %s", specData.Namespace, specData.ControllerKind, specData.ControllerName)
201263

202264
// Don't show resource if there is no cost data before or after
203265
if !(specData.CostBefore.CPUMonthlyRate == 0 && specData.CostAfter.CPUMonthlyRate == 0) {
204-
cpuUnits := "CPU cores"
205-
avgCPUInUnits := specData.CostChange.MonthlyCPUCoreHours / timeutil.HoursPerMonth
206-
if avgCPUInUnits < 1 {
207-
cpuUnits = "CPU millicores"
208-
avgCPUInUnits = specData.CostChange.MonthlyCPUCoreHours / timeutil.HoursPerMonth * 1000
266+
units := "CPU cores"
267+
avgUnitsNew := specData.CostAfter.MonthlyCPUCoreHours / timeutil.HoursPerMonth
268+
avgUnitsDiff := specData.CostChange.MonthlyCPUCoreHours / timeutil.HoursPerMonth
269+
factor := 1.0
270+
if avgUnitsNew*factor < 1 {
271+
units = "CPU millicores"
272+
factor = 1000
209273
}
210-
costPerUnit := specData.CostChange.CPUMonthlyRate / avgCPUInUnits
211-
cpuRow := table.Row{
274+
avgUnitsDiff *= factor
275+
avgUnitsNew *= factor
276+
costPerUnit := specData.CostChange.CPUMonthlyRate / avgUnitsDiff
277+
row := table.Row{
212278
workloadName,
213-
avgCPUInUnits,
214-
cpuUnits,
279+
avgUnitsNew,
280+
avgUnitsDiff,
281+
units,
215282
costPerUnit,
283+
specData.CostAfter.CPUMonthlyRate,
216284
specData.CostChange.CPUMonthlyRate,
217285
}
218286
if specData.CostBefore.CPUMonthlyRate != 0 {
219-
cpuRow = append(cpuRow, specData.CostChange.CPUMonthlyRate/specData.CostBefore.CPUMonthlyRate*100)
287+
row = append(row, specData.CostChange.CPUMonthlyRate/specData.CostBefore.CPUMonthlyRate*100)
220288
}
221-
t.AppendRow(cpuRow)
289+
t.AppendRow(row)
222290
}
223291

224292
if !(specData.CostBefore.RAMMonthlyRate == 0 && specData.CostAfter.RAMMonthlyRate == 0) {
225-
226-
ramUnits := "RAM GiB"
227-
ramUnitDivisor := 1024 * 1024 * 1024.0
228-
avgRAMInUnits := specData.CostChange.MonthlyRAMByteHours / ramUnitDivisor / timeutil.HoursPerMonth
229-
// If < 1 GiB, convert to MiB
230-
if avgRAMInUnits < 1 {
231-
ramUnits = "RAM MiB"
232-
ramUnitDivisor = 1024 * 1024.0
233-
avgRAMInUnits = specData.CostChange.MonthlyRAMByteHours / ramUnitDivisor / timeutil.HoursPerMonth
293+
units := "RAM GiB"
294+
avgUnitsNew := specData.CostAfter.MonthlyRAMByteHours / timeutil.HoursPerMonth
295+
avgUnitsDiff := specData.CostChange.MonthlyRAMByteHours / timeutil.HoursPerMonth
296+
factor := 1.0 / (1024 * 1024 * 1024)
297+
if avgUnitsNew*factor < 1 {
298+
units = "RAM MiB"
299+
factor = 1.0 / (1024 * 1024)
234300
}
235-
costPerUnit := specData.CostChange.RAMMonthlyRate / avgRAMInUnits
236-
ramRow := table.Row{
301+
avgUnitsDiff *= factor
302+
avgUnitsNew *= factor
303+
costPerUnit := specData.CostChange.RAMMonthlyRate / avgUnitsDiff
304+
row := table.Row{
237305
workloadName,
238-
avgRAMInUnits,
239-
ramUnits,
306+
avgUnitsNew,
307+
avgUnitsDiff,
308+
units,
240309
costPerUnit,
310+
specData.CostAfter.RAMMonthlyRate,
241311
specData.CostChange.RAMMonthlyRate,
242312
}
243313
if specData.CostBefore.RAMMonthlyRate != 0 {
244-
ramRow = append(ramRow, specData.CostChange.RAMMonthlyRate/specData.CostBefore.RAMMonthlyRate*100)
314+
row = append(row, specData.CostChange.RAMMonthlyRate/specData.CostBefore.RAMMonthlyRate*100)
245315
}
246-
t.AppendRow(ramRow)
316+
t.AppendRow(row)
247317
}
248318

249319
if !(specData.CostBefore.GPUMonthlyRate == 0 && specData.CostAfter.GPUMonthlyRate == 0) {
250-
avgGPUs := specData.CostChange.MonthlyGPUHours / timeutil.HoursPerMonth
251-
costPerGPU := specData.CostChange.GPUMonthlyRate / avgGPUs
252-
gpuRow := table.Row{
320+
units := "GPUs"
321+
avgUnitsNew := specData.CostAfter.MonthlyGPUHours / timeutil.HoursPerMonth
322+
avgUnitsDiff := specData.CostChange.MonthlyGPUHours / timeutil.HoursPerMonth
323+
factor := 1.0
324+
avgUnitsDiff *= factor
325+
avgUnitsNew *= factor
326+
costPerUnit := specData.CostChange.GPUMonthlyRate / avgUnitsDiff
327+
row := table.Row{
253328
workloadName,
254-
avgGPUs,
255-
"GPUs",
256-
costPerGPU,
329+
avgUnitsNew,
330+
avgUnitsDiff,
331+
units,
332+
costPerUnit,
333+
specData.CostAfter.GPUMonthlyRate,
257334
specData.CostChange.GPUMonthlyRate,
258335
}
259336
if specData.CostBefore.GPUMonthlyRate != 0 {
260-
gpuRow = append(gpuRow, specData.CostChange.GPUMonthlyRate/specData.CostBefore.GPUMonthlyRate*100)
337+
row = append(row, specData.CostChange.GPUMonthlyRate/specData.CostBefore.GPUMonthlyRate*100)
261338
}
262-
t.AppendRow(gpuRow)
339+
t.AppendRow(row)
263340
}
264341
t.AppendSeparator()
265342
}
266343

267344
t.AppendFooter(table.Row{
268-
"Total monthly cost change",
345+
"Total monthly cost",
346+
"",
269347
"",
270348
"",
271349
"",
350+
totalCostNew,
272351
totalCostImpact,
273352
})
274353

pkg/cmd/predict.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func NewCmdPredict(
6969
cmd.Flags().StringVar(&predictO.avgUsageWindow, "window-usage", "2d", "The window of Kubecost data to calculate historical average usage from, if historical data exists. See https://github.com/kubecost/docs/blob/master/allocation.md#querying for a detailed explanation of what can be passed here.")
7070
cmd.Flags().StringVar(&predictO.resourceCostWindow, "window-cost", "7d offset 48h", "The window of Kubecost data to base resource costs on. Defaults with an offset of 48h to incorporate reconciled data if reconciliation is set up. See https://github.com/kubecost/docs/blob/master/allocation.md#querying for a detailed explanation of what can be passed here.")
7171
cmd.Flags().BoolVar(&predictO.noUsage, "no-usage", false, "Set true ignore historical usage data (if any exists) when performing cost prediction.")
72+
cmd.Flags().BoolVar(&predictO.ShowTotal, "show-total", false, "Show the total cost of the new spec(s). See --hide-diff for a similar option..")
73+
cmd.Flags().BoolVar(&predictO.HideDiff, "hide-diff", false, "Hide the cost difference of applying the new spec(s). See --show-total for a similar option..")
7274

7375
query.AddQueryBackendOptionsFlags(cmd, &predictO.QueryBackendOptions)
7476
display.AddPredictDisplayOptionsFlags(cmd, &predictO.PredictDisplayOptions)

0 commit comments

Comments
 (0)