From 1cc2a29ca65e24783d26836b7ffdabcff541a928 Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii <35969035+varex83@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:27:59 +0200 Subject: [PATCH 1/3] feat: add p2p metrics --- Cargo.lock | 3 + crates/charon-p2p/Cargo.toml | 3 + crates/charon-p2p/examples/metrics.rs | 59 +++++++ crates/charon-p2p/src/lib.rs | 3 + crates/charon-p2p/src/metrics.rs | 149 ++++++++++++++++++ .../docker-compose.yml | 0 .../test-infra => test-infra}/prometheus.yml | 0 .../promtail-config.yml | 0 8 files changed, 217 insertions(+) create mode 100644 crates/charon-p2p/examples/metrics.rs create mode 100644 crates/charon-p2p/src/metrics.rs rename {crates/tracing/examples/test-infra => test-infra}/docker-compose.yml (100%) rename {crates/tracing/examples/test-infra => test-infra}/prometheus.yml (100%) rename {crates/tracing/examples/test-infra => test-infra}/promtail-config.yml (100%) diff --git a/Cargo.lock b/Cargo.lock index f853ade5..37f20a2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,6 +562,9 @@ dependencies = [ "k256", "libp2p", "thiserror 2.0.17", + "tokio", + "vise", + "vise-exporter", ] [[package]] diff --git a/crates/charon-p2p/Cargo.toml b/crates/charon-p2p/Cargo.toml index c140c7ec..274057bb 100644 --- a/crates/charon-p2p/Cargo.toml +++ b/crates/charon-p2p/Cargo.toml @@ -12,9 +12,12 @@ thiserror.workspace = true k256.workspace = true charon-eth2.workspace = true charon-k1util.workspace = true +vise.workspace = true +tokio.workspace = true [dev-dependencies] charon-testutil.workspace = true +vise-exporter.workspace = true [lints] workspace = true diff --git a/crates/charon-p2p/examples/metrics.rs b/crates/charon-p2p/examples/metrics.rs new file mode 100644 index 00000000..e9ccbb50 --- /dev/null +++ b/crates/charon-p2p/examples/metrics.rs @@ -0,0 +1,59 @@ +//! Example demonstrating the charon-p2p metrics functionality. +//! +//! To run this example, run the local Prometheus and Grafana containers: +//! ```bash +//! docker compose -f test-infra/docker-compose.yml up -d +//! ``` +//! +//! Then run the example: +//! ```bash +//! cargo run --example metrics -p charon-p2p +//! ``` +//! +//! Metrics will be available in Grafana at http://localhost:3000. + +use std::net::SocketAddr; + +use charon_p2p::metrics::{ + ConnectionType, Direction, P2P_METRICS, PeerConnectionLabels, PeerNetworkLabels, + PeerStreamLabels, Protocol, RelayConnectionLabels, +}; +use vise_exporter::MetricsExporter; + +#[tokio::main] +async fn main() { + let bind_address = SocketAddr::from(([0, 0, 0, 0], 9464)); + + let exporter = MetricsExporter::default() + .bind(bind_address) + .await + .expect("Failed to bind metrics exporter"); + tokio::spawn(async move { + exporter + .start() + .await + .expect("Failed to start metrics exporter"); + }); + + P2P_METRICS.ping_latency_secs["rustnew"].observe(1.0); + P2P_METRICS.ping_error_total["rustnew"].inc(); + P2P_METRICS.ping_success["rustnew"].set(1); + P2P_METRICS.reachability_status.set(1); + P2P_METRICS.relay_connections["rustnew"].set(1); + P2P_METRICS.peer_connection_types + [&PeerConnectionLabels::new("rustnew", ConnectionType::Direct, Protocol::Tcp)] + .set(1); + P2P_METRICS.relay_connection_types + [&RelayConnectionLabels::new("rustnew", ConnectionType::Direct, Protocol::Tcp)] + .set(1); + P2P_METRICS.peer_streams[&PeerStreamLabels::new("rustnew", Direction::Inbound, Protocol::Tcp)] + .set(1); + P2P_METRICS.peer_connection_total["rustnew"].inc(); + P2P_METRICS.peer_network_receive_bytes_total[&PeerNetworkLabels::new("rustnew", Protocol::Tcp)] + .inc(); + P2P_METRICS.peer_network_sent_bytes_total[&PeerNetworkLabels::new("rustnew", Protocol::Tcp)] + .inc(); + + // Wait for 10 seconds to see the logs in Loki + std::thread::sleep(std::time::Duration::from_secs(20)); +} diff --git a/crates/charon-p2p/src/lib.rs b/crates/charon-p2p/src/lib.rs index 63312162..98b71497 100644 --- a/crates/charon-p2p/src/lib.rs +++ b/crates/charon-p2p/src/lib.rs @@ -13,3 +13,6 @@ pub mod name; /// P2P configuration. pub mod config; + +/// Metrics. +pub mod metrics; diff --git a/crates/charon-p2p/src/metrics.rs b/crates/charon-p2p/src/metrics.rs new file mode 100644 index 00000000..bfadff76 --- /dev/null +++ b/crates/charon-p2p/src/metrics.rs @@ -0,0 +1,149 @@ +use vise::*; + +const BUCKETS: [f64; 11] = [ + 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, +]; + +/// Metrics for the P2P layer. +#[derive(Debug, Metrics)] +#[metrics(prefix = "p2p")] +pub struct P2PMetrics { + /// Ping latencies in seconds per peer + #[metrics(buckets = &BUCKETS, labels = ["peer"])] + pub ping_latency_secs: LabeledFamily, + + /// Total number of ping errors per peer + #[metrics(labels = ["peer"])] + pub ping_error_total: LabeledFamily, + + /// Whether the last ping was successful (1) or not (0). Can be used as + /// proxy for connected peers + #[metrics(labels = ["peer"])] + pub ping_success: LabeledFamily, + + /// Current libp2p reachability status of this node as detected by autonat: + /// unknown(0), public(1) or private(2). + pub reachability_status: Gauge, + + /// Connected relays by name + #[metrics(labels = ["peer"])] + pub relay_connections: LabeledFamily, + + /// Current number of libp2p connections by peer, type (`direct` or + /// `relay`), and protocol (`tcp`, `quic`). Note that peers may have + /// multiple connections. + pub peer_connection_types: Family, + + /// Current number of libp2p connections by relay, type (`direct` or + /// `relay`), and protocol (`tcp`, `quic`). Note that peers may have + /// multiple connections. + pub relay_connection_types: Family, + + /// Current number of libp2p streams by peer, direction ('inbound' or + /// 'outbound' or 'unknown') and protocol. + pub peer_streams: Family, + + /// Total number of libp2p connections per peer. + #[metrics(labels = ["peer"])] + pub peer_connection_total: LabeledFamily, + + /// Total number of network bytes received from the peer by protocol. + pub peer_network_receive_bytes_total: Family, + + /// Total number of network bytes sent to the peer by protocol. + pub peer_network_sent_bytes_total: Family, +} + +/// The type of connection. +#[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelValue)] +#[metrics(rename_all = "snake_case")] +pub enum ConnectionType { + /// A direct connection to a peer. + Direct, + /// A connection to a relay. + Relay, +} + +/// The direction of a connection. +#[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelValue)] +#[metrics(rename_all = "snake_case")] +pub enum Direction { + /// An inbound connection. + Inbound, + /// An outbound connection. + Outbound, + /// An unknown connection. + Unknown, +} + +/// The protocol of a connection. +#[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelValue)] +#[metrics(rename_all = "snake_case")] +pub enum Protocol { + /// A TCP connection. + Tcp, + /// A QUIC connection. + Quic, +} + +/// Labels for peer connections. +#[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] +pub struct PeerConnectionLabels { + peer: String, + r#type: ConnectionType, + protocol: Protocol, +} + +impl PeerConnectionLabels { + /// Creates a new peer connection labels. + pub fn new(peer: &str, r#type: ConnectionType, protocol: Protocol) -> Self { + Self { + peer: peer.to_string(), + r#type, + protocol, + } + } +} + +/// Relay connection labels +pub type RelayConnectionLabels = PeerConnectionLabels; + +/// Labels for peer streams. +#[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] +pub struct PeerStreamLabels { + peer: String, + direction: Direction, + protocol: Protocol, +} + +impl PeerStreamLabels { + /// Creates a new peer stream labels. + pub fn new(peer: &str, direction: Direction, protocol: Protocol) -> Self { + Self { + peer: peer.to_string(), + direction, + protocol, + } + } +} + +/// Labels for peer network. +#[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] +pub struct PeerNetworkLabels { + peer: String, + protocol: Protocol, +} + +impl PeerNetworkLabels { + /// Creates a new peer network labels. + pub fn new(peer: &str, protocol: Protocol) -> Self { + Self { + peer: peer.to_string(), + protocol, + } + } +} + +/// Global metrics for the P2P layer. +#[vise::register] +pub static P2P_METRICS: Global = Global::new(); diff --git a/crates/tracing/examples/test-infra/docker-compose.yml b/test-infra/docker-compose.yml similarity index 100% rename from crates/tracing/examples/test-infra/docker-compose.yml rename to test-infra/docker-compose.yml diff --git a/crates/tracing/examples/test-infra/prometheus.yml b/test-infra/prometheus.yml similarity index 100% rename from crates/tracing/examples/test-infra/prometheus.yml rename to test-infra/prometheus.yml diff --git a/crates/tracing/examples/test-infra/promtail-config.yml b/test-infra/promtail-config.yml similarity index 100% rename from crates/tracing/examples/test-infra/promtail-config.yml rename to test-infra/promtail-config.yml From c432d86745250b321f312d083c15149ae1411b8d Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii <35969035+varex83@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:34:10 +0200 Subject: [PATCH 2/3] fix: rename label in example --- crates/charon-p2p/examples/metrics.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/charon-p2p/examples/metrics.rs b/crates/charon-p2p/examples/metrics.rs index e9ccbb50..75d28fed 100644 --- a/crates/charon-p2p/examples/metrics.rs +++ b/crates/charon-p2p/examples/metrics.rs @@ -35,23 +35,23 @@ async fn main() { .expect("Failed to start metrics exporter"); }); - P2P_METRICS.ping_latency_secs["rustnew"].observe(1.0); - P2P_METRICS.ping_error_total["rustnew"].inc(); - P2P_METRICS.ping_success["rustnew"].set(1); + P2P_METRICS.ping_latency_secs["rust"].observe(1.0); + P2P_METRICS.ping_error_total["rust"].inc(); + P2P_METRICS.ping_success["rust"].set(1); P2P_METRICS.reachability_status.set(1); - P2P_METRICS.relay_connections["rustnew"].set(1); + P2P_METRICS.relay_connections["rust"].set(1); P2P_METRICS.peer_connection_types - [&PeerConnectionLabels::new("rustnew", ConnectionType::Direct, Protocol::Tcp)] + [&PeerConnectionLabels::new("rust", ConnectionType::Direct, Protocol::Tcp)] .set(1); P2P_METRICS.relay_connection_types - [&RelayConnectionLabels::new("rustnew", ConnectionType::Direct, Protocol::Tcp)] + [&RelayConnectionLabels::new("rust", ConnectionType::Direct, Protocol::Tcp)] .set(1); - P2P_METRICS.peer_streams[&PeerStreamLabels::new("rustnew", Direction::Inbound, Protocol::Tcp)] + P2P_METRICS.peer_streams[&PeerStreamLabels::new("rust", Direction::Inbound, Protocol::Tcp)] .set(1); - P2P_METRICS.peer_connection_total["rustnew"].inc(); - P2P_METRICS.peer_network_receive_bytes_total[&PeerNetworkLabels::new("rustnew", Protocol::Tcp)] + P2P_METRICS.peer_connection_total["rust"].inc(); + P2P_METRICS.peer_network_receive_bytes_total[&PeerNetworkLabels::new("rust", Protocol::Tcp)] .inc(); - P2P_METRICS.peer_network_sent_bytes_total[&PeerNetworkLabels::new("rustnew", Protocol::Tcp)] + P2P_METRICS.peer_network_sent_bytes_total[&PeerNetworkLabels::new("rust", Protocol::Tcp)] .inc(); // Wait for 10 seconds to see the logs in Loki From aeaa9710c3ea1e033419bef672db170e74f0341e Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii <35969035+varex83@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:35:35 +0200 Subject: [PATCH 3/3] fix: linting --- crates/charon-p2p/examples/metrics.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/charon-p2p/examples/metrics.rs b/crates/charon-p2p/examples/metrics.rs index 75d28fed..99472c53 100644 --- a/crates/charon-p2p/examples/metrics.rs +++ b/crates/charon-p2p/examples/metrics.rs @@ -51,8 +51,7 @@ async fn main() { P2P_METRICS.peer_connection_total["rust"].inc(); P2P_METRICS.peer_network_receive_bytes_total[&PeerNetworkLabels::new("rust", Protocol::Tcp)] .inc(); - P2P_METRICS.peer_network_sent_bytes_total[&PeerNetworkLabels::new("rust", Protocol::Tcp)] - .inc(); + P2P_METRICS.peer_network_sent_bytes_total[&PeerNetworkLabels::new("rust", Protocol::Tcp)].inc(); // Wait for 10 seconds to see the logs in Loki std::thread::sleep(std::time::Duration::from_secs(20));