use prometheus_client::metrics::counter::Counter;
use prometheus_client::metrics::gauge::Gauge;
use prometheus_client::registry::Registry;
use std::sync::Arc;
#[derive(Clone)]
pub struct NetworkMetrics {
pub events_dropped: Counter,
pub dials_rejected_per_ip: Counter,
pub dials_rejected_global: Counter,
pub gossip_rejected_validator_only: Counter,
pub gossip_rejected_invalid: Counter,
pub gossip_rejected_duplicate: Counter,
pub gossip_published: Counter,
pub gossip_accepted: Counter,
pub connections_established: Gauge,
pub connections_inbound_total: Counter,
pub connections_outbound_total: Counter,
pub peers_banned: Gauge,
pub peers_connected: Gauge,
pub kad_routing_table_size: Gauge,
pub gossipsub_mesh_size: Gauge,
pub peer_address_migrations_total: Counter,
}
impl NetworkMetrics {
pub fn register(registry: &mut Registry) -> Arc<Self> {
let sub = registry.sub_registry_with_prefix("tenzro_network");
let events_dropped = Counter::default();
sub.register(
"events_dropped",
"Swarm events dropped due to event channel back-pressure",
events_dropped.clone(),
);
let dials_rejected_per_ip = Counter::default();
sub.register(
"dials_rejected_per_ip",
"Inbound dials rejected due to per-IP rate limit",
dials_rejected_per_ip.clone(),
);
let dials_rejected_global = Counter::default();
sub.register(
"dials_rejected_global",
"Inbound dials rejected due to global dial rate limit",
dials_rejected_global.clone(),
);
let gossip_rejected_validator_only = Counter::default();
sub.register(
"gossip_rejected_validator_only",
"Gossip messages rejected because publisher is not a known validator",
gossip_rejected_validator_only.clone(),
);
let gossip_rejected_invalid = Counter::default();
sub.register(
"gossip_rejected_invalid",
"Gossip messages rejected by structural validation",
gossip_rejected_invalid.clone(),
);
let gossip_rejected_duplicate = Counter::default();
sub.register(
"gossip_rejected_duplicate",
"Gossip messages rejected as application-level duplicates",
gossip_rejected_duplicate.clone(),
);
let gossip_published = Counter::default();
sub.register(
"gossip_published",
"Gossip messages successfully published",
gossip_published.clone(),
);
let gossip_accepted = Counter::default();
sub.register(
"gossip_accepted",
"Gossip messages received and accepted",
gossip_accepted.clone(),
);
let connections_established = Gauge::default();
sub.register(
"connections_established",
"Current established libp2p connections",
connections_established.clone(),
);
let connections_inbound_total = Counter::default();
sub.register(
"connections_inbound_total",
"Total inbound libp2p connections accepted",
connections_inbound_total.clone(),
);
let connections_outbound_total = Counter::default();
sub.register(
"connections_outbound_total",
"Total outbound libp2p connections established",
connections_outbound_total.clone(),
);
let peers_banned = Gauge::default();
sub.register(
"peers_banned",
"Peers currently in the banned state",
peers_banned.clone(),
);
let peers_connected = Gauge::default();
sub.register(
"peers_connected",
"Peers currently connected",
peers_connected.clone(),
);
let kad_routing_table_size = Gauge::default();
sub.register(
"kad_routing_table_size",
"Entries in the Kademlia k-bucket routing table",
kad_routing_table_size.clone(),
);
let gossipsub_mesh_size = Gauge::default();
sub.register(
"gossipsub_mesh_size",
"Aggregate gossipsub mesh peer count across all subscribed topics",
gossipsub_mesh_size.clone(),
);
let peer_address_migrations_total = Counter::default();
sub.register(
"peer_address_migrations_total",
"Peer remote-address migrations observed (QUIC path migration, NAT rebinding)",
peer_address_migrations_total.clone(),
);
Arc::new(Self {
events_dropped,
dials_rejected_per_ip,
dials_rejected_global,
gossip_rejected_validator_only,
gossip_rejected_invalid,
gossip_rejected_duplicate,
gossip_published,
gossip_accepted,
connections_established,
connections_inbound_total,
connections_outbound_total,
peers_banned,
peers_connected,
kad_routing_table_size,
gossipsub_mesh_size,
peer_address_migrations_total,
})
}
pub fn unregistered() -> Arc<Self> {
let mut registry = Registry::default();
Self::register(&mut registry)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_all_metrics() {
let mut registry = Registry::default();
let metrics = NetworkMetrics::register(&mut registry);
assert_eq!(metrics.events_dropped.get(), 0);
assert_eq!(metrics.dials_rejected_per_ip.get(), 0);
assert_eq!(metrics.gossip_published.get(), 0);
metrics.events_dropped.inc();
assert_eq!(metrics.events_dropped.get(), 1);
}
#[test]
fn test_unregistered_metrics() {
let metrics = NetworkMetrics::unregistered();
metrics.gossip_published.inc();
metrics.peers_connected.set(42);
assert_eq!(metrics.gossip_published.get(), 1);
assert_eq!(metrics.peers_connected.get(), 42);
}
#[test]
fn test_metrics_clone_shares_state() {
let metrics = NetworkMetrics::unregistered();
let metrics_clone = metrics.clone();
metrics.events_dropped.inc();
metrics.events_dropped.inc();
assert_eq!(metrics_clone.events_dropped.get(), 2);
}
}