Skip to main content

har/
timing.rs

1use crate::model::Phases;
2use serde::Serialize;
3
4#[derive(Debug, Clone, Serialize)]
5pub struct PhaseBreakdown {
6    pub blocked: Option<f64>,
7    pub dns: Option<f64>,
8    pub connect: Option<f64>,
9    pub ssl: Option<f64>,
10    pub send: f64,
11    pub wait: f64,
12    pub receive: f64,
13}
14
15impl PhaseBreakdown {
16    pub fn from_phases(p: &Phases) -> Self {
17        PhaseBreakdown {
18            blocked: p.blocked,
19            dns: p.dns,
20            connect: p.connect,
21            ssl: p.ssl,
22            send: p.send,
23            wait: p.wait,
24            receive: p.receive,
25        }
26    }
27}
28
29/// Label the dominant timing phase, or "unknown" when nothing is positive.
30pub fn classify_bottleneck(p: &Phases) -> &'static str {
31    let candidates = [
32        ("queueing/blocked", p.blocked.unwrap_or(0.0)),
33        ("DNS", p.dns.unwrap_or(0.0)),
34        ("TCP connect", p.connect.unwrap_or(0.0)),
35        ("TLS handshake", p.ssl.unwrap_or(0.0)),
36        ("request upload", p.send),
37        ("server wait/TTFB", p.wait),
38        ("download/receive", p.receive),
39    ];
40    let mut best: (&'static str, f64) = ("unknown", 0.0);
41    for (label, v) in candidates {
42        if v > best.1 {
43            best = (label, v);
44        }
45    }
46    best.0
47}
48
49#[cfg(test)]
50mod tests {
51    use super::{PhaseBreakdown, classify_bottleneck};
52    use crate::model::Phases;
53
54    #[test]
55    fn picks_dominant_phase() {
56        let p = Phases {
57            wait: 500.0,
58            receive: 10.0,
59            send: 1.0,
60            ..Phases::default()
61        };
62        assert_eq!(classify_bottleneck(&p), "server wait/TTFB");
63    }
64
65    #[test]
66    fn dns_dominant() {
67        let p = Phases {
68            dns: Some(300.0),
69            wait: 5.0,
70            ..Phases::default()
71        };
72        assert_eq!(classify_bottleneck(&p), "DNS");
73    }
74
75    #[test]
76    fn all_zero_is_unknown() {
77        assert_eq!(classify_bottleneck(&Phases::default()), "unknown");
78    }
79
80    #[test]
81    fn breakdown_copies_phases() {
82        let p = Phases {
83            dns: Some(3.0),
84            wait: 9.0,
85            ..Phases::default()
86        };
87        let b = PhaseBreakdown::from_phases(&p);
88        assert_eq!(b.dns, Some(3.0));
89        assert_eq!(b.wait, 9.0);
90    }
91}