glean_core/metrics/
dual_labeled_counter.rs1use std::borrow::Cow;
6use std::char;
7use std::collections::{HashMap, HashSet};
8use std::mem;
9use std::sync::{Arc, Mutex};
10
11use rusqlite::{params, Transaction};
12
13use crate::common_metric_data::{
14 CommonMetricData, CommonMetricDataInternal, LabelCheck, MetricLabel,
15};
16use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
17use crate::metrics::{CounterMetric, MetricType};
18use crate::TestGetValue;
19
20const MAX_LABELS: usize = 16;
21const OTHER_LABEL: &str = "__other__";
22const MAX_LABEL_LENGTH: usize = 111;
23pub(crate) const RECORD_SEPARATOR: char = '\x1E';
24
25#[derive(Debug)]
30pub struct DualLabeledCounterMetric {
31 keys: Option<Vec<Cow<'static, str>>>,
32 categories: Option<Vec<Cow<'static, str>>>,
33 counter: CounterMetric,
36
37 dual_label_map: Mutex<HashMap<(String, String), Arc<CounterMetric>>>,
40}
41
42impl ::malloc_size_of::MallocSizeOf for DualLabeledCounterMetric {
43 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
44 let mut n = 0;
45 n += self.keys.size_of(ops);
46 n += self.categories.size_of(ops);
47 n += self.counter.size_of(ops);
48
49 let map = self.dual_label_map.lock().unwrap();
52
53 let shallow_size = if ops.has_malloc_enclosing_size_of() {
57 map.values()
58 .next()
59 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
60 } else {
61 map.capacity()
62 * (mem::size_of::<String>() + mem::size_of::<Arc<CounterMetric>>() + mem::size_of::<CounterMetric>() + mem::size_of::<usize>())
67 };
68
69 let mut map_size = shallow_size;
70 for (k, v) in map.iter() {
71 map_size += k.size_of(ops);
72 map_size += v.size_of(ops);
73 }
74 n += map_size;
75
76 n
77 }
78}
79
80impl MetricType for DualLabeledCounterMetric {
81 fn meta(&self) -> &CommonMetricDataInternal {
82 self.counter.meta()
83 }
84}
85
86impl DualLabeledCounterMetric {
87 pub fn new(
89 meta: CommonMetricData,
90 keys: Option<Vec<Cow<'static, str>>>,
91 catgories: Option<Vec<Cow<'static, str>>>,
92 ) -> DualLabeledCounterMetric {
93 let submetric = CounterMetric::new(meta);
94 DualLabeledCounterMetric::new_inner(submetric, keys, catgories)
95 }
96
97 fn new_inner(
98 counter: CounterMetric,
99 keys: Option<Vec<Cow<'static, str>>>,
100 categories: Option<Vec<Cow<'static, str>>>,
101 ) -> DualLabeledCounterMetric {
102 let dual_label_map = Default::default();
103 DualLabeledCounterMetric {
104 keys,
105 categories,
106 counter,
107 dual_label_map,
108 }
109 }
110
111 fn new_counter_metric(&self, key: &str, category: &str) -> CounterMetric {
114 match (&self.keys, &self.categories) {
115 (None, None) => self
116 .counter
117 .with_label(MetricLabel::KeyAndCategory(key.into(), category.into())),
118 (None, _) => {
119 let static_category = self.static_category(category);
120 self.counter
121 .with_label(MetricLabel::KeyOnly(key.into(), static_category.into()))
122 }
123 (_, None) => {
124 let static_key = self.static_key(key);
125 self.counter.with_label(MetricLabel::CategoryOnly(
126 static_key.into(),
127 category.into(),
128 ))
129 }
130 (_, _) => {
131 let static_key = self.static_key(key);
133 let static_category = self.static_category(category);
134 let label = format!("{static_key}{RECORD_SEPARATOR}{static_category}");
135 self.counter.with_label(MetricLabel::Static(label))
136 }
137 }
138 }
139
140 fn static_key<'a>(&self, key: &'a str) -> &'a str {
155 debug_assert!(self.keys.is_some());
156 let keys = self.keys.as_ref().unwrap();
157 if keys.iter().any(|l| l == key) {
158 key
159 } else {
160 OTHER_LABEL
161 }
162 }
163
164 fn static_category<'a>(&self, category: &'a str) -> &'a str {
179 debug_assert!(self.categories.is_some());
180 let categories = self.categories.as_ref().unwrap();
181 if categories.iter().any(|l| l == category) {
182 category
183 } else {
184 OTHER_LABEL
185 }
186 }
187
188 pub fn get<S: AsRef<str>>(&self, key: S, category: S) -> Arc<CounterMetric> {
200 let key = key.as_ref();
201 let category = category.as_ref();
202
203 let mut map = self.dual_label_map.lock().unwrap();
204 map.entry((key.to_string(), category.to_string()))
205 .or_insert_with(|| {
206 let metric = self.new_counter_metric(key, category);
207 Arc::new(metric)
208 })
209 .clone()
210 }
211
212 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
224 crate::block_on_dispatcher();
225 crate::core::with_glean(|glean| {
226 test_get_num_recorded_errors(glean, self.counter.meta(), error).unwrap_or(0)
227 })
228 }
229}
230
231impl TestGetValue for DualLabeledCounterMetric {
232 type Output = HashMap<String, HashMap<String, i32>>;
233
234 fn test_get_value(
235 &self,
236 ping_name: Option<String>,
237 ) -> Option<HashMap<String, HashMap<String, i32>>> {
238 let mut out: HashMap<String, HashMap<String, i32>> = HashMap::new();
239 let map = self.dual_label_map.lock().unwrap();
240 for ((key, category), metric) in map.iter() {
241 if let Some(value) = metric.test_get_value(ping_name.clone()) {
242 out.entry(key.clone())
243 .or_default()
244 .insert(category.clone(), value);
245 }
246 }
247 Some(out)
248 }
249}
250
251pub fn validate_dual_label_sqlite(
252 tx: &Transaction,
253 base_identifier: &str,
254 key: &str,
255 category: &str,
256) -> LabelCheck {
257 let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
258
259 if key.contains(RECORD_SEPARATOR) || category.contains(RECORD_SEPARATOR) {
263 log::warn!("Metric {base_identifier:?}: Label cannot contain the ASCII record separator character (0x1E)");
264 return LabelCheck::Error(format!("{OTHER_LABEL}{RECORD_SEPARATOR}{OTHER_LABEL}"), 1);
265 }
266
267 let mut existing_keys = HashSet::new();
268 let mut existing_categories = HashSet::new();
269 'checkdb: {
270 let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
271 break 'checkdb;
273 };
274
275 let Ok(mut rows) = stmt.query(params![base_identifier]) else {
276 break 'checkdb;
278 };
279
280 while let Ok(Some(row)) = rows.next() {
281 let existing_labels: String = row.get(0).unwrap();
282 let Some((existing_key, existing_category)) =
283 existing_labels.split_once(RECORD_SEPARATOR)
284 else {
285 log::debug!("Metric {base_identifier:?}: Database contains invalid dual-label: {existing_labels:?}");
287 continue;
288 };
289
290 existing_keys.insert(existing_key.to_string());
291 existing_categories.insert(existing_category.to_string());
292 }
293 }
294
295 let mut errors = 0;
296 let new_key = if (existing_keys.contains(key) || existing_keys.len() < MAX_LABELS)
297 && label_is_valid(key, base_identifier)
298 {
299 key
300 } else {
301 errors += 1;
302 OTHER_LABEL
303 };
304
305 let new_category = if (existing_categories.contains(category)
306 || existing_categories.len() < MAX_LABELS)
307 && label_is_valid(category, base_identifier)
308 {
309 category
310 } else {
311 errors += 1;
312 OTHER_LABEL
313 };
314
315 let label = format!("{new_key}{RECORD_SEPARATOR}{new_category}");
316 if errors == 0 {
317 LabelCheck::Label(label)
318 } else {
319 LabelCheck::Error(label, errors)
320 }
321}
322
323fn label_is_valid(label: &str, metric_id: &str) -> bool {
324 if label.len() > MAX_LABEL_LENGTH {
325 log::warn!(
326 "Metric {:?}: label length {} exceeds maximum of {}",
327 metric_id,
328 label.len(),
329 MAX_LABEL_LENGTH
330 );
331 false
332 } else if label.contains(RECORD_SEPARATOR) {
333 log::warn!("Metric {metric_id:?}: Label cannot contain the ASCII record separator character (0x1E)");
334 false
335 } else {
336 true
337 }
338}