rlmesh-runtime 0.1.0-beta.1

Runtime driver for RLMesh model-environment evaluation sessions
Documentation
use std::time::{Duration, Instant};

use rlmesh_proto::core::v1::{OperationMetric, OperationTelemetry, operation_metric};

use super::stats::percentile_ms;
use super::{PhaseTiming, StepTimingSample, TelemetryWindowAccumulator};
use crate::hooks::RuntimeRouteContext;

#[test]
fn phase_timing_tracks_basic_stats() {
    let mut timing = PhaseTiming::default();
    timing.record(Duration::from_millis(10));
    timing.record(Duration::from_millis(30));
    timing.record(Duration::from_millis(20));

    assert_eq!(timing.count, 3);
    assert_eq!(timing.total, Duration::from_millis(60));
    assert_eq!(timing.min, Some(Duration::from_millis(10)));
    assert_eq!(timing.max, Duration::from_millis(30));
    assert!((timing.avg_ms() - 20.0).abs() < f64::EPSILON);
}

#[test]
fn percentile_uses_sorted_window_samples() {
    let samples = [
        Duration::from_millis(10),
        Duration::from_millis(30),
        Duration::from_millis(20),
        Duration::from_millis(40),
    ];
    assert_eq!(percentile_ms(&samples, 0.95), Some(40.0));
}

#[test]
fn telemetry_window_includes_generic_runtime_and_endpoint_timings() {
    let mut accumulator = TelemetryWindowAccumulator::default();
    accumulator.started_at = Instant::now() - Duration::from_secs(2);
    accumulator.record_operation_telemetry(
        "model-a",
        Some(&OperationTelemetry {
            operation: "model.predict".to_string(),
            component_id: String::new(),
            metrics: vec![OperationMetric {
                name: "endpoint.total".to_string(),
                labels: Default::default(),
                value: Some(operation_metric::Value::DurationNs(2_000_000)),
            }],
        }),
    );
    accumulator.record_step(StepTimingSample {
        model_wait: Duration::from_millis(10),
        env_step: Duration::from_millis(20),
        request_bytes: 3,
        response_bytes: 4,
        env_component_id: "env-a",
        model_component_id: "model-a",
    });

    let event = accumulator
        .flush("session-a", RuntimeRouteContext::default())
        .unwrap();

    assert!(event.timings.iter().any(|timing| {
        timing.operation == "model.predict"
            && timing.component_id == "model-a"
            && timing.name == "endpoint.total"
            && timing.avg_ms == Some(2.0)
    }));
    assert!(event.timings.iter().any(|timing| {
        timing.operation == "model.predict"
            && timing.component_id == "model-a"
            && timing.name == "rpc.total"
            && timing.avg_ms == Some(10.0)
    }));
    assert!(event.timings.iter().any(|timing| {
        timing.operation == "env.step"
            && timing.component_id == "env-a"
            && timing.name == "rpc.total"
            && timing.avg_ms == Some(20.0)
    }));
}

#[test]
fn telemetry_summary_keeps_samples_after_window_flush() {
    let mut accumulator = TelemetryWindowAccumulator::default();
    accumulator.started_at = Instant::now() - Duration::from_secs(2);
    accumulator.record_step(StepTimingSample {
        model_wait: Duration::from_millis(10),
        env_step: Duration::from_millis(20),
        request_bytes: 3,
        response_bytes: 4,
        env_component_id: "env-a",
        model_component_id: "model-a",
    });

    let window = accumulator
        .flush("session-a", RuntimeRouteContext::default())
        .unwrap();
    assert_eq!(window.sample_count, 1);

    accumulator.started_at = Instant::now() - Duration::from_secs(2);
    accumulator.record_step(StepTimingSample {
        model_wait: Duration::from_millis(30),
        env_step: Duration::from_millis(40),
        request_bytes: 5,
        response_bytes: 6,
        env_component_id: "env-a",
        model_component_id: "model-a",
    });

    let summary = accumulator
        .summary("session-a", RuntimeRouteContext::default())
        .unwrap();
    assert_eq!(summary.sample_count, 2);
    assert_eq!(summary.env_latency_ms_avg, Some(30.0));
    assert_eq!(summary.model_latency_ms_avg, Some(20.0));
    assert_eq!(summary.round_trip_ms_avg, Some(50.0));
}