1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 use inherent::inherent;
6 
7 use glean::traits::Counter;
8 
9 use super::CommonMetricData;
10 
11 use crate::ipc::{need_ipc, with_ipc_payload};
12 use crate::private::{CounterMetric, MetricId};
13 use std::collections::HashMap;
14 
15 /// A counter metric that knows it's a labeled counter's submetric.
16 ///
17 /// It has special work to do when in a non-parent process.
18 /// When on the parent process, it dispatches calls to the normal CounterMetric.
19 #[derive(Clone)]
20 pub enum LabeledCounterMetric {
21     Parent(CounterMetric),
22     Child { id: MetricId, label: String },
23 }
24 
25 impl LabeledCounterMetric {
26     /// Create a new labeled counter submetric.
new(id: MetricId, meta: CommonMetricData, label: String) -> Self27     pub fn new(id: MetricId, meta: CommonMetricData, label: String) -> Self {
28         if need_ipc() {
29             LabeledCounterMetric::Child { id, label }
30         } else {
31             LabeledCounterMetric::Parent(CounterMetric::new(id, meta))
32         }
33     }
34 
35     #[cfg(test)]
metric_id(&self) -> MetricId36     pub(crate) fn metric_id(&self) -> MetricId {
37         match self {
38             LabeledCounterMetric::Parent(p) => p.metric_id(),
39             LabeledCounterMetric::Child { id, .. } => *id,
40         }
41     }
42 }
43 
44 #[inherent(pub)]
45 impl Counter for LabeledCounterMetric {
46     /// Increase the counter by `amount`.
47     ///
48     /// ## Arguments
49     ///
50     /// * `amount` - The amount to increase by. Should be positive.
51     ///
52     /// ## Notes
53     ///
54     /// Logs an error if the `amount` is 0 or negative.
add(&self, amount: i32)55     fn add(&self, amount: i32) {
56         match self {
57             LabeledCounterMetric::Parent(p) => p.add(amount),
58             LabeledCounterMetric::Child { id, label } => {
59                 with_ipc_payload(move |payload| {
60                     if let Some(map) = payload.labeled_counters.get_mut(id) {
61                         if let Some(v) = map.get_mut(label) {
62                             *v += amount;
63                         } else {
64                             map.insert(label.to_string(), amount);
65                         }
66                     } else {
67                         let mut map = HashMap::new();
68                         map.insert(label.to_string(), amount);
69                         payload.labeled_counters.insert(*id, map);
70                     }
71                 });
72             }
73         }
74     }
75 
76     /// **Test-only API.**
77     ///
78     /// Get the currently stored value as an integer.
79     /// This doesn't clear the stored value.
80     ///
81     /// ## Arguments
82     ///
83     /// * `ping_name` - the storage name to look into.
84     ///
85     /// ## Return value
86     ///
87     /// Returns the stored value or `None` if nothing stored.
test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32>88     fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32> {
89         match self {
90             LabeledCounterMetric::Parent(p) => p.test_get_value(ping_name),
91             LabeledCounterMetric::Child { id, .. } => {
92                 panic!("Cannot get test value for {:?} in non-parent process!", id)
93             }
94         }
95     }
96 
97     /// **Test-only API.**
98     ///
99     /// Gets the number of recorded errors for the given metric and error type.
100     ///
101     /// # Arguments
102     ///
103     /// * `error` - The type of error
104     /// * `ping_name` - represents the optional name of the ping to retrieve the
105     ///   metric for. Defaults to the first value in `send_in_pings`.
106     ///
107     /// # Returns
108     ///
109     /// The number of errors reported.
test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>( &self, error: glean::ErrorType, ping_name: S, ) -> i32110     fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
111         &self,
112         error: glean::ErrorType,
113         ping_name: S,
114     ) -> i32 {
115         match self {
116             LabeledCounterMetric::Parent(p) => p.test_get_num_recorded_errors(error, ping_name),
117             LabeledCounterMetric::Child { id, .. } => panic!(
118                 "Cannot get the number of recorded errors for {:?} in non-parent process!",
119                 id
120             ),
121         }
122     }
123 }
124 
125 #[cfg(test)]
126 mod test {
127     use crate::{common_test::*, ipc, metrics};
128 
129     #[test]
sets_labeled_counter_value_parent()130     fn sets_labeled_counter_value_parent() {
131         let _lock = lock_test();
132 
133         let metric = &metrics::test_only_ipc::a_labeled_counter;
134         metric.get("a_label").add(1);
135 
136         assert_eq!(1, metric.get("a_label").test_get_value("store1").unwrap());
137     }
138 
139     #[test]
sets_labeled_counter_value_child()140     fn sets_labeled_counter_value_child() {
141         let _lock = lock_test();
142 
143         let label = "some_label";
144 
145         let parent_metric = &metrics::test_only_ipc::a_labeled_counter;
146         parent_metric.get(label).add(3);
147 
148         {
149             // scope for need_ipc RAII
150             let _raii = ipc::test_set_need_ipc(true);
151             let child_metric = parent_metric.get(label);
152 
153             let metric_id = child_metric.metric_id();
154 
155             child_metric.add(42);
156 
157             ipc::with_ipc_payload(move |payload| {
158                 assert_eq!(
159                     42,
160                     *payload
161                         .labeled_counters
162                         .get(&metric_id)
163                         .unwrap()
164                         .get(label)
165                         .unwrap(),
166                     "Stored the correct value in the ipc payload"
167                 );
168             });
169         }
170 
171         assert!(
172             false == ipc::need_ipc(),
173             "RAII dropped, should not need ipc any more"
174         );
175         assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
176 
177         assert_eq!(
178             45,
179             parent_metric.get(label).test_get_value("store1").unwrap(),
180             "Values from the 'processes' should be summed"
181         );
182     }
183 }
184