204 lines
6.4 KiB
Rust
204 lines
6.4 KiB
Rust
use std::time::Duration;
|
|
|
|
use opentelemetry::{
|
|
KeyValue,
|
|
metrics::{Counter, Histogram, Meter, UpDownCounter},
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct Metrics {
|
|
requests: Counter<u64>,
|
|
request_duration: Histogram<f64>,
|
|
active_requests: UpDownCounter<i64>,
|
|
upstream_duration: Histogram<f64>,
|
|
errors: Counter<u64>,
|
|
active_tunnels: UpDownCounter<i64>,
|
|
tunnel_duration: Histogram<f64>,
|
|
tunnel_bytes: Counter<u64>,
|
|
acl_decisions: Counter<u64>,
|
|
}
|
|
|
|
impl Metrics {
|
|
pub fn new(meter: Meter) -> Self {
|
|
Self {
|
|
requests: meter
|
|
.u64_counter("hermes.proxy.requests")
|
|
.with_description("Number of HTTP requests and CONNECT handshakes handled")
|
|
.with_unit("{request}")
|
|
.build(),
|
|
request_duration: meter
|
|
.f64_histogram("http.server.request.duration")
|
|
.with_description("Duration of downstream HTTP request handling")
|
|
.with_unit("s")
|
|
.build(),
|
|
active_requests: meter
|
|
.i64_up_down_counter("http.server.active_requests")
|
|
.with_description("Number of HTTP requests currently being handled")
|
|
.with_unit("{request}")
|
|
.build(),
|
|
upstream_duration: meter
|
|
.f64_histogram("http.client.request.duration")
|
|
.with_description("Duration of upstream HTTP requests through response headers")
|
|
.with_unit("s")
|
|
.build(),
|
|
errors: meter
|
|
.u64_counter("hermes.proxy.errors")
|
|
.with_description("Number of proxy failures")
|
|
.with_unit("{error}")
|
|
.build(),
|
|
active_tunnels: meter
|
|
.i64_up_down_counter("hermes.proxy.tunnels.active")
|
|
.with_description("Number of active CONNECT tunnels")
|
|
.with_unit("{tunnel}")
|
|
.build(),
|
|
tunnel_duration: meter
|
|
.f64_histogram("hermes.proxy.tunnel.duration")
|
|
.with_description("Duration of completed CONNECT tunnels")
|
|
.with_unit("s")
|
|
.build(),
|
|
tunnel_bytes: meter
|
|
.u64_counter("hermes.proxy.tunnel.bytes")
|
|
.with_description("Bytes relayed through completed CONNECT tunnels")
|
|
.with_unit("By")
|
|
.build(),
|
|
acl_decisions: meter
|
|
.u64_counter("hermes.proxy.acl.decisions")
|
|
.with_description("Number of ACL decisions by outcome and reason")
|
|
.with_unit("{decision}")
|
|
.build(),
|
|
}
|
|
}
|
|
|
|
pub fn request_started(&self) -> ActiveRequest {
|
|
self.active_requests.add(1, &[]);
|
|
ActiveRequest {
|
|
metrics: self.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn request_finished(
|
|
&self,
|
|
method: &str,
|
|
status: u16,
|
|
error_type: Option<&str>,
|
|
duration: Duration,
|
|
) {
|
|
let mut attributes = vec![
|
|
KeyValue::new("http.request.method", method.to_owned()),
|
|
KeyValue::new("http.response.status_code", i64::from(status)),
|
|
];
|
|
if let Some(error_type) = error_type {
|
|
attributes.push(KeyValue::new("error.type", error_type.to_owned()));
|
|
}
|
|
|
|
self.requests.add(1, &attributes);
|
|
self.request_duration
|
|
.record(duration.as_secs_f64(), &attributes);
|
|
}
|
|
|
|
pub fn upstream_finished(
|
|
&self,
|
|
method: &str,
|
|
status: Option<u16>,
|
|
error_type: Option<&str>,
|
|
duration: Duration,
|
|
) {
|
|
let mut attributes = vec![KeyValue::new("http.request.method", method.to_owned())];
|
|
if let Some(status) = status {
|
|
attributes.push(KeyValue::new(
|
|
"http.response.status_code",
|
|
i64::from(status),
|
|
));
|
|
}
|
|
if let Some(error_type) = error_type {
|
|
attributes.push(KeyValue::new("error.type", error_type.to_owned()));
|
|
}
|
|
|
|
self.upstream_duration
|
|
.record(duration.as_secs_f64(), &attributes);
|
|
}
|
|
|
|
pub fn error(&self, phase: &str, error_type: &str) {
|
|
self.errors.add(
|
|
1,
|
|
&[
|
|
KeyValue::new("proxy.phase", phase.to_owned()),
|
|
KeyValue::new("error.type", error_type.to_owned()),
|
|
],
|
|
);
|
|
}
|
|
|
|
pub fn acl_decision(&self, protocol: &str, decision: &str, reason: &str) {
|
|
self.acl_decisions.add(
|
|
1,
|
|
&[
|
|
KeyValue::new("proxy.protocol", protocol.to_owned()),
|
|
KeyValue::new("acl.decision", decision.to_owned()),
|
|
KeyValue::new("acl.reason", reason.to_owned()),
|
|
],
|
|
);
|
|
}
|
|
|
|
pub fn tunnel_started(&self, protocol: &'static str) -> ActiveTunnel {
|
|
self.active_tunnels
|
|
.add(1, &[KeyValue::new("proxy.protocol", protocol)]);
|
|
ActiveTunnel {
|
|
metrics: self.clone(),
|
|
protocol,
|
|
}
|
|
}
|
|
|
|
pub fn tunnel_finished(
|
|
&self,
|
|
protocol: &str,
|
|
duration: Duration,
|
|
client_to_upstream: u64,
|
|
upstream_to_client: u64,
|
|
error_type: Option<&str>,
|
|
) {
|
|
let mut attributes = vec![KeyValue::new("proxy.protocol", protocol.to_owned())];
|
|
if let Some(error_type) = error_type {
|
|
attributes.push(KeyValue::new("error.type", error_type.to_owned()));
|
|
}
|
|
self.tunnel_duration
|
|
.record(duration.as_secs_f64(), &attributes);
|
|
self.tunnel_bytes.add(
|
|
client_to_upstream,
|
|
&[
|
|
KeyValue::new("proxy.protocol", protocol.to_owned()),
|
|
KeyValue::new("direction", "client_to_upstream"),
|
|
],
|
|
);
|
|
self.tunnel_bytes.add(
|
|
upstream_to_client,
|
|
&[
|
|
KeyValue::new("proxy.protocol", protocol.to_owned()),
|
|
KeyValue::new("direction", "upstream_to_client"),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
pub(crate) struct ActiveRequest {
|
|
metrics: Metrics,
|
|
}
|
|
|
|
impl Drop for ActiveRequest {
|
|
fn drop(&mut self) {
|
|
self.metrics.active_requests.add(-1, &[]);
|
|
}
|
|
}
|
|
|
|
pub(crate) struct ActiveTunnel {
|
|
metrics: Metrics,
|
|
protocol: &'static str,
|
|
}
|
|
|
|
impl Drop for ActiveTunnel {
|
|
fn drop(&mut self) {
|
|
self.metrics
|
|
.active_tunnels
|
|
.add(-1, &[KeyValue::new("proxy.protocol", self.protocol)]);
|
|
}
|
|
}
|