aboutsummaryrefslogtreecommitdiffstats
path: root/src/transport/traffic.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/transport/traffic.rs')
-rw-r--r--src/transport/traffic.rs244
1 files changed, 243 insertions, 1 deletions
diff --git a/src/transport/traffic.rs b/src/transport/traffic.rs
index b237db2..b7907fb 100644
--- a/src/transport/traffic.rs
+++ b/src/transport/traffic.rs
@@ -8,7 +8,8 @@
//! A message dropped by the debug firewall (in the paths layer, below) still counts as
//! sent (the firewall plays a lossy network) but not as received.
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
+use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use anyhow::Result;
@@ -29,6 +30,11 @@ impl Counter {
self.messages += 1;
self.bytes += bytes as u64;
}
+
+ fn absorb(&mut self, other: Counter) {
+ self.messages += other.messages;
+ self.bytes += other.bytes;
+ }
}
#[derive(Debug, Default, Clone, Copy, Serialize)]
@@ -45,9 +51,42 @@ pub(crate) struct ByKind {
pub(crate) identity: Counter,
/// PING and PONG (`transport::paths`), counted by the paths layer under the address.
pub(crate) paths: Counter,
+ /// LINKS and LINKS_REPLY (`swarm::links`).
+ pub(crate) links: Counter,
}
impl ByKind {
+ fn absorb(&mut self, other: &ByKind) {
+ for kind in [
+ MessageKind::Hello,
+ MessageKind::HelloReply,
+ MessageKind::Sync,
+ MessageKind::Status,
+ MessageKind::StatusReply,
+ MessageKind::Blob,
+ MessageKind::Who,
+ MessageKind::Ping,
+ MessageKind::Links,
+ ] {
+ let theirs = *other.get(kind);
+ self.get_mut(kind).absorb(theirs);
+ }
+ }
+
+ fn get(&self, kind: MessageKind) -> &Counter {
+ match kind {
+ MessageKind::Hello => &self.hello,
+ MessageKind::HelloReply => &self.hello_reply,
+ MessageKind::Sync => &self.sync,
+ MessageKind::Status => &self.status,
+ MessageKind::StatusReply => &self.status_reply,
+ MessageKind::Blob => &self.blob,
+ MessageKind::Who | MessageKind::Me => &self.identity,
+ MessageKind::Ping | MessageKind::Pong => &self.paths,
+ MessageKind::Links | MessageKind::LinksReply => &self.links,
+ }
+ }
+
fn get_mut(&mut self, kind: MessageKind) -> &mut Counter {
match kind {
MessageKind::Hello => &mut self.hello,
@@ -58,6 +97,7 @@ impl ByKind {
MessageKind::Blob => &mut self.blob,
MessageKind::Who | MessageKind::Me => &mut self.identity,
MessageKind::Ping | MessageKind::Pong => &mut self.paths,
+ MessageKind::Links | MessageKind::LinksReply => &mut self.links,
}
}
}
@@ -82,6 +122,169 @@ struct Table {
#[derive(Debug, Default)]
pub(crate) struct Traffic {
table: Mutex<Table>,
+ /// The messages one by one, while the dashboard looks.
+ pub(crate) log: MessageLog,
+}
+
+/// Messages kept in the log (`MessageLog`) at most, and the text of one.
+const LOGGED: usize = 1000;
+const LOGGED_TEXT: usize = 8 * 1024;
+
+/// The node's messages one by one, for the dashboard's console (`api::dashboard`):
+/// kept only while it looks (`watch`), the latest `LOGGED`. Those this layer sees: the
+/// node's own, not the paths layer's PINGs and PONGs nor the identity layer's WHO and
+/// ME (counted under their addresses, `ByKind`), but the PINGs and PONGs the node is
+/// handed.
+#[derive(Debug, Default)]
+pub(crate) struct MessageLog {
+ watching: AtomicBool,
+ inner: Mutex<LogInner>,
+}
+
+#[derive(Debug, Default)]
+struct LogInner {
+ next: u64,
+ entries: VecDeque<Logged>,
+}
+
+/// One message, as the dashboard shows it.
+#[derive(Debug, Clone, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct Logged {
+ /// Counts up, from 1.
+ pub(crate) seq: u64,
+ /// Unix milliseconds, by the system's clock.
+ pub(crate) at: u64,
+ /// `sent`, `received` or `refused` (the transport wouldn't send it).
+ pub(crate) way: &'static str,
+ pub(crate) peer: String,
+ /// The message's type, as the docs name it: `HELLO_REPLY`, `SYNC`, …
+ #[serde(rename = "type")]
+ pub(crate) kind: &'static str,
+ /// Its kind among the counters (`ByKind`'s names): `helloReply`, `sync`, …
+ pub(crate) counted: &'static str,
+ pub(crate) bytes: usize,
+ /// The message, written out (`Debug`; sizes for its bytes), `LOGGED_TEXT` at most.
+ pub(crate) text: String,
+ /// What ties it to other messages (`related`): those with the same are one
+ /// exchange.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) related: Option<String>,
+ /// The address it went to or came from, where the paths layer logged it under that
+ /// (its own PINGs and PONGs) and the node there is known: `peer` is the node then
+ /// (`api::dashboard`).
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) address: Option<String>,
+}
+
+/// What ties `message`, with `peer`, to other messages: a PING and its PONG (the
+/// nonce alone: a PING may go to an address and its PONG come back up from the node
+/// there, `paths`), a LINKS and its replies (the nonce), a HELLO and its replies (the run of the
+/// node that sent the HELLO, which the reply echoes), a blob's requests and answers, from
+/// any peer (its ID), and a delta offered in a SYNC with the blob it is fetched as. None
+/// for the rest (a STATUS and its reply carry nothing of the kind).
+fn related(peer: &str, message: &ParsedMessage) -> Option<String> {
+ use crate::transport::{Hex, SyncBody};
+ Some(match message {
+ ParsedMessage::Ping(nonce) | ParsedMessage::Pong { nonce, .. } => {
+ format!("ping {nonce}")
+ }
+ ParsedMessage::Links(request) => format!("links {peer} {}", request.nonce),
+ ParsedMessage::LinksReply(reply) => format!("links {peer} {}", reply.nonce),
+ ParsedMessage::Hello(hello) => format!("hello {peer} {}", Hex(&hello.run_id)),
+ ParsedMessage::HelloReply(reply) => format!("hello {peer} {}", Hex(&reply.run_id)),
+ ParsedMessage::Blob(blob) => format!("blob {}", blob.id),
+ ParsedMessage::Sync(envelope) => match &envelope.body {
+ SyncBody::Delta(spec) => {
+ format!("blob {}", crate::transport::BlobId::Delta(spec.id()))
+ }
+ SyncBody::Message(_) => return None,
+ },
+ _ => return None,
+ })
+}
+
+// Read by the dashboard only.
+#[cfg_attr(not(feature = "dashboard"), allow(dead_code))]
+impl MessageLog {
+ /// Keeps messages from now on (`true`), or no more, forgetting those kept.
+ pub(crate) fn watch(&self, on: bool) {
+ self.watching.store(on, Ordering::Relaxed);
+ if !on {
+ self.inner.lock_or_recover().entries.clear();
+ }
+ }
+
+ pub(crate) fn note(
+ &self,
+ way: &'static str,
+ peer: &str,
+ message: &ParsedMessage,
+ bytes: usize,
+ ) {
+ if !self.watching.load(Ordering::Relaxed) {
+ return;
+ }
+ let at = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map_or(0, |since| since.as_millis() as u64);
+ let mut text = format!("{message:#?}");
+ if text.len() > LOGGED_TEXT {
+ let mut cut = LOGGED_TEXT;
+ while !text.is_char_boundary(cut) {
+ cut -= 1;
+ }
+ text.truncate(cut);
+ text.push_str("\n…");
+ }
+ let (kind, counted) = names(message.kind());
+ let mut inner = self.inner.lock_or_recover();
+ inner.next += 1;
+ let seq = inner.next;
+ inner.entries.push_back(Logged {
+ seq,
+ at,
+ way,
+ peer: peer.to_owned(),
+ kind,
+ counted,
+ bytes,
+ text,
+ related: related(peer, message),
+ address: None,
+ });
+ if inner.entries.len() > LOGGED {
+ inner.entries.pop_front();
+ }
+ }
+
+ /// The messages after `seq`, the latest `most` of them.
+ pub(crate) fn after(&self, seq: u64, most: usize) -> Vec<Logged> {
+ let inner = self.inner.lock_or_recover();
+ let newer: Vec<&Logged> = inner.entries.iter().filter(|e| e.seq > seq).collect();
+ newer[newer.len().saturating_sub(most)..]
+ .iter()
+ .map(|entry| (*entry).clone())
+ .collect()
+ }
+}
+
+/// A message kind's type name, and its counter's (`ByKind`).
+fn names(kind: MessageKind) -> (&'static str, &'static str) {
+ match kind {
+ MessageKind::Hello => ("HELLO", "hello"),
+ MessageKind::HelloReply => ("HELLO_REPLY", "helloReply"),
+ MessageKind::Sync => ("SYNC", "sync"),
+ MessageKind::Status => ("STATUS", "status"),
+ MessageKind::StatusReply => ("STATUS_REPLY", "statusReply"),
+ MessageKind::Blob => ("BLOB", "blob"),
+ MessageKind::Who => ("WHO", "identity"),
+ MessageKind::Me => ("ME", "identity"),
+ MessageKind::Ping => ("PING", "paths"),
+ MessageKind::Pong => ("PONG", "paths"),
+ MessageKind::Links => ("LINKS", "links"),
+ MessageKind::LinksReply => ("LINKS_REPLY", "links"),
+ }
}
impl Traffic {
@@ -115,6 +318,22 @@ impl Traffic {
self.lock().peers.get(peer).copied()
}
+ /// A node's traffic: counted under it, and under `addresses`, where it is (the
+ /// layers below the node count what they send and receive on their own there: WHO
+ /// and ME, the paths layer's PINGs and PONGs).
+ pub(crate) fn node(&self, node: &str, addresses: &[String]) -> PeerTraffic {
+ let table = self.lock();
+ let mut traffic = table.peers.get(node).copied().unwrap_or_default();
+ for address in addresses.iter().filter(|address| address.as_str() != node) {
+ if let Some(there) = table.peers.get(address) {
+ traffic.sent.absorb(&there.sent);
+ traffic.received.absorb(&there.received);
+ traffic.refused.absorb(&there.refused);
+ }
+ }
+ traffic
+ }
+
pub(crate) fn total(&self) -> PeerTraffic {
self.lock().total
}
@@ -152,6 +371,10 @@ impl Transport for CountingTransport {
self.inner.delivery_limit()
}
+ fn sync_reply_timeout(&self) -> std::time::Duration {
+ self.inner.sync_reply_timeout()
+ }
+
fn local_addresses(&self) -> Vec<String> {
self.inner.local_addresses()
}
@@ -176,6 +399,14 @@ impl Transport for CountingTransport {
self.inner.discovered_seeds()
}
+ fn on_seeds_found(&self, found: std::sync::Arc<tokio::sync::Notify>) {
+ self.inner.on_seeds_found(found)
+ }
+
+ fn on_reachable(&self, reachable: std::sync::Arc<tokio::sync::Notify>) {
+ self.inner.on_reachable(reachable)
+ }
+
fn transports(&self) -> Vec<String> {
self.inner.transports()
}
@@ -204,6 +435,10 @@ impl Transport for CountingTransport {
self.inner.rtt(peer)
}
+ fn clock_offset(&self, peer: &str) -> Option<crate::transport::paths::ClockOffset> {
+ self.inner.clock_offset(peer)
+ }
+
async fn resolve_seed(&self, seed: &str) -> Result<String> {
self.inner.resolve_seed(seed).await
}
@@ -221,6 +456,12 @@ impl Transport for CountingTransport {
self.traffic
.refused(&message.peer, message.kind, message.payload_len);
}
+ if let Some(parsed) = &message.message {
+ let way = if result.is_ok() { "sent" } else { "refused" };
+ self.traffic
+ .log
+ .note(way, &message.peer, parsed, message.payload_len);
+ }
result
}
@@ -229,6 +470,7 @@ impl Transport for CountingTransport {
self.inner
.start_consumer(Box::new(move |sender, message, payload_len| {
traffic.received(&sender, message.kind(), payload_len);
+ traffic.log.note("received", &sender, &message, payload_len);
handler(sender, message, payload_len)
}))
.await