use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum MetricValue {
Double(f64),
Long(i64),
Histogram(MetricDistribution),
Vector(Vec<f64>),
String(String),
Boolean(bool),
Map(HashMap<String, MetricValue>),
}
impl MetricValue {
pub fn is_numeric(&self) -> bool {
matches!(self, MetricValue::Double(_) | MetricValue::Long(_))
}
pub fn as_f64(&self) -> Option<f64> {
match self {
MetricValue::Double(v) => Some(*v),
MetricValue::Long(v) => Some(*v as f64),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
MetricValue::Long(v) => Some(*v),
MetricValue::Double(v) => {
if v.fract() == 0.0 {
Some(*v as i64)
} else {
None
}
}
_ => None,
}
}
pub fn to_string_pretty(&self) -> String {
match self {
MetricValue::Double(v) => {
if v.fract() == 0.0 {
format!("{v:.0}")
} else {
format!("{v:.4}")
}
}
MetricValue::Long(v) => v.to_string(),
MetricValue::String(s) => s.clone(),
MetricValue::Boolean(b) => b.to_string(),
MetricValue::Histogram(h) => format!("Histogram({} buckets)", h.buckets.len()),
MetricValue::Vector(v) => format!("Vector({} elements)", v.len()),
MetricValue::Map(m) => format!("Map({} entries)", m.len()),
}
}
}
impl fmt::Display for MetricValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_pretty())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MetricDistribution {
pub buckets: Vec<HistogramBucket>,
pub total_count: u64,
pub min: Option<f64>,
pub max: Option<f64>,
pub mean: Option<f64>,
pub std_dev: Option<f64>,
}
impl MetricDistribution {
pub fn new() -> Self {
Self {
buckets: Vec::new(),
total_count: 0,
min: None,
max: None,
mean: None,
std_dev: None,
}
}
pub fn from_buckets(buckets: Vec<HistogramBucket>) -> Self {
let total_count = buckets.iter().map(|b| b.count).sum();
Self {
buckets,
total_count,
min: None,
max: None,
mean: None,
std_dev: None,
}
}
pub fn with_stats(mut self, min: f64, max: f64, mean: f64, std_dev: f64) -> Self {
self.min = Some(min);
self.max = Some(max);
self.mean = Some(mean);
self.std_dev = Some(std_dev);
self
}
}
impl Default for MetricDistribution {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HistogramBucket {
pub lower_bound: f64,
pub upper_bound: f64,
pub count: u64,
}
impl HistogramBucket {
pub fn new(lower_bound: f64, upper_bound: f64, count: u64) -> Self {
Self {
lower_bound,
upper_bound,
count,
}
}
pub fn width(&self) -> f64 {
self.upper_bound - self.lower_bound
}
pub fn midpoint(&self) -> f64 {
(self.lower_bound + self.upper_bound) / 2.0
}
}
pub trait AnalyzerMetric: Into<MetricValue> + Send + Sync + fmt::Debug {}
impl AnalyzerMetric for MetricValue {}
impl From<f64> for MetricValue {
fn from(value: f64) -> Self {
MetricValue::Double(value)
}
}
impl From<i64> for MetricValue {
fn from(value: i64) -> Self {
MetricValue::Long(value)
}
}
impl From<bool> for MetricValue {
fn from(value: bool) -> Self {
MetricValue::Boolean(value)
}
}
impl From<String> for MetricValue {
fn from(value: String) -> Self {
MetricValue::String(value)
}
}
impl From<&str> for MetricValue {
fn from(value: &str) -> Self {
MetricValue::String(value.to_string())
}
}