github.com/jgbaldwinbrown/perf@v0.1.1/benchfmt/writer.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package benchfmt 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 ) 12 13 // A Writer writes the Go benchmark format. 14 type Writer struct { 15 w io.Writer 16 buf bytes.Buffer 17 18 first bool 19 fileConfig map[string]Config 20 order []string 21 } 22 23 // NewWriter returns a writer that writes Go benchmark results to w. 24 func NewWriter(w io.Writer) *Writer { 25 return &Writer{w: w, first: true, fileConfig: make(map[string]Config)} 26 } 27 28 // Write writes Record rec to w. If rec is a *Result and rec's file 29 // configuration differs from the current file configuration in w, it 30 // first emits the appropriate file configuration lines. For 31 // Result.Values that have a non-zero OrigUnit, this uses OrigValue and 32 // OrigUnit in order to better reproduce the original input. 33 func (w *Writer) Write(rec Record) error { 34 switch rec := rec.(type) { 35 case *Result: 36 w.writeResult(rec) 37 case *UnitMetadata: 38 w.writeUnitMetadata(rec) 39 case *SyntaxError: 40 // Ignore 41 return nil 42 default: 43 return fmt.Errorf("unknown Record type %T", rec) 44 } 45 46 // Flush the buffer out to the io.Writer. Write to the buffer 47 // can't fail, so we only have to check if this fails. 48 _, err := w.w.Write(w.buf.Bytes()) 49 w.buf.Reset() 50 return err 51 } 52 53 func (w *Writer) writeResult(res *Result) { 54 // If any file config changed, write out the changes. 55 if len(w.fileConfig) != len(res.Config) { 56 w.writeFileConfig(res) 57 } else { 58 for _, cfg := range res.Config { 59 if have, ok := w.fileConfig[cfg.Key]; !ok || !bytes.Equal(cfg.Value, have.Value) || cfg.File != have.File { 60 w.writeFileConfig(res) 61 break 62 } 63 } 64 } 65 66 // Print the benchmark line. 67 fmt.Fprintf(&w.buf, "Benchmark%s %d", res.Name, res.Iters) 68 for _, val := range res.Values { 69 if val.OrigUnit == "" { 70 fmt.Fprintf(&w.buf, " %v %s", val.Value, val.Unit) 71 } else { 72 fmt.Fprintf(&w.buf, " %v %s", val.OrigValue, val.OrigUnit) 73 } 74 } 75 w.buf.WriteByte('\n') 76 77 w.first = false 78 } 79 80 func (w *Writer) writeFileConfig(res *Result) { 81 if !w.first { 82 // Configuration blocks after results get an extra blank. 83 w.buf.WriteByte('\n') 84 w.first = true 85 } 86 87 // Walk keys we know to find changes and deletions. 88 for i := 0; i < len(w.order); i++ { 89 key := w.order[i] 90 have := w.fileConfig[key] 91 idx, ok := res.ConfigIndex(key) 92 if !ok { 93 // Key was deleted. 94 fmt.Fprintf(&w.buf, "%s:\n", key) 95 delete(w.fileConfig, key) 96 copy(w.order[i:], w.order[i+1:]) 97 w.order = w.order[:len(w.order)-1] 98 i-- 99 continue 100 } 101 cfg := &res.Config[idx] 102 if bytes.Equal(have.Value, cfg.Value) && have.File == cfg.File { 103 // Value did not change. 104 continue 105 } 106 // Value changed. 107 if cfg.File { 108 // Omit internal config. 109 fmt.Fprintf(&w.buf, "%s: %s\n", key, cfg.Value) 110 } 111 have.Value = append(have.Value[:0], cfg.Value...) 112 have.File = cfg.File 113 w.fileConfig[key] = have 114 } 115 116 // Find new keys. 117 if len(w.fileConfig) != len(res.Config) { 118 for _, cfg := range res.Config { 119 if _, ok := w.fileConfig[cfg.Key]; ok { 120 continue 121 } 122 // New key. 123 if cfg.File { 124 fmt.Fprintf(&w.buf, "%s: %s\n", cfg.Key, cfg.Value) 125 } 126 w.fileConfig[cfg.Key] = Config{cfg.Key, append([]byte(nil), cfg.Value...), cfg.File} 127 w.order = append(w.order, cfg.Key) 128 } 129 } 130 131 w.buf.WriteByte('\n') 132 } 133 134 func (w *Writer) writeUnitMetadata(m *UnitMetadata) { 135 fmt.Fprintf(&w.buf, "Unit %s %s=%s\n", m.OrigUnit, m.Key, m.Value) 136 }