Skip to content

Commit

Permalink
node: Make output of graphman infomore concise
Browse files Browse the repository at this point in the history
graphman info prints one fairly big block of text for each subgraph
version. A lot of the information is repetetive since it is tied to a
deployment, not a subgraph name. To make the output more usable, it is now
organized around deployments, and lists all the names for a deployment in
one place, like:

```
Namespace        | sgd114 [primary]
Hash             | QmVsp1bC9rS3rf861cXgyvsqkpdsTXKSnS4729boXZvZyH
Versions         | u65281/s53035/latest (current)
                 | u65281/s53035/v2 (current)
Chain            | mainnet
Node ID          | default
Active           | true
Paused           | false
Synced           | false
Health           | healthy
Earliest Block   | 9923743
Latest Block     | 9931270
Chain Head Block | 20988707
   Blocks behind | 11057437
```

The new options `--brief` and `--no-name` can be used to further cut down
on the output by supressing names for all but active deployments or
suppressing all names
  • Loading branch information
lutter committed Feb 17, 2025
1 parent 8bc4645 commit 2eb479d
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 67 deletions.
10 changes: 10 additions & 0 deletions node/src/bin/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ pub enum Command {
/// List only used (current and pending) versions
#[clap(long, short)]
used: bool,
/// List names only for the active deployment
#[clap(long, short)]
brief: bool,
/// Do not print subgraph names
#[clap(long, short = 'N')]
no_name: bool,
},
/// Manage unused deployments
///
Expand Down Expand Up @@ -1127,6 +1133,8 @@ async fn main() -> anyhow::Result<()> {
status,
used,
all,
brief,
no_name,
} => {
let (store, primary_pool) = ctx.store_and_primary();

Expand All @@ -1142,6 +1150,8 @@ async fn main() -> anyhow::Result<()> {
status,
used,
all,
brief,
no_name,
};

commands::deployment::info::run(ctx, args)
Expand Down
145 changes: 80 additions & 65 deletions node/src/manager/commands/deployment/info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::io;
use std::sync::Arc;

use anyhow::bail;
Expand All @@ -12,7 +14,8 @@ use graphman::deployment::Deployment;
use graphman::deployment::DeploymentSelector;
use graphman::deployment::DeploymentVersionSelector;

use crate::manager::display::List;
use crate::manager::display::Columns;
use crate::manager::display::Row;

pub struct Context {
pub primary_pool: ConnectionPool,
Expand All @@ -26,6 +29,8 @@ pub struct Args {
pub status: bool,
pub used: bool,
pub all: bool,
pub brief: bool,
pub no_name: bool,
}

pub fn run(ctx: Context, args: Args) -> Result<()> {
Expand All @@ -41,6 +46,8 @@ pub fn run(ctx: Context, args: Args) -> Result<()> {
status,
used,
all,
brief,
no_name,
} = args;

let deployment = match deployment {
Expand All @@ -65,8 +72,7 @@ pub fn run(ctx: Context, args: Args) -> Result<()> {
None
};

print_info(deployments, statuses);

render(brief, no_name, deployments, statuses);
Ok(())
}

Expand All @@ -85,77 +91,86 @@ fn make_deployment_version_selector(
}
}

fn print_info(deployments: Vec<Deployment>, statuses: Option<HashMap<i32, DeploymentStatus>>) {
let mut headers = vec![
"Name",
"Status",
"Hash",
"Namespace",
"Shard",
"Active",
"Chain",
"Node ID",
];

if statuses.is_some() {
headers.extend(vec![
"Paused",
"Synced",
"Health",
"Earliest Block",
"Latest Block",
"Chain Head Block",
]);
}
const NONE: &str = "---";

let mut list = List::new(headers);
fn optional(s: Option<impl ToString>) -> String {
s.map(|x| x.to_string()).unwrap_or(NONE.to_owned())
}

const NONE: &str = "---";
fn render(
brief: bool,
no_name: bool,
deployments: Vec<Deployment>,
statuses: Option<HashMap<i32, DeploymentStatus>>,
) {
fn name_and_status(deployment: &Deployment) -> String {
format!("{} ({})", deployment.name, deployment.version_status)
}

fn optional(s: Option<impl ToString>) -> String {
s.map(|x| x.to_string()).unwrap_or(NONE.to_owned())
fn number(n: Option<i32>) -> String {
n.map(|x| format!("{x}")).unwrap_or(NONE.to_owned())
}

let mut table = Columns::default();

let mut combined: BTreeMap<_, Vec<_>> = BTreeMap::new();
for deployment in deployments {
let mut row = vec![
deployment.name,
deployment.version_status,
deployment.hash,
deployment.namespace,
deployment.shard,
deployment.is_active.to_string(),
deployment.chain,
optional(deployment.node_id),
];

let status = statuses.as_ref().map(|x| x.get(&deployment.id));

match status {
Some(Some(status)) => {
row.extend(vec![
optional(status.is_paused),
status.is_synced.to_string(),
status.health.as_str().to_string(),
status.earliest_block_number.to_string(),
optional(status.latest_block.as_ref().map(|x| x.number)),
optional(status.chain_head_block.as_ref().map(|x| x.number)),
]);
let status = statuses.as_ref().and_then(|x| x.get(&deployment.id));
combined
.entry(deployment.id)
.or_default()
.push((deployment, status));
}

let mut first = true;
for (_, deployments) in combined {
let deployment = &deployments[0].0;
if first {
first = false;
} else {
table.push_row(Row::separator());
}
table.push_row([
"Namespace",
&format!("{} [{}]", deployment.namespace, deployment.shard),
]);
table.push_row(["Hash", &deployment.hash]);
if !no_name && (!brief || deployment.is_active) {
if deployments.len() > 1 {
table.push_row(["Versions", &name_and_status(deployment)]);
for (d, _) in &deployments[1..] {
table.push_row(["", &name_and_status(d)]);
}
} else {
table.push_row(["Version", &name_and_status(deployment)]);
}
Some(None) => {
row.extend(vec![
NONE.to_owned(),
NONE.to_owned(),
NONE.to_owned(),
NONE.to_owned(),
NONE.to_owned(),
NONE.to_owned(),
]);
table.push_row(["Chain", &deployment.chain]);
}
table.push_row(["Node ID", &optional(deployment.node_id.as_ref())]);
table.push_row(["Active", &deployment.is_active.to_string()]);
if let Some((_, status)) = deployments.get(0) {
if let Some(status) = status {
table.push_row(["Paused", &optional(status.is_paused)]);
table.push_row(["Synced", &status.is_synced.to_string()]);
table.push_row(["Health", status.health.as_str()]);

let earliest = status.earliest_block_number;
let latest = status.latest_block.as_ref().map(|x| x.number);
let chain_head = status.chain_head_block.as_ref().map(|x| x.number);
let behind = match (latest, chain_head) {
(Some(latest), Some(chain_head)) => Some(chain_head - latest),
_ => None,
};

table.push_row(["Earliest Block", &earliest.to_string()]);
table.push_row(["Latest Block", &number(latest)]);
table.push_row(["Chain Head Block", &number(chain_head)]);
if let Some(behind) = behind {
table.push_row([" Blocks behind", &behind.to_string()]);
}
}
None => {}
}

list.append(row);
}

list.render();
table.render(&mut io::stdout()).ok();
}
100 changes: 98 additions & 2 deletions node/src/manager/display.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::io::{self, Write};

const LINE_WIDTH: usize = 78;

pub struct List {
pub headers: Vec<String>,
pub rows: Vec<Vec<String>>,
Expand Down Expand Up @@ -29,8 +33,6 @@ impl List {
}

pub fn render(&self) {
const LINE_WIDTH: usize = 78;

let header_width = self.headers.iter().map(|h| h.len()).max().unwrap_or(0);
let header_width = if header_width < 5 { 5 } else { header_width };
let mut first = true;
Expand All @@ -52,3 +54,97 @@ impl List {
}
}
}

/// A more general list of columns than `List`. In practical terms, this is
/// a very simple table with two columns, where both columns are
/// left-aligned
pub struct Columns {
widths: Vec<usize>,
rows: Vec<Row>,
}

impl Columns {
pub fn push_row<R: Into<Row>>(&mut self, row: R) {
let row = row.into();
for (idx, width) in row.widths().iter().enumerate() {
if idx >= self.widths.len() {
self.widths.push(*width);
} else {
self.widths[idx] = (*width).max(self.widths[idx]);
}
}
self.rows.push(row);
}

pub fn render(&self, out: &mut dyn Write) -> io::Result<()> {
for row in &self.rows {
row.render(out, &self.widths)?;
}
Ok(())
}
}

impl Default for Columns {
fn default() -> Self {
Self {
widths: Vec::new(),
rows: Vec::new(),
}
}
}

pub enum Row {
Cells(Vec<String>),
Separator,
}

impl Row {
pub fn separator() -> Self {
Self::Separator
}

fn widths(&self) -> Vec<usize> {
match self {
Row::Cells(cells) => cells.iter().map(|cell| cell.len()).collect(),
Row::Separator => vec![],
}
}

fn render(&self, out: &mut dyn Write, widths: &[usize]) -> io::Result<()> {
match self {
Row::Cells(cells) => {
for (idx, cell) in cells.iter().enumerate() {
if idx > 0 {
write!(out, " | ")?;
}
write!(out, "{cell:width$}", width = widths[idx])?;
}
}
Row::Separator => {
let total_width = widths.iter().sum::<usize>();
let extra_width = if total_width >= LINE_WIDTH {
0
} else {
LINE_WIDTH - total_width
};
for (idx, width) in widths.iter().enumerate() {
if idx > 0 {
write!(out, "-+-")?;
}
if idx == widths.len() - 1 {
write!(out, "{:-<width$}", "", width = width + extra_width)?;
} else {
write!(out, "{:-<width$}", "")?;
}
}
}
}
writeln!(out)
}
}

impl From<[&str; 2]> for Row {
fn from(row: [&str; 2]) -> Self {
Self::Cells(row.iter().map(|s| s.to_string()).collect())
}
}

0 comments on commit 2eb479d

Please sign in to comment.