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