feature: introduce /load endpoint for self-reported quantised NymNode load (#5326)
* feature: introduce /load endpoint for self-reported quantised NymNode load * return Load::Unknown for value of 0 because it means we misread some data * add additional filtering on 'en...' endpoints
This commit is contained in:
committed by
GitHub
parent
dcfb092758
commit
03c4895f2b
Generated
+64
-15
@@ -232,6 +232,12 @@ dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.3"
|
||||
@@ -1292,9 +1298,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
@@ -6070,6 +6076,7 @@ name = "nym-node"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"axum 0.7.7",
|
||||
"axum-extra",
|
||||
@@ -6153,7 +6160,6 @@ name = "nym-node-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"celes",
|
||||
"humantime 2.1.0",
|
||||
"humantime-serde",
|
||||
@@ -6166,6 +6172,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.26.3",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -9667,17 +9674,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.13"
|
||||
version = "0.33.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
|
||||
checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"windows 0.52.0",
|
||||
"windows 0.57.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11388,11 +11394,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||
dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-core 0.57.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
@@ -11415,19 +11421,42 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement 0.57.0",
|
||||
"windows-interface 0.57.0",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-implement 0.58.0",
|
||||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.58.0"
|
||||
@@ -11439,6 +11468,17 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.58.0"
|
||||
@@ -11450,6 +11490,15 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
@@ -11465,7 +11514,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-result 0.2.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
|
||||
+2
-1
@@ -194,6 +194,7 @@ aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.95"
|
||||
arc-swap = "1.7.1"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.85"
|
||||
axum-client-ip = "0.6.1"
|
||||
@@ -324,7 +325,7 @@ strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.13"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.43"
|
||||
tempfile = "3.15"
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Default for ClientStatsReport {
|
||||
pub struct OsInformation {
|
||||
pub(crate) os_type: String,
|
||||
pub(crate) os_version: Option<String>,
|
||||
pub(crate) os_arch: Option<String>,
|
||||
pub(crate) os_arch: String,
|
||||
}
|
||||
|
||||
impl OsInformation {
|
||||
|
||||
@@ -16,6 +16,7 @@ license = "GPL-3.0"
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
anyhow.workspace = true
|
||||
arc-swap = { workspace = true }
|
||||
bip39 = { workspace = true, features = ["zeroize"] }
|
||||
bs58.workspace = true
|
||||
celes = { workspace = true } # country codes
|
||||
|
||||
@@ -11,13 +11,13 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
celes = { workspace = true } # country codes
|
||||
humantime = { workspace = true }
|
||||
humantime-serde = { workspace = true }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
|
||||
@@ -11,16 +11,16 @@ use async_trait::async_trait;
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_http_api_client::{ApiClient, HttpClientError};
|
||||
|
||||
use super::v1::gateway::models::Wireguard;
|
||||
use super::v1::metrics::models::SessionStats;
|
||||
use crate::api::v1::authenticator::models::Authenticator;
|
||||
use crate::api::v1::health::models::NodeHealth;
|
||||
use crate::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy;
|
||||
use crate::api::v1::network_requester::models::NetworkRequester;
|
||||
use crate::api::v1::node_load::models::NodeLoad;
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
use super::v1::gateway::models::Wireguard;
|
||||
use super::v1::metrics::models::SessionStats;
|
||||
|
||||
pub type NymNodeApiClientError = HttpClientError<ErrorResponse>;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -30,6 +30,10 @@ pub trait NymNodeApiClientExt: ApiClient {
|
||||
self.get_json_from(routes::api::v1::health_absolute()).await
|
||||
}
|
||||
|
||||
async fn get_node_load(&self) -> Result<NodeLoad, NymNodeApiClientError> {
|
||||
self.get_json_from(routes::api::v1::load_absolute()).await
|
||||
}
|
||||
|
||||
async fn get_host_information(&self) -> Result<SignedHostInformation, NymNodeApiClientError> {
|
||||
self.get_json_from(routes::api::v1::host_info_absolute())
|
||||
.await
|
||||
|
||||
@@ -9,3 +9,4 @@ pub mod metrics;
|
||||
pub mod mixnode;
|
||||
pub mod network_requester;
|
||||
pub mod node;
|
||||
pub mod node_load;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod models;
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
#[derive(
|
||||
Display,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumString,
|
||||
PartialEq,
|
||||
Eq,
|
||||
JsonSchema,
|
||||
PartialOrd,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub enum Load {
|
||||
#[default]
|
||||
Unknown,
|
||||
|
||||
// order of the variants matter as we've derived `PartialOrd` on them
|
||||
Negligible, // 0 - 0.1
|
||||
Low, // 0.1 - 0.3
|
||||
Medium, // 0.3 - 0.6
|
||||
High, // 0.6 - 0.8
|
||||
VeryHigh, // 0.8 - 0.95
|
||||
AtCapacity, // >= 0.95
|
||||
}
|
||||
|
||||
impl Load {
|
||||
// returns load of one tier higher
|
||||
pub fn increment(&self) -> Self {
|
||||
match self {
|
||||
Self::Unknown => Self::Unknown,
|
||||
Self::Negligible => Self::Low,
|
||||
Self::Low => Self::Medium,
|
||||
Self::Medium => Self::High,
|
||||
Self::High => Self::VeryHigh,
|
||||
Self::VeryHigh => Self::AtCapacity,
|
||||
Self::AtCapacity => Self::AtCapacity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Load {
|
||||
fn from(value: f64) -> Self {
|
||||
if value == 0. || value.is_nan() || value.is_infinite() {
|
||||
Self::Unknown
|
||||
} else if value <= 0.1 {
|
||||
Self::Negligible
|
||||
} else if value <= 0.3 {
|
||||
Self::Low
|
||||
} else if value <= 0.6 {
|
||||
Self::Medium
|
||||
} else if value <= 0.8 {
|
||||
Self::High
|
||||
} else if value <= 0.95 {
|
||||
Self::VeryHigh
|
||||
} else {
|
||||
Self::AtCapacity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct NodeLoad {
|
||||
pub total: Load,
|
||||
pub machine: Load,
|
||||
pub network: Load,
|
||||
}
|
||||
@@ -35,6 +35,7 @@ pub mod routes {
|
||||
pub const NODE_DESCRIPTION: &str = "/description";
|
||||
pub const AUXILIARY: &str = "/auxiliary-details";
|
||||
pub const HEALTH: &str = "/health";
|
||||
pub const LOAD: &str = "/load";
|
||||
pub const SWAGGER: &str = "/swagger";
|
||||
|
||||
pub const GATEWAY: &str = "/gateway";
|
||||
@@ -46,6 +47,7 @@ pub mod routes {
|
||||
|
||||
// define helper functions to get absolute routes
|
||||
absolute_route!(health_absolute, v1_absolute(), HEALTH);
|
||||
absolute_route!(load_absolute, v1_absolute(), LOAD);
|
||||
absolute_route!(roles_absolute, v1_absolute(), ROLES);
|
||||
absolute_route!(build_info_absolute, v1_absolute(), BUILD_INFO);
|
||||
absolute_route!(host_info_absolute, v1_absolute(), HOST_INFO);
|
||||
|
||||
@@ -470,6 +470,15 @@ pub struct Http {
|
||||
/// This option is superseded by `expose_system_hardware`
|
||||
/// default: true
|
||||
pub expose_crypto_hardware: bool,
|
||||
|
||||
/// Specify the cache ttl of the node load.
|
||||
/// default: 30s
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_load_cache_ttl: Duration,
|
||||
}
|
||||
|
||||
impl Http {
|
||||
pub const DEFAULT_NODE_LOAD_CACHE_TTL: Duration = Duration::from_secs(30);
|
||||
}
|
||||
|
||||
impl Default for Http {
|
||||
@@ -481,6 +490,7 @@ impl Default for Http {
|
||||
expose_system_info: true,
|
||||
expose_system_hardware: true,
|
||||
expose_crypto_hardware: true,
|
||||
node_load_cache_ttl: Self::DEFAULT_NODE_LOAD_CACHE_TTL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1027,6 +1027,7 @@ pub async fn try_upgrade_config_v6<P: AsRef<Path>>(
|
||||
expose_system_info: old_cfg.http.expose_system_info,
|
||||
expose_system_hardware: old_cfg.http.expose_system_hardware,
|
||||
expose_crypto_hardware: old_cfg.http.expose_crypto_hardware,
|
||||
..Default::default()
|
||||
},
|
||||
verloc: Verloc {
|
||||
bind_address: old_cfg.mixnode.verloc.bind_address,
|
||||
|
||||
@@ -72,7 +72,7 @@ pub(crate) fn get_system_info(
|
||||
system_name: System::name(),
|
||||
kernel_version: System::kernel_version(),
|
||||
os_version: System::os_version(),
|
||||
cpu_arch: System::cpu_arch(),
|
||||
cpu_arch: Some(System::cpu_arch()),
|
||||
hardware,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ use nym_node_requests::api::v1::health::models::NodeHealth;
|
||||
tag = "Health",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Vec<NodeHealth> = "application/json"),
|
||||
(Vec<NodeHealth> = "application/yaml")
|
||||
(NodeHealth = "application/json"),
|
||||
(NodeHealth = "application/yaml")
|
||||
), description = "the api is available and healthy")
|
||||
),
|
||||
params(OutputParams)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_node_requests::api::v1::node_load::models::NodeLoad;
|
||||
|
||||
/// Returns current relative load this node.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/load",
|
||||
context_path = "/api/v1",
|
||||
tag = "Node",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(NodeLoad = "application/json"),
|
||||
(NodeLoad = "application/yaml")
|
||||
), description = "current node load")
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
pub(crate) async fn root_load(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> NodeLoadResponse {
|
||||
let output = output.output.unwrap_or_default();
|
||||
let load = state.cached_load.current_load();
|
||||
|
||||
output.to_response(load)
|
||||
}
|
||||
|
||||
pub type NodeLoadResponse = FormattedResponse<NodeLoad>;
|
||||
@@ -10,6 +10,7 @@ pub mod authenticator;
|
||||
pub mod gateway;
|
||||
pub mod health;
|
||||
pub mod ip_packet_router;
|
||||
pub mod load;
|
||||
pub mod metrics;
|
||||
pub mod mixnode;
|
||||
pub mod network_requester;
|
||||
@@ -30,6 +31,7 @@ pub struct Config {
|
||||
pub(super) fn routes(config: Config) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(v1::HEALTH, get(health::root_health))
|
||||
.route(v1::LOAD, get(load::root_load))
|
||||
.nest(v1::METRICS, metrics::routes(config.metrics))
|
||||
.nest(v1::GATEWAY, gateway::routes(config.gateway))
|
||||
.nest(v1::MIXNODE, mixnode::routes(config.mixnode))
|
||||
|
||||
@@ -24,6 +24,7 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
api::v1::metrics::verloc::verloc_stats,
|
||||
api::v1::metrics::prometheus::prometheus_metrics,
|
||||
api::v1::health::root_health,
|
||||
api::v1::load::root_load,
|
||||
api::v1::gateway::root::root_gateway,
|
||||
api::v1::gateway::client_interfaces::client_interfaces,
|
||||
api::v1::gateway::client_interfaces::mixnet_websockets,
|
||||
@@ -38,6 +39,8 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
nym_http_api_common::OutputParams,
|
||||
api_requests::v1::health::models::NodeHealth,
|
||||
api_requests::v1::health::models::NodeStatus,
|
||||
api_requests::v1::node_load::models::NodeLoad,
|
||||
api_requests::v1::node_load::models::Load,
|
||||
api_requests::v1::node::models::BinaryBuildInformationOwned,
|
||||
api_requests::v1::node::models::HostInformation,
|
||||
api_requests::v1::node::models::HostKeys,
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use nym_node_requests::api::v1::node_load::models::{Load, NodeLoad};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, Networks, System};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CachedNodeLoad {
|
||||
ttl: Duration,
|
||||
being_updated: Arc<AtomicBool>,
|
||||
inner: Arc<ArcSwap<CachedNodeLoadInner>>,
|
||||
}
|
||||
|
||||
impl CachedNodeLoad {
|
||||
pub(crate) fn new(ttl: Duration) -> Self {
|
||||
CachedNodeLoad {
|
||||
ttl,
|
||||
being_updated: Arc::new(Default::default()),
|
||||
inner: Arc::new(ArcSwap::new(Arc::new(CachedNodeLoadInner::initial()))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn current_load(&self) -> NodeLoad {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let inner = self.inner.load();
|
||||
if inner.timestamp + self.ttl < now {
|
||||
// new
|
||||
let already_being_updated = self.being_updated.swap(true, Ordering::SeqCst);
|
||||
if already_being_updated {
|
||||
// use the 'stale' entry because it is already being updated by another thread
|
||||
inner.load
|
||||
} else {
|
||||
self.update_cache()
|
||||
}
|
||||
} else {
|
||||
inner.load
|
||||
}
|
||||
}
|
||||
|
||||
fn update_cache(&self) -> NodeLoad {
|
||||
let old = self.inner.load();
|
||||
let current_load = CachedNodeLoadInner::update(&old);
|
||||
let load = current_load.load;
|
||||
self.inner.store(Arc::new(current_load));
|
||||
self.being_updated.store(false, Ordering::SeqCst);
|
||||
load
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CachedNodeLoadInner {
|
||||
timestamp: OffsetDateTime,
|
||||
last_transmitted: u64,
|
||||
last_received: u64,
|
||||
load: NodeLoad,
|
||||
}
|
||||
|
||||
struct RawUsage {
|
||||
load_per_cpu: f64,
|
||||
memory_usage: f64,
|
||||
swap_usage: f64,
|
||||
eth_transmitted: u64,
|
||||
eth_received: u64,
|
||||
total_swap: u64,
|
||||
}
|
||||
|
||||
impl RawUsage {
|
||||
fn current() -> RawUsage {
|
||||
let mut system = sysinfo::System::new();
|
||||
let networks = Networks::new_with_refreshed_list();
|
||||
|
||||
system.refresh_memory_specifics(MemoryRefreshKind::everything());
|
||||
system.refresh_cpu_specifics(CpuRefreshKind::nothing());
|
||||
|
||||
let average_load = System::load_average();
|
||||
let cpu_count = system.cpus().len();
|
||||
|
||||
let load_per_cpu = average_load.five / cpu_count as f64;
|
||||
|
||||
let total_memory = system.total_memory();
|
||||
let used_memory = system.used_memory();
|
||||
|
||||
let memory_usage = used_memory as f64 / total_memory as f64;
|
||||
|
||||
let total_swap = system.total_swap();
|
||||
let used_swap = system.free_swap();
|
||||
|
||||
let swap_usage = used_swap as f64 / total_swap as f64;
|
||||
|
||||
let mut eth_transmitted = 0;
|
||||
let mut eth_received = 0;
|
||||
|
||||
// we're only interested in interfaces with 'eth' or 'en' prefix
|
||||
// (that's a very weak assumption, but that's just first iteration of this endpoint)
|
||||
for (interface, data) in networks.list() {
|
||||
if interface.starts_with("eth") || interface.starts_with("en") {
|
||||
eth_transmitted += data.total_transmitted();
|
||||
eth_received += data.total_received();
|
||||
}
|
||||
}
|
||||
|
||||
debug!(average_load = ?average_load, memory_usage=memory_usage, swap_usage = swap_usage, "current load");
|
||||
|
||||
RawUsage {
|
||||
load_per_cpu,
|
||||
memory_usage,
|
||||
swap_usage,
|
||||
eth_transmitted,
|
||||
eth_received,
|
||||
total_swap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedNodeLoadInner {
|
||||
pub fn initial() -> CachedNodeLoadInner {
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let raw_usage = RawUsage::current();
|
||||
|
||||
let mut base_load = Load::from(raw_usage.load_per_cpu);
|
||||
let memory_load = Load::from(raw_usage.memory_usage);
|
||||
|
||||
// if memory load is of higher tier, increment the base load by one level
|
||||
// (i.e. for example from 'Low' to 'Medium')
|
||||
if memory_load > base_load {
|
||||
base_load = base_load.increment();
|
||||
}
|
||||
|
||||
if raw_usage.total_swap > 1024 * 1024 * 1024 {
|
||||
// same with swap
|
||||
let swap_load = Load::from(raw_usage.swap_usage);
|
||||
if swap_load > base_load {
|
||||
base_load = base_load.increment();
|
||||
}
|
||||
}
|
||||
|
||||
CachedNodeLoadInner {
|
||||
timestamp,
|
||||
last_transmitted: raw_usage.eth_transmitted,
|
||||
last_received: raw_usage.eth_received,
|
||||
load: NodeLoad {
|
||||
total: base_load,
|
||||
machine: base_load,
|
||||
network: Load::Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(previous: &Self) -> CachedNodeLoadInner {
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let raw_usage = RawUsage::current();
|
||||
|
||||
let time_delta = (timestamp - previous.timestamp).as_seconds_f64();
|
||||
let tx_delta = raw_usage.eth_transmitted - previous.last_transmitted;
|
||||
let rx_delta = raw_usage.eth_received - previous.last_received;
|
||||
|
||||
let tx_bs = tx_delta as f64 / time_delta;
|
||||
let rx_bs = rx_delta as f64 / time_delta;
|
||||
|
||||
// currently we consider value of 1Gbps to be maximum load
|
||||
// in the future we should allow specifying custom sizes of network cards
|
||||
// (Gbps = Bytes/s * 0.000000008)
|
||||
let tx_gbps = tx_bs * 0.000000008;
|
||||
let rx_gbps = rx_bs * 0.000000008;
|
||||
let network_load = Load::from(tx_gbps.max(rx_gbps));
|
||||
|
||||
debug!(tx_gbps = tx_gbps, rx_gbps = rx_gbps, "network load");
|
||||
|
||||
let mut base_load = Load::from(raw_usage.load_per_cpu);
|
||||
let memory_load = Load::from(raw_usage.memory_usage);
|
||||
|
||||
// if memory load is of higher tier, increment the base load by one level
|
||||
// (i.e. for example from 'Low' to 'Medium')
|
||||
if memory_load > base_load {
|
||||
base_load = base_load.increment();
|
||||
}
|
||||
|
||||
if raw_usage.total_swap > 1024 * 1024 * 1024 {
|
||||
// same with swap
|
||||
let swap_load = Load::from(raw_usage.swap_usage);
|
||||
if swap_load > base_load {
|
||||
base_load = base_load.increment();
|
||||
}
|
||||
}
|
||||
|
||||
let total_load = if base_load > network_load {
|
||||
base_load
|
||||
} else {
|
||||
base_load.increment()
|
||||
};
|
||||
|
||||
CachedNodeLoadInner {
|
||||
timestamp,
|
||||
last_transmitted: raw_usage.eth_transmitted,
|
||||
last_received: raw_usage.eth_received,
|
||||
load: NodeLoad {
|
||||
total: total_load,
|
||||
machine: base_load,
|
||||
network: network_load,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,39 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::http::state::load::CachedNodeLoad;
|
||||
use crate::node::http::state::metrics::MetricsAppState;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use nym_verloc::measurements::SharedVerlocStats;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
pub mod load;
|
||||
pub mod metrics;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub(crate) startup_time: Instant,
|
||||
|
||||
pub(crate) cached_load: CachedNodeLoad,
|
||||
|
||||
pub(crate) metrics: MetricsAppState,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new(metrics: NymNodeMetrics, verloc: SharedVerlocStats) -> Self {
|
||||
pub fn new(
|
||||
metrics: NymNodeMetrics,
|
||||
verloc: SharedVerlocStats,
|
||||
load_cache_ttl: Duration,
|
||||
) -> Self {
|
||||
AppState {
|
||||
// is it 100% accurate?
|
||||
// no.
|
||||
// does it have to be?
|
||||
// also no.
|
||||
startup_time: Instant::now(),
|
||||
cached_load: CachedNodeLoad::new(load_cache_ttl),
|
||||
metrics: MetricsAppState { metrics, verloc },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,7 +785,11 @@ impl NymNode {
|
||||
config.api.v1_config.node.roles.ip_packet_router_enabled = true;
|
||||
}
|
||||
|
||||
let app_state = AppState::new(self.metrics.clone(), self.verloc_stats.clone());
|
||||
let app_state = AppState::new(
|
||||
self.metrics.clone(),
|
||||
self.verloc_stats.clone(),
|
||||
self.config.http.node_load_cache_ttl,
|
||||
);
|
||||
|
||||
Ok(NymNodeRouter::new(config, app_state)
|
||||
.build_server(&self.config.http.bind_address)
|
||||
|
||||
Generated
+1
-1
@@ -3329,7 +3329,6 @@ name = "nym-node-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"celes",
|
||||
"humantime 2.1.0",
|
||||
"humantime-serde",
|
||||
@@ -3341,6 +3340,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.26.3",
|
||||
"thiserror 1.0.64",
|
||||
"time",
|
||||
"utoipa",
|
||||
|
||||
Reference in New Issue
Block a user