Skip to content

Commit 1f58521

Browse files
Merge pull request #141 from kubecost/mmd/prediction-naive-diff-support
Add support for cost difference in "predict" using new API
2 parents b472074 + 0777015 commit 1f58521

File tree

10 files changed

+359
-128
lines changed

10 files changed

+359
-128
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Example output:
7979
+-------------------+-----------+----------+----------+-------------+----------+----------+----------+-------------+--------------------+
8080
```
8181

82-
Predict the cost of a YAML spec based on its requests:
82+
Predict the cost impact of a YAML spec based on its requests:
8383
``` sh
8484
read -r -d '' DEF << EndOfMessage
8585
apiVersion: apps/v1
@@ -110,11 +110,11 @@ echo "$DEF" | kubectl cost predict -f -
110110
```
111111
Example output:
112112
```
113-
+-----------------------------+-----+-----+------------+-----------+------------+
114-
| WORKLOAD | CPU | MEM | CPU/MO | MEM/MO | TOTAL/MO |
115-
+-----------------------------+-----+-----+------------+-----------+------------+
116-
| Deployment/nginx-deployment | 9 | 6Gi | 209.51 USD | 18.73 USD | 228.24 USD |
117-
+-----------------------------+-----+-----+------------+-----------+------------+
113+
+-------------------------------------+-----+-----+-----+------------+-----------+----------+-----------+-----------+------------+
114+
| WORKLOAD | CPU | MEM | GPU | CPU/MO | MEM/MO | GPU/MO | Δ CPU/MO | Δ MEM/MO | TOTAL/MO |
115+
+-------------------------------------+-----+-----+-----+------------+-----------+----------+-----------+-----------+------------+
116+
| default/Deployment/nginx-deployment | 9 | 6Gi | 0 | 207.68 USD | 18.56 USD | 0.00 USD | 38.30 USD | 11.51 USD | 226.24 USD |
117+
+-------------------------------------+-----+-----+-----+------------+-----------+----------+-----------+-----------+------------+
118118
```
119119

120120
Show how much each namespace cost over the past 5 days
@@ -227,6 +227,8 @@ Kubecost/OpenCost APIs:
227227
228228
--allocation-path string URL path at which Allocation queries can be served from the configured service. If using OpenCost, you may want to set this to '/allocation/compute' (default "/model/allocation")
229229
--predict-resource-cost-path string URL path at which Resource Cost Prediction queries can be served from the configured service. (default "/model/prediction/resourcecost")
230+
--predict-resource-cost-diff-path string URL path at which Resource Cost Prediction diff queries can be served from the configured service. (default "/model/prediction/resourcecostdiff")
231+
--no-diff Set true to not attempt a cost difference with a matching in-cluster workload, if one can be found.
230232
```
231233

232234

