use std::{
borrow::Cow,
sync::{Arc, LazyLock},
};
use opentelemetry::{
global,
metrics::{Gauge, Histogram},
Key, KeyValue, StringValue, Value,
};
pub(crate) static POOL_METRICS: LazyLock<Arc<Metrics>> = LazyLock::new(|| Arc::new(Metrics::new()));
const KEY_POOL_NAME: Key = Key::from_static_str("db.client.connection.pool.name");
const KEY_STATE: Key = Key::from_static_str("db.client.connection.state");
pub(crate) struct Metrics {
pub conn_count: Gauge<u64>,
pub wait_time: Histogram<f64>,
pub use_time: Histogram<f64>,
pub op_duration: Histogram<f64>,
pub idle_max: Gauge<u64>,
pub idle_min: Gauge<u64>,
pub conn_max: Gauge<u64>,
}
impl Metrics {
pub(crate) fn new() -> Self {
let meter = global::meter("uxum-pools");
let conn_count = meter
.u64_gauge("db.client.connection.count")
.with_description("The number of connections that are currently in state described by the state attribute.")
.init();
let wait_time = meter
.f64_histogram("db.client.connection.wait_time")
.with_unit("s")
.with_description("The time it took to obtain an open connection from the pool.")
.init();
let use_time = meter
.f64_histogram("db.client.connection.use_time")
.with_unit("s")
.with_description(
"The time between borrowing a connection and returning it to the pool.",
)
.init();
let op_duration = meter
.f64_histogram("db.client.operation.duration")
.with_unit("s")
.with_description("Duration of database client operations.")
.init();
let idle_max = meter
.u64_gauge("db.client.connection.idle.max")
.with_description("The maximum number of idle open connections allowed.")
.init();
let idle_min = meter
.u64_gauge("db.client.connection.idle.min")
.with_description("The minimum number of idle open connections allowed.")
.init();
let conn_max = meter
.u64_gauge("db.client.connection.max")
.with_description("The maximum number of open connections allowed.")
.init();
Metrics {
conn_count,
wait_time,
use_time,
op_duration,
idle_max,
idle_min,
conn_max,
}
}
pub(crate) fn record_state(&self, label: &[KeyValue], state: PoolState) {
if let Some(max_size) = state.max_size {
self.conn_max.record(max_size as u64, label);
}
if let Some(size) = state.size {
let total_label = status_kv(label[0].clone(), "total");
self.conn_count.record(size as u64, &total_label);
}
if let Some(idle) = state.idle {
let idle_label = status_kv(label[0].clone(), "idle");
self.conn_count.record(idle as u64, &idle_label);
}
if let Some(in_use) = state.in_use {
let used_label = status_kv(label[0].clone(), "used");
self.conn_count.record(in_use as u64, &used_label);
}
if let Some(min_idle) = state.min_idle {
self.idle_min.record(min_idle as u64, label);
}
if let Some(max_idle) = state.max_idle {
self.idle_max.record(max_idle as u64, label);
}
}
}
impl Default for Metrics {
fn default() -> Self {
Metrics::new()
}
}
pub(crate) fn pool_kv(name: Option<Cow<'static, str>>) -> [KeyValue; 1] {
match name {
Some(n) => [KeyValue::new(KEY_POOL_NAME, n)],
None => [KeyValue::new(KEY_POOL_NAME, "default")],
}
}
pub(crate) fn status_kv(name: KeyValue, status: &'static str) -> [KeyValue; 2] {
[
name,
KeyValue::new(KEY_STATE, Value::String(StringValue::from(status))),
]
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PoolState {
pub max_size: Option<usize>,
pub size: Option<usize>,
pub idle: Option<usize>,
pub in_use: Option<usize>,
pub min_idle: Option<usize>,
pub max_idle: Option<usize>,
}