Skip to content

Commit

Permalink
feature: wireguard metrics (#5278)
Browse files Browse the repository at this point in the history
* experimental log

* introduce wireguard metrics updates

* add wireguard traffic rates to console logger

* missing import

* changed order of displayed values

* expose bytes information via rest endpoint

* clippy
  • Loading branch information
jstuczyn authored Dec 19, 2024
1 parent a2322d6 commit 67976b1
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions common/wireguard/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ nym-gateway-storage = { path = "../gateway-storage" }
nym-network-defaults = { path = "../network-defaults" }
nym-task = { path = "../task" }
nym-wireguard-types = { path = "../wireguard-types" }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
2 changes: 2 additions & 0 deletions common/wireguard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub struct WireguardData {
#[cfg(target_os = "linux")]
pub async fn start_wireguard(
storage: nym_gateway_storage::GatewayStorage,
metrics: nym_node_metrics::NymNodeMetrics,
all_peers: Vec<nym_gateway_storage::models::WireguardPeer>,
task_client: nym_task::TaskClient,
wireguard_data: WireguardData,
Expand Down Expand Up @@ -175,6 +176,7 @@ pub async fn start_wireguard(
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new(
storage,
metrics,
wg_api.clone(),
host,
peer_bandwidth_managers,
Expand Down
52 changes: 52 additions & 0 deletions common/wireguard/src/peer_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use nym_credential_verification::{
ClientBandwidth,
};
use nym_gateway_storage::GatewayStorage;
use nym_node_metrics::NymNodeMetrics;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::time::{Duration, SystemTime};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
Expand Down Expand Up @@ -65,6 +67,11 @@ pub struct QueryBandwidthControlResponse {

pub struct PeerController {
storage: GatewayStorage,

// we have "all" metrics of a node, but they're behind a single Arc pointer,
// so the overhead is minimal
metrics: NymNodeMetrics,

// used to receive commands from individual handles too
request_tx: mpsc::Sender<PeerControlRequest>,
request_rx: mpsc::Receiver<PeerControlRequest>,
Expand All @@ -76,8 +83,10 @@ pub struct PeerController {
}

impl PeerController {
#[allow(clippy::too_many_arguments)]
pub fn new(
storage: GatewayStorage,
metrics: NymNodeMetrics,
wg_api: Arc<WgApiWrapper>,
initial_host_information: Host,
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager>, Peer)>,
Expand Down Expand Up @@ -123,6 +132,7 @@ impl PeerController {
request_rx,
timeout_check_interval,
task_client,
metrics,
}
}

Expand Down Expand Up @@ -257,6 +267,46 @@ impl PeerController {
}))
}

fn update_metrics(&self, new_host: &Host) {
let now = SystemTime::now();
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);

let total_peers = new_host.peers.len();
let mut active_peers = 0;
let mut total_rx = 0;
let mut total_tx = 0;

for peer in new_host.peers.values() {
total_rx += peer.rx_bytes;
total_tx += peer.tx_bytes;

// if a peer hasn't performed a handshake in last minute,
// I think it's reasonable to assume it's no longer active
let Some(last_handshake) = peer.last_handshake else {
continue;
};
let Ok(elapsed) = now.duration_since(last_handshake) else {
continue;
};
if elapsed < ACTIVITY_THRESHOLD {
active_peers += 1;
}
}

self.metrics.wireguard.update(
// if the conversion fails it means we're running not running on a 64bit system
// and that's a reason enough for this failure.
total_rx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_tx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_peers,
active_peers,
);
}

pub async fn run(&mut self) {
info!("started wireguard peer controller");
loop {
Expand All @@ -266,6 +316,8 @@ impl PeerController {
log::error!("Can't read wireguard kernel data");
continue;
};
self.update_metrics(&host);

*self.host_information.write().await = host;
}
_ = self.task_client.recv() => {
Expand Down
8 changes: 8 additions & 0 deletions gateway/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mod internal_service_providers;
pub use client_handling::active_clients::ActiveClientsStore;
pub use nym_gateway_stats_storage::PersistentStatsStorage;
pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage};
use nym_node_metrics::NymNodeMetrics;
pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -81,6 +82,8 @@ pub struct GatewayTasksBuilder {

metrics_sender: MetricEventsSender,

metrics: NymNodeMetrics,

mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,

shutdown: TaskClient,
Expand All @@ -102,12 +105,14 @@ impl Drop for GatewayTasksBuilder {
}

impl GatewayTasksBuilder {
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
identity: Arc<ed25519::KeyPair>,
storage: GatewayStorage,
mix_packet_sender: MixForwardingSender,
metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
) -> GatewayTasksBuilder {
Expand All @@ -121,6 +126,7 @@ impl GatewayTasksBuilder {
storage,
mix_packet_sender,
metrics_sender,
metrics,
mnemonic,
shutdown,
ecash_manager: None,
Expand Down Expand Up @@ -443,6 +449,7 @@ impl GatewayTasksBuilder {
pub async fn try_start_wireguard(
&mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
let _ = self.metrics.clone();
unimplemented!("wireguard is not supported on this platform")
}

Expand All @@ -460,6 +467,7 @@ impl GatewayTasksBuilder {

let wg_handle = nym_wireguard::start_wireguard(
self.storage.clone(),
self.metrics.clone(),
all_peers,
self.shutdown.fork("wireguard"),
wireguard_data,
Expand Down
3 changes: 3 additions & 0 deletions nym-node/nym-node-metrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
use crate::entry::EntryStats;
use crate::mixnet::MixingStats;
use crate::network::NetworkStats;
use crate::wireguard::WireguardStats;
use std::ops::Deref;
use std::sync::Arc;

pub mod entry;
pub mod events;
pub mod mixnet;
pub mod network;
pub mod wireguard;

#[derive(Clone, Default)]
pub struct NymNodeMetrics {
Expand All @@ -34,6 +36,7 @@ impl Deref for NymNodeMetrics {
pub struct NymNodeMetricsInner {
pub mixnet: MixingStats,
pub entry: EntryStats,
pub wireguard: WireguardStats,

pub network: NetworkStats,
}
44 changes: 44 additions & 0 deletions nym-node/nym-node-metrics/src/wireguard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use std::sync::atomic::{AtomicUsize, Ordering};

#[derive(Default)]
pub struct WireguardStats {
bytes_rx: AtomicUsize,
bytes_tx: AtomicUsize,

total_peers: AtomicUsize,
active_peers: AtomicUsize,
}

impl WireguardStats {
pub fn bytes_rx(&self) -> usize {
self.bytes_rx.load(Ordering::Relaxed)
}

pub fn bytes_tx(&self) -> usize {
self.bytes_tx.load(Ordering::Relaxed)
}

pub fn total_peers(&self) -> usize {
self.total_peers.load(Ordering::Relaxed)
}

pub fn active_peers(&self) -> usize {
self.active_peers.load(Ordering::Relaxed)
}

pub fn update(
&self,
bytes_rx: usize,
bytes_tx: usize,
total_peers: usize,
active_peers: usize,
) {
self.bytes_rx.store(bytes_rx, Ordering::Relaxed);
self.bytes_tx.store(bytes_tx, Ordering::Relaxed);
self.total_peers.store(total_peers, Ordering::Relaxed);
self.active_peers.store(active_peers, Ordering::Relaxed);
}
}
13 changes: 13 additions & 0 deletions nym-node/nym-node-requests/src/api/v1/metrics/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
pub use mixing::*;
pub use session::*;
pub use verloc::*;
pub use wireguard::*;

pub mod wireguard {
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct WireguardStats {
pub bytes_tx: usize,

pub bytes_rx: usize,
}
}

pub mod packets {
use serde::{Deserialize, Serialize};
Expand Down
6 changes: 6 additions & 0 deletions nym-node/nym-node-requests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,18 @@ pub mod routes {

pub const LEGACY_MIXING: &str = "/mixing";
pub const PACKETS_STATS: &str = "/packets-stats";
pub const WIREGUARD_STATS: &str = "/wireguard-stats";
pub const SESSIONS: &str = "/sessions";
pub const VERLOC: &str = "/verloc";
pub const PROMETHEUS: &str = "/prometheus";

absolute_route!(legacy_mixing_absolute, metrics_absolute(), LEGACY_MIXING);
absolute_route!(packets_stats_absolute, metrics_absolute(), PACKETS_STATS);
absolute_route!(
wireguard_stats_absolute,
metrics_absolute(),
WIREGUARD_STATS
);
absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS);
absolute_route!(verloc_absolute, metrics_absolute(), VERLOC);
absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS);
Expand Down
3 changes: 3 additions & 0 deletions nym-node/src/node/http/router/api/v1/metrics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::node::http::api::v1::metrics::packets_stats::packets_stats;
use crate::node::http::api::v1::metrics::prometheus::prometheus_metrics;
use crate::node::http::api::v1::metrics::sessions::sessions_stats;
use crate::node::http::api::v1::metrics::verloc::verloc_stats;
use crate::node::http::api::v1::metrics::wireguard::wireguard_stats;
use crate::node::http::state::metrics::MetricsAppState;
use axum::extract::FromRef;
use axum::routing::get;
Expand All @@ -16,6 +17,7 @@ pub mod packets_stats;
pub mod prometheus;
pub mod sessions;
pub mod verloc;
pub mod wireguard;

#[derive(Debug, Clone, Default)]
pub struct Config {
Expand All @@ -34,6 +36,7 @@ where
get(legacy_mixing::legacy_mixing_stats),
)
.route(metrics::PACKETS_STATS, get(packets_stats))
.route(metrics::WIREGUARD_STATS, get(wireguard_stats))
.route(metrics::SESSIONS, get(sessions_stats))
.route(metrics::VERLOC, get(verloc_stats))
.route(metrics::PROMETHEUS, get(prometheus_metrics))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: GPL-3.0-only

use crate::node::http::state::metrics::MetricsAppState;
use axum::extract::{Query, State};
Expand Down
40 changes: 40 additions & 0 deletions nym-node/src/node/http/router/api/v1/metrics/wireguard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only

use crate::node::http::state::metrics::MetricsAppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_metrics::NymNodeMetrics;
use nym_node_requests::api::v1::metrics::models::WireguardStats;

/// If applicable, returns wireguard statistics information of this node.
/// This information is **PURELY** self-reported and in no way validated.
#[utoipa::path(
get,
path = "/wireguard-stats",
context_path = "/api/v1/metrics",
tag = "Metrics",
responses(
(status = 200, content(
("application/json" = WireguardStats),
("application/yaml" = WireguardStats)
))
),
params(OutputParams),
)]
pub(crate) async fn wireguard_stats(
Query(output): Query<OutputParams>,
State(metrics_state): State<MetricsAppState>,
) -> WireguardStatsResponse {
let output = output.output.unwrap_or_default();
output.to_response(build_response(&metrics_state.metrics))
}

fn build_response(metrics: &NymNodeMetrics) -> WireguardStats {
WireguardStats {
bytes_tx: metrics.wireguard.bytes_tx(),
bytes_rx: metrics.wireguard.bytes_rx(),
}
}

pub type WireguardStatsResponse = FormattedResponse<WireguardStats>;
Loading

0 comments on commit 67976b1

Please sign in to comment.