1use std::borrow::Cow;
6use std::collections::HashSet;
7use std::collections::{hash_map::Entry, HashMap};
8use std::mem;
9use std::sync::{Arc, Mutex};
10
11use malloc_size_of::MallocSizeOf;
12
13use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
14use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
15use crate::histogram::HistogramType;
16use crate::metrics::{
17 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
18 Metric, MetricType, QuantityMetric, StringMetric, TimeUnit, TimingDistributionMetric,
19};
20use crate::Glean;
21
22const MAX_LABELS: usize = 16;
23const OTHER_LABEL: &str = "__other__";
24const MAX_LABEL_LENGTH: usize = 111;
25
26pub type LabeledCounter = LabeledMetric<CounterMetric>;
28
29pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
31
32pub type LabeledString = LabeledMetric<StringMetric>;
34
35pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
37
38pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
40
41pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
43
44pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
46
47pub enum LabeledMetricData {
52 #[allow(missing_docs)]
54 Common { cmd: CommonMetricData },
55 #[allow(missing_docs)]
57 CustomDistribution {
58 cmd: CommonMetricData,
59 range_min: i64,
60 range_max: i64,
61 bucket_count: i64,
62 histogram_type: HistogramType,
63 },
64 #[allow(missing_docs)]
66 MemoryDistribution {
67 cmd: CommonMetricData,
68 unit: MemoryUnit,
69 },
70 #[allow(missing_docs)]
72 TimingDistribution {
73 cmd: CommonMetricData,
74 unit: TimeUnit,
75 },
76}
77
78#[derive(Debug)]
82pub struct LabeledMetric<T> {
83 labels: Option<Vec<Cow<'static, str>>>,
84 submetric: T,
87
88 label_map: Mutex<HashMap<String, Arc<T>>>,
91}
92
93impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
94 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
95 let map = self.label_map.lock().unwrap();
96
97 let shallow_size = if ops.has_malloc_enclosing_size_of() {
101 map.values()
102 .next()
103 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
104 } else {
105 map.capacity()
106 * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
107 };
108
109 let mut map_size = shallow_size;
110 for (k, v) in map.iter() {
111 map_size += k.size_of(ops);
112 map_size += v.size_of(ops);
113 }
114
115 self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
116 }
117}
118
119mod private {
123 use super::LabeledMetricData;
124 use crate::metrics::{
125 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
126 QuantityMetric, StringMetric, TimingDistributionMetric,
127 };
128
129 pub trait Sealed {
135 fn new_inner(meta: LabeledMetricData) -> Self;
137 }
138
139 impl Sealed for CounterMetric {
140 fn new_inner(meta: LabeledMetricData) -> Self {
141 match meta {
142 LabeledMetricData::Common { cmd } => Self::new(cmd),
143 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
144 }
145 }
146 }
147
148 impl Sealed for BooleanMetric {
149 fn new_inner(meta: LabeledMetricData) -> Self {
150 match meta {
151 LabeledMetricData::Common { cmd } => Self::new(cmd),
152 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
153 }
154 }
155 }
156
157 impl Sealed for StringMetric {
158 fn new_inner(meta: LabeledMetricData) -> Self {
159 match meta {
160 LabeledMetricData::Common { cmd } => Self::new(cmd),
161 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
162 }
163 }
164 }
165
166 impl Sealed for CustomDistributionMetric {
167 fn new_inner(meta: LabeledMetricData) -> Self {
168 match meta {
169 LabeledMetricData::CustomDistribution {
170 cmd,
171 range_min,
172 range_max,
173 bucket_count,
174 histogram_type,
175 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
176 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
177 }
178 }
179 }
180
181 impl Sealed for MemoryDistributionMetric {
182 fn new_inner(meta: LabeledMetricData) -> Self {
183 match meta {
184 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
185 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
186 }
187 }
188 }
189
190 impl Sealed for TimingDistributionMetric {
191 fn new_inner(meta: LabeledMetricData) -> Self {
192 match meta {
193 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
194 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
195 }
196 }
197 }
198
199 impl Sealed for QuantityMetric {
200 fn new_inner(meta: LabeledMetricData) -> Self {
201 match meta {
202 LabeledMetricData::Common { cmd } => Self::new(cmd),
203 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
204 }
205 }
206 }
207}
208
209pub trait AllowLabeled: MetricType {
211 fn new_labeled(meta: LabeledMetricData) -> Self;
213}
214
215impl<T> AllowLabeled for T
217where
218 T: MetricType,
219 T: private::Sealed,
220{
221 fn new_labeled(meta: LabeledMetricData) -> Self {
222 T::new_inner(meta)
223 }
224}
225
226impl<T> LabeledMetric<T>
227where
228 T: AllowLabeled + Clone,
229{
230 pub fn new(
234 meta: LabeledMetricData,
235 labels: Option<Vec<Cow<'static, str>>>,
236 ) -> LabeledMetric<T> {
237 let submetric = T::new_labeled(meta);
238 LabeledMetric::new_inner(submetric, labels)
239 }
240
241 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
242 let label_map = Default::default();
243 LabeledMetric {
244 labels,
245 submetric,
246 label_map,
247 }
248 }
249
250 fn new_metric_with_name(&self, name: String) -> T {
254 self.submetric.with_name(name)
255 }
256
257 fn new_metric_with_dynamic_label(&self, label: DynamicLabelType) -> T {
262 self.submetric.with_dynamic_label(label)
263 }
264
265 fn static_label<'a>(&self, label: &'a str) -> &'a str {
280 debug_assert!(self.labels.is_some());
281 let labels = self.labels.as_ref().unwrap();
282 if labels.iter().any(|l| l == label) {
283 label
284 } else {
285 OTHER_LABEL
286 }
287 }
288
289 pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
301 let label = label.as_ref();
302
303 let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
306
307 let mut map = self.label_map.lock().unwrap();
308 match map.entry(id) {
309 Entry::Occupied(entry) => Arc::clone(entry.get()),
310 Entry::Vacant(entry) => {
311 let metric = match self.labels {
318 Some(_) => {
319 let label = self.static_label(label);
320 self.new_metric_with_name(combine_base_identifier_and_label(
321 &self.submetric.meta().inner.name,
322 label,
323 ))
324 }
325 None => self
326 .new_metric_with_dynamic_label(DynamicLabelType::Label(label.to_string())),
327 };
328 let metric = Arc::new(metric);
329 entry.insert(Arc::clone(&metric));
330 metric
331 }
332 }
333 }
334
335 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
347 crate::block_on_dispatcher();
348 crate::core::with_glean(|glean| {
349 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
350 })
351 }
352}
353
354pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
356 format!("{}/{}", base_identifer, label)
357}
358
359pub fn strip_label(identifier: &str) -> &str {
361 identifier.split_once('/').map_or(identifier, |s| s.0)
362}
363
364pub fn validate_dynamic_label(
378 glean: &Glean,
379 meta: &CommonMetricDataInternal,
380 base_identifier: &str,
381 label: &str,
382) -> String {
383 let key = combine_base_identifier_and_label(base_identifier, label);
384 for store in &meta.inner.send_in_pings {
385 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
386 return key;
387 }
388 }
389
390 let mut labels = HashSet::new();
391 let prefix = &key[..=base_identifier.len()];
392 let mut snapshotter = |metric_id: &[u8], _: &Metric| {
393 labels.insert(metric_id.to_vec());
394 };
395
396 let lifetime = meta.inner.lifetime;
397 for store in &meta.inner.send_in_pings {
398 glean
399 .storage()
400 .iter_store_from(lifetime, store, Some(prefix), &mut snapshotter);
401 }
402
403 let label_count = labels.len();
404 let error = if label_count >= MAX_LABELS {
405 true
406 } else if label.len() > MAX_LABEL_LENGTH {
407 let msg = format!(
408 "label length {} exceeds maximum of {}",
409 label.len(),
410 MAX_LABEL_LENGTH
411 );
412 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
413 true
414 } else {
415 false
416 };
417
418 if error {
419 combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
420 } else {
421 key
422 }
423}