Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
}
}
Loading