use std::collections::HashMap;
use std::time::Duration;
use human_size::Size;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::config::primitives::toggle::ToggleWith;
use crate::config::primitives::value_or_expression::ValueOrExpression;
use crate::config::telemetry::{
tracing::OtlpGrpcConfig, tracing::OtlpHttpConfig, tracing::OtlpProtocol,
};
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct MetricsConfig {
#[serde(default)]
pub exporters: Vec<MetricsExporterConfig>,
#[serde(default)]
pub instrumentation: MetricsInstrumentationConfig,
}
impl MetricsConfig {
pub fn is_enabled(&self) -> bool {
self.exporters.iter().any(|exporter| exporter.is_enabled())
}
}
fn default_metrics_interval() -> Duration {
Duration::from_secs(60)
}
fn default_metrics_max_export_timeout() -> Duration {
Duration::from_secs(5)
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum MetricsTemporality {
#[default]
Cumulative,
Delta,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct MetricsInstrumentationConfig {
#[serde(default)]
pub common: MetricsCommonConfig,
#[serde(default)]
pub instruments: MetricsInstrumentsConfig,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct MetricsCommonConfig {
#[serde(default)]
pub histogram: MetricsHistogramConfig,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields, tag = "aggregation", rename_all = "snake_case")]
pub enum MetricsHistogramConfig {
Explicit {
#[serde(default = "default_explicit_histogram_seconds")]
seconds: MetricsExplicitHistogramUnitConfig,
#[serde(default = "default_explicit_histogram_bytes")]
bytes: MetricsExplicitHistogramUnitConfig,
},
Exponential {
max_size: u32,
max_scale: i8,
#[serde(default)]
record_min_max: bool,
},
}
impl Default for MetricsHistogramConfig {
fn default() -> Self {
Self::Explicit {
seconds: default_explicit_histogram_seconds(),
bytes: default_explicit_histogram_bytes(),
}
}
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct MetricsExplicitHistogramUnitConfig {
pub buckets: MetricsHistogramBuckets,
#[serde(default)]
pub record_min_max: bool,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(untagged)]
pub enum MetricsHistogramBuckets {
Numeric(Vec<f64>),
HumanReadable(Vec<String>),
}
impl MetricsExplicitHistogramUnitConfig {
pub fn resolve_seconds_buckets(&self) -> Result<Vec<f64>, String> {
match &self.buckets {
MetricsHistogramBuckets::Numeric(values) => Ok(values.clone()),
MetricsHistogramBuckets::HumanReadable(values) => values
.iter()
.map(|value| {
humantime::parse_duration(value)
.map(|duration| duration.as_secs_f64())
.map_err(|err| {
format!("Invalid duration bucket '{value}' in seconds.buckets: {err}")
})
})
.collect(),
}
}
pub fn resolve_bytes_buckets(&self) -> Result<Vec<f64>, String> {
match &self.buckets {
MetricsHistogramBuckets::Numeric(values) => Ok(values.clone()),
MetricsHistogramBuckets::HumanReadable(values) => values
.iter()
.map(|value| {
value
.parse::<Size>()
.map(|size| size.to_bytes() as f64)
.map_err(|err| {
format!("Invalid byte bucket '{value}' in bytes.buckets: {err}")
})
})
.collect(),
}
}
}
fn default_explicit_histogram_seconds_buckets() -> Vec<f64> {
vec![
0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0,
]
}
fn default_explicit_histogram_bytes_buckets() -> Vec<f64> {
vec![
128.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0, 131072.0,
262144.0, 524288.0, 1048576.0, 2097152.0, 3145728.0, 4194304.0, 5242880.0,
]
}
fn default_explicit_histogram_seconds() -> MetricsExplicitHistogramUnitConfig {
MetricsExplicitHistogramUnitConfig {
buckets: MetricsHistogramBuckets::Numeric(default_explicit_histogram_seconds_buckets()),
record_min_max: false,
}
}
fn default_explicit_histogram_bytes() -> MetricsExplicitHistogramUnitConfig {
MetricsExplicitHistogramUnitConfig {
buckets: MetricsHistogramBuckets::Numeric(default_explicit_histogram_bytes_buckets()),
record_min_max: false,
}
}
pub type MetricsInstrumentsConfig = HashMap<String, ToggleWith<InstrumentConfig>>;
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Default, PartialEq)]
pub struct InstrumentConfig {
pub attributes: HashMap<String, bool>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields, tag = "kind")]
pub enum MetricsExporterConfig {
#[serde(rename = "otlp")]
Otlp(Box<MetricsOtlpConfig>),
#[serde(rename = "prometheus")]
Prometheus(Box<MetricsPrometheusConfig>),
}
impl MetricsExporterConfig {
fn is_enabled(&self) -> bool {
match self {
MetricsExporterConfig::Otlp(config) => config.enabled,
MetricsExporterConfig::Prometheus(config) => config.enabled,
}
}
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct MetricsOtlpConfig {
#[serde(default = "default_otlp_config_enabled")]
pub enabled: bool,
#[serde(default)]
pub endpoint: ValueOrExpression<String>,
pub protocol: OtlpProtocol,
#[serde(
default = "default_metrics_interval",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "String")]
pub interval: Duration,
#[serde(default)]
pub temporality: MetricsTemporality,
#[serde(
default = "default_metrics_max_export_timeout",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "String")]
pub max_export_timeout: Duration,
#[serde(default)]
pub http: Option<OtlpHttpConfig>,
#[serde(default)]
pub grpc: Option<OtlpGrpcConfig>,
}
fn default_otlp_config_enabled() -> bool {
true
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct MetricsPrometheusConfig {
#[serde(default = "default_prometheus_enabled")]
pub enabled: bool,
#[serde(default)]
pub port: Option<u16>,
#[serde(default = "default_prometheus_path")]
pub path: String,
}
fn default_prometheus_enabled() -> bool {
true
}
fn default_prometheus_path() -> String {
"/metrics".to_string()
}