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::fmt::Display;
6use std::sync::atomic::{AtomicU8, Ordering};
7
8use malloc_size_of_derive::MallocSizeOf;
9use rusqlite::Transaction;
10
11use crate::error::{Error, ErrorKind};
12use crate::error_recording::record_error_sqlite;
13use crate::metrics::dual_labeled_counter::validate_dual_label_sqlite;
14use crate::metrics::labeled::validate_dynamic_label_sqlite;
15use crate::{ErrorType, Glean};
16use serde::{Deserialize, Serialize};
17
18/// The supported metrics' lifetimes.
19///
20/// A metric's lifetime determines when its stored data gets reset.
21#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default, MallocSizeOf)]
22#[repr(i32)] // Use i32 to be compatible with our JNA definition
23#[serde(rename_all = "lowercase")]
24pub enum Lifetime {
25    /// The metric is reset with each sent ping
26    #[default]
27    Ping,
28    /// The metric is reset on application restart
29    Application,
30    /// The metric is reset with each user profile
31    User,
32}
33
34impl Lifetime {
35    /// String representation of the lifetime.
36    pub fn as_str(self) -> &'static str {
37        match self {
38            Lifetime::Ping => "ping",
39            Lifetime::Application => "app",
40            Lifetime::User => "user",
41        }
42    }
43}
44
45impl TryFrom<i32> for Lifetime {
46    type Error = Error;
47
48    fn try_from(value: i32) -> Result<Lifetime, Self::Error> {
49        match value {
50            0 => Ok(Lifetime::Ping),
51            1 => Ok(Lifetime::Application),
52            2 => Ok(Lifetime::User),
53            e => Err(ErrorKind::Lifetime(e).into()),
54        }
55    }
56}
57
58/// The common set of data shared across all different metric types.
59#[derive(Default, Debug, Clone, Deserialize, Serialize, MallocSizeOf)]
60pub struct CommonMetricData {
61    /// The metric's name.
62    pub name: String,
63    /// The metric's category.
64    pub category: String,
65    /// List of ping names to include this metric in.
66    pub send_in_pings: Vec<String>,
67    /// The metric's lifetime.
68    pub lifetime: Lifetime,
69    /// Whether or not the metric is disabled.
70    ///
71    /// Disabled metrics are never recorded.
72    pub disabled: bool,
73    /// Whether this metric is inside of the session scope.
74    ///
75    /// `false` (the default) means this metric bypasses session sampling and
76    /// does not carry session metadata. Non-event metrics should use the default
77    /// for now. Event metrics that participate in session tracking must
78    /// explicitly set this to `true`.
79    pub in_session: bool,
80
81    /// Label for this metric.
82    ///
83    /// When a [`LabeledMetric<T>`](crate::metrics::LabeledMetric) factory
84    /// or [`DualLabeledCounterMetric`](crate::metrics::DualLabeledCounterMetric)
85    /// creates the specific metric to be recorded to,
86    /// labels are stored in the metric data
87    /// so that it can validated against the database later.
88    pub label: Option<MetricLabel>,
89}
90
91/// The type of dynamic label applied to a base metric. Used to help identify
92/// the necessary validation to be performed.
93#[derive(Debug, Clone, Deserialize, Serialize, MallocSizeOf, uniffi::Enum)]
94pub enum MetricLabel {
95    /// Static Label -- no validation required
96    Static(String),
97    /// A dynamic label applied from a `LabeledMetric`
98    Label(String),
99    /// A label applied by a `DualLabeledCounter` that contains a dynamic key and static category
100    KeyOnly(String, String),
101    /// A label applied by a `DualLabeledCounter` that contains a static key and dynamic category
102    CategoryOnly(String, String),
103    /// A label applied by a `DualLabeledCounter` that contains a dynamic key and category
104    KeyAndCategory(String, String),
105}
106
107impl Default for MetricLabel {
108    fn default() -> Self {
109        Self::Label(String::new())
110    }
111}
112
113impl Display for MetricLabel {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR;
116        match self {
117            MetricLabel::Static(label) | MetricLabel::Label(label) => write!(f, "{label}"),
118            MetricLabel::KeyOnly(key, category)
119            | MetricLabel::CategoryOnly(key, category)
120            | MetricLabel::KeyAndCategory(key, category) => {
121                write!(f, "{key}{RECORD_SEPARATOR}{category}")
122            }
123        }
124    }
125}
126
127#[derive(Default, Debug, MallocSizeOf)]
128pub struct CommonMetricDataInternal {
129    pub inner: CommonMetricData,
130    pub disabled: AtomicU8,
131}
132
133impl Clone for CommonMetricDataInternal {
134    fn clone(&self) -> Self {
135        Self {
136            inner: self.inner.clone(),
137            disabled: AtomicU8::new(self.disabled.load(Ordering::Relaxed)),
138        }
139    }
140}
141
142impl From<CommonMetricData> for CommonMetricDataInternal {
143    fn from(input_data: CommonMetricData) -> Self {
144        let disabled = input_data.disabled;
145        Self {
146            inner: input_data,
147            disabled: AtomicU8::new(u8::from(disabled)),
148        }
149    }
150}
151
152/// A label checked for validity against label rules and against the database (for a specific metric).
153pub enum LabelCheck {
154    /// No label was supplied, no check done.
155    NoLabel,
156    /// Label was validated and is usable.
157    Label(String),
158    /// Label check errored, the provided replacement label should be used.
159    /// Errors can be recorded using [`LabelCheck::record_error`].
160    Error(String, i32),
161}
162
163impl LabelCheck {
164    /// Extract the label to be used (or an empty string if no label was checked)
165    pub fn label(&self) -> &str {
166        use LabelCheck::*;
167        match self {
168            NoLabel => "",
169            Label(label) | Error(label, _) => label,
170        }
171    }
172
173    /// Record the number of errors that were detected during the label check.
174    pub fn record_error(
175        &self,
176        glean: &Glean,
177        tx: &mut Transaction,
178        metric_name: &str,
179        send_in_pings: &[String],
180    ) {
181        let LabelCheck::Error(_, count) = self else {
182            return;
183        };
184
185        record_error_sqlite(
186            glean,
187            tx,
188            metric_name,
189            send_in_pings,
190            ErrorType::InvalidLabel,
191            *count,
192        );
193    }
194
195    /// Maps a `LabelCheck` by applying a function to the contained label (if any).
196    ///
197    /// This only maps over the already checked label.
198    /// No further label check is done.
199    fn map(self, mut f: impl FnMut(String) -> String) -> Self {
200        use LabelCheck::*;
201
202        match self {
203            NoLabel => NoLabel,
204            Label(s) => Label(f(s)),
205            Error(s, cnt) => Error(f(s), cnt),
206        }
207    }
208}
209
210impl CommonMetricDataInternal {
211    /// Creates a new metadata object.
212    pub fn new<A: Into<String>, B: Into<String>, C: Into<String>>(
213        category: A,
214        name: B,
215        ping_name: C,
216    ) -> CommonMetricDataInternal {
217        CommonMetricDataInternal {
218            inner: CommonMetricData {
219                name: name.into(),
220                category: category.into(),
221                send_in_pings: vec![ping_name.into()],
222                ..Default::default()
223            },
224            disabled: AtomicU8::new(0),
225        }
226    }
227
228    /// The metric's base identifier, including the category and name, but not the label.
229    ///
230    /// If `category` is empty, it's ommitted.
231    /// Otherwise, it's the combination of the metric's `category` and `name`.
232    pub(crate) fn base_identifier(&self) -> String {
233        if self.inner.category.is_empty() {
234            self.inner.name.clone()
235        } else {
236            format!("{}.{}", self.inner.category, self.inner.name)
237        }
238    }
239
240    /// Check the label for validity against the database.
241    ///
242    /// Returns the result of the check.
243    /// No error is recorded in the database if the check fails.
244    /// Extract the validated label, if any, using [`LabelCheck::label`].
245    /// Record an error, if any, using [`LabelCheck::record_error`].
246    pub(crate) fn check_labels(&self, tx: &Transaction<'_>) -> LabelCheck {
247        let base_identifier = self.base_identifier();
248
249        if let Some(label) = &self.inner.label {
250            match label {
251                MetricLabel::Static(label) => LabelCheck::Label(label.to_string()),
252                MetricLabel::Label(label) => {
253                    validate_dynamic_label_sqlite(tx, &base_identifier, label)
254                }
255                MetricLabel::KeyOnly(key, static_category) => {
256                    validate_dual_label_sqlite(tx, &base_identifier, key, "")
257                        .map(|key| format!("{key}{static_category}"))
258                }
259                MetricLabel::CategoryOnly(static_key, category) => {
260                    validate_dual_label_sqlite(tx, &base_identifier, "", category)
261                        .map(|category| format!("{static_key}{category}"))
262                }
263                MetricLabel::KeyAndCategory(key, category) => {
264                    validate_dual_label_sqlite(tx, &base_identifier, key, category)
265                }
266            }
267        } else {
268            LabelCheck::NoLabel
269        }
270    }
271
272    /// Whether or not the metric is `in_session`.
273    ///
274    /// Metrics that are `in_session` participate in session tracking and carry session metadata.
275    /// This is mostly relevant for event metrics, other metric types should generally not be `in_session`
276    /// and default to `false`.
277    pub fn in_session(&self) -> bool {
278        self.inner.in_session
279    }
280
281    /// The list of storages this metric should be recorded into.
282    pub fn storage_names(&self) -> &[String] {
283        &self.inner.send_in_pings
284    }
285}