Feature/stats endpoint (#631)
* Idea for stats endpoint * Introduced /stats endpoint replacing sending data to metrics server * Removed metrics client * Removed old metrics file * cargo fmt
This commit is contained in:
committed by
GitHub
parent
94a52aa2db
commit
82def1349f
@@ -1,4 +1,5 @@
|
||||
pub(crate) mod description;
|
||||
pub(crate) mod stats;
|
||||
pub(crate) mod verloc;
|
||||
|
||||
use rocket::Request;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::node::node_statistics::{NodeStats, NodeStatsSimple, NodeStatsWrapper};
|
||||
use rocket::State;
|
||||
use rocket_contrib::json::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum NodeStatsResponse {
|
||||
Full(NodeStats),
|
||||
Simple(NodeStatsSimple),
|
||||
}
|
||||
|
||||
/// Returns a running stats of the node.
|
||||
#[get("/stats?<debug>")]
|
||||
pub(crate) async fn stats(
|
||||
stats: State<'_, NodeStatsWrapper>,
|
||||
debug: Option<bool>,
|
||||
) -> Json<NodeStatsResponse> {
|
||||
let snapshot_data = stats.clone_data().await;
|
||||
|
||||
// there's no point in returning the entire hashmap of sending destinations in regular mode
|
||||
if let Some(debug) = debug {
|
||||
if debug {
|
||||
return Json(NodeStatsResponse::Full(snapshot_data));
|
||||
}
|
||||
}
|
||||
|
||||
Json(NodeStatsResponse::Simple(snapshot_data.simplify()))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::metrics;
|
||||
use crate::node::node_statistics;
|
||||
use crypto::asymmetric::encryption;
|
||||
use mixnode_common::cached_packet_processor::error::MixProcessingError;
|
||||
use mixnode_common::cached_packet_processor::processor::CachedPacketProcessor;
|
||||
@@ -15,25 +15,25 @@ pub struct PacketProcessor {
|
||||
inner_processor: CachedPacketProcessor,
|
||||
|
||||
/// Responsible for updating metrics data
|
||||
metrics_reporter: metrics::MetricsReporter,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
}
|
||||
|
||||
impl PacketProcessor {
|
||||
pub(crate) fn new(
|
||||
encryption_key: &encryption::PrivateKey,
|
||||
metrics_reporter: metrics::MetricsReporter,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
cache_entry_ttl: Duration,
|
||||
) -> Self {
|
||||
PacketProcessor {
|
||||
inner_processor: CachedPacketProcessor::new(encryption_key.into(), cache_entry_ttl),
|
||||
metrics_reporter,
|
||||
node_stats_update_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clone_without_cache(&self) -> Self {
|
||||
PacketProcessor {
|
||||
inner_processor: self.inner_processor.clone_without_cache(),
|
||||
metrics_reporter: self.metrics_reporter.clone(),
|
||||
node_stats_update_sender: self.node_stats_update_sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ impl PacketProcessor {
|
||||
&self,
|
||||
received: FramedSphinxPacket,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
self.metrics_reporter.report_received();
|
||||
self.node_stats_update_sender.report_received();
|
||||
self.inner_processor.process_received(received)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use log::trace;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
const METRICS_FAILURE_BACKOFF: Duration = Duration::from_secs(30);
|
||||
|
||||
type SentMetricsMap = HashMap<String, u64>;
|
||||
|
||||
pub(crate) enum MetricEvent {
|
||||
Sent(String),
|
||||
Received,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// Note: you should NEVER create more than a single instance of this using 'new()'.
|
||||
// You should always use .clone() to create additional instances
|
||||
struct MixMetrics {
|
||||
inner: Arc<MixMetricsInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MixMetricsInner {
|
||||
received: AtomicU64,
|
||||
sent: Mutex<SentMetricsMap>,
|
||||
}
|
||||
|
||||
impl MixMetrics {
|
||||
pub(crate) fn new() -> Self {
|
||||
MixMetrics {
|
||||
inner: Arc::new(MixMetricsInner {
|
||||
received: AtomicU64::new(0),
|
||||
sent: Mutex::new(HashMap::new()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_received_metrics(&mut self) {
|
||||
self.inner.received.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
async fn increment_sent_metrics(&mut self, destination: String) {
|
||||
let mut unlocked = self.inner.sent.lock().await;
|
||||
let receiver_count = unlocked.entry(destination).or_insert(0);
|
||||
*receiver_count += 1;
|
||||
}
|
||||
|
||||
async fn acquire_and_reset_metrics(&mut self) -> (u64, SentMetricsMap) {
|
||||
let mut unlocked = self.inner.sent.lock().await;
|
||||
let received = self.inner.received.swap(0, Ordering::SeqCst);
|
||||
|
||||
let sent = std::mem::take(unlocked.deref_mut());
|
||||
|
||||
(received, sent)
|
||||
}
|
||||
}
|
||||
|
||||
struct MetricsReceiver {
|
||||
metrics: MixMetrics,
|
||||
metrics_rx: mpsc::UnboundedReceiver<MetricEvent>,
|
||||
}
|
||||
|
||||
impl MetricsReceiver {
|
||||
fn new(metrics: MixMetrics, metrics_rx: mpsc::UnboundedReceiver<MetricEvent>) -> Self {
|
||||
MetricsReceiver {
|
||||
metrics,
|
||||
metrics_rx,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
while let Some(metrics_data) = self.metrics_rx.next().await {
|
||||
match metrics_data {
|
||||
MetricEvent::Received => self.metrics.increment_received_metrics(),
|
||||
MetricEvent::Sent(destination) => {
|
||||
self.metrics.increment_sent_metrics(destination).await
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MetricsSender {
|
||||
metrics: MixMetrics,
|
||||
#[allow(dead_code)]
|
||||
metrics_client: metrics_client::Client,
|
||||
#[allow(dead_code)]
|
||||
pub_key_str: String,
|
||||
metrics_informer: MetricsInformer,
|
||||
}
|
||||
|
||||
impl MetricsSender {
|
||||
fn new(
|
||||
metrics: MixMetrics,
|
||||
metrics_server: String,
|
||||
pub_key_str: String,
|
||||
running_logging_delay: Duration,
|
||||
) -> Self {
|
||||
MetricsSender {
|
||||
metrics,
|
||||
metrics_client: metrics_client::Client::new(metrics_client::Config::new(
|
||||
metrics_server,
|
||||
)),
|
||||
pub_key_str,
|
||||
metrics_informer: MetricsInformer::new(running_logging_delay),
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let (received, sent) = self.metrics.acquire_and_reset_metrics().await;
|
||||
|
||||
self.metrics_informer.update_running_stats(received, &sent);
|
||||
self.metrics_informer.log_report_stats(received, &sent);
|
||||
self.metrics_informer.try_log_running_stats();
|
||||
|
||||
tokio::time::sleep(METRICS_FAILURE_BACKOFF).await;
|
||||
|
||||
// let sending_delay = match self
|
||||
// .metrics_client
|
||||
// .post_mix_metrics(MixMetric {
|
||||
// pub_key: self.pub_key_str.clone(),
|
||||
// received,
|
||||
// sent,
|
||||
// })
|
||||
// .await
|
||||
// {
|
||||
// Err(err) => {
|
||||
// error!("failed to send metrics - {:?}", err);
|
||||
// tokio::time::sleep(METRICS_FAILURE_BACKOFF)
|
||||
// }
|
||||
// Ok(new_interval) => {
|
||||
// debug!("sent metrics information");
|
||||
// tokio::time::sleep(Duration::from_secs(new_interval.next_report_in))
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// // wait for however much is left
|
||||
// sending_delay.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MetricsInformer {
|
||||
total_received: u64,
|
||||
sent_map: SentMetricsMap,
|
||||
|
||||
running_stats_logging_delay: Duration,
|
||||
last_reported_stats: SystemTime,
|
||||
}
|
||||
|
||||
impl MetricsInformer {
|
||||
fn new(running_stats_logging_delay: Duration) -> Self {
|
||||
MetricsInformer {
|
||||
total_received: 0,
|
||||
sent_map: HashMap::new(),
|
||||
running_stats_logging_delay,
|
||||
last_reported_stats: SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn should_log_running_stats(&self) -> bool {
|
||||
self.last_reported_stats + self.running_stats_logging_delay < SystemTime::now()
|
||||
}
|
||||
|
||||
fn try_log_running_stats(&mut self) {
|
||||
if self.should_log_running_stats() {
|
||||
self.log_running_stats()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_running_stats(&mut self, pre_reset_received: u64, pre_reset_sent: &SentMetricsMap) {
|
||||
self.total_received += pre_reset_received;
|
||||
|
||||
for (mix, count) in pre_reset_sent.iter() {
|
||||
*self.sent_map.entry(mix.clone()).or_insert(0) += *count;
|
||||
}
|
||||
}
|
||||
|
||||
fn log_report_stats(&self, pre_reset_received: u64, pre_reset_sent: &SentMetricsMap) {
|
||||
debug!(
|
||||
"Since last metrics report mixed {} packets!",
|
||||
pre_reset_received
|
||||
);
|
||||
debug!(
|
||||
"Since last metrics report received {} packets",
|
||||
pre_reset_sent.values().sum::<u64>()
|
||||
);
|
||||
trace!(
|
||||
"Since last metrics report sent packets to the following: \n{:#?}",
|
||||
pre_reset_sent
|
||||
);
|
||||
}
|
||||
|
||||
fn log_running_stats(&mut self) {
|
||||
info!(
|
||||
"Since startup mixed {} packets!",
|
||||
self.sent_map.values().sum::<u64>()
|
||||
);
|
||||
debug!("Since startup received {} packets", self.total_received);
|
||||
trace!(
|
||||
"Since startup sent packets to the following: \n{:#?}",
|
||||
self.sent_map
|
||||
);
|
||||
self.last_reported_stats = SystemTime::now();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MetricsReporter {
|
||||
metrics_tx: mpsc::UnboundedSender<MetricEvent>,
|
||||
}
|
||||
|
||||
impl MetricsReporter {
|
||||
pub(crate) fn new(metrics_tx: mpsc::UnboundedSender<MetricEvent>) -> Self {
|
||||
MetricsReporter { metrics_tx }
|
||||
}
|
||||
|
||||
pub(crate) fn report_sent(&self, destination: String) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.metrics_tx
|
||||
.unbounded_send(MetricEvent::Sent(destination))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// TODO: in the future this could be slightly optimised to get rid of the channel
|
||||
// in favour of incrementing value directly
|
||||
pub(crate) fn report_received(&self) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.metrics_tx
|
||||
.unbounded_send(MetricEvent::Received)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// basically an easy single entry point to start all metrics related tasks
|
||||
pub struct MetricsController {
|
||||
receiver: MetricsReceiver,
|
||||
reporter: MetricsReporter,
|
||||
sender: MetricsSender,
|
||||
}
|
||||
|
||||
impl MetricsController {
|
||||
pub(crate) fn new(
|
||||
directory_server: String,
|
||||
pub_key_str: String,
|
||||
running_stats_logging_delay: Duration,
|
||||
) -> Self {
|
||||
let (metrics_tx, metrics_rx) = mpsc::unbounded();
|
||||
let shared_metrics = MixMetrics::new();
|
||||
|
||||
MetricsController {
|
||||
sender: MetricsSender::new(
|
||||
shared_metrics.clone(),
|
||||
directory_server,
|
||||
pub_key_str,
|
||||
running_stats_logging_delay,
|
||||
),
|
||||
receiver: MetricsReceiver::new(shared_metrics, metrics_rx),
|
||||
reporter: MetricsReporter::new(metrics_tx),
|
||||
}
|
||||
}
|
||||
|
||||
// reporter is how node is going to be accessing the metrics data
|
||||
pub(crate) fn start(self) -> MetricsReporter {
|
||||
// TODO: should we do anything with JoinHandle(s) returned by start methods?
|
||||
self.receiver.start();
|
||||
self.sender.start();
|
||||
self.reporter
|
||||
}
|
||||
}
|
||||
+29
-19
@@ -5,12 +5,14 @@ use crate::config::Config;
|
||||
use crate::node::http::{
|
||||
description::description,
|
||||
not_found,
|
||||
stats::stats,
|
||||
verloc::{verloc, VerlocState},
|
||||
};
|
||||
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
|
||||
use crate::node::listener::connection_handler::ConnectionHandler;
|
||||
use crate::node::listener::Listener;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::node::node_statistics::NodeStatsWrapper;
|
||||
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use log::{error, info, warn};
|
||||
@@ -22,8 +24,9 @@ use version_checker::parse_version;
|
||||
|
||||
pub(crate) mod http;
|
||||
mod listener;
|
||||
mod metrics;
|
||||
// mod metrics;
|
||||
pub(crate) mod node_description;
|
||||
pub(crate) mod node_statistics;
|
||||
pub(crate) mod packet_delayforwarder;
|
||||
|
||||
// the MixNode will live for whole duration of this program
|
||||
@@ -49,7 +52,11 @@ impl MixNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_http_api(&self, atomic_verloc_result: AtomicVerlocResult) {
|
||||
fn start_http_api(
|
||||
&self,
|
||||
atomic_verloc_result: AtomicVerlocResult,
|
||||
node_stats_pointer: NodeStatsWrapper,
|
||||
) {
|
||||
info!("Starting HTTP API on http://localhost:8000");
|
||||
|
||||
let mut config = rocket::config::Config::release_default();
|
||||
@@ -62,35 +69,38 @@ impl MixNode {
|
||||
tokio::spawn(async move {
|
||||
rocket::build()
|
||||
.configure(config)
|
||||
.mount("/", routes![verloc, description])
|
||||
.mount("/", routes![verloc, description, stats])
|
||||
.register("/", catchers![not_found])
|
||||
.manage(verloc_state)
|
||||
.manage(descriptor)
|
||||
.manage(node_stats_pointer)
|
||||
.launch()
|
||||
.await
|
||||
});
|
||||
}
|
||||
|
||||
fn start_metrics_reporter(&self) -> metrics::MetricsReporter {
|
||||
info!("Starting metrics reporter...");
|
||||
metrics::MetricsController::new(
|
||||
self.config.get_metrics_server(),
|
||||
self.identity_keypair.public_key().to_base58_string(),
|
||||
self.config.get_metrics_running_stats_logging_delay(),
|
||||
)
|
||||
.start()
|
||||
fn start_node_stats_controller(&self) -> (NodeStatsWrapper, node_statistics::UpdateSender) {
|
||||
info!("Starting node stats controller...");
|
||||
let controller = node_statistics::Controller::new(
|
||||
self.config.get_node_stats_logging_delay(),
|
||||
self.config.get_node_stats_updating_delay(),
|
||||
);
|
||||
let node_stats_pointer = controller.get_node_stats_data_pointer();
|
||||
let update_sender = controller.start();
|
||||
|
||||
(node_stats_pointer, update_sender)
|
||||
}
|
||||
|
||||
fn start_socket_listener(
|
||||
&self,
|
||||
metrics_reporter: metrics::MetricsReporter,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
delay_forwarding_channel: PacketDelayForwardSender,
|
||||
) {
|
||||
info!("Starting socket listener...");
|
||||
|
||||
let packet_processor = PacketProcessor::new(
|
||||
self.sphinx_keypair.private_key(),
|
||||
metrics_reporter,
|
||||
node_stats_update_sender,
|
||||
self.config.get_cache_entry_ttl(),
|
||||
);
|
||||
|
||||
@@ -103,7 +113,7 @@ impl MixNode {
|
||||
|
||||
fn start_packet_delay_forwarder(
|
||||
&mut self,
|
||||
metrics_reporter: metrics::MetricsReporter,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
) -> PacketDelayForwardSender {
|
||||
info!("Starting packet delay-forwarder...");
|
||||
|
||||
@@ -112,7 +122,7 @@ impl MixNode {
|
||||
self.config.get_packet_forwarding_maximum_backoff(),
|
||||
self.config.get_initial_connection_timeout(),
|
||||
self.config.get_maximum_connection_buffer_size(),
|
||||
metrics_reporter,
|
||||
node_stats_update_sender,
|
||||
);
|
||||
|
||||
let packet_sender = packet_forwarder.sender();
|
||||
@@ -212,12 +222,12 @@ impl MixNode {
|
||||
}
|
||||
}
|
||||
|
||||
let metrics_reporter = self.start_metrics_reporter();
|
||||
let delay_forwarding_channel = self.start_packet_delay_forwarder(metrics_reporter.clone());
|
||||
self.start_socket_listener(metrics_reporter, delay_forwarding_channel);
|
||||
let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller();
|
||||
let delay_forwarding_channel = self.start_packet_delay_forwarder(node_stats_update_sender.clone());
|
||||
self.start_socket_listener(node_stats_update_sender, delay_forwarding_channel);
|
||||
|
||||
let atomic_verloc_results= self.start_rtt_measurer();
|
||||
self.start_http_api(atomic_verloc_results);
|
||||
self.start_http_api(atomic_verloc_results, node_stats_pointer);
|
||||
|
||||
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
|
||||
self.wait_for_interrupt().await
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
// convenience aliases
|
||||
type PacketsMap = HashMap<String, u64>;
|
||||
type PacketDataReceiver = mpsc::UnboundedReceiver<PacketEvent>;
|
||||
type PacketDataSender = mpsc::UnboundedSender<PacketEvent>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct NodeStatsWrapper {
|
||||
inner: Arc<RwLock<NodeStats>>,
|
||||
}
|
||||
|
||||
impl NodeStatsWrapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
let now = SystemTime::now();
|
||||
NodeStatsWrapper {
|
||||
inner: Arc::new(RwLock::new(NodeStats {
|
||||
update_time: now,
|
||||
previous_update_time: now,
|
||||
packets_received_since_startup: 0,
|
||||
packets_sent_since_startup: HashMap::new(),
|
||||
packets_explicitly_dropped_since_startup: HashMap::new(),
|
||||
packets_received_since_last_update: 0,
|
||||
packets_sent_since_last_update: HashMap::new(),
|
||||
packets_explicitly_dropped_since_last_update: HashMap::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn update(
|
||||
&self,
|
||||
new_received: u64,
|
||||
new_sent: PacketsMap,
|
||||
new_dropped: PacketsMap,
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
let snapshot_time = SystemTime::now();
|
||||
|
||||
guard.previous_update_time = guard.update_time;
|
||||
guard.update_time = snapshot_time;
|
||||
|
||||
guard.packets_received_since_startup += new_received;
|
||||
for (mix, count) in new_sent.iter() {
|
||||
*guard
|
||||
.packets_sent_since_startup
|
||||
.entry(mix.clone())
|
||||
.or_insert(0) += *count;
|
||||
}
|
||||
|
||||
for (mix, count) in new_dropped.iter() {
|
||||
*guard
|
||||
.packets_explicitly_dropped_since_last_update
|
||||
.entry(mix.clone())
|
||||
.or_insert(0) += *count;
|
||||
}
|
||||
|
||||
guard.packets_received_since_last_update = new_received;
|
||||
guard.packets_sent_since_last_update = new_sent;
|
||||
guard.packets_explicitly_dropped_since_last_update = new_dropped;
|
||||
}
|
||||
|
||||
pub(crate) async fn clone_data(&self) -> NodeStats {
|
||||
self.inner.read().await.clone()
|
||||
}
|
||||
|
||||
async fn read(&self) -> RwLockReadGuard<'_, NodeStats> {
|
||||
self.inner.read().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub(crate) struct NodeStats {
|
||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||
update_time: SystemTime,
|
||||
|
||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||
previous_update_time: SystemTime,
|
||||
|
||||
packets_received_since_startup: u64,
|
||||
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_startup: PacketsMap,
|
||||
|
||||
// we know for sure we dropped packets to those destinations
|
||||
packets_explicitly_dropped_since_startup: PacketsMap,
|
||||
|
||||
packets_received_since_last_update: u64,
|
||||
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_last_update: PacketsMap,
|
||||
|
||||
// we know for sure we dropped packets to those destinations
|
||||
packets_explicitly_dropped_since_last_update: PacketsMap,
|
||||
}
|
||||
|
||||
impl NodeStats {
|
||||
pub(crate) fn simplify(&self) -> NodeStatsSimple {
|
||||
NodeStatsSimple {
|
||||
update_time: self.update_time,
|
||||
previous_update_time: self.previous_update_time,
|
||||
packets_received_since_startup: self.packets_received_since_startup,
|
||||
packets_sent_since_startup: self.packets_sent_since_startup.values().sum(),
|
||||
packets_explicitly_dropped_since_startup: self
|
||||
.packets_explicitly_dropped_since_startup
|
||||
.values()
|
||||
.sum(),
|
||||
packets_received_since_last_update: self.packets_received_since_last_update,
|
||||
packets_sent_since_last_update: self.packets_sent_since_last_update.values().sum(),
|
||||
packets_explicitly_dropped_since_last_update: self
|
||||
.packets_explicitly_dropped_since_last_update
|
||||
.values()
|
||||
.sum(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub(crate) struct NodeStatsSimple {
|
||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||
update_time: SystemTime,
|
||||
|
||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||
previous_update_time: SystemTime,
|
||||
|
||||
packets_received_since_startup: u64,
|
||||
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_startup: u64,
|
||||
|
||||
// we know for sure we dropped those packets
|
||||
packets_explicitly_dropped_since_startup: u64,
|
||||
|
||||
packets_received_since_last_update: u64,
|
||||
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_last_update: u64,
|
||||
|
||||
// we know for sure we dropped those packets
|
||||
packets_explicitly_dropped_since_last_update: u64,
|
||||
}
|
||||
|
||||
pub(crate) enum PacketEvent {
|
||||
Sent(String),
|
||||
Received,
|
||||
Dropped(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CurrentPacketData {
|
||||
inner: Arc<PacketDataInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PacketDataInner {
|
||||
received: AtomicU64,
|
||||
sent: Mutex<PacketsMap>,
|
||||
dropped: Mutex<PacketsMap>,
|
||||
}
|
||||
|
||||
impl CurrentPacketData {
|
||||
pub(crate) fn new() -> Self {
|
||||
CurrentPacketData {
|
||||
inner: Arc::new(PacketDataInner {
|
||||
received: AtomicU64::new(0),
|
||||
sent: Mutex::new(HashMap::new()),
|
||||
dropped: Mutex::new(HashMap::new()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_received(&self) {
|
||||
self.inner.received.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
async fn increment_sent(&self, destination: String) {
|
||||
let mut unlocked = self.inner.sent.lock().await;
|
||||
let receiver_count = unlocked.entry(destination).or_insert(0);
|
||||
*receiver_count += 1;
|
||||
}
|
||||
|
||||
async fn increment_dropped(&self, destination: String) {
|
||||
let mut unlocked = self.inner.dropped.lock().await;
|
||||
let dropped_count = unlocked.entry(destination).or_insert(0);
|
||||
*dropped_count += 1;
|
||||
}
|
||||
|
||||
async fn acquire_and_reset(&self) -> (u64, PacketsMap, PacketsMap) {
|
||||
let mut unlocked_sent = self.inner.sent.lock().await;
|
||||
let mut unlocked_dropped = self.inner.dropped.lock().await;
|
||||
let received = self.inner.received.swap(0, Ordering::SeqCst);
|
||||
|
||||
let sent = std::mem::take(unlocked_sent.deref_mut());
|
||||
let dropped = std::mem::take(unlocked_dropped.deref_mut());
|
||||
|
||||
(received, sent, dropped)
|
||||
}
|
||||
}
|
||||
|
||||
struct UpdateHandler {
|
||||
current_data: CurrentPacketData,
|
||||
update_receiver: PacketDataReceiver,
|
||||
}
|
||||
|
||||
impl UpdateHandler {
|
||||
fn new(current_data: CurrentPacketData, update_receiver: PacketDataReceiver) -> Self {
|
||||
UpdateHandler {
|
||||
current_data,
|
||||
update_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
while let Some(packet_data) = self.update_receiver.next().await {
|
||||
match packet_data {
|
||||
PacketEvent::Received => self.current_data.increment_received(),
|
||||
PacketEvent::Sent(destination) => {
|
||||
self.current_data.increment_sent(destination).await
|
||||
}
|
||||
PacketEvent::Dropped(destination) => {
|
||||
self.current_data.increment_dropped(destination).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateSender(PacketDataSender);
|
||||
|
||||
impl UpdateSender {
|
||||
pub(crate) fn new(update_sender: PacketDataSender) -> Self {
|
||||
UpdateSender(update_sender)
|
||||
}
|
||||
|
||||
pub(crate) fn report_sent(&self, destination: String) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.0
|
||||
.unbounded_send(PacketEvent::Sent(destination))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// TODO: in the future this could be slightly optimised to get rid of the channel
|
||||
// in favour of incrementing value directly
|
||||
pub(crate) fn report_received(&self) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.0.unbounded_send(PacketEvent::Received).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn report_dropped(&self, destination: String) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.0
|
||||
.unbounded_send(PacketEvent::Dropped(destination))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
struct StatsUpdater {
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: NodeStatsWrapper,
|
||||
}
|
||||
|
||||
impl StatsUpdater {
|
||||
fn new(
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: NodeStatsWrapper,
|
||||
) -> Self {
|
||||
StatsUpdater {
|
||||
updating_delay,
|
||||
current_packet_data,
|
||||
current_stats,
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_stats(&self) {
|
||||
// grab new data since last update
|
||||
let (received, sent, dropped) = self.current_packet_data.acquire_and_reset().await;
|
||||
self.current_stats.update(received, sent, dropped).await;
|
||||
}
|
||||
|
||||
async fn run(&self) {
|
||||
loop {
|
||||
tokio::time::sleep(self.updating_delay).await;
|
||||
self.update_stats().await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: question: should this data still be logged to the console or should we perhaps remove it
|
||||
// since we have the http endpoint now?
|
||||
struct PacketStatsConsoleLogger {
|
||||
logging_delay: Duration,
|
||||
stats: NodeStatsWrapper,
|
||||
}
|
||||
|
||||
impl PacketStatsConsoleLogger {
|
||||
fn new(logging_delay: Duration, stats: NodeStatsWrapper) -> Self {
|
||||
PacketStatsConsoleLogger {
|
||||
logging_delay,
|
||||
stats,
|
||||
}
|
||||
}
|
||||
|
||||
async fn log_running_stats(&mut self) {
|
||||
let stats = self.stats.read().await;
|
||||
|
||||
// it's super unlikely this will ever fail, but anything involving time is super weird
|
||||
// so let's just guard against it
|
||||
if let Ok(time_difference) = stats.update_time.duration_since(stats.previous_update_time) {
|
||||
// we honestly don't care if it was 30.000828427s or 30.002461449s, 30s is enough
|
||||
let difference_secs = time_difference.as_secs();
|
||||
|
||||
info!(
|
||||
"Since startup mixed {} packets! ({} in last {} seconds)",
|
||||
stats.packets_sent_since_startup.values().sum::<u64>(),
|
||||
stats.packets_sent_since_last_update.values().sum::<u64>(),
|
||||
difference_secs,
|
||||
);
|
||||
if !stats.packets_explicitly_dropped_since_startup.is_empty() {
|
||||
info!(
|
||||
"Since startup dropped {} packets! ({} in last {} seconds)",
|
||||
stats
|
||||
.packets_explicitly_dropped_since_startup
|
||||
.values()
|
||||
.sum::<u64>(),
|
||||
stats
|
||||
.packets_explicitly_dropped_since_last_update
|
||||
.values()
|
||||
.sum::<u64>(),
|
||||
difference_secs,
|
||||
);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Since startup received {} packets ({} in last {} seconds)",
|
||||
stats.packets_received_since_startup,
|
||||
stats.packets_received_since_last_update,
|
||||
difference_secs,
|
||||
);
|
||||
trace!(
|
||||
"Since startup sent packets to the following: \n{:#?} \n And in last {} seconds: {:#?})",
|
||||
stats.packets_sent_since_startup,
|
||||
difference_secs,
|
||||
stats.packets_sent_since_last_update
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"Since startup mixed {} packets!",
|
||||
stats.packets_sent_since_startup.values().sum::<u64>(),
|
||||
);
|
||||
if !stats.packets_explicitly_dropped_since_startup.is_empty() {
|
||||
info!(
|
||||
"Since startup dropped {} packets!",
|
||||
stats
|
||||
.packets_explicitly_dropped_since_startup
|
||||
.values()
|
||||
.sum::<u64>(),
|
||||
);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Since startup received {} packets",
|
||||
stats.packets_received_since_startup
|
||||
);
|
||||
trace!(
|
||||
"Since startup sent packets to the following: \n{:#?}",
|
||||
stats.packets_sent_since_startup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
loop {
|
||||
tokio::time::sleep(self.logging_delay).await;
|
||||
self.log_running_stats().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// basically an easy single entry point to start all of the required tasks
|
||||
pub struct Controller {
|
||||
/// Responsible for handling data coming from UpdateSender
|
||||
update_handler: UpdateHandler,
|
||||
|
||||
/// Wrapper around channel sending information about new packet being received or sent
|
||||
update_sender: UpdateSender,
|
||||
|
||||
/// Responsible for logging stats to the console at given interval
|
||||
console_logger: PacketStatsConsoleLogger,
|
||||
|
||||
/// Responsible for updating stats at given interval
|
||||
stats_updater: StatsUpdater,
|
||||
|
||||
/// Pointer to the current node stats
|
||||
node_stats: NodeStatsWrapper,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub(crate) fn new(logging_delay: Duration, stats_updating_delay: Duration) -> Self {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
let shared_packet_data = CurrentPacketData::new();
|
||||
let shared_node_stats = NodeStatsWrapper::new();
|
||||
|
||||
Controller {
|
||||
update_handler: UpdateHandler::new(shared_packet_data.clone(), receiver),
|
||||
update_sender: UpdateSender::new(sender),
|
||||
console_logger: PacketStatsConsoleLogger::new(logging_delay, shared_node_stats.clone()),
|
||||
stats_updater: StatsUpdater::new(
|
||||
stats_updating_delay,
|
||||
shared_packet_data,
|
||||
shared_node_stats.clone(),
|
||||
),
|
||||
node_stats: shared_node_stats,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_node_stats_data_pointer(&self) -> NodeStatsWrapper {
|
||||
NodeStatsWrapper {
|
||||
inner: Arc::clone(&self.node_stats.inner),
|
||||
}
|
||||
}
|
||||
|
||||
// reporter is how node is going to be accessing the metrics data
|
||||
pub(crate) fn start(self) -> UpdateSender {
|
||||
// move out of self
|
||||
let mut update_handler = self.update_handler;
|
||||
let stats_updater = self.stats_updater;
|
||||
let mut console_logger = self.console_logger;
|
||||
|
||||
tokio::spawn(async move { update_handler.run().await });
|
||||
tokio::spawn(async move { stats_updater.run().await });
|
||||
tokio::spawn(async move { console_logger.run().await });
|
||||
|
||||
self.update_sender
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::metrics::MetricsReporter;
|
||||
use crate::node::node_statistics::UpdateSender;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::debug;
|
||||
use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, TimerError};
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use std::io;
|
||||
use tokio::time::{Duration, Instant};
|
||||
|
||||
// Delay + MixPacket vs Instant + MixPacket
|
||||
@@ -22,7 +22,7 @@ pub(crate) struct DelayForwarder {
|
||||
mixnet_client: mixnet_client::Client,
|
||||
packet_sender: PacketDelayForwardSender,
|
||||
packet_receiver: PacketDelayForwardReceiver,
|
||||
metrics_reporter: MetricsReporter,
|
||||
node_stats_update_sender: UpdateSender,
|
||||
}
|
||||
|
||||
impl DelayForwarder {
|
||||
@@ -31,7 +31,7 @@ impl DelayForwarder {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
metrics_reporter: MetricsReporter,
|
||||
node_stats_update_sender: UpdateSender,
|
||||
) -> Self {
|
||||
let client_config = mixnet_client::Config::new(
|
||||
initial_reconnection_backoff,
|
||||
@@ -47,7 +47,7 @@ impl DelayForwarder {
|
||||
mixnet_client: mixnet_client::Client::new(client_config),
|
||||
packet_sender,
|
||||
packet_receiver,
|
||||
metrics_reporter,
|
||||
node_stats_update_sender,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,20 @@ impl DelayForwarder {
|
||||
self.mixnet_client
|
||||
.send_without_response(next_hop, sphinx_packet, packet_mode)
|
||||
{
|
||||
debug!("failed to forward the packet to {} - {}", next_hop, err)
|
||||
if err.kind() == io::ErrorKind::WouldBlock {
|
||||
// we only know for sure if we dropped a packet if our sending queue was full
|
||||
// in any other case the connection might still be re-established (or created for the first time)
|
||||
// and the packet might get sent, but we won't know about it
|
||||
self.node_stats_update_sender
|
||||
.report_dropped(next_hop.to_string())
|
||||
} else if err.kind() == io::ErrorKind::NotConnected {
|
||||
// let's give the benefit of the doubt and assume we manage to establish connection
|
||||
self.node_stats_update_sender
|
||||
.report_sent(next_hop.to_string());
|
||||
}
|
||||
} else {
|
||||
self.metrics_reporter.report_sent(next_hop.to_string());
|
||||
self.node_stats_update_sender
|
||||
.report_sent(next_hop.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user