1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*!
Monitor back-end performance using Server-Timing the HTTP header.

```
use simple_server_timing_header::Timer;

fn handle_request() {
    let mut timer = Timer::new();
    // ... do some stuff
    timer.add("parse_headers");
    // ... do some more stuff
    timer.add("get_db_data");
    // Generate the header value
    assert_eq!(timer.header_value(), "parse_headers;dur=0, get_db_data;dur=0");
}
```
*/
use std::time::Instant;

/// Timer used to share performance metrics to the client using the HTTP Server-Timing header
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
pub struct Timer {
    last: Instant,
    timings: Vec<Timing>,
}

struct Timing {
    name: String,
    /// Time in milliseconds
    duration: u128,
}

impl Timer {
    pub fn new() -> Self {
        Timer {
            last: Instant::now(),
            timings: Vec::new(),
        }
    }

    /// Adds a named measurement, counting from the last one
    pub fn add(&mut self, name: &str) {
        let now = Instant::now();
        let duration = now.duration_since(self.last).as_millis();
        self.last = now;
        self.timings.push(Timing {
            name: name.into(),
            duration,
        });
    }

    // Header key for `Server-Timing`
    pub fn header_key() -> &'static str {
        "Server-Timing"
    }

    /// Returns the value for a Server-Timings header.
    /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
    pub fn header_value(&self) -> String {
        let mut out = String::new();
        use std::fmt::Write;
        for timing in self.timings.iter() {
            _ = write!(out, "{};dur={}, ", timing.name, timing.duration);
        }
        // remove the trailing ", "
        out.pop();
        out.pop();

        out
    }
}

impl Default for Timer {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test() {
        let mut timer = Timer::new();
        // ... do some stuff
        timer.add("parse_headers");
        // ... do some more stuff
        timer.add("get_db_data");
        // Generate the header value
        assert_eq!(
            timer.header_value(),
            "parse_headers;dur=0, get_db_data;dur=0"
        );
    }
}