From 612c89118f47fd8489475fdde5d82ca5259c0942 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 29 Dec 2024 20:44:03 -0500 Subject: [PATCH 1/6] add subs_debug Signed-off-by: kernelkind --- crates/enostr/src/client/message.rs | 2 +- crates/enostr/src/lib.rs | 1 + crates/enostr/src/relay/message.rs | 6 + crates/enostr/src/relay/mod.rs | 1 + crates/enostr/src/relay/subs_debug.rs | 289 ++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 crates/enostr/src/relay/subs_debug.rs diff --git a/crates/enostr/src/client/message.rs b/crates/enostr/src/client/message.rs index e912cdf2..f5ac36e0 100644 --- a/crates/enostr/src/client/message.rs +++ b/crates/enostr/src/client/message.rs @@ -3,7 +3,7 @@ use nostrdb::Filter; use serde_json::json; /// Messages sent by clients, received by relays -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ClientMessage { Event { note: Note, diff --git a/crates/enostr/src/lib.rs b/crates/enostr/src/lib.rs index e33e563a..e6d7d162 100644 --- a/crates/enostr/src/lib.rs +++ b/crates/enostr/src/lib.rs @@ -18,6 +18,7 @@ pub use profile::Profile; pub use pubkey::Pubkey; pub use relay::message::{RelayEvent, RelayMessage}; pub use relay::pool::{PoolEvent, RelayPool}; +pub use relay::subs_debug::{OwnedRelayEvent, RelayLogEvent, SubsDebug, TransferStats}; pub use relay::{Relay, RelayStatus}; pub type Result = std::result::Result; diff --git a/crates/enostr/src/relay/message.rs b/crates/enostr/src/relay/message.rs index a7dd12e5..bc0cb589 100644 --- a/crates/enostr/src/relay/message.rs +++ b/crates/enostr/src/relay/message.rs @@ -8,6 +8,12 @@ pub struct CommandResult<'a> { message: &'a str, } +pub fn calculate_command_result_size(result: &CommandResult) -> usize { + std::mem::size_of_val(result) + + result.event_id.as_bytes().len() + + result.message.as_bytes().len() +} + #[derive(Debug, Eq, PartialEq)] pub enum RelayMessage<'a> { OK(CommandResult<'a>), diff --git a/crates/enostr/src/relay/mod.rs b/crates/enostr/src/relay/mod.rs index 532252b4..68554f75 100644 --- a/crates/enostr/src/relay/mod.rs +++ b/crates/enostr/src/relay/mod.rs @@ -8,6 +8,7 @@ use tracing::{debug, error, info}; pub mod message; pub mod pool; +pub mod subs_debug; #[derive(Debug)] pub enum RelayStatus { diff --git a/crates/enostr/src/relay/subs_debug.rs b/crates/enostr/src/relay/subs_debug.rs new file mode 100644 index 00000000..32db510e --- /dev/null +++ b/crates/enostr/src/relay/subs_debug.rs @@ -0,0 +1,289 @@ +use std::{collections::HashMap, mem, time::SystemTime}; + +use ewebsock::WsMessage; +use nostrdb::Filter; + +use crate::{ClientMessage, Error, Note, RelayEvent, RelayMessage}; + +use super::message::calculate_command_result_size; + +type RelayId = String; +type SubId = String; + +pub struct SubsDebug { + data: HashMap, + time_incd: SystemTime, + pub relay_events_selection: Option, +} + +#[derive(Default)] +pub struct RelayStats { + pub count: TransferStats, + pub events: Vec, + pub sub_data: HashMap, +} + +#[derive(Clone)] +pub enum RelayLogEvent { + Send(ClientMessage), + Recieve(OwnedRelayEvent), +} + +#[derive(Clone)] +pub enum OwnedRelayEvent { + Opened, + Closed, + Other(String), + Error(String), + Message(String), +} + +impl From> for OwnedRelayEvent { + fn from(value: RelayEvent<'_>) -> Self { + match value { + RelayEvent::Opened => OwnedRelayEvent::Opened, + RelayEvent::Closed => OwnedRelayEvent::Closed, + RelayEvent::Other(ws_message) => { + let ws_str = match ws_message { + WsMessage::Binary(_) => "Binary".to_owned(), + WsMessage::Text(t) => format!("Text:{}", t), + WsMessage::Unknown(u) => format!("Unknown:{}", u), + WsMessage::Ping(_) => "Ping".to_owned(), + WsMessage::Pong(_) => "Pong".to_owned(), + }; + OwnedRelayEvent::Other(ws_str) + } + RelayEvent::Error(error) => OwnedRelayEvent::Error(error.to_string()), + RelayEvent::Message(relay_message) => { + let relay_msg = match relay_message { + RelayMessage::OK(_) => "OK".to_owned(), + RelayMessage::Eose(s) => format!("EOSE:{}", s), + RelayMessage::Event(_, s) => format!("EVENT:{}", s), + RelayMessage::Notice(s) => format!("NOTICE:{}", s), + }; + OwnedRelayEvent::Message(relay_msg) + } + } + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct RelaySub { + pub(crate) subid: String, + pub(crate) filter: String, +} + +#[derive(Default)] +pub struct SubStats { + pub filter: String, + pub count: TransferStats, +} + +#[derive(Default)] +pub struct TransferStats { + pub up_total: usize, + pub down_total: usize, + + // 1 sec < last tick < 2 sec + pub up_sec_prior: usize, + pub down_sec_prior: usize, + + // < 1 sec since last tick + up_sec_cur: usize, + down_sec_cur: usize, +} + +impl Default for SubsDebug { + fn default() -> Self { + Self { + data: Default::default(), + time_incd: SystemTime::now(), + relay_events_selection: None, + } + } +} + +impl SubsDebug { + pub fn get_data(&self) -> &HashMap { + &self.data + } + + pub(crate) fn send_cmd(&mut self, relay: String, cmd: &ClientMessage) { + let data = self.data.entry(relay).or_default(); + let msg_num_bytes = calculate_client_message_size(cmd); + match cmd { + ClientMessage::Req { sub_id, filters } => { + data.sub_data.insert( + sub_id.to_string(), + SubStats { + filter: filters_to_string(filters), + count: Default::default(), + }, + ); + } + + ClientMessage::Close { sub_id } => { + data.sub_data.remove(sub_id); + } + + _ => {} + } + + data.count.up_sec_cur += msg_num_bytes; + + data.events.push(RelayLogEvent::Send(cmd.clone())); + } + + pub(crate) fn receive_cmd(&mut self, relay: String, cmd: RelayEvent) { + let data = self.data.entry(relay).or_default(); + let msg_num_bytes = calculate_relay_event_size(&cmd); + if let RelayEvent::Message(RelayMessage::Event(sid, _)) = cmd { + if let Some(sub_data) = data.sub_data.get_mut(sid) { + let c = &mut sub_data.count; + c.down_sec_cur += msg_num_bytes; + } + }; + + data.count.down_sec_cur += msg_num_bytes; + + data.events.push(RelayLogEvent::Recieve(cmd.into())); + } + + pub fn try_increment_stats(&mut self) { + let cur_time = SystemTime::now(); + if let Ok(dur) = cur_time.duration_since(self.time_incd) { + if dur.as_secs() >= 1 { + self.time_incd = cur_time; + self.internal_inc_stats(); + } + } + } + + fn internal_inc_stats(&mut self) { + for relay_data in self.data.values_mut() { + let c = &mut relay_data.count; + inc_data_count(c); + + for sub in relay_data.sub_data.values_mut() { + inc_data_count(&mut sub.count); + } + } + } +} + +fn inc_data_count(c: &mut TransferStats) { + c.up_total += c.up_sec_cur; + c.up_sec_prior = c.up_sec_cur; + + c.down_total += c.down_sec_cur; + c.down_sec_prior = c.down_sec_cur; + + c.up_sec_cur = 0; + c.down_sec_cur = 0; +} + +fn calculate_note_size(note: &Note) -> usize { + let stack_size = mem::size_of_val(¬e.id) + + mem::size_of_val(¬e.pubkey) + + mem::size_of_val(¬e.created_at) + + mem::size_of_val(¬e.kind) + + mem::size_of_val(¬e.tags) + + mem::size_of_val(¬e.content) + + mem::size_of_val(¬e.sig); + + let heap_size = note + .tags + .iter() + .map(|tag| { + tag.iter().map(|s| s.len()).sum::() + tag.len() * mem::size_of::() + }) + .sum::() + + note.tags.len() * mem::size_of::>() + + note.content.len() + + note.sig.len(); + + stack_size + heap_size +} + +fn calculate_client_message_size(message: &ClientMessage) -> usize { + match message { + ClientMessage::Event { note } => mem::size_of_val(message) + calculate_note_size(note), + ClientMessage::Req { sub_id, filters } => { + mem::size_of_val(message) + + mem::size_of_val(sub_id) + + sub_id.as_bytes().len() + + filters.iter().map(mem::size_of_val).sum::() + } + ClientMessage::Close { sub_id } => { + mem::size_of_val(message) + mem::size_of_val(sub_id) + sub_id.as_bytes().len() + } + ClientMessage::Raw(data) => mem::size_of_val(message) + data.as_bytes().len(), + } +} + +fn calculate_relay_event_size(event: &RelayEvent<'_>) -> usize { + let base_size = mem::size_of_val(event); // Size of the enum on the stack + + let variant_size = match event { + RelayEvent::Opened | RelayEvent::Closed => 0, // No additional data + RelayEvent::Other(ws_message) => calculate_ws_message_size(ws_message), + RelayEvent::Error(error) => calculate_error_size(error), + RelayEvent::Message(message) => calculate_relay_message_size(message), + }; + + base_size + variant_size +} + +fn calculate_ws_message_size(message: &WsMessage) -> usize { + match message { + WsMessage::Binary(vec) | WsMessage::Ping(vec) | WsMessage::Pong(vec) => { + mem::size_of_val(message) + vec.len() + } + WsMessage::Text(string) | WsMessage::Unknown(string) => { + mem::size_of_val(message) + string.as_bytes().len() + } + } +} + +fn calculate_error_size(error: &Error) -> usize { + match error { + Error::Empty + | Error::DecodeFailed + | Error::HexDecodeFailed + | Error::InvalidBech32 + | Error::InvalidByteSize + | Error::InvalidSignature + | Error::InvalidPublicKey => mem::size_of_val(error), // No heap usage + + Error::Json(json_err) => mem::size_of_val(error) + json_err.to_string().as_bytes().len(), + + Error::Nostrdb(nostrdb_err) => { + mem::size_of_val(error) + nostrdb_err.to_string().as_bytes().len() + } + + Error::Generic(string) => mem::size_of_val(error) + string.as_bytes().len(), + } +} + +fn calculate_relay_message_size(message: &RelayMessage) -> usize { + match message { + RelayMessage::OK(result) => calculate_command_result_size(result), + RelayMessage::Eose(str_ref) + | RelayMessage::Event(str_ref, _) + | RelayMessage::Notice(str_ref) => mem::size_of_val(message) + str_ref.as_bytes().len(), + } +} + +fn filters_to_string(f: &Vec) -> String { + let mut cur_str = String::new(); + for filter in f { + if let Ok(json) = filter.json() { + if !cur_str.is_empty() { + cur_str.push_str(", "); + } + cur_str.push_str(&json); + } + } + + cur_str +} From 54dfa0c945359374f76b9b1ccd4e77b35c281b97 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 29 Dec 2024 20:45:38 -0500 Subject: [PATCH 2/6] integrate SubsDebug into RelayPool Signed-off-by: kernelkind --- crates/enostr/src/relay/pool.rs | 38 ++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/crates/enostr/src/relay/pool.rs b/crates/enostr/src/relay/pool.rs index 4d2fe8f2..daca2aa4 100644 --- a/crates/enostr/src/relay/pool.rs +++ b/crates/enostr/src/relay/pool.rs @@ -13,6 +13,8 @@ use ewebsock::{WsEvent, WsMessage}; #[cfg(not(target_arch = "wasm32"))] use tracing::{debug, error}; +use super::subs_debug::SubsDebug; + #[derive(Debug)] pub struct PoolEvent<'a> { pub relay: &'a str, @@ -58,6 +60,7 @@ impl PoolRelay { pub struct RelayPool { pub relays: Vec, pub ping_rate: Duration, + pub debug: Option, } impl Default for RelayPool { @@ -72,9 +75,14 @@ impl RelayPool { RelayPool { relays: vec![], ping_rate: Duration::from_secs(25), + debug: None, } } + pub fn use_debug(&mut self) { + self.debug = Some(SubsDebug::default()); + } + pub fn ping_rate(&mut self, duration: Duration) -> &mut Self { self.ping_rate = duration; self @@ -99,18 +107,32 @@ impl RelayPool { pub fn send(&mut self, cmd: &ClientMessage) { for relay in &mut self.relays { + if let Some(debug) = &mut self.debug { + debug.send_cmd(relay.relay.url.clone(), cmd); + } relay.relay.send(cmd); } } pub fn unsubscribe(&mut self, subid: String) { for relay in &mut self.relays { - relay.relay.send(&ClientMessage::close(subid.clone())); + let cmd = &ClientMessage::close(subid.clone()); + if let Some(debug) = &mut self.debug { + debug.send_cmd(relay.relay.url.clone(), cmd); + } + relay.relay.send(cmd); } } pub fn subscribe(&mut self, subid: String, filter: Vec) { for relay in &mut self.relays { + if let Some(debug) = &mut self.debug { + debug.send_cmd( + relay.relay.url.clone(), + &ClientMessage::req(subid.clone(), filter.clone()), + ); + } + relay.relay.subscribe(subid.clone(), filter.clone()); } } @@ -164,6 +186,9 @@ impl RelayPool { for relay in &mut self.relays { let relay = &mut relay.relay; if relay.url == relay_url { + if let Some(debug) = &mut self.debug { + debug.send_cmd(relay.url.clone(), cmd); + } relay.send(cmd); return; } @@ -242,10 +267,17 @@ impl RelayPool { } } } - return Some(PoolEvent { + + if let Some(debug) = &mut self.debug { + debug.receive_cmd(relay.url.clone(), (&event).into()); + } + + let pool_event = PoolEvent { event, relay: &relay.url, - }); + }; + + return Some(pool_event); } } From 83411fdef055c09ca985854ffc61811920262ded Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 29 Dec 2024 20:59:05 -0500 Subject: [PATCH 3/6] RelayDebugView Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/relay_debug.rs | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 crates/notedeck_columns/src/ui/relay_debug.rs diff --git a/crates/notedeck_columns/src/ui/relay_debug.rs b/crates/notedeck_columns/src/ui/relay_debug.rs new file mode 100644 index 00000000..22ec6a5c --- /dev/null +++ b/crates/notedeck_columns/src/ui/relay_debug.rs @@ -0,0 +1,164 @@ +use egui::ScrollArea; +use enostr::{RelayLogEvent, SubsDebug}; + +pub struct RelayDebugView<'a> { + debug: &'a mut SubsDebug, +} + +impl<'a> RelayDebugView<'a> { + pub fn new(debug: &'a mut SubsDebug) -> Self { + Self { debug } + } +} + +impl<'a> RelayDebugView<'a> { + pub fn ui(&mut self, ui: &mut egui::Ui) { + ScrollArea::vertical() + .id_salt(ui.id().with("relays_debug")) + .max_height(ui.max_rect().height() / 2.0) + .show(ui, |ui| { + ui.label("Active Relays:"); + for (relay_str, data) in self.debug.get_data() { + egui::CollapsingHeader::new(format!( + "{} {} {}", + relay_str, + format_total(&data.count), + format_sec(&data.count) + )) + .default_open(true) + .show(ui, |ui| { + ui.horizontal_wrapped(|ui| { + for (i, sub_data) in data.sub_data.values().enumerate() { + ui.label(format!( + "Filter {} ({})", + i + 1, + format_sec(&sub_data.count) + )) + .on_hover_cursor(egui::CursorIcon::Help) + .on_hover_text(sub_data.filter.to_string()); + } + }) + }); + } + }); + + ui.separator(); + egui::ComboBox::from_label("Show events from relay") + .selected_text( + self.debug + .relay_events_selection + .as_ref() + .map_or(String::new(), |s| s.clone()), + ) + .show_ui(ui, |ui| { + let mut make_selection = None; + for relay in self.debug.get_data().keys() { + if ui + .selectable_label( + if let Some(s) = &self.debug.relay_events_selection { + *s == *relay + } else { + false + }, + relay, + ) + .clicked() + { + make_selection = Some(relay.clone()); + } + } + if make_selection.is_some() { + self.debug.relay_events_selection = make_selection + } + }); + let show_relay_evs = + |ui: &mut egui::Ui, relay: Option, events: Vec| { + for ev in events { + ui.horizontal_wrapped(|ui| { + if let Some(r) = &relay { + ui.label("relay").on_hover_text(r.clone()); + } + match ev { + RelayLogEvent::Send(client_message) => { + ui.label("SEND: "); + let msg = &match client_message { + enostr::ClientMessage::Event { .. } => "Event", + enostr::ClientMessage::Req { .. } => "Req", + enostr::ClientMessage::Close { .. } => "Close", + enostr::ClientMessage::Raw(_) => "Raw", + }; + + if let Ok(json) = client_message.to_json() { + ui.label(*msg).on_hover_text(json) + } else { + ui.label(*msg) + } + } + RelayLogEvent::Recieve(e) => { + ui.label("RECIEVE: "); + match e { + enostr::OwnedRelayEvent::Opened => ui.label("Opened"), + enostr::OwnedRelayEvent::Closed => ui.label("Closed"), + enostr::OwnedRelayEvent::Other(s) => { + ui.label("Other").on_hover_text(s) + } + enostr::OwnedRelayEvent::Error(s) => { + ui.label("Error").on_hover_text(s) + } + enostr::OwnedRelayEvent::Message(s) => { + ui.label("Message").on_hover_text(s) + } + } + } + } + }); + } + }; + + ScrollArea::vertical() + .id_salt(ui.id().with("events")) + .show(ui, |ui| { + if let Some(relay) = &self.debug.relay_events_selection { + if let Some(data) = self.debug.get_data().get(relay) { + show_relay_evs(ui, None, data.events.clone()); + } + } else { + for (relay, data) in self.debug.get_data() { + show_relay_evs(ui, Some(relay.clone()), data.events.clone()); + } + } + }); + + self.debug.try_increment_stats(); + } +} + +fn format_sec(c: &enostr::TransferStats) -> String { + format!( + "⬇{} ⬆️{}", + byte_to_string(c.down_sec_prior), + byte_to_string(c.up_sec_prior) + ) +} + +fn format_total(c: &enostr::TransferStats) -> String { + format!( + "total: ⬇{} ⬆️{}", + byte_to_string(c.down_total), + byte_to_string(c.up_total) + ) +} + +const MB: usize = 1_048_576; +const KB: usize = 1024; +fn byte_to_string(b: usize) -> String { + if b >= MB { + let mbs = b as f32 / MB as f32; + format!("{:.2} MB", mbs) + } else if b >= KB { + let kbs = b as f32 / KB as f32; + format!("{:.2} KB", kbs) + } else { + format!("{} B", b) + } +} From 7d9679e05c7b449d68897fe59c8eba895d2d8e5a Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 1 Jan 2025 15:27:13 -0500 Subject: [PATCH 4/6] add egui window Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/relay_debug.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/notedeck_columns/src/ui/relay_debug.rs b/crates/notedeck_columns/src/ui/relay_debug.rs index 22ec6a5c..ef427a03 100644 --- a/crates/notedeck_columns/src/ui/relay_debug.rs +++ b/crates/notedeck_columns/src/ui/relay_debug.rs @@ -131,6 +131,15 @@ impl<'a> RelayDebugView<'a> { self.debug.try_increment_stats(); } + + pub fn window(ctx: &egui::Context, debug: &mut SubsDebug) { + let mut open = true; + egui::Window::new("Relay Debugger") + .open(&mut open) + .show(ctx, |ui| { + RelayDebugView::new(debug).ui(ui); + }); + } } fn format_sec(c: &enostr::TransferStats) -> String { From d88036ecba017aecfe445c5e017fbc43687c6c37 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 1 Jan 2025 15:34:06 -0500 Subject: [PATCH 5/6] add relay_debug arg Signed-off-by: kernelkind --- crates/notedeck/src/args.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/notedeck/src/args.rs b/crates/notedeck/src/args.rs index b9b87bc3..b84aaf70 100644 --- a/crates/notedeck/src/args.rs +++ b/crates/notedeck/src/args.rs @@ -7,6 +7,7 @@ pub struct Args { pub keys: Vec, pub light: bool, pub debug: bool, + pub relay_debug: bool, /// Enable when running tests so we don't panic on app startup pub tests: bool, @@ -24,6 +25,7 @@ impl Args { keys: vec![], light: false, debug: false, + relay_debug: false, tests: false, use_keystore: true, dbpath: None, @@ -108,6 +110,8 @@ impl Args { res.relays.push(relay.clone()); } else if arg == "--no-keystore" { res.use_keystore = false; + } else if arg == "--relay-debug" { + res.relay_debug = true; } i += 1; From efa0bfcca1bbe337dfe6ed3b65a7a5f8c53970d6 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 1 Jan 2025 15:47:40 -0500 Subject: [PATCH 6/6] integrate RelayDebugView Signed-off-by: kernelkind --- crates/notedeck_chrome/src/app.rs | 11 +++++++++++ crates/notedeck_columns/src/ui/mod.rs | 1 + 2 files changed, 12 insertions(+) diff --git a/crates/notedeck_chrome/src/app.rs b/crates/notedeck_chrome/src/app.rs index 6e4c62c4..0210bd0b 100644 --- a/crates/notedeck_chrome/src/app.rs +++ b/crates/notedeck_chrome/src/app.rs @@ -7,6 +7,7 @@ use notedeck::{ use enostr::RelayPool; use nostrdb::{Config, Ndb, Transaction}; +use notedeck_columns::ui::relay_debug::RelayDebugView; use std::cell::RefCell; use std::path::Path; use std::rc::Rc; @@ -80,6 +81,16 @@ impl eframe::App for Notedeck { self.app_rect_handler.try_save_app_size(ctx); + if self.args.relay_debug { + if self.pool.debug.is_none() { + self.pool.use_debug(); + } + + if let Some(debug) = &mut self.pool.debug { + RelayDebugView::window(ctx, debug); + } + } + #[cfg(feature = "profiling")] puffin_egui::profiler_window(ctx); } diff --git a/crates/notedeck_columns/src/ui/mod.rs b/crates/notedeck_columns/src/ui/mod.rs index 5a4bb159..dc228566 100644 --- a/crates/notedeck_columns/src/ui/mod.rs +++ b/crates/notedeck_columns/src/ui/mod.rs @@ -10,6 +10,7 @@ pub mod note; pub mod preview; pub mod profile; pub mod relay; +pub mod relay_debug; pub mod side_panel; pub mod support; pub mod thread;