package obistats // // Dupplicated code from internal module available at : // https://github.com/golang-design/bench.git // import ( "fmt" ) // A Collection is a collection of benchmark results. type Collection struct { // Configs, Groups, and Units give the set of configs, // groups, and units from the keys in Stats in an order // meant to match the order the benchmarks were read in. Configs, Groups, Units []string // Benchmarks gives the set of benchmarks from the keys in // Stats by group in an order meant to match the order // benchmarks were read in. Benchmarks map[string][]string // Metrics holds the accumulated metrics for each key. Metrics map[Key]*Metrics // DeltaTest is the test to use to decide if a change is significant. // If nil, it defaults to UTest. DeltaTest DeltaTest // Alpha is the p-value cutoff to report a change as significant. // If zero, it defaults to 0.05. Alpha float64 // AddGeoMean specifies whether to add a line to the table // showing the geometric mean of all the benchmark results. AddGeoMean bool // SplitBy specifies the labels to split results by. // By default, results will only be split by full name. SplitBy []string // Order specifies the row display order for this table. // If Order is nil, the table rows are printed in order of // first appearance in the input. Order Order } // A Key identifies one metric (e.g., "ns/op", "B/op") from one // benchmark (function name sans "Benchmark" prefix) and optional // group in one configuration (input file name). type Key struct { Config, Group, Benchmark, Unit string } // A Metrics holds the measurements of a single metric // (for example, ns/op or MB/s) // for all runs of a particular benchmark. type Metrics struct { Unit string // unit being measured Values []float64 // measured values RValues []float64 // Values with outliers removed Min float64 // min of RValues Mean float64 // mean of RValues Max float64 // max of RValues } // FormatMean formats m.Mean using scaler. func (m *Metrics) FormatMean(scaler Scaler) string { var s string if scaler != nil { s = scaler(m.Mean) } else { s = fmt.Sprint(m.Mean) } return s } // FormatDiff computes and formats the percent variation of max and min compared to mean. // If b.Mean or b.Max is zero, FormatDiff returns an empty string. func (m *Metrics) FormatDiff() string { if m.Mean == 0 || m.Max == 0 { return "" } diff := 1 - m.Min/m.Mean if d := m.Max/m.Mean - 1; d > diff { diff = d } return fmt.Sprintf("±%.0f%%", diff*100.0) } // Format returns a textual formatting of "Mean ±Diff" using scaler. func (m *Metrics) Format(scaler Scaler) string { if m.Unit == "" { return "" } mean := m.FormatMean(scaler) diff := m.FormatDiff() if diff == "" { return mean + " " } return fmt.Sprintf("%s %3s", mean, diff) } // computeStats updates the derived statistics in m from the raw // samples in m.Values. func (m *Metrics) computeStats() { // Discard outliers. values := Sample{Xs: m.Values} q1, q3 := values.Percentile(0.25), values.Percentile(0.75) lo, hi := q1-1.5*(q3-q1), q3+1.5*(q3-q1) for _, value := range m.Values { if lo <= value && value <= hi { m.RValues = append(m.RValues, value) } } // Compute statistics of remaining data. m.Min, m.Max = Bounds(m.RValues) m.Mean = Mean(m.RValues) } // addMetrics returns the metrics with the given key from c, // creating a new one if needed. func (c *Collection) addMetrics(key Key) *Metrics { if c.Metrics == nil { c.Metrics = make(map[Key]*Metrics) } if stat, ok := c.Metrics[key]; ok { return stat } addString := func(strings *[]string, add string) { for _, s := range *strings { if s == add { return } } *strings = append(*strings, add) } addString(&c.Configs, key.Config) addString(&c.Groups, key.Group) if c.Benchmarks == nil { c.Benchmarks = make(map[string][]string) } benchmarks := c.Benchmarks[key.Group] addString(&benchmarks, key.Benchmark) c.Benchmarks[key.Group] = benchmarks addString(&c.Units, key.Unit) m := &Metrics{Unit: key.Unit} c.Metrics[key] = m return m } // // AddFile adds the benchmark results in the formatted data // // (read from the reader r) to the named configuration. // func (c *Collection) AddFile(config string, f io.Reader) error { // c.Configs = append(c.Configs, config) // key := Key{Config: config} // br := benchfmt.NewReader(f) // for br.Next() { // c.addResult(key, br.Result()) // } // return br.Err() // } // // AddData adds the benchmark results in the formatted data // // (read from the reader r) to the named configuration. // func (c *Collection) AddData(config string, data []byte) error { // return c.AddFile(config, bytes.NewReader(data)) // } // AddResults adds the benchmark results to the named configuration. // func (c *Collection) AddResults(config string, results []*benchfmt.Result) { // c.Configs = append(c.Configs, config) // key := Key{Config: config} // for _, r := range results { // c.addResult(key, r) // } // } // func (c *Collection) addResult(key Key, r *benchfmt.Result) { // f := strings.Fields(r.Content) // if len(f) < 4 { // return // } // name := f[0] // if !strings.HasPrefix(name, "Benchmark") { // return // } // name = strings.TrimPrefix(name, "Benchmark") // n, _ := strconv.Atoi(f[1]) // if n == 0 { // return // } // key.Group = c.makeGroup(r) // key.Benchmark = name // for i := 2; i+2 <= len(f); i += 2 { // val, err := strconv.ParseFloat(f[i], 64) // if err != nil { // continue // } // key.Unit = f[i+1] // m := c.addMetrics(key) // m.Values = append(m.Values, val) // } // } // func (c *Collection) makeGroup(r *benchfmt.Result) string { // var out string // for _, s := range c.SplitBy { // v := r.NameLabels[s] // if v == "" { // v = r.Labels[s] // } // if v != "" { // if out != "" { // out = out + " " // } // out += fmt.Sprintf("%s:%s", s, v) // } // } // return out // }