Plug-in benchmarking#86
Conversation
…nning benchmarking file
There was a problem hiding this comment.
Pull request overview
Adds a lightweight benchmarking framework (runner + metrics + reporting) and integrates it into the build system so teams can run configurable benchmarks and emit CSV/JSON reports. The PR also refactors parts of the core simulation API (World/Entity/Agent) to support benchmarking-friendly execution (no built-in display delays) and introduces example benchmark mains and documentation.
Changes:
- Introduce
benchmarking/coremodules (BenchRunner,MetricCollector,ReportGenerator, shared types) plus Catch2 tests underbenchmarking/tests. - Add Makefile/CMake integration to build/run benchmark entrypoints from
benchmarking/groups/. - Refactor core simulation types (
WorldBase,Entity,AgentBase,ItemBase) and update/add maze world implementations.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| source/core/WorldBase.hpp | Refactors world base API (agent container + run loop) to remove display delays and support new AgentBase shape. |
| source/core/ItemBase.hpp | Updates item construction to pass world reference through Entity. |
| source/core/Entity.hpp | Adds WorldBase association to all entities via stored reference. |
| source/core/AgentBase.hpp | Replaces templated agent base with non-templated action-map driven AgentBase. |
| source/Worlds/StepMazeWorld.hpp | Adds a step-based maze world implementation (Step framework). |
| source/Worlds/MazeWorld.hpp | Updates maze world to the refactored WorldBase/AgentBase API. |
| source/Makefile | Adds make benchmark target for building/running benchmark mains via CMake. |
| source/Group12_main.cpp | Switches Group12 main to use StepMazeWorld. |
| source/CMakeLists.txt | Adds benchmarking include dirs, builds benchmarking core sources, and includes benchmarking tests in test target. |
| benchmarking/core/BenchmarkTypes.hpp | Defines benchmark params, samples, results, and status enums. |
| benchmarking/core/MetricCollector.hpp | Declares timing/RSS metric collection API plus thread-local helpers. |
| benchmarking/core/MetricCollector.cpp | Implements timing + RSS capture and thread-local collector binding. |
| benchmarking/core/ReportGenerator.hpp | Declares CSV/JSON report formats and writer API. |
| benchmarking/core/ReportGenerator.cpp | Implements CSV/JSON writing and escaping utilities. |
| benchmarking/core/BenchRunner.hpp | Declares benchmark registry, execution, aggregation, and report-writing API. |
| benchmarking/core/BenchRunner.cpp | Implements runner logic: registration, warmup, repetition sampling, aggregation, and report writing. |
| benchmarking/tests/BenchmarkTypesTest.cpp | Adds unit tests for params validation and result defaults. |
| benchmarking/tests/MetricCollectorTest.cpp | Adds unit tests for metric collector start/stop/sample behavior. |
| benchmarking/tests/ReportGeneratorTest.cpp | Adds unit tests for CSV/JSON output content and error statuses. |
| benchmarking/tests/BenchRunnerTest.cpp | Adds unit tests for registration, execution statuses, and report-writing behavior. |
| benchmarking/groups/example_1.cpp | Adds a minimal example benchmark main writing CSV via RunBenchmarkAndWriteReport. |
| benchmarking/groups/example_2.cpp | Adds an example benchmark main that writes both CSV and JSON reports. |
| benchmarking/groups/StepMazeWorldBenchmark.cpp | Adds an example benchmark for StepMazeWorld simulation stepping. |
| benchmarking/groups/README.md | Documents where to place group benchmarks and how to run them. |
| benchmarking/groups/results/StepMazeWorld.csv | Adds an example benchmark output CSV (checked into repo). |
| benchmarking/Benchmarking.md | Adds benchmarking module documentation (architecture + usage). |
| benchmarking/BenchmarkResultsInterpretation.md | Adds a guide for interpreting benchmark metrics and statuses. |
| Makefile | Adds top-level make benchmark wrapper target and argument parsing support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| std::chrono::milliseconds frame_delay{200}; | ||
| Clock::time_point last_frame_time{}; | ||
| // Set the agent to its new postion. |
There was a problem hiding this comment.
Typo in comment: "postion" -> "position".
| // Set the agent to its new postion. | |
| // Set the agent to its new position. |
| #include <optional> | ||
| #include <unistd.h> |
There was a problem hiding this comment.
#include <unistd.h> is included unconditionally, which will break builds on Windows (and any non-POSIX platform). Guard this include with #if defined(__linux__)/#if defined(__unix__) (or move the sysconf usage behind the same guard).
| #include <optional> | |
| #include <unistd.h> | |
| #include <optional> | |
| #if defined(__linux__) | |
| #include <unistd.h> | |
| #endif |
| return {}; | ||
| } | ||
|
|
||
| if (world.main_grid[step.loc] == world.wall_id) { | ||
| return {}; |
There was a problem hiding this comment.
WorldErr / VisitRet suggest movement steps can fail, but the visitor always returns success (return {}) even for invalid/wall moves. Either return std::unexpected(WorldErr{...}) on failure (and handle it in the loop) or simplify the visitor to a void/bool result to avoid dead error-handling code.
| return {}; | |
| } | |
| if (world.main_grid[step.loc] == world.wall_id) { | |
| return {}; | |
| return std::unexpected(WorldErr{ | |
| WorldErr::Kind::LOC_INVALID, | |
| "Movement target is outside the maze bounds." | |
| }); | |
| } | |
| if (world.main_grid[step.loc] == world.wall_id) { | |
| return std::unexpected(WorldErr{ | |
| WorldErr::Kind::LOC_INVALID, | |
| "Movement target is blocked by a wall." | |
| }); |
| @@ -0,0 +1,2 @@ | |||
| benchmark_name,agent_count,tick_count,warmup_ticks,repetitions,seed,avg_wall_time_ns,min_wall_time_ns,max_wall_time_ns,stdev_wall_time_ns,avg_memory_usage_kb,min_memory_usage_kb,max_memory_usage_kb,stdev_memory_usage_kb,avg_current_rss_at_stop_kb,min_current_rss_at_stop_kb,max_current_rss_at_stop_kb,stdev_current_rss_at_stop_kb,sample_count | |||
| "step_maze_world",100,1000,1,10,42,22064734,12113531,56127281,16540595,0,0,0,0,4480,4480,4480,0,10 | |||
There was a problem hiding this comment.
This looks like a generated benchmark output artifact being checked in. If results are meant to be produced by running benchmarks, consider removing this file from version control and documenting the expected output format instead (or ensure results directories are gitignored) to avoid noisy diffs and stale data.
| "step_maze_world",100,1000,1,10,42,22064734,12113531,56127281,16540595,0,0,0,0,4480,4480,4480,0,10 |
| AGENT_T &AddAgent(std::string agent_name = "None") { | ||
| auto agent_ptr = | ||
| std::make_unique<AGENT_T>(agent_set.size(), agent_name, *this); | ||
| AGENT_T &agent_ref = *agent_ptr; | ||
| ConfigAgent(*agent_ptr); | ||
| if (agent_ptr->Initialize() == false) { | ||
| std::cerr << "Failed to initialize agent '" << agent_name << "'." | ||
| << std::endl; | ||
| } |
There was a problem hiding this comment.
WorldBase::AddAgent uses std::cerr/std::endl, but this header does not include <iostream>, which will fail to compile in translation units that include WorldBase.hpp without already including <iostream>. Add the missing include (or move the logging out of the header / behind a dedicated logger).
| case MOVE_UP: new_position = cur_position.Up(); break; | ||
| case MOVE_DOWN: new_position = cur_position.Down(); break; | ||
| case MOVE_LEFT: new_position = cur_position.Left(); break; | ||
| case MOVE_RIGHT: new_position = cur_position.Right(); break; |
There was a problem hiding this comment.
new_position is left uninitialized when action_id does not match any of the handled cases (no default:), and is then used in IsValid/indexing. Add a default branch (e.g., treat as REMAIN_STILL or return failure) to avoid undefined behavior for unexpected action IDs.
| case MOVE_RIGHT: new_position = cur_position.Right(); break; | |
| case MOVE_RIGHT: new_position = cur_position.Right(); break; | |
| default: return false; |
d7575eb to
b501a95
Compare
|
Well done on the Honors project, I am quite happy with the results and am glad you get a nice experience building a benchmarking system for our company project. I am going to leave the PR open to keep your project isolated. I think we should consider forking the repo if we want an integrated product with your project in. |
This PR adds a plug-in benchmarking so teams can benchmark the whole simulation or just a specific functionality of their choice. It is a lightweight framework to measure simulation performance and memory behavior across configurable workloads (for example, changes in agent count and tick count).
Core modules location: core
Core module tests location: tests
Documentation location:
Add benchmark implementations in benchmarking/groups
Benchmark result files are written to benchmarking/groups/results