glean_core/metrics/
labeled.rs

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
5use std::any::Any;
6use std::borrow::Cow;
7use std::collections::HashSet;
8use std::collections::{hash_map::Entry, HashMap};
9use std::mem;
10use std::sync::{Arc, Mutex};
11
12use malloc_size_of::MallocSizeOf;
13
14use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
15use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
16use crate::histogram::HistogramType;
17use crate::metrics::{
18    BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
19    Metric, MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit,
20    TimingDistributionMetric,
21};
22use crate::Glean;
23
24const MAX_LABELS: usize = 16;
25const OTHER_LABEL: &str = "__other__";
26const MAX_LABEL_LENGTH: usize = 111;
27
28/// A labeled counter.
29pub type LabeledCounter = LabeledMetric<CounterMetric>;
30
31/// A labeled boolean.
32pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
33
34/// A labeled string.
35pub type LabeledString = LabeledMetric<StringMetric>;
36
37/// A labeled custom_distribution.
38pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
39
40/// A labeled memory_distribution.
41pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
42
43/// A labeled timing_distribution.
44pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
45
46/// A labeled quantity
47pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
48
49/// The metric data needed to construct inner submetrics.
50///
51/// Different Labeled metrics require different amounts and kinds of information to
52/// be constructed.
53pub enum LabeledMetricData {
54    /// The common case: just a CMD.
55    #[allow(missing_docs)]
56    Common { cmd: CommonMetricData },
57    /// The custom_distribution-specific case.
58    #[allow(missing_docs)]
59    CustomDistribution {
60        cmd: CommonMetricData,
61        range_min: i64,
62        range_max: i64,
63        bucket_count: i64,
64        histogram_type: HistogramType,
65    },
66    /// The memory_distribution-specific case.
67    #[allow(missing_docs)]
68    MemoryDistribution {
69        cmd: CommonMetricData,
70        unit: MemoryUnit,
71    },
72    /// The timing_distribution-specific case.
73    #[allow(missing_docs)]
74    TimingDistribution {
75        cmd: CommonMetricData,
76        unit: TimeUnit,
77    },
78}
79
80/// A labeled metric.
81///
82/// Labeled metrics allow to record multiple sub-metrics of the same type under different string labels.
83#[derive(Debug)]
84pub struct LabeledMetric<T> {
85    labels: Option<Vec<Cow<'static, str>>>,
86    /// Type of the underlying metric
87    /// We hold on to an instance of it, which is cloned to create new modified instances.
88    submetric: T,
89
90    /// A map from a unique ID for the labeled submetric to a handle of an instantiated
91    /// metric type.
92    label_map: Mutex<HashMap<String, Arc<T>>>,
93}
94
95impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
96    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
97        let map = self.label_map.lock().unwrap();
98
99        // Copy of `MallocShallowSizeOf` implementation for `HashMap<K, V>` in `wr_malloc_size_of`.
100        // Note: An instantiated submetric is behind an `Arc`.
101        // `size_of` should only be called from a single thread to avoid double-counting.
102        let shallow_size = if ops.has_malloc_enclosing_size_of() {
103            map.values()
104                .next()
105                .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
106        } else {
107            map.capacity()
108                * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
109        };
110
111        let mut map_size = shallow_size;
112        for (k, v) in map.iter() {
113            map_size += k.size_of(ops);
114            map_size += v.size_of(ops);
115        }
116
117        self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
118    }
119}
120
121/// Sealed traits protect against downstream implementations.
122///
123/// We wrap it in a private module that is inaccessible outside of this module.
124mod private {
125    use super::LabeledMetricData;
126    use crate::metrics::{
127        BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
128        QuantityMetric, StringMetric, TimingDistributionMetric,
129    };
130
131    /// The sealed labeled trait.
132    ///
133    /// This also allows us to hide methods, that are only used internally
134    /// and should not be visible to users of the object implementing the
135    /// `Labeled<T>` trait.
136    pub trait Sealed {
137        /// Create a new `glean_core` metric from the metadata.
138        fn new_inner(meta: LabeledMetricData) -> Self;
139    }
140
141    impl Sealed for CounterMetric {
142        fn new_inner(meta: LabeledMetricData) -> Self {
143            match meta {
144                LabeledMetricData::Common { cmd } => Self::new(cmd),
145                _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
146            }
147        }
148    }
149
150    impl Sealed for BooleanMetric {
151        fn new_inner(meta: LabeledMetricData) -> Self {
152            match meta {
153                LabeledMetricData::Common { cmd } => Self::new(cmd),
154                _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
155            }
156        }
157    }
158
159    impl Sealed for StringMetric {
160        fn new_inner(meta: LabeledMetricData) -> Self {
161            match meta {
162                LabeledMetricData::Common { cmd } => Self::new(cmd),
163                _ => panic!("Incorrect construction of Labeled<StringMetric>"),
164            }
165        }
166    }
167
168    impl Sealed for CustomDistributionMetric {
169        fn new_inner(meta: LabeledMetricData) -> Self {
170            match meta {
171                LabeledMetricData::CustomDistribution {
172                    cmd,
173                    range_min,
174                    range_max,
175                    bucket_count,
176                    histogram_type,
177                } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
178                _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
179            }
180        }
181    }
182
183    impl Sealed for MemoryDistributionMetric {
184        fn new_inner(meta: LabeledMetricData) -> Self {
185            match meta {
186                LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
187                _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
188            }
189        }
190    }
191
192    impl Sealed for TimingDistributionMetric {
193        fn new_inner(meta: LabeledMetricData) -> Self {
194            match meta {
195                LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
196                _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
197            }
198        }
199    }
200
201    impl Sealed for QuantityMetric {
202        fn new_inner(meta: LabeledMetricData) -> Self {
203            match meta {
204                LabeledMetricData::Common { cmd } => Self::new(cmd),
205                _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
206            }
207        }
208    }
209}
210
211/// Trait for metrics that can be nested inside a labeled metric.
212pub trait AllowLabeled: MetricType {
213    /// Create a new labeled metric.
214    fn new_labeled(meta: LabeledMetricData) -> Self;
215}
216
217// Implement the trait for everything we marked as allowed.
218impl<T> AllowLabeled for T
219where
220    T: MetricType,
221    T: private::Sealed,
222{
223    fn new_labeled(meta: LabeledMetricData) -> Self {
224        T::new_inner(meta)
225    }
226}
227
228impl<T> LabeledMetric<T>
229where
230    T: AllowLabeled + Clone,
231{
232    /// Creates a new labeled metric from the given metric instance and optional list of labels.
233    ///
234    /// See [`get`](LabeledMetric::get) for information on how static or dynamic labels are handled.
235    pub fn new(
236        meta: LabeledMetricData,
237        labels: Option<Vec<Cow<'static, str>>>,
238    ) -> LabeledMetric<T> {
239        let submetric = T::new_labeled(meta);
240        LabeledMetric::new_inner(submetric, labels)
241    }
242
243    fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
244        let label_map = Default::default();
245        LabeledMetric {
246            labels,
247            submetric,
248            label_map,
249        }
250    }
251
252    /// Creates a new metric with a specific label.
253    ///
254    /// This is used for static labels where we can just set the name to be `name/label`.
255    fn new_metric_with_name(&self, name: String) -> T {
256        self.submetric.with_name(name)
257    }
258
259    /// Creates a new metric with a specific label.
260    ///
261    /// This is used for dynamic labels where we have to actually validate and correct the
262    /// label later when we have a Glean object.
263    fn new_metric_with_dynamic_label(&self, label: DynamicLabelType) -> T {
264        self.submetric.with_dynamic_label(label)
265    }
266
267    /// Creates a static label.
268    ///
269    /// # Safety
270    ///
271    /// Should only be called when static labels are available on this metric.
272    ///
273    /// # Arguments
274    ///
275    /// * `label` - The requested label
276    ///
277    /// # Returns
278    ///
279    /// The requested label if it is in the list of allowed labels.
280    /// Otherwise `OTHER_LABEL` is returned.
281    fn static_label<'a>(&self, label: &'a str) -> &'a str {
282        debug_assert!(self.labels.is_some());
283        let labels = self.labels.as_ref().unwrap();
284        if labels.iter().any(|l| l == label) {
285            label
286        } else {
287            OTHER_LABEL
288        }
289    }
290
291    /// Gets a specific metric for a given label.
292    ///
293    /// If a set of acceptable labels were specified in the `metrics.yaml` file,
294    /// and the given label is not in the set, it will be recorded under the special `OTHER_LABEL` label.
295    ///
296    /// If a set of acceptable labels was not specified in the `metrics.yaml` file,
297    /// only the first 16 unique labels will be used.
298    /// After that, any additional labels will be recorded under the special `OTHER_LABEL` label.
299    ///
300    /// Labels must have a maximum of 111 characters, and may comprise any printable ASCII characters.
301    /// If an invalid label is used, the metric will be recorded in the special `OTHER_LABEL` label.
302    pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
303        let label = label.as_ref();
304
305        // The handle is a unique number per metric.
306        // The label identifies the submetric.
307        let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
308
309        let mut map = self.label_map.lock().unwrap();
310        match map.entry(id) {
311            Entry::Occupied(entry) => Arc::clone(entry.get()),
312            Entry::Vacant(entry) => {
313                // We have 2 scenarios to consider:
314                // * Static labels. No database access needed. We just look at what is in memory.
315                // * Dynamic labels. We look up in the database all previously stored
316                //   labels in order to keep a maximum of allowed labels. This is done later
317                //   when the specific metric is actually recorded, when we are guaranteed to have
318                //   an initialized Glean object.
319                let metric = match self.labels {
320                    Some(_) => {
321                        let label = self.static_label(label);
322                        self.new_metric_with_name(combine_base_identifier_and_label(
323                            &self.submetric.meta().inner.name,
324                            label,
325                        ))
326                    }
327                    None => self
328                        .new_metric_with_dynamic_label(DynamicLabelType::Label(label.to_string())),
329                };
330                let metric = Arc::new(metric);
331                entry.insert(Arc::clone(&metric));
332                metric
333            }
334        }
335    }
336
337    /// **Exported for test purposes.**
338    ///
339    /// Gets the number of recorded errors for the given metric and error type.
340    ///
341    /// # Arguments
342    ///
343    /// * `error` - The type of error
344    ///
345    /// # Returns
346    ///
347    /// The number of errors reported.
348    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
349        crate::block_on_dispatcher();
350        crate::core::with_glean(|glean| {
351            test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
352        })
353    }
354}
355
356impl<T, S> TestGetValue<HashMap<String, S>> for LabeledMetric<T>
357where
358    T: AllowLabeled + TestGetValue<S>,
359    S: Any,
360{
361    fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
362        let mut out = HashMap::new();
363        let map = self.label_map.lock().unwrap();
364        map.iter().for_each(|(label, submetric)| {
365            if let Some(v) = submetric.test_get_value(ping_name.clone()) {
366                out.insert(
367                    label.replace(&format!("{}/", self.submetric.meta().base_identifier()), ""),
368                    v,
369                );
370            }
371        });
372        Some(out)
373    }
374}
375
376/// Combines a metric's base identifier and label
377pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
378    format!("{}/{}", base_identifer, label)
379}
380
381/// Strips the label off of a complete identifier
382pub fn strip_label(identifier: &str) -> &str {
383    identifier.split_once('/').map_or(identifier, |s| s.0)
384}
385
386/// Validates a dynamic label, changing it to `OTHER_LABEL` if it's invalid.
387///
388/// Checks the requested label against limitations, such as the label length and allowed
389/// characters.
390///
391/// # Arguments
392///
393/// * `label` - The requested label
394///
395/// # Returns
396///
397/// The entire identifier for the metric, including the base identifier and the corrected label.
398/// The errors are logged.
399pub fn validate_dynamic_label(
400    glean: &Glean,
401    meta: &CommonMetricDataInternal,
402    base_identifier: &str,
403    label: &str,
404) -> String {
405    let key = combine_base_identifier_and_label(base_identifier, label);
406    for store in &meta.inner.send_in_pings {
407        if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
408            return key;
409        }
410    }
411
412    let mut labels = HashSet::new();
413    let prefix = &key[..=base_identifier.len()];
414    let mut snapshotter = |metric_id: &[u8], _: &Metric| {
415        labels.insert(metric_id.to_vec());
416    };
417
418    let lifetime = meta.inner.lifetime;
419    for store in &meta.inner.send_in_pings {
420        glean
421            .storage()
422            .iter_store_from(lifetime, store, Some(prefix), &mut snapshotter);
423    }
424
425    let label_count = labels.len();
426    let error = if label_count >= MAX_LABELS {
427        true
428    } else if label.len() > MAX_LABEL_LENGTH {
429        let msg = format!(
430            "label length {} exceeds maximum of {}",
431            label.len(),
432            MAX_LABEL_LENGTH
433        );
434        record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
435        true
436    } else {
437        false
438    };
439
440    if error {
441        combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
442    } else {
443        key
444    }
445}