From 7bd8639fbcaa6c70ef707d3f708d8e506aa0103a Mon Sep 17 00:00:00 2001 From: Jin Zhang Date: Tue, 22 Feb 2022 07:16:09 +0000 Subject: [PATCH] [benchmark] add cpu utilization --- hack/benchmark/time-to-k8s/chart.go | 79 ++++++++++------ hack/benchmark/time-to-k8s/cpu.go | 102 ++++++++++++++++++++ hack/benchmark/time-to-k8s/page.go | 108 ++++++++++++++++++++++ hack/benchmark/time-to-k8s/time-to-k8s.sh | 7 +- 4 files changed, 264 insertions(+), 32 deletions(-) create mode 100644 hack/benchmark/time-to-k8s/cpu.go create mode 100644 hack/benchmark/time-to-k8s/page.go diff --git a/hack/benchmark/time-to-k8s/chart.go b/hack/benchmark/time-to-k8s/chart.go index ba03ecadfa..7e8f6e3857 100644 --- a/hack/benchmark/time-to-k8s/chart.go +++ b/hack/benchmark/time-to-k8s/chart.go @@ -19,10 +19,8 @@ package main import ( "bytes" "encoding/csv" - "flag" "fmt" "io" - "log" "os" "strconv" @@ -45,27 +43,12 @@ type run struct { type runs struct { version string runs []run + cpus []cpu } -func main() { - csvPath := flag.String("csv", "", "path to the CSV file") - chartPath := flag.String("output", "", "path to output the chart to") - flag.Parse() - - // map of the apps (minikube, kind, k3d) and their runs - apps := make(map[string]runs) - - if err := readInCSV(*csvPath, apps); err != nil { - log.Fatal(err) - } - - values, totals, names := values(apps) - - outputMarkdownTable(values, totals, names) - - if err := createChart(*chartPath, values, totals, names); err != nil { - log.Fatal(err) - } +type cpu struct { + cpuPct float64 // percentage + cpuTime float64 // second } func readInCSV(csvPath string, apps map[string]runs) error { @@ -92,7 +75,7 @@ func readInCSV(csvPath string, apps map[string]runs) error { values := []float64{} // 8-13 contain the run results - for i := 8; i <= 13; i++ { + for i := 8; i <= 16; i++ { v, err := strconv.ParseFloat(d[i], 64) if err != nil { return err @@ -100,6 +83,7 @@ func readInCSV(csvPath string, apps map[string]runs) error { values = append(values, v) } newRun := run{values[0], values[1], values[2], values[3], values[4], values[5]} + newCPU := cpu{values[6], values[8]} // get the app from the map and add the new run to it name := d[0] @@ -108,14 +92,18 @@ func readInCSV(csvPath string, apps map[string]runs) error { k = runs{version: d[5]} } k.runs = append(k.runs, newRun) + k.cpus = append(k.cpus, newCPU) apps[name] = k } return nil } -func values(apps map[string]runs) ([]plotter.Values, []float64, []string) { +func values(apps map[string]runs) ([]plotter.Values, []plotter.Values, []plotter.Values, []float64, []string) { var cmdValues, apiValues, k8sValues, dnsSvcValues, appValues, dnsAnsValues plotter.Values + var cpuPctValues, cpuTimeValues plotter.Values + var cpuMinikube, cpuKind, cpuK3d plotter.Values + names := []string{} totals := []float64{} @@ -123,6 +111,8 @@ func values(apps map[string]runs) ([]plotter.Values, []float64, []string) { for _, name := range []string{"minikube", "kind", "k3d"} { app := apps[name] var cmd, api, k8s, dnsSvc, appRun, dnsAns float64 + var cpuPct, cpuTime float64 + names = append(names, app.version) for _, l := range app.runs { @@ -134,6 +124,11 @@ func values(apps map[string]runs) ([]plotter.Values, []float64, []string) { dnsAns += l.dnsAns } + for _, l := range app.cpus { + cpuPct += l.cpuPct + cpuTime += l.cpuTime + } + c := float64(len(app.runs)) cmdAvg := cmd / c @@ -143,6 +138,9 @@ func values(apps map[string]runs) ([]plotter.Values, []float64, []string) { appAvg := appRun / c dnsAnsAvg := dnsAns / c + cpuPctAvg := cpuPct / c + cpuTimeAvg := cpuTime / c + cmdValues = append(cmdValues, cmdAvg) apiValues = append(apiValues, apiAvg) k8sValues = append(k8sValues, k8sAvg) @@ -152,11 +150,32 @@ func values(apps map[string]runs) ([]plotter.Values, []float64, []string) { total := cmdAvg + apiAvg + k8sAvg + dnsSvcAvg + appAvg + dnsAnsAvg totals = append(totals, total) + + cpuPctValues = append(cpuPctValues, cpuPctAvg) + cpuTimeValues = append(cpuTimeValues, cpuTimeAvg) + + cpuSummary := []float64{cpuPctAvg, cpuTimeAvg} + + switch name { + case "minikube": + cpuMinikube = cpuSummary + case "kind": + cpuKind = cpuSummary + case "k3d": + cpuK3d = cpuSummary + } + } - values := []plotter.Values{cmdValues, apiValues, k8sValues, dnsSvcValues, appValues, dnsAnsValues} + runningTime := []plotter.Values{cmdValues, apiValues, k8sValues, dnsSvcValues, appValues, dnsAnsValues} - return values, totals, names + // for markdown table, row is either cpu utilization or cpu time, col is process name + cpu := []plotter.Values{cpuPctValues, cpuTimeValues} + + // row is process name, col is either cpu utilization, or cpu time + cpureverse := []plotter.Values{cpuMinikube, cpuKind, cpuK3d} + + return runningTime, cpu, cpureverse, totals, names } func outputMarkdownTable(categories []plotter.Values, totals []float64, names []string) { @@ -184,7 +203,7 @@ func outputMarkdownTable(categories []plotter.Values, totals []float64, names [] t.SetCenterSeparator("|") t.AppendBulk(c) t.Render() - fmt.Println(b.String()) + data.TimeMarkdown = b.String() } func createChart(chartPath string, values []plotter.Values, totals []float64, names []string) error { @@ -252,7 +271,13 @@ func createChart(chartPath string, values []plotter.Values, totals []float64, na p.Add(l) - return p.Save(12*vg.Inch, 8*vg.Inch, chartPath) + if err := p.Save(12*vg.Inch, 8*vg.Inch, chartPath); err != nil { + return err + } + + data.TimeChart = chartPath + + return nil } func createBars(values plotter.Values, index int) (*plotter.BarChart, error) { diff --git a/hack/benchmark/time-to-k8s/cpu.go b/hack/benchmark/time-to-k8s/cpu.go new file mode 100644 index 0000000000..5dd7eaa16e --- /dev/null +++ b/hack/benchmark/time-to-k8s/cpu.go @@ -0,0 +1,102 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "fmt" + + "github.com/olekukonko/tablewriter" + "gonum.org/v1/plot" + "gonum.org/v1/plot/plotter" + "gonum.org/v1/plot/plotutil" + "gonum.org/v1/plot/vg" +) + +var fields = []string{"CPU Utilization(%)", "CPU Time(seconds)"} + +func cpuMarkdownTable(categories []plotter.Values, names []string) { + + // categories row is the either cpu pct or time, col is process name + headers := append([]string{""}, names...) + c := [][]string{} + for i, values := range categories { + row := []string{fields[i]} + for _, value := range values { + row = append(row, fmt.Sprintf("%.3f", value)) + } + c = append(c, row) + } + b := new(bytes.Buffer) + t := tablewriter.NewWriter(b) + t.SetAutoWrapText(false) + t.SetHeader(headers) + t.SetAutoFormatHeaders(false) + t.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) + t.SetCenterSeparator("|") + t.AppendBulk(c) + t.Render() + data.CPUMarkdown = b.String() +} + +func createCPUChart(chartPath string, values []plotter.Values, names []string) error { + p := plot.New() + p.Title.Text = "CPU utilization to go from 0 to successful Kubernetes deployment" + p.Y.Label.Text = "CPU utilization" + + w := vg.Points(20) + + barsA, err := plotter.NewBarChart(values[0], w) + if err != nil { + panic(err) + } + barsA.LineStyle.Width = vg.Length(0) + barsA.Color = plotutil.Color(0) + barsA.Offset = -w + + barsB, err := plotter.NewBarChart(values[1], w) + if err != nil { + panic(err) + } + barsB.LineStyle.Width = vg.Length(0) + barsB.Color = plotutil.Color(1) + barsB.Offset = 0 + + barsC, err := plotter.NewBarChart(values[2], w) + if err != nil { + panic(err) + } + barsC.LineStyle.Width = vg.Length(0) + barsC.Color = plotutil.Color(2) + barsC.Offset = w + + p.Add(barsA, barsB, barsC) + p.Legend.Add(names[0], barsA) + p.Legend.Add(names[1], barsB) + p.Legend.Add(names[2], barsC) + + p.Legend.Top = true + p.NominalX(fields...) + + if err := p.Save(8*vg.Inch, 8*vg.Inch, chartPath); err != nil { + return err + } + + data.CPUChart = chartPath + + return nil +} diff --git a/hack/benchmark/time-to-k8s/page.go b/hack/benchmark/time-to-k8s/page.go new file mode 100644 index 0000000000..ab30b0a73a --- /dev/null +++ b/hack/benchmark/time-to-k8s/page.go @@ -0,0 +1,108 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "flag" + "fmt" + "log" + "os" + "text/template" + "time" +) + +var page = `--- +title: "{{.Version}} Benchmark" +linkTitle: "{{.Version}} Benchmark" +weight: -{{.Weight}} +--- + +![time-to-k8s]({{.TimeChart}}) + +{{.TimeMarkdown}} + + +![cpu-to-k8s]({{.CPUChart}}) + +{{.CPUMarkdown}} +` + +type Data struct { + Version string + Weight string + TimeChart string + TimeMarkdown string + CPUChart string + CPUMarkdown string +} + +var data Data + +func main() { + csvPath := flag.String("csv", "", "path to the CSV file") + imagePath := flag.String("image", "", "path to output the chart to") + pagePath := flag.String("page", "", "path to output the page to") + + flag.Parse() + + t := time.Now() + data.Weight = fmt.Sprintf("%d%d%d", t.Year(), t.Month(), t.Day()) + + // map of the apps (minikube, kind, k3d) and their runs + apps := make(map[string]runs) + + if err := readInCSV(*csvPath, apps); err != nil { + log.Fatal(err) + } + + runningTime, cpuMdPlot, cpuChartPlot, totals, names := values(apps) + + // markdown table for running time + outputMarkdownTable(runningTime, totals, names) + + // chart for running time + if err := createChart(*imagePath+"-time.png", runningTime, totals, names); err != nil { + log.Fatal(err) + } + + // markdown table for cpu utilization + cpuMarkdownTable(cpuMdPlot, names) + + // chart for cpu utilization + if err := createCPUChart(*imagePath+"-cpu.png", cpuChartPlot, names); err != nil { + log.Fatal(err) + } + + // generate page and save + tmpl, err := template.New("msg").Parse(page) + + if err != nil { + log.Fatal(err) + } + + f, err := os.Create(*pagePath) + if err != nil { + log.Fatal(err) + } + + if err = tmpl.Execute(f, data); err != nil { + log.Fatal(err) + } + + f.Close() + +} diff --git a/hack/benchmark/time-to-k8s/time-to-k8s.sh b/hack/benchmark/time-to-k8s/time-to-k8s.sh index 213f538aea..348cd86a96 100755 --- a/hack/benchmark/time-to-k8s/time-to-k8s.sh +++ b/hack/benchmark/time-to-k8s/time-to-k8s.sh @@ -37,12 +37,10 @@ run_benchmark() { go run . --config local-kubernetes.yaml --iterations 10 --output output.csv ) } -generate_chart() { - go run ./hack/benchmark/time-to-k8s/chart.go --csv ./hack/benchmark/time-to-k8s/time-to-k8s-repo/output.csv --output ./site/static/images/benchmarks/timeToK8s/"$1".png >> ./site/content/en/docs/benchmarks/timeToK8s/"$1".md -} +# create page and generate chart inside the code create_page() { - printf -- "---\ntitle: \"%s Benchmark\"\nlinkTitle: \"%s Benchmark\"\nweight: -$(date +'%Y%m%d')\n---\n\n![time-to-k8s](/images/benchmarks/timeToK8s/%s.png)\n" "$1" "$1" "$1" > ./site/content/en/docs/benchmarks/timeToK8s/"$1".md + go run ./hack/benchmark/time-to-k8s/*.go --csv ./hack/benchmark/time-to-k8s/time-to-k8s-repo/output.csv --image ./site/static/images/benchmarks/timeToK8s/"$1" --page ./site/content/en/docs/benchmarks/timeToK8s/"$1".md } cleanup() { @@ -56,5 +54,4 @@ install_minikube VERSION=$(minikube version --short) run_benchmark create_page "$VERSION" -generate_chart "$VERSION" cleanup