summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/api/src/private/custom_distribution.rs
blob: 2114430898e271927672cbd4f5403160e0ff3384 (plain)
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use inherent::inherent;

use super::{CommonMetricData, MetricId};
use glean::{DistributionData, ErrorType, HistogramType};

use crate::ipc::{need_ipc, with_ipc_payload};
use glean::traits::CustomDistribution;

/// A custom distribution metric.
///
/// Custom distributions are used to record the distribution of arbitrary values.
pub enum CustomDistributionMetric {
    Parent {
        /// The metric's ID.
        ///
        /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
        id: MetricId,
        inner: glean::private::CustomDistributionMetric,
    },
    Child(CustomDistributionMetricIpc),
}
#[derive(Debug)]
pub struct CustomDistributionMetricIpc(MetricId);

impl CustomDistributionMetric {
    /// Create a new timing distribution metric.
    pub fn new(
        id: MetricId,
        meta: CommonMetricData,
        range_min: u64,
        range_max: u64,
        bucket_count: u64,
        histogram_type: HistogramType,
    ) -> Self {
        if need_ipc() {
            CustomDistributionMetric::Child(CustomDistributionMetricIpc(id))
        } else {
            debug_assert!(
                range_min <= i64::MAX as u64,
                "sensible limits enforced by glean_parser"
            );
            debug_assert!(
                range_max <= i64::MAX as u64,
                "sensible limits enforced by glean_parser"
            );
            debug_assert!(
                bucket_count <= i64::MAX as u64,
                "sensible limits enforced by glean_parser"
            );
            let inner = glean::private::CustomDistributionMetric::new(
                meta,
                range_min as i64,
                range_max as i64,
                bucket_count as i64,
                histogram_type,
            );
            CustomDistributionMetric::Parent { id, inner }
        }
    }

    #[cfg(test)]
    pub(crate) fn child_metric(&self) -> Self {
        match self {
            CustomDistributionMetric::Parent { id, .. } => {
                CustomDistributionMetric::Child(CustomDistributionMetricIpc(*id))
            }
            CustomDistributionMetric::Child(_) => {
                panic!("Can't get a child metric from a child metric")
            }
        }
    }
}

#[inherent]
impl CustomDistribution for CustomDistributionMetric {
    pub fn accumulate_samples_signed(&self, samples: Vec<i64>) {
        match self {
            CustomDistributionMetric::Parent { inner, .. } => inner.accumulate_samples(samples),
            CustomDistributionMetric::Child(c) => {
                with_ipc_payload(move |payload| {
                    if let Some(v) = payload.custom_samples.get_mut(&c.0) {
                        v.extend(samples);
                    } else {
                        payload.custom_samples.insert(c.0, samples);
                    }
                });
            }
        }
    }

    pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
        &self,
        ping_name: S,
    ) -> Option<DistributionData> {
        let ping_name = ping_name.into().map(|s| s.to_string());
        match self {
            CustomDistributionMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
            CustomDistributionMetric::Child(c) => {
                panic!("Cannot get test value for {:?} in non-parent process!", c)
            }
        }
    }

    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
        match self {
            CustomDistributionMetric::Parent { inner, .. } => {
                inner.test_get_num_recorded_errors(error)
            }
            CustomDistributionMetric::Child(c) => panic!(
                "Cannot get number of recorded errors for {:?} in non-parent process!",
                c
            ),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::{common_test::*, ipc, metrics};

    #[test]
    fn smoke_test_custom_distribution() {
        let _lock = lock_test();

        let metric = &metrics::test_only_ipc::a_custom_dist;

        metric.accumulate_samples_signed(vec![1, 2, 3]);

        assert!(metric.test_get_value("store1").is_some());
    }

    #[test]
    fn custom_distribution_child() {
        let _lock = lock_test();

        let parent_metric = &metrics::test_only_ipc::a_custom_dist;
        parent_metric.accumulate_samples_signed(vec![1, 268435458]);

        {
            let child_metric = parent_metric.child_metric();

            // scope for need_ipc RAII
            let _raii = ipc::test_set_need_ipc(true);

            child_metric.accumulate_samples_signed(vec![4, 268435460]);
        }

        let buf = ipc::take_buf().unwrap();
        assert!(buf.len() > 0);
        assert!(ipc::replay_from_buf(&buf).is_ok());

        let data = parent_metric
            .test_get_value("store1")
            .expect("should have some data");

        assert_eq!(2, data.values[&1], "Low bucket has 2 values");
        assert_eq!(
            2, data.values[&268435456],
            "Next higher bucket has 2 values"
        );
        assert_eq!(
            1 + 4 + 268435458 + 268435460,
            data.sum,
            "Sum of all recorded values"
        );
    }
}