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