1use std::sync::atomic::{AtomicU64, Ordering};
21use std::sync::Arc;
22use std::time::{Duration, Instant};
23
24use metrics::{counter, gauge, histogram};
25
26#[derive(Debug, Clone)]
31pub struct XdsMetrics {
32 inner: Arc<XdsMetricsInner>,
33}
34
35#[derive(Debug)]
36struct XdsMetricsInner {
37 active_streams: AtomicU64,
39 active_connections: AtomicU64,
41}
42
43impl Default for XdsMetrics {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl XdsMetrics {
50 pub fn new() -> Self {
52 Self {
53 inner: Arc::new(XdsMetricsInner {
54 active_streams: AtomicU64::new(0),
55 active_connections: AtomicU64::new(0),
56 }),
57 }
58 }
59
60 pub fn record_request(&self, type_url: &str) {
62 counter!("xds_requests_total", "type_url" => type_url.to_string()).increment(1);
63 }
64
65 pub fn record_response(&self, type_url: &str, latency_ms: u64) {
67 counter!("xds_responses_total", "type_url" => type_url.to_string()).increment(1);
68 histogram!("xds_response_latency_ms", "type_url" => type_url.to_string())
69 .record(latency_ms as f64);
70 }
71
72 pub fn record_nack(&self, type_url: &str) {
74 counter!("xds_nacks_total", "type_url" => type_url.to_string()).increment(1);
75 }
76
77 pub fn record_ack(&self, type_url: &str) {
79 counter!("xds_acks_total", "type_url" => type_url.to_string()).increment(1);
80 }
81
82 pub fn stream_opened(&self, service: &str) {
84 let count = self.inner.active_streams.fetch_add(1, Ordering::Relaxed) + 1;
85 counter!("xds_streams_opened_total", "service" => service.to_string()).increment(1);
86 gauge!("xds_active_streams").set(count as f64);
87 }
88
89 pub fn stream_closed(&self, service: &str, duration: Duration) {
91 let count = self.inner.active_streams.fetch_sub(1, Ordering::Relaxed) - 1;
92 counter!("xds_streams_closed_total", "service" => service.to_string()).increment(1);
93 gauge!("xds_active_streams").set(count as f64);
94 histogram!("xds_stream_duration_seconds", "service" => service.to_string())
95 .record(duration.as_secs_f64());
96 }
97
98 pub fn connection_opened(&self) {
100 let count = self.inner.active_connections.fetch_add(1, Ordering::Relaxed) + 1;
101 counter!("xds_connections_opened_total").increment(1);
102 gauge!("xds_active_connections").set(count as f64);
103 }
104
105 pub fn connection_closed(&self) {
107 let count = self.inner.active_connections.fetch_sub(1, Ordering::Relaxed) - 1;
108 counter!("xds_connections_closed_total").increment(1);
109 gauge!("xds_active_connections").set(count as f64);
110 }
111
112 pub fn cache_hit(&self, type_url: &str) {
114 counter!("xds_cache_hits_total", "type_url" => type_url.to_string()).increment(1);
115 }
116
117 pub fn cache_miss(&self, type_url: &str) {
119 counter!("xds_cache_misses_total", "type_url" => type_url.to_string()).increment(1);
120 }
121
122 pub fn snapshot_updated(&self, node_count: usize, resource_count: usize) {
124 counter!("xds_snapshot_updates_total").increment(1);
125 gauge!("xds_snapshot_nodes").set(node_count as f64);
126 gauge!("xds_snapshot_resources").set(resource_count as f64);
127 }
128
129 pub fn active_streams(&self) -> u64 {
131 self.inner.active_streams.load(Ordering::Relaxed)
132 }
133
134 pub fn active_connections(&self) -> u64 {
136 self.inner.active_connections.load(Ordering::Relaxed)
137 }
138}
139
140#[derive(Debug)]
144pub struct LatencyTimer {
145 start: Instant,
146 type_url: String,
147 metrics: XdsMetrics,
148}
149
150impl LatencyTimer {
151 pub fn new(metrics: XdsMetrics, type_url: impl Into<String>) -> Self {
153 Self {
154 start: Instant::now(),
155 type_url: type_url.into(),
156 metrics,
157 }
158 }
159
160 pub fn finish(self) {
162 let elapsed = self.start.elapsed();
163 self.metrics
164 .record_response(&self.type_url, elapsed.as_millis() as u64);
165 }
166}
167
168impl Drop for LatencyTimer {
169 fn drop(&mut self) {
170 }
172}
173
174#[derive(Debug)]
178pub struct StreamTracker {
179 start: Instant,
180 service: String,
181 metrics: XdsMetrics,
182}
183
184impl StreamTracker {
185 pub fn new(metrics: XdsMetrics, service: impl Into<String>) -> Self {
187 let service = service.into();
188 metrics.stream_opened(&service);
189 Self {
190 start: Instant::now(),
191 service,
192 metrics,
193 }
194 }
195}
196
197impl Drop for StreamTracker {
198 fn drop(&mut self) {
199 self.metrics
200 .stream_closed(&self.service, self.start.elapsed());
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn metrics_creation() {
210 let metrics = XdsMetrics::new();
211 assert_eq!(metrics.active_streams(), 0);
212 assert_eq!(metrics.active_connections(), 0);
213 }
214
215 #[test]
216 fn stream_tracking() {
217 let metrics = XdsMetrics::new();
218
219 metrics.stream_opened("ads");
220 assert_eq!(metrics.active_streams(), 1);
221
222 metrics.stream_opened("cds");
223 assert_eq!(metrics.active_streams(), 2);
224
225 metrics.stream_closed("ads", Duration::from_secs(10));
226 assert_eq!(metrics.active_streams(), 1);
227 }
228
229 #[test]
230 fn connection_tracking() {
231 let metrics = XdsMetrics::new();
232
233 metrics.connection_opened();
234 assert_eq!(metrics.active_connections(), 1);
235
236 metrics.connection_opened();
237 assert_eq!(metrics.active_connections(), 2);
238
239 metrics.connection_closed();
240 assert_eq!(metrics.active_connections(), 1);
241 }
242}