diff options
author | Andrea Grandi <agrandi@google.com> | 2022-10-21 08:27:50 -0700 |
---|---|---|
committer | Chromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2022-11-02 01:18:54 +0000 |
commit | 79d4706f92680ed4a192b585a1db072e9e3da7ad (patch) | |
tree | 790c1d62a0493417cf496b091f828dc3d953b2d4 | |
parent | 741bbd86cb48de471759c1519901202467342fca (diff) | |
download | chrome-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.mk | 1 | ||||
-rw-r--r-- | board/nocturne_fp/build.mk | 1 | ||||
-rw-r--r-- | include/benchmark.h | 156 | ||||
-rw-r--r-- | test/benchmark.cc | 98 | ||||
-rw-r--r-- | test/benchmark.tasklist | 9 | ||||
-rw-r--r-- | test/build.mk | 2 | ||||
-rwxr-xr-x | test/run_device_tests.py | 1 |
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"), |