1use std::borrow::Cow;
6use std::char;
7#[cfg(feature = "sqlite")]
8use std::collections::BTreeMap;
9use std::collections::{HashMap, HashSet};
10use std::mem;
11use std::sync::{Arc, Mutex};
12
13#[cfg(feature = "sqlite")]
14use rusqlite::params;
15
16#[cfg(feature = "sqlite")]
17use crate::common_metric_data::LabelCheck;
18use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, MetricLabel};
19use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
20use crate::metrics::{CounterMetric, MetricType};
21use crate::TestGetValue;
22#[cfg(not(feature = "sqlite"))]
23use crate::{error_recording::record_error, metrics::Metric, Glean};
24
25const MAX_LABELS: usize = 16;
26const OTHER_LABEL: &str = "__other__";
27const MAX_LABEL_LENGTH: usize = 111;
28pub(crate) const RECORD_SEPARATOR: char = '\x1E';
29
30#[derive(Debug)]
35pub struct DualLabeledCounterMetric {
36 keys: Option<Vec<Cow<'static, str>>>,
37 categories: Option<Vec<Cow<'static, str>>>,
38 counter: CounterMetric,
41
42 dual_label_map: Mutex<HashMap<(String, String), Arc<CounterMetric>>>,
45}
46
47impl ::malloc_size_of::MallocSizeOf for DualLabeledCounterMetric {
48 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
49 let mut n = 0;
50 n += self.keys.size_of(ops);
51 n += self.categories.size_of(ops);
52 n += self.counter.size_of(ops);
53
54 let map = self.dual_label_map.lock().unwrap();
57
58 let shallow_size = if ops.has_malloc_enclosing_size_of() {
62 map.values()
63 .next()
64 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
65 } else {
66 map.capacity()
67 * (mem::size_of::<String>() + mem::size_of::<Arc<CounterMetric>>() + mem::size_of::<CounterMetric>() + mem::size_of::<usize>())
72 };
73
74 let mut map_size = shallow_size;
75 for (k, v) in map.iter() {
76 map_size += k.size_of(ops);
77 map_size += v.size_of(ops);
78 }
79 n += map_size;
80
81 n
82 }
83}
84
85impl MetricType for DualLabeledCounterMetric {
86 fn meta(&self) -> &CommonMetricDataInternal {
87 self.counter.meta()
88 }
89}
90
91impl DualLabeledCounterMetric {
92 pub fn new(
94 meta: CommonMetricData,
95 keys: Option<Vec<Cow<'static, str>>>,
96 catgories: Option<Vec<Cow<'static, str>>>,
97 ) -> DualLabeledCounterMetric {
98 let submetric = CounterMetric::new(meta);
99 DualLabeledCounterMetric::new_inner(submetric, keys, catgories)
100 }
101
102 fn new_inner(
103 counter: CounterMetric,
104 keys: Option<Vec<Cow<'static, str>>>,
105 categories: Option<Vec<Cow<'static, str>>>,
106 ) -> DualLabeledCounterMetric {
107 let dual_label_map = Default::default();
108 DualLabeledCounterMetric {
109 keys,
110 categories,
111 counter,
112 dual_label_map,
113 }
114 }
115
116 fn new_counter_metric(&self, key: &str, category: &str) -> CounterMetric {
119 match (&self.keys, &self.categories) {
120 (None, None) => self
121 .counter
122 .with_label(MetricLabel::KeyAndCategory(key.into(), category.into())),
123 (None, _) => {
124 let static_category = self.static_category(category);
125 self.counter
126 .with_label(MetricLabel::KeyOnly(key.into(), static_category.into()))
127 }
128 (_, None) => {
129 let static_key = self.static_key(key);
130 self.counter.with_label(MetricLabel::CategoryOnly(
131 static_key.into(),
132 category.into(),
133 ))
134 }
135 (_, _) => {
136 let static_key = self.static_key(key);
138 let static_category = self.static_category(category);
139 let label = format!("{static_key}{RECORD_SEPARATOR}{static_category}");
140 self.counter.with_label(MetricLabel::Static(label))
141 }
142 }
143 }
144
145 fn static_key<'a>(&self, key: &'a str) -> &'a str {
160 debug_assert!(self.keys.is_some());
161 let keys = self.keys.as_ref().unwrap();
162 if keys.iter().any(|l| l == key) {
163 key
164 } else {
165 OTHER_LABEL
166 }
167 }
168
169 fn static_category<'a>(&self, category: &'a str) -> &'a str {
184 debug_assert!(self.categories.is_some());
185 let categories = self.categories.as_ref().unwrap();
186 if categories.iter().any(|l| l == category) {
187 category
188 } else {
189 OTHER_LABEL
190 }
191 }
192
193 pub fn get<S: AsRef<str>>(&self, key: S, category: S) -> Arc<CounterMetric> {
205 let key = key.as_ref();
206 let category = category.as_ref();
207
208 let mut map = self.dual_label_map.lock().unwrap();
209 map.entry((key.to_string(), category.to_string()))
210 .or_insert_with(|| {
211 let metric = self.new_counter_metric(key, category);
212 Arc::new(metric)
213 })
214 .clone()
215 }
216
217 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
229 crate::block_on_dispatcher();
230 crate::core::with_glean(|glean| {
231 test_get_num_recorded_errors(glean, self.counter.meta(), error).unwrap_or(0)
232 })
233 }
234}
235
236impl TestGetValue for DualLabeledCounterMetric {
237 type Output = HashMap<String, HashMap<String, i32>>;
238
239 fn test_get_value(
240 &self,
241 ping_name: Option<String>,
242 ) -> Option<HashMap<String, HashMap<String, i32>>> {
243 let mut out: HashMap<String, HashMap<String, i32>> = HashMap::new();
244 let map = self.dual_label_map.lock().unwrap();
245 for ((key, category), metric) in map.iter() {
246 if let Some(value) = metric.test_get_value(ping_name.clone()) {
247 out.entry(key.clone())
248 .or_default()
249 .insert(category.clone(), value);
250 }
251 }
252 Some(out)
253 }
254}
255
256#[cfg(feature = "sqlite")]
257pub fn validate_dual_label_sqlite(
258 tx: &rusqlite::Connection,
259 base_identifier: &str,
260 key: &str,
261 category: &str,
262) -> LabelCheck {
263 let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
264
265 if key.contains(RECORD_SEPARATOR) || category.contains(RECORD_SEPARATOR) {
269 log::warn!(
270 "Metric {base_identifier:?}: Label cannot contain the ASCII record separator character (0x1E)"
271 );
272 return LabelCheck::Error(format!("{OTHER_LABEL}{RECORD_SEPARATOR}{OTHER_LABEL}"), 1);
273 }
274
275 let mut existing_keys = HashSet::new();
276 let mut existing_categories = HashSet::new();
277 'checkdb: {
278 let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
279 break 'checkdb;
281 };
282
283 let Ok(mut rows) = stmt.query(params![base_identifier]) else {
284 break 'checkdb;
286 };
287
288 while let Ok(Some(row)) = rows.next() {
289 let existing_labels: String = row.get(0).unwrap();
290 let Some((existing_key, existing_category)) =
291 existing_labels.split_once(RECORD_SEPARATOR)
292 else {
293 log::debug!(
295 "Metric {base_identifier:?}: Database contains invalid dual-label: {existing_labels:?}"
296 );
297 continue;
298 };
299
300 existing_keys.insert(existing_key.to_string());
301 existing_categories.insert(existing_category.to_string());
302 }
303 }
304
305 let mut errors = 0;
306 let new_key = if (existing_keys.contains(key) || existing_keys.len() < MAX_LABELS)
307 && label_is_valid(key, base_identifier)
308 {
309 key
310 } else {
311 errors += 1;
312 OTHER_LABEL
313 };
314
315 let new_category = if (existing_categories.contains(category)
316 || existing_categories.len() < MAX_LABELS)
317 && label_is_valid(category, base_identifier)
318 {
319 category
320 } else {
321 errors += 1;
322 OTHER_LABEL
323 };
324
325 let label = format!("{new_key}{RECORD_SEPARATOR}{new_category}");
326 if errors == 0 {
327 LabelCheck::Label(label)
328 } else {
329 LabelCheck::Error(label, errors)
330 }
331}
332
333#[cfg(feature = "sqlite")]
334pub fn validate_dual_label_in_memory(
335 map: &BTreeMap<String, crate::metrics::Metric>,
336 meta: &CommonMetricDataInternal,
337 base_identifier: &str,
338 key: &str,
339 category: &str,
340) -> LabelCheck {
341 if key.contains(RECORD_SEPARATOR) || category.contains(RECORD_SEPARATOR) {
345 log::warn!(
346 "Metric {base_identifier:?}: Label cannot contain the ASCII record separator character (0x1E)"
347 );
348 return LabelCheck::Error(format!("{OTHER_LABEL}{RECORD_SEPARATOR}{OTHER_LABEL}"), 1);
349 }
350
351 let mut existing_keys = HashSet::new();
352 let mut existing_categories = HashSet::new();
353 for store in &meta.inner.send_in_pings {
354 let prefix_key = [store, base_identifier, ""].join("|");
355
356 for key in map.keys() {
357 if key.starts_with(&prefix_key) {
358 let mut parts = key.splitn(3, '|');
359 let Some(_store) = parts.next() else { continue };
360 let Some(_name) = parts.next() else { continue };
361 let Some(existing_labels) = parts.next() else {
362 continue;
363 };
364
365 let Some((existing_key, existing_category)) =
366 existing_labels.split_once(RECORD_SEPARATOR)
367 else {
368 log::debug!(
370 "Metric {base_identifier:?}: Database contains invalid dual-label: {existing_labels:?}"
371 );
372 continue;
373 };
374
375 existing_keys.insert(existing_key.to_string());
376 existing_categories.insert(existing_category.to_string());
377 }
378 }
379 }
380
381 let mut errors = 0;
382 let new_key = if (existing_keys.contains(key) || existing_keys.len() < MAX_LABELS)
383 && label_is_valid(key, base_identifier)
384 {
385 key
386 } else {
387 errors += 1;
388 OTHER_LABEL
389 };
390
391 let new_category = if (existing_categories.contains(category)
392 || existing_categories.len() < MAX_LABELS)
393 && label_is_valid(category, base_identifier)
394 {
395 category
396 } else {
397 errors += 1;
398 OTHER_LABEL
399 };
400
401 let label = format!("{new_key}{RECORD_SEPARATOR}{new_category}");
402 if errors == 0 {
403 LabelCheck::Label(label)
404 } else {
405 LabelCheck::Error(label, errors)
406 }
407}
408
409#[cfg(feature = "sqlite")]
410fn label_is_valid(label: &str, metric_id: &str) -> bool {
411 if label.len() > MAX_LABEL_LENGTH {
412 log::warn!(
413 "Metric {:?}: label length {} exceeds maximum of {}",
414 metric_id,
415 label.len(),
416 MAX_LABEL_LENGTH
417 );
418 false
419 } else if label.contains(RECORD_SEPARATOR) {
420 log::warn!(
421 "Metric {metric_id:?}: Label cannot contain the ASCII record separator character (0x1E)"
422 );
423 false
424 } else {
425 true
426 }
427}
428
429#[cfg(not(feature = "sqlite"))]
430fn label_is_valid_record(label: &str, glean: &Glean, meta: &CommonMetricDataInternal) -> bool {
431 if label.len() > MAX_LABEL_LENGTH {
432 let msg = format!(
433 "label length {} exceeds maximum of {}",
434 label.len(),
435 MAX_LABEL_LENGTH
436 );
437 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
438 false
439 } else {
440 true
441 }
442}
443
444#[cfg(not(feature = "sqlite"))]
454pub fn validate_dual_label_rkv(
455 glean: &Glean,
456 meta: &CommonMetricDataInternal,
457 base_identifier: &str,
458 label: &MetricLabel,
459 record: bool,
460) -> String {
461 let (key, category) = match label {
462 MetricLabel::Static(_) | MetricLabel::Label(_) => {
463 if record {
464 record_error(
465 glean,
466 meta,
467 ErrorType::InvalidLabel,
468 "Invalid `DualLabeledCounter` label format, unable to determine key and/or category",
469 None,
470 );
471 }
472 return combine_labels(OTHER_LABEL, OTHER_LABEL);
473 }
474 MetricLabel::KeyOnly(key, category)
475 | MetricLabel::CategoryOnly(key, category)
476 | MetricLabel::KeyAndCategory(key, category) => (key, category),
477 };
478
479 if key.contains(RECORD_SEPARATOR) || category.contains(RECORD_SEPARATOR) {
480 let msg = "Label cannot contain the ASCII record separator character (0x1E)".to_string();
481 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
482 return combine_labels(OTHER_LABEL, OTHER_LABEL);
483 }
484
485 for store in &meta.inner.send_in_pings {
488 let id = combine_base_identifier_and_labels(base_identifier, key, category);
489 if glean.storage().has_metric(meta.inner.lifetime, store, &id) {
490 return combine_labels(key, category);
491 }
492 }
493
494 let mut key = &key[..];
495 let mut category = &category[..];
496
497 let (seen_keys, seen_categories) = get_seen_keys_and_categories(meta, glean);
500 match label {
501 MetricLabel::KeyOnly(..) => {
502 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
503 || !label_is_valid_record(key, glean, meta)
504 {
505 key = OTHER_LABEL;
506 }
507 }
508 MetricLabel::CategoryOnly(..) => {
509 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
510 || !label_is_valid_record(category, glean, meta)
511 {
512 category = OTHER_LABEL;
513 }
514 }
515 MetricLabel::KeyAndCategory(..) => {
516 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
517 || !label_is_valid_record(key, glean, meta)
518 {
519 key = OTHER_LABEL;
520 }
521 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
522 || !label_is_valid_record(category, glean, meta)
523 {
524 category = OTHER_LABEL;
525 }
526 }
527 _ => {}
529 }
530
531 combine_labels(key, category)
532}
533
534#[cfg(not(feature = "sqlite"))]
535fn get_seen_keys_and_categories(
536 meta: &CommonMetricDataInternal,
537 glean: &Glean,
538) -> (HashSet<String>, HashSet<String>) {
539 let base_identifier = &meta.base_identifier();
540 let mut seen_keys: HashSet<String> = HashSet::new();
541 let mut seen_categories: HashSet<String> = HashSet::new();
542 let mut snapshotter = |_metric_id: &[u8], labels: &[&str], _: &Metric| {
543 if labels.len() == 2 {
544 seen_keys.insert(labels[0].to_string());
545 seen_categories.insert(labels[1].to_string());
546 } else {
547 record_error(
548 glean,
549 meta,
550 ErrorType::InvalidLabel,
551 "Dual Labeled Counter label doesn't contain exactly 2 parts".to_string(),
552 None,
553 );
554 }
555 };
556
557 let lifetime = meta.inner.lifetime;
558 for store in &meta.inner.send_in_pings {
559 glean
560 .storage()
561 .iter_store_from(lifetime, store, Some(base_identifier), &mut snapshotter)
562 .ok();
563 }
564
565 (seen_keys, seen_categories)
566}
567
568#[cfg(not(feature = "sqlite"))]
569fn combine_labels(key: &str, category: &str) -> String {
570 format!("{}{}{}", key, RECORD_SEPARATOR, category)
571}
572
573#[cfg(not(feature = "sqlite"))]
574pub fn combine_base_identifier_and_labels(
575 base_identifier: &str,
576 key: &str,
577 category: &str,
578) -> String {
579 format!(
580 "{}{}{}{}{}",
581 base_identifier, RECORD_SEPARATOR, key, RECORD_SEPARATOR, category
582 )
583}