Skip to main content

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::{hash_map::Entry, HashMap};
8use std::mem;
9use std::sync::{Arc, Mutex};
10
11use malloc_size_of::MallocSizeOf;
12use rusqlite::{params, Transaction};
13
14use crate::common_metric_data::{CommonMetricData, LabelCheck, MetricLabel};
15use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
16use crate::histogram::HistogramType;
17use crate::metrics::{
18    BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
19    MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit, TimingDistributionMetric,
20};
21use crate::storage::StorageManager;
22
23const MAX_LABELS: usize = 16;
24const OTHER_LABEL: &str = "__other__";
25const MAX_LABEL_LENGTH: usize = 111;
26
27/// A labeled counter.
28pub type LabeledCounter = LabeledMetric<CounterMetric>;
29
30/// A labeled boolean.
31pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
32
33/// A labeled string.
34pub type LabeledString = LabeledMetric<StringMetric>;
35
36/// A labeled custom_distribution.
37pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
38
39/// A labeled memory_distribution.
40pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
41
42/// A labeled timing_distribution.
43pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
44
45/// A labeled quantity
46pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
47
48/// The metric data needed to construct inner submetrics.
49///
50/// Different Labeled metrics require different amounts and kinds of information to
51/// be constructed.
52pub enum LabeledMetricData {
53    /// The common case: just a CMD.
54    #[allow(missing_docs)]
55    Common { cmd: CommonMetricData },
56    /// The custom_distribution-specific case.
57    #[allow(missing_docs)]
58    CustomDistribution {
59        cmd: CommonMetricData,
60        range_min: i64,
61        range_max: i64,
62        bucket_count: i64,
63        histogram_type: HistogramType,
64    },
65    /// The memory_distribution-specific case.
66    #[allow(missing_docs)]
67    MemoryDistribution {
68        cmd: CommonMetricData,
69        unit: MemoryUnit,
70    },
71    /// The timing_distribution-specific case.
72    #[allow(missing_docs)]
73    TimingDistribution {
74        cmd: CommonMetricData,
75        unit: TimeUnit,
76    },
77}
78
79/// A labeled metric.
80///
81/// Labeled metrics allow to record multiple sub-metrics of the same type under different string labels.
82#[derive(Debug)]
83pub struct LabeledMetric<T> {
84    labels: Option<Vec<Cow<'static, str>>>,
85    /// Type of the underlying metric
86    /// We hold on to an instance of it, which is cloned to create new modified instances.
87    submetric: T,
88
89    /// A map from a unique ID for the labeled submetric to a handle of an instantiated
90    /// metric type.
91    label_map: Mutex<HashMap<String, Arc<T>>>,
92}
93
94impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
95    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
96        let map = self.label_map.lock().unwrap();
97
98        // Copy of `MallocShallowSizeOf` implementation for `HashMap<K, V>` in `wr_malloc_size_of`.
99        // Note: An instantiated submetric is behind an `Arc`.
100        // `size_of` should only be called from a single thread to avoid double-counting.
101        let shallow_size = if ops.has_malloc_enclosing_size_of() {
102            map.values()
103                .next()
104                .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
105        } else {
106            map.capacity()
107                * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
108        };
109
110        let mut map_size = shallow_size;
111        for (k, v) in map.iter() {
112            map_size += k.size_of(ops);
113            map_size += v.size_of(ops);
114        }
115
116        self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
117    }
118}
119
120/// Sealed traits protect against downstream implementations.
121///
122/// We wrap it in a private module that is inaccessible outside of this module.
123mod private {
124    use super::LabeledMetricData;
125    use crate::metrics::{
126        BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
127        QuantityMetric, StringMetric, TimingDistributionMetric,
128    };
129
130    /// The sealed labeled trait.
131    ///
132    /// This also allows us to hide methods, that are only used internally
133    /// and should not be visible to users of the object implementing the
134    /// `Labeled<T>` trait.
135    pub trait Sealed {
136        /// Create a new `glean_core` metric from the metadata.
137        fn new_inner(meta: LabeledMetricData) -> Self;
138    }
139
140    impl Sealed for CounterMetric {
141        fn new_inner(meta: LabeledMetricData) -> Self {
142            match meta {
143                LabeledMetricData::Common { cmd } => Self::new(cmd),
144                _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
145            }
146        }
147    }
148
149    impl Sealed for BooleanMetric {
150        fn new_inner(meta: LabeledMetricData) -> Self {
151            match meta {
152                LabeledMetricData::Common { cmd } => Self::new(cmd),
153                _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
154            }
155        }
156    }
157
158    impl Sealed for StringMetric {
159        fn new_inner(meta: LabeledMetricData) -> Self {
160            match meta {
161                LabeledMetricData::Common { cmd } => Self::new(cmd),
162                _ => panic!("Incorrect construction of Labeled<StringMetric>"),
163            }
164        }
165    }
166
167    impl Sealed for CustomDistributionMetric {
168        fn new_inner(meta: LabeledMetricData) -> Self {
169            match meta {
170                LabeledMetricData::CustomDistribution {
171                    cmd,
172                    range_min,
173                    range_max,
174                    bucket_count,
175                    histogram_type,
176                } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
177                _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
178            }
179        }
180    }
181
182    impl Sealed for MemoryDistributionMetric {
183        fn new_inner(meta: LabeledMetricData) -> Self {
184            match meta {
185                LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
186                _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
187            }
188        }
189    }
190
191    impl Sealed for TimingDistributionMetric {
192        fn new_inner(meta: LabeledMetricData) -> Self {
193            match meta {
194                LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
195                _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
196            }
197        }
198    }
199
200    impl Sealed for QuantityMetric {
201        fn new_inner(meta: LabeledMetricData) -> Self {
202            match meta {
203                LabeledMetricData::Common { cmd } => Self::new(cmd),
204                _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
205            }
206        }
207    }
208}
209
210/// Trait for metrics that can be nested inside a labeled metric.
211pub trait AllowLabeled: MetricType {
212    /// Create a new labeled metric.
213    fn new_labeled(meta: LabeledMetricData) -> Self;
214}
215
216// Implement the trait for everything we marked as allowed.
217impl<T> AllowLabeled for T
218where
219    T: MetricType,
220    T: private::Sealed,
221{
222    fn new_labeled(meta: LabeledMetricData) -> Self {
223        T::new_inner(meta)
224    }
225}
226
227impl<T> LabeledMetric<T>
228where
229    T: AllowLabeled + Clone,
230{
231    /// Creates a new labeled metric from the given metric instance and optional list of labels.
232    ///
233    /// See [`get`](LabeledMetric::get) for information on how static or dynamic labels are handled.
234    pub fn new(
235        meta: LabeledMetricData,
236        labels: Option<Vec<Cow<'static, str>>>,
237    ) -> LabeledMetric<T> {
238        let submetric = T::new_labeled(meta);
239        LabeledMetric::new_inner(submetric, labels)
240    }
241
242    fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
243        let label_map = Default::default();
244        LabeledMetric {
245            labels,
246            submetric,
247            label_map,
248        }
249    }
250
251    /// Creates a new metric with a specific label.
252    ///
253    /// This is used for static labels where we can just set the name to be `name/label`.
254    fn new_metric_with_label(&self, label: MetricLabel) -> T {
255        self.submetric.with_label(label)
256    }
257
258    /// Creates a new metric with a specific label.
259    ///
260    /// This is used for dynamic labels where we have to actually validate and correct the
261    /// label later when we have a Glean object.
262    ///
263    /// TODO: Consolidate with `new_metric_with_label` above.
264    fn new_metric_with_dynamic_label(&self, label: MetricLabel) -> T {
265        self.submetric.with_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_label(MetricLabel::Static(label.to_string()))
324                    }
325                    None => {
326                        self.new_metric_with_dynamic_label(MetricLabel::Label(label.to_string()))
327                    }
328                };
329                let metric = Arc::new(metric);
330                entry.insert(Arc::clone(&metric));
331                metric
332            }
333        }
334    }
335
336    /// **Exported for test purposes.**
337    ///
338    /// Gets the number of recorded errors for the given metric and error type.
339    ///
340    /// # Arguments
341    ///
342    /// * `error` - The type of error
343    ///
344    /// # Returns
345    ///
346    /// The number of errors reported.
347    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
348        crate::block_on_dispatcher();
349        crate::core::with_glean(|glean| {
350            test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
351        })
352    }
353}
354
355impl<T, S> TestGetValue for LabeledMetric<T>
356where
357    T: AllowLabeled + TestGetValue<Output = S> + Clone,
358    S: Any,
359{
360    type Output = HashMap<String, S>;
361
362    fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
363        // We get the labels from the db because our in-memory cache is not guaranteed to be complete.
364        crate::block_on_dispatcher();
365        let labels = crate::core::with_glean(|glean| {
366            let queried_ping_name = ping_name
367                .as_ref()
368                .unwrap_or_else(|| &self.submetric.meta().inner.send_in_pings[0]);
369            StorageManager.snapshot_labels(
370                glean.storage(),
371                queried_ping_name,
372                &self.submetric.meta().base_identifier(),
373                self.submetric.meta().inner.lifetime,
374            )
375        });
376        let mut out = HashMap::new();
377        labels.iter().for_each(|label| {
378            if let Some(v) = self.get(label).test_get_value(ping_name.clone()) {
379                out.insert(label.to_owned(), v);
380            }
381        });
382        Some(out)
383    }
384}
385
386pub fn validate_dynamic_label_sqlite(
387    tx: &Transaction,
388    base_identifier: &str,
389    label: &str,
390) -> LabelCheck {
391    let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
392
393    let mut label_already_used = false;
394    let mut label_count = 0;
395    {
396        let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
397            // If we can't fetch from the database, assume the label is ok to use
398            return LabelCheck::Label(label.to_string());
399        };
400
401        let Ok(mut rows) = stmt.query(params![base_identifier]) else {
402            // If we can't fetch from the database, assume the label is ok to use
403            return LabelCheck::Label(label.to_string());
404        };
405
406        while let Ok(Some(row)) = rows.next() {
407            let existing_label: String = row.get(0).unwrap();
408
409            label_count += 1;
410            if existing_label == label {
411                label_already_used = true;
412                break;
413            }
414        }
415    }
416
417    if !label_already_used && label_count >= MAX_LABELS {
418        LabelCheck::Label(String::from(OTHER_LABEL))
419    } else if label.len() > MAX_LABEL_LENGTH {
420        log::warn!(
421            "Metric {:?}: label length {} exceeds maximum of {}",
422            base_identifier,
423            label.len(),
424            MAX_LABEL_LENGTH
425        );
426        LabelCheck::Error(String::from(OTHER_LABEL), 1)
427    } else {
428        LabelCheck::Label(label.to_string())
429    }
430}