[api] Move proxy message benchmarks into bench_proto_proxy.cpp

Splitting these out from bench_proto_encode.cpp and bench_proto_decode.cpp
moves them to the end of the linker's static-init order. CodSpeed's
callgrind runner has been segfaulting immediately after measuring the
last existing decode benchmark (Decode_SwitchCommandRequest), and
isolating the new code into its own translation unit lets us see whether
the crash is triggered by one of the new benchmarks or by something
about the new USE_*_PROXY/USE_INFRARED/USE_RADIO_FREQUENCY defines
changing how api_pb2.cpp compiles.
This commit is contained in:
J. Nick Koston
2026-04-29 21:38:01 -05:00
parent f841de0664
commit 483d294ef6
3 changed files with 287 additions and 308 deletions
@@ -1,7 +1,5 @@
#include <benchmark/benchmark.h>
#include <cstring>
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_buffer.h"
@@ -79,188 +77,6 @@ static void Decode_SwitchCommandRequest(benchmark::State &state) {
}
BENCHMARK(Decode_SwitchCommandRequest);
// --- ZWaveProxyFrame decode (~16-byte data buffer) ---
#ifdef USE_ZWAVE_PROXY
static void Decode_ZWaveProxyFrame(benchmark::State &state) {
static const uint8_t frame_data[] = {0x01, 0x09, 0x00, 0x13, 0x01, 0x02, 0x00, 0x00,
0x25, 0x00, 0x05, 0xC4, 0x00, 0x00, 0x00, 0x00};
ZWaveProxyFrame source;
source.data = frame_data;
source.data_len = sizeof(frame_data);
auto encoded = encode_message(source);
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ZWaveProxyFrame msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Decode_ZWaveProxyFrame);
static void Decode_ZWaveProxyRequest(benchmark::State &state) {
static const uint8_t req_data[] = {0xDE, 0xAD, 0xBE, 0xEF};
ZWaveProxyRequest source;
source.type = enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE;
source.data = req_data;
source.data_len = sizeof(req_data);
auto encoded = encode_message(source);
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ZWaveProxyRequest msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Decode_ZWaveProxyRequest);
#endif // USE_ZWAVE_PROXY
// --- SerialProxyWriteRequest decode (instance + 64-byte data) ---
//
// SerialProxyWriteRequest is decode-only (SOURCE_CLIENT), so we encode via
// SerialProxyDataReceived which has identical wire format
// (uint32 instance = 1; bytes data = 2;).
#ifdef USE_SERIAL_PROXY
static void Decode_SerialProxyWriteRequest(benchmark::State &state) {
static constexpr size_t kPayloadSize = 64;
static uint8_t payload[kPayloadSize];
for (size_t i = 0; i < kPayloadSize; i++)
payload[i] = static_cast<uint8_t>(i);
SerialProxyDataReceived source;
source.instance = 0;
source.set_data(payload, kPayloadSize);
auto encoded = encode_message(source);
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
SerialProxyWriteRequest msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Decode_SerialProxyWriteRequest);
#endif // USE_SERIAL_PROXY
// --- InfraredRFTransmitRawTimingsRequest decode (100 zigzag-encoded timings) ---
//
// Hand-built wire bytes since this message is decode-only and has no sister
// type with an identical layout. Wire format:
// field 2 (key, fixed32): tag=0x15, 4 LE bytes
// field 3 (carrier_frequency): tag=0x18, varint
// field 4 (repeat_count): tag=0x20, varint
// field 5 (timings, packed sint32): tag=0x2A, length varint, packed payload
// field 6 (modulation): tag=0x30, varint
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
static APIBuffer build_infrared_rf_transmit_wire() {
// Build the entire wire payload into a stack buffer, then copy into the
// returned APIBuffer in a single resize+memcpy. Keeps allocation count
// low so callgrind/valgrind doesn't churn through hundreds of grow_()s.
uint8_t bytes[256];
size_t len = 0;
auto put_byte = [&](uint8_t b) { bytes[len++] = b; };
auto put_varint = [&](uint32_t v) {
while (v >= 0x80) {
bytes[len++] = static_cast<uint8_t>((v & 0x7F) | 0x80);
v >>= 7;
}
bytes[len++] = static_cast<uint8_t>(v);
};
auto encode_zigzag = [](int32_t v) -> uint32_t {
return (static_cast<uint32_t>(v) << 1) ^ static_cast<uint32_t>(v >> 31);
};
// field 2: key (fixed32) = 0xDEADBEEF
put_byte(0x15);
put_byte(0xEF);
put_byte(0xBE);
put_byte(0xAD);
put_byte(0xDE);
// field 3: carrier_frequency = 38000
put_byte(0x18);
put_varint(38000);
// field 4: repeat_count = 2
put_byte(0x20);
put_varint(2);
// field 5: timings (packed sint32) — 100 entries alternating mark/space.
// Each entry encodes to 2 bytes (zigzag(560)=1120 → varint 0xE0 0x08), so
// packed payload is 200 bytes; with tag (1) + length varint (2) it fits in
// the 256-byte stack buffer.
uint8_t packed[200];
size_t packed_len = 0;
for (int i = 0; i < 100; i++) {
int32_t value = (i % 2 == 0) ? 560 : -560;
uint32_t zz = encode_zigzag(value);
while (zz >= 0x80) {
packed[packed_len++] = static_cast<uint8_t>((zz & 0x7F) | 0x80);
zz >>= 7;
}
packed[packed_len++] = static_cast<uint8_t>(zz);
}
put_byte(0x2A);
put_varint(static_cast<uint32_t>(packed_len));
std::memcpy(bytes + len, packed, packed_len);
len += packed_len;
// field 6: modulation = 0 — skip (default value, not encoded by senders)
APIBuffer buf;
buf.resize(len);
std::memcpy(buf.data(), bytes, len);
return buf;
}
static void Decode_InfraredRFTransmitRawTimingsRequest(benchmark::State &state) {
auto encoded = build_infrared_rf_transmit_wire();
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
InfraredRFTransmitRawTimingsRequest msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Decode_InfraredRFTransmitRawTimingsRequest);
#endif // USE_IR_RF || USE_RADIO_FREQUENCY
// --- LightCommandRequest decode (complex command with many fields) ---
static void Decode_LightCommandRequest(benchmark::State &state) {