cli: Add `rad node debug` command
This commit is contained in:
parent
749e823985
commit
30c9b0db0b
|
|
@ -33,6 +33,7 @@ Usage
|
||||||
rad node start [--foreground] [--verbose] [<option>...] [-- <node-option>...]
|
rad node start [--foreground] [--verbose] [<option>...] [-- <node-option>...]
|
||||||
rad node stop [<option>...]
|
rad node stop [<option>...]
|
||||||
rad node logs [-n <lines>]
|
rad node logs [-n <lines>]
|
||||||
|
rad node debug [<option>...]
|
||||||
rad node connect <nid>@<addr> [<option>...]
|
rad node connect <nid>@<addr> [<option>...]
|
||||||
rad node routing [--rid <rid>] [--nid <nid>] [--json] [<option>...]
|
rad node routing [--rid <rid>] [--nid <nid>] [--json] [<option>...]
|
||||||
rad node events [--timeout <secs>] [-n <count>] [<option>...]
|
rad node events [--timeout <secs>] [-n <count>] [<option>...]
|
||||||
|
|
@ -98,6 +99,7 @@ pub enum Operation {
|
||||||
lines: usize,
|
lines: usize,
|
||||||
},
|
},
|
||||||
Status,
|
Status,
|
||||||
|
Debug,
|
||||||
Sessions,
|
Sessions,
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +115,7 @@ pub enum OperationName {
|
||||||
Start,
|
Start,
|
||||||
#[default]
|
#[default]
|
||||||
Status,
|
Status,
|
||||||
|
Debug,
|
||||||
Sessions,
|
Sessions,
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +155,7 @@ impl Args for Options {
|
||||||
"status" => op = Some(OperationName::Status),
|
"status" => op = Some(OperationName::Status),
|
||||||
"stop" => op = Some(OperationName::Stop),
|
"stop" => op = Some(OperationName::Stop),
|
||||||
"sessions" => op = Some(OperationName::Sessions),
|
"sessions" => op = Some(OperationName::Sessions),
|
||||||
|
"debug" => op = Some(OperationName::Debug),
|
||||||
|
|
||||||
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
||||||
},
|
},
|
||||||
|
|
@ -222,6 +226,7 @@ impl Args for Options {
|
||||||
path: path.unwrap_or(PathBuf::from("radicle-node")),
|
path: path.unwrap_or(PathBuf::from("radicle-node")),
|
||||||
},
|
},
|
||||||
OperationName::Status => Operation::Status,
|
OperationName::Status => Operation::Status,
|
||||||
|
OperationName::Debug => Operation::Debug,
|
||||||
OperationName::Sessions => Operation::Sessions,
|
OperationName::Sessions => Operation::Sessions,
|
||||||
OperationName::Stop => Operation::Stop,
|
OperationName::Stop => Operation::Stop,
|
||||||
};
|
};
|
||||||
|
|
@ -250,6 +255,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
Operation::Db { args } => {
|
Operation::Db { args } => {
|
||||||
commands::db(&profile, args)?;
|
commands::db(&profile, args)?;
|
||||||
}
|
}
|
||||||
|
Operation::Debug => {
|
||||||
|
control::debug(&mut node)?;
|
||||||
|
}
|
||||||
Operation::Sessions => {
|
Operation::Sessions => {
|
||||||
let sessions = control::sessions(&node)?;
|
let sessions = control::sessions(&node)?;
|
||||||
if let Some(table) = sessions {
|
if let Some(table) = sessions {
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,13 @@ pub fn stop(node: Node) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debug(node: &mut Node) -> anyhow::Result<()> {
|
||||||
|
let json = node.debug()?;
|
||||||
|
term::json::to_pretty(&json, Path::new("debug.json"))?.print();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn logs(lines: usize, follow: Option<time::Duration>, profile: &Profile) -> anyhow::Result<()> {
|
pub fn logs(lines: usize, follow: Option<time::Duration>, profile: &Profile) -> anyhow::Result<()> {
|
||||||
let logs = profile.home.node().join("node.log");
|
let logs = profile.home.node().join("node.log");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,11 @@ where
|
||||||
}
|
}
|
||||||
Err(e) => return Err(CommandError::Runtime(e)),
|
Err(e) => return Err(CommandError::Runtime(e)),
|
||||||
},
|
},
|
||||||
|
Command::Debug => {
|
||||||
|
let debug = handle.debug()?;
|
||||||
|
|
||||||
|
CommandResult::Okay(debug).to_writer(writer)?;
|
||||||
|
}
|
||||||
Command::Shutdown => {
|
Command::Shutdown => {
|
||||||
log::debug!(target: "control", "Shutdown requested..");
|
log::debug!(target: "control", "Shutdown requested..");
|
||||||
// Channel might already be disconnected if shutdown
|
// Channel might already be disconnected if shutdown
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crossbeam_channel as chan;
|
||||||
use radicle::node::{ConnectOptions, ConnectResult, Link, Seeds};
|
use radicle::node::{ConnectOptions, ConnectResult, Link, Seeds};
|
||||||
use radicle::storage::refs::RefsAt;
|
use radicle::storage::refs::RefsAt;
|
||||||
use reactor::poller::popol::PopolWaker;
|
use reactor::poller::popol::PopolWaker;
|
||||||
|
use serde_json::json;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::identity::RepoId;
|
use crate::identity::RepoId;
|
||||||
|
|
@ -319,4 +320,45 @@ impl radicle::node::Handle for Handle {
|
||||||
.shutdown()
|
.shutdown()
|
||||||
.map_err(|_| Error::ChannelDisconnected)
|
.map_err(|_| Error::ChannelDisconnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(&self) -> Result<serde_json::Value, Self::Error> {
|
||||||
|
let (sender, receiver) = chan::bounded(1);
|
||||||
|
let query: Arc<QueryState> = Arc::new(move |state| {
|
||||||
|
let debug = serde_json::json!({
|
||||||
|
"outboxSize": state.outbox().len(),
|
||||||
|
"fetching": state.fetching().iter().map(|(rid, state)| {
|
||||||
|
json!({
|
||||||
|
"rid": rid,
|
||||||
|
"from": state.from,
|
||||||
|
"refsAt": state.refs_at,
|
||||||
|
"subscribers": state.subscribers.len(),
|
||||||
|
})
|
||||||
|
}).collect::<Vec<_>>(),
|
||||||
|
"queue": state.queue().iter().map(|fetch| {
|
||||||
|
json!({
|
||||||
|
"rid": fetch.rid,
|
||||||
|
"from": fetch.from,
|
||||||
|
"refsAt": fetch.refs_at,
|
||||||
|
})
|
||||||
|
}).collect::<Vec<_>>(),
|
||||||
|
"rateLimiter": state.limiter().buckets.iter().map(|(host, bucket)| {
|
||||||
|
json!({
|
||||||
|
"host": host.to_string(),
|
||||||
|
"bucket": bucket
|
||||||
|
})
|
||||||
|
}).collect::<Vec<_>>(),
|
||||||
|
"metrics": state.metrics(),
|
||||||
|
});
|
||||||
|
sender.send(debug).ok();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
let (err_sender, err_receiver) = chan::bounded(1);
|
||||||
|
self.command(service::Command::QueryState(query, err_sender))?;
|
||||||
|
err_receiver.recv()??;
|
||||||
|
|
||||||
|
let debug = receiver.recv()?;
|
||||||
|
|
||||||
|
Ok(debug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,38 @@ pub use message::INVENTORY_LIMIT;
|
||||||
/// Maximum number of project git references imposed by message size limits.
|
/// Maximum number of project git references imposed by message size limits.
|
||||||
pub use message::REF_REMOTE_LIMIT;
|
pub use message::REF_REMOTE_LIMIT;
|
||||||
|
|
||||||
|
/// Metrics we track.
|
||||||
|
#[derive(Clone, Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Metrics {
|
||||||
|
peers: HashMap<NodeId, PeerMetrics>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metrics {
|
||||||
|
/// Get metrics for the given peer.
|
||||||
|
pub fn peer(&mut self, nid: NodeId) -> &mut PeerMetrics {
|
||||||
|
self.peers.entry(nid).or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-peer metrics we track.
|
||||||
|
#[derive(Clone, Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PeerMetrics {
|
||||||
|
pub received_git_bytes: usize,
|
||||||
|
pub received_fetch_requests: usize,
|
||||||
|
pub received_bytes: usize,
|
||||||
|
pub received_gossip_messages: usize,
|
||||||
|
pub sent_bytes: usize,
|
||||||
|
pub sent_fetch_requests: usize,
|
||||||
|
pub sent_git_bytes: usize,
|
||||||
|
pub sent_gossip_messages: usize,
|
||||||
|
pub streams_opened: usize,
|
||||||
|
pub inbound_connection_attempts: usize,
|
||||||
|
pub outbound_connection_attempts: usize,
|
||||||
|
pub disconnects: usize,
|
||||||
|
}
|
||||||
|
|
||||||
/// Result of syncing our routing table with a node's inventory.
|
/// Result of syncing our routing table with a node's inventory.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SyncedRouting {
|
struct SyncedRouting {
|
||||||
|
|
@ -252,13 +284,13 @@ enum TryFetchError<'a> {
|
||||||
|
|
||||||
/// Fetch state for an ongoing fetch.
|
/// Fetch state for an ongoing fetch.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FetchState {
|
pub struct FetchState {
|
||||||
/// Node we're fetching from.
|
/// Node we're fetching from.
|
||||||
from: NodeId,
|
pub from: NodeId,
|
||||||
/// What refs we're fetching.
|
/// What refs we're fetching.
|
||||||
refs_at: Vec<RefsAt>,
|
pub refs_at: Vec<RefsAt>,
|
||||||
/// Channels waiting for fetch results.
|
/// Channels waiting for fetch results.
|
||||||
subscribers: Vec<chan::Sender<FetchResult>>,
|
pub subscribers: Vec<chan::Sender<FetchResult>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchState {
|
impl FetchState {
|
||||||
|
|
@ -272,13 +304,13 @@ impl FetchState {
|
||||||
|
|
||||||
/// Fetch waiting to be processed, in the fetch queue.
|
/// Fetch waiting to be processed, in the fetch queue.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct QueuedFetch {
|
pub struct QueuedFetch {
|
||||||
/// Repo being fetched.
|
/// Repo being fetched.
|
||||||
rid: RepoId,
|
pub rid: RepoId,
|
||||||
/// Peer being fetched from.
|
/// Peer being fetched from.
|
||||||
from: NodeId,
|
pub from: NodeId,
|
||||||
/// Refs being fetched.
|
/// Refs being fetched.
|
||||||
refs_at: Vec<RefsAt>,
|
pub refs_at: Vec<RefsAt>,
|
||||||
/// The timeout given for the fetch request.
|
/// The timeout given for the fetch request.
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
/// Result channel.
|
/// Result channel.
|
||||||
|
|
@ -409,6 +441,8 @@ pub struct Service<D, S, G> {
|
||||||
emitter: Emitter<Event>,
|
emitter: Emitter<Event>,
|
||||||
/// Local listening addresses.
|
/// Local listening addresses.
|
||||||
listening: Vec<net::SocketAddr>,
|
listening: Vec<net::SocketAddr>,
|
||||||
|
/// Latest metrics for all nodes connected to since the last start.
|
||||||
|
metrics: Metrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D, S, G> Service<D, S, G>
|
impl<D, S, G> Service<D, S, G>
|
||||||
|
|
@ -476,6 +510,7 @@ where
|
||||||
started_at: None, // Updated on initialize.
|
started_at: None, // Updated on initialize.
|
||||||
emitter,
|
emitter,
|
||||||
listening: vec![],
|
listening: vec![],
|
||||||
|
metrics: Metrics::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -695,7 +730,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, now: LocalTime) {
|
pub fn tick(&mut self, now: LocalTime, metrics: &Metrics) {
|
||||||
trace!(
|
trace!(
|
||||||
target: "service",
|
target: "service",
|
||||||
"Tick +{}",
|
"Tick +{}",
|
||||||
|
|
@ -712,6 +747,7 @@ where
|
||||||
"System clock is not monotonic: {now} is not greater or equal to {}", self.clock
|
"System clock is not monotonic: {now} is not greater or equal to {}", self.clock
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
self.metrics = metrics.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wake(&mut self) {
|
pub fn wake(&mut self) {
|
||||||
|
|
@ -2391,6 +2427,14 @@ pub trait ServiceState {
|
||||||
fn nid(&self) -> &NodeId;
|
fn nid(&self) -> &NodeId;
|
||||||
/// Get the existing sessions.
|
/// Get the existing sessions.
|
||||||
fn sessions(&self) -> &Sessions;
|
fn sessions(&self) -> &Sessions;
|
||||||
|
/// Get fetch state.
|
||||||
|
fn fetching(&self) -> &HashMap<RepoId, FetchState>;
|
||||||
|
/// Get fetch queue.
|
||||||
|
fn queue(&self) -> &VecDeque<QueuedFetch>;
|
||||||
|
/// Get outbox.
|
||||||
|
fn outbox(&self) -> &Outbox;
|
||||||
|
/// Get rate limitter.
|
||||||
|
fn limiter(&self) -> &RateLimiter;
|
||||||
/// Get a repository from storage.
|
/// Get a repository from storage.
|
||||||
fn get(&self, rid: RepoId) -> Result<Option<Doc<Verified>>, RepositoryError>;
|
fn get(&self, rid: RepoId) -> Result<Option<Doc<Verified>>, RepositoryError>;
|
||||||
/// Get the clock.
|
/// Get the clock.
|
||||||
|
|
@ -2399,6 +2443,8 @@ pub trait ServiceState {
|
||||||
fn clock_mut(&mut self) -> &mut LocalTime;
|
fn clock_mut(&mut self) -> &mut LocalTime;
|
||||||
/// Get service configuration.
|
/// Get service configuration.
|
||||||
fn config(&self) -> &Config;
|
fn config(&self) -> &Config;
|
||||||
|
/// Get service metrics.
|
||||||
|
fn metrics(&self) -> &Metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D, S, G> ServiceState for Service<D, S, G>
|
impl<D, S, G> ServiceState for Service<D, S, G>
|
||||||
|
|
@ -2415,6 +2461,22 @@ where
|
||||||
&self.sessions
|
&self.sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetching(&self) -> &HashMap<RepoId, FetchState> {
|
||||||
|
&self.fetching
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue(&self) -> &VecDeque<QueuedFetch> {
|
||||||
|
&self.queue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outbox(&self) -> &Outbox {
|
||||||
|
&self.outbox
|
||||||
|
}
|
||||||
|
|
||||||
|
fn limiter(&self) -> &RateLimiter {
|
||||||
|
&self.limiter
|
||||||
|
}
|
||||||
|
|
||||||
fn get(&self, rid: RepoId) -> Result<Option<Doc<Verified>>, RepositoryError> {
|
fn get(&self, rid: RepoId) -> Result<Option<Doc<Verified>>, RepositoryError> {
|
||||||
self.storage.get(rid)
|
self.storage.get(rid)
|
||||||
}
|
}
|
||||||
|
|
@ -2430,6 +2492,10 @@ where
|
||||||
fn config(&self) -> &Config {
|
fn config(&self) -> &Config {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn metrics(&self) -> &Metrics {
|
||||||
|
&self.metrics
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disconnect reason.
|
/// Disconnect reason.
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,12 @@ impl Outbox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of items in outbox.
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.io.len()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test"))]
|
#[cfg(any(test, feature = "test"))]
|
||||||
pub(crate) fn queue(&mut self) -> &mut VecDeque<Io> {
|
pub(crate) fn queue(&mut self) -> &mut VecDeque<Io> {
|
||||||
&mut self.io
|
&mut self.io
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ use radicle::node::{address, config, HostName, NodeId};
|
||||||
/// bucket's capacity.
|
/// bucket's capacity.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct RateLimiter {
|
pub struct RateLimiter {
|
||||||
buckets: HashMap<HostName, TokenBucket>,
|
pub buckets: HashMap<HostName, TokenBucket>,
|
||||||
bypass: HashSet<NodeId>,
|
pub bypass: HashSet<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RateLimiter {
|
impl RateLimiter {
|
||||||
|
|
@ -74,7 +74,8 @@ impl AsTokens for config::RateLimit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TokenBucket {
|
pub struct TokenBucket {
|
||||||
/// Token refill rate per second.
|
/// Token refill rate per second.
|
||||||
rate: f64,
|
rate: f64,
|
||||||
|
|
|
||||||
|
|
@ -115,4 +115,8 @@ impl radicle::node::Handle for Handle {
|
||||||
fn shutdown(self) -> Result<(), Self::Error> {
|
fn shutdown(self) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(&self) -> Result<serde_json::Value, Self::Error> {
|
||||||
|
Ok(serde_json::Value::Null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -406,13 +406,15 @@ where
|
||||||
pub fn messages(&mut self, remote: NodeId) -> impl Iterator<Item = Message> {
|
pub fn messages(&mut self, remote: NodeId) -> impl Iterator<Item = Message> {
|
||||||
let mut msgs = Vec::new();
|
let mut msgs = Vec::new();
|
||||||
|
|
||||||
self.service.outbox().queue().retain(|o| match o {
|
Service::outbox(&mut self.service)
|
||||||
Io::Write(a, messages) if *a == remote => {
|
.queue()
|
||||||
msgs.extend(messages.clone());
|
.retain(|o| match o {
|
||||||
false
|
Io::Write(a, messages) if *a == remote => {
|
||||||
}
|
msgs.extend(messages.clone());
|
||||||
_ => true,
|
false
|
||||||
});
|
}
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
msgs.into_iter()
|
msgs.into_iter()
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +425,7 @@ where
|
||||||
let mut filtered: Vec<Message> = Vec::new();
|
let mut filtered: Vec<Message> = Vec::new();
|
||||||
let nid = *self.nid();
|
let nid = *self.nid();
|
||||||
|
|
||||||
for o in self.service.outbox().queue() {
|
for o in Service::outbox(&mut self.service).queue() {
|
||||||
match o {
|
match o {
|
||||||
Io::Write(a, messages) if *a == remote => {
|
Io::Write(a, messages) if *a == remote => {
|
||||||
let (relayed, other): (Vec<Message>, _) =
|
let (relayed, other): (Vec<Message>, _) =
|
||||||
|
|
@ -448,7 +450,7 @@ where
|
||||||
pub fn inventory_announcements(&mut self, remote: NodeId) -> impl Iterator<Item = Message> {
|
pub fn inventory_announcements(&mut self, remote: NodeId) -> impl Iterator<Item = Message> {
|
||||||
let mut invs: Vec<Message> = Vec::new();
|
let mut invs: Vec<Message> = Vec::new();
|
||||||
|
|
||||||
for o in self.service.outbox().queue() {
|
for o in Service::outbox(&mut self.service).queue() {
|
||||||
match o {
|
match o {
|
||||||
Io::Write(a, messages) if *a == remote => {
|
Io::Write(a, messages) if *a == remote => {
|
||||||
let (inventories, other): (Vec<Message>, _) =
|
let (inventories, other): (Vec<Message>, _) =
|
||||||
|
|
@ -473,12 +475,12 @@ where
|
||||||
|
|
||||||
/// Get a draining iterator over the peer's I/O outbox.
|
/// Get a draining iterator over the peer's I/O outbox.
|
||||||
pub fn outbox(&mut self) -> impl Iterator<Item = Io> + '_ {
|
pub fn outbox(&mut self) -> impl Iterator<Item = Io> + '_ {
|
||||||
iter::from_fn(|| self.service.outbox().next())
|
iter::from_fn(|| Service::outbox(&mut self.service).next())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a draining iterator over the peer's I/O outbox, which only returns fetches.
|
/// Get a draining iterator over the peer's I/O outbox, which only returns fetches.
|
||||||
pub fn fetches(&mut self) -> impl Iterator<Item = (RepoId, NodeId)> + '_ {
|
pub fn fetches(&mut self) -> impl Iterator<Item = (RepoId, NodeId)> + '_ {
|
||||||
iter::from_fn(|| self.service.outbox().next()).filter_map(|io| {
|
iter::from_fn(|| Service::outbox(&mut self.service).next()).filter_map(|io| {
|
||||||
if let Io::Fetch { rid, remote, .. } = io {
|
if let Io::Fetch { rid, remote, .. } = io {
|
||||||
Some((rid, remote))
|
Some((rid, remote))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use log::*;
|
||||||
use crate::crypto::Signer;
|
use crate::crypto::Signer;
|
||||||
use crate::prelude::{Address, RepoId};
|
use crate::prelude::{Address, RepoId};
|
||||||
use crate::service::io::Io;
|
use crate::service::io::Io;
|
||||||
use crate::service::{DisconnectReason, Event, Message, NodeId};
|
use crate::service::{DisconnectReason, Event, Message, Metrics, NodeId};
|
||||||
use crate::storage::Namespaces;
|
use crate::storage::Namespaces;
|
||||||
use crate::storage::{ReadRepository, WriteStorage};
|
use crate::storage::{ReadRepository, WriteStorage};
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
|
|
@ -368,7 +368,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
|
||||||
let Scheduled { input, node, .. } = next;
|
let Scheduled { input, node, .. } = next;
|
||||||
|
|
||||||
if let Some(ref mut p) = nodes.get_mut(&node) {
|
if let Some(ref mut p) = nodes.get_mut(&node) {
|
||||||
p.tick(time);
|
p.tick(time, &Metrics::default());
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Input::Connecting { id, addr } => {
|
Input::Connecting { id, addr } => {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ use crate::prelude::Deserializer;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::service::io::Io;
|
use crate::service::io::Io;
|
||||||
use crate::service::FETCH_TIMEOUT;
|
use crate::service::FETCH_TIMEOUT;
|
||||||
use crate::service::{session, DisconnectReason, Service};
|
use crate::service::{session, DisconnectReason, Metrics, Service};
|
||||||
use crate::wire::frame;
|
use crate::wire::frame;
|
||||||
use crate::wire::frame::{Frame, FrameData, StreamId};
|
use crate::wire::frame::{Frame, FrameData, StreamId};
|
||||||
use crate::wire::Encode;
|
use crate::wire::Encode;
|
||||||
|
|
@ -309,6 +309,8 @@ pub struct Wire<D, S, G: Signer + Ecdh> {
|
||||||
worker: chan::Sender<Task>,
|
worker: chan::Sender<Task>,
|
||||||
/// Used for authentication.
|
/// Used for authentication.
|
||||||
signer: G,
|
signer: G,
|
||||||
|
/// Node metrics.
|
||||||
|
metrics: service::Metrics,
|
||||||
/// Internal queue of actions to send to the reactor.
|
/// Internal queue of actions to send to the reactor.
|
||||||
actions: VecDeque<Action<G>>,
|
actions: VecDeque<Action<G>>,
|
||||||
/// Outbound attempted peers without a session.
|
/// Outbound attempted peers without a session.
|
||||||
|
|
@ -334,6 +336,7 @@ where
|
||||||
service,
|
service,
|
||||||
worker,
|
worker,
|
||||||
signer,
|
signer,
|
||||||
|
metrics: Metrics::default(),
|
||||||
actions: VecDeque::new(),
|
actions: VecDeque::new(),
|
||||||
inbound: RandomMap::default(),
|
inbound: RandomMap::default(),
|
||||||
outbound: RandomMap::default(),
|
outbound: RandomMap::default(),
|
||||||
|
|
@ -456,11 +459,13 @@ where
|
||||||
log::debug!(target: "wire", "Stream {stream} cannot be found; ignoring flush");
|
log::debug!(target: "wire", "Stream {stream} cannot be found; ignoring flush");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let metrics = self.metrics.peer(remote);
|
||||||
|
|
||||||
for data in s.channels.try_iter() {
|
for data in s.channels.try_iter() {
|
||||||
let frame = match data {
|
let frame = match data {
|
||||||
ChannelEvent::Data(data) => {
|
ChannelEvent::Data(data) => {
|
||||||
s.sent_bytes += data.len();
|
metrics.sent_git_bytes += data.len();
|
||||||
|
metrics.sent_bytes += data.len();
|
||||||
Frame::git(stream, data)
|
Frame::git(stream, data)
|
||||||
}
|
}
|
||||||
ChannelEvent::Close => Frame::control(*link, frame::Control::Close { stream }),
|
ChannelEvent::Close => Frame::control(*link, frame::Control::Close { stream }),
|
||||||
|
|
@ -498,8 +503,10 @@ where
|
||||||
type Command = Control;
|
type Command = Control;
|
||||||
|
|
||||||
fn tick(&mut self, time: Timestamp) {
|
fn tick(&mut self, time: Timestamp) {
|
||||||
self.service
|
self.service.tick(
|
||||||
.tick(LocalTime::from_millis(time.as_millis() as u128));
|
LocalTime::from_millis(time.as_millis() as u128),
|
||||||
|
&self.metrics,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_timer(&mut self) {
|
fn handle_timer(&mut self) {
|
||||||
|
|
@ -603,6 +610,7 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let (addr, link) = if let Some(peer) = self.inbound.remove(&fd) {
|
let (addr, link) = if let Some(peer) = self.inbound.remove(&fd) {
|
||||||
|
self.metrics.peer(nid).inbound_connection_attempts += 1;
|
||||||
(peer.addr, Link::Inbound)
|
(peer.addr, Link::Inbound)
|
||||||
} else if let Some(peer) = self.outbound.remove(&fd) {
|
} else if let Some(peer) = self.outbound.remove(&fd) {
|
||||||
assert_eq!(nid, peer.nid);
|
assert_eq!(nid, peer.nid);
|
||||||
|
|
@ -711,6 +719,9 @@ where
|
||||||
..
|
..
|
||||||
}) = self.peers.get_mut(&id)
|
}) = self.peers.get_mut(&id)
|
||||||
{
|
{
|
||||||
|
let metrics = self.metrics.peer(*nid);
|
||||||
|
metrics.received_bytes += data.len();
|
||||||
|
|
||||||
if inbox.input(&data).is_err() {
|
if inbox.input(&data).is_err() {
|
||||||
log::error!(target: "wire", "Maximum inbox size ({MAX_INBOX_SIZE}) reached for peer {nid}");
|
log::error!(target: "wire", "Maximum inbox size ({MAX_INBOX_SIZE}) reached for peer {nid}");
|
||||||
log::error!(target: "wire", "Unable to process messages fast enough for peer {nid}; disconnecting..");
|
log::error!(target: "wire", "Unable to process messages fast enough for peer {nid}; disconnecting..");
|
||||||
|
|
@ -726,6 +737,8 @@ where
|
||||||
..
|
..
|
||||||
})) => {
|
})) => {
|
||||||
log::debug!(target: "wire", "Received `open` command for stream {stream} from {nid}");
|
log::debug!(target: "wire", "Received `open` command for stream {stream} from {nid}");
|
||||||
|
metrics.streams_opened += 1;
|
||||||
|
metrics.received_fetch_requests += 1;
|
||||||
|
|
||||||
let Some(channels) = streams.register(stream, FETCH_TIMEOUT) else {
|
let Some(channels) = streams.register(stream, FETCH_TIMEOUT) else {
|
||||||
log::warn!(target: "wire", "Peer attempted to open already-open stream stream {stream}");
|
log::warn!(target: "wire", "Peer attempted to open already-open stream stream {stream}");
|
||||||
|
|
@ -777,6 +790,7 @@ where
|
||||||
data: FrameData::Gossip(msg),
|
data: FrameData::Gossip(msg),
|
||||||
..
|
..
|
||||||
})) => {
|
})) => {
|
||||||
|
metrics.received_gossip_messages += 1;
|
||||||
self.service.received_message(*nid, msg);
|
self.service.received_message(*nid, msg);
|
||||||
}
|
}
|
||||||
Ok(Some(Frame {
|
Ok(Some(Frame {
|
||||||
|
|
@ -785,7 +799,7 @@ where
|
||||||
..
|
..
|
||||||
})) => {
|
})) => {
|
||||||
if let Some(s) = streams.get_mut(&stream) {
|
if let Some(s) = streams.get_mut(&stream) {
|
||||||
s.received_bytes += data.len();
|
metrics.received_git_bytes += data.len();
|
||||||
|
|
||||||
if s.channels.send(ChannelEvent::Data(data)).is_err() {
|
if s.channels.send(ChannelEvent::Data(data)).is_err() {
|
||||||
log::error!(target: "wire", "Worker is disconnected; cannot send data");
|
log::error!(target: "wire", "Worker is disconnected; cannot send data");
|
||||||
|
|
@ -943,13 +957,17 @@ where
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: "wire", "Writing {} message(s) to {}", msgs.len(), node_id
|
target: "wire", "Writing {} message(s) to {}", msgs.len(), node_id
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
let metrics = self.metrics.peer(node_id);
|
||||||
|
metrics.sent_gossip_messages += msgs.len();
|
||||||
|
|
||||||
for msg in msgs {
|
for msg in msgs {
|
||||||
Frame::gossip(link, msg)
|
Frame::gossip(link, msg)
|
||||||
.encode(&mut data)
|
.encode(&mut data)
|
||||||
.expect("in-memory writes never fail");
|
.expect("in-memory writes never fail");
|
||||||
}
|
}
|
||||||
|
metrics.sent_bytes += data.len();
|
||||||
|
|
||||||
self.actions.push_back(reactor::Action::Send(fd, data));
|
self.actions.push_back(reactor::Action::Send(fd, data));
|
||||||
}
|
}
|
||||||
Io::Connect(node_id, addr) => {
|
Io::Connect(node_id, addr) => {
|
||||||
|
|
@ -963,6 +981,7 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.service.attempted(node_id, addr.clone());
|
self.service.attempted(node_id, addr.clone());
|
||||||
|
self.metrics.peer(node_id).outbound_connection_attempts += 1;
|
||||||
|
|
||||||
match dial::<G>(
|
match dial::<G>(
|
||||||
addr.to_inner(),
|
addr.to_inner(),
|
||||||
|
|
@ -1003,7 +1022,9 @@ where
|
||||||
}
|
}
|
||||||
Io::Disconnect(nid, reason) => {
|
Io::Disconnect(nid, reason) => {
|
||||||
if let Some((id, Peer::Connected { .. })) = self.peers.lookup(&nid) {
|
if let Some((id, Peer::Connected { .. })) = self.peers.lookup(&nid) {
|
||||||
self.disconnect(id, reason);
|
if let Some((nid, _)) = self.disconnect(id, reason) {
|
||||||
|
self.metrics.peer(nid).disconnects += 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!(target: "wire", "Peer {nid} is not connected: ignoring disconnect");
|
log::warn!(target: "wire", "Peer {nid} is not connected: ignoring disconnect");
|
||||||
}
|
}
|
||||||
|
|
@ -1053,6 +1074,10 @@ where
|
||||||
if self.worker.send(task).is_err() {
|
if self.worker.send(task).is_err() {
|
||||||
log::error!(target: "wire", "Worker pool is disconnected; cannot send fetch request");
|
log::error!(target: "wire", "Worker pool is disconnected; cannot send fetch request");
|
||||||
}
|
}
|
||||||
|
let metrics = self.metrics.peer(remote);
|
||||||
|
metrics.streams_opened += 1;
|
||||||
|
metrics.sent_fetch_requests += 1;
|
||||||
|
|
||||||
self.actions.push_back(Action::Send(
|
self.actions.push_back(Action::Send(
|
||||||
fd,
|
fd,
|
||||||
Frame::control(link, frame::Control::Open { stream }).to_bytes(),
|
Frame::control(link, frame::Control::Open { stream }).to_bytes(),
|
||||||
|
|
|
||||||
|
|
@ -516,6 +516,9 @@ pub enum Command {
|
||||||
/// Get the node's status.
|
/// Get the node's status.
|
||||||
Status,
|
Status,
|
||||||
|
|
||||||
|
/// Get node debug information.
|
||||||
|
Debug,
|
||||||
|
|
||||||
/// Get the node's NID.
|
/// Get the node's NID.
|
||||||
NodeId,
|
NodeId,
|
||||||
|
|
||||||
|
|
@ -928,6 +931,8 @@ pub trait Handle: Clone + Sync + Send {
|
||||||
fn sessions(&self) -> Result<Self::Sessions, Self::Error>;
|
fn sessions(&self) -> Result<Self::Sessions, Self::Error>;
|
||||||
/// Subscribe to node events.
|
/// Subscribe to node events.
|
||||||
fn subscribe(&self, timeout: time::Duration) -> Result<Self::Events, Self::Error>;
|
fn subscribe(&self, timeout: time::Duration) -> Result<Self::Events, Self::Error>;
|
||||||
|
/// Return debug information as a JSON value.
|
||||||
|
fn debug(&self) -> Result<json::Value, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator of results `T` when passing a [`Command`] to [`Node::call`].
|
/// Iterator of results `T` when passing a [`Command`] to [`Node::call`].
|
||||||
|
|
@ -1239,6 +1244,15 @@ impl Handle for Node {
|
||||||
Ok(sessions)
|
Ok(sessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(&self) -> Result<json::Value, Self::Error> {
|
||||||
|
let debug = self
|
||||||
|
.call::<json::Value>(Command::Debug, DEFAULT_TIMEOUT)?
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::EmptyResponse {})??;
|
||||||
|
|
||||||
|
Ok(debug)
|
||||||
|
}
|
||||||
|
|
||||||
fn shutdown(self) -> Result<(), Error> {
|
fn shutdown(self) -> Result<(), Error> {
|
||||||
for line in self.call::<Success>(Command::Shutdown, DEFAULT_TIMEOUT)? {
|
for line in self.call::<Success>(Command::Shutdown, DEFAULT_TIMEOUT)? {
|
||||||
line?;
|
line?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue