| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package benchfmt | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "io" | |
| ) | |
| // A Writer writes the Go benchmark format. | |
| type Writer struct { | |
| w io.Writer | |
| buf bytes.Buffer | |
| first bool | |
| fileConfig map[string]Config | |
| order []string | |
| } | |
| // NewWriter returns a writer that writes Go benchmark results to w. | |
| func NewWriter(w io.Writer) *Writer { | |
| return &Writer{w: w, first: true, fileConfig: make(map[string]Config)} | |
| } | |
| // Write writes Record rec to w. If rec is a *Result and rec's file | |
| // configuration differs from the current file configuration in w, it | |
| // first emits the appropriate file configuration lines. For | |
| // Result.Values that have a non-zero OrigUnit, this uses OrigValue and | |
| // OrigUnit in order to better reproduce the original input. | |
| func (w *Writer) Write(rec Record) error { | |
| switch rec := rec.(type) { | |
| case *Result: | |
| w.writeResult(rec) | |
| case *UnitMetadata: | |
| w.writeUnitMetadata(rec) | |
| case *SyntaxError: | |
| // Ignore | |
| return nil | |
| default: | |
| return fmt.Errorf("unknown Record type %T", rec) | |
| } | |
| // Flush the buffer out to the io.Writer. Write to the buffer | |
| // can't fail, so we only have to check if this fails. | |
| _, err := w.w.Write(w.buf.Bytes()) | |
| w.buf.Reset() | |
| return err | |
| } | |
| func (w *Writer) writeResult(res *Result) { | |
| // If any file config changed, write out the changes. | |
| if len(w.fileConfig) != len(res.Config) { | |
| w.writeFileConfig(res) | |
| } else { | |
| for _, cfg := range res.Config { | |
| if have, ok := w.fileConfig[cfg.Key]; !ok || !bytes.Equal(cfg.Value, have.Value) || cfg.File != have.File { | |
| w.writeFileConfig(res) | |
| break | |
| } | |
| } | |
| } | |
| // Print the benchmark line. | |
| fmt.Fprintf(&w.buf, "Benchmark%s %d", res.Name, res.Iters) | |
| for _, val := range res.Values { | |
| if val.OrigUnit == "" { | |
| fmt.Fprintf(&w.buf, " %v %s", val.Value, val.Unit) | |
| } else { | |
| fmt.Fprintf(&w.buf, " %v %s", val.OrigValue, val.OrigUnit) | |
| } | |
| } | |
| w.buf.WriteByte('\n') | |
| w.first = false | |
| } | |
| func (w *Writer) writeFileConfig(res *Result) { | |
| if !w.first { | |
| // Configuration blocks after results get an extra blank. | |
| w.buf.WriteByte('\n') | |
| w.first = true | |
| } | |
| // Walk keys we know to find changes and deletions. | |
| for i := 0; i < len(w.order); i++ { | |
| key := w.order[i] | |
| have := w.fileConfig[key] | |
| idx, ok := res.ConfigIndex(key) | |
| if !ok { | |
| // Key was deleted. | |
| fmt.Fprintf(&w.buf, "%s:\n", key) | |
| delete(w.fileConfig, key) | |
| copy(w.order[i:], w.order[i+1:]) | |
| w.order = w.order[:len(w.order)-1] | |
| i-- | |
| continue | |
| } | |
| cfg := &res.Config[idx] | |
| if bytes.Equal(have.Value, cfg.Value) && have.File == cfg.File { | |
| // Value did not change. | |
| continue | |
| } | |
| // Value changed. | |
| if cfg.File { | |
| // Omit internal config. | |
| fmt.Fprintf(&w.buf, "%s: %s\n", key, cfg.Value) | |
| } | |
| have.Value = append(have.Value[:0], cfg.Value...) | |
| have.File = cfg.File | |
| w.fileConfig[key] = have | |
| } | |
| // Find new keys. | |
| if len(w.fileConfig) != len(res.Config) { | |
| for _, cfg := range res.Config { | |
| if _, ok := w.fileConfig[cfg.Key]; ok { | |
| continue | |
| } | |
| // New key. | |
| if cfg.File { | |
| fmt.Fprintf(&w.buf, "%s: %s\n", cfg.Key, cfg.Value) | |
| } | |
| w.fileConfig[cfg.Key] = Config{cfg.Key, append([]byte(nil), cfg.Value...), cfg.File} | |
| w.order = append(w.order, cfg.Key) | |
| } | |
| } | |
| w.buf.WriteByte('\n') | |
| } | |
| func (w *Writer) writeUnitMetadata(m *UnitMetadata) { | |
| fmt.Fprintf(&w.buf, "Unit %s %s=%s\n", m.OrigUnit, m.Key, m.Value) | |
| } |