From 435f236812ec8ab4a2eae0753ba0c7440eef6c38 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Tue, 15 Oct 2024 09:18:02 +0200 Subject: [PATCH] [Product Data] First step in gateway usage data collection (#4963) * add stats model * add stats collection * add stats route * propagate stuff and run stuff * cargo stuff * sqlx unused what? * add sessions started stat * session durations in miliseconds * apply Jon's comments * [Product Data] Second step in gateway usage data collection (#4964) * turn stats collection into event based * move events into a common crate for future use elsewhere * apply Jon's comments --- Cargo.lock | 11 +++ Cargo.toml | 1 + common/statistics/Cargo.toml | 16 ++++ common/statistics/src/events.rs | 39 +++++++++ common/statistics/src/lib.rs | 4 + gateway/Cargo.toml | 9 +- .../node/client_handling/active_clients.rs | 18 +++- gateway/src/node/mod.rs | 32 ++++++- gateway/src/node/statistics/mod.rs | 63 ++++++++++++++ gateway/src/node/statistics/sessions.rs | 86 +++++++++++++++++++ .../src/router/api/v1/metrics/mod.rs | 3 + .../src/router/api/v1/metrics/sessions.rs | 33 +++++++ .../nym-node-http-api/src/state/metrics.rs | 59 ++++++++++++- nym-node/nym-node-http-api/src/state/mod.rs | 10 ++- .../src/api/v1/metrics/models.rs | 15 ++++ nym-node/nym-node-requests/src/lib.rs | 2 + nym-node/src/node/mod.rs | 6 +- 17 files changed, 394 insertions(+), 13 deletions(-) create mode 100644 common/statistics/Cargo.toml create mode 100644 common/statistics/src/events.rs create mode 100644 common/statistics/src/lib.rs create mode 100644 gateway/src/node/statistics/mod.rs create mode 100644 gateway/src/node/statistics/sessions.rs create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs diff --git a/Cargo.lock b/Cargo.lock index a35a6acaaf5..f2864c02279 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5219,6 +5219,7 @@ dependencies = [ "nym-node-http-api", "nym-pemstore", "nym-sphinx", + "nym-statistics-common", "nym-task", "nym-types", "nym-validator-client", @@ -5232,6 +5233,7 @@ dependencies = [ "sqlx", "subtle-encoding", "thiserror", + "time", "tokio", "tokio-stream", "tokio-tungstenite", @@ -6283,6 +6285,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-statistics-common" +version = "0.1.0" +dependencies = [ + "futures", + "nym-sphinx", + "time", +] + [[package]] name = "nym-store-cipher" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 04e5f5c8e8c..f7fdcd17166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ members = [ "common/socks5-client-core", "common/socks5/proxy-helpers", "common/socks5/requests", + "common/statistics", "common/store-cipher", "common/task", "common/topology", diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml new file mode 100644 index 00000000000..e07c8630655 --- /dev/null +++ b/common/statistics/Cargo.toml @@ -0,0 +1,16 @@ +# Copyright 2024 - Nym Technologies SA +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "nym-statistics-common" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = { workspace = true } +time = { workspace = true } + +nym-sphinx = { path = "../nymsphinx" } diff --git a/common/statistics/src/events.rs b/common/statistics/src/events.rs new file mode 100644 index 00000000000..12fc0b209c2 --- /dev/null +++ b/common/statistics/src/events.rs @@ -0,0 +1,39 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use futures::channel::mpsc; +use nym_sphinx::DestinationAddressBytes; +use time::OffsetDateTime; + +pub type StatsEventSender = mpsc::UnboundedSender; +pub type StatsEventReceiver = mpsc::UnboundedReceiver; +pub enum StatsEvent { + SessionStatsEvent(SessionEvent), +} + +impl StatsEvent { + pub fn new_session_start(client: DestinationAddressBytes) -> StatsEvent { + StatsEvent::SessionStatsEvent(SessionEvent::SessionStart { + start_time: OffsetDateTime::now_utc(), + client, + }) + } + + pub fn new_session_stop(client: DestinationAddressBytes) -> StatsEvent { + StatsEvent::SessionStatsEvent(SessionEvent::SessionStop { + stop_time: OffsetDateTime::now_utc(), + client, + }) + } +} + +pub enum SessionEvent { + SessionStart { + start_time: OffsetDateTime, + client: DestinationAddressBytes, + }, + SessionStop { + stop_time: OffsetDateTime, + client: DestinationAddressBytes, + }, +} diff --git a/common/statistics/src/lib.rs b/common/statistics/src/lib.rs new file mode 100644 index 00000000000..222251db6c6 --- /dev/null +++ b/common/statistics/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod events; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index cbdcfc1ad2f..f910f13b6e2 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -41,15 +41,9 @@ rand = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } si-scale = { workspace = true } -sqlx = { workspace = true, features = [ - "runtime-tokio-rustls", - "sqlite", - "macros", - "migrate", - "time", -] } subtle-encoding = { workspace = true, features = ["bech32-preview"] } thiserror = { workspace = true } +time = { workspace = true } tokio = { workspace = true, features = [ "rt-multi-thread", "net", @@ -83,6 +77,7 @@ nym-network-requester = { path = "../service-providers/network-requester" } nym-node-http-api = { path = "../nym-node/nym-node-http-api" } nym-pemstore = { path = "../common/pemstore" } nym-sphinx = { path = "../common/nymsphinx" } +nym-statistics-common = { path = "../common/statistics" } nym-task = { path = "../common/task" } nym-types = { path = "../common/types" } nym-validator-client = { path = "../common/client-libs/validator-client" } diff --git a/gateway/src/node/client_handling/active_clients.rs b/gateway/src/node/client_handling/active_clients.rs index 962765067e5..82cdf98dc31 100644 --- a/gateway/src/node/client_handling/active_clients.rs +++ b/gateway/src/node/client_handling/active_clients.rs @@ -5,6 +5,8 @@ use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender use crate::node::client_handling::embedded_clients::LocalEmbeddedClientHandle; use dashmap::DashMap; use nym_sphinx::DestinationAddressBytes; +use nym_statistics_common::events; +use nym_statistics_common::events::StatsEventSender; use std::sync::Arc; use tracing::warn; @@ -35,6 +37,7 @@ impl ActiveClient { #[derive(Clone)] pub(crate) struct ActiveClientsStore { inner: Arc>, + stats_event_sender: StatsEventSender, } #[derive(Clone)] @@ -48,9 +51,10 @@ pub(crate) struct ClientIncomingChannels { impl ActiveClientsStore { /// Creates new instance of `ActiveClientsStore` to store in-memory handles to all currently connected clients. - pub(crate) fn new() -> Self { + pub(crate) fn new(stats_event_sender: StatsEventSender) -> Self { ActiveClientsStore { inner: Arc::new(DashMap::new()), + stats_event_sender, } } @@ -126,6 +130,12 @@ impl ActiveClientsStore { /// * `client`: address of the client for which to remove the handle. pub(crate) fn disconnect(&self, client: DestinationAddressBytes) { self.inner.remove(&client); + if let Err(e) = self + .stats_event_sender + .unbounded_send(events::StatsEvent::new_session_stop(client)) + { + warn!("Failed to send session stop event to collector : {e}") + }; } /// Insert new client handle into the store. @@ -147,6 +157,12 @@ impl ActiveClientsStore { if self.inner.insert(client, entry).is_some() { panic!("inserted a duplicate remote client") } + if let Err(e) = self + .stats_event_sender + .unbounded_send(events::StatsEvent::new_session_start(client)) + { + warn!("Failed to send session start event to collector : {e}") + }; } /// Inserts a handle to the embedded client diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index bf5c3f0ab0a..b06878c42fb 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -22,12 +22,15 @@ use nym_crypto::asymmetric::{encryption, identity}; use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; use nym_network_defaults::NymNetworkDetails; use nym_network_requester::{LocalGateway, NRServiceProviderBuilder, RequestFilter}; +use nym_node_http_api::state::metrics::SharedSessionStats; +use nym_statistics_common::events; use nym_task::{TaskClient, TaskHandle, TaskManager}; use nym_types::gateway::GatewayNodeDetailsResponse; use nym_validator_client::nyxd::{Coin, CosmWasmClient}; use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; use rand::seq::SliceRandom; use rand::thread_rng; +use statistics::GatewayStatisticsCollector; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; @@ -36,6 +39,7 @@ use tracing::*; pub(crate) mod client_handling; pub(crate) mod helpers; pub(crate) mod mixnet_handling; +pub(crate) mod statistics; pub use nym_gateway_storage::{PersistentStorage, Storage}; @@ -147,6 +151,8 @@ pub struct Gateway { wireguard_data: Option, + session_stats: Option, + run_http_server: bool, task_client: Option, } @@ -168,6 +174,7 @@ impl Gateway { ip_packet_router_opts, authenticator_opts: None, wireguard_data: None, + session_stats: None, run_http_server: true, task_client: None, }) @@ -191,6 +198,7 @@ impl Gateway { sphinx_keypair, storage, wireguard_data: None, + session_stats: None, run_http_server: true, task_client: None, } @@ -204,6 +212,10 @@ impl Gateway { self.task_client = Some(task_client) } + pub fn set_session_stats(&mut self, session_stats: SharedSessionStats) { + self.session_stats = Some(session_stats); + } + pub fn set_wireguard_data(&mut self, wireguard_data: nym_wireguard::WireguardData) { self.wireguard_data = Some(wireguard_data) } @@ -393,6 +405,19 @@ impl Gateway { packet_sender } + fn start_stats_collector( + &self, + shared_session_stats: SharedSessionStats, + shutdown: TaskClient, + ) -> events::StatsEventSender { + info!("Starting gateway stats collector..."); + + let (mut stats_collector, stats_event_sender) = + GatewayStatisticsCollector::new(shared_session_stats); + tokio::spawn(async move { stats_collector.run(shutdown).await }); + stats_event_sender + } + // TODO: rethink the logic in this function... async fn start_network_requester( &self, @@ -599,6 +624,11 @@ impl Gateway { return Err(GatewayError::InsufficientNodeBalance { account, balance }); } } + let shared_session_stats = self.session_stats.take().unwrap_or_default(); + let stats_event_sender = self.start_stats_collector( + shared_session_stats, + shutdown.fork("statistics::GatewayStatisticsCollector"), + ); let handler_config = CredentialHandlerConfig { revocation_bandwidth_penalty: self @@ -629,7 +659,7 @@ impl Gateway { let mix_forwarding_channel = self.start_packet_forwarder(shutdown.fork("PacketForwarder")); - let active_clients_store = ActiveClientsStore::new(); + let active_clients_store = ActiveClientsStore::new(stats_event_sender.clone()); self.start_mix_socket_listener( mix_forwarding_channel.clone(), active_clients_store.clone(), diff --git a/gateway/src/node/statistics/mod.rs b/gateway/src/node/statistics/mod.rs new file mode 100644 index 00000000000..53ea277db55 --- /dev/null +++ b/gateway/src/node/statistics/mod.rs @@ -0,0 +1,63 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use futures::{channel::mpsc, StreamExt}; +use nym_node_http_api::state::metrics::SharedSessionStats; +use nym_statistics_common::events::{StatsEvent, StatsEventReceiver, StatsEventSender}; +use nym_task::TaskClient; +use sessions::SessionStatsHandler; +use std::time::Duration; +use time::OffsetDateTime; +use tracing::trace; + +pub mod sessions; + +const STATISTICS_UPDATE_TIMER_INTERVAL: Duration = Duration::from_secs(3600); //update timer, no need to check everytime + +pub(crate) struct GatewayStatisticsCollector { + stats_event_rx: StatsEventReceiver, + session_stats: SessionStatsHandler, + //here goes additionnal stats handler +} + +impl GatewayStatisticsCollector { + pub fn new( + shared_session_stats: SharedSessionStats, + ) -> (GatewayStatisticsCollector, StatsEventSender) { + let (stats_event_tx, stats_event_rx) = mpsc::unbounded(); + let collector = GatewayStatisticsCollector { + stats_event_rx, + session_stats: SessionStatsHandler::new(shared_session_stats), + }; + (collector, stats_event_tx) + } + + async fn update_shared_state(&mut self, update_time: OffsetDateTime) { + self.session_stats.update_shared_state(update_time).await; + //here goes additionnal stats handler update + } + + pub async fn run(&mut self, mut shutdown: TaskClient) { + let mut update_interval = tokio::time::interval(STATISTICS_UPDATE_TIMER_INTERVAL); + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + trace!("StatisticsCollector: Received shutdown"); + }, + _ = update_interval.tick() => { + let now = OffsetDateTime::now_utc(); + self.update_shared_state(now).await; + }, + + Some(stat_event) = self.stats_event_rx.next() => { + //dispatching event to proper handler + match stat_event { + StatsEvent::SessionStatsEvent(event) => self.session_stats.handle_event(event), + } + }, + + } + } + } +} diff --git a/gateway/src/node/statistics/sessions.rs b/gateway/src/node/statistics/sessions.rs new file mode 100644 index 00000000000..1f58bc1223d --- /dev/null +++ b/gateway/src/node/statistics/sessions.rs @@ -0,0 +1,86 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_node_http_api::state::metrics::SharedSessionStats; +use nym_sphinx::DestinationAddressBytes; +use std::collections::{HashMap, HashSet}; +use time::{Date, OffsetDateTime}; + +use nym_statistics_common::events::SessionEvent; + +type SessionDuration = u64; //in miliseconds + +pub(crate) struct SessionStatsHandler { + last_update_day: Date, + + shared_session_stats: SharedSessionStats, + active_sessions: HashMap, + unique_users: HashSet, + sessions_started: u32, + finished_sessions: Vec, +} + +impl SessionStatsHandler { + pub fn new(shared_session_stats: SharedSessionStats) -> Self { + SessionStatsHandler { + last_update_day: OffsetDateTime::now_utc().date(), + shared_session_stats, + active_sessions: Default::default(), + unique_users: Default::default(), + sessions_started: 0, + finished_sessions: Default::default(), + } + } + + pub(crate) fn handle_event(&mut self, event: SessionEvent) { + match event { + SessionEvent::SessionStart { start_time, client } => { + self.handle_session_start(start_time, client); + } + SessionEvent::SessionStop { stop_time, client } => { + self.handle_session_stop(stop_time, client); + } + } + } + fn handle_session_start( + &mut self, + start_time: OffsetDateTime, + client: DestinationAddressBytes, + ) { + self.sessions_started += 1; + self.unique_users.insert(client); + self.active_sessions.insert(client, start_time); + } + fn handle_session_stop(&mut self, stop_time: OffsetDateTime, client: DestinationAddressBytes) { + if let Some(session_start) = self.active_sessions.remove(&client) { + let session_duration = (stop_time - session_start).whole_milliseconds(); + + //this should always happen because it should always be positive and u64::max milliseconds is 500k millenia, but anyway + if let Ok(duration_u64) = session_duration.try_into() { + self.finished_sessions.push(duration_u64); + } + } + } + + //update shared state once a day has passed, with data from the previous day + pub(crate) async fn update_shared_state(&mut self, update_time: OffsetDateTime) { + let update_date = update_time.date(); + if update_date != self.last_update_day { + { + let mut shared_state = self.shared_session_stats.write().await; + shared_state.update_time = self.last_update_day; + shared_state.unique_active_users = self.unique_users.len() as u32; + shared_state.session_started = self.sessions_started; + shared_state.session_durations = self.finished_sessions.clone(); + } + self.reset_stats(update_date); + } + } + + fn reset_stats(&mut self, reset_day: Date) { + self.last_update_day = reset_day; + self.unique_users = self.active_sessions.keys().copied().collect(); + self.finished_sessions = Default::default(); + self.sessions_started = 0; + } +} diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs index 7f3dc9151ef..cabf4aedf52 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs @@ -3,6 +3,7 @@ use crate::api::v1::metrics::mixing::mixing_stats; use crate::api::v1::metrics::prometheus::prometheus_metrics; +use crate::api::v1::metrics::sessions::sessions_stats; use crate::api::v1::metrics::verloc::verloc_stats; use crate::state::metrics::MetricsAppState; use axum::extract::FromRef; @@ -12,6 +13,7 @@ use nym_node_requests::routes::api::v1::metrics; pub mod mixing; pub mod prometheus; +pub mod sessions; pub mod verloc; #[derive(Debug, Clone, Default)] @@ -26,6 +28,7 @@ where { Router::new() .route(metrics::MIXING, get(mixing_stats)) + .route(metrics::SESSIONS, get(sessions_stats)) .route(metrics::VERLOC, get(verloc_stats)) .route(metrics::PROMETHEUS, get(prometheus_metrics)) } diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs new file mode 100644 index 00000000000..59eceb8f89c --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/sessions.rs @@ -0,0 +1,33 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_requests::api::v1::metrics::models::SessionStats; + +/// If applicable, returns sessions statistics information of this node. +/// This information is **PURELY** self-reported and in no way validated. +#[utoipa::path( + get, + path = "/sessions", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = SessionStats), + ("application/yaml" = SessionStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn sessions_stats( + Query(output): Query, + State(metrics_state): State, +) -> SessionStatsResponse { + let output = output.output.unwrap_or_default(); + let response = metrics_state.session_stats.read().await.as_response(); + output.to_response(response) +} + +pub type SessionStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/state/metrics.rs b/nym-node/nym-node-http-api/src/state/metrics.rs index d4691727d98..75616cf8850 100644 --- a/nym-node/nym-node-http-api/src/state/metrics.rs +++ b/nym-node/nym-node-http-api/src/state/metrics.rs @@ -4,11 +4,12 @@ use crate::state::AppState; use axum::extract::FromRef; use nym_node_requests::api::v1::metrics::models::{ - MixingStats, VerlocResult, VerlocResultData, VerlocStats, + MixingStats, SessionStats, VerlocResult, VerlocResultData, VerlocStats, }; use std::collections::HashMap; use std::sync::Arc; -use time::OffsetDateTime; +use time::macros::time; +use time::{Date, OffsetDateTime}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; pub use nym_node_requests::api::v1::metrics::models::{VerlocMeasurement, VerlocNodeResult}; @@ -132,12 +133,66 @@ impl VerlocStatsState { } } +#[derive(Clone, Debug, Default)] +pub struct SharedSessionStats { + inner: Arc>, +} + +impl SharedSessionStats { + pub fn new() -> SharedSessionStats { + SharedSessionStats { + inner: Arc::new(RwLock::new(Default::default())), + } + } + + pub async fn read(&self) -> RwLockReadGuard<'_, SessionStatsState> { + self.inner.read().await + } + + pub async fn write(&self) -> RwLockWriteGuard<'_, SessionStatsState> { + self.inner.write().await + } +} + +#[derive(Debug, Clone)] +pub struct SessionStatsState { + pub update_time: Date, + pub unique_active_users: u32, + pub session_started: u32, + pub session_durations: Vec, +} + +impl SessionStatsState { + pub fn as_response(&self) -> SessionStats { + SessionStats { + update_time: self.update_time.with_time(time!(0:00)).assume_utc(), + unique_active_users: self.unique_active_users, + session_durations: self.session_durations.clone(), + sessions_started: self.session_started, + sessions_finished: self.session_durations.len() as u32, + } + } +} + +impl Default for SessionStatsState { + fn default() -> Self { + SessionStatsState { + update_time: OffsetDateTime::UNIX_EPOCH.date(), + unique_active_users: 0, + session_started: 0, + session_durations: Default::default(), + } + } +} + #[derive(Debug, Clone, Default)] pub struct MetricsAppState { pub(crate) prometheus_access_token: Option, pub(crate) mixing_stats: SharedMixingStats, + pub(crate) session_stats: SharedSessionStats, + pub(crate) verloc: SharedVerlocStats, } diff --git a/nym-node/nym-node-http-api/src/state/mod.rs b/nym-node/nym-node-http-api/src/state/mod.rs index 077ca782b62..67f16d1bd3c 100644 --- a/nym-node/nym-node-http-api/src/state/mod.rs +++ b/nym-node/nym-node-http-api/src/state/mod.rs @@ -1,7 +1,9 @@ // Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::state::metrics::{MetricsAppState, SharedMixingStats, SharedVerlocStats}; +use crate::state::metrics::{ + MetricsAppState, SharedMixingStats, SharedSessionStats, SharedVerlocStats, +}; use tokio::time::Instant; pub mod metrics; @@ -32,6 +34,12 @@ impl AppState { self } + #[must_use] + pub fn with_sessions_stats(mut self, session_stats: SharedSessionStats) -> Self { + self.metrics.session_stats = session_stats; + self + } + #[must_use] pub fn with_verloc_stats(mut self, verloc_stats: SharedVerlocStats) -> Self { self.metrics.verloc = verloc_stats; diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs index f0051e81e19..502e223c245 100644 --- a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -35,6 +35,21 @@ pub struct MixingStats { pub dropped_since_last_update: u64, } +#[derive(Serialize, Deserialize, Debug, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct SessionStats { + #[serde(with = "time::serde::rfc3339")] + pub update_time: OffsetDateTime, + + pub unique_active_users: u32, + + pub session_durations: Vec, + + pub sessions_started: u32, + + pub sessions_finished: u32, +} + #[derive(Serialize, Deserialize, Default, Debug, Clone)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct VerlocStats { diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index 7b4e0ee9b7d..983b1a6aecd 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -65,10 +65,12 @@ pub mod routes { use super::*; pub const MIXING: &str = "/mixing"; + pub const SESSIONS: &str = "/sessions"; pub const VERLOC: &str = "/verloc"; pub const PROMETHEUS: &str = "/prometheus"; absolute_route!(mixing_absolute, metrics_absolute(), MIXING); + absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS); absolute_route!(verloc_absolute, metrics_absolute(), VERLOC); absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS); } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index d3e756c6e0b..5a7bc0c419a 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -26,7 +26,7 @@ use nym_node::config::{ use nym_node::error::{EntryGatewayError, ExitGatewayError, MixnodeError, NymNodeError}; use nym_node_http_api::api::api_requests; use nym_node_http_api::api::api_requests::v1::node::models::{AnnouncePorts, NodeDescription}; -use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats}; +use nym_node_http_api::state::metrics::{SharedMixingStats, SharedSessionStats, SharedVerlocStats}; use nym_node_http_api::state::AppState; use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter}; use nym_sphinx_acknowledgements::AckKey; @@ -67,6 +67,7 @@ impl MixnodeData { pub struct EntryGatewayData { mnemonic: Zeroizing, client_storage: nym_gateway::node::PersistentStorage, + sessions_stats: SharedSessionStats, } impl EntryGatewayData { @@ -93,6 +94,7 @@ impl EntryGatewayData { ) .await .map_err(nym_gateway::GatewayError::from)?, + sessions_stats: SharedSessionStats::new(), }) } } @@ -581,6 +583,7 @@ impl NymNode { ); entry_gateway.disable_http_server(); entry_gateway.set_task_client(task_client); + entry_gateway.set_session_stats(self.entry_gateway.sessions_stats.clone()); if self.config.wireguard.enabled { entry_gateway.set_wireguard_data(self.wireguard.into()); } @@ -728,6 +731,7 @@ impl NymNode { let app_state = AppState::new() .with_mixing_stats(self.mixnode.mixing_stats.clone()) + .with_sessions_stats(self.entry_gateway.sessions_stats.clone()) .with_verloc_stats(self.verloc_stats.clone()) .with_metrics_key(self.config.http.access_token.clone());