1#[cfg(feature = "sqlite")]
6use std::collections::BTreeMap;
7use std::fmt::Display;
8use std::sync::atomic::{AtomicU8, Ordering};
9
10use malloc_size_of_derive::MallocSizeOf;
11
12#[cfg(feature = "sqlite")]
13use rusqlite::Transaction;
14
15use crate::error::{Error, ErrorKind};
16#[cfg(feature = "sqlite")]
17use crate::error_recording::{record_error, record_error_sqlite};
18
19#[cfg(feature = "sqlite")]
20use crate::metrics::Metric;
21#[cfg(feature = "sqlite")]
22use crate::metrics::{
23 dual_labeled_counter::validate_dual_label_in_memory,
24 dual_labeled_counter::validate_dual_label_sqlite, labeled::validate_dynamic_label_in_memory,
25 labeled::validate_dynamic_label_sqlite,
26};
27
28#[cfg(not(feature = "sqlite"))]
29use crate::metrics::{
30 dual_labeled_counter::validate_dual_label_rkv,
31 labeled::{combine_base_identifier_and_label, validate_dynamic_label_rkv},
32};
33#[cfg(feature = "sqlite")]
34use crate::ErrorType;
35use crate::Glean;
36use serde::{Deserialize, Serialize};
37
38#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default, MallocSizeOf)]
42#[repr(i32)] #[serde(rename_all = "lowercase")]
44pub enum Lifetime {
45 #[default]
47 Ping,
48 Application,
50 User,
52}
53
54impl Lifetime {
55 pub fn as_str(self) -> &'static str {
57 match self {
58 Lifetime::Ping => "ping",
59 Lifetime::Application => "app",
60 Lifetime::User => "user",
61 }
62 }
63}
64
65impl TryFrom<i32> for Lifetime {
66 type Error = Error;
67
68 fn try_from(value: i32) -> Result<Lifetime, Self::Error> {
69 match value {
70 0 => Ok(Lifetime::Ping),
71 1 => Ok(Lifetime::Application),
72 2 => Ok(Lifetime::User),
73 e => Err(ErrorKind::Lifetime(e).into()),
74 }
75 }
76}
77
78#[derive(Default, Debug, Clone, Deserialize, Serialize, MallocSizeOf)]
80pub struct CommonMetricData {
81 pub name: String,
83 pub category: String,
85 pub send_in_pings: Vec<String>,
87 pub lifetime: Lifetime,
89 pub disabled: bool,
93 pub in_session: bool,
100
101 pub label: Option<MetricLabel>,
109}
110
111#[derive(Debug, Clone, Deserialize, Serialize, MallocSizeOf, uniffi::Enum)]
114pub enum MetricLabel {
115 Static(String),
117 Label(String),
119 KeyOnly(String, String),
121 CategoryOnly(String, String),
123 KeyAndCategory(String, String),
125}
126
127impl Default for MetricLabel {
128 fn default() -> Self {
129 Self::Label(String::new())
130 }
131}
132
133impl Display for MetricLabel {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR;
136 match self {
137 MetricLabel::Static(label) | MetricLabel::Label(label) => write!(f, "{label}"),
138 MetricLabel::KeyOnly(key, category)
139 | MetricLabel::CategoryOnly(key, category)
140 | MetricLabel::KeyAndCategory(key, category) => {
141 write!(f, "{key}{RECORD_SEPARATOR}{category}")
142 }
143 }
144 }
145}
146
147#[derive(Default, Debug, MallocSizeOf)]
148pub struct CommonMetricDataInternal {
149 pub inner: CommonMetricData,
150 pub disabled: AtomicU8,
151}
152
153impl Clone for CommonMetricDataInternal {
154 fn clone(&self) -> Self {
155 Self {
156 inner: self.inner.clone(),
157 disabled: AtomicU8::new(self.disabled.load(Ordering::Relaxed)),
158 }
159 }
160}
161
162impl From<CommonMetricData> for CommonMetricDataInternal {
163 fn from(input_data: CommonMetricData) -> Self {
164 let disabled = input_data.disabled;
165 Self {
166 inner: input_data,
167 disabled: AtomicU8::new(u8::from(disabled)),
168 }
169 }
170}
171
172#[cfg(feature = "sqlite")]
174pub enum LabelCheck {
175 NoLabel,
177 Label(String),
179 Error(String, i32),
182}
183
184#[cfg(feature = "sqlite")]
185impl LabelCheck {
186 pub fn label(&self) -> Option<&str> {
188 use LabelCheck::*;
189 match self {
190 NoLabel => None,
191 Label(label) | Error(label, _) => Some(label),
192 }
193 }
194
195 pub fn record_error(
197 &self,
198 glean: &Glean,
199 tx: &mut Transaction,
200 metric_name: &str,
201 send_in_pings: &[String],
202 ) {
203 let LabelCheck::Error(_, count) = self else {
204 return;
205 };
206
207 #[cfg(feature = "sqlite")]
208 record_error_sqlite(
209 glean,
210 tx,
211 metric_name,
212 send_in_pings,
213 ErrorType::InvalidLabel,
214 *count,
215 );
216 }
217
218 pub fn record_error_metric(&self, glean: &Glean, meta: &CommonMetricDataInternal) {
219 let LabelCheck::Error(_, count) = self else {
220 return;
221 };
222
223 record_error(
224 glean,
225 meta,
226 ErrorType::InvalidLabel,
227 "invalid label",
228 *count,
229 );
230 }
231
232 fn map(self, mut f: impl FnMut(String) -> String) -> Self {
237 use LabelCheck::*;
238
239 match self {
240 NoLabel => NoLabel,
241 Label(s) => Label(f(s)),
242 Error(s, cnt) => Error(f(s), cnt),
243 }
244 }
245}
246
247impl CommonMetricDataInternal {
248 pub fn new<A: Into<String>, B: Into<String>, C: Into<String>>(
250 category: A,
251 name: B,
252 ping_name: C,
253 ) -> CommonMetricDataInternal {
254 CommonMetricDataInternal {
255 inner: CommonMetricData {
256 name: name.into(),
257 category: category.into(),
258 send_in_pings: vec![ping_name.into()],
259 ..Default::default()
260 },
261 disabled: AtomicU8::new(0),
262 }
263 }
264
265 pub(crate) fn base_identifier(&self) -> String {
270 if self.inner.category.is_empty() {
271 self.inner.name.clone()
272 } else {
273 let mut id =
274 String::with_capacity(self.inner.category.len() + self.inner.name.len() + 1);
275 id.push_str(&self.inner.category);
276 id.push('.');
277 id.push_str(&self.inner.name);
278 id
279 }
280 }
281
282 #[cfg(not(feature = "sqlite"))]
287 pub(crate) fn identifier(&self, glean: &Glean, record: bool) -> String {
288 let base_identifier = self.base_identifier();
289
290 if let Some(label) = &self.inner.label {
291 let label = match label {
292 MetricLabel::Static(label) => label.to_string(),
293 MetricLabel::Label(label) => {
294 validate_dynamic_label_rkv(glean, self, &base_identifier, label, record)
295 }
296 MetricLabel::KeyOnly(..) => {
297 validate_dual_label_rkv(glean, self, &base_identifier, label, record)
298 }
299 MetricLabel::CategoryOnly(..) => {
300 validate_dual_label_rkv(glean, self, &base_identifier, label, record)
301 }
302 MetricLabel::KeyAndCategory(..) => {
303 validate_dual_label_rkv(glean, self, &base_identifier, label, record)
304 }
305 };
306
307 combine_base_identifier_and_label(&base_identifier, &label)
308 } else {
309 base_identifier
310 }
311 }
312
313 #[cfg(feature = "sqlite")]
320 pub(crate) fn check_labels(&self, tx: &rusqlite::Connection) -> LabelCheck {
321 let base_identifier = self.base_identifier();
322
323 if let Some(label) = &self.inner.label {
324 match label {
325 MetricLabel::Static(label) => LabelCheck::Label(label.to_string()),
326 MetricLabel::Label(label) => {
327 validate_dynamic_label_sqlite(tx, &base_identifier, label)
328 }
329 MetricLabel::KeyOnly(key, static_category) => {
330 validate_dual_label_sqlite(tx, &base_identifier, key, "")
331 .map(|key| [&key[..], &static_category[..]].concat())
332 }
333 MetricLabel::CategoryOnly(static_key, category) => {
334 validate_dual_label_sqlite(tx, &base_identifier, "", category)
335 .map(|category| [&static_key[..], &category[..]].concat())
336 }
337 MetricLabel::KeyAndCategory(key, category) => {
338 validate_dual_label_sqlite(tx, &base_identifier, key, category)
339 }
340 }
341 } else {
342 LabelCheck::NoLabel
343 }
344 }
345
346 #[cfg(feature = "sqlite")]
347 pub(crate) fn check_labels_in_memory(&self, map: &BTreeMap<String, Metric>) -> LabelCheck {
348 let base_identifier = self.base_identifier();
349
350 if let Some(label) = &self.inner.label {
351 match label {
352 MetricLabel::Static(label) => LabelCheck::Label(label.to_string()),
353 MetricLabel::Label(label) => {
354 validate_dynamic_label_in_memory(map, self, &base_identifier, label)
355 }
356 MetricLabel::KeyOnly(key, static_category) => {
357 validate_dual_label_in_memory(map, self, &base_identifier, key, "")
358 .map(|key| format!("{key}{static_category}"))
359 }
360 MetricLabel::CategoryOnly(static_key, category) => {
361 validate_dual_label_in_memory(map, self, &base_identifier, "", category)
362 .map(|category| format!("{static_key}{category}"))
363 }
364 MetricLabel::KeyAndCategory(key, category) => {
365 validate_dual_label_in_memory(map, self, &base_identifier, key, category)
366 }
367 }
368 } else {
369 LabelCheck::NoLabel
370 }
371 }
372
373 pub fn in_session(&self) -> bool {
379 self.inner.in_session
380 }
381
382 pub fn storage_names(&self) -> &[String] {
384 &self.inner.send_in_pings
385 }
386}