use opentelemetry_otlp::WithTonicConfig;
use opentelemetry_prometheus_text_exporter::PrometheusExporter;
use opentelemetry_sdk::metrics::periodic_reader_with_async_runtime::PeriodicReader;
use opentelemetry_sdk::metrics::{Aggregation, Instrument, SdkMeterProvider, Stream};
use tonic::transport::ClientTlsConfig;
use super::OTEL_DEFAULT_RESOURCE;
use crate::cnf::{METRICS_ENABLED, TELEMETRY_DISABLE_METRICS, TELEMETRY_PROVIDER};
static HISTOGRAM_BUCKETS_MS: &[f64] = &[
5.0, 10.0, 20.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0, 500.0, 750.0, 1000.0, 1500.0,
2000.0, 2500.0, 5000.0, 10000.0, 15000.0, 30000.0,
];
static HISTOGRAM_BUCKETS_SECONDS: &[f64] = &[
0.005, 0.01, 0.02, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25, 0.3, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 5.0,
10.0, 15.0, 30.0,
];
const KB: f64 = 1024.0;
const MB: f64 = 1024.0 * KB;
const HISTOGRAM_BUCKETS_BYTES: &[f64] = &[
1.0 * KB,
2.0 * KB,
5.0 * KB,
10.0 * KB,
100.0 * KB,
500.0 * KB,
1.0 * MB,
2.5 * MB,
5.0 * MB,
10.0 * MB,
25.0 * MB,
50.0 * MB,
100.0 * MB,
];
pub fn otlp_metrics_active() -> bool {
TELEMETRY_PROVIDER.trim().eq_ignore_ascii_case("otlp") && !*TELEMETRY_DISABLE_METRICS
}
pub struct MetricsInit {
pub provider: SdkMeterProvider,
pub prometheus_exporter: Option<PrometheusExporter>,
}
pub fn init() -> anyhow::Result<Option<MetricsInit>> {
let prom_enabled = *METRICS_ENABLED;
let otlp_enabled = otlp_metrics_active();
if !prom_enabled && !otlp_enabled {
return Ok(None);
}
let mut builder = SdkMeterProvider::builder()
.with_resource(OTEL_DEFAULT_RESOURCE.clone())
.with_view(duration_seconds_view)
.with_view(duration_ms_view)
.with_view(size_bytes_view);
let prometheus_exporter = if prom_enabled {
let exporter = PrometheusExporter::builder()
.build();
builder = builder.with_reader(exporter.clone());
Some(exporter)
} else {
None
};
if otlp_enabled {
let exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_tls_config(ClientTlsConfig::new().with_native_roots())
.with_temporality(opentelemetry_sdk::metrics::Temporality::Cumulative)
.build()?;
let reader = PeriodicReader::builder(exporter, opentelemetry_sdk::runtime::Tokio).build();
builder = builder.with_reader(reader);
}
Ok(Some(MetricsInit {
provider: builder.build(),
prometheus_exporter,
}))
}
fn duration_seconds_view(instrument: &Instrument) -> Option<Stream> {
let name_matches = instrument.name().ends_with(".duration");
let is_seconds = instrument.unit().eq_ignore_ascii_case("s");
if !name_matches || !is_seconds {
return None;
}
Stream::builder()
.with_aggregation(Aggregation::ExplicitBucketHistogram {
boundaries: HISTOGRAM_BUCKETS_SECONDS.to_vec(),
record_min_max: true,
})
.build()
.ok()
}
fn duration_ms_view(instrument: &Instrument) -> Option<Stream> {
let name_matches = instrument.name().ends_with(".duration");
let is_milliseconds = instrument.unit().eq_ignore_ascii_case("ms");
if !name_matches || !is_milliseconds {
return None;
}
Stream::builder()
.with_aggregation(Aggregation::ExplicitBucketHistogram {
boundaries: HISTOGRAM_BUCKETS_MS.to_vec(),
record_min_max: true,
})
.build()
.ok()
}
fn size_bytes_view(instrument: &Instrument) -> Option<Stream> {
let name_matches = instrument.name().ends_with(".size");
let is_bytes = {
let unit = instrument.unit();
unit.eq_ignore_ascii_case("by") || unit.eq_ignore_ascii_case("bytes")
};
if !name_matches || !is_bytes {
return None;
}
Stream::builder()
.with_aggregation(Aggregation::ExplicitBucketHistogram {
boundaries: HISTOGRAM_BUCKETS_BYTES.to_vec(),
record_min_max: true,
})
.build()
.ok()
}