Skip to main content

iota_http/metrics/
mod.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Measures the connections and the requests of one listener.
5//!
6//! The listener wraps each socket it accepts in a `TrackedIo` and uses it for
7//! the whole life of the connection.
8
9use std::{
10    fmt,
11    net::SocketAddr,
12    sync::{Arc, OnceLock},
13};
14
15use iota_metrics::peak::IntGaugeWithPeakGauge;
16use prometheus_filtered::{
17    Histogram, IntCounterVec, MetricLevel, Registry, register_histogram_with_registry,
18    register_int_counter_vec_with_registry,
19};
20
21mod connection;
22mod io;
23mod requests;
24mod scope;
25mod tls;
26mod track_requests;
27
28pub(crate) use connection::{ConnectionGuard, ConnectionMetrics};
29pub(crate) use io::TrackedIo;
30use scope::Scope;
31pub(crate) use tls::HandshakeResult;
32pub(crate) use track_requests::TrackRequests;
33
34/// Connection lifetimes in seconds.
35const CONNECTION_LIFETIME_SEC_BUCKETS: &[f64] =
36    &[1.0, 5.0, 30.0, 60.0, 300.0, 1800.0, 14400.0, 86400.0];
37
38/// The metrics of one listener, and the state they need.
39#[derive(Clone)]
40pub struct ListenerMetrics(Arc<Inner>);
41
42impl fmt::Debug for ListenerMetrics {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        f.debug_struct("ListenerMetrics")
45            .field("listener", &self.0.prefix)
46            .finish_non_exhaustive()
47    }
48}
49
50impl ListenerMetrics {
51    /// Registers the metrics as `{prefix}_<name>` in `registry`, all at
52    /// `level`. The TLS metrics are registered later, by `enable_tls`.
53    ///
54    /// # Panics
55    ///
56    /// Panics if the same `prefix` is registered twice in one registry.
57    pub fn new(prefix: &str, registry: &Registry, level: MetricLevel) -> Self {
58        Self(Arc::new(Inner {
59            prefix: prefix.to_owned(),
60            registry: registry.clone(),
61            level,
62            metrics: Metrics::new(prefix, registry, level),
63            requests: requests::Metrics::new(prefix, registry, level),
64            tls: OnceLock::new(),
65        }))
66    }
67
68    /// Registers the TLS metrics. The server calls it when it starts with TLS;
69    /// a second call does nothing.
70    pub(crate) fn enable_tls(&self) {
71        self.0.tls.get_or_init(|| {
72            Arc::new(tls::Metrics::new(
73                &self.0.prefix,
74                &self.0.registry,
75                self.0.level,
76            ))
77        });
78    }
79
80    /// Tracks a socket the listener has just accepted, before any TLS
81    /// handshake, until the guard is dropped.
82    fn track_connection(&self, remote: SocketAddr) -> ConnectionGuard {
83        let metrics = &self.0.metrics;
84        let scope: &str = Scope::of(remote.ip()).into();
85        metrics
86            .inbound_connections_accepted
87            .with_label_values(&[scope])
88            .inc();
89        metrics.inbound_connections.inc();
90        ConnectionGuard::new(self.0.clone())
91    }
92}
93
94struct Inner {
95    prefix: String,
96    registry: Registry,
97    level: MetricLevel,
98    metrics: Metrics,
99    requests: requests::Metrics,
100    /// Registered when the listener starts with TLS.
101    tls: OnceLock<Arc<tls::Metrics>>,
102}
103
104struct Metrics {
105    inbound_connections_accepted: IntCounterVec,
106    inbound_connections: IntGaugeWithPeakGauge,
107    connection_lifetime_seconds: Histogram,
108}
109
110impl Metrics {
111    fn new(prefix: &str, registry: &Registry, level: MetricLevel) -> Self {
112        Self {
113            inbound_connections_accepted: register_int_counter_vec_with_registry!(
114                format!("{prefix}_inbound_connections_accepted"),
115                "Sockets accepted, counted from the TCP accept, before any TLS handshake. \
116                 The scope is where the remote address is: loopback, private or public",
117                &["scope"],
118                registry;
119                level
120            )
121            .expect("the metrics of a listener register without collision"),
122            inbound_connections: IntGaugeWithPeakGauge::register(
123                (
124                    &format!("{prefix}_inbound_connections"),
125                    "The number of connections open now. A connection counts from its TCP accept \
126                     to its close, TLS handshake included",
127                ),
128                (
129                    &format!("{prefix}_inbound_connections_peak"),
130                    "The most open connections over the last 2 minutes. A scrape reports at \
131                     least the current number",
132                ),
133                module_path!(),
134                registry,
135                level,
136            ),
137            connection_lifetime_seconds: register_histogram_with_registry!(
138                format!("{prefix}_connection_lifetime_seconds"),
139                "Time from the TCP accept to the close, in seconds",
140                CONNECTION_LIFETIME_SEC_BUCKETS.to_vec(),
141                registry;
142                level
143            )
144            .expect("the metrics of a listener register without collision"),
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests;