Files
nym/nym-node/nym-node-metrics/src/prometheus_wrapper.rs
2025-01-08 10:26:40 +00:00

391 lines
14 KiB
Rust

// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_metrics::{metrics_registry, HistogramTimer, Metric};
use std::sync::LazyLock;
use strum::{Display, EnumCount, EnumIter, EnumProperty, IntoEnumIterator};
pub static PROMETHEUS_METRICS: LazyLock<NymNodePrometheusMetrics> =
LazyLock::new(NymNodePrometheusMetrics::initialise);
const CLIENT_SESSION_DURATION_BUCKETS: &[f64] = &[
// sub 3s (implicitly)
3., // 3s - 15s
15., // 15s - 70s
70., // 70s - 2min
120., // 2 min - 5 min
300., // 5min - 15min
900., // 15min - 1h
3600., // 1h - 12h
43200., // 12h - 23.5h
84600., // 23.5h - 24.5h
88200., // 24.5h - 72h
259200., // 72h+ (implicitly)
];
#[derive(Clone, Debug, EnumIter, Display, EnumProperty, EnumCount, Eq, Hash, PartialEq)]
#[strum(serialize_all = "snake_case", prefix = "nym_node_")]
pub enum PrometheusMetric {
// # MIXNET
// ## INGRESS
#[strum(props(help = "The number of ingress forward hop sphinx packets received"))]
MixnetIngressForwardPacketsReceived,
#[strum(props(help = "The number of ingress final hop sphinx packets received"))]
MixnetIngressFinalHopPacketsReceived,
#[strum(props(help = "The number of ingress malformed sphinx packets received"))]
MixnetIngressMalformedPacketsReceived,
#[strum(props(
help = "The number of ingress forward sphinx packets that specified excessive delay received"
))]
MixnetIngressExcessiveDelayPacketsReceived,
#[strum(props(help = "The number of ingress forward hop sphinx packets dropped"))]
MixnetIngressForwardPacketsDropped,
#[strum(props(help = "The number of ingress final hop sphinx packets dropped"))]
MixnetIngressFinalHopPacketsDropped,
#[strum(props(help = "The current rate of receiving ingress forward hop sphinx packets"))]
MixnetIngressForwardPacketsReceivedRate,
#[strum(props(help = "The current rate of receiving ingress final hop sphinx packets"))]
MixnetIngressFinalHopPacketsReceivedRate,
#[strum(props(help = "The current rate of receiving ingress malformed sphinx packets"))]
MixnetIngressMalformedPacketsReceivedRate,
#[strum(props(
help = "The current rate of receiving ingress sphinx packets that specified excessive delay"
))]
MixnetIngressExcessiveDelayPacketsReceivedRate,
#[strum(props(help = "The current rate of dropping ingress forward hop sphinx packets"))]
MixnetIngressForwardPacketsDroppedRate,
#[strum(props(help = "The current rate of dropping ingress final hop sphinx packets"))]
MixnetIngressFinalHopPacketsDroppedRate,
// ## EGRESS
#[strum(props(
help = "The number of unwrapped final hop packets stored on disk for offline clients"
))]
MixnetEgressStoredOnDiskFinalHopPackets,
#[strum(props(help = "The number of egress forward hop sphinx packets sent/forwarded"))]
MixnetEgressForwardPacketsSent,
#[strum(props(
help = "The number of egress forward hop sphinx packets sent/forwarded (acks only)"
))]
MixnetEgressAckSent,
#[strum(props(help = "The number of egress forward hop sphinx packets dropped"))]
MixnetEgressForwardPacketsDropped,
#[strum(props(
help = "The current rate of sending/forwarding egress forward hop sphinx packets"
))]
MixnetEgressForwardPacketsSendRate,
#[strum(props(
help = "The current rate of sending/forwarding egress forward hop sphinx packets (acks only)"
))]
MixnetEgressAckSendRate,
#[strum(props(help = "The current rate of dropping egress forward hop sphinx packets"))]
MixnetEgressForwardPacketsDroppedRate,
// # ENTRY
#[strum(props(help = "The number of unique users"))]
EntryClientUniqueUsers,
#[strum(props(help = "The number of client sessions started"))]
EntryClientSessionsStarted,
#[strum(props(help = "The number of client sessions finished"))]
EntryClientSessionsFinished,
#[strum(to_string = "entry_client_sessions_durations_{typ}")]
#[strum(props(help = "The distribution of client sessions duration of the specified type"))]
EntryClientSessionsDurations { typ: String },
// # WIREGUARD
#[strum(props(help = "The amount of bytes transmitted via wireguard"))]
WireguardBytesTx,
#[strum(props(help = "The amount of bytes received via wireguard"))]
WireguardBytesRx,
#[strum(props(help = "The current number of all registered wireguard peers"))]
WireguardTotalPeers,
#[strum(props(help = "The current number of active wireguard peers"))]
WireguardActivePeers,
#[strum(props(help = "The current sending rate of wireguard"))]
WireguardBytesTxRate,
#[strum(props(help = "The current receiving rate of wireguard"))]
WireguardBytesRxRate,
// # NETWORK
#[strum(props(help = "The number of active ingress mixnet connections"))]
NetworkActiveIngressMixnetConnections,
#[strum(props(help = "The number of active ingress websocket connections"))]
NetworkActiveIngressWebSocketConnections,
#[strum(props(help = "The number of active egress mixnet connections"))]
NetworkActiveEgressMixnetConnections,
// # PROCESS
#[strum(props(help = "The current number of packets being delayed"))]
ProcessForwardHopPacketsBeingDelayed,
#[strum(props(
help = "The current number of packets waiting in the queue to get delayed and sent into the mixnet"
))]
ProcessPacketForwarderQueueSize,
#[strum(props(
help = "The latency distribution of attempting to retrieve network topology (from nym-api)"
))]
ProcessTopologyQueryResolutionLatency,
#[strum(props(
help = "The current number of final hop packets stuck in channels waiting to get delivered to appropriate websocket connections"
))]
ProcessFinalHopPacketsPendingDelivery,
#[strum(props(
help = "The current number of forward hop packets stuck in channels waiting to get delivered to appropriate TCP connections"
))]
ProcessForwardHopPacketsPendingDelivery,
}
impl PrometheusMetric {
fn name(&self) -> String {
self.to_string()
}
fn help(&self) -> &'static str {
// SAFETY: every variant has a `help` prop defined (and there's a unit test is checking for that)
#[allow(clippy::unwrap_used)]
self.get_str("help").unwrap()
}
fn is_complex(&self) -> bool {
matches!(self, PrometheusMetric::EntryClientSessionsDurations { .. })
// match self {
// PrometheusMetric::EntryClientSessionsDurations { .. } => true,
// _ => false,
// }
}
fn to_registrable_metric(&self) -> Option<Metric> {
let name = self.name();
let help = self.help();
match self {
PrometheusMetric::MixnetIngressForwardPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressMalformedPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressExcessiveDelayPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressForwardPacketsDropped => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsDropped => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressForwardPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressMalformedPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressExcessiveDelayPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressForwardPacketsDroppedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsDroppedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetEgressStoredOnDiskFinalHopPackets => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetEgressForwardPacketsSent => Metric::new_int_gauge(&name, help),
PrometheusMetric::MixnetEgressAckSent => Metric::new_int_gauge(&name, help),
PrometheusMetric::MixnetEgressForwardPacketsDropped => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetEgressForwardPacketsSendRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetEgressAckSendRate => Metric::new_float_gauge(&name, help),
PrometheusMetric::MixnetEgressForwardPacketsDroppedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::EntryClientUniqueUsers => Metric::new_int_gauge(&name, help),
PrometheusMetric::EntryClientSessionsStarted => Metric::new_int_gauge(&name, help),
PrometheusMetric::EntryClientSessionsFinished => Metric::new_int_gauge(&name, help),
PrometheusMetric::EntryClientSessionsDurations { .. } => {
Metric::new_histogram(&name, help, Some(CLIENT_SESSION_DURATION_BUCKETS))
}
PrometheusMetric::WireguardBytesTx => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardBytesRx => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardTotalPeers => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardActivePeers => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardBytesTxRate => Metric::new_float_gauge(&name, help),
PrometheusMetric::WireguardBytesRxRate => Metric::new_float_gauge(&name, help),
PrometheusMetric::NetworkActiveIngressMixnetConnections => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::NetworkActiveIngressWebSocketConnections => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::NetworkActiveEgressMixnetConnections => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::ProcessForwardHopPacketsBeingDelayed => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::ProcessPacketForwarderQueueSize => Metric::new_int_gauge(&name, help),
PrometheusMetric::ProcessTopologyQueryResolutionLatency => {
Metric::new_histogram(&name, help, None)
}
PrometheusMetric::ProcessFinalHopPacketsPendingDelivery => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::ProcessForwardHopPacketsPendingDelivery => {
Metric::new_int_gauge(&name, help)
}
}
}
fn set(&self, value: i64) {
metrics_registry().set(&self.name(), value);
}
fn set_float(&self, value: f64) {
metrics_registry().set_float(&self.name(), value);
}
fn inc(&self) {
metrics_registry().inc(&self.name());
}
fn inc_by(&self, value: i64) {
metrics_registry().inc_by(&self.name(), value);
}
fn observe_histogram(&self, value: f64) {
let reg = metrics_registry();
if !reg.add_to_histogram(&self.name(), value) {
if let Some(registrable) = self.to_registrable_metric() {
reg.register_metric(registrable);
reg.add_to_histogram(&self.name(), value);
}
}
}
fn start_timer(&self) -> Option<HistogramTimer> {
metrics_registry().start_timer(&self.name())
}
}
#[non_exhaustive]
pub struct NymNodePrometheusMetrics {}
impl NymNodePrometheusMetrics {
// initialise all fields on startup with default values so that they'd be immediately available for query
pub(crate) fn initialise() -> Self {
let registry = metrics_registry();
// we can't initialise complex metrics as their names will only be fully known at runtime
for kind in PrometheusMetric::iter() {
if !kind.is_complex() {
if let Some(metric) = kind.to_registrable_metric() {
registry.register_metric(metric);
}
}
}
NymNodePrometheusMetrics {}
}
pub fn set(&self, metric: PrometheusMetric, value: i64) {
metric.set(value)
}
pub fn set_float(&self, metric: PrometheusMetric, value: f64) {
metric.set_float(value)
}
pub fn inc(&self, metric: PrometheusMetric) {
metric.inc()
}
pub fn inc_by(&self, metric: PrometheusMetric, value: i64) {
metric.inc_by(value)
}
pub fn observe_histogram(&self, metric: PrometheusMetric, value: f64) {
metric.observe_histogram(value)
}
pub fn start_timer(&self, metric: PrometheusMetric) -> Option<HistogramTimer> {
metric.start_timer()
}
}
#[cfg(test)]
mod tests {
use super::*;
use strum::IntoEnumIterator;
#[test]
fn prometheus_metrics() {
// a sanity check for anyone adding new metrics. if this test fails,
// make sure any methods on `PrometheusMetric` enum don't need updating
// or require custom Display impl
assert_eq!(37, PrometheusMetric::COUNT)
}
#[test]
fn every_variant_has_help_property() {
for variant in PrometheusMetric::iter() {
assert!(variant.get_str("help").is_some())
}
}
#[test]
fn prometheus_metrics_names() {
// make sure nothing changed in our serialisation
let simple = PrometheusMetric::MixnetIngressForwardPacketsReceived.to_string();
assert_eq!("nym_node_mixnet_ingress_forward_packets_received", simple);
let parameterised =
PrometheusMetric::EntryClientSessionsDurations { typ: "vpn".into() }.to_string();
assert_eq!(
"nym_node_entry_client_sessions_durations_vpn",
parameterised
)
}
}