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