- Use BENCHMARK_BINARY= marker for reliable binary path extraction
instead of fragile tail -1 (PlatformIO can print warnings after path)
- Fix Scheduler_Defer: defer() is protected on Component, use
set_timeout(delay=0) directly on Scheduler instead
Measures the cost of registering scheduler items:
- Scheduler_SetTimeout: set_timeout with 1s delay (heap path)
- Scheduler_SetInterval: set_interval with 1s period (heap path + offset calc)
- Scheduler_Defer: Component::defer (set_timeout with delay=0)
Uses i%5 for IDs to exercise the cancel-existing-timer path that
happens when re-registering with the same ID.
Add USE_BENCHMARK define to benchmark build flags. Guard the
warn_blocking call in finish() with #ifndef USE_BENCHMARK so
scheduler benchmarks using fake monotonic time don't trigger
the underflow (fake now > real millis()).
Remove BenchComponent — no longer needed with the ifdef.
The warn_blocking underflow only happens with fake time in benchmarks,
not in production (millis() is monotonic). Accept the consistent
overhead from one warning per call — CodSpeed regression detection
works on relative changes, not absolute values.
When curr_time (from millis()) is less than started_ (the `now` passed
to scheduler.call()), the subtraction wraps to a huge value (~4 billion).
This triggers spurious blocking warnings with nonsensical times.
This can happen when the scheduler's execute_item_() returns a millis()
value that subsequent items use as their guard start, but the next
scheduler.call() passes a `now` value from a slightly different source.
Fix by skipping the blocking check when curr_time < started_ (underflow).
Also restore the scheduler firing benchmark to use intervals with
monotonically increasing fake time, now that the guard handles underflow.
- Revert component.h change (no core changes for benchmarks)
- Remove -DWARN_IF_BLOCKING_OVER_MS from build flags (can't shadow constexpr)
- Drop inner loop — 5 heap pops + callbacks + pushes per call is well
above CodSpeed's 60ns instrumentation overhead
- Add warm-up call before benchmark loop to trigger the blocking guard
once and ramp the threshold
- interval=0 causes infinite loop, must use interval=1 with fake time
interval=0 causes infinite loop (reschedules at same time, never breaks).
interval=1 with millis() doesn't work (real time doesn't advance fast
enough between inner iterations for intervals to re-fire).
Solution: use interval=1 with monotonically increasing fake time (now++)
and disable WarnIfComponentBlockingGuard at compile time via
-DWARN_IF_BLOCKING_OVER_MS=UINT32_MAX in benchmark build flags. This
prevents the guard's (millis() - started_) underflow when fake time
exceeds real millis().
Use interval=0 so all 5 intervals fire unconditionally every call().
Pass real millis() to scheduler.call() so WarnIfComponentBlockingGuard
doesn't see fake time ahead of wall clock (which causes uint32_t
underflow in the blocking time calculation).
WarnIfComponentBlockingGuard compares the `now` passed to
scheduler.call() against real millis() in finish(). Using fake time
ahead of real millis() caused uint32_t underflow in the guard, triggering
blocking warnings. Fix by reading real millis() at the start of each
outer iteration so fake time stays close to wall clock.
millis_64_from_() tracks 32-bit rollovers, so going backwards in time
would appear as a ~49 day forward jump. Keep now monotonically
increasing across all iterations. With 2000 inner iterations per outer
iteration, overflow is not a practical concern.
- Remove trailing " from AUTO GENERATED INCLUDE BLOCK END comment
- Reset scheduler `now` at start of each outer iteration to avoid
unbounded growth toward UINT32_MAX across benchmark iterations
Under valgrind, 2000 inner iterations take long enough in wall clock
to trigger WarnIfComponentBlockingGuard. Use a BenchComponent subclass
that sets warn_if_blocking_over_ to UINT16_MAX to prevent log noise.
Adds Scheduler_Call_5IntervalsFiring: 5 intervals with 1ms period,
time advancing each inner iteration so all 5 fire every call().
This benchmarks the real scheduler hot path where callbacks execute.
Sub-microsecond benchmarks are dominated by the ~60ns per-iteration
valgrind start/stop cost in CodSpeed simulation mode. Add kInnerIterations
(1000) inner loops to all fast benchmarks so the actual work dominates.
Move DoNotOptimize calls outside inner loops to prevent artificial overhead.
Also address review feedback:
- Use tokenless CodSpeed (public repo, no CODSPEED_TOKEN needed)
- Fix warning message to show component-specific path
- Fix stray ". :" in error message
- Verify pinned SHA on re-runs to prevent stale checkouts
- Remove BM_ prefix from all benchmark names (unnecessary convention)
- Remove bench_application_loop.cpp (App needs pre_setup/setup which
requires full code generation; will revisit as integration benchmark)
- Fix extra_include_dirs to use os.path.relpath for sibling directories
Core is not a component — its benchmarks belong in tests/benchmarks/core/
not tests/benchmarks/components/core/. Add extra_include_dirs parameter
to build_and_run to support non-component benchmark directories.