lobe-core 0.1.0

Local HTTP performance profiling engine — the shared library behind the Lobe CLI. Captures DNS/TCP/TLS/TTFB/download phases per request with grounded network baselines.
Documentation
use crate::models::ProbeResult;

#[derive(Debug, Clone, PartialEq)]
pub struct RegressionSummary {
    pub previous_ttfb_ms: u64,
    pub current_ttfb_ms: u64,
    pub delta_ms: i64,
    pub delta_percent: f64,
    pub threshold_percent: f64,
    pub exceeded_threshold: bool,
}

pub fn compare_ttfb(
    previous: &ProbeResult,
    current: &ProbeResult,
    threshold_percent: f64,
) -> Option<RegressionSummary> {
    if previous.report.ttfb_ms == 0 {
        return None;
    }

    let previous_ttfb_ms = previous.report.ttfb_ms;
    let current_ttfb_ms = current.report.ttfb_ms;
    let delta_ms = current_ttfb_ms as i64 - previous_ttfb_ms as i64;
    let delta_percent = ((current_ttfb_ms as f64 - previous_ttfb_ms as f64)
        / previous_ttfb_ms as f64)
        * 100.0;

    Some(RegressionSummary {
        previous_ttfb_ms,
        current_ttfb_ms,
        delta_ms,
        delta_percent,
        threshold_percent,
        exceeded_threshold: delta_percent >= threshold_percent,
    })
}

#[cfg(test)]
mod tests {
    use crate::engine::timing::build_report;
    use crate::models::{ProbeResult, ProbeStatus};

    use super::compare_ttfb;

    fn probe_result(ttfb_ms: u64) -> ProbeResult {
        ProbeResult {
            id: 1,
            target: "http://localhost:3000".to_string(),
            request_path: "/".to_string(),
            report: build_report(10, 20, 30, ttfb_ms, 0),
            status: ProbeStatus::Succeeded,
            response_status_code: Some(200),
            response_status_text: Some("OK".to_string()),
            response_bytes: Some(128),
            error_message: None,
            tls: None,
            created_at_ms: 1,
        }
    }

    #[test]
    fn compare_ttfb_detects_regression_above_threshold() {
        let previous = probe_result(100);
        let current = probe_result(150);

        let summary = compare_ttfb(&previous, &current, 40.0).expect("summary should exist");

        assert_eq!(summary.previous_ttfb_ms, 100);
        assert_eq!(summary.current_ttfb_ms, 150);
        assert_eq!(summary.delta_ms, 50);
        assert_eq!(summary.delta_percent, 50.0);
        assert!(summary.exceeded_threshold);
    }

    #[test]
    fn compare_ttfb_detects_improvement_without_threshold_breach() {
        let previous = probe_result(100);
        let current = probe_result(80);

        let summary = compare_ttfb(&previous, &current, 40.0).expect("summary should exist");

        assert_eq!(summary.delta_ms, -20);
        assert_eq!(summary.delta_percent, -20.0);
        assert!(!summary.exceeded_threshold);
    }

    #[test]
    fn compare_ttfb_returns_none_when_previous_is_zero() {
        let previous = probe_result(0);
        let current = probe_result(80);

        assert!(compare_ttfb(&previous, &current, 40.0).is_none());
    }
}