[api] Add encode/decode benchmarks for Z-Wave, IR/RF, and serial proxy messages

Mirrors the existing BluetoothLERawAdvertisementsResponse benchmarks for
the remaining proxy message families: ZWaveProxyFrame/ZWaveProxyRequest,
SerialProxyDataReceived/SerialProxyWriteRequest, and
InfraredRFReceiveEvent/InfraredRFTransmitRawTimingsRequest.

Adds minimal stub headers under tests/benchmarks/stubs/ for the
zwave_proxy, infrared, radio_frequency, and serial_proxy components so
api_connection.cpp compiles without dragging in their UART/RMT/BLE
hardware dependencies.
This commit is contained in:
J. Nick Koston
2026-04-29 21:07:39 -05:00
parent 1a871e231d
commit 4c027e87ba
7 changed files with 481 additions and 3 deletions
@@ -77,6 +77,181 @@ 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() {
APIBuffer buf;
auto put_byte = [&](uint8_t b) {
size_t s = buf.size();
buf.resize(s + 1);
buf.data()[s] = b;
};
auto put_varint = [&](uint32_t v) {
while (v >= 0x80) {
put_byte(static_cast<uint8_t>((v & 0x7F) | 0x80));
v >>= 7;
}
put_byte(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.
uint8_t packed[400];
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));
for (size_t i = 0; i < packed_len; i++)
put_byte(packed[i]);
// field 6: modulation = 0 — skip (default value, not encoded by senders)
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) {