1use std::any::Any;
6use std::borrow::Cow;
7#[cfg(feature = "sqlite")]
8use std::collections::BTreeMap;
9use std::collections::HashSet;
10use std::collections::{hash_map::Entry, HashMap};
11use std::mem;
12use std::sync::{Arc, Mutex};
13
14use malloc_size_of::MallocSizeOf;
15#[cfg(feature = "sqlite")]
16use rusqlite::params;
17
18#[cfg(feature = "sqlite")]
19use crate::common_metric_data::LabelCheck;
20use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, MetricLabel};
21use crate::database::EMPTY_LABEL;
22use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
23use crate::histogram::HistogramType;
24use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR;
25use crate::metrics::{
26 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
27 MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit, TimingDistributionMetric,
28};
29use crate::storage::StorageManager;
30#[cfg(not(feature = "sqlite"))]
31use crate::{error_recording::record_error, metrics::Metric, Glean};
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 if label.contains(RECORD_SEPARATOR) {
439 log::warn!(
440 "Metric {:?}: Label cannot contain the ASCII record separator character (0x1E)",
441 base_identifier
442 );
443 LabelCheck::Error(String::from(OTHER_LABEL), 1)
444 } else if label.contains(EMPTY_LABEL) {
445 log::warn!(
446 "Metric {:?}: label can't contain DEL (`\\x7F`)",
447 base_identifier,
448 );
449 LabelCheck::Error(String::from(OTHER_LABEL), 1)
450 } else {
451 LabelCheck::Label(label.to_string())
452 }
453}
454
455#[cfg(feature = "sqlite")]
465pub fn validate_dynamic_label_in_memory(
466 map: &BTreeMap<String, crate::metrics::Metric>,
467 meta: &CommonMetricDataInternal,
468 base_identifier: &str,
469 mut label: &str,
470) -> LabelCheck {
471 for store in &meta.inner.send_in_pings {
472 let key = [store, base_identifier, label].join("|");
473 if map.contains_key(&key) {
474 return LabelCheck::Label(label.to_string());
475 }
476 }
477
478 let mut labels = HashSet::new();
479 for store in &meta.inner.send_in_pings {
480 let prefix_key = [store, base_identifier, ""].join("|");
481
482 for (key, _) in map
483 .range(prefix_key.clone()..)
484 .take_while(|(k, _)| k.starts_with(&prefix_key))
485 {
486 let mut parts = key.splitn(3, '|');
487 let Some(_store) = parts.next() else { continue };
488 let Some(_name) = parts.next() else { continue };
489 let Some(label) = parts.next() else { continue };
490 labels.insert(label);
491 }
492 }
493
494 let label_count = labels.len();
495 let error = if label_count >= MAX_LABELS {
496 label = OTHER_LABEL;
499 false
500 } else if label.len() > MAX_LABEL_LENGTH {
501 true
502 } else {
503 false
504 };
505
506 if error {
507 LabelCheck::Error(OTHER_LABEL.to_string(), 1)
508 } else {
509 LabelCheck::Label(label.to_string())
510 }
511}
512
513#[cfg(not(feature = "sqlite"))]
523pub fn validate_dynamic_label_rkv(
524 glean: &Glean,
525 meta: &CommonMetricDataInternal,
526 base_identifier: &str,
527 label: &str,
528 record: bool,
529) -> String {
530 let key = combine_base_identifier_and_label(base_identifier, label);
531 for store in &meta.inner.send_in_pings {
532 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
533 return label.to_string();
534 }
535 }
536
537 let mut labels = HashSet::new();
538 let mut snapshotter = |_metric_id: &[u8], metric_labels: &[&str], _: &Metric| {
539 for &label in metric_labels {
540 labels.insert(label.to_string());
541 }
542 };
543
544 let lifetime = meta.inner.lifetime;
545 for store in &meta.inner.send_in_pings {
546 glean
547 .storage()
548 .iter_store_from(lifetime, store, Some(base_identifier), &mut snapshotter)
549 .ok();
550 }
551
552 let label_count = labels.len();
553 let error = if label_count >= MAX_LABELS {
554 true
555 } else if label.len() > MAX_LABEL_LENGTH {
556 if record {
557 let msg = format!(
558 "label length {} exceeds maximum of {}",
559 label.len(),
560 MAX_LABEL_LENGTH
561 );
562 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
563 }
564 true
565 } else if label.contains(RECORD_SEPARATOR) {
566 if record {
567 let msg =
568 String::from("Label cannot contain the ASCII record separator character (0x1E)");
569 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
570 }
571 true
572 } else if label.contains(EMPTY_LABEL) {
573 if record {
574 let msg = String::from("label can't contain DEL (`\\x7F`)");
575 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
576 }
577 true
578 } else {
579 false
580 };
581
582 if error {
583 OTHER_LABEL.to_string()
584 } else {
585 label.to_string()
586 }
587}
588
589#[cfg(not(feature = "sqlite"))]
591pub fn combine_base_identifier_and_label(base_identifier: &str, label: &str) -> String {
592 format!("{}/{}", base_identifier, label)
593}