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}
79
80/// The type of dynamic label applied to a base metric. Used to help identify
81/// the necessary validation to be performed.
82#[derive(Debug, Clone, Deserialize, Serialize, MallocSizeOf, uniffi::Enum)]
83pub enum DynamicLabelType {
84    /// A dynamic label applied from a `LabeledMetric`
85    Label(String),
86    /// A label applied by a `DualLabeledCounter` that contains a dynamic key
87    KeyOnly(String),
88    /// A label applied by a `DualLabeledCounter` that contains a dynamic category
89    CategoryOnly(String),
90    /// A label applied by a `DualLabeledCounter` that contains a dynamic key and category
91    KeyAndCategory(String),
92}
93
94impl Default for DynamicLabelType {
95    fn default() -> Self {
96        Self::Label(String::new())
97    }
98}
99
100impl Deref for DynamicLabelType {
101    type Target = str;
102
103    fn deref(&self) -> &Self::Target {
104        match self {
105            DynamicLabelType::Label(label) => label,
106            DynamicLabelType::KeyOnly(key) => key,
107            DynamicLabelType::CategoryOnly(category) => category,
108            DynamicLabelType::KeyAndCategory(key_and_category) => key_and_category,
109        }
110    }
111}
112
113#[derive(Default, Debug, MallocSizeOf)]
114pub struct CommonMetricDataInternal {
115    pub inner: CommonMetricData,
116    pub disabled: AtomicU8,
117}
118
119impl Clone for CommonMetricDataInternal {
120    fn clone(&self) -> Self {
121        Self {
122            inner: self.inner.clone(),
123            disabled: AtomicU8::new(self.disabled.load(Ordering::Relaxed)),
124        }
125    }
126}
127
128impl From<CommonMetricData> for CommonMetricDataInternal {
129    fn from(input_data: CommonMetricData) -> Self {
130        let disabled = input_data.disabled;
131        Self {
132            inner: input_data,
133            disabled: AtomicU8::new(u8::from(disabled)),
134        }
135    }
136}
137
138impl CommonMetricDataInternal {
139    /// Creates a new metadata object.
140    pub fn new<A: Into<String>, B: Into<String>, C: Into<String>>(
141        category: A,
142        name: B,
143        ping_name: C,
144    ) -> CommonMetricDataInternal {
145        CommonMetricDataInternal {
146            inner: CommonMetricData {
147                name: name.into(),
148                category: category.into(),
149                send_in_pings: vec![ping_name.into()],
150                ..Default::default()
151            },
152            disabled: AtomicU8::new(0),
153        }
154    }
155
156    /// The metric's base identifier, including the category and name, but not the label.
157    ///
158    /// If `category` is empty, it's ommitted.
159    /// Otherwise, it's the combination of the metric's `category` and `name`.
160    pub(crate) fn base_identifier(&self) -> String {
161        if self.inner.category.is_empty() {
162            self.inner.name.clone()
163        } else {
164            format!("{}.{}", self.inner.category, self.inner.name)
165        }
166    }
167
168    /// The metric's unique identifier, including the category, name and label.
169    ///
170    /// If `category` is empty, it's ommitted.
171    /// Otherwise, it's the combination of the metric's `category`, `name` and `label`.
172    pub(crate) fn identifier(&self, glean: &Glean) -> String {
173        let base_identifier = self.base_identifier();
174
175        if let Some(label) = &self.inner.dynamic_label {
176            match label {
177                DynamicLabelType::Label(label) => {
178                    validate_dynamic_label(glean, self, &base_identifier, label)
179                }
180                _ => validate_dynamic_key_and_or_category(
181                    glean,
182                    self,
183                    &base_identifier,
184                    label.clone(),
185                ),
186            }
187        } else {
188            base_identifier
189        }
190    }
191
192    /// The list of storages this metric should be recorded into.
193    pub fn storage_names(&self) -> &[String] {
194        &self.inner.send_in_pings
195    }
196}