use std::any::Any;
use std::borrow::Cow;
use std::collections::HashSet;
use std::collections::{hash_map::Entry, HashMap};
use std::mem;
use std::sync::{Arc, Mutex};
use malloc_size_of::MallocSizeOf;
use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
use crate::histogram::HistogramType;
use crate::metrics::{
BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
Metric, MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit,
TimingDistributionMetric,
};
use crate::storage::StorageManager;
use crate::Glean;
const MAX_LABELS: usize = 16;
const OTHER_LABEL: &str = "__other__";
const MAX_LABEL_LENGTH: usize = 111;
pub type LabeledCounter = LabeledMetric<CounterMetric>;
pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
pub type LabeledString = LabeledMetric<StringMetric>;
pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
pub enum LabeledMetricData {
#[allow(missing_docs)]
Common { cmd: CommonMetricData },
#[allow(missing_docs)]
CustomDistribution {
cmd: CommonMetricData,
range_min: i64,
range_max: i64,
bucket_count: i64,
histogram_type: HistogramType,
},
#[allow(missing_docs)]
MemoryDistribution {
cmd: CommonMetricData,
unit: MemoryUnit,
},
#[allow(missing_docs)]
TimingDistribution {
cmd: CommonMetricData,
unit: TimeUnit,
},
}
#[derive(Debug)]
pub struct LabeledMetric<T> {
labels: Option<Vec<Cow<'static, str>>>,
submetric: T,
label_map: Mutex<HashMap<String, Arc<T>>>,
}
impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
let map = self.label_map.lock().unwrap();
let shallow_size = if ops.has_malloc_enclosing_size_of() {
map.values()
.next()
.map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
} else {
map.capacity()
* (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
};
let mut map_size = shallow_size;
for (k, v) in map.iter() {
map_size += k.size_of(ops);
map_size += v.size_of(ops);
}
self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
}
}
mod private {
use super::LabeledMetricData;
use crate::metrics::{
BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
QuantityMetric, StringMetric, TimingDistributionMetric,
};
pub trait Sealed {
fn new_inner(meta: LabeledMetricData) -> Self;
}
impl Sealed for CounterMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::Common { cmd } => Self::new(cmd),
_ => panic!("Incorrect construction of Labeled<CounterMetric>"),
}
}
}
impl Sealed for BooleanMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::Common { cmd } => Self::new(cmd),
_ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
}
}
}
impl Sealed for StringMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::Common { cmd } => Self::new(cmd),
_ => panic!("Incorrect construction of Labeled<StringMetric>"),
}
}
}
impl Sealed for CustomDistributionMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::CustomDistribution {
cmd,
range_min,
range_max,
bucket_count,
histogram_type,
} => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
_ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
}
}
}
impl Sealed for MemoryDistributionMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
_ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
}
}
}
impl Sealed for TimingDistributionMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
_ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
}
}
}
impl Sealed for QuantityMetric {
fn new_inner(meta: LabeledMetricData) -> Self {
match meta {
LabeledMetricData::Common { cmd } => Self::new(cmd),
_ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
}
}
}
}
pub trait AllowLabeled: MetricType {
fn new_labeled(meta: LabeledMetricData) -> Self;
}
impl<T> AllowLabeled for T
where
T: MetricType,
T: private::Sealed,
{
fn new_labeled(meta: LabeledMetricData) -> Self {
T::new_inner(meta)
}
}
impl<T> LabeledMetric<T>
where
T: AllowLabeled + Clone,
{
pub fn new(
meta: LabeledMetricData,
labels: Option<Vec<Cow<'static, str>>>,
) -> LabeledMetric<T> {
let submetric = T::new_labeled(meta);
LabeledMetric::new_inner(submetric, labels)
}
fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
let label_map = Default::default();
LabeledMetric {
labels,
submetric,
label_map,
}
}
fn new_metric_with_name(&self, name: String) -> T {
self.submetric.with_name(name)
}
fn new_metric_with_dynamic_label(&self, label: DynamicLabelType) -> T {
self.submetric.with_dynamic_label(label)
}
fn static_label<'a>(&self, label: &'a str) -> &'a str {
debug_assert!(self.labels.is_some());
let labels = self.labels.as_ref().unwrap();
if labels.iter().any(|l| l == label) {
label
} else {
OTHER_LABEL
}
}
pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
let label = label.as_ref();
let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
let mut map = self.label_map.lock().unwrap();
match map.entry(id) {
Entry::Occupied(entry) => Arc::clone(entry.get()),
Entry::Vacant(entry) => {
let metric = match self.labels {
Some(_) => {
let label = self.static_label(label);
self.new_metric_with_name(combine_base_identifier_and_label(
&self.submetric.meta().inner.name,
label,
))
}
None => self
.new_metric_with_dynamic_label(DynamicLabelType::Label(label.to_string())),
};
let metric = Arc::new(metric);
entry.insert(Arc::clone(&metric));
metric
}
}
}
pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
crate::block_on_dispatcher();
crate::core::with_glean(|glean| {
test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
})
}
}
impl<T, S> TestGetValue for LabeledMetric<T>
where
T: AllowLabeled + TestGetValue<Output = S> + Clone,
S: Any,
{
type Output = HashMap<String, S>;
fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
crate::block_on_dispatcher();
let labels = crate::core::with_glean(|glean| {
let queried_ping_name = ping_name
.as_ref()
.unwrap_or_else(|| &self.submetric.meta().inner.send_in_pings[0]);
StorageManager.snapshot_labels(
glean.storage(),
queried_ping_name,
&self.submetric.meta().identifier(glean),
self.submetric.meta().inner.lifetime,
)
});
let mut out = HashMap::new();
labels.iter().for_each(|label| {
if let Some(v) = self.get(label).test_get_value(ping_name.clone()) {
out.insert(label.to_owned(), v);
}
});
Some(out)
}
}
pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
format!("{}/{}", base_identifer, label)
}
pub fn strip_label(identifier: &str) -> &str {
identifier.split_once('/').map_or(identifier, |s| s.0)
}
pub fn validate_dynamic_label(
glean: &Glean,
meta: &CommonMetricDataInternal,
base_identifier: &str,
label: &str,
) -> String {
let key = combine_base_identifier_and_label(base_identifier, label);
for store in &meta.inner.send_in_pings {
if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
return key;
}
}
let mut labels = HashSet::new();
let prefix = &key[..=base_identifier.len()];
let mut snapshotter = |metric_id: &[u8], _: &Metric| {
labels.insert(metric_id.to_vec());
};
let lifetime = meta.inner.lifetime;
for store in &meta.inner.send_in_pings {
glean
.storage()
.iter_store_from(lifetime, store, Some(prefix), &mut snapshotter);
}
let label_count = labels.len();
let error = if label_count >= MAX_LABELS {
true
} else if label.len() > MAX_LABEL_LENGTH {
let msg = format!(
"label length {} exceeds maximum of {}",
label.len(),
MAX_LABEL_LENGTH
);
record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
true
} else {
false
};
if error {
combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
} else {
key
}
}