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 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
254pub fn combine_base_identifier_and_labels(
256 base_identifer: &str,
257 key: &str,
258 category: &str,
259) -> String {
260 format!(
261 "{}{}",
262 base_identifer,
263 make_label_from_key_and_category(key, category)
264 )
265}
266
267pub fn separate_label_into_key_and_category(label: &str) -> Option<(&str, &str)> {
271 label
272 .strip_prefix(RECORD_SEPARATOR)
273 .unwrap_or(label)
274 .split_once(RECORD_SEPARATOR)
275}
276
277pub fn make_label_from_key_and_category(key: &str, category: &str) -> String {
280 format!(
281 "{}{}{}{}",
282 RECORD_SEPARATOR, key, RECORD_SEPARATOR, category
283 )
284}
285
286pub fn validate_dynamic_key_and_or_category(
300 glean: &Glean,
301 meta: &CommonMetricDataInternal,
302 base_identifier: &str,
303 label: DynamicLabelType,
304) -> String {
305 if label.split(RECORD_SEPARATOR).count() != 3 {
311 let msg = "Label cannot contain the ASCII record separator character (0x1E)".to_string();
312 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
313 return combine_base_identifier_and_labels(base_identifier, OTHER_LABEL, OTHER_LABEL);
314 }
315
316 if let Some((mut key, mut category)) = separate_label_into_key_and_category(&label) {
318 for store in &meta.inner.send_in_pings {
321 if glean.storage().has_metric(meta.inner.lifetime, store, key) {
322 return combine_base_identifier_and_labels(base_identifier, key, category);
323 }
324 }
325
326 let (seen_keys, seen_categories) = get_seen_keys_and_categories(meta, glean);
329 match label {
330 DynamicLabelType::Label(ref label) => {
331 record_error(
332 glean,
333 meta,
334 ErrorType::InvalidLabel,
335 format!("Invalid `DualLabeledCounter` label format: {label:?}"),
336 None,
337 );
338 key = OTHER_LABEL;
339 category = OTHER_LABEL;
340 }
341 DynamicLabelType::KeyOnly(_) => {
342 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
343 || !label_is_valid(key, glean, meta)
344 {
345 key = OTHER_LABEL;
346 }
347 }
348 DynamicLabelType::CategoryOnly(_) => {
349 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
350 || !label_is_valid(category, glean, meta)
351 {
352 category = OTHER_LABEL;
353 }
354 }
355 DynamicLabelType::KeyAndCategory(_) => {
356 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
357 || !label_is_valid(key, glean, meta)
358 {
359 key = OTHER_LABEL;
360 }
361 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
362 || !label_is_valid(category, glean, meta)
363 {
364 category = OTHER_LABEL;
365 }
366 }
367 }
368 combine_base_identifier_and_labels(base_identifier, key, category)
369 } else {
370 record_error(
371 glean,
372 meta,
373 ErrorType::InvalidLabel,
374 "Invalid `DualLabeledCounter` label format, unable to determine key and/or category",
375 None,
376 );
377 combine_base_identifier_and_labels(base_identifier, OTHER_LABEL, OTHER_LABEL)
378 }
379}
380
381fn label_is_valid(label: &str, glean: &Glean, meta: &CommonMetricDataInternal) -> bool {
382 if label.len() > MAX_LABEL_LENGTH {
383 let msg = format!(
384 "label length {} exceeds maximum of {}",
385 label.len(),
386 MAX_LABEL_LENGTH
387 );
388 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
389 false
390 } else {
391 true
392 }
393}
394
395fn get_seen_keys_and_categories(
396 meta: &CommonMetricDataInternal,
397 glean: &Glean,
398) -> (HashSet<String>, HashSet<String>) {
399 let base_identifier = &meta.base_identifier();
400 let prefix = format!("{base_identifier}{RECORD_SEPARATOR}");
401 let mut seen_keys: HashSet<String> = HashSet::new();
402 let mut seen_categories: HashSet<String> = HashSet::new();
403 let mut snapshotter = |metric_id: &[u8], _: &Metric| {
404 let metric_id_str = String::from_utf8_lossy(metric_id);
405
406 let parts: Vec<&str> = metric_id_str.split(RECORD_SEPARATOR).collect();
408
409 if parts.len() == 2 {
410 seen_keys.insert(parts[0].into());
411 seen_categories.insert(parts[1].into());
412 } else {
413 record_error(
414 glean,
415 meta,
416 ErrorType::InvalidLabel,
417 "Dual Labeled Counter label doesn't contain exactly 2 parts".to_string(),
418 None,
419 );
420 }
421 };
422
423 let lifetime = meta.inner.lifetime;
424 for store in &meta.inner.send_in_pings {
425 glean
426 .storage()
427 .iter_store_from(lifetime, store, Some(&prefix), &mut snapshotter);
428 }
429
430 (seen_keys, seen_categories)
431}