use std::time::{Duration, Instant};
use anyhow::{Error, anyhow};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use clap::Parser;
use minotari_app_utilities::consts;
use tari_comms::connection_manager::SelfLivenessStatus;
use tokio::time;
use super::{CommandContext, HandleCommand};
use crate::commands::status_line::{StatusLine, StatusLineOutput};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(short, long, default_value_t = StatusLineOutput::StdOutAndLog)]
output: StatusLineOutput,
}
#[async_trait]
impl HandleCommand<Args> for CommandContext {
async fn handle_command(&mut self, args: Args) -> Result<(), Error> {
self.status(args.output).await
}
}
impl CommandContext {
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::too_many_lines)]
pub async fn status(&mut self, output: StatusLineOutput) -> Result<(), Error> {
let mut full_log = false;
if self.last_time_full.elapsed() > Duration::from_secs(120) {
self.last_time_full = Instant::now();
full_log = true;
}
let mut status_line = StatusLine::new();
status_line.add_field("", format!("v{}", consts::APP_VERSION_NUMBER));
status_line.add_field("", self.config.network());
status_line.add_field("State", self.state_machine_info.borrow().state_info.short_desc());
let metadata = self.node_service.get_metadata().await?;
let height = metadata.best_block_height();
let last_header = self
.node_service
.get_header(height)
.await?
.ok_or_else(|| anyhow!("No last header"))?;
let last_block_time =
DateTime::<Utc>::from_timestamp(last_header.header().timestamp.as_u64() as i64, 0).unwrap_or_default();
status_line.add_field(
"Tip",
format!("{} ({})", metadata.best_block_height(), last_block_time.to_rfc2822()),
);
let constants = self.consensus_rules.consensus_constants(metadata.best_block_height());
let fut = self.mempool_service.get_mempool_stats();
if let Ok(mempool_stats) = time::timeout(Duration::from_secs(5), fut).await? {
status_line.add_field(
"Mempool",
format!(
"{}tx ({}g, +/- {}blks)",
mempool_stats.unconfirmed_txs,
mempool_stats.unconfirmed_weight,
if mempool_stats.unconfirmed_weight == 0 {
0
} else {
1 + mempool_stats.unconfirmed_weight / constants.max_block_transaction_weight()
},
),
);
} else {
status_line.add_field("Mempool", "query timed out");
};
let conns = self.comms.connectivity().get_active_connections().await?;
let (num_nodes, num_clients) = conns.iter().fold((0usize, 0usize), |(nodes, clients), conn| {
if conn.peer_features().is_node() {
(nodes + 1, clients)
} else {
(nodes, clients + 1)
}
});
status_line.add_field("Connections", format!("{num_nodes}|{num_clients}"));
let banned_peers = self.fetch_banned_peers().await?;
status_line.add_field("Banned", banned_peers.len());
let num_messages = self
.dht_metrics_collector
.get_total_message_count_in_timespan(Duration::from_secs(60))
.await?;
status_line.add_field("Messages (last 60s)", num_messages);
let num_active_rpc_sessions = self.rpc_server.get_num_active_sessions().await?;
status_line.add_field(
"Rpc",
format!(
"{}/{}",
num_active_rpc_sessions, self.config.base_node.p2p.rpc_max_simultaneous_sessions
),
);
match self.comms.liveness_status() {
SelfLivenessStatus::Disabled => {},
SelfLivenessStatus::Checking => {
status_line.add("⏳️️");
},
SelfLivenessStatus::Unreachable => {
status_line.add("️🔌");
},
SelfLivenessStatus::Live(latency) => {
status_line.add(format!("⚡️ {latency:.2?}"));
},
}
if full_log {
status_line.add_field(
"RandomX",
format!(
"#{} with flags {:?}",
self.state_machine_info.borrow().randomx_vm_cnt,
self.state_machine_info.borrow().randomx_vm_flags
),
);
}
let target = "base_node::app::status";
match output {
StatusLineOutput::StdOutAndLog => {
println!("{status_line}");
log::info!(target: target, "{status_line}");
},
StatusLineOutput::Log => log::info!(target: target, "{status_line}"),
};
Ok(())
}
}