1use std::borrow::Cow;
6use std::char;
7use std::collections::{HashMap, HashSet};
8use std::mem;
9use std::sync::{Arc, Mutex};
10
11#[cfg(feature = "sqlite")]
12use rusqlite::params;
13
14#[cfg(feature = "sqlite")]
15use crate::common_metric_data::LabelCheck;
16use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, MetricLabel};
17use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
18use crate::metrics::{CounterMetric, MetricType};
19use crate::TestGetValue;
20#[cfg(not(feature = "sqlite"))]
21use crate::{error_recording::record_error, metrics::Metric, Glean};
22
23const MAX_LABELS: usize = 16;
24const OTHER_LABEL: &str = "__other__";
25const MAX_LABEL_LENGTH: usize = 111;
26pub(crate) const RECORD_SEPARATOR: char = '\x1E';
27
28#[derive(Debug)]
33pub struct DualLabeledCounterMetric {
34 keys: Option<Vec<Cow<'static, str>>>,
35 categories: Option<Vec<Cow<'static, str>>>,
36 counter: CounterMetric,
39
40 dual_label_map: Mutex<HashMap<(String, String), Arc<CounterMetric>>>,
43}
44
45impl ::malloc_size_of::MallocSizeOf for DualLabeledCounterMetric {
46 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
47 let mut n = 0;
48 n += self.keys.size_of(ops);
49 n += self.categories.size_of(ops);
50 n += self.counter.size_of(ops);
51
52 let map = self.dual_label_map.lock().unwrap();
55
56 let shallow_size = if ops.has_malloc_enclosing_size_of() {
60 map.values()
61 .next()
62 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
63 } else {
64 map.capacity()
65 * (mem::size_of::<String>() + mem::size_of::<Arc<CounterMetric>>() + mem::size_of::<CounterMetric>() + mem::size_of::<usize>())
70 };
71
72 let mut map_size = shallow_size;
73 for (k, v) in map.iter() {
74 map_size += k.size_of(ops);
75 map_size += v.size_of(ops);
76 }
77 n += map_size;
78
79 n
80 }
81}
82
83impl MetricType for DualLabeledCounterMetric {
84 fn meta(&self) -> &CommonMetricDataInternal {
85 self.counter.meta()
86 }
87}
88
89impl DualLabeledCounterMetric {
90 pub fn new(
92 meta: CommonMetricData,
93 keys: Option<Vec<Cow<'static, str>>>,
94 catgories: Option<Vec<Cow<'static, str>>>,
95 ) -> DualLabeledCounterMetric {
96 let submetric = CounterMetric::new(meta);
97 DualLabeledCounterMetric::new_inner(submetric, keys, catgories)
98 }
99
100 fn new_inner(
101 counter: CounterMetric,
102 keys: Option<Vec<Cow<'static, str>>>,
103 categories: Option<Vec<Cow<'static, str>>>,
104 ) -> DualLabeledCounterMetric {
105 let dual_label_map = Default::default();
106 DualLabeledCounterMetric {
107 keys,
108 categories,
109 counter,
110 dual_label_map,
111 }
112 }
113
114 fn new_counter_metric(&self, key: &str, category: &str) -> CounterMetric {
117 match (&self.keys, &self.categories) {
118 (None, None) => self
119 .counter
120 .with_label(MetricLabel::KeyAndCategory(key.into(), category.into())),
121 (None, _) => {
122 let static_category = self.static_category(category);
123 self.counter
124 .with_label(MetricLabel::KeyOnly(key.into(), static_category.into()))
125 }
126 (_, None) => {
127 let static_key = self.static_key(key);
128 self.counter.with_label(MetricLabel::CategoryOnly(
129 static_key.into(),
130 category.into(),
131 ))
132 }
133 (_, _) => {
134 let static_key = self.static_key(key);
136 let static_category = self.static_category(category);
137 let label = format!("{static_key}{RECORD_SEPARATOR}{static_category}");
138 self.counter.with_label(MetricLabel::Static(label))
139 }
140 }
141 }
142
143 fn static_key<'a>(&self, key: &'a str) -> &'a str {
158 debug_assert!(self.keys.is_some());
159 let keys = self.keys.as_ref().unwrap();
160 if keys.iter().any(|l| l == key) {
161 key
162 } else {
163 OTHER_LABEL
164 }
165 }
166
167 fn static_category<'a>(&self, category: &'a str) -> &'a str {
182 debug_assert!(self.categories.is_some());
183 let categories = self.categories.as_ref().unwrap();
184 if categories.iter().any(|l| l == category) {
185 category
186 } else {
187 OTHER_LABEL
188 }
189 }
190
191 pub fn get<S: AsRef<str>>(&self, key: S, category: S) -> Arc<CounterMetric> {
203 let key = key.as_ref();
204 let category = category.as_ref();
205
206 let mut map = self.dual_label_map.lock().unwrap();
207 map.entry((key.to_string(), category.to_string()))
208 .or_insert_with(|| {
209 let metric = self.new_counter_metric(key, category);
210 Arc::new(metric)
211 })
212 .clone()
213 }
214
215 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
227 crate::block_on_dispatcher();
228 crate::core::with_glean(|glean| {
229 test_get_num_recorded_errors(glean, self.counter.meta(), error).unwrap_or(0)
230 })
231 }
232}
233
234impl TestGetValue for DualLabeledCounterMetric {
235 type Output = HashMap<String, HashMap<String, i32>>;
236
237 fn test_get_value(
238 &self,
239 ping_name: Option<String>,
240 ) -> Option<HashMap<String, HashMap<String, i32>>> {
241 let mut out: HashMap<String, HashMap<String, i32>> = HashMap::new();
242 let map = self.dual_label_map.lock().unwrap();
243 for ((key, category), metric) in map.iter() {
244 if let Some(value) = metric.test_get_value(ping_name.clone()) {
245 out.entry(key.clone())
246 .or_default()
247 .insert(category.clone(), value);
248 }
249 }
250 Some(out)
251 }
252}
253
254#[cfg(feature = "sqlite")]
255pub fn validate_dual_label_sqlite(
256 tx: &rusqlite::Connection,
257 base_identifier: &str,
258 key: &str,
259 category: &str,
260) -> LabelCheck {
261 let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
262
263 if key.contains(RECORD_SEPARATOR) || category.contains(RECORD_SEPARATOR) {
267 log::warn!(
268 "Metric {base_identifier:?}: Label cannot contain the ASCII record separator character (0x1E)"
269 );
270 return LabelCheck::Error(format!("{OTHER_LABEL}{RECORD_SEPARATOR}{OTHER_LABEL}"), 1);
271 }
272
273 let mut existing_keys = HashSet::new();
274 let mut existing_categories = HashSet::new();
275 'checkdb: {
276 let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
277 break 'checkdb;
279 };
280
281 let Ok(mut rows) = stmt.query(params![base_identifier]) else {
282 break 'checkdb;
284 };
285
286 while let Ok(Some(row)) = rows.next() {
287 let existing_labels: String = row.get(0).unwrap();
288 let Some((existing_key, existing_category)) =
289 existing_labels.split_once(RECORD_SEPARATOR)
290 else {
291 log::debug!(
293 "Metric {base_identifier:?}: Database contains invalid dual-label: {existing_labels:?}"
294 );
295 continue;
296 };
297
298 existing_keys.insert(existing_key.to_string());
299 existing_categories.insert(existing_category.to_string());
300 }
301 }
302
303 let mut errors = 0;
304 let new_key = if (existing_keys.contains(key) || existing_keys.len() < MAX_LABELS)
305 && label_is_valid(key, base_identifier)
306 {
307 key
308 } else {
309 errors += 1;
310 OTHER_LABEL
311 };
312
313 let new_category = if (existing_categories.contains(category)
314 || existing_categories.len() < MAX_LABELS)
315 && label_is_valid(category, base_identifier)
316 {
317 category
318 } else {
319 errors += 1;
320 OTHER_LABEL
321 };
322
323 let label = format!("{new_key}{RECORD_SEPARATOR}{new_category}");
324 if errors == 0 {
325 LabelCheck::Label(label)
326 } else {
327 LabelCheck::Error(label, errors)
328 }
329}
330
331#[cfg(feature = "sqlite")]
332fn label_is_valid(label: &str, metric_id: &str) -> bool {
333 if label.len() > MAX_LABEL_LENGTH {
334 log::warn!(
335 "Metric {:?}: label length {} exceeds maximum of {}",
336 metric_id,
337 label.len(),
338 MAX_LABEL_LENGTH
339 );
340 false
341 } else if label.contains(RECORD_SEPARATOR) {
342 log::warn!(
343 "Metric {metric_id:?}: Label cannot contain the ASCII record separator character (0x1E)"
344 );
345 false
346 } else {
347 true
348 }
349}
350
351#[cfg(not(feature = "sqlite"))]
352fn label_is_valid_record(label: &str, glean: &Glean, meta: &CommonMetricDataInternal) -> bool {
353 if label.len() > MAX_LABEL_LENGTH {
354 let msg = format!(
355 "label length {} exceeds maximum of {}",
356 label.len(),
357 MAX_LABEL_LENGTH
358 );
359 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
360 false
361 } else {
362 true
363 }
364}
365
366#[cfg(not(feature = "sqlite"))]
376pub fn validate_dual_label_rkv(
377 glean: &Glean,
378 meta: &CommonMetricDataInternal,
379 base_identifier: &str,
380 label: &MetricLabel,
381 record: bool,
382) -> String {
383 let (key, category) = match label {
384 MetricLabel::Static(_) | MetricLabel::Label(_) => {
385 if record {
386 record_error(
387 glean,
388 meta,
389 ErrorType::InvalidLabel,
390 "Invalid `DualLabeledCounter` label format, unable to determine key and/or category",
391 None,
392 );
393 }
394 return combine_labels(OTHER_LABEL, OTHER_LABEL);
395 }
396 MetricLabel::KeyOnly(key, category)
397 | MetricLabel::CategoryOnly(key, category)
398 | MetricLabel::KeyAndCategory(key, category) => (key, category),
399 };
400
401 if key.contains(RECORD_SEPARATOR) || category.contains(RECORD_SEPARATOR) {
402 let msg = "Label cannot contain the ASCII record separator character (0x1E)".to_string();
403 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
404 return combine_labels(OTHER_LABEL, OTHER_LABEL);
405 }
406
407 for store in &meta.inner.send_in_pings {
410 let id = combine_base_identifier_and_labels(base_identifier, key, category);
411 if glean.storage().has_metric(meta.inner.lifetime, store, &id) {
412 return combine_labels(key, category);
413 }
414 }
415
416 let mut key = &key[..];
417 let mut category = &category[..];
418
419 let (seen_keys, seen_categories) = get_seen_keys_and_categories(meta, glean);
422 match label {
423 MetricLabel::KeyOnly(..) => {
424 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
425 || !label_is_valid_record(key, glean, meta)
426 {
427 key = OTHER_LABEL;
428 }
429 }
430 MetricLabel::CategoryOnly(..) => {
431 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
432 || !label_is_valid_record(category, glean, meta)
433 {
434 category = OTHER_LABEL;
435 }
436 }
437 MetricLabel::KeyAndCategory(..) => {
438 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
439 || !label_is_valid_record(key, glean, meta)
440 {
441 key = OTHER_LABEL;
442 }
443 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
444 || !label_is_valid_record(category, glean, meta)
445 {
446 category = OTHER_LABEL;
447 }
448 }
449 _ => {}
451 }
452
453 combine_labels(key, category)
454}
455
456#[cfg(not(feature = "sqlite"))]
457fn get_seen_keys_and_categories(
458 meta: &CommonMetricDataInternal,
459 glean: &Glean,
460) -> (HashSet<String>, HashSet<String>) {
461 let base_identifier = &meta.base_identifier();
462 let mut seen_keys: HashSet<String> = HashSet::new();
463 let mut seen_categories: HashSet<String> = HashSet::new();
464 let mut snapshotter = |_metric_id: &[u8], labels: &[&str], _: &Metric| {
465 if labels.len() == 2 {
466 seen_keys.insert(labels[0].to_string());
467 seen_categories.insert(labels[1].to_string());
468 } else {
469 record_error(
470 glean,
471 meta,
472 ErrorType::InvalidLabel,
473 "Dual Labeled Counter label doesn't contain exactly 2 parts".to_string(),
474 None,
475 );
476 }
477 };
478
479 let lifetime = meta.inner.lifetime;
480 for store in &meta.inner.send_in_pings {
481 glean
482 .storage()
483 .iter_store_from(lifetime, store, Some(base_identifier), &mut snapshotter)
484 .ok();
485 }
486
487 (seen_keys, seen_categories)
488}
489
490#[cfg(not(feature = "sqlite"))]
491fn combine_labels(key: &str, category: &str) -> String {
492 format!("{}{}{}", key, RECORD_SEPARATOR, category)
493}
494
495#[cfg(not(feature = "sqlite"))]
496pub fn combine_base_identifier_and_labels(
497 base_identifer: &str,
498 key: &str,
499 category: &str,
500) -> String {
501 format!(
502 "{}{}{}{}{}",
503 base_identifer, RECORD_SEPARATOR, key, RECORD_SEPARATOR, category
504 )
505}