uxum_pools/
metrics.rs

1use std::{
2    borrow::Cow,
3    sync::{Arc, LazyLock},
4};
5
6use opentelemetry::{
7    global,
8    metrics::{Gauge, Histogram},
9    Key, KeyValue, StringValue, Value,
10};
11
12/// Central metrics singleton for instrumented pool metrics.
13pub(crate) static POOL_METRICS: LazyLock<Arc<Metrics>> = LazyLock::new(|| Arc::new(Metrics::new()));
14
15const KEY_POOL_NAME: Key = Key::from_static_str("db.client.connection.pool.name");
16const KEY_STATE: Key = Key::from_static_str("db.client.connection.state");
17
18/// Storage for pool metrics.
19pub(crate) struct Metrics {
20    /// The number of connections that are currently in state described by the state attribute.
21    pub conn_count: Gauge<u64>,
22    /// The time it took to obtain an open connection from the pool.
23    pub wait_time: Histogram<f64>,
24    /// The time between borrowing a connection and returning it to the pool.
25    pub use_time: Histogram<f64>,
26    /// Duration of database client operations.
27    pub op_duration: Histogram<f64>,
28    /// The maximum number of idle open connections allowed.
29    pub idle_max: Gauge<u64>,
30    /// The minimum number of idle open connections allowed.
31    pub idle_min: Gauge<u64>,
32    /// The maximum number of open connections allowed.
33    pub conn_max: Gauge<u64>,
34}
35
36impl Metrics {
37    /// Create new storage for pool metrics.
38    ///
39    /// You probably don't need this, as all pools use a central metrics singleton for storage.
40    pub(crate) fn new() -> Self {
41        let meter = global::meter("uxum-pools");
42        // db.client.connection.pool.name (string)
43        // db.client.connection.state (idle / used)
44        // TODO: unused
45        let conn_count = meter
46            .u64_gauge("db.client.connection.count")
47            .with_description("The number of connections that are currently in state described by the state attribute.")
48            .build();
49        // db.client.connection.pool.name (string)
50        let wait_time = meter
51            .f64_histogram("db.client.connection.wait_time")
52            .with_unit("s")
53            .with_description("The time it took to obtain an open connection from the pool.")
54            .build();
55        // db.client.connection.pool.name (string)
56        let use_time = meter
57            .f64_histogram("db.client.connection.use_time")
58            .with_unit("s")
59            .with_description(
60                "The time between borrowing a connection and returning it to the pool.",
61            )
62            .build();
63        // db.system.name (string, DB type)
64        // db.collection.name (string, table)
65        // db.namespace (string, database/schema, fully qualified)
66        // db.operation.name (string, command)
67        // db.response.status_code (string, error or exception code)
68        // error.type (string, class of error)
69        // server.port (int, local service)
70        // db.query.summary (string, abbreviated query)
71        // network.peer.address (string, raw remote addr of DB node)
72        // network.peer.port (int, remote port)
73        // server.address (string, name of DB host)
74        // db.query.text (string, query without binds, dubious)
75        let op_duration = meter
76            .f64_histogram("db.client.operation.duration")
77            .with_unit("s")
78            .with_description("Duration of database client operations.")
79            .build();
80        // db.client.connection.pool.name (string)
81        let idle_max = meter
82            .u64_gauge("db.client.connection.idle.max")
83            .with_description("The maximum number of idle open connections allowed.")
84            .build();
85        // db.client.connection.pool.name (string)
86        let idle_min = meter
87            .u64_gauge("db.client.connection.idle.min")
88            .with_description("The minimum number of idle open connections allowed.")
89            .build();
90        // db.client.connection.pool.name (string)
91        let conn_max = meter
92            .u64_gauge("db.client.connection.max")
93            .with_description("The maximum number of open connections allowed.")
94            .build();
95        Metrics {
96            conn_count,
97            wait_time,
98            use_time,
99            op_duration,
100            idle_max,
101            idle_min,
102            conn_max,
103        }
104    }
105
106    pub(crate) fn record_state(&self, label: &[KeyValue], state: PoolState) {
107        if let Some(max_size) = state.max_size {
108            self.conn_max.record(max_size as u64, label);
109        }
110        if let Some(size) = state.size {
111            let total_label = status_kv(label[0].clone(), "total");
112            self.conn_count.record(size as u64, &total_label);
113        }
114        if let Some(idle) = state.idle {
115            let idle_label = status_kv(label[0].clone(), "idle");
116            self.conn_count.record(idle as u64, &idle_label);
117        }
118        if let Some(in_use) = state.in_use {
119            let used_label = status_kv(label[0].clone(), "used");
120            self.conn_count.record(in_use as u64, &used_label);
121        }
122        if let Some(min_idle) = state.min_idle {
123            self.idle_min.record(min_idle as u64, label);
124        }
125        if let Some(max_idle) = state.max_idle {
126            self.idle_max.record(max_idle as u64, label);
127        }
128    }
129}
130
131impl Default for Metrics {
132    fn default() -> Self {
133        Metrics::new()
134    }
135}
136
137pub(crate) fn pool_kv(name: Option<Cow<'static, str>>) -> [KeyValue; 1] {
138    match name {
139        Some(n) => [KeyValue::new(KEY_POOL_NAME, n)],
140        None => [KeyValue::new(KEY_POOL_NAME, "default")],
141    }
142}
143
144pub(crate) fn status_kv(name: KeyValue, status: &'static str) -> [KeyValue; 2] {
145    [
146        name,
147        KeyValue::new(KEY_STATE, Value::String(StringValue::from(status))),
148    ]
149}
150
151/// State of a pool object.
152///
153/// If pool type doesn't support some metrics, these metrics must be left as `None`.
154#[derive(Clone, Debug, Default, PartialEq, Eq)]
155pub struct PoolState {
156    /// Maximum total (`idle` + `in_use`) number of resources in the pool.
157    pub max_size: Option<usize>,
158    /// Current total (`idle` + `in_use`) number of resources in the pool.
159    pub size: Option<usize>,
160    /// Current number of idle (not acquired) resources.
161    pub idle: Option<usize>,
162    /// Current number of in-use (acquired) resources.
163    pub in_use: Option<usize>,
164    /// Minimum number of idle resources to keep in the pool.
165    pub min_idle: Option<usize>,
166    /// Maximum number of idle resources to keep in the pool.
167    pub max_idle: Option<usize>,
168}