//! ADR-115 P9 — MQTT pipeline throughput micro-benchmark. //! //! Measures the hot-path cost of: //! - Building a HA discovery payload (`DiscoveryBuilder::build`) //! - Encoding a numeric state message (`StateEncoder::numeric`) //! - Rate-limit decision (`RateLimiter::allow`) //! - Privacy filter (`privacy::decide`) //! - Full bus tick across all 10 semantic primitives //! //! Targets (laptop-class, single-threaded, release build): //! - discovery payload: < 5 µs //! - state encode: < 2 µs //! - rate limit: < 100 ns //! - privacy decide: < 50 ns //! - bus tick (10 prim):< 10 µs //! //! The bench is intentionally feature-gated so the default workspace //! build doesn't pull `criterion` in (it has a big-ish dep tree). //! //! Run with: //! cargo bench -p wifi-densepose-sensing-server --bench mqtt_throughput #![cfg(feature = "mqtt")] use std::time::Duration; use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use wifi_densepose_sensing_server::mqtt::{ config::PublishRates, discovery::{DiscoveryBuilder, EntityKind}, privacy::decide, state::{RateLimiter, StateEncoder, VitalsSnapshot}, }; use wifi_densepose_sensing_server::semantic::{PrimitiveConfig, RawSnapshot, SemanticBus}; fn builder() -> DiscoveryBuilder<'static> { DiscoveryBuilder { discovery_prefix: "homeassistant", node_id: "aabbccddeeff", node_friendly_name: Some("Bedroom"), sw_version: "v0.7.0", model: "ESP32-S3 CSI node", via_device: Some("cognitum_seed_1"), } } fn snap() -> VitalsSnapshot { VitalsSnapshot { node_id: "aabbccddeeff".into(), timestamp_ms: 1779_512_400_000, presence: true, fall_detected: false, motion: 0.35, motion_energy: 1234.5, presence_score: 0.91, breathing_rate_bpm: Some(14.2), heartrate_bpm: Some(68.2), n_persons: 1, rssi_dbm: Some(-52.0), vital_confidence: 0.87, } } fn raw_snap() -> RawSnapshot { RawSnapshot { node_id: "aabbccddeeff".into(), since_start: Duration::from_secs(120), timestamp_ms: 1779_512_400_000, presence: true, fall_detected: false, motion: 0.35, motion_energy: 1234.5, breathing_rate_bpm: Some(14.2), heart_rate_bpm: Some(68.2), n_persons: 1, rssi_dbm: Some(-52.0), vital_confidence: 0.87, active_zones: vec!["bathroom".into()], bed_zones: vec!["bedroom".into()], local_seconds_since_midnight: 2 * 3600, } } fn rates() -> PublishRates { PublishRates::default() } fn bench_discovery_payload(c: &mut Criterion) { let b = builder(); c.bench_function("discovery::build_presence", |bench| { bench.iter(|| { let cfg = b.build(black_box(EntityKind::Presence)); black_box(serde_json::to_string(&cfg).unwrap()) }); }); c.bench_function("discovery::build_heart_rate", |bench| { bench.iter(|| { let cfg = b.build(black_box(EntityKind::HeartRate)); black_box(serde_json::to_string(&cfg).unwrap()) }); }); c.bench_function("discovery::build_fall_event", |bench| { bench.iter(|| { let cfg = b.build(black_box(EntityKind::FallDetected)); black_box(serde_json::to_string(&cfg).unwrap()) }); }); } fn bench_state_encode(c: &mut Criterion) { let b = builder(); let s = snap(); let enc = StateEncoder { builder: &b }; c.bench_function("state::numeric_heart_rate", |bench| { bench.iter(|| { black_box(enc.numeric(EntityKind::HeartRate, &s).unwrap()) }); }); c.bench_function("state::boolean_presence", |bench| { bench.iter(|| { black_box(enc.boolean(EntityKind::Presence, true).unwrap()) }); }); c.bench_function("state::event_fall", |bench| { bench.iter(|| { black_box(enc.event(EntityKind::FallDetected, "fall_detected", 0, Some(0.87)).unwrap()) }); }); } fn bench_rate_limit(c: &mut Criterion) { let r = rates(); c.bench_function("rate_limiter::allow_first", |bench| { bench.iter_batched( RateLimiter::new, |mut rl| { black_box(rl.allow( black_box(EntityKind::HeartRate), Duration::from_secs(0), &r, )) }, BatchSize::SmallInput, ); }); c.bench_function("rate_limiter::allow_within_gap", |bench| { bench.iter_batched( || { let mut rl = RateLimiter::new(); rl.allow(EntityKind::HeartRate, Duration::from_secs(0), &r); rl }, |mut rl| { black_box(rl.allow( black_box(EntityKind::HeartRate), Duration::from_secs(1), &r, )) }, BatchSize::SmallInput, ); }); } fn bench_privacy(c: &mut Criterion) { c.bench_function("privacy::decide_hr_strip", |bench| { bench.iter(|| black_box(decide(EntityKind::HeartRate, true))); }); c.bench_function("privacy::decide_presence_keep", |bench| { bench.iter(|| black_box(decide(EntityKind::Presence, true))); }); } fn bench_semantic_bus(c: &mut Criterion) { c.bench_function("semantic::bus_tick_all_10_primitives", |bench| { bench.iter_batched( || (SemanticBus::new(PrimitiveConfig::default()), raw_snap()), |(mut bus, s)| black_box(bus.tick(&s)), BatchSize::SmallInput, ); }); } criterion_group!( benches, bench_discovery_payload, bench_state_encode, bench_rate_limit, bench_privacy, bench_semantic_bus, ); criterion_main!(benches);