veilid-cli cleanup

This commit is contained in:
John Smith 2022-09-06 16:49:43 -04:00
parent 4d65903ee4
commit ca6c616d66
17 changed files with 689 additions and 108 deletions

11
Cargo.lock generated
View File

@ -1209,6 +1209,15 @@ dependencies = [
"xi-unicode", "xi-unicode",
] ]
[[package]]
name = "cursive_table_view"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8935dd87d19c54b7506b245bc988a7b4e65b1058e1d0d64c0ad9b3188e48060"
dependencies = [
"cursive_core",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "3.2.1" version = "3.2.1"
@ -5087,6 +5096,7 @@ dependencies = [
"cursive", "cursive",
"cursive-flexi-logger-view", "cursive-flexi-logger-view",
"cursive_buffered_backend", "cursive_buffered_backend",
"cursive_table_view",
"directories", "directories",
"flexi_logger", "flexi_logger",
"futures", "futures",
@ -5155,6 +5165,7 @@ dependencies = [
"nix 0.25.0", "nix 0.25.0",
"no-std-net", "no-std-net",
"once_cell", "once_cell",
"owning_ref",
"owo-colors", "owo-colors",
"parking_lot 0.12.1", "parking_lot 0.12.1",
"rand 0.7.3", "rand 0.7.3",

View File

@ -23,9 +23,9 @@ tokio-util = { version = "^0", features = ["compat"], optional = true}
async-tungstenite = { version = "^0.8" } async-tungstenite = { version = "^0.8" }
cursive-flexi-logger-view = { path = "../external/cursive-flexi-logger-view" } cursive-flexi-logger-view = { path = "../external/cursive-flexi-logger-view" }
cursive_buffered_backend = { path = "../external/cursive_buffered_backend" } cursive_buffered_backend = { path = "../external/cursive_buffered_backend" }
# cursive-multiplex = "0.4.0" # cursive-multiplex = "0.6.0"
# cursive_tree_view = "0.6.0" # cursive_tree_view = "0.6.0"
# cursive_table_view = "0.12.0" cursive_table_view = "0.14.0"
# cursive-tabs = "0.5.0" # cursive-tabs = "0.5.0"
clap = "^3" clap = "^3"
directories = "^4" directories = "^4"

View File

@ -8,7 +8,7 @@ use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use veilid_core::xx::{Eventual, EventualCommon}; use veilid_core::xx::{Eventual, EventualCommon};
use veilid_core::VeilidConfigLogLevel; use veilid_core::*;
pub fn convert_loglevel(s: &str) -> Result<VeilidConfigLogLevel, String> { pub fn convert_loglevel(s: &str) -> Result<VeilidConfigLogLevel, String> {
match s.to_ascii_lowercase().as_str() { match s.to_ascii_lowercase().as_str() {
@ -323,9 +323,12 @@ change_log_level - change the log level for a tracing layer
} }
pub fn update_network_status(&mut self, network: veilid_core::VeilidStateNetwork) { pub fn update_network_status(&mut self, network: veilid_core::VeilidStateNetwork) {
self.inner_mut() self.inner_mut().ui.set_network_status(
.ui network.started,
.set_network_status(network.started, network.bps_down, network.bps_up); network.bps_down,
network.bps_up,
network.peers,
);
} }
pub fn update_log(&mut self, log: veilid_core::VeilidStateLog) { pub fn update_log(&mut self, log: veilid_core::VeilidStateLog) {

View File

@ -12,6 +12,7 @@ use tools::*;
mod client_api_connection; mod client_api_connection;
mod command_processor; mod command_processor;
mod peers_table_view;
mod settings; mod settings;
mod tools; mod tools;
mod ui; mod ui;

View File

@ -0,0 +1,99 @@
use super::*;
use cursive_table_view::*;
use std::cmp::Ordering;
use veilid_core::PeerTableData;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum PeerTableColumn {
NodeId,
Address,
LatencyAvg,
TransferDownAvg,
TransferUpAvg,
}
// impl PeerTableColumn {
// fn as_str(&self) -> &str {
// match self {
// PeerTableColumn::NodeId => "Node Id",
// PeerTableColumn::Address => "Address",
// PeerTableColumn::LatencyAvg => "Latency",
// PeerTableColumn::TransferDownAvg => "Down",
// PeerTableColumn::TransferUpAvg => "Up",
// }
// }
// }
fn format_ts(ts: u64) -> String {
let secs = timestamp_to_secs(ts);
if secs >= 1.0 {
format!("{:.2}s", timestamp_to_secs(ts))
} else {
format!("{:.2}ms", timestamp_to_secs(ts) * 1000.0)
}
}
fn format_bps(bps: u64) -> String {
if bps >= 1024u64 * 1024u64 * 1024u64 {
format!("{:.2}GB/s", (bps / (1024u64 * 1024u64)) as f64 / 1024.0)
} else if bps >= 1024u64 * 1024u64 {
format!("{:.2}MB/s", (bps / 1024u64) as f64 / 1024.0)
} else if bps >= 1024u64 {
format!("{:.2}KB/s", bps as f64 / 1024.0)
} else {
format!("{:.2}B/s", bps as f64)
}
}
impl TableViewItem<PeerTableColumn> for PeerTableData {
fn to_column(&self, column: PeerTableColumn) -> String {
match column {
PeerTableColumn::NodeId => self.node_id.encode(),
PeerTableColumn::Address => format!(
"{:?}:{}",
self.peer_address.protocol_type(),
self.peer_address.to_socket_addr()
),
PeerTableColumn::LatencyAvg => format!(
"{}",
self.peer_stats
.latency
.as_ref()
.map(|l| format_ts(l.average))
.unwrap_or("---".to_owned())
),
PeerTableColumn::TransferDownAvg => format_bps(self.peer_stats.transfer.down.average),
PeerTableColumn::TransferUpAvg => format_bps(self.peer_stats.transfer.up.average),
}
}
fn cmp(&self, other: &Self, column: PeerTableColumn) -> Ordering
where
Self: Sized,
{
match column {
PeerTableColumn::NodeId => self.node_id.cmp(&other.node_id),
PeerTableColumn::Address => self.to_column(column).cmp(&other.to_column(column)),
PeerTableColumn::LatencyAvg => self
.peer_stats
.latency
.as_ref()
.map(|l| l.average)
.cmp(&other.peer_stats.latency.as_ref().map(|l| l.average)),
PeerTableColumn::TransferDownAvg => self
.peer_stats
.transfer
.down
.average
.cmp(&other.peer_stats.transfer.down.average),
PeerTableColumn::TransferUpAvg => self
.peer_stats
.transfer
.up
.average
.cmp(&other.peer_stats.transfer.up.average),
}
}
}
pub type PeersTableView = TableView<PeerTableData, PeerTableColumn>;

View File

@ -1,4 +1,5 @@
use crate::command_processor::*; use crate::command_processor::*;
use crate::peers_table_view::*;
use crate::settings::Settings; use crate::settings::Settings;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use cursive::align::*; use cursive::align::*;
@ -10,6 +11,7 @@ use cursive::views::*;
use cursive::Cursive; use cursive::Cursive;
use cursive::CursiveRunnable; use cursive::CursiveRunnable;
use cursive_flexi_logger_view::{CursiveLogWriter, FlexiLoggerView}; use cursive_flexi_logger_view::{CursiveLogWriter, FlexiLoggerView};
//use cursive_multiplex::*;
use log::*; use log::*;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
@ -20,7 +22,7 @@ use veilid_core::*;
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
/// ///
struct Dirty<T> { struct Dirty<T> {
pub value: T, value: T,
dirty: bool, dirty: bool,
} }
@ -52,6 +54,7 @@ struct UIState {
network_started: Dirty<bool>, network_started: Dirty<bool>,
network_down_up: Dirty<(f32, f32)>, network_down_up: Dirty<(f32, f32)>,
connection_state: Dirty<ConnectionState>, connection_state: Dirty<ConnectionState>,
peers_state: Dirty<Vec<PeerTableData>>,
} }
impl UIState { impl UIState {
@ -61,6 +64,7 @@ impl UIState {
network_started: Dirty::new(false), network_started: Dirty::new(false),
network_down_up: Dirty::new((0.0, 0.0)), network_down_up: Dirty::new((0.0, 0.0)),
connection_state: Dirty::new(ConnectionState::Disconnected), connection_state: Dirty::new(ConnectionState::Disconnected),
peers_state: Dirty::new(Vec::new()),
} }
} }
} }
@ -219,6 +223,9 @@ impl UI {
fn status_bar(s: &mut Cursive) -> ViewRef<TextView> { fn status_bar(s: &mut Cursive) -> ViewRef<TextView> {
s.find_name("status-bar").unwrap() s.find_name("status-bar").unwrap()
} }
fn peers(s: &mut Cursive) -> ViewRef<PeersTableView> {
s.find_name("peers").unwrap()
}
fn render_attachment_state<'a>(inner: &mut UIInner) -> &'a str { fn render_attachment_state<'a>(inner: &mut UIInner) -> &'a str {
match inner.ui_state.attachment_state.get() { match inner.ui_state.attachment_state.get() {
AttachmentState::Detached => " Detached [----]", AttachmentState::Detached => " Detached [----]",
@ -607,15 +614,23 @@ impl UI {
statusbar.set_content(status); statusbar.set_content(status);
} }
fn refresh_peers(s: &mut Cursive) {
let mut peers = UI::peers(s);
let inner = Self::inner_mut(s);
peers.set_items_stable(inner.ui_state.peers_state.get().clone());
}
fn update_cb(s: &mut Cursive) { fn update_cb(s: &mut Cursive) {
let mut inner = Self::inner_mut(s); let mut inner = Self::inner_mut(s);
let mut refresh_statusbar = false; let mut refresh_statusbar = false;
let mut refresh_button_attach = false; let mut refresh_button_attach = false;
let mut refresh_connection_dialog = false; let mut refresh_connection_dialog = false;
let mut refresh_peers = false;
if inner.ui_state.attachment_state.take_dirty() { if inner.ui_state.attachment_state.take_dirty() {
refresh_statusbar = true; refresh_statusbar = true;
refresh_button_attach = true; refresh_button_attach = true;
refresh_peers = true;
} }
if inner.ui_state.network_started.take_dirty() { if inner.ui_state.network_started.take_dirty() {
refresh_statusbar = true; refresh_statusbar = true;
@ -627,6 +642,10 @@ impl UI {
refresh_statusbar = true; refresh_statusbar = true;
refresh_button_attach = true; refresh_button_attach = true;
refresh_connection_dialog = true; refresh_connection_dialog = true;
refresh_peers = true;
}
if inner.ui_state.peers_state.take_dirty() {
refresh_peers = true;
} }
drop(inner); drop(inner);
@ -640,6 +659,9 @@ impl UI {
if refresh_connection_dialog { if refresh_connection_dialog {
Self::refresh_connection_dialog(s); Self::refresh_connection_dialog(s);
} }
if refresh_peers {
Self::refresh_peers(s);
}
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -686,30 +708,48 @@ impl UI {
siv.set_user_data(this.inner.clone()); siv.set_user_data(this.inner.clone());
// Create layouts // Create layouts
let mut mainlayout = LinearLayout::vertical().with_name("main-layout");
mainlayout.get_mut().add_child( let node_events_view = Panel::new(
Panel::new( FlexiLoggerView::new_scrollable()
FlexiLoggerView::new_scrollable() .with_name("node-events")
.with_name("node-events") .full_screen(),
.full_screen(), )
) .title_position(HAlign::Left)
.title_position(HAlign::Left) .title("Node Events");
.title("Node Events"),
); let peers_table_view = PeersTableView::new()
mainlayout.get_mut().add_child( .column(PeerTableColumn::NodeId, "Node Id", |c| c.width(43))
Panel::new(ScrollView::new( .column(PeerTableColumn::Address, "Address", |c| c)
TextView::new("Peer Table") .column(PeerTableColumn::LatencyAvg, "Ping", |c| c.width(8))
.with_name("peers") .column(PeerTableColumn::TransferDownAvg, "Down", |c| c.width(8))
.fixed_height(8) .column(PeerTableColumn::TransferUpAvg, "Up", |c| c.width(8))
.scrollable(), .with_name("peers")
)) .full_width()
.title_position(HAlign::Left) .min_height(8);
.title("Peers"),
); // attempt at using Mux. Mux has bugs, like resizing problems.
// let mut mux = Mux::new();
// let node_node_events_view = mux
// .add_below(node_events_view, mux.root().build().unwrap())
// .unwrap();
// let node_peers_table_view = mux
// .add_below(peers_table_view, node_node_events_view)
// .unwrap();
// mux.set_container_split_ratio(node_peers_table_view, 0.75)
// .unwrap();
// let mut mainlayout = LinearLayout::vertical();
// mainlayout.add_child(mux);
// Back to fixed layout
let mut mainlayout = LinearLayout::vertical();
mainlayout.add_child(node_events_view);
mainlayout.add_child(peers_table_view);
// ^^^ fixed layout
let mut command = StyledString::new(); let mut command = StyledString::new();
command.append_styled("Command> ", ColorStyle::title_primary()); command.append_styled("Command> ", ColorStyle::title_primary());
// //
mainlayout.get_mut().add_child( mainlayout.add_child(
LinearLayout::horizontal() LinearLayout::horizontal()
.child(TextView::new(command)) .child(TextView::new(command))
.child( .child(
@ -738,7 +778,7 @@ impl UI {
ColorStyle::highlight_inactive(), ColorStyle::highlight_inactive(),
); );
mainlayout.get_mut().add_child( mainlayout.add_child(
LinearLayout::horizontal() LinearLayout::horizontal()
.color(Some(ColorStyle::highlight_inactive())) .color(Some(ColorStyle::highlight_inactive()))
.child( .child(
@ -776,13 +816,20 @@ impl UI {
inner.ui_state.attachment_state.set(state); inner.ui_state.attachment_state.set(state);
let _ = inner.cb_sink.send(Box::new(UI::update_cb)); let _ = inner.cb_sink.send(Box::new(UI::update_cb));
} }
pub fn set_network_status(&mut self, started: bool, bps_down: u64, bps_up: u64) { pub fn set_network_status(
&mut self,
started: bool,
bps_down: u64,
bps_up: u64,
peers: Vec<PeerTableData>,
) {
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.borrow_mut();
inner.ui_state.network_started.set(started); inner.ui_state.network_started.set(started);
inner.ui_state.network_down_up.set(( inner.ui_state.network_down_up.set((
((bps_down as f64) / 1000.0f64) as f32, ((bps_down as f64) / 1000.0f64) as f32,
((bps_up as f64) / 1000.0f64) as f32, ((bps_up as f64) / 1000.0f64) as f32,
)); ));
inner.ui_state.peers_state.set(peers);
let _ = inner.cb_sink.send(Box::new(UI::update_cb)); let _ = inner.cb_sink.send(Box::new(UI::update_cb));
} }
pub fn set_connection_state(&mut self, state: ConnectionState) { pub fn set_connection_state(&mut self, state: ConnectionState) {
@ -790,6 +837,7 @@ impl UI {
inner.ui_state.connection_state.set(state); inner.ui_state.connection_state.set(state);
let _ = inner.cb_sink.send(Box::new(UI::update_cb)); let _ = inner.cb_sink.send(Box::new(UI::update_cb));
} }
pub fn add_node_event(&self, event: String) { pub fn add_node_event(&self, event: String) {
let inner = self.inner.borrow(); let inner = self.inner.borrow();
let color = *inner.log_colors.get(&Level::Info).unwrap(); let color = *inner.log_colors.get(&Level::Info).unwrap();

View File

@ -41,6 +41,7 @@ lazy_static = "^1"
directories = "^4" directories = "^4"
once_cell = "^1" once_cell = "^1"
json = "^0" json = "^0"
owning_ref = "^0"
flume = { version = "^0", features = ["async"] } flume = { version = "^0", features = ["async"] }
enumset = { version= "^1", features = ["serde"] } enumset = { version= "^1", features = ["serde"] }
backtrace = { version = "^0", optional = true } backtrace = { version = "^0", optional = true }

View File

@ -1680,12 +1680,30 @@ impl NetworkManager {
started: true, started: true,
bps_down: inner.stats.self_stats.transfer_stats.down.average, bps_down: inner.stats.self_stats.transfer_stats.down.average,
bps_up: inner.stats.self_stats.transfer_stats.up.average, bps_up: inner.stats.self_stats.transfer_stats.up.average,
peers: {
let mut out = Vec::new();
let routing_table = inner.routing_table.as_ref().unwrap();
for (k, v) in routing_table.get_recent_peers() {
if let Some(nr) = routing_table.lookup_node_ref(k) {
let peer_stats = nr.peer_stats();
let peer = PeerTableData {
node_id: k,
peer_address: v.last_connection.remote(),
peer_stats,
};
out.push(peer);
}
}
out
},
} }
} else { } else {
VeilidStateNetwork { VeilidStateNetwork {
started: false, started: false,
bps_down: 0, bps_down: 0,
bps_up: 0, bps_up: 0,
peers: Vec::new(),
} }
} }
} }

View File

@ -29,7 +29,7 @@ const RECENT_PEERS_TABLE_SIZE: usize = 64;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RecentPeersEntry { pub struct RecentPeersEntry {
last_connection: ConnectionDescriptor, pub last_connection: ConnectionDescriptor,
} }
/// RoutingTable rwlock-internal data /// RoutingTable rwlock-internal data
@ -776,13 +776,15 @@ impl RoutingTable {
descriptor: ConnectionDescriptor, descriptor: ConnectionDescriptor,
timestamp: u64, timestamp: u64,
) -> Option<NodeRef> { ) -> Option<NodeRef> {
self.create_node_ref(node_id, |e| { let out = self.create_node_ref(node_id, |e| {
// set the most recent node address for connection finding and udp replies
e.set_last_connection(descriptor, timestamp);
// this node is live because it literally just connected to us // this node is live because it literally just connected to us
e.touch_last_seen(timestamp); e.touch_last_seen(timestamp);
}) });
if let Some(nr) = &out {
// set the most recent node address for connection finding and udp replies
nr.set_last_connection(descriptor, timestamp);
}
out
} }
// Ticks about once per second // Ticks about once per second
@ -834,11 +836,8 @@ impl RoutingTable {
.collect() .collect()
} }
fn touch_recent_peer( pub fn touch_recent_peer(&self, node_id: DHTKey, last_connection: ConnectionDescriptor) {
inner: &mut RoutingTableInner, let mut inner = self.inner.write();
node_id: DHTKey,
last_connection: ConnectionDescriptor,
) {
inner inner
.recent_peers .recent_peers
.insert(node_id, RecentPeersEntry { last_connection }); .insert(node_id, RecentPeersEntry { last_connection });

View File

@ -200,6 +200,9 @@ impl NodeRef {
pub fn state(&self, cur_ts: u64) -> BucketEntryState { pub fn state(&self, cur_ts: u64) -> BucketEntryState {
self.operate(|_rti, e| e.state(cur_ts)) self.operate(|_rti, e| e.state(cur_ts))
} }
pub fn peer_stats(&self) -> PeerStats {
self.operate(|_rti, e| e.peer_stats().clone())
}
// Per-RoutingDomain accessors // Per-RoutingDomain accessors
pub fn make_peer_info(&self, routing_domain: RoutingDomain) -> Option<PeerInfo> { pub fn make_peer_info(&self, routing_domain: RoutingDomain) -> Option<PeerInfo> {
@ -322,7 +325,9 @@ impl NodeRef {
} }
pub fn set_last_connection(&self, connection_descriptor: ConnectionDescriptor, ts: u64) { pub fn set_last_connection(&self, connection_descriptor: ConnectionDescriptor, ts: u64) {
self.operate_mut(|_rti, e| e.set_last_connection(connection_descriptor, ts)) self.operate_mut(|_rti, e| e.set_last_connection(connection_descriptor, ts));
self.routing_table
.touch_recent_peer(self.node_id(), connection_descriptor);
} }
pub fn has_any_dial_info(&self) -> bool { pub fn has_any_dial_info(&self) -> bool {

View File

@ -0,0 +1,53 @@
use super::*;
#[derive(Debug, Clone)]
pub enum Origin {
Sender,
PrivateRoute(PrivateRoute),
}
impl Origin {
pub fn sender() -> Self {
Self::Sender
}
pub fn private_route(private_route: PrivateRoute) -> Self {
Self::PrivateRoute(private_route)
}
pub fn into_respond_to(self, destination: &Destination) -> Result<RespondTo, RPCError> {
match self {
Self::Sender => {
let peer = match destination {
Destination::Direct {
target,
safety_route_spec,
} => todo!(),
Destination::Relay {
relay,
target,
safety_route_spec,
} => todo!(),
Destination::PrivateRoute {
private_route,
safety_route_spec,
} => todo!(),
};
let routing_table = peer.routing_table();
let routing_domain = peer.best_routing_domain();
// Send some signed node info along with the question if this node needs to be replied to
if routing_table.has_valid_own_node_info()
&& !peer.has_seen_our_node_info(routing_domain)
{
let our_sni = self
.routing_table()
.get_own_signed_node_info(routing_domain);
RespondTo::Sender(Some(our_sni))
} else {
RespondTo::Sender(None)
}
}
Self::PrivateRoute(pr) => RespondTo::PrivateRoute(pr),
}
}
}

View File

@ -215,22 +215,32 @@ impl fmt::Display for VeilidLogLevel {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VeilidStateLog { pub struct VeilidStateLog {
pub log_level: VeilidLogLevel, pub log_level: VeilidLogLevel,
pub message: String, pub message: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VeilidStateAttachment { pub struct VeilidStateAttachment {
pub state: AttachmentState, pub state: AttachmentState,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PeerTableData {
pub node_id: DHTKey,
pub peer_address: PeerAddress,
pub peer_stats: PeerStats,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VeilidStateNetwork { pub struct VeilidStateNetwork {
pub started: bool, pub started: bool,
#[serde(with = "json_as_string")]
pub bps_down: u64, pub bps_down: u64,
#[serde(with = "json_as_string")]
pub bps_up: u64, pub bps_up: u64,
pub peers: Vec<PeerTableData>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -706,6 +716,15 @@ impl Address {
} }
} }
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Address::IPV4(v4) => write!(f, "{}", v4),
Address::IPV6(v6) => write!(f, "{}", v6),
}
}
}
impl FromStr for Address { impl FromStr for Address {
type Err = VeilidAPIError; type Err = VeilidAPIError;
fn from_str(host: &str) -> Result<Address, VeilidAPIError> { fn from_str(host: &str) -> Result<Address, VeilidAPIError> {
@ -1447,6 +1466,7 @@ impl PeerInfo {
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct PeerAddress { pub struct PeerAddress {
protocol_type: ProtocolType, protocol_type: ProtocolType,
#[serde(with = "json_as_string")]
socket_address: SocketAddress, socket_address: SocketAddress,
} }
@ -1565,42 +1585,53 @@ impl FromStr for NodeDialInfo {
} }
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct LatencyStats { pub struct LatencyStats {
#[serde(with = "json_as_string")]
pub fastest: u64, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies pub fastest: u64, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies
#[serde(with = "json_as_string")]
pub average: u64, // average latency over the ROLLING_LATENCIES_SIZE last latencies pub average: u64, // average latency over the ROLLING_LATENCIES_SIZE last latencies
#[serde(with = "json_as_string")]
pub slowest: u64, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies pub slowest: u64, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct TransferStats { pub struct TransferStats {
pub total: u64, // total amount transferred ever #[serde(with = "json_as_string")]
pub total: u64, // total amount transferred ever
#[serde(with = "json_as_string")]
pub maximum: u64, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts pub maximum: u64, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts
#[serde(with = "json_as_string")]
pub average: u64, // average rate over the ROLLING_TRANSFERS_SIZE last amounts pub average: u64, // average rate over the ROLLING_TRANSFERS_SIZE last amounts
#[serde(with = "json_as_string")]
pub minimum: u64, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts pub minimum: u64, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct TransferStatsDownUp { pub struct TransferStatsDownUp {
pub down: TransferStats, pub down: TransferStats,
pub up: TransferStats, pub up: TransferStats,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct RPCStats { pub struct RPCStats {
pub messages_sent: u32, // number of rpcs that have been sent in the total_time range pub messages_sent: u32, // number of rpcs that have been sent in the total_time range
pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range
pub questions_in_flight: u32, // number of questions issued that have yet to be answered pub questions_in_flight: u32, // number of questions issued that have yet to be answered
#[serde(with = "opt_json_as_string")]
pub last_question: Option<u64>, // when the peer was last questioned (either successfully or not) and we wanted an answer pub last_question: Option<u64>, // when the peer was last questioned (either successfully or not) and we wanted an answer
#[serde(with = "opt_json_as_string")]
pub last_seen_ts: Option<u64>, // when the peer was last seen for any reason, including when we first attempted to reach out to it pub last_seen_ts: Option<u64>, // when the peer was last seen for any reason, including when we first attempted to reach out to it
#[serde(with = "opt_json_as_string")]
pub first_consecutive_seen_ts: Option<u64>, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question) pub first_consecutive_seen_ts: Option<u64>, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question)
pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability
pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PeerStats { pub struct PeerStats {
pub time_added: u64, // when the peer was added to the routing table #[serde(with = "json_as_string")]
pub time_added: u64, // when the peer was added to the routing table
pub rpc_stats: RPCStats, // information about RPCs pub rpc_stats: RPCStats, // information about RPCs
pub latency: Option<LatencyStats>, // latencies for communications with the peer pub latency: Option<LatencyStats>, // latencies for communications with the peer
pub transfer: TransferStatsDownUp, // Stats for communications with the peer pub transfer: TransferStatsDownUp, // Stats for communications with the peer

View File

@ -40,3 +40,59 @@ pub fn serialize_json<T: Serialize + Debug>(val: T) -> String {
} }
} }
} }
pub mod json_as_string {
use std::fmt::Display;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serializer};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}
pub mod opt_json_as_string {
use std::fmt::Display;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serializer};
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
match value {
Some(v) => serializer.collect_str(v),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
match Option::<String>::deserialize(deserializer)? {
None => Ok(None),
Some(v) => Ok(Some(v.parse::<T>().map_err(de::Error::custom)?)),
}
}
}

View File

@ -3,7 +3,6 @@ import 'dart:typed_data';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import 'package:flutter_loggy/flutter_loggy.dart'; import 'package:flutter_loggy/flutter_loggy.dart';
@ -188,7 +187,7 @@ class _MyAppState extends State<MyApp> with UiLoggy {
if (update is VeilidUpdateLog) { if (update is VeilidUpdateLog) {
await processUpdateLog(update); await processUpdateLog(update);
} else { } else {
loggy.trace("Update: " + update.json.toString()); loggy.trace("Update: ${update.json}");
} }
} }
} }

View File

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -28,21 +28,14 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -63,21 +56,21 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.0.1"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.2" version: "6.1.4"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -134,30 +127,30 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.12"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
path_provider: path_provider:
dependency: transitive dependency: "direct main"
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -169,14 +162,14 @@ packages:
name: path_provider_android name: path_provider_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.14" version: "2.0.20"
path_provider_ios: path_provider_ios:
dependency: transitive dependency: transitive
description: description:
name: path_provider_ios name: path_provider_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.10" version: "2.0.11"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -204,7 +197,7 @@ packages:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.3"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -232,7 +225,7 @@ packages:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.27.4" version: "0.27.5"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -244,7 +237,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.9.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -265,21 +258,21 @@ packages:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.12"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -300,14 +293,14 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.7.0" version: "3.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0+1" version: "0.2.0+2"
sdks: sdks:
dart: ">=2.17.0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"

View File

@ -34,6 +34,8 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
loggy: ^2.0.1+1 loggy: ^2.0.1+1
flutter_loggy: ^2.0.1 flutter_loggy: ^2.0.1
path_provider: ^2.0.11
path: ^1.8.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -987,6 +987,243 @@ class VeilidConfig {
network = VeilidConfigNetwork.fromJson(json['network']); network = VeilidConfigNetwork.fromJson(json['network']);
} }
////////////
class LatencyStats {
BigInt fastest;
BigInt average;
BigInt slowest;
LatencyStats({
required this.fastest,
required this.average,
required this.slowest,
});
Map<String, dynamic> get json {
return {
'fastest': fastest.toString(),
'average': average.toString(),
'slowest': slowest.toString(),
};
}
LatencyStats.fromJson(Map<String, dynamic> json)
: fastest = BigInt.parse(json['fastest']),
average = BigInt.parse(json['average']),
slowest = BigInt.parse(json['slowest']);
}
////////////
class TransferStats {
BigInt total;
BigInt fastest;
BigInt average;
BigInt slowest;
TransferStats({
required this.total,
required this.fastest,
required this.average,
required this.slowest,
});
Map<String, dynamic> get json {
return {
'total': total.toString(),
'fastest': fastest.toString(),
'average': average.toString(),
'slowest': slowest.toString(),
};
}
TransferStats.fromJson(Map<String, dynamic> json)
: total = BigInt.parse(json['fastest']),
fastest = BigInt.parse(json['fastest']),
average = BigInt.parse(json['average']),
slowest = BigInt.parse(json['slowest']);
}
////////////
class TransferStatsDownUp {
TransferStats down;
TransferStats up;
TransferStatsDownUp({
required this.down,
required this.up,
});
Map<String, dynamic> get json {
return {
'down': down.toString(),
'up': up.toString(),
};
}
TransferStatsDownUp.fromJson(Map<String, dynamic> json)
: down = TransferStats.fromJson(json['down']),
up = TransferStats.fromJson(json['up']);
}
////////////
class RPCStats {
int messagesSent;
int messagesRcvd;
int questionsInFlight;
BigInt? lastQuestion;
BigInt? lastSeenTs;
BigInt? firstConsecutiveSeenTs;
int recentLostAnswers;
int failedToSend;
RPCStats({
required this.messagesSent,
required this.messagesRcvd,
required this.questionsInFlight,
required this.lastQuestion,
required this.lastSeenTs,
required this.firstConsecutiveSeenTs,
required this.recentLostAnswers,
required this.failedToSend,
});
Map<String, dynamic> get json {
return {
'messages_sent': messagesSent,
'messages_rcvd': messagesRcvd,
'questions_in_flight': questionsInFlight,
'last_question': lastQuestion?.toString(),
'last_seen_ts': lastSeenTs?.toString(),
'first_consecutive_seen_ts': firstConsecutiveSeenTs?.toString(),
'recent_lost_answers': recentLostAnswers,
'failed_to_send': failedToSend,
};
}
RPCStats.fromJson(Map<String, dynamic> json)
: messagesSent = json['messages_sent'],
messagesRcvd = json['messages_rcvd'],
questionsInFlight = json['questions_in_flight'],
lastQuestion = json['last_question'] != null
? BigInt.parse(json['last_question'])
: null,
lastSeenTs = json['last_seen_ts'] != null
? BigInt.parse(json['last_seen_ts'])
: null,
firstConsecutiveSeenTs = json['first_consecutive_seen_ts'] != null
? BigInt.parse(json['first_consecutive_seen_ts'])
: null,
recentLostAnswers = json['recent_lost_answers'],
failedToSend = json['failed_to_send'];
}
////////////
class PeerStats {
BigInt timeAdded;
RPCStats rpcStats;
LatencyStats? latency;
TransferStatsDownUp transfer;
PeerStats({
required this.timeAdded,
required this.rpcStats,
required this.latency,
required this.transfer,
});
Map<String, dynamic> get json {
return {
'time_added': timeAdded.toString(),
'rpc_stats': rpcStats.json,
'latency': latency?.json,
'transfer': transfer.json,
};
}
PeerStats.fromJson(Map<String, dynamic> json)
: timeAdded = BigInt.parse(json['time_added']),
rpcStats = RPCStats.fromJson(json['rpc_stats']),
latency = json['latency'] != null
? LatencyStats.fromJson(json['latency'])
: null,
transfer = TransferStatsDownUp.fromJson(json['transfer']);
}
////////////
class PeerTableData {
String nodeId;
PeerAddress peerAddress;
PeerStats peerStats;
PeerTableData({
required this.nodeId,
required this.peerAddress,
required this.peerStats,
});
Map<String, dynamic> get json {
return {
'node_id': nodeId,
'peer_address': peerAddress.json,
'peer_stats': peerStats.json,
};
}
PeerTableData.fromJson(Map<String, dynamic> json)
: nodeId = json['node_id'],
peerAddress = PeerAddress.fromJson(json['peer_address']),
peerStats = PeerStats.fromJson(json['peer_stats']);
}
//////////////////////////////////////
/// AttachmentState
enum ProtocolType {
udp,
tcp,
ws,
wss,
}
extension ProtocolTypeExt on ProtocolType {
String get json {
return name.toUpperCase();
}
}
ProtocolType protocolTypeFromJson(String j) {
return ProtocolType.values.byName(j.toLowerCase());
}
////////////
class PeerAddress {
ProtocolType protocolType;
String socketAddress;
PeerAddress({
required this.protocolType,
required this.socketAddress,
});
Map<String, dynamic> get json {
return {
'protocol_type': protocolType.json,
'socket_address': socketAddress,
};
}
PeerAddress.fromJson(Map<String, dynamic> json)
: protocolType = protocolTypeFromJson(json['protocol_type']),
socketAddress = json['socket_address'];
}
////////////////////////////////////// //////////////////////////////////////
/// VeilidUpdate /// VeilidUpdate
@ -996,16 +1233,17 @@ abstract class VeilidUpdate {
case "Log": case "Log":
{ {
return VeilidUpdateLog( return VeilidUpdateLog(
veilidLogLevelFromJson(json["log_level"]), json["message"]); logLevel: veilidLogLevelFromJson(json["log_level"]),
message: json["message"]);
} }
case "Attachment": case "Attachment":
{ {
return VeilidUpdateAttachment(attachmentStateFromJson(json["state"])); return VeilidUpdateAttachment(
state: VeilidStateAttachment.fromJson(json));
} }
case "Network": case "Network":
{ {
return VeilidUpdateNetwork( return VeilidUpdateNetwork(state: VeilidStateNetwork.fromJson(json));
json["started"], json["bps_up"], json["bps_down"]);
} }
default: default:
{ {
@ -1021,7 +1259,10 @@ class VeilidUpdateLog implements VeilidUpdate {
final VeilidLogLevel logLevel; final VeilidLogLevel logLevel;
final String message; final String message;
// //
VeilidUpdateLog(this.logLevel, this.message); VeilidUpdateLog({
required this.logLevel,
required this.message,
});
@override @override
Map<String, dynamic> get json { Map<String, dynamic> get json {
@ -1034,34 +1275,28 @@ class VeilidUpdateLog implements VeilidUpdate {
} }
class VeilidUpdateAttachment implements VeilidUpdate { class VeilidUpdateAttachment implements VeilidUpdate {
final AttachmentState state; final VeilidStateAttachment state;
// //
VeilidUpdateAttachment(this.state); VeilidUpdateAttachment({required this.state});
@override @override
Map<String, dynamic> get json { Map<String, dynamic> get json {
return { var jsonRep = state.json;
'kind': "Attachment", jsonRep['kind'] = "Attachment";
'state': state.json, return jsonRep;
};
} }
} }
class VeilidUpdateNetwork implements VeilidUpdate { class VeilidUpdateNetwork implements VeilidUpdate {
final bool started; final VeilidStateNetwork state;
final int bpsDown;
final int bpsUp;
// //
VeilidUpdateNetwork(this.started, this.bpsDown, this.bpsUp); VeilidUpdateNetwork({required this.state});
@override @override
Map<String, dynamic> get json { Map<String, dynamic> get json {
return { var jsonRep = state.json;
'kind': "Network", jsonRep['kind'] = "Network";
'started': started, return jsonRep;
'bps_down': bpsDown,
'bps_up': bpsUp
};
} }
} }
@ -1075,6 +1310,12 @@ class VeilidStateAttachment {
VeilidStateAttachment.fromJson(Map<String, dynamic> json) VeilidStateAttachment.fromJson(Map<String, dynamic> json)
: state = attachmentStateFromJson(json['state']); : state = attachmentStateFromJson(json['state']);
Map<String, dynamic> get json {
return {
'state': state.json,
};
}
} }
////////////////////////////////////// //////////////////////////////////////
@ -1082,11 +1323,30 @@ class VeilidStateAttachment {
class VeilidStateNetwork { class VeilidStateNetwork {
final bool started; final bool started;
final int bpsDown;
final int bpsUp;
final List<PeerTableData> peers;
VeilidStateNetwork(this.started); VeilidStateNetwork(
{required this.started,
required this.bpsDown,
required this.bpsUp,
required this.peers});
VeilidStateNetwork.fromJson(Map<String, dynamic> json) VeilidStateNetwork.fromJson(Map<String, dynamic> json)
: started = json['started']; : started = json['started'],
bpsDown = json['bps_down'],
bpsUp = json['bps_up'],
peers = json['peers'].map((j) => PeerTableData.fromJson(j)).toList();
Map<String, dynamic> get json {
return {
'started': started,
'bps_down': bpsDown,
'bps_up': bpsUp,
'peers': peers.map((p) => p.json).toList(),
};
}
} }
////////////////////////////////////// //////////////////////////////////////
@ -1096,11 +1356,13 @@ class VeilidState {
final VeilidStateAttachment attachment; final VeilidStateAttachment attachment;
final VeilidStateNetwork network; final VeilidStateNetwork network;
VeilidState(this.attachment, this.network);
VeilidState.fromJson(Map<String, dynamic> json) VeilidState.fromJson(Map<String, dynamic> json)
: attachment = VeilidStateAttachment.fromJson(json['attachment']), : attachment = VeilidStateAttachment.fromJson(json['attachment']),
network = VeilidStateNetwork.fromJson(json['network']); network = VeilidStateNetwork.fromJson(json['network']);
Map<String, dynamic> get json {
return {'attachment': attachment.json, 'network': network.json};
}
} }
////////////////////////////////////// //////////////////////////////////////