FixedVector requires init() before push_back(). Update all test cases
to call set_sensor_count()/set_binary_sensor_count() before adding
sensors, matching what the Python codegen does.
Add find_first parameter to cancel_item_locked_ and
mark_matching_items_removed_locked_, defaulting to false (cancel all).
When find_first=true, stops after the first match and exits across
containers. set_timer_common_ passes find_first=true where the
cancel-before-add invariant guarantees at most one match.
The public cancel path uses the default find_first=false to cancel ALL
matches, needed for DelayAction parallel mode where skip_cancel=true
can create multiple items with the same key.
Also fixes the Scheduler_SetInterval benchmark to call
process_to_add() every kKeyCount registrations with a final drain
after the inner loop, reflecting production behavior.
The test was calling show_logs() which invokes _wait_for_serial_port("/dev/ttyUSB0").
Since that path doesn't exist in CI/dev environments, it spun in a 30-second polling
loop before returning. Add mock_wait_for_serial_port fixture to skip the wait.
The compiler constant-folds ProtoSize::varint(42) to 1 and optimizes
the entire inner loop to a single addition, causing ~62ns jitter-dominated
measurements. Use varying inputs (i & 0x7F for small, 0xFFFF0000 | i for
large) so each call computes a real result.
- 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
Three benchmarks covering the most common ESP_LOGW patterns:
- Logger_NoFormat: plain string, no format specifiers (fastest path)
- Logger_3Uint32: 3x uint32_t (common for status/diagnostics)
- Logger_3Float: 3x float with precision (common for sensor values)
Encode messages once in setup using the real protobuf API, then decode
the resulting bytes in the benchmark loop. This keeps decode benchmarks
automatically in sync with the protobuf schema — hand-encoded byte
arrays would silently break when fields change.
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.
Both _Fresh variants now use kInnerIterations with fresh buffer
creation inside the inner loop. This amortizes CodSpeed's per-iteration
instrumentation overhead while still measuring alloc+calc+encode per item.
- Remove inner loop from CalcAndEncode_DeviceInfoResponse_Fresh to match
SensorStateResponse_Fresh — both now measure single alloc+encode per
iteration as intended for heap allocation cost benchmarking
- Add comments documenting why _Fresh variants skip inner loops
- Add first-wins comment to load_component_yaml_configs
- Clean up .gitignore template comment
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
- Call original_setup() in benchmark main.cpp so code-generated App
initialization (pre_setup, area/device registration, looping_components_
init) runs before benchmarks
- Restore ApplicationLoop_Empty benchmark that calls the actual App.loop()