1/*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19/*
20To format the benchmark result:
21  go run benchmark/benchresult/main.go resultfile
22
23To see the performance change based on a old result:
24  go run benchmark/benchresult/main.go resultfile_old resultfile
25It will print the comparison result of intersection benchmarks between two files.
26
27*/
28package main
29
30import (
31	"encoding/gob"
32	"fmt"
33	"log"
34	"os"
35	"strings"
36	"time"
37
38	"google.golang.org/grpc/benchmark/stats"
39)
40
41func createMap(fileName string) map[string]stats.BenchResults {
42	f, err := os.Open(fileName)
43	if err != nil {
44		log.Fatalf("Read file %s error: %s\n", fileName, err)
45	}
46	defer f.Close()
47	var data []stats.BenchResults
48	decoder := gob.NewDecoder(f)
49	if err = decoder.Decode(&data); err != nil {
50		log.Fatalf("Decode file %s error: %s\n", fileName, err)
51	}
52	m := make(map[string]stats.BenchResults)
53	for _, d := range data {
54		m[d.RunMode+"-"+d.Features.String()] = d
55	}
56	return m
57}
58
59func intChange(title string, val1, val2 uint64) string {
60	return fmt.Sprintf("%20s %12d %12d %8.2f%%\n", title, val1, val2, float64(int64(val2)-int64(val1))*100/float64(val1))
61}
62
63func floatChange(title string, val1, val2 float64) string {
64	return fmt.Sprintf("%20s %12.2f %12.2f %8.2f%%\n", title, val1, val2, float64(int64(val2)-int64(val1))*100/float64(val1))
65}
66func timeChange(title string, val1, val2 time.Duration) string {
67	return fmt.Sprintf("%20s %12s %12s %8.2f%%\n", title, val1.String(),
68		val2.String(), float64(val2-val1)*100/float64(val1))
69}
70
71func strDiff(title, val1, val2 string) string {
72	return fmt.Sprintf("%20s %12s %12s\n", title, val1, val2)
73}
74
75func compareTwoMap(m1, m2 map[string]stats.BenchResults) {
76	for k2, v2 := range m2 {
77		if v1, ok := m1[k2]; ok {
78			changes := k2 + "\n"
79			changes += fmt.Sprintf("%20s %12s %12s %8s\n", "Title", "Before", "After", "Percentage")
80			changes += intChange("TotalOps", v1.Data.TotalOps, v2.Data.TotalOps)
81			changes += intChange("SendOps", v1.Data.SendOps, v2.Data.SendOps)
82			changes += intChange("RecvOps", v1.Data.RecvOps, v2.Data.RecvOps)
83			changes += floatChange("Bytes/op", v1.Data.AllocedBytes, v2.Data.AllocedBytes)
84			changes += floatChange("Allocs/op", v1.Data.Allocs, v2.Data.Allocs)
85			changes += floatChange("ReqT/op", v1.Data.ReqT, v2.Data.ReqT)
86			changes += floatChange("RespT/op", v1.Data.RespT, v2.Data.RespT)
87			changes += timeChange("50th-Lat", v1.Data.Fiftieth, v2.Data.Fiftieth)
88			changes += timeChange("90th-Lat", v1.Data.Ninetieth, v2.Data.Ninetieth)
89			changes += timeChange("99th-Lat", v1.Data.NinetyNinth, v2.Data.NinetyNinth)
90			changes += timeChange("Avg-Lat", v1.Data.Average, v2.Data.Average)
91			changes += strDiff("GoVersion", v1.GoVersion, v2.GoVersion)
92			changes += strDiff("GrpcVersion", v1.GrpcVersion, v2.GrpcVersion)
93			fmt.Printf("%s\n", changes)
94		}
95	}
96}
97
98func compareBenchmark(file1, file2 string) {
99	compareTwoMap(createMap(file1), createMap(file2))
100}
101
102func printHeader() {
103	fmt.Printf("%-80s%12s%12s%12s%18s%18s%18s%18s%12s%12s%12s%12s\n",
104		"Name", "TotalOps", "SendOps", "RecvOps", "Bytes/op (B)", "Allocs/op (#)",
105		"RequestT", "ResponseT", "L-50", "L-90", "L-99", "L-Avg")
106}
107
108func printline(benchName string, d stats.RunData) {
109	fmt.Printf("%-80s%12d%12d%12d%18.2f%18.2f%18.2f%18.2f%12v%12v%12v%12v\n",
110		benchName, d.TotalOps, d.SendOps, d.RecvOps, d.AllocedBytes, d.Allocs,
111		d.ReqT, d.RespT, d.Fiftieth, d.Ninetieth, d.NinetyNinth, d.Average)
112}
113
114func formatBenchmark(fileName string) {
115	f, err := os.Open(fileName)
116	if err != nil {
117		log.Fatalf("Read file %s error: %s\n", fileName, err)
118	}
119	defer f.Close()
120	var results []stats.BenchResults
121	decoder := gob.NewDecoder(f)
122	if err = decoder.Decode(&results); err != nil {
123		log.Fatalf("Decode file %s error: %s\n", fileName, err)
124	}
125	if len(results) == 0 {
126		log.Fatalf("No benchmark results in file %s\n", fileName)
127	}
128
129	fmt.Println("\nShared features:\n" + strings.Repeat("-", 20))
130	fmt.Print(results[0].Features.SharedFeatures(results[0].SharedFeatures))
131	fmt.Println(strings.Repeat("-", 35))
132
133	wantFeatures := results[0].SharedFeatures
134	for i := 0; i < len(results[0].SharedFeatures); i++ {
135		wantFeatures[i] = !wantFeatures[i]
136	}
137
138	printHeader()
139	for _, r := range results {
140		printline(r.RunMode+r.Features.PrintableName(wantFeatures), r.Data)
141	}
142}
143
144func main() {
145	if len(os.Args) == 2 {
146		formatBenchmark(os.Args[1])
147	} else {
148		compareBenchmark(os.Args[1], os.Args[2])
149	}
150}
151