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