use std::collections::HashMap;
use serde::Serialize;
use crate::error_recording::{record_error, ErrorType};
use crate::histogram::{Bucketing, Histogram, HistogramType};
use crate::metrics::Metric;
use crate::metrics::MetricType;
use crate::storage::StorageManager;
use crate::CommonMetricData;
use crate::Glean;
#[derive(Debug)]
pub struct CustomDistributionMetric {
meta: CommonMetricData,
range_min: u64,
range_max: u64,
bucket_count: u64,
histogram_type: HistogramType,
}
#[derive(Debug, Serialize)]
pub struct Snapshot {
values: HashMap<u64, u64>,
sum: u64,
}
pub(crate) fn snapshot<B: Bucketing>(hist: &Histogram<B>) -> Snapshot {
Snapshot {
values: hist.snapshot_values(),
sum: hist.sum(),
}
}
impl MetricType for CustomDistributionMetric {
fn meta(&self) -> &CommonMetricData {
&self.meta
}
fn meta_mut(&mut self) -> &mut CommonMetricData {
&mut self.meta
}
}
impl CustomDistributionMetric {
pub fn new(
meta: CommonMetricData,
range_min: u64,
range_max: u64,
bucket_count: u64,
histogram_type: HistogramType,
) -> Self {
Self {
meta,
range_min,
range_max,
bucket_count,
histogram_type,
}
}
pub fn accumulate_samples_signed(&self, glean: &Glean, samples: Vec<i64>) {
let mut num_negative_samples = 0;
fn accumulate<B: Bucketing, F>(
samples: &[i64],
mut hist: Histogram<B>,
metric: F,
) -> (i32, Metric)
where
F: Fn(Histogram<B>) -> Metric,
{
let mut num_negative_samples = 0;
for &sample in samples.iter() {
if sample < 0 {
num_negative_samples += 1;
} else {
let sample = sample as u64;
hist.accumulate(sample);
}
}
(num_negative_samples, metric(hist))
}
glean.storage().record_with(glean, &self.meta, |old_value| {
let (num_negative, hist) = match self.histogram_type {
HistogramType::Linear => {
let hist = if let Some(Metric::CustomDistributionLinear(hist)) = old_value {
hist
} else {
Histogram::linear(
self.range_min,
self.range_max,
self.bucket_count as usize,
)
};
accumulate(&samples, hist, Metric::CustomDistributionLinear)
}
HistogramType::Exponential => {
let hist = if let Some(Metric::CustomDistributionExponential(hist)) = old_value
{
hist
} else {
Histogram::exponential(
self.range_min,
self.range_max,
self.bucket_count as usize,
)
};
accumulate(&samples, hist, Metric::CustomDistributionExponential)
}
};
num_negative_samples = num_negative;
hist
});
if num_negative_samples > 0 {
let msg = format!("Accumulated {} negative samples", num_negative_samples);
record_error(
glean,
&self.meta,
ErrorType::InvalidValue,
msg,
num_negative_samples,
);
}
}
pub fn test_get_value(
&self,
glean: &Glean,
storage_name: &str,
) -> Option<Histogram<Box<dyn Bucketing>>> {
match StorageManager.snapshot_metric(
glean.storage(),
storage_name,
&self.meta.identifier(glean),
) {
Some(Metric::CustomDistributionExponential(hist)) => Some(hist.boxed()),
Some(Metric::CustomDistributionLinear(hist)) => Some(hist.boxed()),
_ => None,
}
}
pub fn test_get_value_as_json_string(
&self,
glean: &Glean,
storage_name: &str,
) -> Option<String> {
self.test_get_value(glean, storage_name)
.map(|hist| serde_json::to_string(&snapshot(&hist)).unwrap())
}
}