1use 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#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default, MallocSizeOf)]
22#[repr(i32)] #[serde(rename_all = "lowercase")]
24pub enum Lifetime {
25 #[default]
27 Ping,
28 Application,
30 User,
32}
33
34impl Lifetime {
35 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#[derive(Default, Debug, Clone, Deserialize, Serialize, MallocSizeOf)]
60pub struct CommonMetricData {
61 pub name: String,
63 pub category: String,
65 pub send_in_pings: Vec<String>,
67 pub lifetime: Lifetime,
69 pub disabled: bool,
73 pub in_session: bool,
80
81 pub label: Option<MetricLabel>,
89}
90
91#[derive(Debug, Clone, Deserialize, Serialize, MallocSizeOf, uniffi::Enum)]
94pub enum MetricLabel {
95 Static(String),
97 Label(String),
99 KeyOnly(String, String),
101 CategoryOnly(String, String),
103 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
152pub enum LabelCheck {
154 NoLabel,
156 Label(String),
158 Error(String, i32),
161}
162
163impl LabelCheck {
164 pub fn label(&self) -> &str {
166 use LabelCheck::*;
167 match self {
168 NoLabel => "",
169 Label(label) | Error(label, _) => label,
170 }
171 }
172
173 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 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 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 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 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 pub fn in_session(&self) -> bool {
278 self.inner.in_session
279 }
280
281 pub fn storage_names(&self) -> &[String] {
283 &self.inner.send_in_pings
284 }
285}