1use std::borrow::Cow;
6use std::char;
7use std::collections::{HashMap, HashSet};
8use std::mem;
9use std::sync::{Arc, Mutex};
10
11use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
12use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
13use crate::metrics::{CounterMetric, Metric, MetricType};
14use crate::{Glean, TestGetValue};
15
16const MAX_LABELS: usize = 16;
17const OTHER_LABEL: &str = "__other__";
18const MAX_LABEL_LENGTH: usize = 111;
19pub(crate) const RECORD_SEPARATOR: char = '\x1E';
20
21#[derive(Debug)]
26pub struct DualLabeledCounterMetric {
27 keys: Option<Vec<Cow<'static, str>>>,
28 categories: Option<Vec<Cow<'static, str>>>,
29 counter: CounterMetric,
32
33 dual_label_map: Mutex<HashMap<(String, String), Arc<CounterMetric>>>,
36}
37
38impl ::malloc_size_of::MallocSizeOf for DualLabeledCounterMetric {
39 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
40 let mut n = 0;
41 n += self.keys.size_of(ops);
42 n += self.categories.size_of(ops);
43 n += self.counter.size_of(ops);
44
45 let map = self.dual_label_map.lock().unwrap();
48
49 let shallow_size = if ops.has_malloc_enclosing_size_of() {
53 map.values()
54 .next()
55 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
56 } else {
57 map.capacity()
58 * (mem::size_of::<String>() + mem::size_of::<Arc<CounterMetric>>() + mem::size_of::<CounterMetric>() + mem::size_of::<usize>())
63 };
64
65 let mut map_size = shallow_size;
66 for (k, v) in map.iter() {
67 map_size += k.size_of(ops);
68 map_size += v.size_of(ops);
69 }
70 n += map_size;
71
72 n
73 }
74}
75
76impl MetricType for DualLabeledCounterMetric {
77 fn meta(&self) -> &CommonMetricDataInternal {
78 self.counter.meta()
79 }
80}
81
82impl DualLabeledCounterMetric {
83 pub fn new(
85 meta: CommonMetricData,
86 keys: Option<Vec<Cow<'static, str>>>,
87 catgories: Option<Vec<Cow<'static, str>>>,
88 ) -> DualLabeledCounterMetric {
89 let submetric = CounterMetric::new(meta);
90 DualLabeledCounterMetric::new_inner(submetric, keys, catgories)
91 }
92
93 fn new_inner(
94 counter: CounterMetric,
95 keys: Option<Vec<Cow<'static, str>>>,
96 categories: Option<Vec<Cow<'static, str>>>,
97 ) -> DualLabeledCounterMetric {
98 let dual_label_map = Default::default();
99 DualLabeledCounterMetric {
100 keys,
101 categories,
102 counter,
103 dual_label_map,
104 }
105 }
106
107 fn new_counter_metric(&self, key: &str, category: &str) -> CounterMetric {
110 match (&self.keys, &self.categories) {
111 (None, None) => self
112 .counter
113 .with_dynamic_label(DynamicLabelType::KeyAndCategory(
114 make_label_from_key_and_category(key, category),
115 )),
116 (None, _) => {
117 let static_category = self.static_category(category);
118 self.counter.with_dynamic_label(DynamicLabelType::KeyOnly(
119 make_label_from_key_and_category(key, static_category),
120 ))
121 }
122 (_, None) => {
123 let static_key = self.static_key(key);
124 self.counter
125 .with_dynamic_label(DynamicLabelType::CategoryOnly(
126 make_label_from_key_and_category(static_key, category),
127 ))
128 }
129 (_, _) => {
130 let static_key = self.static_key(key);
132 let static_category = self.static_category(category);
133 let name = combine_base_identifier_and_labels(
134 self.counter.meta().inner.name.as_str(),
135 static_key,
136 static_category,
137 );
138 self.counter.with_name(name)
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<HashMap<String, HashMap<String, i32>>> for DualLabeledCounterMetric {
235 fn test_get_value(
236 &self,
237 ping_name: Option<String>,
238 ) -> Option<HashMap<String, HashMap<String, i32>>> {
239 let mut out: HashMap<String, HashMap<String, i32>> = HashMap::new();
240 let map = self.dual_label_map.lock().unwrap();
241 for ((key, category), metric) in map.iter() {
242 if let Some(value) = metric.test_get_value(ping_name.clone()) {
243 out.entry(key.clone())
244 .or_default()
245 .insert(category.clone(), value);
246 }
247 }
248 Some(out)
249 }
250}
251
252pub fn combine_base_identifier_and_labels(
254 base_identifer: &str,
255 key: &str,
256 category: &str,
257) -> String {
258 format!(
259 "{}{}",
260 base_identifer,
261 make_label_from_key_and_category(key, category)
262 )
263}
264
265pub fn separate_label_into_key_and_category(label: &str) -> Option<(&str, &str)> {
269 label
270 .strip_prefix(RECORD_SEPARATOR)
271 .unwrap_or(label)
272 .split_once(RECORD_SEPARATOR)
273}
274
275pub fn make_label_from_key_and_category(key: &str, category: &str) -> String {
278 format!(
279 "{}{}{}{}",
280 RECORD_SEPARATOR, key, RECORD_SEPARATOR, category
281 )
282}
283
284pub fn validate_dynamic_key_and_or_category(
298 glean: &Glean,
299 meta: &CommonMetricDataInternal,
300 base_identifier: &str,
301 label: DynamicLabelType,
302) -> String {
303 if label.split(RECORD_SEPARATOR).count() != 3 {
309 let msg = "Label cannot contain the ASCII record separator character (0x1E)".to_string();
310 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
311 return combine_base_identifier_and_labels(base_identifier, OTHER_LABEL, OTHER_LABEL);
312 }
313
314 if let Some((mut key, mut category)) = separate_label_into_key_and_category(&label) {
316 for store in &meta.inner.send_in_pings {
319 if glean.storage().has_metric(meta.inner.lifetime, store, key) {
320 return combine_base_identifier_and_labels(base_identifier, key, category);
321 }
322 }
323
324 let (seen_keys, seen_categories) = get_seen_keys_and_categories(meta, glean);
327 match label {
328 DynamicLabelType::Label(ref label) => {
329 record_error(
330 glean,
331 meta,
332 ErrorType::InvalidLabel,
333 format!("Invalid `DualLabeledCounter` label format: {label:?}"),
334 None,
335 );
336 key = OTHER_LABEL;
337 category = OTHER_LABEL;
338 }
339 DynamicLabelType::KeyOnly(_) => {
340 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
341 || !label_is_valid(key, glean, meta)
342 {
343 key = OTHER_LABEL;
344 }
345 }
346 DynamicLabelType::CategoryOnly(_) => {
347 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
348 || !label_is_valid(category, glean, meta)
349 {
350 category = OTHER_LABEL;
351 }
352 }
353 DynamicLabelType::KeyAndCategory(_) => {
354 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
355 || !label_is_valid(key, glean, meta)
356 {
357 key = OTHER_LABEL;
358 }
359 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
360 || !label_is_valid(category, glean, meta)
361 {
362 category = OTHER_LABEL;
363 }
364 }
365 }
366 combine_base_identifier_and_labels(base_identifier, key, category)
367 } else {
368 record_error(
369 glean,
370 meta,
371 ErrorType::InvalidLabel,
372 "Invalid `DualLabeledCounter` label format, unable to determine key and/or category",
373 None,
374 );
375 combine_base_identifier_and_labels(base_identifier, OTHER_LABEL, OTHER_LABEL)
376 }
377}
378
379fn label_is_valid(label: &str, glean: &Glean, meta: &CommonMetricDataInternal) -> bool {
380 if label.len() > MAX_LABEL_LENGTH {
381 let msg = format!(
382 "label length {} exceeds maximum of {}",
383 label.len(),
384 MAX_LABEL_LENGTH
385 );
386 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
387 false
388 } else {
389 true
390 }
391}
392
393fn get_seen_keys_and_categories(
394 meta: &CommonMetricDataInternal,
395 glean: &Glean,
396) -> (HashSet<String>, HashSet<String>) {
397 let base_identifier = &meta.base_identifier();
398 let prefix = format!("{base_identifier}{RECORD_SEPARATOR}");
399 let mut seen_keys: HashSet<String> = HashSet::new();
400 let mut seen_categories: HashSet<String> = HashSet::new();
401 let mut snapshotter = |metric_id: &[u8], _: &Metric| {
402 let metric_id_str = String::from_utf8_lossy(metric_id);
403
404 let parts: Vec<&str> = metric_id_str.split(RECORD_SEPARATOR).collect();
406
407 if parts.len() == 2 {
408 seen_keys.insert(parts[0].into());
409 seen_categories.insert(parts[1].into());
410 } else {
411 record_error(
412 glean,
413 meta,
414 ErrorType::InvalidLabel,
415 "Dual Labeled Counter label doesn't contain exactly 2 parts".to_string(),
416 None,
417 );
418 }
419 };
420
421 let lifetime = meta.inner.lifetime;
422 for store in &meta.inner.send_in_pings {
423 glean
424 .storage()
425 .iter_store_from(lifetime, store, Some(&prefix), &mut snapshotter);
426 }
427
428 (seen_keys, seen_categories)
429}