1use std::any::Any;
6use std::borrow::Cow;
7use std::collections::HashSet;
8use std::collections::{hash_map::Entry, HashMap};
9use std::mem;
10use std::sync::{Arc, Mutex};
11
12use malloc_size_of::MallocSizeOf;
13
14use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
15use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
16use crate::histogram::HistogramType;
17use crate::metrics::{
18 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
19 Metric, MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit,
20 TimingDistributionMetric,
21};
22use crate::storage::StorageManager;
23use crate::Glean;
24
25const MAX_LABELS: usize = 16;
26const OTHER_LABEL: &str = "__other__";
27const MAX_LABEL_LENGTH: usize = 111;
28
29pub type LabeledCounter = LabeledMetric<CounterMetric>;
31
32pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
34
35pub type LabeledString = LabeledMetric<StringMetric>;
37
38pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
40
41pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
43
44pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
46
47pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
49
50pub enum LabeledMetricData {
55 #[allow(missing_docs)]
57 Common { cmd: CommonMetricData },
58 #[allow(missing_docs)]
60 CustomDistribution {
61 cmd: CommonMetricData,
62 range_min: i64,
63 range_max: i64,
64 bucket_count: i64,
65 histogram_type: HistogramType,
66 },
67 #[allow(missing_docs)]
69 MemoryDistribution {
70 cmd: CommonMetricData,
71 unit: MemoryUnit,
72 },
73 #[allow(missing_docs)]
75 TimingDistribution {
76 cmd: CommonMetricData,
77 unit: TimeUnit,
78 },
79}
80
81#[derive(Debug)]
85pub struct LabeledMetric<T> {
86 labels: Option<Vec<Cow<'static, str>>>,
87 submetric: T,
90
91 label_map: Mutex<HashMap<String, Arc<T>>>,
94}
95
96impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
97 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
98 let map = self.label_map.lock().unwrap();
99
100 let shallow_size = if ops.has_malloc_enclosing_size_of() {
104 map.values()
105 .next()
106 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
107 } else {
108 map.capacity()
109 * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
110 };
111
112 let mut map_size = shallow_size;
113 for (k, v) in map.iter() {
114 map_size += k.size_of(ops);
115 map_size += v.size_of(ops);
116 }
117
118 self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
119 }
120}
121
122mod private {
126 use super::LabeledMetricData;
127 use crate::metrics::{
128 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
129 QuantityMetric, StringMetric, TimingDistributionMetric,
130 };
131
132 pub trait Sealed {
138 fn new_inner(meta: LabeledMetricData) -> Self;
140 }
141
142 impl Sealed for CounterMetric {
143 fn new_inner(meta: LabeledMetricData) -> Self {
144 match meta {
145 LabeledMetricData::Common { cmd } => Self::new(cmd),
146 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
147 }
148 }
149 }
150
151 impl Sealed for BooleanMetric {
152 fn new_inner(meta: LabeledMetricData) -> Self {
153 match meta {
154 LabeledMetricData::Common { cmd } => Self::new(cmd),
155 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
156 }
157 }
158 }
159
160 impl Sealed for StringMetric {
161 fn new_inner(meta: LabeledMetricData) -> Self {
162 match meta {
163 LabeledMetricData::Common { cmd } => Self::new(cmd),
164 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
165 }
166 }
167 }
168
169 impl Sealed for CustomDistributionMetric {
170 fn new_inner(meta: LabeledMetricData) -> Self {
171 match meta {
172 LabeledMetricData::CustomDistribution {
173 cmd,
174 range_min,
175 range_max,
176 bucket_count,
177 histogram_type,
178 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
179 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
180 }
181 }
182 }
183
184 impl Sealed for MemoryDistributionMetric {
185 fn new_inner(meta: LabeledMetricData) -> Self {
186 match meta {
187 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
188 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
189 }
190 }
191 }
192
193 impl Sealed for TimingDistributionMetric {
194 fn new_inner(meta: LabeledMetricData) -> Self {
195 match meta {
196 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
197 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
198 }
199 }
200 }
201
202 impl Sealed for QuantityMetric {
203 fn new_inner(meta: LabeledMetricData) -> Self {
204 match meta {
205 LabeledMetricData::Common { cmd } => Self::new(cmd),
206 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
207 }
208 }
209 }
210}
211
212pub trait AllowLabeled: MetricType {
214 fn new_labeled(meta: LabeledMetricData) -> Self;
216}
217
218impl<T> AllowLabeled for T
220where
221 T: MetricType,
222 T: private::Sealed,
223{
224 fn new_labeled(meta: LabeledMetricData) -> Self {
225 T::new_inner(meta)
226 }
227}
228
229impl<T> LabeledMetric<T>
230where
231 T: AllowLabeled + Clone,
232{
233 pub fn new(
237 meta: LabeledMetricData,
238 labels: Option<Vec<Cow<'static, str>>>,
239 ) -> LabeledMetric<T> {
240 let submetric = T::new_labeled(meta);
241 LabeledMetric::new_inner(submetric, labels)
242 }
243
244 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
245 let label_map = Default::default();
246 LabeledMetric {
247 labels,
248 submetric,
249 label_map,
250 }
251 }
252
253 fn new_metric_with_name(&self, name: String) -> T {
257 self.submetric.with_name(name)
258 }
259
260 fn new_metric_with_dynamic_label(&self, label: DynamicLabelType) -> T {
265 self.submetric.with_dynamic_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_name(combine_base_identifier_and_label(
324 &self.submetric.meta().inner.name,
325 label,
326 ))
327 }
328 None => self
329 .new_metric_with_dynamic_label(DynamicLabelType::Label(label.to_string())),
330 };
331 let metric = Arc::new(metric);
332 entry.insert(Arc::clone(&metric));
333 metric
334 }
335 }
336 }
337
338 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
350 crate::block_on_dispatcher();
351 crate::core::with_glean(|glean| {
352 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
353 })
354 }
355}
356
357impl<T, S> TestGetValue for LabeledMetric<T>
358where
359 T: AllowLabeled + TestGetValue<Output = S> + Clone,
360 S: Any,
361{
362 type Output = HashMap<String, S>;
363
364 fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
365 crate::block_on_dispatcher();
367 let labels = crate::core::with_glean(|glean| {
368 let queried_ping_name = ping_name
369 .as_ref()
370 .unwrap_or_else(|| &self.submetric.meta().inner.send_in_pings[0]);
371 StorageManager.snapshot_labels(
372 glean.storage(),
373 queried_ping_name,
374 &self.submetric.meta().identifier(glean),
375 self.submetric.meta().inner.lifetime,
376 )
377 });
378 let mut out = HashMap::new();
379 labels.iter().for_each(|label| {
380 if let Some(v) = self.get(label).test_get_value(ping_name.clone()) {
381 out.insert(label.to_owned(), v);
382 }
383 });
384 Some(out)
385 }
386}
387
388pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
390 format!("{}/{}", base_identifer, label)
391}
392
393pub fn strip_label(identifier: &str) -> &str {
395 identifier.split_once('/').map_or(identifier, |s| s.0)
396}
397
398pub fn validate_dynamic_label(
412 glean: &Glean,
413 meta: &CommonMetricDataInternal,
414 base_identifier: &str,
415 label: &str,
416) -> String {
417 let key = combine_base_identifier_and_label(base_identifier, label);
418 for store in &meta.inner.send_in_pings {
419 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
420 return key;
421 }
422 }
423
424 let mut labels = HashSet::new();
425 let prefix = &key[..=base_identifier.len()];
426 let mut snapshotter = |metric_id: &[u8], _: &Metric| {
427 labels.insert(metric_id.to_vec());
428 };
429
430 let lifetime = meta.inner.lifetime;
431 for store in &meta.inner.send_in_pings {
432 glean
433 .storage()
434 .iter_store_from(lifetime, store, Some(prefix), &mut snapshotter);
435 }
436
437 let label_count = labels.len();
438 let error = if label_count >= MAX_LABELS {
439 true
440 } else if label.len() > MAX_LABEL_LENGTH {
441 let msg = format!(
442 "label length {} exceeds maximum of {}",
443 label.len(),
444 MAX_LABEL_LENGTH
445 );
446 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
447 true
448 } else {
449 false
450 };
451
452 if error {
453 combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
454 } else {
455 key
456 }
457}