use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use metrics::{counter, gauge, histogram};
#[derive(Debug, Clone)]
pub struct XdsMetrics {
inner: Arc<XdsMetricsInner>,
}
#[derive(Debug)]
struct XdsMetricsInner {
active_streams: AtomicU64,
active_connections: AtomicU64,
}
impl Default for XdsMetrics {
fn default() -> Self {
Self::new()
}
}
impl XdsMetrics {
pub fn new() -> Self {
Self {
inner: Arc::new(XdsMetricsInner {
active_streams: AtomicU64::new(0),
active_connections: AtomicU64::new(0),
}),
}
}
pub fn record_request(&self, type_url: &str) {
counter!("xds_requests_total", "type_url" => type_url.to_string()).increment(1);
}
pub fn record_response(&self, type_url: &str, latency_ms: u64) {
counter!("xds_responses_total", "type_url" => type_url.to_string()).increment(1);
histogram!("xds_response_latency_ms", "type_url" => type_url.to_string())
.record(latency_ms as f64);
}
pub fn record_nack(&self, type_url: &str) {
counter!("xds_nacks_total", "type_url" => type_url.to_string()).increment(1);
}
pub fn record_ack(&self, type_url: &str) {
counter!("xds_acks_total", "type_url" => type_url.to_string()).increment(1);
}
pub fn stream_opened(&self, service: &str) {
let count = self.inner.active_streams.fetch_add(1, Ordering::Relaxed) + 1;
counter!("xds_streams_opened_total", "service" => service.to_string()).increment(1);
gauge!("xds_active_streams").set(count as f64);
}
pub fn stream_closed(&self, service: &str, duration: Duration) {
let count = self.inner.active_streams.fetch_sub(1, Ordering::Relaxed) - 1;
counter!("xds_streams_closed_total", "service" => service.to_string()).increment(1);
gauge!("xds_active_streams").set(count as f64);
histogram!("xds_stream_duration_seconds", "service" => service.to_string())
.record(duration.as_secs_f64());
}
pub fn connection_opened(&self) {
let count = self.inner.active_connections.fetch_add(1, Ordering::Relaxed) + 1;
counter!("xds_connections_opened_total").increment(1);
gauge!("xds_active_connections").set(count as f64);
}
pub fn connection_closed(&self) {
let count = self.inner.active_connections.fetch_sub(1, Ordering::Relaxed) - 1;
counter!("xds_connections_closed_total").increment(1);
gauge!("xds_active_connections").set(count as f64);
}
pub fn cache_hit(&self, type_url: &str) {
counter!("xds_cache_hits_total", "type_url" => type_url.to_string()).increment(1);
}
pub fn cache_miss(&self, type_url: &str) {
counter!("xds_cache_misses_total", "type_url" => type_url.to_string()).increment(1);
}
pub fn snapshot_updated(&self, node_count: usize, resource_count: usize) {
counter!("xds_snapshot_updates_total").increment(1);
gauge!("xds_snapshot_nodes").set(node_count as f64);
gauge!("xds_snapshot_resources").set(resource_count as f64);
}
pub fn active_streams(&self) -> u64 {
self.inner.active_streams.load(Ordering::Relaxed)
}
pub fn active_connections(&self) -> u64 {
self.inner.active_connections.load(Ordering::Relaxed)
}
}
#[derive(Debug)]
pub struct LatencyTimer {
start: Instant,
type_url: String,
metrics: XdsMetrics,
}
impl LatencyTimer {
pub fn new(metrics: XdsMetrics, type_url: impl Into<String>) -> Self {
Self {
start: Instant::now(),
type_url: type_url.into(),
metrics,
}
}
pub fn finish(self) {
let elapsed = self.start.elapsed();
self.metrics
.record_response(&self.type_url, elapsed.as_millis() as u64);
}
}
impl Drop for LatencyTimer {
fn drop(&mut self) {
}
}
#[derive(Debug)]
pub struct StreamTracker {
start: Instant,
service: String,
metrics: XdsMetrics,
}
impl StreamTracker {
pub fn new(metrics: XdsMetrics, service: impl Into<String>) -> Self {
let service = service.into();
metrics.stream_opened(&service);
Self {
start: Instant::now(),
service,
metrics,
}
}
}
impl Drop for StreamTracker {
fn drop(&mut self) {
self.metrics
.stream_closed(&self.service, self.start.elapsed());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metrics_creation() {
let metrics = XdsMetrics::new();
assert_eq!(metrics.active_streams(), 0);
assert_eq!(metrics.active_connections(), 0);
}
#[test]
fn stream_tracking() {
let metrics = XdsMetrics::new();
metrics.stream_opened("ads");
assert_eq!(metrics.active_streams(), 1);
metrics.stream_opened("cds");
assert_eq!(metrics.active_streams(), 2);
metrics.stream_closed("ads", Duration::from_secs(10));
assert_eq!(metrics.active_streams(), 1);
}
#[test]
fn connection_tracking() {
let metrics = XdsMetrics::new();
metrics.connection_opened();
assert_eq!(metrics.active_connections(), 1);
metrics.connection_opened();
assert_eq!(metrics.active_connections(), 2);
metrics.connection_closed();
assert_eq!(metrics.active_connections(), 1);
}
}