Skip to main content

iroh_proxy_utils/upstream/
metrics.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, RwLock},
4};
5
6use iroh_metrics::Counter;
7
8use crate::Authority;
9
10/// Aggregate metrics for an [`super::UpstreamProxy`] instance.
11///
12/// Tracks connection and request counts across all targets, and provides
13/// access to per-target metrics via [`UpstreamMetrics::get`] and [`UpstreamMetrics::for_each`].
14#[derive(Debug, Default)]
15pub struct UpstreamMetrics {
16    targets: RwLock<BTreeMap<Authority, Arc<TargetMetrics>>>,
17    pub(super) connections_accepted: Counter,
18    pub(super) connections_completed: Counter,
19    pub(super) requests_accepted: Counter,
20    pub(super) requests_denied: Counter,
21    pub(super) requests_completed: Counter,
22    pub(super) requests_failed: Counter,
23}
24
25impl UpstreamMetrics {
26    /// Returns the total number of denied requests across all targets.
27    pub fn denied_requests(&self) -> u64 {
28        self.requests_denied.get()
29    }
30
31    /// Returns the total number of accepted requests across all targets.
32    pub fn accepted_requests(&self) -> u64 {
33        self.requests_accepted.get()
34    }
35
36    /// Returns the estimated number of currently in-flight requests.
37    ///
38    /// Uses saturating arithmetic since counters are read non-atomically.
39    pub fn active_requests(&self) -> u64 {
40        self.requests_accepted
41            .get()
42            .saturating_sub(self.requests_completed.get())
43            .saturating_sub(self.requests_failed.get())
44    }
45
46    /// Returns the estimated number of currently open iroh connections.
47    ///
48    /// Uses saturating arithmetic since counters are read non-atomically.
49    pub fn active_iroh_connections(&self) -> u64 {
50        self.connections_accepted
51            .get()
52            .saturating_sub(self.connections_completed.get())
53    }
54
55    /// Returns the total number of iroh connections ever accepted.
56    pub fn total_iroh_connections(&self) -> u64 {
57        self.connections_accepted.get()
58    }
59
60    /// Returns the per-target metrics for `target`, if any requests have been made to it.
61    pub fn get(&self, target: &Authority) -> Option<Arc<TargetMetrics>> {
62        let inner = self.targets.read().expect("poisoned");
63        inner.get(target).cloned()
64    }
65
66    pub(super) fn get_or_insert(&self, target: Authority) -> Arc<TargetMetrics> {
67        {
68            let inner = self.targets.read().expect("poisoned");
69            if let Some(value) = inner.get(&target) {
70                return value.clone();
71            }
72        }
73        let mut inner = self.targets.write().expect("poisoned");
74        let value = inner.entry(target).or_default();
75        value.clone()
76    }
77
78    /// Calls `f` for each tracked target and its metrics.
79    ///
80    /// Holds a read lock on the target map for the duration of the call.
81    pub fn for_each(&self, f: impl Fn(&Authority, &TargetMetrics)) {
82        let inner = self.targets.read().expect("poisoned");
83        for (k, v) in inner.iter() {
84            f(k, v);
85        }
86    }
87}
88
89/// Per-target metrics tracked by the upstream proxy.
90///
91/// Each request increments:
92/// - either `requests_accepted` or `requests_denied`
93/// - on completion: either `requests_failed` or `requests_completed`
94#[derive(Default, Debug)]
95pub struct TargetMetrics {
96    pub(super) requests_accepted: Counter,
97    pub(super) requests_denied: Counter,
98    pub(super) requests_completed: Counter,
99    pub(super) requests_failed: Counter,
100    pub(super) bytes_to_origin: Counter,
101    pub(super) bytes_from_origin: Counter,
102}
103
104impl TargetMetrics {
105    /// Returns the number of requests denied by the auth handler for this target.
106    pub fn denied_requests(&self) -> u64 {
107        self.requests_denied.get()
108    }
109
110    /// Returns the number of requests accepted by the auth handler for this target.
111    pub fn accepted_requests(&self) -> u64 {
112        self.requests_accepted.get()
113    }
114
115    /// Returns the number of requests that failed (connection error, origin unreachable, etc.).
116    pub fn failed_requests(&self) -> u64 {
117        self.requests_failed.get()
118    }
119
120    /// Returns the estimated number of currently in-flight requests for this target.
121    ///
122    /// Uses saturating arithmetic since counters are read non-atomically.
123    pub fn active_requests(&self) -> u64 {
124        self.requests_accepted
125            .get()
126            .saturating_sub(self.requests_completed.get())
127            .saturating_sub(self.requests_failed.get())
128    }
129
130    /// Returns the total bytes sent to the origin server for this target.
131    pub fn bytes_to_origin(&self) -> u64 {
132        self.bytes_to_origin.get()
133    }
134
135    /// Returns the total bytes received from the origin server for this target.
136    pub fn bytes_from_origin(&self) -> u64 {
137        self.bytes_from_origin.get()
138    }
139}