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
//! Tools to help testing metrics

use cadence::{SpyMetricSink, StatsdClient};
use crossbeam_channel::Receiver;
use statsd_parser::Message;

/// Helper to collect metrics during tests, and make assertions about them.
pub struct MetricsWatcher {
    /// Crossbeam channel that receives metrics lines as bytes.
    rx: Receiver<Vec<u8>>,

    /// Metrics received by the watcher from [`rx`].
    messages: Vec<statsd_parser::Message>,
}

impl MetricsWatcher {
    /// Make a new metrics watcher, attach it to a [`StatsdClient`] and return both.
    pub fn new_with_client() -> (Self, StatsdClient) {
        let (rx, spy_sink) = SpyMetricSink::new();
        let metrics_client = cadence::StatsdClient::from_sink("", spy_sink);
        let metrics_watcher = Self {
            rx,
            messages: vec![],
        };

        (metrics_watcher, metrics_client)
    }

    /// Consume any waiting events from `rx` and parse them as metrics.
    fn process_events(&mut self) {
        self.messages.extend(self.rx.try_iter().map(|bytes| {
            let s = String::from_utf8(bytes).expect("Invalid UTF8 in metric message");
            statsd_parser::parse(s).expect("Metric message parse error")
        }));
    }

    /// Get a list of all the metrics seen by this watcher, primarily for debugging.
    pub fn all_messages(&mut self) -> &[statsd_parser::Message] {
        self.process_events();
        self.messages.as_slice()
    }

    /// Test if any metric this watcher received matches `predicate`.
    ///
    /// # Example
    ///
    /// ```
    /// # use merino_integration_tests::MetricsWatcher;
    /// # use cadence::CountedExt;
    /// # let (mut metrics_watcher, metrics_client) = MetricsWatcher::new_with_client();
    /// #
    /// use statsd_parser::{Message, Metric, Counter};
    /// metrics_client.incr("a-metric");
    ///
    /// assert!(metrics_watcher.has(|msg| {
    ///     println!("@@@ msg: {:?}", msg);
    ///     msg.name == "a-metric"
    ///         && matches!(msg.metric, Metric::Counter(Counter { value, .. }) if value == 1.0)
    /// }));
    /// ```
    pub fn has<F>(&mut self, predicate: F) -> bool
    where
        F: FnMut(&Message) -> bool,
    {
        self.all_messages().iter().any(predicate)
    }

    /// Test if any metric this watcher received was a histogram with the given name and value.
    ///
    /// Values are compared by taking the absolute difference between them, and
    /// checking if it less than an epsilon of 0.0001.
    pub fn has_histogram(&mut self, name: &str, expected_value: f64) -> bool {
        self.has(|msg| {
            msg.name == name
                && match &msg.metric {
                    statsd_parser::Metric::Histogram(histogram) => {
                        (histogram.value - expected_value).abs() <= 0.0001
                    }
                    _ => false,
                }
        })
    }

    /// Test if any metric this watcher increase in value for a given name.
    pub fn has_incr(&mut self, name: &str) -> bool {
        self.has(|msg| {
            msg.name == name
                && match &msg.metric {
                    statsd_parser::Metric::Counter(counter) => (counter.value - 1.0).abs() < 0.0001,
                    _ => false,
                }
        })
    }
}