summaryrefslogtreecommitdiff
path: root/include/benchmark.h
blob: 190580ebb7a73412df8d66f937ea0b1de79595bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* Copyright 2022 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Benchmark utility functions.
 */

#ifndef __CROS_EC_BENCHMARK_H
#define __CROS_EC_BENCHMARK_H

#include <stdint.h>

#include <array>
#include <functional>
#include <optional>
#include <string_view>

extern "C" {
#include "clock.h"
#include "console.h"
#include "timer.h"
#include "util.h"
#include "watchdog.h"
}

/* Benchmark execution options */
struct BenchmarkOptions {
	/* Number of test iterations */
	int num_iterations = 10;
	/* Whether to reload the watchdog between executions of f() */
	bool reload_watchdog = true;
	/* Whether to enable fast CPU clock during the test (when supported) */
	bool use_fast_cpu = true;
};

/* The result of a benchmark run with various timing metrics.
 * All time measurements are in micro seconds, except the average that
 * is captured in nanoseconds for increased resolution.
 */
struct BenchmarkResult {
	/* Name of the test, used when printing results */
	std::string_view name;
	/* Total elapsed time (us) for all iterations */
	uint32_t elapsed_time;
	/* Average elapsed time (ns) for a single iteration */
	uint32_t average_time;
	/* Minimum elapsed time (us) for a single iteration */
	uint32_t min_time;
	/* Maximum elapsed time (us) for a single iteration */
	uint32_t max_time;
};

/* Benchmark main class responsible for running the experiments and
 * collecting/printing the results.
 * Note that the implementation intentionally avoid dynamic memory allocations
 * and stores up to MAX_NUM_RESULTS results into a std::array.
 */
template <int MAX_NUM_RESULTS = 5> class Benchmark {
    public:
	explicit Benchmark(const BenchmarkOptions &options = BenchmarkOptions())
		: options_(options){};

	/* Run benchmark of the function f().
	 *
	 * TODO(b/253099367): replace std::optional with StatusOr
	 */
	std::optional<BenchmarkResult>
	run(const std::string_view benchmark_name, std::function<void()> f)
	{
		if (benchmark_name.empty()) {
			ccprintf("%s: benchmark_name cannot be empty\n",
				 __func__);
			return {};
		}
		if (num_results_ >= MAX_NUM_RESULTS) {
			ccprintf("%s: cannot store new BenchmarkResults\n",
				 __func__);
			return {};
		}

		BenchmarkResult &result = results_[num_results_++];
		result.name = benchmark_name;
		result.elapsed_time = 0;

		if (options_.use_fast_cpu)
			clock_enable_module(MODULE_FAST_CPU, 1);

		bool valid_min_max = false;
		for (int i = 0; i < options_.num_iterations; ++i) {
			timestamp_t start_time = get_time();
			f();
			uint32_t iteration_time = time_since32(start_time);

			if (options_.reload_watchdog)
				watchdog_reload();

			if (valid_min_max) {
				result.max_time =
					MAX(result.max_time, iteration_time);
				result.min_time =
					MIN(result.min_time, iteration_time);
			} else {
				result.max_time = iteration_time;
				result.min_time = iteration_time;
				valid_min_max = true;
			}
			result.elapsed_time += iteration_time;
		}

		if (options_.use_fast_cpu)
			clock_enable_module(MODULE_FAST_CPU, 0);

		result.average_time =
			(result.elapsed_time) / options_.num_iterations;

		return result;
	}

	void print_results() const
	{
		print_header();
		for (int i = 0; i < num_results_; ++i)
			print_result(results_[i]);
	}

    private:
	const BenchmarkOptions options_;
	std::array<BenchmarkResult, MAX_NUM_RESULTS> results_;
	int num_results_ = 0;

	/* Print table header with column names */
	void print_header() const
	{
		constexpr char kSeparator[] = "--------------------------";

		ccprintf("%16s | %15s | %13s | %13s | %13s | %13s\n",
			 "Test Name", "Num Iterations", "Elapsed (us)",
			 "Min (us)", "Max (us)", "Avg (us)");
		ccprintf("%.*s | %.*s | %.*s | %.*s | %.*s | %.*s\n", 16,
			 kSeparator, 15, kSeparator, 13, kSeparator, 13,
			 kSeparator, 13, kSeparator, 13, kSeparator);
		cflush();
	}

	/* Print a single benchmark result */
	int print_result(const BenchmarkResult &result) const
	{
		ccprintf("%16s | %15u | %13u | %13u | %13u | %13u\n",
			 result.name.data(), options_.num_iterations,
			 result.elapsed_time, result.min_time, result.max_time,
			 result.average_time);
		cflush();
		return EC_SUCCESS;
	}
};

#endif /* __CROS_EC_BENCHMARK_H */