1 | module benchmark |
2 | |
3 | import time |
4 | import term |
5 | |
6 | pub const ( |
7 | b_ok = term.ok_message('OK ') |
8 | b_fail = term.fail_message('FAIL') |
9 | b_skip = term.warn_message('SKIP') |
10 | b_spent = term.ok_message('SPENT') |
11 | ) |
12 | |
13 | pub struct Benchmark { |
14 | pub mut: |
15 | bench_timer time.StopWatch |
16 | verbose bool |
17 | no_cstep bool |
18 | step_timer time.StopWatch |
19 | ntotal int |
20 | nok int |
21 | nfail int |
22 | nskip int |
23 | nexpected_steps int |
24 | njobs int |
25 | cstep int |
26 | bok string |
27 | bfail string |
28 | } |
29 | |
30 | // new_benchmark returns a `Benchmark` instance on the stack. |
31 | pub fn new_benchmark() Benchmark { |
32 | return Benchmark{ |
33 | bench_timer: time.new_stopwatch() |
34 | verbose: true |
35 | } |
36 | } |
37 | |
38 | // new_benchmark_no_cstep returns a new `Benchmark` instance with step counting disabled. |
39 | pub fn new_benchmark_no_cstep() Benchmark { |
40 | return Benchmark{ |
41 | bench_timer: time.new_stopwatch() |
42 | verbose: true |
43 | no_cstep: true |
44 | } |
45 | } |
46 | |
47 | // new_benchmark_pointer returns a new `Benchmark` instance allocated on the heap. |
48 | // This is useful for long-lived use of `Benchmark` instances. |
49 | pub fn new_benchmark_pointer() &Benchmark { |
50 | return &Benchmark{ |
51 | bench_timer: time.new_stopwatch() |
52 | verbose: true |
53 | } |
54 | } |
55 | |
56 | // set_total_expected_steps sets the the total amount of steps the benchmark is expected to take. |
57 | pub fn (mut b Benchmark) set_total_expected_steps(n int) { |
58 | b.nexpected_steps = n |
59 | } |
60 | |
61 | // stop stops the internal benchmark timer. |
62 | pub fn (mut b Benchmark) stop() { |
63 | b.bench_timer.stop() |
64 | } |
65 | |
66 | // step increases the step count by 1 and restarts the internal timer. |
67 | pub fn (mut b Benchmark) step() { |
68 | b.step_timer.restart() |
69 | if !b.no_cstep { |
70 | b.cstep++ |
71 | } |
72 | } |
73 | |
74 | // fail increases the fail count by 1 and stops the internal timer. |
75 | pub fn (mut b Benchmark) fail() { |
76 | b.step_timer.stop() |
77 | b.ntotal++ |
78 | b.nfail++ |
79 | } |
80 | |
81 | // ok increases the ok count by 1 and stops the internal timer. |
82 | pub fn (mut b Benchmark) ok() { |
83 | b.step_timer.stop() |
84 | b.ntotal++ |
85 | b.nok++ |
86 | } |
87 | |
88 | // skip increases the skip count by 1 and stops the internal timer. |
89 | pub fn (mut b Benchmark) skip() { |
90 | b.step_timer.stop() |
91 | b.ntotal++ |
92 | b.nskip++ |
93 | } |
94 | |
95 | // fail_many increases the fail count by `n` and stops the internal timer. |
96 | pub fn (mut b Benchmark) fail_many(n int) { |
97 | b.step_timer.stop() |
98 | b.ntotal += n |
99 | b.nfail += n |
100 | } |
101 | |
102 | // ok_many increases the ok count by `n` and stops the internal timer. |
103 | pub fn (mut b Benchmark) ok_many(n int) { |
104 | b.step_timer.stop() |
105 | b.ntotal += n |
106 | b.nok += n |
107 | } |
108 | |
109 | // neither_fail_nor_ok stops the internal timer. |
110 | pub fn (mut b Benchmark) neither_fail_nor_ok() { |
111 | b.step_timer.stop() |
112 | } |
113 | |
114 | // start returns a new, running, instance of `Benchmark`. |
115 | // This is a shorthand for calling `new_benchmark().step()`. |
116 | pub fn start() Benchmark { |
117 | mut b := new_benchmark() |
118 | b.step() |
119 | return b |
120 | } |
121 | |
122 | // measure prints the current time spent doing `label`, since the benchmark was started, or since its last call |
123 | pub fn (mut b Benchmark) measure(label string) i64 { |
124 | b.ok() |
125 | res := b.step_timer.elapsed().microseconds() |
126 | println(b.step_message_with_label(benchmark.b_spent, 'in ${label}')) |
127 | b.step() |
128 | return res |
129 | } |
130 | |
131 | // step_message_with_label_and_duration returns a string describing the current step. |
132 | pub fn (b &Benchmark) step_message_with_label_and_duration(label string, msg string, sduration time.Duration) string { |
133 | timed_line := b.tdiff_in_ms(msg, sduration.microseconds()) |
134 | if b.nexpected_steps > 1 { |
135 | mut sprogress := '' |
136 | if b.nexpected_steps < 10 { |
137 | sprogress = if b.no_cstep { |
138 | 'TMP1/${b.nexpected_steps:1d}' |
139 | } else { |
140 | '${b.cstep:1d}/${b.nexpected_steps:1d}' |
141 | } |
142 | } else if b.nexpected_steps >= 10 && b.nexpected_steps < 100 { |
143 | sprogress = if b.no_cstep { |
144 | 'TMP2/${b.nexpected_steps:2d}' |
145 | } else { |
146 | '${b.cstep:2d}/${b.nexpected_steps:2d}' |
147 | } |
148 | } else if b.nexpected_steps >= 100 && b.nexpected_steps < 1000 { |
149 | sprogress = if b.no_cstep { |
150 | 'TMP3/${b.nexpected_steps:3d}' |
151 | } else { |
152 | '${b.cstep:3d}/${b.nexpected_steps:3d}' |
153 | } |
154 | } else { |
155 | sprogress = if b.no_cstep { |
156 | 'TMP4/${b.nexpected_steps:4d}' |
157 | } else { |
158 | '${b.cstep:4d}/${b.nexpected_steps:4d}' |
159 | } |
160 | } |
161 | return '${label:-5s} [${sprogress}] ${timed_line}' |
162 | } |
163 | return '${label:-5s}${timed_line}' |
164 | } |
165 | |
166 | // step_message_with_label returns a string describing the current step using current time as duration. |
167 | pub fn (b &Benchmark) step_message_with_label(label string, msg string) string { |
168 | return b.step_message_with_label_and_duration(label, msg, b.step_timer.elapsed()) |
169 | } |
170 | |
171 | // step_message returns a string describing the current step. |
172 | pub fn (b &Benchmark) step_message(msg string) string { |
173 | return b.step_message_with_label('', msg) |
174 | } |
175 | |
176 | // step_message_ok returns a string describing the current step with an standard "OK" label. |
177 | pub fn (b &Benchmark) step_message_ok(msg string) string { |
178 | return b.step_message_with_label(benchmark.b_ok, msg) |
179 | } |
180 | |
181 | // step_message_fail returns a string describing the current step with an standard "FAIL" label. |
182 | pub fn (b &Benchmark) step_message_fail(msg string) string { |
183 | return b.step_message_with_label(benchmark.b_fail, msg) |
184 | } |
185 | |
186 | // step_message_skip returns a string describing the current step with an standard "SKIP" label. |
187 | pub fn (b &Benchmark) step_message_skip(msg string) string { |
188 | return b.step_message_with_label(benchmark.b_skip, msg) |
189 | } |
190 | |
191 | // total_message returns a string with total summary of the benchmark run. |
192 | pub fn (b &Benchmark) total_message(msg string) string { |
193 | the_label := term.colorize(term.gray, msg) |
194 | mut tmsg := term.colorize(term.bold, 'Summary for ${the_label}:') + ' ' |
195 | if b.nfail > 0 { |
196 | tmsg += term.colorize(term.bold, term.colorize(term.red, '${b.nfail} failed')) + ', ' |
197 | } |
198 | if b.nok > 0 { |
199 | tmsg += term.colorize(term.bold, term.colorize(term.green, '${b.nok} passed')) + ', ' |
200 | } |
201 | if b.nskip > 0 { |
202 | tmsg += term.colorize(term.bold, term.colorize(term.yellow, '${b.nskip} skipped')) + ', ' |
203 | } |
204 | mut njobs_label := '' |
205 | if b.njobs > 0 { |
206 | if b.njobs == 1 { |
207 | njobs_label = ', on ${term.colorize(term.bold, 1.str())} job' |
208 | } else { |
209 | njobs_label = ', on ${term.colorize(term.bold, b.njobs.str())} parallel jobs' |
210 | } |
211 | } |
212 | tmsg += '${b.ntotal} total. ${term.colorize(term.bold, 'Runtime:')} ${b.bench_timer.elapsed().microseconds() / 1000} ms${njobs_label}.\n' |
213 | return tmsg |
214 | } |
215 | |
216 | // total_duration returns the duration in ms. |
217 | pub fn (b &Benchmark) total_duration() i64 { |
218 | return b.bench_timer.elapsed().milliseconds() |
219 | } |
220 | |
221 | // tdiff_in_ms prefixes `s` with a time difference calculation. |
222 | fn (b &Benchmark) tdiff_in_ms(s string, tdiff i64) string { |
223 | if b.verbose { |
224 | return '${f64(tdiff) / 1000.0:9.3f} ms ${s}' |
225 | } |
226 | return s |
227 | } |