1// Copyright (c) 2016 Uber Technologies, Inc.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21package main
22
23import (
24 "flag"
25 "fmt"
26 "io/ioutil"
27 "log"
28 "os"
29 "os/exec"
30 "sort"
31 "strconv"
32 "strings"
33 "text/template"
34 "time"
35)
36
37var (
38 libraryNameToMarkdownName = map[string]string{
39 "Zap": ":zap: zap",
40 "Zap.Sugar": ":zap: zap (sugared)",
41 "stdlib.Println": "standard library",
42 "sirupsen/logrus": "logrus",
43 "go-kit/kit/log": "go-kit",
44 "inconshreveable/log15": "log15",
45 "apex/log": "apex/log",
46 "go.pedge.io/lion": "lion",
47 "rs/zerolog": "zerolog",
48 }
49)
50
51func main() {
52 flag.Parse()
53 if err := do(); err != nil {
54 log.Fatal(err)
55 }
56}
57
58func do() error {
59 tmplData, err := getTmplData()
60 if err != nil {
61 return err
62 }
63 data, err := ioutil.ReadAll(os.Stdin)
64 if err != nil {
65 return err
66 }
67 t, err := template.New("tmpl").Parse(string(data))
68 if err != nil {
69 return err
70 }
71 return t.Execute(os.Stdout, tmplData)
72}
73
74func getTmplData() (*tmplData, error) {
75 tmplData := &tmplData{}
76 rows, err := getBenchmarkRows("BenchmarkAddingFields")
77 if err != nil {
78 return nil, err
79 }
80 tmplData.BenchmarkAddingFields = rows
81 rows, err = getBenchmarkRows("BenchmarkAccumulatedContext")
82 if err != nil {
83 return nil, err
84 }
85 tmplData.BenchmarkAccumulatedContext = rows
86 rows, err = getBenchmarkRows("BenchmarkWithoutFields")
87 if err != nil {
88 return nil, err
89 }
90 tmplData.BenchmarkWithoutFields = rows
91 return tmplData, nil
92}
93
94func getBenchmarkRows(benchmarkName string) (string, error) {
95 benchmarkOutput, err := getBenchmarkOutput(benchmarkName)
96 if err != nil {
97 return "", err
98 }
99 var benchmarkRows []*benchmarkRow
100 for libraryName := range libraryNameToMarkdownName {
101 benchmarkRow, err := getBenchmarkRow(benchmarkOutput, benchmarkName, libraryName)
102 if err != nil {
103 return "", err
104 }
105 if benchmarkRow == nil {
106 continue
107 }
108 benchmarkRows = append(benchmarkRows, benchmarkRow)
109 }
110 sort.Sort(benchmarkRowsByTime(benchmarkRows))
111 rows := []string{
112 "| Package | Time | Objects Allocated |",
113 "| :--- | :---: | :---: |",
114 }
115 for _, benchmarkRow := range benchmarkRows {
116 rows = append(rows, benchmarkRow.String())
117 }
118 return strings.Join(rows, "\n"), nil
119}
120
121func getBenchmarkRow(input []string, benchmarkName string, libraryName string) (*benchmarkRow, error) {
122 line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName))
123 if err != nil {
124 return nil, err
125 }
126 if line == "" {
127 return nil, nil
128 }
129 split := strings.Split(line, "\t")
130 if len(split) < 5 {
131 return nil, fmt.Errorf("unknown benchmark line: %s", line)
132 }
133 duration, err := time.ParseDuration(strings.Replace(strings.TrimSuffix(strings.TrimSpace(split[2]), "/op"), " ", "", -1))
134 if err != nil {
135 return nil, err
136 }
137 allocatedBytes, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[3]), " B/op"))
138 if err != nil {
139 return nil, err
140 }
141 allocatedObjects, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[4]), " allocs/op"))
142 if err != nil {
143 return nil, err
144 }
145 return &benchmarkRow{
146 libraryNameToMarkdownName[libraryName],
147 duration,
148 allocatedBytes,
149 allocatedObjects,
150 }, nil
151}
152
153func findUniqueSubstring(input []string, substring string) (string, error) {
154 var output string
155 for _, line := range input {
156 if strings.Contains(line, substring) {
157 if output != "" {
158 return "", fmt.Errorf("input has duplicate substring %s", substring)
159 }
160 output = line
161 }
162 }
163 return output, nil
164}
165
166func getBenchmarkOutput(benchmarkName string) ([]string, error) {
167 return getOutput("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem", "./benchmarks")
168}
169
170func getOutput(name string, arg ...string) ([]string, error) {
171 output, err := exec.Command(name, arg...).CombinedOutput()
172 if err != nil {
173 return nil, fmt.Errorf("error running %s %s: %v\n%s", name, strings.Join(arg, " "), err, string(output))
174 }
175 return strings.Split(string(output), "\n"), nil
176}
177
178type tmplData struct {
179 BenchmarkAddingFields string
180 BenchmarkAccumulatedContext string
181 BenchmarkWithoutFields string
182}
183
184type benchmarkRow struct {
185 Name string
186 Time time.Duration
187 AllocatedBytes int
188 AllocatedObjects int
189}
190
191func (b *benchmarkRow) String() string {
192 return fmt.Sprintf("| %s | %d ns/op | %d allocs/op |", b.Name, b.Time.Nanoseconds(), b.AllocatedObjects)
193}
194
195type benchmarkRowsByTime []*benchmarkRow
196
197func (b benchmarkRowsByTime) Len() int { return len(b) }
198func (b benchmarkRowsByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
199func (b benchmarkRowsByTime) Less(i, j int) bool {
200 left, right := b[i], b[j]
201 leftZap, rightZap := strings.Contains(left.Name, "zap"), strings.Contains(right.Name, "zap")
202
203 // If neither benchmark is for zap or both are, sort by time.
204 if !(leftZap || rightZap) || (leftZap && rightZap) {
205 return left.Time.Nanoseconds() < right.Time.Nanoseconds()
206 }
207 // Sort zap benchmark first.
208 return leftZap
209}
210