omena-reactive 0.4.0

Read-only reactive graph primitives for Omena control-plane observation
Documentation
use std::{
    error::Error,
    time::{Duration, Instant},
};

use omena_reactive::{
    ChangePolicyV0, ReactiveEngineV0, ReactiveGraphBuilderV0, ReactiveNodeIdV0, ReactiveStateV0,
    ReactiveValueV0, StabilizeStatusV0,
};

const INPUT_COUNT: usize = 256;
const EVENT_COUNT: usize = 512;
const P95_CEILING: Duration = Duration::from_millis(250);
const MAX_CEILING: Duration = Duration::from_millis(750);

fn project_counter(state: &ReactiveStateV0) -> ReactiveStateV0 {
    match state {
        ReactiveStateV0::Available(ReactiveValueV0::Counter(value)) => {
            ReactiveStateV0::available(ReactiveValueV0::Counter(value.rotate_left(7)))
        }
        _ => ReactiveStateV0::unavailable("counterRequired", "expected a counter deposit"),
    }
}

fn settle(engine: &mut ReactiveEngineV0) -> Result<(), Box<dyn Error>> {
    loop {
        if matches!(
            engine.stabilize_step(4_096)?,
            StabilizeStatusV0::Settled { .. }
        ) {
            return Ok(());
        }
    }
}

fn graph() -> Result<(ReactiveEngineV0, Vec<ReactiveNodeIdV0>), Box<dyn Error>> {
    let policy = ChangePolicyV0::exact("performanceSemanticValue");
    let mut graph = ReactiveGraphBuilderV0::new();
    let mut inputs = Vec::with_capacity(INPUT_COUNT);
    let mut projections = Vec::with_capacity(INPUT_COUNT);
    for index in 0..INPUT_COUNT {
        let input = graph.add_input(
            ReactiveStateV0::available(ReactiveValueV0::Counter(index as u64)),
            policy,
        );
        inputs.push(input);
        projections.push((
            format!("projection-{index}"),
            graph.add_map(input, project_counter, policy),
        ));
    }
    let fold = graph.add_delta_fold(projections, policy)?;
    let boundary = graph.add_effect_boundary(fold, "performance-observation", policy);
    let mut engine = graph.build()?;
    engine.observe(boundary)?;
    settle(&mut engine)?;
    engine.drain_effect_receipts();
    Ok((engine, inputs))
}

fn measure(changes_input: bool) -> Result<Vec<Duration>, Box<dyn Error>> {
    let (mut engine, inputs) = graph()?;
    let mut samples = Vec::with_capacity(EVENT_COUNT);
    for event in 0..EVENT_COUNT {
        let index = event % INPUT_COUNT;
        let value = if changes_input {
            (index + event + 1) as u64
        } else {
            index as u64
        };
        let started = Instant::now();
        engine.deposit(
            inputs[index],
            ReactiveStateV0::available(ReactiveValueV0::Counter(value)),
        )?;
        settle(&mut engine)?;
        engine.drain_effect_receipts();
        samples.push(started.elapsed());
    }
    Ok(samples)
}

fn percentile(samples: &[Duration], percentile: usize) -> Duration {
    let mut sorted = samples.to_vec();
    sorted.sort_unstable();
    let index = (sorted.len() - 1) * percentile / 100;
    sorted[index]
}

fn maximum(samples: &[Duration]) -> Duration {
    samples.iter().copied().max().unwrap_or_default()
}

fn milliseconds(duration: Duration) -> f64 {
    duration.as_secs_f64() * 1_000.0
}

fn main() -> Result<(), Box<dyn Error>> {
    let changed_input = measure(true)?;
    let unchanged_input = measure(false)?;
    let changed_p95 = percentile(&changed_input, 95);
    let changed_max = maximum(&changed_input);
    let unchanged_p95 = percentile(&unchanged_input, 95);
    let unchanged_max = maximum(&unchanged_input);

    println!(
        concat!(
            "{{\"schemaVersion\":\"omena.reactive-engine-step-envelope.v0\",",
            "\"inputCount\":{},\"eventCount\":{},",
            "\"p95CeilingMs\":{:.3},\"maxCeilingMs\":{:.3},",
            "\"changedInput\":{{\"p95Ms\":{:.3},\"maxMs\":{:.3}}},",
            "\"unchangedInput\":{{\"p95Ms\":{:.3},\"maxMs\":{:.3}}}}}"
        ),
        INPUT_COUNT,
        EVENT_COUNT,
        milliseconds(P95_CEILING),
        milliseconds(MAX_CEILING),
        milliseconds(changed_p95),
        milliseconds(changed_max),
        milliseconds(unchanged_p95),
        milliseconds(unchanged_max),
    );

    if changed_p95 > P95_CEILING
        || changed_max > MAX_CEILING
        || unchanged_p95 > P95_CEILING
        || unchanged_max > MAX_CEILING
    {
        return Err("reactive engine stepping exceeded its latency envelope".into());
    }
    Ok(())
}