simple_server_timing_header/
lib.rs

1/*!
2Monitor back-end performance using Server-Timing the HTTP header.
3
4```
5use simple_server_timing_header::Timer;
6
7fn handle_request() {
8    let mut timer = Timer::new();
9    // ... do some stuff
10    timer.add("parse_headers");
11    // ... do some more stuff
12    timer.add("get_db_data");
13    // Generate the header value
14    assert_eq!(timer.header_value(), "parse_headers;dur=0, get_db_data;dur=0");
15}
16```
17*/
18use std::time::Instant;
19
20/// Timer used to share performance metrics to the client using the HTTP Server-Timing header
21/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
22pub struct Timer {
23    last: Instant,
24    timings: Vec<Timing>,
25}
26
27struct Timing {
28    name: String,
29    /// Time in milliseconds
30    duration: u128,
31}
32
33impl Timer {
34    pub fn new() -> Self {
35        Timer {
36            last: Instant::now(),
37            timings: Vec::new(),
38        }
39    }
40
41    /// Adds a named measurement, counting from the last one.
42    /// Only alphanumeric characters are allowed, other characters are replaced with underscores.
43    pub fn add(&mut self, name: &str) {
44        let now = Instant::now();
45        let duration = now.duration_since(self.last).as_millis();
46        self.last = now;
47        self.timings.push(Timing {
48            name: name.into(),
49            duration,
50        });
51    }
52
53    // Header key for `Server-Timing`
54    pub fn header_key() -> &'static str {
55        "Server-Timing"
56    }
57
58    /// Returns the value for a Server-Timings header.
59    /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
60    pub fn header_value(&self) -> String {
61        let mut out = String::new();
62        use std::fmt::Write;
63        for timing in self.timings.iter() {
64            // Special characters and spaces are not properly parsed, so we replace them with underscores
65            let name = timing.name.replace(|c: char| !c.is_alphanumeric(), "_");
66            _ = write!(out, "{};dur={}, ", name, timing.duration);
67        }
68        // remove the trailing ", "
69        out.pop();
70        out.pop();
71
72        out
73    }
74}
75
76impl Default for Timer {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use super::*;
85
86    #[test]
87    fn test() {
88        let mut timer = Timer::new();
89        // ... do some stuff
90        timer.add("parse headers");
91        // ... do some more stuff
92        timer.add("get_db_data");
93        // Generate the header value
94        assert_eq!(
95            timer.header_value(),
96            "parse_headers;dur=0, get_db_data;dur=0"
97        );
98    }
99}