1
2
3
4
5
6
7 package benchmark
8
9 import (
10 "fmt"
11 "time"
12
13 "github.com/montanaflynn/stats"
14 )
15
16 type BenchResult struct {
17 Name string
18 Trials int
19 Duration time.Duration
20 Raw []Result
21 DataSize int
22 Operations int
23 hasErrors *bool
24 }
25
26 type Metric struct {
27 Name string `json:"name"`
28 Value interface{} `json:"value"`
29 }
30
31 func (r *BenchResult) EvergreenPerfFormat() ([]interface{}, error) {
32 timings := r.timings()
33
34 median, err := stats.Median(timings)
35 if err != nil {
36 return nil, err
37 }
38
39 min, err := stats.Min(timings)
40 if err != nil {
41 return nil, err
42 }
43
44 max, err := stats.Max(timings)
45 if err != nil {
46 return nil, err
47 }
48
49 out := []interface{}{
50 map[string]interface{}{
51 "info": map[string]interface{}{
52 "test_name": r.Name + "-throughput",
53 "args": map[string]interface{}{
54 "threads": 1,
55 },
56 },
57 "metrics": []Metric{
58 {Name: "seconds", Value: r.Duration.Round(time.Millisecond).Seconds()},
59 {Name: "ops_per_second", Value: r.getThroughput(median)},
60 {Name: "ops_per_second_min", Value: r.getThroughput(min)},
61 {Name: "ops_per_second_max", Value: r.getThroughput(max)},
62 },
63 },
64 }
65
66 if r.DataSize > 0 {
67 out = append(out, interface{}(map[string]interface{}{
68 "info": map[string]interface{}{
69 "test_name": r.Name + "-MB-adjusted",
70 "args": map[string]interface{}{
71 "threads": 1,
72 },
73 },
74 "metrics": []Metric{
75 {Name: "seconds", Value: r.Duration.Round(time.Millisecond).Seconds()},
76 {Name: "ops_per_second", Value: r.adjustResults(median)},
77 {Name: "ops_per_second_min", Value: r.adjustResults(min)},
78 {Name: "ops_per_second_max", Value: r.adjustResults(max)},
79 },
80 }))
81 }
82
83 return out, nil
84 }
85
86 func (r *BenchResult) timings() []float64 {
87 out := []float64{}
88 for _, r := range r.Raw {
89 out = append(out, r.Duration.Seconds())
90 }
91 return out
92 }
93
94 func (r *BenchResult) totalDuration() time.Duration {
95 var out time.Duration
96 for _, trial := range r.Raw {
97 out += trial.Duration
98 }
99 return out
100 }
101
102 func (r *BenchResult) adjustResults(data float64) float64 { return float64(r.DataSize) / data }
103 func (r *BenchResult) getThroughput(data float64) float64 { return float64(r.Operations) / data }
104 func (r *BenchResult) roundedRuntime() time.Duration { return roundDurationMS(r.Duration) }
105
106 func (r *BenchResult) String() string {
107 return fmt.Sprintf("name=%s, trials=%d, secs=%s", r.Name, r.Trials, r.Duration)
108 }
109
110 func (r *BenchResult) HasErrors() bool {
111 if r.hasErrors == nil {
112 var val bool
113 for _, res := range r.Raw {
114 if res.Error != nil {
115 val = true
116 break
117 }
118 }
119 r.hasErrors = &val
120 }
121
122 return *r.hasErrors
123 }
124
125 func (r *BenchResult) errReport() []string {
126 errs := []string{}
127 for _, res := range r.Raw {
128 if res.Error != nil {
129 errs = append(errs, res.Error.Error())
130 }
131 }
132 return errs
133 }
134
135 type Result struct {
136 Duration time.Duration
137 Iterations int
138 Error error
139 }
140
141 func roundDurationMS(d time.Duration) time.Duration {
142 rounded := d.Round(time.Millisecond)
143 if rounded == 1<<63-1 {
144 return 0
145 }
146 return rounded
147 }
148
View as plain text