Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
Add output in JSON for solana-ledger-tool bounds subcommand (#28410)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gnapoli23 authored Jan 13, 2023
1 parent 59fde13 commit 5eab3fb
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 45 deletions.
101 changes: 56 additions & 45 deletions ledger-tool/src/main.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -106,6 +106,7 @@ use {

mod bigtable;
mod ledger_path;
mod output;

#[derive(PartialEq, Eq)]
enum LedgerOutputMethod {
Expand Down Expand Up @@ -1738,16 +1739,19 @@ 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")
.takes_value(false)
.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)
Expand Down Expand Up @@ -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:?}");
}
}
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;
}
last_rooted = slot;
total_rooted += 1;
// 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);
}
let mut count_past_root = 0;
for slot in slots.iter().rev() {
if *slot > last_rooted {
count_past_root += 1;
} else {
break;

// 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;
}
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),
};
}
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:?}");
Expand Down
73 changes: 73 additions & 0 deletions ledger-tool/src/output.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}

0 comments on commit 5eab3fb

Please sign in to comment.