pkg/cmd/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func addQueryBackendOptionsFlags(cmd *cobra.Command, options *query.QueryBackend
7070
cmd.Flags().BoolVar(&options.UseProxy, "use-proxy", false, "Instead of temporarily port-forwarding, proxy a request to Kubecost through the Kubernetes API server.")
7171
cmd.Flags().StringVar(&options.AllocationPath, "allocation-path", "/model/allocation", "URL path at which Allocation queries can be served from the configured service. If using OpenCost, you may want to set this to '/allocation/compute'")
7272
cmd.Flags().StringVar(&options.PredictResourceCostPath, "predict-resource-cost-path", "/model/prediction/resourcecost", "URL path at which Resource Cost Prediction queries can be served from the configured service.")
73+
cmd.Flags().StringVar(&options.PredictResourceCostDiffPath, "predict-resource-cost-diff-path", "/model/prediction/resourcecostdiff", "URL path at which Resource Cost Prediction diff queries can be served from the configured service.")
7374

7475
//Check if environment variable KUBECTL_COST_USE_PROXY is set, it defaults to false
7576
v := viper.New()

pkg/cmd/cost.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ type KubeOptions struct {
8787
restConfig *rest.Config
8888
args []string
8989

90+
// Namespace should be the currently-configured defaultNamespace of the client.
91+
// This allows e.g. predict to fill in the defaultNamespace if one is not provided
92+
// in the workload spec.
93+
defaultNamespace string
94+
9095
genericclioptions.IOStreams
9196
}
9297

@@ -194,7 +199,12 @@ func (o *KubeOptions) Complete(cmd *cobra.Command, args []string) error {
194199

195200
o.restConfig, err = o.configFlags.ToRESTConfig()
196201
if err != nil {
197-
return err
202+
return fmt.Errorf("converting to REST config: %s", err)
203+
}
204+
205+
o.defaultNamespace, _, err = o.configFlags.ToRawKubeConfigLoader().Namespace()
206+
if err != nil {
207+
return fmt.Errorf("retrieving default namespace: %s", err)
198208
}
199209

200210
return nil

pkg/cmd/output.go

Lines changed: 98 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -29,111 +29,117 @@ const (
2929
CPUCostCol = "CPU Cost"
3030
RAMCostCol = "RAM Cost"
3131

32-
PredictColWorkload = "Workload"
33-
PredictColReqCPU = "CPU"
34-
PredictColReqMemory = "Mem"
35-
PredictColReqGPU = "GPU"
36-
PredictColMoCoreHours = "Mo. core-hrs"
37-
PredictColMoGibHours = "Mo. GiB-hrs"
38-
PredictColMoGPUHours = "Mo. GPU-hrs"
39-
PredictColCostCoreHr = "Cost/core-hr"
40-
PredictColCostGiBHr = "Cost/GiB-hr"
41-
PredictColCostGPUHr = "Cost/GPU-hr"
42-
PredictColMoCostCPU = "CPU/mo"
43-
PredictColMoCostMemory = "Mem/mo"
44-
PredictColMoCostGPU = "GPU/mo"
45-
PredictColMoCostTotal = "Total/mo"
32+
PredictColWorkload = "Workload"
33+
PredictColReqCPU = "CPU"
34+
PredictColReqMemory = "Mem"
35+
PredictColReqGPU = "GPU"
36+
PredictColMoCoreHours = "Mo. core-hrs"
37+
PredictColMoGibHours = "Mo. GiB-hrs"
38+
PredictColMoGPUHours = "Mo. GPU-hrs"
39+
PredictColCostCoreHr = "Cost/core-hr"
40+
PredictColCostGiBHr = "Cost/GiB-hr"
41+
PredictColCostGPUHr = "Cost/GPU-hr"
42+
PredictColMoCostCPU = "CPU/mo"
43+
PredictColMoCostMemory = "Mem/mo"
44+
PredictColMoCostGPU = "GPU/mo"
45+
PredictColMoCostTotal = "Total/mo"
46+
PredictColMoCostDiffCPU = "Δ CPU/mo"
47+
PredictColMoCostDiffMemory = "Δ Mem/mo"
4648
)
4749

4850
func formatFloat(f float64) string {
4951
return fmt.Sprintf("%.6f", f)
5052
}
5153

52-
func writePredictionTable(out io.Writer, rowData []predictRowData, currencyCode string, showCostPerResourceHr bool) {
53-
t := makePredictionTable(rowData, currencyCode, showCostPerResourceHr)
54+
type predictionTableOptions struct {
55+
currencyCode string
56+
showCostPerResourceHr bool
57+
noDiff bool
58+
}
59+
60+
func writePredictionTable(out io.Writer, rowData []predictRowData, opts predictionTableOptions) {
61+
t := makePredictionTable(rowData, opts)
5462
t.SetOutputMirror(out)
5563
t.Render()
5664
}
5765

58-
func makePredictionTable(rowData []predictRowData, currencyCode string, showCostPerResourceHr bool) table.Writer {
66+
func makePredictionTable(rowData []predictRowData, opts predictionTableOptions) table.Writer {
5967
t := table.NewWriter()
6068

61-
columnConfigs := []table.ColumnConfig{
62-
table.ColumnConfig{
69+
t.SetColumnConfigs([]table.ColumnConfig{
70+
{
6371
Name: PredictColWorkload,
6472
},
65-
table.ColumnConfig{
73+
{
6674
Name: PredictColReqCPU,
6775
},
68-
table.ColumnConfig{
76+
{
6977
Name: PredictColReqMemory,
7078
},
71-
table.ColumnConfig{
79+
{
7280
Name: PredictColReqGPU,
7381
},
74-
}
75-
76-
if showCostPerResourceHr {
77-
columnConfigs = append(columnConfigs, []table.ColumnConfig{
78-
table.ColumnConfig{
79-
Name: PredictColCostCoreHr,
80-
},
81-
table.ColumnConfig{
82-
Name: PredictColCostGiBHr,
83-
},
84-
table.ColumnConfig{
85-
Name: PredictColCostGPUHr,
86-
},
87-
}...)
88-
}
89-
90-
columnConfigs = append(columnConfigs, []table.ColumnConfig{
91-
table.ColumnConfig{
82+
{
83+
Name: PredictColCostCoreHr,
84+
Hidden: !opts.showCostPerResourceHr,
85+
},
86+
{
87+
Name: PredictColCostGiBHr,
88+
Hidden: !opts.showCostPerResourceHr,
89+
},
90+
{
91+
Name: PredictColCostGPUHr,
92+
Hidden: !opts.showCostPerResourceHr,
93+
},
94+
{
9295
Name: PredictColMoCostCPU,
9396
Align: text.AlignRight,
9497
AlignFooter: text.AlignRight,
9598
},
96-
table.ColumnConfig{
99+
{
97100
Name: PredictColMoCostMemory,
98101
Align: text.AlignRight,
99102
AlignFooter: text.AlignRight,
100103
},
101-
table.ColumnConfig{
104+
{
102105
Name: PredictColMoCostGPU,
103106
Align: text.AlignRight,
104107
AlignFooter: text.AlignRight,
105108
},
106-
table.ColumnConfig{
109+
{
110+
Name: PredictColMoCostDiffCPU,
111+
Hidden: opts.noDiff,
112+
Align: text.AlignRight,
113+
AlignFooter: text.AlignRight,
114+
},
115+
{
116+
Name: PredictColMoCostDiffMemory,
117+
Hidden: opts.noDiff,
118+
Align: text.AlignRight,
119+
AlignFooter: text.AlignRight,
120+
},
121+
{
107122
Name: PredictColMoCostTotal,
108123
Align: text.AlignRight,
109124
AlignFooter: text.AlignRight,
110125
},
111-
}...)
112-
t.SetColumnConfigs(columnConfigs)
126+
})
113127

114-
headerRow := table.Row{
128+
t.AppendHeader(table.Row{
115129
PredictColWorkload,
116130
PredictColReqCPU,
117131
PredictColReqMemory,
118132
PredictColReqGPU,
119-
}
120-
121-
if showCostPerResourceHr {
122-
headerRow = append(headerRow,
123-
PredictColCostCoreHr,
124-
PredictColCostGiBHr,
125-
PredictColCostGPUHr,
126-
)
127-
}
128-
129-
headerRow = append(headerRow,
133+
PredictColCostCoreHr,
134+
PredictColCostGiBHr,
135+
PredictColCostGPUHr,
130136
PredictColMoCostCPU,
131137
PredictColMoCostMemory,
132138
PredictColMoCostGPU,
139+
PredictColMoCostDiffCPU,
140+
PredictColMoCostDiffMemory,
133141
PredictColMoCostTotal,
134-
)
135-
136-
t.AppendHeader(headerRow)
142+
})
137143

138144
t.SortBy([]table.SortBy{
139145
{
@@ -145,48 +151,52 @@ func makePredictionTable(rowData []predictRowData, currencyCode string, showCost
145151
var summedMonthlyCPU float64
146152
var summedMonthlyMem float64
147153
var summedMonthlyGPU float64
154+
var summedMonthlyDiffCPU float64
155+
var summedMonthlyDiffMemory float64
148156
var summedMonthlyTotal float64
149157

150158
for _, rowDatum := range rowData {
151159
row := table.Row{}
152-
row = append(row, fmt.Sprintf("%s/%s", rowDatum.workloadType, rowDatum.workloadName))
153-
row = append(row, rowDatum.cpuStr)
154-
row = append(row, rowDatum.memStr)
155-
row = append(row, rowDatum.gpuStr)
156-
157-
if showCostPerResourceHr {
158-
row = append(row, fmt.Sprintf("%.4f %s", rowDatum.prediction.DerivedCostPerCPUCoreHour, currencyCode))
159-
row = append(row, fmt.Sprintf("%.4f %s", rowDatum.prediction.DerivedCostPerMemoryByteHour*1024*1024*1024, currencyCode))
160-
row = append(row, fmt.Sprintf("%.4f %s", rowDatum.prediction.DerivedCostPerGPUHour, currencyCode))
161-
}
162-
163-
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.prediction.MonthlyCostCPU, currencyCode))
164-
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.prediction.MonthlyCostMemory, currencyCode))
165-
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.prediction.MonthlyCostGPU, currencyCode))
166-
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.prediction.MonthlyCostTotal, currencyCode))
167-
168-
summedMonthlyCPU += rowDatum.prediction.MonthlyCostCPU
169-
summedMonthlyMem += rowDatum.prediction.MonthlyCostMemory
170-
summedMonthlyGPU += rowDatum.prediction.MonthlyCostGPU
171-
summedMonthlyTotal += rowDatum.prediction.MonthlyCostTotal
160+
row = append(row, fmt.Sprintf("%s/%s/%s", rowDatum.workloadNamespace, rowDatum.workloadType, rowDatum.workloadName))
161+
row = append(row, rowDatum.totalCPURequested)
162+
row = append(row, rowDatum.totalMemoryRequested)
163+
row = append(row, rowDatum.totalGPURequested)
164+
165+
row = append(row, fmt.Sprintf("%.4f %s", rowDatum.cpuCostMonthly/rowDatum.requestedCPUCoreHours, opts.currencyCode))
166+
row = append(row, fmt.Sprintf("%.4f %s", (rowDatum.memoryCostMonthly/rowDatum.requestedMemoryByteHours)*1024*1024*1024, opts.currencyCode))
167+
row = append(row, fmt.Sprintf("%.4f %s", rowDatum.gpuCostMonthly/rowDatum.requestedGPUHours, opts.currencyCode))
168+
169+
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.cpuCostMonthly, opts.currencyCode))
170+
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.memoryCostMonthly, opts.currencyCode))
171+
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.gpuCostMonthly, opts.currencyCode))
172+
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.cpuCostChangeMonthly, opts.currencyCode))
173+
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.memoryCostChangeMonthly, opts.currencyCode))
174+
row = append(row, fmt.Sprintf("%.2f %s", rowDatum.totalCostMonthly, opts.currencyCode))
175+
176+
summedMonthlyCPU += rowDatum.cpuCostMonthly
177+
summedMonthlyMem += rowDatum.memoryCostMonthly
178+
summedMonthlyGPU += rowDatum.gpuCostMonthly
179+
summedMonthlyDiffCPU += rowDatum.cpuCostChangeMonthly
180+
summedMonthlyDiffMemory += rowDatum.memoryCostChangeMonthly
181+
summedMonthlyTotal += rowDatum.totalCostMonthly
172182

173183
t.AppendRow(row)
174184
}
175185

176186
// A summary footer is redundant if there is only one row
177187
if len(rowData) > 1 {
178188
footerRow := table.Row{}
179-
blankRows := 4
180-
if showCostPerResourceHr {
181-
blankRows += 2
182-
}
189+
blankRows := 7
190+
183191
for i := 0; i < blankRows; i++ {
184192
footerRow = append(footerRow, "")
185193
}
186-
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyCPU, currencyCode))
187-
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyMem, currencyCode))
188-
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyGPU, currencyCode))
189-
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyTotal, currencyCode))
194+
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyCPU, opts.currencyCode))
195+
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyMem, opts.currencyCode))
196+
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyGPU, opts.currencyCode))
197+
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyDiffCPU, opts.currencyCode))
198+
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyDiffMemory, opts.currencyCode))
199+
footerRow = append(footerRow, fmt.Sprintf("%.2f %s", summedMonthlyTotal, opts.currencyCode))
190200
t.AppendFooter(footerRow)
191201
}
192202

0 commit comments

Comments
 (0)