1use std::any::Any;
6use std::borrow::Cow;
7#[cfg(not(feature = "sqlite"))]
8use std::collections::HashSet;
9use std::collections::{hash_map::Entry, HashMap};
10use std::mem;
11use std::sync::{Arc, Mutex};
12
13use malloc_size_of::MallocSizeOf;
14#[cfg(feature = "sqlite")]
15use rusqlite::params;
16
17#[cfg(feature = "sqlite")]
18use crate::common_metric_data::LabelCheck;
19use crate::common_metric_data::{CommonMetricData, MetricLabel};
20use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
21use crate::histogram::HistogramType;
22use crate::metrics::{
23 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
24 MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit, TimingDistributionMetric,
25};
26use crate::storage::StorageManager;
27#[cfg(not(feature = "sqlite"))]
28use crate::{
29 common_metric_data::CommonMetricDataInternal, error_recording::record_error, metrics::Metric,
30 Glean,
31};
32
33const MAX_LABELS: usize = 16;
34const OTHER_LABEL: &str = "__other__";
35const MAX_LABEL_LENGTH: usize = 111;
36
37pub type LabeledCounter = LabeledMetric<CounterMetric>;
39
40pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
42
43pub type LabeledString = LabeledMetric<StringMetric>;
45
46pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
48
49pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
51
52pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
54
55pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
57
58pub enum LabeledMetricData {
63 #[allow(missing_docs)]
65 Common { cmd: CommonMetricData },
66 #[allow(missing_docs)]
68 CustomDistribution {
69 cmd: CommonMetricData,
70 range_min: i64,
71 range_max: i64,
72 bucket_count: i64,
73 histogram_type: HistogramType,
74 },
75 #[allow(missing_docs)]
77 MemoryDistribution {
78 cmd: CommonMetricData,
79 unit: MemoryUnit,
80 },
81 #[allow(missing_docs)]
83 TimingDistribution {
84 cmd: CommonMetricData,
85 unit: TimeUnit,
86 },
87}
88
89#[derive(Debug)]
93pub struct LabeledMetric<T> {
94 labels: Option<Vec<Cow<'static, str>>>,
95 submetric: T,
98
99 label_map: Mutex<HashMap<String, Arc<T>>>,
102}
103
104impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
105 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
106 let map = self.label_map.lock().unwrap();
107
108 let shallow_size = if ops.has_malloc_enclosing_size_of() {
112 map.values()
113 .next()
114 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
115 } else {
116 map.capacity()
117 * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
118 };
119
120 let mut map_size = shallow_size;
121 for (k, v) in map.iter() {
122 map_size += k.size_of(ops);
123 map_size += v.size_of(ops);
124 }
125
126 self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
127 }
128}
129
130mod private {
134 use super::LabeledMetricData;
135 use crate::metrics::{
136 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
137 QuantityMetric, StringMetric, TimingDistributionMetric,
138 };
139
140 pub trait Sealed {
146 fn new_inner(meta: LabeledMetricData) -> Self;
148 }
149
150 impl Sealed for CounterMetric {
151 fn new_inner(meta: LabeledMetricData) -> Self {
152 match meta {
153 LabeledMetricData::Common { cmd } => Self::new(cmd),
154 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
155 }
156 }
157 }
158
159 impl Sealed for BooleanMetric {
160 fn new_inner(meta: LabeledMetricData) -> Self {
161 match meta {
162 LabeledMetricData::Common { cmd } => Self::new(cmd),
163 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
164 }
165 }
166 }
167
168 impl Sealed for StringMetric {
169 fn new_inner(meta: LabeledMetricData) -> Self {
170 match meta {
171 LabeledMetricData::Common { cmd } => Self::new(cmd),
172 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
173 }
174 }
175 }
176
177 impl Sealed for CustomDistributionMetric {
178 fn new_inner(meta: LabeledMetricData) -> Self {
179 match meta {
180 LabeledMetricData::CustomDistribution {
181 cmd,
182 range_min,
183 range_max,
184 bucket_count,
185 histogram_type,
186 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
187 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
188 }
189 }
190 }
191
192 impl Sealed for MemoryDistributionMetric {
193 fn new_inner(meta: LabeledMetricData) -> Self {
194 match meta {
195 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
196 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
197 }
198 }
199 }
200
201 impl Sealed for TimingDistributionMetric {
202 fn new_inner(meta: LabeledMetricData) -> Self {
203 match meta {
204 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
205 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
206 }
207 }
208 }
209
210 impl Sealed for QuantityMetric {
211 fn new_inner(meta: LabeledMetricData) -> Self {
212 match meta {
213 LabeledMetricData::Common { cmd } => Self::new(cmd),
214 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
215 }
216 }
217 }
218}
219
220pub trait AllowLabeled: MetricType {
222 fn new_labeled(meta: LabeledMetricData) -> Self;
224}
225
226impl<T> AllowLabeled for T
228where
229 T: MetricType,
230 T: private::Sealed,
231{
232 fn new_labeled(meta: LabeledMetricData) -> Self {
233 T::new_inner(meta)
234 }
235}
236
237impl<T> LabeledMetric<T>
238where
239 T: AllowLabeled + Clone,
240{
241 pub fn new(
245 meta: LabeledMetricData,
246 labels: Option<Vec<Cow<'static, str>>>,
247 ) -> LabeledMetric<T> {
248 let submetric = T::new_labeled(meta);
249 LabeledMetric::new_inner(submetric, labels)
250 }
251
252 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
253 let label_map = Default::default();
254 LabeledMetric {
255 labels,
256 submetric,
257 label_map,
258 }
259 }
260
261 fn new_metric_with_label(&self, label: MetricLabel) -> T {
265 self.submetric.with_label(label)
266 }
267
268 fn new_metric_with_dynamic_label(&self, label: MetricLabel) -> T {
275 self.submetric.with_label(label)
276 }
277
278 fn static_label<'a>(&self, label: &'a str) -> &'a str {
293 debug_assert!(self.labels.is_some());
294 let labels = self.labels.as_ref().unwrap();
295 if labels.iter().any(|l| l == label) {
296 label
297 } else {
298 OTHER_LABEL
299 }
300 }
301
302 pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
314 let label = label.as_ref();
315
316 let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
319
320 let mut map = self.label_map.lock().unwrap();
321 match map.entry(id) {
322 Entry::Occupied(entry) => Arc::clone(entry.get()),
323 Entry::Vacant(entry) => {
324 let metric = match self.labels {
331 Some(_) => {
332 let label = self.static_label(label);
333 self.new_metric_with_label(MetricLabel::Static(label.to_string()))
334 }
335 None => {
336 self.new_metric_with_dynamic_label(MetricLabel::Label(label.to_string()))
337 }
338 };
339 let metric = Arc::new(metric);
340 entry.insert(Arc::clone(&metric));
341 metric
342 }
343 }
344 }
345
346 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
358 crate::block_on_dispatcher();
359 crate::core::with_glean(|glean| {
360 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
361 })
362 }
363}
364
365impl<T, S> TestGetValue for LabeledMetric<T>
366where
367 T: AllowLabeled + TestGetValue<Output = S> + Clone,
368 S: Any,
369{
370 type Output = HashMap<String, S>;
371
372 fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
373 crate::block_on_dispatcher();
375 let labels = crate::core::with_glean(|glean| {
376 let queried_ping_name = ping_name
377 .as_ref()
378 .unwrap_or_else(|| &self.submetric.meta().inner.send_in_pings[0]);
379 StorageManager.snapshot_labels(
380 glean.storage(),
381 queried_ping_name,
382 &self.submetric.meta().base_identifier(),
383 self.submetric.meta().inner.lifetime,
384 )
385 });
386 let mut out = HashMap::new();
387 labels.iter().for_each(|label| {
388 if let Some(v) = self.get(label).test_get_value(ping_name.clone()) {
389 out.insert(label.to_owned(), v);
390 }
391 });
392 Some(out)
393 }
394}
395
396#[cfg(feature = "sqlite")]
397pub fn validate_dynamic_label_sqlite(
398 tx: &rusqlite::Connection,
399 base_identifier: &str,
400 label: &str,
401) -> LabelCheck {
402 let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
403
404 let mut label_already_used = false;
405 let mut label_count = 0;
406 {
407 let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
408 return LabelCheck::Label(label.to_string());
410 };
411
412 let Ok(mut rows) = stmt.query(params![base_identifier]) else {
413 return LabelCheck::Label(label.to_string());
415 };
416
417 while let Ok(Some(row)) = rows.next() {
418 let existing_label: String = row.get(0).unwrap();
419
420 label_count += 1;
421 if existing_label == label {
422 label_already_used = true;
423 break;
424 }
425 }
426 }
427
428 if !label_already_used && label_count >= MAX_LABELS {
429 LabelCheck::Label(String::from(OTHER_LABEL))
430 } else if label.len() > MAX_LABEL_LENGTH {
431 log::warn!(
432 "Metric {:?}: label length {} exceeds maximum of {}",
433 base_identifier,
434 label.len(),
435 MAX_LABEL_LENGTH
436 );
437 LabelCheck::Error(String::from(OTHER_LABEL), 1)
438 } else {
439 LabelCheck::Label(label.to_string())
440 }
441}
442
443#[cfg(not(feature = "sqlite"))]
453pub fn validate_dynamic_label_rkv(
454 glean: &Glean,
455 meta: &CommonMetricDataInternal,
456 base_identifier: &str,
457 label: &str,
458 record: bool,
459) -> String {
460 let key = combine_base_identifier_and_label(base_identifier, label);
461 for store in &meta.inner.send_in_pings {
462 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
463 return label.to_string();
464 }
465 }
466
467 let mut labels = HashSet::new();
468 let mut snapshotter = |_metric_id: &[u8], metric_labels: &[&str], _: &Metric| {
469 for &label in metric_labels {
470 labels.insert(label.to_string());
471 }
472 };
473
474 let lifetime = meta.inner.lifetime;
475 for store in &meta.inner.send_in_pings {
476 glean
477 .storage()
478 .iter_store_from(lifetime, store, Some(base_identifier), &mut snapshotter)
479 .ok();
480 }
481
482 let label_count = labels.len();
483 let error = if label_count >= MAX_LABELS {
484 true
485 } else if label.len() > MAX_LABEL_LENGTH {
486 if record {
487 let msg = format!(
488 "label length {} exceeds maximum of {}",
489 label.len(),
490 MAX_LABEL_LENGTH
491 );
492 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
493 }
494 true
495 } else {
496 false
497 };
498
499 if error {
500 OTHER_LABEL.to_string()
501 } else {
502 label.to_string()
503 }
504}
505
506#[cfg(not(feature = "sqlite"))]
508pub fn combine_base_identifier_and_label(base_identifier: &str, label: &str) -> String {
509 format!("{}/{}", base_identifier, label)
510}