1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package main
23
24 import (
25 "flag"
26 "fmt"
27 "io"
28 "log"
29 "os"
30 "os/exec"
31 "sort"
32 "strconv"
33 "strings"
34 "text/template"
35 "time"
36 )
37
38 var 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 "rs/zerolog": "zerolog",
47 "slog": "slog",
48 "slog.LogAttrs": "slog (LogAttrs)",
49 }
50
51 func main() {
52 flag.Parse()
53 if err := do(); err != nil {
54 log.Fatal(err)
55 }
56 }
57
58 func do() error {
59 tmplData, err := getTmplData()
60 if err != nil {
61 return err
62 }
63 data, err := io.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
74 func 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
94 func getBenchmarkRows(benchmarkName string) (string, error) {
95 benchmarkOutput, err := getBenchmarkOutput(benchmarkName)
96 if err != nil {
97 return "", err
98 }
99
100
101 baseline, err := getBenchmarkRow(benchmarkOutput, benchmarkName, "Zap", nil)
102 if err != nil {
103 return "", err
104 }
105
106 var benchmarkRows []*benchmarkRow
107 for libraryName := range libraryNameToMarkdownName {
108 benchmarkRow, err := getBenchmarkRow(
109 benchmarkOutput, benchmarkName, libraryName, baseline,
110 )
111 if err != nil {
112 return "", err
113 }
114 if benchmarkRow == nil {
115 continue
116 }
117 benchmarkRows = append(benchmarkRows, benchmarkRow)
118 }
119 sort.Sort(benchmarkRowsByTime(benchmarkRows))
120 rows := []string{
121 "| Package | Time | Time % to zap | Objects Allocated |",
122 "| :------ | :--: | :-----------: | :---------------: |",
123 }
124 for _, benchmarkRow := range benchmarkRows {
125 rows = append(rows, benchmarkRow.String())
126 }
127 return strings.Join(rows, "\n"), nil
128 }
129
130 func getBenchmarkRow(
131 input []string, benchmarkName string, libraryName string, baseline *benchmarkRow,
132 ) (*benchmarkRow, error) {
133 line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName))
134 if err != nil {
135 return nil, err
136 }
137 if line == "" {
138 return nil, nil
139 }
140 split := strings.Split(line, "\t")
141 if len(split) < 5 {
142 return nil, fmt.Errorf("unknown benchmark line: %s", line)
143 }
144 duration, err := time.ParseDuration(strings.ReplaceAll(strings.TrimSuffix(strings.TrimSpace(split[2]), "/op"), " ", ""))
145 if err != nil {
146 return nil, err
147 }
148 allocatedBytes, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[3]), " B/op"))
149 if err != nil {
150 return nil, err
151 }
152 allocatedObjects, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[4]), " allocs/op"))
153 if err != nil {
154 return nil, err
155 }
156 r := &benchmarkRow{
157 Name: libraryNameToMarkdownName[libraryName],
158 Time: duration,
159 AllocatedBytes: allocatedBytes,
160 AllocatedObjects: allocatedObjects,
161 }
162
163 if baseline != nil {
164 r.ZapTime = baseline.Time
165 r.ZapAllocatedBytes = baseline.AllocatedBytes
166 r.ZapAllocatedObjects = baseline.AllocatedObjects
167 }
168
169 return r, nil
170 }
171
172 func findUniqueSubstring(input []string, substring string) (string, error) {
173 var output string
174 for _, line := range input {
175 if strings.Contains(line, substring) {
176 if output != "" {
177 return "", fmt.Errorf("input has duplicate substring %s", substring)
178 }
179 output = line
180 }
181 }
182 return output, nil
183 }
184
185 func getBenchmarkOutput(benchmarkName string) ([]string, error) {
186 cmd := exec.Command("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem")
187 cmd.Dir = "benchmarks"
188 output, err := cmd.CombinedOutput()
189 if err != nil {
190 return nil, fmt.Errorf("error running 'go test -bench=%q': %v\n%s", benchmarkName, err, string(output))
191 }
192 return strings.Split(string(output), "\n"), nil
193 }
194
195 type tmplData struct {
196 BenchmarkAddingFields string
197 BenchmarkAccumulatedContext string
198 BenchmarkWithoutFields string
199 }
200
201 type benchmarkRow struct {
202 Name string
203
204 Time time.Duration
205 AllocatedBytes int
206 AllocatedObjects int
207
208 ZapTime time.Duration
209 ZapAllocatedBytes int
210 ZapAllocatedObjects int
211 }
212
213 func (b *benchmarkRow) String() string {
214 pct := func(val, baseline int64) string {
215 return fmt.Sprintf(
216 "%+0.f%%",
217 ((float64(val)/float64(baseline))*100)-100,
218 )
219 }
220 t := b.Time.Nanoseconds()
221 tp := pct(t, b.ZapTime.Nanoseconds())
222
223 return fmt.Sprintf(
224 "| %s | %d ns/op | %s | %d allocs/op", b.Name,
225 t, tp, b.AllocatedObjects,
226 )
227 }
228
229 type benchmarkRowsByTime []*benchmarkRow
230
231 func (b benchmarkRowsByTime) Len() int { return len(b) }
232 func (b benchmarkRowsByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
233 func (b benchmarkRowsByTime) Less(i, j int) bool {
234 left, right := b[i], b[j]
235 leftZap, rightZap := strings.Contains(left.Name, "zap"), strings.Contains(right.Name, "zap")
236
237
238 if leftZap == rightZap {
239 return left.Time.Nanoseconds() < right.Time.Nanoseconds()
240 }
241
242 return leftZap
243 }
244
View as plain text