1use std::any::Any;
6use std::borrow::Cow;
7use std::collections::{hash_map::Entry, HashMap};
8use std::mem;
9use std::sync::{Arc, Mutex};
10
11use malloc_size_of::MallocSizeOf;
12use rusqlite::{params, Transaction};
13
14use crate::common_metric_data::{CommonMetricData, LabelCheck, MetricLabel};
15use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
16use crate::histogram::HistogramType;
17use crate::metrics::{
18 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
19 MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit, TimingDistributionMetric,
20};
21use crate::storage::StorageManager;
22
23const MAX_LABELS: usize = 16;
24const OTHER_LABEL: &str = "__other__";
25const MAX_LABEL_LENGTH: usize = 111;
26
27pub type LabeledCounter = LabeledMetric<CounterMetric>;
29
30pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
32
33pub type LabeledString = LabeledMetric<StringMetric>;
35
36pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
38
39pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
41
42pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
44
45pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
47
48pub enum LabeledMetricData {
53 #[allow(missing_docs)]
55 Common { cmd: CommonMetricData },
56 #[allow(missing_docs)]
58 CustomDistribution {
59 cmd: CommonMetricData,
60 range_min: i64,
61 range_max: i64,
62 bucket_count: i64,
63 histogram_type: HistogramType,
64 },
65 #[allow(missing_docs)]
67 MemoryDistribution {
68 cmd: CommonMetricData,
69 unit: MemoryUnit,
70 },
71 #[allow(missing_docs)]
73 TimingDistribution {
74 cmd: CommonMetricData,
75 unit: TimeUnit,
76 },
77}
78
79#[derive(Debug)]
83pub struct LabeledMetric<T> {
84 labels: Option<Vec<Cow<'static, str>>>,
85 submetric: T,
88
89 label_map: Mutex<HashMap<String, Arc<T>>>,
92}
93
94impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
95 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
96 let map = self.label_map.lock().unwrap();
97
98 let shallow_size = if ops.has_malloc_enclosing_size_of() {
102 map.values()
103 .next()
104 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
105 } else {
106 map.capacity()
107 * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
108 };
109
110 let mut map_size = shallow_size;
111 for (k, v) in map.iter() {
112 map_size += k.size_of(ops);
113 map_size += v.size_of(ops);
114 }
115
116 self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
117 }
118}
119
120mod private {
124 use super::LabeledMetricData;
125 use crate::metrics::{
126 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
127 QuantityMetric, StringMetric, TimingDistributionMetric,
128 };
129
130 pub trait Sealed {
136 fn new_inner(meta: LabeledMetricData) -> Self;
138 }
139
140 impl Sealed for CounterMetric {
141 fn new_inner(meta: LabeledMetricData) -> Self {
142 match meta {
143 LabeledMetricData::Common { cmd } => Self::new(cmd),
144 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
145 }
146 }
147 }
148
149 impl Sealed for BooleanMetric {
150 fn new_inner(meta: LabeledMetricData) -> Self {
151 match meta {
152 LabeledMetricData::Common { cmd } => Self::new(cmd),
153 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
154 }
155 }
156 }
157
158 impl Sealed for StringMetric {
159 fn new_inner(meta: LabeledMetricData) -> Self {
160 match meta {
161 LabeledMetricData::Common { cmd } => Self::new(cmd),
162 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
163 }
164 }
165 }
166
167 impl Sealed for CustomDistributionMetric {
168 fn new_inner(meta: LabeledMetricData) -> Self {
169 match meta {
170 LabeledMetricData::CustomDistribution {
171 cmd,
172 range_min,
173 range_max,
174 bucket_count,
175 histogram_type,
176 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
177 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
178 }
179 }
180 }
181
182 impl Sealed for MemoryDistributionMetric {
183 fn new_inner(meta: LabeledMetricData) -> Self {
184 match meta {
185 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
186 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
187 }
188 }
189 }
190
191 impl Sealed for TimingDistributionMetric {
192 fn new_inner(meta: LabeledMetricData) -> Self {
193 match meta {
194 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
195 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
196 }
197 }
198 }
199
200 impl Sealed for QuantityMetric {
201 fn new_inner(meta: LabeledMetricData) -> Self {
202 match meta {
203 LabeledMetricData::Common { cmd } => Self::new(cmd),
204 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
205 }
206 }
207 }
208}
209
210pub trait AllowLabeled: MetricType {
212 fn new_labeled(meta: LabeledMetricData) -> Self;
214}
215
216impl<T> AllowLabeled for T
218where
219 T: MetricType,
220 T: private::Sealed,
221{
222 fn new_labeled(meta: LabeledMetricData) -> Self {
223 T::new_inner(meta)
224 }
225}
226
227impl<T> LabeledMetric<T>
228where
229 T: AllowLabeled + Clone,
230{
231 pub fn new(
235 meta: LabeledMetricData,
236 labels: Option<Vec<Cow<'static, str>>>,
237 ) -> LabeledMetric<T> {
238 let submetric = T::new_labeled(meta);
239 LabeledMetric::new_inner(submetric, labels)
240 }
241
242 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
243 let label_map = Default::default();
244 LabeledMetric {
245 labels,
246 submetric,
247 label_map,
248 }
249 }
250
251 fn new_metric_with_label(&self, label: MetricLabel) -> T {
255 self.submetric.with_label(label)
256 }
257
258 fn new_metric_with_dynamic_label(&self, label: MetricLabel) -> T {
265 self.submetric.with_label(label)
266 }
267
268 fn static_label<'a>(&self, label: &'a str) -> &'a str {
283 debug_assert!(self.labels.is_some());
284 let labels = self.labels.as_ref().unwrap();
285 if labels.iter().any(|l| l == label) {
286 label
287 } else {
288 OTHER_LABEL
289 }
290 }
291
292 pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
304 let label = label.as_ref();
305
306 let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
309
310 let mut map = self.label_map.lock().unwrap();
311 match map.entry(id) {
312 Entry::Occupied(entry) => Arc::clone(entry.get()),
313 Entry::Vacant(entry) => {
314 let metric = match self.labels {
321 Some(_) => {
322 let label = self.static_label(label);
323 self.new_metric_with_label(MetricLabel::Static(label.to_string()))
324 }
325 None => {
326 self.new_metric_with_dynamic_label(MetricLabel::Label(label.to_string()))
327 }
328 };
329 let metric = Arc::new(metric);
330 entry.insert(Arc::clone(&metric));
331 metric
332 }
333 }
334 }
335
336 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
348 crate::block_on_dispatcher();
349 crate::core::with_glean(|glean| {
350 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
351 })
352 }
353}
354
355impl<T, S> TestGetValue for LabeledMetric<T>
356where
357 T: AllowLabeled + TestGetValue<Output = S> + Clone,
358 S: Any,
359{
360 type Output = HashMap<String, S>;
361
362 fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
363 crate::block_on_dispatcher();
365 let labels = crate::core::with_glean(|glean| {
366 let queried_ping_name = ping_name
367 .as_ref()
368 .unwrap_or_else(|| &self.submetric.meta().inner.send_in_pings[0]);
369 StorageManager.snapshot_labels(
370 glean.storage(),
371 queried_ping_name,
372 &self.submetric.meta().base_identifier(),
373 self.submetric.meta().inner.lifetime,
374 )
375 });
376 let mut out = HashMap::new();
377 labels.iter().for_each(|label| {
378 if let Some(v) = self.get(label).test_get_value(ping_name.clone()) {
379 out.insert(label.to_owned(), v);
380 }
381 });
382 Some(out)
383 }
384}
385
386pub fn validate_dynamic_label_sqlite(
387 tx: &Transaction,
388 base_identifier: &str,
389 label: &str,
390) -> LabelCheck {
391 let existing_labels_sql = "SELECT DISTINCT labels FROM telemetry WHERE id = ?1";
392
393 let mut label_already_used = false;
394 let mut label_count = 0;
395 {
396 let Ok(mut stmt) = tx.prepare(existing_labels_sql) else {
397 return LabelCheck::Label(label.to_string());
399 };
400
401 let Ok(mut rows) = stmt.query(params![base_identifier]) else {
402 return LabelCheck::Label(label.to_string());
404 };
405
406 while let Ok(Some(row)) = rows.next() {
407 let existing_label: String = row.get(0).unwrap();
408
409 label_count += 1;
410 if existing_label == label {
411 label_already_used = true;
412 break;
413 }
414 }
415 }
416
417 if !label_already_used && label_count >= MAX_LABELS {
418 LabelCheck::Label(String::from(OTHER_LABEL))
419 } else if label.len() > MAX_LABEL_LENGTH {
420 log::warn!(
421 "Metric {:?}: label length {} exceeds maximum of {}",
422 base_identifier,
423 label.len(),
424 MAX_LABEL_LENGTH
425 );
426 LabelCheck::Error(String::from(OTHER_LABEL), 1)
427 } else {
428 LabelCheck::Label(label.to_string())
429 }
430}