// Copyright (c) 2016 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package main import ( "flag" "fmt" "io" "log" "os" "os/exec" "sort" "strconv" "strings" "text/template" "time" ) var ( libraryNameToMarkdownName = map[string]string{ "Zap": ":zap: zap", "Zap.Sugar": ":zap: zap (sugared)", "stdlib.Println": "standard library", "sirupsen/logrus": "logrus", "go-kit/kit/log": "go-kit", "inconshreveable/log15": "log15", "apex/log": "apex/log", "rs/zerolog": "zerolog", } ) func main() { flag.Parse() if err := do(); err != nil { log.Fatal(err) } } func do() error { tmplData, err := getTmplData() if err != nil { return err } data, err := io.ReadAll(os.Stdin) if err != nil { return err } t, err := template.New("tmpl").Parse(string(data)) if err != nil { return err } return t.Execute(os.Stdout, tmplData) } func getTmplData() (*tmplData, error) { tmplData := &tmplData{} rows, err := getBenchmarkRows("BenchmarkAddingFields") if err != nil { return nil, err } tmplData.BenchmarkAddingFields = rows rows, err = getBenchmarkRows("BenchmarkAccumulatedContext") if err != nil { return nil, err } tmplData.BenchmarkAccumulatedContext = rows rows, err = getBenchmarkRows("BenchmarkWithoutFields") if err != nil { return nil, err } tmplData.BenchmarkWithoutFields = rows return tmplData, nil } func getBenchmarkRows(benchmarkName string) (string, error) { benchmarkOutput, err := getBenchmarkOutput(benchmarkName) if err != nil { return "", err } // get the Zap time (unsugared) as baseline to compare with other loggers baseline, err := getBenchmarkRow(benchmarkOutput, benchmarkName, "Zap", nil) if err != nil { return "", err } var benchmarkRows []*benchmarkRow for libraryName := range libraryNameToMarkdownName { benchmarkRow, err := getBenchmarkRow( benchmarkOutput, benchmarkName, libraryName, baseline, ) if err != nil { return "", err } if benchmarkRow == nil { continue } benchmarkRows = append(benchmarkRows, benchmarkRow) } sort.Sort(benchmarkRowsByTime(benchmarkRows)) rows := []string{ "| Package | Time | Time % to zap | Objects Allocated |", "| :------ | :--: | :-----------: | :---------------: |", } for _, benchmarkRow := range benchmarkRows { rows = append(rows, benchmarkRow.String()) } return strings.Join(rows, "\n"), nil } func getBenchmarkRow( input []string, benchmarkName string, libraryName string, baseline *benchmarkRow, ) (*benchmarkRow, error) { line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName)) if err != nil { return nil, err } if line == "" { return nil, nil } split := strings.Split(line, "\t") if len(split) < 5 { return nil, fmt.Errorf("unknown benchmark line: %s", line) } duration, err := time.ParseDuration(strings.ReplaceAll(strings.TrimSuffix(strings.TrimSpace(split[2]), "/op"), " ", "")) if err != nil { return nil, err } allocatedBytes, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[3]), " B/op")) if err != nil { return nil, err } allocatedObjects, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[4]), " allocs/op")) if err != nil { return nil, err } r := &benchmarkRow{ Name: libraryNameToMarkdownName[libraryName], Time: duration, AllocatedBytes: allocatedBytes, AllocatedObjects: allocatedObjects, } if baseline != nil { r.ZapTime = baseline.Time r.ZapAllocatedBytes = baseline.AllocatedBytes r.ZapAllocatedObjects = baseline.AllocatedObjects } return r, nil } func findUniqueSubstring(input []string, substring string) (string, error) { var output string for _, line := range input { if strings.Contains(line, substring) { if output != "" { return "", fmt.Errorf("input has duplicate substring %s", substring) } output = line } } return output, nil } func getBenchmarkOutput(benchmarkName string) ([]string, error) { cmd := exec.Command("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem") cmd.Dir = "benchmarks" output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("error running 'go test -bench=%q': %v\n%s", benchmarkName, err, string(output)) } return strings.Split(string(output), "\n"), nil } type tmplData struct { BenchmarkAddingFields string BenchmarkAccumulatedContext string BenchmarkWithoutFields string } type benchmarkRow struct { Name string Time time.Duration AllocatedBytes int AllocatedObjects int ZapTime time.Duration ZapAllocatedBytes int ZapAllocatedObjects int } func (b *benchmarkRow) String() string { pct := func(val, baseline int64) string { return fmt.Sprintf( "%+0.f%%", ((float64(val)/float64(baseline))*100)-100, ) } t := b.Time.Nanoseconds() tp := pct(t, b.ZapTime.Nanoseconds()) return fmt.Sprintf( "| %s | %d ns/op | %s | %d allocs/op", b.Name, t, tp, b.AllocatedObjects, ) } type benchmarkRowsByTime []*benchmarkRow func (b benchmarkRowsByTime) Len() int { return len(b) } func (b benchmarkRowsByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b benchmarkRowsByTime) Less(i, j int) bool { left, right := b[i], b[j] leftZap, rightZap := strings.Contains(left.Name, "zap"), strings.Contains(right.Name, "zap") // If neither benchmark is for zap or both are, sort by time. if leftZap == rightZap { return left.Time.Nanoseconds() < right.Time.Nanoseconds() } // Sort zap benchmark first. return leftZap }