mod builder;
mod pipeline;
use std::time::Duration;
use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter};
use opentelemetry::KeyValue;
use crate::metrics::{ConnectionPoolStats, Metrics};
pub use builder::OtelMetricsBuilder;
pub use pipeline::init_otlp_pipeline;
pub struct OtelMetrics {
request_counter: Counter<u64>,
response_counter: Counter<u64>,
error_counter: Counter<u64>,
latency_histogram: Histogram<f64>,
queue_depth_gauge: Gauge<u64>,
pool_active_gauge: Gauge<u64>,
pool_idle_gauge: Gauge<u64>,
pool_created_counter: Counter<u64>,
pool_closed_counter: Counter<u64>,
persistence_error_counter: Counter<u64>,
push_delivery_counter: Counter<u64>,
}
impl std::fmt::Debug for OtelMetrics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OtelMetrics").finish_non_exhaustive()
}
}
impl OtelMetrics {
#[must_use]
pub fn from_meter(meter: &Meter) -> Self {
let request_counter = meter
.u64_counter("a2a.server.requests")
.with_description("Total number of inbound A2A requests")
.with_unit("request")
.build();
let response_counter = meter
.u64_counter("a2a.server.responses")
.with_description("Total number of outbound A2A responses")
.with_unit("response")
.build();
let error_counter = meter
.u64_counter("a2a.server.errors")
.with_description("Total number of A2A request errors")
.with_unit("error")
.build();
let latency_histogram = meter
.f64_histogram("a2a.server.latency")
.with_description("A2A request latency")
.with_unit("s")
.build();
let queue_depth_gauge = meter
.u64_gauge("a2a.server.queue_depth")
.with_description("Number of active event queues")
.with_unit("queue")
.build();
let pool_active_gauge = meter
.u64_gauge("a2a.server.pool.active")
.with_description("Number of active (in-use) HTTP connections")
.with_unit("connection")
.build();
let pool_idle_gauge = meter
.u64_gauge("a2a.server.pool.idle")
.with_description("Number of idle HTTP connections")
.with_unit("connection")
.build();
let pool_created_counter = meter
.u64_counter("a2a.server.pool.created")
.with_description("Total HTTP connections created since process start")
.with_unit("connection")
.build();
let pool_closed_counter = meter
.u64_counter("a2a.server.pool.closed")
.with_description("HTTP connections closed due to errors or timeouts")
.with_unit("connection")
.build();
let (persistence_error_counter, push_delivery_counter) = Self::failure_instruments(meter);
Self {
request_counter,
response_counter,
error_counter,
latency_histogram,
queue_depth_gauge,
pool_active_gauge,
pool_idle_gauge,
pool_created_counter,
pool_closed_counter,
persistence_error_counter,
push_delivery_counter,
}
}
fn failure_instruments(meter: &Meter) -> (Counter<u64>, Counter<u64>) {
let persistence_error_counter = meter
.u64_counter("a2a.server.persistence_errors")
.with_description(
"Task writes the background processor could not persist. \
Non-zero means data loss: the streaming client already \
received the event.",
)
.with_unit("error")
.build();
let push_delivery_counter = meter
.u64_counter("a2a.server.push_deliveries")
.with_description(
"Push notification delivery attempts, by outcome \
(delivered / failed / timeout)",
)
.with_unit("delivery")
.build();
(persistence_error_counter, push_delivery_counter)
}
}
impl Metrics for OtelMetrics {
fn on_request(&self, method: &str) {
self.request_counter
.add(1, &[KeyValue::new("method", method.to_owned())]);
}
fn on_response(&self, method: &str) {
self.response_counter
.add(1, &[KeyValue::new("method", method.to_owned())]);
}
fn on_error(&self, method: &str, error: &str) {
self.error_counter.add(
1,
&[
KeyValue::new("method", method.to_owned()),
KeyValue::new("error", error.to_owned()),
],
);
}
fn on_latency(&self, method: &str, duration: Duration) {
self.latency_histogram.record(
duration.as_secs_f64(),
&[KeyValue::new("method", method.to_owned())],
);
}
fn on_queue_depth_change(&self, active_queues: usize) {
#[allow(clippy::cast_possible_truncation)]
self.queue_depth_gauge.record(active_queues as u64, &[]);
}
fn on_persistence_error(&self, operation: &str, error_kind: &str) {
self.persistence_error_counter.add(
1,
&[
KeyValue::new("operation", operation.to_owned()),
KeyValue::new("error", error_kind.to_owned()),
],
);
}
fn on_push_delivery(&self, outcome: &str) {
self.push_delivery_counter
.add(1, &[KeyValue::new("outcome", outcome.to_owned())]);
}
fn on_connection_pool_stats(&self, stats: &ConnectionPoolStats) {
self.pool_active_gauge
.record(u64::from(stats.active_connections), &[]);
self.pool_idle_gauge
.record(u64::from(stats.idle_connections), &[]);
self.pool_created_counter
.add(stats.total_connections_created, &[]);
self.pool_closed_counter.add(stats.connections_closed, &[]);
}
}
#[cfg(test)]
mod tests;