summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrea Grandi <agrandi@google.com>2022-10-21 08:27:50 -0700
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2022-11-02 01:18:54 +0000
commit79d4706f92680ed4a192b585a1db072e9e3da7ad (patch)
tree790c1d62a0493417cf496b091f828dc3d953b2d4
parent741bbd86cb48de471759c1519901202467342fca (diff)
downloadchrome-ec-79d4706f92680ed4a192b585a1db072e9e3da7ad.tar.gz
test: Add benchmark library (C++)
Add a benchmark library (benchmark.h) to measure the execution time of functions. It is written in C++ to simplify the interface. For example, it can be used to benchmark lambdas with captures. BUG=b:246366702 BRANCH=none TEST=test/run_device_tests.py -b bloonchipper -t benchmark TEST=make run-benchmark Signed-off-by: Andrea Grandi <agrandi@google.com> Change-Id: Ibed907609a27566e386c511153fcd2d819981356 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3971378 Code-Coverage: Zoss <zoss-cl-coverage@prod.google.com> Reviewed-by: Tom Hughes <tomhughes@chromium.org>
-rw-r--r--board/hatch_fp/build.mk1
-rw-r--r--board/nocturne_fp/build.mk1
-rw-r--r--include/benchmark.h156
-rw-r--r--test/benchmark.cc98
-rw-r--r--test/benchmark.tasklist9
-rw-r--r--test/build.mk2
-rwxr-xr-xtest/run_device_tests.py1
7 files changed, 268 insertions, 0 deletions
diff --git a/board/hatch_fp/build.mk b/board/hatch_fp/build.mk
index 828e7523b3..8ff7ec6cfb 100644
--- a/board/hatch_fp/build.mk
+++ b/board/hatch_fp/build.mk
@@ -29,6 +29,7 @@ test-list-y=\
abort \
aes \
always_memset \
+ benchmark \
cec \
compile_time_macros \
cortexm_fpu \
diff --git a/board/nocturne_fp/build.mk b/board/nocturne_fp/build.mk
index 65ffd6c096..cd3981263c 100644
--- a/board/nocturne_fp/build.mk
+++ b/board/nocturne_fp/build.mk
@@ -29,6 +29,7 @@ test-list-y=\
abort \
aes \
always_memset \
+ benchmark \
cec \
compile_time_macros \
cortexm_fpu \
diff --git a/include/benchmark.h b/include/benchmark.h
new file mode 100644
index 0000000000..f9c9a1afcd
--- /dev/null
+++ b/include/benchmark.h
@@ -0,0 +1,156 @@
+/* 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 <array>
+#include <optional>
+#include <functional>
+#include <stdint.h>
+#include <string_view>
+
+extern "C" {
+#include "console.h"
+#include "timer.h"
+#include "util.h"
+#include "clock.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()
+ {
+ 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()
+ {
+ 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)
+ {
+ 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 */
diff --git a/test/benchmark.cc b/test/benchmark.cc
new file mode 100644
index 0000000000..e28bfbd999
--- /dev/null
+++ b/test/benchmark.cc
@@ -0,0 +1,98 @@
+/* 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.
+ *
+ * Simple test to validate the benchmark library.
+ */
+
+#include "benchmark.h"
+
+extern "C" {
+#include "test_util.h"
+}
+
+/* Sample function for the benchmark */
+static void float_mult()
+{
+ float a = 1.1;
+ float b = 1.1;
+ int i;
+
+ for (i = 0; i < 1000; ++i)
+ a *= b;
+}
+
+test_static int test_valid_benchmark()
+{
+ Benchmark benchmark;
+
+ auto result = benchmark.run("float_mult", float_mult);
+ TEST_ASSERT(result.has_value());
+
+ benchmark.print_results();
+ return EC_SUCCESS;
+}
+
+test_static int test_num_iterations()
+{
+ Benchmark benchmark({ .num_iterations = 5 });
+ int num_calls = 0;
+
+ auto result = benchmark.run("call_counter", [&] { ++num_calls; });
+ TEST_ASSERT(result.has_value());
+ TEST_EQ(num_calls, 5, "%d");
+
+ return EC_SUCCESS;
+}
+
+test_static int test_empty_benchmark_name()
+{
+ Benchmark benchmark;
+ TEST_ASSERT(!benchmark.run("", [] {}).has_value());
+ return EC_SUCCESS;
+}
+
+test_static int test_too_many_runs()
+{
+ auto benchmark = Benchmark<3>();
+ TEST_ASSERT(benchmark.run("call_1", [] {}).has_value());
+ TEST_ASSERT(benchmark.run("call_2", [] {}).has_value());
+ TEST_ASSERT(benchmark.run("call_3", [] {}).has_value());
+ TEST_ASSERT(!benchmark.run("call_4", [] {}).has_value());
+
+ return EC_SUCCESS;
+}
+
+test_static int test_min_max_time()
+{
+ // Run test 3 times with increasing delay of 1ms, 2ms, and 4ms
+ Benchmark benchmark({ .num_iterations = 3 });
+ int delay_us = 1000;
+
+ auto result = benchmark.run("delay", [&delay_us] {
+ udelay(delay_us);
+ delay_us *= 2;
+ });
+ TEST_ASSERT(result.has_value());
+
+ auto min_time = result.value().min_time;
+ auto max_time = result.value().max_time;
+
+ TEST_GE(min_time, 995U, "%u");
+ TEST_LE(min_time, 1005U, "%u");
+ TEST_GE(max_time, 3995U, "%u");
+ TEST_LE(max_time, 4005U, "%u");
+
+ return EC_SUCCESS;
+}
+
+void run_test(int argc, const char **argv)
+{
+ test_reset();
+ RUN_TEST(test_valid_benchmark);
+ RUN_TEST(test_num_iterations);
+ RUN_TEST(test_too_many_runs);
+ RUN_TEST(test_empty_benchmark_name);
+ RUN_TEST(test_min_max_time);
+ test_print_result();
+}
diff --git a/test/benchmark.tasklist b/test/benchmark.tasklist
new file mode 100644
index 0000000000..6d43377af5
--- /dev/null
+++ b/test/benchmark.tasklist
@@ -0,0 +1,9 @@
+/* Copyright 2022 The ChromiumOS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * See CONFIG_TASK_LIST in config.h for details.
+ */
+#define CONFIG_TEST_TASK_LIST
diff --git a/test/build.mk b/test/build.mk
index 08e2d15c9f..a4f9ee74b8 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -24,6 +24,7 @@ test-list-host += aes
test-list-host += always_memset
test-list-host += base32
test-list-host += battery_get_params_smart
+test-list-host += benchmark
test-list-host += bklight_lid
test-list-host += bklight_passthru
test-list-host += body_detection
@@ -169,6 +170,7 @@ aes-y=aes.o
always_memset-y=always_memset.o
base32-y=base32.o
battery_get_params_smart-y=battery_get_params_smart.o
+benchmark-y=benchmark.o
bklight_lid-y=bklight_lid.o
bklight_passthru-y=bklight_passthru.o
body_detection-y=body_detection.o body_detection_data_literals.o motion_common.o
diff --git a/test/run_device_tests.py b/test/run_device_tests.py
index 4e0b0127e3..0aee1428f5 100755
--- a/test/run_device_tests.py
+++ b/test/run_device_tests.py
@@ -196,6 +196,7 @@ class AllTests:
TestConfig(test_name="abort"),
TestConfig(test_name="aes"),
TestConfig(test_name="always_memset"),
+ TestConfig(test_name="benchmark"),
TestConfig(test_name="cec"),
TestConfig(test_name="cortexm_fpu"),
TestConfig(test_name="crc"),