Add output in JSON for `solana-ledger-tool bounds` subcommand (#28410)

Introduce a struct to store all of the relevant slot/root information, and then output all in one go at the end as either human-readable or json. There are some slight changes to the human-readable format for the case of an empty ledger
This commit is contained in:
Giovanni Napoli 2023-01-13 06:21:04 +01:00 committed by GitHub
parent 59fde130d6
commit 5eab3fb314
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 129 additions and 45 deletions

View File

@ -1,6 +1,6 @@
#![allow(clippy::integer_arithmetic)]
use {
crate::{bigtable::*, ledger_path::*},
crate::{bigtable::*, ledger_path::*, output::*},
chrono::{DateTime, Utc},
clap::{
crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App,
@ -106,6 +106,7 @@ use {
mod bigtable;
mod ledger_path;
mod output;
#[derive(PartialEq, Eq)]
enum LedgerOutputMethod {
@ -1738,8 +1739,10 @@ fn main() {
)
.subcommand(
SubCommand::with_name("bounds")
.about("Print lowest and highest non-empty slots. \
Note that there may be empty slots within the bounds")
.about(
"Print lowest and highest non-empty slots. \
Note that there may be empty slots within the bounds",
)
.arg(
Arg::with_name("all")
.long("all")
@ -1747,7 +1750,8 @@ fn main() {
.required(false)
.help("Additionally print all the non-empty slots within the bounds"),
)
).subcommand(
)
.subcommand(
SubCommand::with_name("json")
.about("Print the ledger in JSON format")
.arg(&starting_slot_arg)
@ -4170,56 +4174,63 @@ fn main() {
&shred_storage_type,
force_update_to_open,
);
match blockstore.slot_meta_iterator(0) {
Ok(metas) => {
let output_format =
OutputFormat::from_matches(arg_matches, "output_format", false);
let all = arg_matches.is_present("all");
let slots: Vec<_> = metas.map(|(slot, _)| slot).collect();
if slots.is_empty() {
println!("Ledger is empty");
let slot_bounds = if slots.is_empty() {
SlotBounds::default()
} else {
let first = slots.first().unwrap();
let last = slots.last().unwrap_or(first);
if first != last {
println!(
"Ledger has data for {} slots {:?} to {:?}",
slots.len(),
first,
last
);
if all {
println!("Non-empty slots: {slots:?}");
}
} else {
println!("Ledger has data for slot {first:?}");
// Collect info about slot bounds
let mut bounds = SlotBounds {
slots: SlotInfo {
total: slots.len(),
first: Some(*slots.first().unwrap()),
last: Some(*slots.last().unwrap()),
..SlotInfo::default()
},
..SlotBounds::default()
};
if all {
bounds.all_slots = Some(&slots);
}
}
if let Ok(rooted) = blockstore.rooted_slot_iterator(0) {
let mut first_rooted = 0;
let mut last_rooted = 0;
let mut total_rooted = 0;
for (i, slot) in rooted.into_iter().enumerate() {
if i == 0 {
first_rooted = slot;
// Consider also rooted slots, if present
if let Ok(rooted) = blockstore.rooted_slot_iterator(0) {
let mut first_rooted = None;
let mut last_rooted = None;
let mut total_rooted = 0;
for (i, slot) in rooted.into_iter().enumerate() {
if i == 0 {
first_rooted = Some(slot);
}
last_rooted = Some(slot);
total_rooted += 1;
}
last_rooted = slot;
total_rooted += 1;
let last_root_for_comparison = last_rooted.unwrap_or_default();
let count_past_root = slots
.iter()
.rev()
.take_while(|slot| *slot > &last_root_for_comparison)
.count();
bounds.roots = SlotInfo {
total: total_rooted,
first: first_rooted,
last: last_rooted,
num_after_last_root: Some(count_past_root),
};
}
let mut count_past_root = 0;
for slot in slots.iter().rev() {
if *slot > last_rooted {
count_past_root += 1;
} else {
break;
}
}
println!(
" with {total_rooted} rooted slots from {first_rooted:?} to {last_rooted:?}"
);
println!(" and {count_past_root} slots past the last root");
} else {
println!(" with no rooted slots");
}
bounds
};
// Print collected data
println!("{}", output_format.formatted_string(&slot_bounds));
}
Err(err) => {
eprintln!("Unable to read the Ledger: {err:?}");

73
ledger-tool/src/output.rs Normal file
View File

@ -0,0 +1,73 @@
use {
serde::Serialize,
solana_cli_output::{QuietDisplay, VerboseDisplay},
std::fmt::{Display, Formatter, Result},
};
#[derive(Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct SlotInfo {
pub total: usize,
pub first: Option<u64>,
pub last: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_after_last_root: Option<usize>,
}
#[derive(Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct SlotBounds<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub all_slots: Option<&'a Vec<u64>>,
pub slots: SlotInfo,
pub roots: SlotInfo,
}
impl VerboseDisplay for SlotBounds<'_> {}
impl QuietDisplay for SlotBounds<'_> {}
impl Display for SlotBounds<'_> {
fn fmt(&self, f: &mut Formatter) -> Result {
if self.slots.total > 0 {
let first = self.slots.first.unwrap();
let last = self.slots.last.unwrap();
if first != last {
writeln!(
f,
"Ledger has data for {:?} slots {:?} to {:?}",
self.slots.total, first, last
)?;
if let Some(all_slots) = self.all_slots {
writeln!(f, "Non-empty slots: {:?}", all_slots)?;
}
} else {
writeln!(f, "Ledger has data for slot {:?}", first)?;
}
if self.roots.total > 0 {
let first_rooted = self.roots.first.unwrap_or_default();
let last_rooted = self.roots.last.unwrap_or_default();
let num_after_last_root = self.roots.num_after_last_root.unwrap_or_default();
writeln!(
f,
" with {:?} rooted slots from {:?} to {:?}",
self.roots.total, first_rooted, last_rooted
)?;
writeln!(
f,
" and {:?} slots past the last root",
num_after_last_root
)?;
} else {
writeln!(f, " with no rooted slots")?;
}
} else {
writeln!(f, "Ledger is empty")?;
}
Ok(())
}
}