...

Source file src/go.uber.org/zap/internal/readme/readme.go

Documentation: go.uber.org/zap/internal/readme

     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  
    21  // readme generates Zap's README from a template.
    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  	// get the Zap time (unsugared) as baseline to compare with other loggers
   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  	// If neither benchmark is for zap or both are, sort by time.
   238  	if leftZap == rightZap {
   239  		return left.Time.Nanoseconds() < right.Time.Nanoseconds()
   240  	}
   241  	// Sort zap benchmark first.
   242  	return leftZap
   243  }
   244  

View as plain text