Merge relay debug view

Fix a few conflicts
This commit is contained in:
William Casarin
2025-01-04 13:54:29 -08:00
12 changed files with 518 additions and 20 deletions

View File

@@ -2,21 +2,21 @@ use crate::Error;
use nostrdb::{Filter, Note};
use serde_json::json;
#[derive(Debug)]
pub struct EventClientMessage<'a> {
note: Note<'a>,
#[derive(Debug, Clone)]
pub struct EventClientMessage {
pub note_json: String,
}
impl EventClientMessage<'_> {
pub fn to_json(&self) -> Result<String, Error> {
Ok(format!("[\"EVENT\", {}]", self.note.json()?))
impl EventClientMessage {
pub fn to_json(&self) -> String {
format!("[\"EVENT\", {}]", self.note_json)
}
}
/// Messages sent by clients, received by relays
#[derive(Debug)]
pub enum ClientMessage<'a> {
Event(EventClientMessage<'a>),
#[derive(Debug, Clone)]
pub enum ClientMessage {
Event(EventClientMessage),
Req {
sub_id: String,
filters: Vec<Filter>,
@@ -27,9 +27,11 @@ pub enum ClientMessage<'a> {
Raw(String),
}
impl<'a> ClientMessage<'a> {
pub fn event(note: Note<'a>) -> Self {
ClientMessage::Event(EventClientMessage { note })
impl ClientMessage {
pub fn event(note: Note) -> Result<Self, Error> {
Ok(ClientMessage::Event(EventClientMessage {
note_json: note.json()?,
}))
}
pub fn raw(raw: String) -> Self {
@@ -46,7 +48,7 @@ impl<'a> ClientMessage<'a> {
pub fn to_json(&self) -> Result<String, Error> {
Ok(match self {
Self::Event(ecm) => ecm.to_json()?,
Self::Event(ecm) => ecm.to_json(),
Self::Raw(raw) => raw.clone(),
Self::Req { sub_id, filters } => {
if filters.is_empty() {

View File

@@ -18,6 +18,7 @@ pub use profile::Profile;
pub use pubkey::Pubkey;
pub use relay::message::{RelayEvent, RelayMessage};
pub use relay::pool::{PoolEvent, PoolRelay, RelayPool};
pub use relay::subs_debug::{OwnedRelayEvent, RelayLogEvent, SubsDebug, TransferStats};
pub use relay::{Relay, RelayStatus};
pub type Result<T> = std::result::Result<T, error::Error>;

View File

@@ -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>),

View File

@@ -13,6 +13,7 @@ use tracing::{debug, error};
pub mod message;
pub mod pool;
pub mod subs_debug;
#[derive(Debug, Copy, Clone)]
pub enum RelayStatus {
@@ -91,7 +92,7 @@ impl MulticastRelay {
}
pub fn send(&self, msg: &EventClientMessage) -> Result<()> {
let json = msg.to_json()?;
let json = msg.to_json();
let len = json.len();
debug!("writing to multicast relay");

View File

@@ -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,
@@ -124,6 +126,7 @@ impl WebsocketRelay {
pub struct RelayPool {
pub relays: Vec<PoolRelay>,
pub ping_rate: Duration,
pub debug: Option<SubsDebug>,
}
impl Default for RelayPool {
@@ -138,6 +141,7 @@ impl RelayPool {
RelayPool {
relays: vec![],
ping_rate: Duration::from_secs(25),
debug: None,
}
}
@@ -150,6 +154,10 @@ impl RelayPool {
Ok(())
}
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
@@ -174,6 +182,9 @@ 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.url().to_owned(), cmd);
}
if let Err(err) = relay.send(cmd) {
error!("error sending {:?} to {}: {err}", cmd, relay.url());
}
@@ -182,7 +193,11 @@ impl RelayPool {
pub fn unsubscribe(&mut self, subid: String) {
for relay in &mut self.relays {
if let Err(err) = relay.send(&ClientMessage::close(subid.clone())) {
let cmd = ClientMessage::close(subid.clone());
if let Some(debug) = &mut self.debug {
debug.send_cmd(relay.url().to_owned(), &cmd);
}
if let Err(err) = relay.send(&cmd) {
error!(
"error unsubscribing from {} on {}: {err}",
&subid,
@@ -194,6 +209,13 @@ impl RelayPool {
pub fn subscribe(&mut self, subid: String, filter: Vec<Filter>) {
for relay in &mut self.relays {
if let Some(debug) = &mut self.debug {
debug.send_cmd(
relay.url().to_owned(),
&ClientMessage::req(subid.clone(), filter.clone()),
);
}
if let Err(err) = relay.send(&ClientMessage::req(subid.clone(), filter.clone())) {
error!("error subscribing to {}: {err}", relay.url());
}
@@ -255,8 +277,11 @@ impl RelayPool {
pub fn send_to(&mut self, cmd: &ClientMessage, relay_url: &str) {
for relay in &mut self.relays {
if relay.url() == relay_url {
if let Some(debug) = &mut self.debug {
debug.send_cmd(relay.url().to_owned(), cmd);
}
if let Err(err) = relay.send(cmd) {
error!("error sending {:?} to {}: {err}", cmd, relay_url);
error!("send_to err: {err}");
}
return;
}
@@ -350,10 +375,17 @@ impl RelayPool {
}
}
}
return Some(PoolEvent {
if let Some(debug) = &mut self.debug {
debug.receive_cmd(relay.url().to_owned(), (&event).into());
}
let pool_event = PoolEvent {
event,
relay: relay.url(),
});
};
return Some(pool_event);
}
}

View File

@@ -0,0 +1,267 @@
use std::{collections::HashMap, mem, time::SystemTime};
use ewebsock::WsMessage;
use nostrdb::Filter;
use crate::{ClientMessage, Error, RelayEvent, RelayMessage};
use super::message::calculate_command_result_size;
type RelayId = String;
type SubId = String;
pub struct SubsDebug {
data: HashMap<RelayId, RelayStats>,
time_incd: SystemTime,
pub relay_events_selection: Option<RelayId>,
}
#[derive(Default)]
pub struct RelayStats {
pub count: TransferStats,
pub events: Vec<RelayLogEvent>,
pub sub_data: HashMap<SubId, SubStats>,
}
#[derive(Clone)]
pub enum RelayLogEvent {
Send(ClientMessage),
Recieve(OwnedRelayEvent),
}
#[derive(Clone)]
pub enum OwnedRelayEvent {
Opened,
Closed,
Other(String),
Error(String),
Message(String),
}
impl From<RelayEvent<'_>> 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<RelayId, RelayStats> {
&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_client_message_size(message: &ClientMessage) -> usize {
match message {
ClientMessage::Event(note) => note.note_json.len() + 10, // 10 is ["EVENT",]
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::<usize>()
}
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::Io(_)
| 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<Filter>) -> 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
}

View File

@@ -7,6 +7,7 @@ pub struct Args {
pub keys: Vec<Keypair>,
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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -61,7 +61,7 @@ impl PostAction {
}
};
pool.send(&enostr::ClientMessage::event(note));
pool.send(&enostr::ClientMessage::event(note)?);
drafts.get_from_post_type(&self.post_type).clear();
Ok(())

View File

@@ -195,7 +195,7 @@ mod preview {
}
}
impl<'a> Preview for EditProfileView<'a> {
impl Preview for EditProfileView<'_> {
type Prev = EditProfilePreivew;
fn preview(_cfg: PreviewConfig) -> Self::Prev {

View File

@@ -0,0 +1,173 @@
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 RelayDebugView<'_> {
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<String>, events: Vec<RelayLogEvent>| {
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();
}
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 {
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)
}
}