use std::collections::HashMap;
use serde::Serialize;
use crate::error_recording::{record_error, ErrorType};
use crate::histogram::{Functional, Histogram};
use crate::metrics::memory_unit::MemoryUnit;
use crate::metrics::Metric;
use crate::metrics::MetricType;
use crate::storage::StorageManager;
use crate::CommonMetricData;
use crate::Glean;
const LOG_BASE: f64 = 2.0;
const BUCKETS_PER_MAGNITUDE: f64 = 16.0;
const MAX_BYTES: u64 = 1 << 40;
#[derive(Debug)]
pub struct MemoryDistributionMetric {
meta: CommonMetricData,
memory_unit: MemoryUnit,
}
#[derive(Debug, Serialize)]
pub struct Snapshot {
values: HashMap<u64, u64>,
sum: u64,
}
pub(crate) fn snapshot(hist: &Histogram<Functional>) -> Snapshot {
Snapshot {
values: hist.snapshot(),
sum: hist.sum(),
}
}
impl MetricType for MemoryDistributionMetric {
fn meta(&self) -> &CommonMetricData {
&self.meta
}
fn meta_mut(&mut self) -> &mut CommonMetricData {
&mut self.meta
}
}
impl MemoryDistributionMetric {
pub fn new(meta: CommonMetricData, memory_unit: MemoryUnit) -> Self {
Self { meta, memory_unit }
}
pub fn accumulate(&self, glean: &Glean, sample: u64) {
if !self.should_record(glean) {
return;
}
let mut sample = self.memory_unit.as_bytes(sample);
if sample > MAX_BYTES {
let msg = "Sample is bigger than 1 terabyte";
record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
sample = MAX_BYTES;
}
glean
.storage()
.record_with(glean, &self.meta, |old_value| match old_value {
Some(Metric::MemoryDistribution(mut hist)) => {
hist.accumulate(sample);
Metric::MemoryDistribution(hist)
}
_ => {
let mut hist = Histogram::functional(LOG_BASE, BUCKETS_PER_MAGNITUDE);
hist.accumulate(sample);
Metric::MemoryDistribution(hist)
}
});
}
pub fn accumulate_samples_signed(&self, glean: &Glean, samples: Vec<i64>) {
let mut num_negative_samples = 0;
let mut num_too_log_samples = 0;
glean.storage().record_with(glean, &self.meta, |old_value| {
let mut hist = match old_value {
Some(Metric::MemoryDistribution(hist)) => hist,
_ => Histogram::functional(LOG_BASE, BUCKETS_PER_MAGNITUDE),
};
for &sample in samples.iter() {
if sample < 0 {
num_negative_samples += 1;
} else {
let sample = sample as u64;
let mut sample = self.memory_unit.as_bytes(sample);
if sample > MAX_BYTES {
num_too_log_samples += 1;
sample = MAX_BYTES;
}
hist.accumulate(sample);
}
}
Metric::MemoryDistribution(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,
);
}
if num_too_log_samples > 0 {
let msg = format!(
"Accumulated {} samples longer than 10 minutes",
num_too_log_samples
);
record_error(
glean,
&self.meta,
ErrorType::InvalidValue,
msg,
num_too_log_samples,
);
}
}
pub fn test_get_value(
&self,
glean: &Glean,
storage_name: &str,
) -> Option<Histogram<Functional>> {
match StorageManager.snapshot_metric(
glean.storage(),
storage_name,
&self.meta.identifier(glean),
) {
Some(Metric::MemoryDistribution(hist)) => Some(hist),
_ => 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())
}
}