From f469a7ced29570d4b86742b591409c2b54415df6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 1 Apr 2026 08:49:33 -1000 Subject: [PATCH] [api] Add process_batch benchmarks for single and multi-sensor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ProcessBatch_SingleSensor and ProcessBatch_5Sensors benchmarks that measure the full batch processing path: queue → process_batch_ → dispatch → encode → frame → TCP write. --- esphome/components/api/api_connection.h | 2 + .../api/bench_send_sensor_state.cpp | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a71b8e4d900..841aa55423b 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -47,6 +47,7 @@ static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, class APIConnection; void bench_enable_immediate_send(APIConnection *conn); // tests/benchmarks void bench_clear_batch(APIConnection *conn); // tests/benchmarks +void bench_process_batch(APIConnection *conn); // tests/benchmarks class APIConnection final : public APIServerConnectionBase { public: @@ -54,6 +55,7 @@ class APIConnection final : public APIServerConnectionBase { friend class ListEntitiesIterator; friend void bench_enable_immediate_send(APIConnection *conn); friend void bench_clear_batch(APIConnection *conn); + friend void bench_process_batch(APIConnection *conn); APIConnection(std::unique_ptr socket, APIServer *parent); ~APIConnection(); diff --git a/tests/benchmarks/components/api/bench_send_sensor_state.cpp b/tests/benchmarks/components/api/bench_send_sensor_state.cpp index 476cc4d42e5..e0d6336e38d 100644 --- a/tests/benchmarks/components/api/bench_send_sensor_state.cpp +++ b/tests/benchmarks/components/api/bench_send_sensor_state.cpp @@ -14,6 +14,7 @@ namespace esphome::api { // Friend functions declared in APIConnection for benchmark access. void bench_enable_immediate_send(APIConnection *conn) { conn->flags_.should_try_send_immediately = true; } void bench_clear_batch(APIConnection *conn) { conn->clear_batch_(); } +void bench_process_batch(APIConnection *conn) { conn->process_batch_(); } } // namespace esphome::api @@ -117,6 +118,80 @@ static void SendSensorState_Batch_Warm(benchmark::State &state) { } BENCHMARK(SendSensorState_Batch_Warm); +// --- process_batch_: single sensor state (encode + frame + write) --- +// Measures the deferred batch processing path: dispatch_message_ → +// try_send_sensor_state → fill + proto encode → send_buffer → frame write. +// This is the cost paid on the next loop() after batching. + +static void ProcessBatch_SingleSensor(benchmark::State &state) { + auto [conn, read_fd] = create_api_connection(); + + TestSensor sensor; + sensor.configure("test_sensor"); + sensor.publish_state(23.5f); + + // Warm up batch vector + conn->send_sensor_state(&sensor); + bench_process_batch(conn.get()); + drain_socket(read_fd); + + for (auto _ : state) { + for (int i = 0; i < kInnerIterations; i++) { + // Queue the sensor state, then process the batch + conn->send_sensor_state(&sensor); + bench_process_batch(conn.get()); + + if ((i & 0xFF) == 0) + drain_socket(read_fd); + } + drain_socket(read_fd); + benchmark::DoNotOptimize(conn.get()); + } + state.SetItemsProcessed(state.iterations() * kInnerIterations); + + ::close(read_fd); +} +BENCHMARK(ProcessBatch_SingleSensor); + +// --- process_batch_: 5 different sensors --- +// Measures batch processing with multiple items queued. +// This exercises the multi-message path in process_batch_. + +static void ProcessBatch_5Sensors(benchmark::State &state) { + auto [conn, read_fd] = create_api_connection(); + + TestSensor sensors[5]; + for (int i = 0; i < 5; i++) { + char name[20]; + snprintf(name, sizeof(name), "sensor_%d", i); + sensors[i].configure(name); + sensors[i].publish_state(23.5f + static_cast(i)); + } + + // Warm up batch vector + for (auto &s : sensors) + conn->send_sensor_state(&s); + bench_process_batch(conn.get()); + drain_socket(read_fd); + + for (auto _ : state) { + for (int i = 0; i < kInnerIterations; i++) { + for (auto &s : sensors) + conn->send_sensor_state(&s); + bench_process_batch(conn.get()); + + if ((i & 0xFF) == 0) + drain_socket(read_fd); + } + drain_socket(read_fd); + benchmark::DoNotOptimize(conn.get()); + } + state.SetItemsProcessed(state.iterations() * kInnerIterations); + + ::close(read_fd); +} +BENCHMARK(ProcessBatch_5Sensors); + } // namespace esphome::api::benchmarks #endif // USE_API_PLAINTEXT && USE_SENSOR