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::database::EMPTY_LABEL;
21use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
22use crate::histogram::HistogramType;
23use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR;
24use crate::metrics::{
25 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
26 MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit, TimingDistributionMetric,
27};
28use crate::storage::StorageManager;
29#[cfg(not(feature = "sqlite"))]
30use crate::{
31 common_metric_data::CommonMetricDataInternal, error_recording::record_error, metrics::Metric,
32 Glean,
33};
34
35const MAX_LABELS: usize = 16;
36const OTHER_LABEL: &str = "__other__";
37const MAX_LABEL_LENGTH: usize = 111;
38
39pub type LabeledCounter = LabeledMetric<CounterMetric>;
41
42pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
44
45pub type LabeledString = LabeledMetric<StringMetric>;
47
48pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
50
51pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
53
54pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
56
57pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
59
60pub enum LabeledMetricData {
65 #[allow(missing_docs)]
67 Common { cmd: CommonMetricData },
68 #[allow(missing_docs)]
70 CustomDistribution {
71 cmd: CommonMetricData,
72 range_min: i64,
73 range_max: i64,
74 bucket_count: i64,
75 histogram_type: HistogramType,
76 },
77 #[allow(missing_docs)]
79 MemoryDistribution {
80 cmd: CommonMetricData,
81 unit: MemoryUnit,
82 },
83 #[allow(missing_docs)]
85 TimingDistribution {
86 cmd: CommonMetricData,
87 unit: TimeUnit,
88 },
89}
90
91#[derive(Debug)]
95pub struct LabeledMetric<T> {
96 labels: Option<Vec<Cow<'static, str>>>,
97 submetric: T,
100
101 label_map: Mutex<HashMap<String, Arc<T>>>,
104}
105
106impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
107 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
108 let map = self.label_map.lock().unwrap();
109
110 let shallow_size = if ops.has_malloc_enclosing_size_of() {
114 map.values()
115 .next()
116 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
117 } else {
118 map.capacity()
119 * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
120 };
121
122 let mut map_size = shallow_size;
123 for (k, v) in map.iter() {
124 map_size += k.size_of(ops);
125 map_size += v.size_of(ops);
126 }
127
128 self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
129 }
130}
131
132mod private {
136 use super::LabeledMetricData;
137 use crate::metrics::{
138 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
139 QuantityMetric, StringMetric, TimingDistributionMetric,
140 };
141
142 pub trait Sealed {
148 fn new_inner(meta: LabeledMetricData) -> Self;
150 }
151
152 impl Sealed for CounterMetric {
153 fn new_inner(meta: LabeledMetricData) -> Self {
154 match meta {
155 LabeledMetricData::Common { cmd } => Self::new(cmd),
156 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
157 }
158 }
159 }
160
161 impl Sealed for BooleanMetric {
162 fn new_inner(meta: LabeledMetricData) -> Self {
163 match meta {
164 LabeledMetricData::Common { cmd } => Self::new(cmd),
165 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
166 }
167 }
168 }
169
170 impl Sealed for StringMetric {
171 fn new_inner(meta: LabeledMetricData) -> Self {
172 match meta {
173 LabeledMetricData::Common { cmd } => Self::new(cmd),
174 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
175 }
176 }
177 }
178
179 impl Sealed for CustomDistributionMetric {
180 fn new_inner(meta: LabeledMetricData) -> Self {
181 match meta {
182 LabeledMetricData::CustomDistribution {
183 cmd,
184 range_min,
185 range_max,
186 bucket_count,
187 histogram_type,
188 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
189 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
190 }
191 }
192 }
193
194 impl Sealed for MemoryDistributionMetric {
195 fn new_inner(meta: LabeledMetricData) -> Self {
196 match meta {
197 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
198 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
199 }
200 }
201 }
202
203 impl Sealed for TimingDistributionMetric {
204 fn new_inner(meta: LabeledMetricData) -> Self {
205 match meta {
206 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
207 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
208 }
209 }
210 }
211
212 impl Sealed for QuantityMetric {
213 fn new_inner(meta: LabeledMetricData) -> Self {
214 match meta {
215 LabeledMetricData::Common { cmd } => Self::new(cmd),
216 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
217 }
218 }
219 }
220}
221
222pub trait AllowLabeled: MetricType {
224 fn new_labeled(meta: LabeledMetricData) -> Self;
226}
227
228impl<T> AllowLabeled for T
230where
231 T: MetricType,
232 T: private::Sealed,
233{
234 fn new_labeled(meta: LabeledMetricData) -> Self {
235 T::new_inner(meta)
236 }
237}
238
239impl<T> LabeledMetric<T>
240where
241 T: AllowLabeled + Clone,
242{
243 pub fn new(
247 meta: LabeledMetricData,
248 labels: Option<Vec<Cow<'static, str>>>,
249 ) -> LabeledMetric<T> {
250 let submetric = T::new_labeled(meta);
251 LabeledMetric::new_inner(submetric, labels)
252 }
253
254 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
255 let label_map = Default::default();
256 LabeledMetric {
257 labels,
258 submetric,
259 label_map,
260 }
261 }
262
263 fn new_metric_with_label(&self, label: MetricLabel) -> T {
267 self.submetric.with_label(label)
268 }
269
270 fn new_metric_with_dynamic_label(&self, label: MetricLabel) -> T {
277 self.submetric.with_label(label)
278 }
279
280 fn static_label<'a>(&self, label: &'a str) -> &'a str {
295 debug_assert!(self.labels.is_some());
296 let labels = self.labels.as_ref().unwrap();
297 if labels.iter().any(|l| l == label) {
298 label
299 } else {
300 OTHER_LABEL
301 }
302 }
303
304 pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
316 let label = label.as_ref();
317
318 let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
321
322 let mut map = self.label_map.lock().unwrap();
323 match map.entry(id) {
324 Entry::Occupied(entry) => Arc::clone(entry.get()),
325 Entry::Vacant(entry) => {
326 let metric = match self.labels {
333 Some(_) => {
334 let label = self.static_label(label);
335 self.new_metric_with_label(MetricLabel::Static(label.to_string()))
336 }
337 None => {
338 self.new_metric_with_dynamic_label(MetricLabel::Label(label.to_string()))
339 }
340 };
341 let metric = Arc::new(metric);
342 entry.insert(Arc::clone(&metric));
343 metric
344 }
345 }
346 }
347
348 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
360 crate::block_on_dispatcher();
361 crate::core::with_glean(|glean| {
362 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
363 })
364 }
365}
366
367impl<T, S> TestGetValue for LabeledMetric<T>
368where
369 T: AllowLabeled + TestGetValue<Output = S> + Clone,
370 S: Any,
371{
372 type Output = HashMap<String, S>;
373
374 fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
375 crate::block_on_dispatcher();
377 let labels = crate::core::with_glean(|glean| {
378 let queried_ping_name = ping_name
379 .as_ref()
380 .unwrap_or_else(|| &self.submetric.meta().inner.send_in_pings[0]);
381 StorageManager.snapshot_labels(
382 glean.storage(),
383 queried_ping_name,
384 &self.submetric.meta().base_identifier(),
385 self.submetric.meta().inner.lifetime,
386 )
387 });
388 let mut out = HashMap::new();
389 labels.iter().for_each(|label| {
390 if let Some(v) = self.get(label).test_get_value(ping_name.clone()) {
391 out.insert(label.to_owned(), v);
392 }
393 });
394 Some(out)
395 }
396}
397
398#[cfg(feature = "sqlite")]
399pub fn validate_dynamic_label_sqlite(
400 tx: &rusqlite::Connection,
401 base_identifier: &str,
402 label: &str,
403) -> LabelCheck {
404 let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
405
406 let mut label_already_used = false;
407 let mut label_count = 0;
408 {
409 let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
410 return LabelCheck::Label(label.to_string());
412 };
413
414 let Ok(mut rows) = stmt.query(params![base_identifier]) else {
415 return LabelCheck::Label(label.to_string());
417 };
418
419 while let Ok(Some(row)) = rows.next() {
420 let existing_label: String = row.get(0).unwrap();
421
422 label_count += 1;
423 if existing_label == label {
424 label_already_used = true;
425 break;
426 }
427 }
428 }
429
430 if !label_already_used && label_count >= MAX_LABELS {
431 LabelCheck::Label(String::from(OTHER_LABEL))
432 } else if label.len() > MAX_LABEL_LENGTH {
433 log::warn!(
434 "Metric {:?}: label length {} exceeds maximum of {}",
435 base_identifier,
436 label.len(),
437 MAX_LABEL_LENGTH
438 );
439 LabelCheck::Error(String::from(OTHER_LABEL), 1)
440 } else if label.contains(RECORD_SEPARATOR) {
441 log::warn!(
442 "Metric {:?}: Label cannot contain the ASCII record separator character (0x1E)",
443 base_identifier
444 );
445 LabelCheck::Error(String::from(OTHER_LABEL), 1)
446 } else if label.contains(EMPTY_LABEL) {
447 log::warn!(
448 "Metric {:?}: label can't contain DEL (`\\x7F`)",
449 base_identifier,
450 );
451 LabelCheck::Error(String::from(OTHER_LABEL), 1)
452 } else {
453 LabelCheck::Label(label.to_string())
454 }
455}
456
457#[cfg(not(feature = "sqlite"))]
467pub fn validate_dynamic_label_rkv(
468 glean: &Glean,
469 meta: &CommonMetricDataInternal,
470 base_identifier: &str,
471 label: &str,
472 record: bool,
473) -> String {
474 let key = combine_base_identifier_and_label(base_identifier, label);
475 for store in &meta.inner.send_in_pings {
476 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
477 return label.to_string();
478 }
479 }
480
481 let mut labels = HashSet::new();
482 let mut snapshotter = |_metric_id: &[u8], metric_labels: &[&str], _: &Metric| {
483 for &label in metric_labels {
484 labels.insert(label.to_string());
485 }
486 };
487
488 let lifetime = meta.inner.lifetime;
489 for store in &meta.inner.send_in_pings {
490 glean
491 .storage()
492 .iter_store_from(lifetime, store, Some(base_identifier), &mut snapshotter)
493 .ok();
494 }
495
496 let label_count = labels.len();
497 let error = if label_count >= MAX_LABELS {
498 true
499 } else if label.len() > MAX_LABEL_LENGTH {
500 if record {
501 let msg = format!(
502 "label length {} exceeds maximum of {}",
503 label.len(),
504 MAX_LABEL_LENGTH
505 );
506 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
507 }
508 true
509 } else if label.contains(RECORD_SEPARATOR) {
510 if record {
511 let msg =
512 String::from("Label cannot contain the ASCII record separator character (0x1E)");
513 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
514 }
515 true
516 } else if label.contains(EMPTY_LABEL) {
517 if record {
518 let msg = String::from("label can't contain DEL (`\\x7F`)");
519 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
520 }
521 true
522 } else {
523 false
524 };
525
526 if error {
527 OTHER_LABEL.to_string()
528 } else {
529 label.to_string()
530 }
531}
532
533#[cfg(not(feature = "sqlite"))]
535pub fn combine_base_identifier_and_label(base_identifier: &str, label: &str) -> String {
536 format!("{}/{}", base_identifier, label)
537}