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,
                }
        })
    }
}