1use std::borrow::Cow;
6use std::char;
7use std::collections::{HashMap, HashSet};
8use std::sync::{Arc, Mutex};
9
10use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
11use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
12use crate::metrics::{CounterMetric, Metric, MetricType};
13use crate::Glean;
14
15const MAX_LABELS: usize = 16;
16const OTHER_LABEL: &str = "__other__";
17const MAX_LABEL_LENGTH: usize = 111;
18pub(crate) const RECORD_SEPARATOR: char = '\x1E';
19
20#[derive(Debug)]
25pub struct DualLabeledCounterMetric {
26 keys: Option<Vec<Cow<'static, str>>>,
27 categories: Option<Vec<Cow<'static, str>>>,
28 counter: CounterMetric,
31
32 dual_label_map: Mutex<HashMap<(String, String), Arc<CounterMetric>>>,
35}
36
37impl ::malloc_size_of::MallocSizeOf for DualLabeledCounterMetric {
38 fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
39 0
63 }
64}
65
66impl DualLabeledCounterMetric {
67 pub fn new(
69 meta: CommonMetricData,
70 keys: Option<Vec<Cow<'static, str>>>,
71 catgories: Option<Vec<Cow<'static, str>>>,
72 ) -> DualLabeledCounterMetric {
73 let submetric = CounterMetric::new(meta);
74 DualLabeledCounterMetric::new_inner(submetric, keys, catgories)
75 }
76
77 fn new_inner(
78 counter: CounterMetric,
79 keys: Option<Vec<Cow<'static, str>>>,
80 categories: Option<Vec<Cow<'static, str>>>,
81 ) -> DualLabeledCounterMetric {
82 let dual_label_map = Default::default();
83 DualLabeledCounterMetric {
84 keys,
85 categories,
86 counter,
87 dual_label_map,
88 }
89 }
90
91 fn new_counter_metric(&self, key: &str, category: &str) -> CounterMetric {
94 match (&self.keys, &self.categories) {
95 (None, None) => self
96 .counter
97 .with_dynamic_label(DynamicLabelType::KeyAndCategory(
98 make_label_from_key_and_category(key, category),
99 )),
100 (None, _) => {
101 let static_category = self.static_category(category);
102 self.counter.with_dynamic_label(DynamicLabelType::KeyOnly(
103 make_label_from_key_and_category(key, static_category),
104 ))
105 }
106 (_, None) => {
107 let static_key = self.static_key(key);
108 self.counter
109 .with_dynamic_label(DynamicLabelType::CategoryOnly(
110 make_label_from_key_and_category(static_key, category),
111 ))
112 }
113 (_, _) => {
114 let static_key = self.static_key(key);
116 let static_category = self.static_category(category);
117 let name = combine_base_identifier_and_labels(
118 self.counter.meta().inner.name.as_str(),
119 static_key,
120 static_category,
121 );
122 self.counter.with_name(name)
123 }
124 }
125 }
126
127 fn static_key<'a>(&self, key: &'a str) -> &'a str {
142 debug_assert!(self.keys.is_some());
143 let keys = self.keys.as_ref().unwrap();
144 if keys.iter().any(|l| l == key) {
145 key
146 } else {
147 OTHER_LABEL
148 }
149 }
150
151 fn static_category<'a>(&self, category: &'a str) -> &'a str {
166 debug_assert!(self.categories.is_some());
167 let categories = self.categories.as_ref().unwrap();
168 if categories.iter().any(|l| l == category) {
169 category
170 } else {
171 OTHER_LABEL
172 }
173 }
174
175 pub fn get<S: AsRef<str>>(&self, key: S, category: S) -> Arc<CounterMetric> {
187 let key = key.as_ref();
188 let category = category.as_ref();
189
190 let mut map = self.dual_label_map.lock().unwrap();
191 map.entry((key.to_string(), category.to_string()))
192 .or_insert_with(|| {
193 let metric = self.new_counter_metric(key, category);
194 Arc::new(metric)
195 })
196 .clone()
197 }
198
199 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
211 crate::block_on_dispatcher();
212 crate::core::with_glean(|glean| {
213 test_get_num_recorded_errors(glean, self.counter.meta(), error).unwrap_or(0)
214 })
215 }
216}
217
218pub fn combine_base_identifier_and_labels(
220 base_identifer: &str,
221 key: &str,
222 category: &str,
223) -> String {
224 format!(
225 "{}{}",
226 base_identifer,
227 make_label_from_key_and_category(key, category)
228 )
229}
230
231pub fn separate_label_into_key_and_category(label: &str) -> Option<(&str, &str)> {
235 label
236 .strip_prefix(RECORD_SEPARATOR)
237 .unwrap_or(label)
238 .split_once(RECORD_SEPARATOR)
239}
240
241pub fn make_label_from_key_and_category(key: &str, category: &str) -> String {
244 format!(
245 "{}{}{}{}",
246 RECORD_SEPARATOR, key, RECORD_SEPARATOR, category
247 )
248}
249
250pub fn validate_dynamic_key_and_or_category(
264 glean: &Glean,
265 meta: &CommonMetricDataInternal,
266 base_identifier: &str,
267 label: DynamicLabelType,
268) -> String {
269 if label.split(RECORD_SEPARATOR).count() != 3 {
275 let msg = "Label cannot contain the ASCII record separator character (0x1E)".to_string();
276 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
277 return combine_base_identifier_and_labels(base_identifier, OTHER_LABEL, OTHER_LABEL);
278 }
279
280 if let Some((mut key, mut category)) = separate_label_into_key_and_category(&label) {
282 for store in &meta.inner.send_in_pings {
285 if glean.storage().has_metric(meta.inner.lifetime, store, key) {
286 return combine_base_identifier_and_labels(base_identifier, key, category);
287 }
288 }
289
290 let (seen_keys, seen_categories) = get_seen_keys_and_categories(meta, glean);
293 match label {
294 DynamicLabelType::Label(ref label) => {
295 record_error(
296 glean,
297 meta,
298 ErrorType::InvalidLabel,
299 format!("Invalid `DualLabeledCounter` label format: {label:?}"),
300 None,
301 );
302 key = OTHER_LABEL;
303 category = OTHER_LABEL;
304 }
305 DynamicLabelType::KeyOnly(_) => {
306 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
307 || !label_is_valid(key, glean, meta)
308 {
309 key = OTHER_LABEL;
310 }
311 }
312 DynamicLabelType::CategoryOnly(_) => {
313 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
314 || !label_is_valid(category, glean, meta)
315 {
316 category = OTHER_LABEL;
317 }
318 }
319 DynamicLabelType::KeyAndCategory(_) => {
320 if (!seen_keys.contains(key) && seen_keys.len() >= MAX_LABELS)
321 || !label_is_valid(key, glean, meta)
322 {
323 key = OTHER_LABEL;
324 }
325 if (!seen_categories.contains(category) && seen_categories.len() >= MAX_LABELS)
326 || !label_is_valid(category, glean, meta)
327 {
328 category = OTHER_LABEL;
329 }
330 }
331 }
332 combine_base_identifier_and_labels(base_identifier, key, category)
333 } else {
334 record_error(
335 glean,
336 meta,
337 ErrorType::InvalidLabel,
338 "Invalid `DualLabeledCounter` label format, unable to determine key and/or category",
339 None,
340 );
341 combine_base_identifier_and_labels(base_identifier, OTHER_LABEL, OTHER_LABEL)
342 }
343}
344
345fn label_is_valid(label: &str, glean: &Glean, meta: &CommonMetricDataInternal) -> bool {
346 if label.len() > MAX_LABEL_LENGTH {
347 let msg = format!(
348 "label length {} exceeds maximum of {}",
349 label.len(),
350 MAX_LABEL_LENGTH
351 );
352 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
353 false
354 } else {
355 true
356 }
357}
358
359fn get_seen_keys_and_categories(
360 meta: &CommonMetricDataInternal,
361 glean: &Glean,
362) -> (HashSet<String>, HashSet<String>) {
363 let base_identifier = &meta.base_identifier();
364 let prefix = format!("{base_identifier}{RECORD_SEPARATOR}");
365 let mut seen_keys: HashSet<String> = HashSet::new();
366 let mut seen_categories: HashSet<String> = HashSet::new();
367 let mut snapshotter = |metric_id: &[u8], _: &Metric| {
368 let metric_id_str = String::from_utf8_lossy(metric_id);
369
370 let parts: Vec<&str> = metric_id_str.split(RECORD_SEPARATOR).collect();
372
373 if parts.len() == 2 {
374 seen_keys.insert(parts[0].into());
375 seen_categories.insert(parts[1].into());
376 } else {
377 record_error(
378 glean,
379 meta,
380 ErrorType::InvalidLabel,
381 "Dual Labeled Counter label doesn't contain exactly 2 parts".to_string(),
382 None,
383 );
384 }
385 };
386
387 let lifetime = meta.inner.lifetime;
388 for store in &meta.inner.send_in_pings {
389 glean
390 .storage()
391 .iter_store_from(lifetime, store, Some(&prefix), &mut snapshotter);
392 }
393
394 (seen_keys, seen_categories)
395}