Files

388 lines
12 KiB
C++

#include <benchmark/benchmark.h>
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_buffer.h"
namespace esphome::api::benchmarks {
// Inner iteration count to amortize CodSpeed instrumentation overhead.
// Without this, the ~60ns per-iteration valgrind start/stop cost dominates
// sub-microsecond benchmarks.
static constexpr int kInnerIterations = 2000;
// --- SensorStateResponse (highest frequency message) ---
static void Encode_SensorStateResponse(benchmark::State &state) {
APIBuffer buffer;
SensorStateResponse msg;
msg.key = 0x12345678;
msg.state = 23.5f;
msg.missing_state = false;
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_SensorStateResponse);
static void CalculateSize_SensorStateResponse(benchmark::State &state) {
SensorStateResponse msg;
msg.key = 0x12345678;
msg.state = 23.5f;
msg.missing_state = false;
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_SensorStateResponse);
// Steady state: buffer already allocated from previous iteration
static void CalcAndEncode_SensorStateResponse(benchmark::State &state) {
APIBuffer buffer;
SensorStateResponse msg;
msg.key = 0x12345678;
msg.state = 23.5f;
msg.missing_state = false;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_SensorStateResponse);
// Cold path: fresh buffer each iteration (measures heap allocation cost).
// Inner loop still needed to amortize CodSpeed instrumentation overhead.
// Each inner iteration creates a fresh buffer, so this measures
// alloc+calc+encode per item.
static void CalcAndEncode_SensorStateResponse_Fresh(benchmark::State &state) {
SensorStateResponse msg;
msg.key = 0x12345678;
msg.state = 23.5f;
msg.missing_state = false;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
APIBuffer buffer;
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
benchmark::DoNotOptimize(buffer.data());
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_SensorStateResponse_Fresh);
// --- BinarySensorStateResponse ---
static void Encode_BinarySensorStateResponse(benchmark::State &state) {
APIBuffer buffer;
BinarySensorStateResponse msg;
msg.key = 0xAABBCCDD;
msg.state = true;
msg.missing_state = false;
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_BinarySensorStateResponse);
// --- HelloResponse (string fields) ---
static void Encode_HelloResponse(benchmark::State &state) {
APIBuffer buffer;
HelloResponse msg;
msg.api_version_major = 1;
msg.api_version_minor = 10;
msg.server_info = StringRef::from_lit("esphome v2026.3.0");
msg.name = StringRef::from_lit("living-room-sensor");
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_HelloResponse);
// --- LightStateResponse (complex multi-field message) ---
static void Encode_LightStateResponse(benchmark::State &state) {
APIBuffer buffer;
LightStateResponse msg;
msg.key = 0x11223344;
msg.state = true;
msg.brightness = 0.8f;
msg.color_mode = enums::COLOR_MODE_RGB_WHITE;
msg.color_brightness = 1.0f;
msg.red = 1.0f;
msg.green = 0.5f;
msg.blue = 0.2f;
msg.white = 0.0f;
msg.color_temperature = 4000.0f;
msg.cold_white = 0.0f;
msg.warm_white = 0.0f;
msg.effect = StringRef::from_lit("rainbow");
uint32_t size = msg.calculate_size();
buffer.resize(size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_LightStateResponse);
static void CalculateSize_LightStateResponse(benchmark::State &state) {
LightStateResponse msg;
msg.key = 0x11223344;
msg.state = true;
msg.brightness = 0.8f;
msg.color_mode = enums::COLOR_MODE_RGB_WHITE;
msg.color_brightness = 1.0f;
msg.red = 1.0f;
msg.green = 0.5f;
msg.blue = 0.2f;
msg.white = 0.0f;
msg.color_temperature = 4000.0f;
msg.cold_white = 0.0f;
msg.warm_white = 0.0f;
msg.effect = StringRef::from_lit("rainbow");
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_LightStateResponse);
// --- DeviceInfoResponse (nested submessages: 20 devices + 20 areas) ---
static DeviceInfoResponse make_device_info_response() {
DeviceInfoResponse msg;
msg.name = StringRef::from_lit("living-room-sensor");
msg.mac_address = StringRef::from_lit("AA:BB:CC:DD:EE:FF");
msg.esphome_version = StringRef::from_lit("2026.3.0");
msg.compilation_time = StringRef::from_lit("Mar 16 2026, 12:00:00");
msg.model = StringRef::from_lit("esp32-poe-iso");
msg.manufacturer = StringRef::from_lit("Olimex");
msg.friendly_name = StringRef::from_lit("Living Room Sensor");
#ifdef USE_DEVICES
for (uint32_t i = 0; i < ESPHOME_DEVICE_COUNT && i < 20; i++) {
msg.devices[i].device_id = i + 1;
msg.devices[i].name = StringRef::from_lit("device");
msg.devices[i].area_id = (i % 20) + 1;
}
#endif
#ifdef USE_AREAS
for (uint32_t i = 0; i < ESPHOME_AREA_COUNT && i < 20; i++) {
msg.areas[i].area_id = i + 1;
msg.areas[i].name = StringRef::from_lit("area");
}
#endif
return msg;
}
static void CalculateSize_DeviceInfoResponse(benchmark::State &state) {
auto msg = make_device_info_response();
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_DeviceInfoResponse);
static void Encode_DeviceInfoResponse(benchmark::State &state) {
auto msg = make_device_info_response();
APIBuffer buffer;
uint32_t total_size = msg.calculate_size();
buffer.resize(total_size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_DeviceInfoResponse);
// Steady state: buffer already allocated from previous iteration
static void CalcAndEncode_DeviceInfoResponse(benchmark::State &state) {
auto msg = make_device_info_response();
APIBuffer buffer;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_DeviceInfoResponse);
// Cold path: fresh buffer each iteration (measures heap allocation cost).
// Inner loop still needed to amortize CodSpeed instrumentation overhead.
// Each inner iteration creates a fresh buffer, so this measures
// alloc+calc+encode per item.
static void CalcAndEncode_DeviceInfoResponse_Fresh(benchmark::State &state) {
auto msg = make_device_info_response();
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
APIBuffer buffer;
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
benchmark::DoNotOptimize(buffer.data());
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_DeviceInfoResponse_Fresh);
// --- BluetoothLERawAdvertisementsResponse (12 adverts, highest-volume BLE message) ---
#ifdef USE_BLUETOOTH_PROXY
static BluetoothLERawAdvertisementsResponse make_ble_raw_advs_12() {
static const uint8_t fake_adv_data[] = {
0x02, 0x01, 0x06, 0x03, 0x03, 0x9F, 0xFE, 0x17, 0x16, 0x9F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
BluetoothLERawAdvertisementsResponse msg;
msg.advertisements_len = 12;
for (int i = 0; i < 12; i++) {
auto &adv = msg.advertisements[i];
adv.address = 0xAABBCCDD0000ULL + i;
adv.rssi = -60 - i;
adv.address_type = 1;
memcpy(adv.data, fake_adv_data, sizeof(fake_adv_data));
adv.data_len = sizeof(fake_adv_data);
}
return msg;
}
static void CalculateSize_BLERawAdvs12(benchmark::State &state) {
auto msg = make_ble_raw_advs_12();
for (auto _ : state) {
uint32_t result = 0;
for (int i = 0; i < kInnerIterations; i++) {
result += msg.calculate_size();
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalculateSize_BLERawAdvs12);
static void Encode_BLERawAdvs12(benchmark::State &state) {
auto msg = make_ble_raw_advs_12();
APIBuffer buffer;
uint32_t total_size = msg.calculate_size();
buffer.resize(total_size);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Encode_BLERawAdvs12);
static void CalcAndEncode_BLERawAdvs12(benchmark::State &state) {
auto msg = make_ble_raw_advs_12();
APIBuffer buffer;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
}
benchmark::DoNotOptimize(buffer.data());
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_BLERawAdvs12);
static void CalcAndEncode_BLERawAdvs12_Fresh(benchmark::State &state) {
auto msg = make_ble_raw_advs_12();
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
APIBuffer buffer;
uint32_t size = msg.calculate_size();
buffer.resize(size);
ProtoWriteBuffer writer(&buffer, 0);
msg.encode(writer);
benchmark::DoNotOptimize(buffer.data());
}
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(CalcAndEncode_BLERawAdvs12_Fresh);
#endif // USE_BLUETOOTH_PROXY
} // namespace esphome::api::benchmarks