Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: wireguard metrics #5278

Merged
merged 7 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading