github.com/Laisky/zap@v1.27.0/internal/readme/readme.go (about) 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 }