[api] Add process_batch benchmarks for single and multi-sensor

Add ProcessBatch_SingleSensor and ProcessBatch_5Sensors benchmarks
that measure the full batch processing path: queue → process_batch_ →
dispatch → encode → frame → TCP write.
This commit is contained in:
J. Nick Koston
2026-04-01 08:49:33 -10:00
parent 3a4a88df29
commit f469a7ced2
2 changed files with 77 additions and 0 deletions
+2
View File
@@ -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::Socket> socket, APIServer *parent);
~APIConnection();
@@ -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<float>(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