source: mainline/uspace/app/hbench/main.c

Last change on this file was 871cff9a, checked in by Vojtech Horky <vojtech.horky@…>, 6 years ago

hbench: add options to set duration and run count

  • Property mode set to 100644
File size: 11.6 KB
RevLine 
[d230358]1/*
2 * Copyright (c) 2018 Jiri Svoboda
[3bd74758]3 * Copyright (c) 2018 Vojtech Horky
[d230358]4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
[fe656783]30/** @addtogroup hbench
[d230358]31 * @{
32 */
33/**
34 * @file
35 */
36
[713ba400]37#include <assert.h>
[60029df]38#include <getopt.h>
[b4a4ad94]39#include <math.h>
[d230358]40#include <stdio.h>
41#include <stddef.h>
42#include <stdlib.h>
43#include <str.h>
[3bd74758]44#include <time.h>
45#include <errno.h>
[60029df]46#include <str_error.h>
[3bd74758]47#include <perf.h>
[713ba400]48#include <types/casting.h>
[fe656783]49#include "hbench.h"
[d230358]50
[3bd74758]51#define MAX_ERROR_STR_LENGTH 1024
52
[e7f9a09]53static void short_report(bench_run_t *info, int run_index,
[79bb48e]54 benchmark_t *bench, uint64_t workload_size)
[3bd74758]55{
[e7f9a09]56 csv_report_add_entry(info, run_index, bench, workload_size);
[60029df]57
[e7f9a09]58 usec_t duration_usec = NSEC2USEC(stopwatch_get_nanos(&info->stopwatch));
[3bd74758]59
[79bb48e]60 printf("Completed %" PRIu64 " operations in %llu us",
[3bd74758]61 workload_size, duration_usec);
62 if (duration_usec > 0) {
[e7f9a09]63 double nanos = stopwatch_get_nanos(&info->stopwatch);
[c7de81b]64 double thruput = (double) workload_size / (nanos / 1000000000.0l);
[b4a4ad94]65 printf(", %.0f ops/s.\n", thruput);
[3bd74758]66 } else {
67 printf(".\n");
68 }
69}
70
[94ebebf]71/** Estimate square root value.
72 *
73 * @param value The value to compute square root of.
74 * @param precision Required precision (e.g. 0.00001).
75 *
76 * @details
77 *
[b4a4ad94]78 * This is a temporary solution until we have proper sqrt() implementation
79 * in libmath.
80 *
81 * The algorithm uses Babylonian method [1].
82 *
83 * [1] https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method
84 */
85static double estimate_square_root(double value, double precision)
[3bd74758]86{
[b4a4ad94]87 double estimate = 1.;
88 double prev_estimate = estimate + 10 * precision;
[3bd74758]89
[b4a4ad94]90 while (fabs(estimate - prev_estimate) > precision) {
91 prev_estimate = estimate;
92 estimate = (prev_estimate + value / prev_estimate) / 2.;
[3bd74758]93 }
94
[b4a4ad94]95 return estimate;
96}
97
[94ebebf]98/** Compute available statistics from given stopwatches.
[b4a4ad94]99 *
100 * We compute normal mean for average duration of the workload and geometric
101 * mean for average thruput. Note that geometric mean is necessary to compute
102 * average throughput correctly - consider the following example:
103 * - we run always 60 operations,
104 * - first run executes in 30 s (i.e. 2 ops/s)
105 * - and second one in 10 s (6 ops/s).
106 * Then, naively, average throughput would be (2+6)/2 = 4 [ops/s]. However, we
107 * actually executed 60 + 60 ops in 30 + 10 seconds. So the actual average
108 * throughput is 3 ops/s (which is exactly what geometric mean means).
109 *
110 */
[e7f9a09]111static void compute_stats(bench_run_t *runs, size_t run_count,
[b4a4ad94]112 uint64_t workload_size, double precision, double *out_duration_avg,
113 double *out_duration_sigma, double *out_thruput_avg)
114{
115 double inv_thruput_sum = 0.0;
116 double nanos_sum = 0.0;
117 double nanos_sum2 = 0.0;
118
[e7f9a09]119 for (size_t i = 0; i < run_count; i++) {
120 double nanos = stopwatch_get_nanos(&runs[i].stopwatch);
[b4a4ad94]121 double thruput = (double) workload_size / nanos;
[3bd74758]122
[b4a4ad94]123 inv_thruput_sum += 1.0 / thruput;
124 nanos_sum += nanos;
125 nanos_sum2 += nanos * nanos;
126 }
[e7f9a09]127 *out_duration_avg = nanos_sum / run_count;
[b4a4ad94]128 double sigma2 = (nanos_sum2 - nanos_sum * (*out_duration_avg)) /
[e7f9a09]129 ((double) run_count - 1);
[b4a4ad94]130 // FIXME: implement sqrt properly
[871cff9a]131 if (run_count > 1) {
132 *out_duration_sigma = estimate_square_root(sigma2, precision);
133 } else {
134 *out_duration_sigma = NAN;
135 }
[e7f9a09]136 *out_thruput_avg = 1.0 / (inv_thruput_sum / run_count);
[b4a4ad94]137}
[3bd74758]138
[e7f9a09]139static void summary_stats(bench_run_t *runs, size_t run_count,
[b4a4ad94]140 benchmark_t *bench, uint64_t workload_size)
141{
142 double duration_avg, duration_sigma, thruput_avg;
[e7f9a09]143 compute_stats(runs, run_count, workload_size, 0.001,
[b4a4ad94]144 &duration_avg, &duration_sigma, &thruput_avg);
145
146 printf("Average: %" PRIu64 " ops in %.0f us (sd %.0f us); "
147 "%.0f ops/s; Samples: %zu\n",
148 workload_size, duration_avg / 1000.0, duration_sigma / 1000.0,
[e7f9a09]149 thruput_avg * 1000000000.0, run_count);
[3bd74758]150}
151
[d17cf8c]152static bool run_benchmark(bench_env_t *env, benchmark_t *bench)
[d230358]153{
[3bd74758]154 printf("Warm up and determine workload size...\n");
155
[e7f9a09]156 /*
157 * We share this buffer across all runs as we know that it is
158 * used only on failure (and we abort after first error).
159 */
[3bd74758]160 char *error_msg = malloc(MAX_ERROR_STR_LENGTH + 1);
161 if (error_msg == NULL) {
162 printf("Out of memory!\n");
163 return false;
164 }
165 str_cpy(error_msg, MAX_ERROR_STR_LENGTH, "");
166
[e7f9a09]167 bench_run_t helper_run;
168 bench_run_init(&helper_run, error_msg, MAX_ERROR_STR_LENGTH);
169
[3bd74758]170 bool ret = true;
[d230358]171
[3bd74758]172 if (bench->setup != NULL) {
[d17cf8c]173 ret = bench->setup(env, &helper_run);
[3bd74758]174 if (!ret) {
175 goto leave_error;
176 }
[d230358]177 }
178
[980611d5]179 /*
180 * Find workload size that is big enough to last few seconds.
181 * We also check that uint64_t is big enough.
182 */
183 uint64_t workload_size = 0;
184 for (size_t bits = 0; bits <= 64; bits++) {
185 if (bits == 64) {
186 str_cpy(error_msg, MAX_ERROR_STR_LENGTH, "Workload too small even for 1 << 63");
187 goto leave_error;
188 }
189 workload_size = ((uint64_t) 1) << bits;
[3bd74758]190
[e7f9a09]191 bench_run_t run;
192 bench_run_init(&run, error_msg, MAX_ERROR_STR_LENGTH);
[3bd74758]193
[d17cf8c]194 bool ok = bench->entry(env, &run, workload_size);
[3bd74758]195 if (!ok) {
196 goto leave_error;
197 }
[e7f9a09]198 short_report(&run, -1, bench, workload_size);
[3bd74758]199
[e7f9a09]200 nsec_t duration = stopwatch_get_nanos(&run.stopwatch);
[871cff9a]201 if (duration > env->minimal_run_duration_nanos) {
[3bd74758]202 break;
203 }
204 }
205
[871cff9a]206 printf("Workload size set to %" PRIu64 ", measuring %zu samples.\n",
207 workload_size, env->run_count);
[3bd74758]208
[871cff9a]209 bench_run_t *runs = calloc(env->run_count, sizeof(bench_run_t));
[e7f9a09]210 if (runs == NULL) {
[3bd74758]211 snprintf(error_msg, MAX_ERROR_STR_LENGTH, "failed allocating memory");
212 goto leave_error;
213 }
[871cff9a]214 for (size_t i = 0; i < env->run_count; i++) {
[e7f9a09]215 bench_run_init(&runs[i], error_msg, MAX_ERROR_STR_LENGTH);
[3bd74758]216
[d17cf8c]217 bool ok = bench->entry(env, &runs[i], workload_size);
[3bd74758]218 if (!ok) {
[e7f9a09]219 free(runs);
[3bd74758]220 goto leave_error;
221 }
[e7f9a09]222 short_report(&runs[i], i, bench, workload_size);
[3bd74758]223 }
224
[871cff9a]225 summary_stats(runs, env->run_count, bench, workload_size);
[3bd74758]226 printf("\nBenchmark completed\n");
227
[e7f9a09]228 free(runs);
[3bd74758]229
230 goto leave;
231
232leave_error:
233 printf("Error: %s\n", error_msg);
234 ret = false;
235
236leave:
237 if (bench->teardown != NULL) {
[d17cf8c]238 bool ok = bench->teardown(env, &helper_run);
[3bd74758]239 if (!ok) {
240 printf("Error: %s\n", error_msg);
241 ret = false;
242 }
243 }
244
245 free(error_msg);
246
247 return ret;
[d230358]248}
249
[d17cf8c]250static int run_benchmarks(bench_env_t *env)
[d230358]251{
[7e85d2b]252 unsigned int count_ok = 0;
253 unsigned int count_fail = 0;
[d230358]254
255 char *failed_names = NULL;
256
257 printf("\n*** Running all benchmarks ***\n\n");
258
[d5caf79]259 for (size_t it = 0; it < benchmark_count; it++) {
260 printf("%s (%s)\n", benchmarks[it]->name, benchmarks[it]->desc);
[d17cf8c]261 if (run_benchmark(env, benchmarks[it])) {
[7e85d2b]262 count_ok++;
[d230358]263 continue;
264 }
265
266 if (!failed_names) {
[d5caf79]267 failed_names = str_dup(benchmarks[it]->name);
[d230358]268 } else {
269 char *f = NULL;
[d5caf79]270 asprintf(&f, "%s, %s", failed_names, benchmarks[it]->name);
[d230358]271 if (!f) {
272 printf("Out of memory.\n");
273 abort();
274 }
275 free(failed_names);
276 failed_names = f;
277 }
[7e85d2b]278 count_fail++;
[d230358]279 }
280
[7e85d2b]281 printf("\nCompleted, %u benchmarks run, %u succeeded.\n",
282 count_ok + count_fail, count_ok);
[d230358]283 if (failed_names)
284 printf("Failed benchmarks: %s\n", failed_names);
285
[7e85d2b]286 return count_fail;
[d230358]287}
288
289static void list_benchmarks(void)
290{
291 size_t len = 0;
[d5caf79]292 for (size_t i = 0; i < benchmark_count; i++) {
293 size_t len_now = str_length(benchmarks[i]->name);
294 if (len_now > len)
295 len = len_now;
[d230358]296 }
297
[713ba400]298 assert(can_cast_size_t_to_int(len) && "benchmark name length overflow");
[d230358]299
[d5caf79]300 for (size_t i = 0; i < benchmark_count; i++)
[60029df]301 printf(" %-*s %s\n", (int) len, benchmarks[i]->name, benchmarks[i]->desc);
302
303 printf(" %-*s Run all benchmarks\n", (int) len, "*");
304}
[d230358]305
[60029df]306static void print_usage(const char *progname)
307{
308 printf("Usage: %s [options] <benchmark>\n", progname);
309 printf("-h, --help "
310 "Print this help and exit\n");
[871cff9a]311 printf("-d, --duration MILLIS "
312 "Set minimal run duration (milliseconds)\n");
313 printf("-n, --count N "
314 "Set number of measured runs\n");
[60029df]315 printf("-o, --output filename.csv "
316 "Store machine-readable data in filename.csv\n");
[f85546d]317 printf("-p, --param KEY=VALUE "
318 "Additional parameters for the benchmark\n");
[60029df]319 printf("<benchmark> is one of the following:\n");
320 list_benchmarks();
[d230358]321}
322
[d17cf8c]323static void handle_param_arg(bench_env_t *env, char *arg)
[f85546d]324{
325 char *value = NULL;
326 char *key = str_tok(arg, "=", &value);
[d17cf8c]327 bench_env_param_set(env, key, value);
[f85546d]328}
329
[d230358]330int main(int argc, char *argv[])
331{
[d17cf8c]332 bench_env_t bench_env;
333 errno_t rc = bench_env_init(&bench_env);
[f85546d]334 if (rc != EOK) {
335 fprintf(stderr, "Failed to initialize internal params structure: %s\n",
336 str_error(rc));
337 return -5;
338 }
339
[871cff9a]340 const char *short_options = "ho:p:n:d:";
[60029df]341 struct option long_options[] = {
[871cff9a]342 { "duration", required_argument, NULL, 'd' },
[60029df]343 { "help", optional_argument, NULL, 'h' },
[871cff9a]344 { "count", required_argument, NULL, 'n' },
[60029df]345 { "output", required_argument, NULL, 'o' },
[871cff9a]346 { "param", required_argument, NULL, 'p' },
[60029df]347 { 0, 0, NULL, 0 }
348 };
349
350 char *csv_output_filename = NULL;
351
352 int opt = 0;
353 while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) > 0) {
354 switch (opt) {
[871cff9a]355 case 'd':
356 errno = EOK;
357 bench_env.minimal_run_duration_nanos = MSEC2NSEC(atoll(optarg));
358 if ((errno != EOK) || (bench_env.minimal_run_duration_nanos <= 0)) {
359 fprintf(stderr, "Invalid -d argument.\n");
360 return -3;
361 }
362 break;
[60029df]363 case 'h':
364 print_usage(*argv);
365 return 0;
[871cff9a]366 case 'n':
367 errno = EOK;
368 bench_env.run_count = (nsec_t) atoll(optarg);
369 if ((errno != EOK) || (bench_env.run_count <= 0)) {
370 fprintf(stderr, "Invalid -n argument.\n");
371 return -3;
372 }
373 break;
[60029df]374 case 'o':
375 csv_output_filename = optarg;
376 break;
[f85546d]377 case 'p':
[d17cf8c]378 handle_param_arg(&bench_env, optarg);
[f85546d]379 break;
[60029df]380 case -1:
381 default:
382 break;
383 }
[d230358]384 }
385
[60029df]386 if (optind + 1 != argc) {
387 print_usage(*argv);
388 fprintf(stderr, "Error: specify one benchmark to run or * for all.\n");
389 return -3;
[d230358]390 }
391
[60029df]392 const char *benchmark = argv[optind];
393
394 if (csv_output_filename != NULL) {
395 errno_t rc = csv_report_open(csv_output_filename);
396 if (rc != EOK) {
397 fprintf(stderr, "Failed to open CSV report '%s': %s\n",
398 csv_output_filename, str_error(rc));
399 return -4;
[d230358]400 }
401 }
402
[60029df]403 int exit_code = 0;
404
405 if (str_cmp(benchmark, "*") == 0) {
[d17cf8c]406 exit_code = run_benchmarks(&bench_env);
[60029df]407 } else {
408 bool benchmark_exists = false;
409 for (size_t i = 0; i < benchmark_count; i++) {
410 if (str_cmp(benchmark, benchmarks[i]->name) == 0) {
411 benchmark_exists = true;
[d17cf8c]412 exit_code = run_benchmark(&bench_env, benchmarks[i]) ? 0 : -1;
[60029df]413 break;
414 }
415 }
416 if (!benchmark_exists) {
417 printf("Unknown benchmark \"%s\"\n", benchmark);
418 exit_code = -2;
419 }
420 }
421
422 csv_report_close();
[d17cf8c]423 bench_env_cleanup(&bench_env);
[60029df]424
425 return exit_code;
[d230358]426}
427
428/** @}
429 */
Note: See TracBrowser for help on using the repository browser.