Implement json output for solana ping (#22959)

This commit is contained in:
Tyera Eulberg 2022-02-05 14:40:12 -07:00 committed by GitHub
parent 9548ea61e5
commit d2c89213ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 248 additions and 65 deletions

View File

@ -46,6 +46,8 @@ use {
}, },
}; };
static CHECK_MARK: Emoji = Emoji("", "");
static CROSS_MARK: Emoji = Emoji("", "");
static WARNING: Emoji = Emoji("⚠️", "!"); static WARNING: Emoji = Emoji("⚠️", "!");
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -2524,6 +2526,172 @@ impl fmt::Display for CliGossipNodes {
impl QuietDisplay for CliGossipNodes {} impl QuietDisplay for CliGossipNodes {}
impl VerboseDisplay for CliGossipNodes {} impl VerboseDisplay for CliGossipNodes {}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliPing {
pub source_pubkey: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub fixed_blockhash: Option<String>,
#[serde(skip_serializing)]
pub blockhash_from_cluster: bool,
pub pings: Vec<CliPingData>,
pub transaction_stats: CliPingTxStats,
#[serde(skip_serializing_if = "Option::is_none")]
pub confirmation_stats: Option<CliPingConfirmationStats>,
}
impl fmt::Display for CliPing {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
if let Some(fixed_blockhash) = &self.fixed_blockhash {
let blockhash_origin = if self.blockhash_from_cluster {
"fetched from cluster"
} else {
"supplied from cli arguments"
};
writeln!(
f,
"Fixed blockhash is used: {} ({})",
fixed_blockhash, blockhash_origin
)?;
}
writeln!(f)?;
for ping in &self.pings {
write!(f, "{}", ping)?;
}
writeln!(f)?;
writeln!(f, "--- transaction statistics ---")?;
write!(f, "{}", self.transaction_stats)?;
if let Some(confirmation_stats) = &self.confirmation_stats {
write!(f, "{}", confirmation_stats)?;
}
Ok(())
}
}
impl QuietDisplay for CliPing {}
impl VerboseDisplay for CliPing {}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliPingData {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing)]
pub print_timestamp: bool,
pub timestamp: String,
pub sequence: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub lamports: Option<u64>,
}
impl fmt::Display for CliPingData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (mark, msg) = if let Some(signature) = &self.signature {
if self.success {
(
CHECK_MARK,
format!(
"{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
self.lamports.unwrap(),
self.sequence,
self.ms.unwrap(),
signature
),
)
} else if let Some(error) = &self.error {
(
CROSS_MARK,
format!(
"Transaction failed: seq={:<3} error={:?} signature={}",
self.sequence, error, signature
),
)
} else {
(
CROSS_MARK,
format!(
"Confirmation timeout: seq={:<3} signature={}",
self.sequence, signature
),
)
}
} else {
(
CROSS_MARK,
format!(
"Submit failed: seq={:<3} error={:?}",
self.sequence,
self.error.as_ref().unwrap(),
),
)
};
writeln!(
f,
"{}{}{}",
if self.print_timestamp {
&self.timestamp
} else {
""
},
mark,
msg
)
}
}
impl QuietDisplay for CliPingData {}
impl VerboseDisplay for CliPingData {}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliPingTxStats {
pub num_transactions: u32,
pub num_transaction_confirmed: u32,
}
impl fmt::Display for CliPingTxStats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
self.num_transactions,
self.num_transaction_confirmed,
(100.
- f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
* 100.)
)
}
}
impl QuietDisplay for CliPingTxStats {}
impl VerboseDisplay for CliPingTxStats {}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliPingConfirmationStats {
pub min: f64,
pub mean: f64,
pub max: f64,
pub std_dev: f64,
}
impl fmt::Display for CliPingConfirmationStats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
self.min, self.mean, self.max, self.std_dev,
)
}
}
impl QuietDisplay for CliPingConfirmationStats {}
impl VerboseDisplay for CliPingConfirmationStats {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use { use {

View File

@ -4,7 +4,7 @@ use {
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
}, },
clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}, clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand},
console::{style, Emoji}, console::style,
crossbeam_channel::unbounded, crossbeam_channel::unbounded,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
solana_clap_utils::{ solana_clap_utils::{
@ -16,7 +16,7 @@ use {
solana_cli_output::{ solana_cli_output::{
display::{ display::{
build_balance_message, format_labeled_address, new_spinner_progress_bar, build_balance_message, format_labeled_address, new_spinner_progress_bar,
println_name_value, println_transaction, unix_timestamp_to_string, writeln_name_value, println_transaction, unix_timestamp_to_string, writeln_name_value,
}, },
*, *,
}, },
@ -75,9 +75,6 @@ use {
thiserror::Error, thiserror::Error,
}; };
static CHECK_MARK: Emoji = Emoji("", "");
static CROSS_MARK: Emoji = Emoji("", "");
pub trait ClusterQuerySubCommands { pub trait ClusterQuerySubCommands {
fn cluster_query_subcommands(self) -> Self; fn cluster_query_subcommands(self) -> Self;
} }
@ -1354,15 +1351,14 @@ pub fn process_ping(
fixed_blockhash: &Option<Hash>, fixed_blockhash: &Option<Hash>,
print_timestamp: bool, print_timestamp: bool,
) -> ProcessResult { ) -> ProcessResult {
println_name_value("Source Account:", &config.signers[0].pubkey().to_string());
println!();
let (signal_sender, signal_receiver) = unbounded(); let (signal_sender, signal_receiver) = unbounded();
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
let _ = signal_sender.send(()); let _ = signal_sender.send(());
}) })
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
let mut cli_pings = vec![];
let mut submit_count = 0; let mut submit_count = 0;
let mut confirmed_count = 0; let mut confirmed_count = 0;
let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024); let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024);
@ -1370,17 +1366,13 @@ pub fn process_ping(
let mut blockhash = rpc_client.get_latest_blockhash()?; let mut blockhash = rpc_client.get_latest_blockhash()?;
let mut lamports = 0; let mut lamports = 0;
let mut blockhash_acquired = Instant::now(); let mut blockhash_acquired = Instant::now();
let mut blockhash_from_cluster = false;
if let Some(fixed_blockhash) = fixed_blockhash { if let Some(fixed_blockhash) = fixed_blockhash {
let blockhash_origin = if *fixed_blockhash != Hash::default() { if *fixed_blockhash != Hash::default() {
blockhash = *fixed_blockhash; blockhash = *fixed_blockhash;
"supplied from cli arguments"
} else { } else {
"fetched from cluster" blockhash_from_cluster = true;
}; }
println!(
"Fixed blockhash is used: {} ({})",
blockhash, blockhash_origin
);
} }
'mainloop: for seq in 0..count.unwrap_or(std::u64::MAX) { 'mainloop: for seq in 0..count.unwrap_or(std::u64::MAX) {
let now = Instant::now(); let now = Instant::now();
@ -1416,11 +1408,7 @@ pub fn process_ping(
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_micros(); .as_micros();
if print_timestamp { format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
} else {
String::new()
}
}; };
match rpc_client.send_transaction(&tx) { match rpc_client.send_transaction(&tx) {
@ -1434,35 +1422,51 @@ pub fn process_ping(
Ok(()) => { Ok(()) => {
let elapsed_time_millis = elapsed_time.as_millis() as u64; let elapsed_time_millis = elapsed_time.as_millis() as u64;
confirmation_time.push_back(elapsed_time_millis); confirmation_time.push_back(elapsed_time_millis);
println!( let cli_ping_data = CliPingData {
"{}{}{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}", success: true,
timestamp(), signature: Some(signature.to_string()),
CHECK_MARK, lamports, seq, elapsed_time_millis, signature ms: Some(elapsed_time_millis),
); error: None,
timestamp: timestamp(),
print_timestamp,
sequence: seq,
lamports: Some(lamports),
};
eprint!("{}", cli_ping_data);
cli_pings.push(cli_ping_data);
confirmed_count += 1; confirmed_count += 1;
} }
Err(err) => { Err(err) => {
println!( let cli_ping_data = CliPingData {
"{}{}Transaction failed: seq={:<3} error={:?} signature={}", success: false,
timestamp(), signature: Some(signature.to_string()),
CROSS_MARK, ms: None,
seq, error: Some(err.to_string()),
err, timestamp: timestamp(),
signature print_timestamp,
); sequence: seq,
lamports: None,
};
eprint!("{}", cli_ping_data);
cli_pings.push(cli_ping_data);
} }
} }
break; break;
} }
if elapsed_time >= *timeout { if elapsed_time >= *timeout {
println!( let cli_ping_data = CliPingData {
"{}{}Confirmation timeout: seq={:<3} signature={}", success: false,
timestamp(), signature: Some(signature.to_string()),
CROSS_MARK, ms: None,
seq, error: None,
signature timestamp: timestamp(),
); print_timestamp,
sequence: seq,
lamports: None,
};
eprint!("{}", cli_ping_data);
cli_pings.push(cli_ping_data);
break; break;
} }
@ -1476,13 +1480,18 @@ pub fn process_ping(
} }
} }
Err(err) => { Err(err) => {
println!( let cli_ping_data = CliPingData {
"{}{}Submit failed: seq={:<3} error={:?}", success: false,
timestamp(), signature: None,
CROSS_MARK, ms: None,
seq, error: Some(err.to_string()),
err timestamp: timestamp(),
); print_timestamp,
sequence: seq,
lamports: None,
};
eprint!("{}", cli_ping_data);
cli_pings.push(cli_ping_data);
} }
} }
submit_count += 1; submit_count += 1;
@ -1492,28 +1501,34 @@ pub fn process_ping(
} }
} }
println!(); let transaction_stats = CliPingTxStats {
println!("--- transaction statistics ---"); num_transactions: submit_count,
println!( num_transaction_confirmed: confirmed_count,
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss", };
submit_count, let confirmation_stats = if !confirmation_time.is_empty() {
confirmed_count,
(100. - f64::from(confirmed_count) / f64::from(submit_count) * 100.)
);
if !confirmation_time.is_empty() {
let samples: Vec<f64> = confirmation_time.iter().map(|t| *t as f64).collect(); let samples: Vec<f64> = confirmation_time.iter().map(|t| *t as f64).collect();
let dist = criterion_stats::Distribution::from(samples.into_boxed_slice()); let dist = criterion_stats::Distribution::from(samples.into_boxed_slice());
let mean = dist.mean(); let mean = dist.mean();
println!( Some(CliPingConfirmationStats {
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms", min: dist.min(),
dist.min(),
mean, mean,
dist.max(), max: dist.max(),
dist.std_dev(Some(mean)) std_dev: dist.std_dev(Some(mean)),
); })
} } else {
None
};
Ok("".to_string()) let cli_ping = CliPing {
source_pubkey: config.signers[0].pubkey().to_string(),
fixed_blockhash: fixed_blockhash.map(|_| blockhash.to_string()),
blockhash_from_cluster,
pings: cli_pings,
transaction_stats,
confirmation_stats,
};
Ok(config.output_format.formatted_string(&cli_ping))
} }
pub fn parse_logs( pub fn parse_logs(