Skip to main content

glean_core/
common_metric_data.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::ops::Deref;
6use std::sync::atomic::{AtomicU8, Ordering};
7
8use malloc_size_of_derive::MallocSizeOf;
9
10use crate::error::{Error, ErrorKind};
11use crate::metrics::dual_labeled_counter::validate_dynamic_key_and_or_category;
12use crate::metrics::labeled::validate_dynamic_label;
13use crate::Glean;
14use serde::{Deserialize, Serialize};
15
16/// The supported metrics' lifetimes.
17///
18/// A metric's lifetime determines when its stored data gets reset.
19#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default, MallocSizeOf)]
20#[repr(i32)] // Use i32 to be compatible with our JNA definition
21#[serde(rename_all = "lowercase")]
22pub enum Lifetime {
23    /// The metric is reset with each sent ping
24    #[default]
25    Ping,
26    /// The metric is reset on application restart
27    Application,
28    /// The metric is reset with each user profile
29    User,
30}
31
32impl Lifetime {
33    /// String representation of the lifetime.
34    pub fn as_str(self) -> &'static str {
35        match self {
36            Lifetime::Ping => "ping",
37            Lifetime::Application => "app",
38            Lifetime::User => "user",
39        }
40    }
41}
42
43impl TryFrom<i32> for Lifetime {
44    type Error = Error;
45
46    fn try_from(value: i32) -> Result<Lifetime, Self::Error> {
47        match value {
48            0 => Ok(Lifetime::Ping),
49            1 => Ok(Lifetime::Application),
50            2 => Ok(Lifetime::User),
51            e => Err(ErrorKind::Lifetime(e).into()),
52        }
53    }
54}
55
56/// The common set of data shared across all different metric types.
57#[derive(Default, Debug, Clone, Deserialize, Serialize, MallocSizeOf)]
58pub struct CommonMetricData {
59    /// The metric's name.
60    pub name: String,
61    /// The metric's category.
62    pub category: String,
63    /// List of ping names to include this metric in.
64    pub send_in_pings: Vec<String>,
65    /// The metric's lifetime.
66    pub lifetime: Lifetime,
67    /// Whether or not the metric is disabled.
68    ///
69    /// Disabled metrics are never recorded.
70    pub disabled: bool,
71    /// Dynamic label.
72    ///
73    /// When a [`LabeledMetric<T>`](crate::metrics::LabeledMetric) factory creates the specific
74    /// metric to be recorded to, dynamic labels are stored in the specific
75    /// label so that we can validate them when the Glean singleton is
76    /// available.
77    pub dynamic_label: Option<DynamicLabelType>,
78    /// Whether this metric is inside of the session scope.
79    ///
80    /// `false` (the default) means this metric bypasses session sampling and
81    /// does not carry session metadata. Non-event metrics should use the default
82    /// for now. Event metrics that participate in session tracking must
83    /// explicitly set this to `true`.
84    pub in_session: bool,
85}
86
87/// The type of dynamic label applied to a base metric. Used to help identify
88/// the necessary validation to be performed.
89#[derive(Debug, Clone, Deserialize, Serialize, MallocSizeOf, uniffi::Enum)]
90pub enum DynamicLabelType {
91    /// A dynamic label applied from a `LabeledMetric`
92    Label(String),
93    /// A label applied by a `DualLabeledCounter` that contains a dynamic key
94    KeyOnly(String),
95    /// A label applied by a `DualLabeledCounter` that contains a dynamic category
96    CategoryOnly(String),
97    /// A label applied by a `DualLabeledCounter` that contains a dynamic key and category
98    KeyAndCategory(String),
99}
100
101impl Default for DynamicLabelType {
102    fn default() -> Self {
103        Self::Label(String::new())
104    }
105}
106
107impl Deref for DynamicLabelType {
108    type Target = str;
109
110    fn deref(&self) -> &Self::Target {
111        match self {
112            DynamicLabelType::Label(label) => label,
113            DynamicLabelType::KeyOnly(key) => key,
114            DynamicLabelType::CategoryOnly(category) => category,
115            DynamicLabelType::KeyAndCategory(key_and_category) => key_and_category,
116        }
117    }
118}
119
120#[derive(Default, Debug, MallocSizeOf)]
121pub struct CommonMetricDataInternal {
122    pub inner: CommonMetricData,
123    pub disabled: AtomicU8,
124}
125
126impl Clone for CommonMetricDataInternal {
127    fn clone(&self) -> Self {
128        Self {
129            inner: self.inner.clone(),
130            disabled: AtomicU8::new(self.disabled.load(Ordering::Relaxed)),
131        }
132    }
133}
134
135impl From<CommonMetricData> for CommonMetricDataInternal {
136    fn from(input_data: CommonMetricData) -> Self {
137        let disabled = input_data.disabled;
138        Self {
139            inner: input_data,
140            disabled: AtomicU8::new(u8::from(disabled)),
141        }
142    }
143}
144
145impl CommonMetricDataInternal {
146    /// Creates a new metadata object.
147    pub fn new<A: Into<String>, B: Into<String>, C: Into<String>>(
148        category: A,
149        name: B,
150        ping_name: C,
151    ) -> CommonMetricDataInternal {
152        CommonMetricDataInternal {
153            inner: CommonMetricData {
154                name: name.into(),
155                category: category.into(),
156                send_in_pings: vec![ping_name.into()],
157                ..Default::default()
158            },
159            disabled: AtomicU8::new(0),
160        }
161    }
162
163    /// The metric's base identifier, including the category and name, but not the label.
164    ///
165    /// If `category` is empty, it's ommitted.
166    /// Otherwise, it's the combination of the metric's `category` and `name`.
167    pub(crate) fn base_identifier(&self) -> String {
168        if self.inner.category.is_empty() {
169            self.inner.name.clone()
170        } else {
171            format!("{}.{}", self.inner.category, self.inner.name)
172        }
173    }
174
175    /// The metric's unique identifier, including the category, name and label.
176    ///
177    /// If `category` is empty, it's ommitted.
178    /// Otherwise, it's the combination of the metric's `category`, `name` and `label`.
179    pub(crate) fn identifier(&self, glean: &Glean) -> String {
180        let base_identifier = self.base_identifier();
181
182        if let Some(label) = &self.inner.dynamic_label {
183            match label {
184                DynamicLabelType::Label(label) => {
185                    validate_dynamic_label(glean, self, &base_identifier, label)
186                }
187                _ => validate_dynamic_key_and_or_category(
188                    glean,
189                    self,
190                    &base_identifier,
191                    label.clone(),
192                ),
193            }
194        } else {
195            base_identifier
196        }
197    }
198
199    /// Whether or not the metric is `in_session`.
200    ///
201    /// Metrics that are `in_session` participate in session tracking and carry session metadata.
202    /// This is mostly relevant for event metrics, other metric types should generally not be `in_session`
203    /// and default to `false`.
204    pub fn in_session(&self) -> bool {
205        self.inner.in_session
206    }
207
208    /// The list of storages this metric should be recorded into.
209    pub fn storage_names(&self) -> &[String] {
210        &self.inner.send_in_pings
211    }
212}