lsmem: implement --output column printing
This commit is contained in:
parent
6fbd0cd072
commit
8b8728e1df
@ -5,7 +5,8 @@
|
||||
|
||||
mod utils;
|
||||
|
||||
use clap::{crate_version, Command};
|
||||
use clap::builder::{EnumValueParser, PossibleValue, PossibleValuesParser};
|
||||
use clap::{crate_version, Command, ValueEnum};
|
||||
use clap::{Arg, ArgAction};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Borrow;
|
||||
@ -21,10 +22,13 @@ const USAGE: &str = help_usage!("lsmem.md");
|
||||
mod options {
|
||||
pub const ALL: &str = "all";
|
||||
pub const BYTES: &str = "bytes";
|
||||
pub const NOHEADINGS: &str = "noheadings";
|
||||
pub const JSON: &str = "json";
|
||||
pub const NOHEADINGS: &str = "noheadings";
|
||||
pub const OUTPUT: &str = "output";
|
||||
pub const OUTPUT_ALL: &str = "output-all";
|
||||
pub const PAIRS: &str = "pairs";
|
||||
pub const RAW: &str = "raw";
|
||||
pub const SPLIT: &str = "split";
|
||||
}
|
||||
|
||||
// const BUFSIZ: usize = 1024;
|
||||
@ -36,8 +40,8 @@ const PATH_SUB_REMOVABLE: &str = "removable";
|
||||
const PATH_SUB_STATE: &str = "state";
|
||||
const NAME_MEMORY: &str = "memory";
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
enum Columns {
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
enum Column {
|
||||
#[serde(rename = "RANGE")]
|
||||
Range,
|
||||
#[serde(rename = "SIZE")]
|
||||
@ -54,55 +58,84 @@ enum Columns {
|
||||
Zones,
|
||||
}
|
||||
|
||||
const ALL_COLUMNS: &[Columns] = &[
|
||||
Columns::Range,
|
||||
Columns::Size,
|
||||
Columns::State,
|
||||
Columns::Removable,
|
||||
Columns::Block,
|
||||
Columns::Node,
|
||||
Columns::Zones,
|
||||
];
|
||||
|
||||
impl Columns {
|
||||
fn get_name(&self) -> String {
|
||||
serde_json::to_string(self)
|
||||
.unwrap()
|
||||
.trim_matches('"')
|
||||
.to_string()
|
||||
impl ValueEnum for Column {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[
|
||||
Column::Range,
|
||||
Column::Size,
|
||||
Column::State,
|
||||
Column::Removable,
|
||||
Column::Block,
|
||||
Column::Node,
|
||||
Column::Zones,
|
||||
]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_float_direction(&self) -> &'static str {
|
||||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||
Some(PossibleValue::new(self.get_name()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Default columns to display if none are explicitly specified.
|
||||
const DEFAULT_COLUMNS: &[Column] = &[
|
||||
Column::Range,
|
||||
Column::Size,
|
||||
Column::State,
|
||||
Column::Removable,
|
||||
Column::Block,
|
||||
];
|
||||
/// Which columns (attributes) are possible to split memory blocks to ranges on.
|
||||
const SPLIT_COLUMNS: &[Column] = &[
|
||||
Column::State,
|
||||
Column::Removable,
|
||||
Column::Node,
|
||||
Column::Zones,
|
||||
];
|
||||
|
||||
impl Column {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
Columns::Range => "<",
|
||||
Columns::Size => ">",
|
||||
Columns::State => ">",
|
||||
Columns::Removable => ">",
|
||||
Columns::Block => ">",
|
||||
Columns::Node => ">",
|
||||
Columns::Zones => ">",
|
||||
Column::Range => "RANGE",
|
||||
Column::Size => "SIZE",
|
||||
Column::State => "STATE",
|
||||
Column::Removable => "REMOVABLE",
|
||||
Column::Block => "BLOCK",
|
||||
Column::Node => "NODE",
|
||||
Column::Zones => "ZONES",
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_width_hint(&self) -> i8 {
|
||||
if self == &Columns::Size {
|
||||
fn get_float_right(&self) -> bool {
|
||||
match self {
|
||||
Column::Range => false,
|
||||
Column::Size => true,
|
||||
Column::State => true,
|
||||
Column::Removable => true,
|
||||
Column::Block => true,
|
||||
Column::Node => true,
|
||||
Column::Zones => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_width_hint(&self) -> usize {
|
||||
if self == &Column::Size {
|
||||
5
|
||||
} else {
|
||||
self.get_name().len() as i8
|
||||
self.get_name().len()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_help(&self) -> &'static str {
|
||||
match self {
|
||||
Columns::Range => "start and end address of the memory range",
|
||||
Columns::Size => "size of the memory range",
|
||||
Columns::State => "online status of the memory range",
|
||||
Columns::Removable => "memory is removable",
|
||||
Columns::Block => "memory block number or blocks range",
|
||||
Columns::Node => "numa node of memory",
|
||||
Columns::Zones => "valid zones for the memory range",
|
||||
Column::Range => "start and end address of the memory range",
|
||||
Column::Size => "size of the memory range",
|
||||
Column::State => "online status of the memory range",
|
||||
Column::Removable => "memory is removable",
|
||||
Column::Block => "memory block number or blocks range",
|
||||
Column::Node => "numa node of memory",
|
||||
Column::Zones => "valid zones for the memory range",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,6 +233,17 @@ struct TableRow {
|
||||
}
|
||||
|
||||
impl TableRow {
|
||||
fn get_value(&self, column: &Column) -> String {
|
||||
match column {
|
||||
Column::Range => self.range.clone(),
|
||||
Column::Size => self.size.clone(),
|
||||
Column::State => self.state.clone(),
|
||||
Column::Removable => self.removable.clone(),
|
||||
Column::Block => self.block.clone(),
|
||||
Column::Node => self.node.clone(),
|
||||
Column::Zones => self.zones.clone(),
|
||||
}
|
||||
}
|
||||
fn to_pairs_string(&self) -> String {
|
||||
format!(
|
||||
r#"RANGE="{}" SIZE="{}" STATE="{}" REMOVABLE="{}" BLOCK="{}""#,
|
||||
@ -220,19 +264,24 @@ struct TableRowJson {
|
||||
}
|
||||
|
||||
struct Options {
|
||||
// Set by command-line arguments
|
||||
all: bool,
|
||||
bytes: bool,
|
||||
export: bool,
|
||||
have_nodes: bool,
|
||||
have_zones: bool,
|
||||
json: bool,
|
||||
list_all: bool,
|
||||
columns: Vec<Column>,
|
||||
noheadings: bool,
|
||||
json: bool,
|
||||
pairs: bool,
|
||||
raw: bool,
|
||||
split_by_node: bool,
|
||||
split_by_removable: bool,
|
||||
split_by_state: bool,
|
||||
split_by_zones: bool,
|
||||
|
||||
// Set by read_info
|
||||
have_nodes: bool,
|
||||
have_zones: bool,
|
||||
|
||||
// Computed from flags above
|
||||
want_summary: bool,
|
||||
want_table: bool,
|
||||
}
|
||||
@ -265,20 +314,22 @@ impl Options {
|
||||
fn new() -> Options {
|
||||
Options {
|
||||
all: false,
|
||||
have_nodes: false,
|
||||
raw: false,
|
||||
export: false,
|
||||
json: false,
|
||||
noheadings: false,
|
||||
list_all: false,
|
||||
bytes: false,
|
||||
columns: Vec::default(),
|
||||
noheadings: false,
|
||||
json: false,
|
||||
pairs: false,
|
||||
raw: false,
|
||||
split_by_node: false,
|
||||
split_by_removable: false,
|
||||
split_by_state: false,
|
||||
split_by_zones: false,
|
||||
|
||||
have_nodes: false,
|
||||
have_zones: false,
|
||||
|
||||
want_summary: true, // default true
|
||||
want_table: true, // default true
|
||||
split_by_node: false,
|
||||
split_by_state: false,
|
||||
split_by_removable: false,
|
||||
split_by_zones: false,
|
||||
have_zones: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -321,7 +372,7 @@ fn read_info(lsmem: &mut Lsmem, opts: &mut Options) {
|
||||
} else {
|
||||
lsmem.mem_offline += lsmem.block_size;
|
||||
}
|
||||
if !opts.all && is_mergeable(lsmem, opts, &blk) {
|
||||
if is_mergeable(lsmem, opts, &blk) {
|
||||
lsmem.blocks[lsmem.nblocks - 1].count += 1;
|
||||
continue;
|
||||
}
|
||||
@ -349,7 +400,7 @@ fn is_mergeable(lsmem: &Lsmem, opts: &Options, blk: &MemoryBlock) -> bool {
|
||||
}
|
||||
|
||||
let curr_block = &lsmem.blocks[lsmem.nblocks - 1];
|
||||
if opts.list_all {
|
||||
if opts.all {
|
||||
return false;
|
||||
}
|
||||
if curr_block.index + curr_block.count != blk.index {
|
||||
@ -482,44 +533,53 @@ fn create_table_rows(lsmem: &Lsmem, opts: &Options) -> Vec<TableRow> {
|
||||
|
||||
fn print_table(lsmem: &Lsmem, opts: &Options) {
|
||||
let table_rows = create_table_rows(lsmem, opts);
|
||||
let mut col_widths = vec![5, 5, 6, 9, 5]; // Initialize with default minimum widths
|
||||
let mut col_widths = vec![0; opts.columns.len()];
|
||||
|
||||
// Initialize column widths based on column names
|
||||
for (i, column) in opts.columns.iter().enumerate() {
|
||||
col_widths[i] = column.get_width_hint();
|
||||
}
|
||||
|
||||
// Calculate minimum column widths based on the actual data
|
||||
for row in &table_rows {
|
||||
col_widths[0] = col_widths[0].max(row.range.len());
|
||||
col_widths[1] = col_widths[1].max(row.size.len());
|
||||
col_widths[2] = col_widths[2].max(row.state.len());
|
||||
col_widths[3] = col_widths[3].max(row.removable.len());
|
||||
col_widths[4] = col_widths[4].max(row.block.len());
|
||||
for (i, column) in opts.columns.iter().enumerate() {
|
||||
let width = match column {
|
||||
Column::Range => row.range.len(),
|
||||
Column::Size => row.size.len(),
|
||||
Column::State => row.state.len(),
|
||||
Column::Removable => row.removable.len(),
|
||||
Column::Block => row.block.len(),
|
||||
Column::Node => row.node.len(),
|
||||
Column::Zones => row.zones.len(),
|
||||
};
|
||||
col_widths[i] = col_widths[i].max(width);
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noheadings {
|
||||
println!(
|
||||
"{:<col0$} {:>col1$} {:>col2$} {:>col3$} {:>col4$}",
|
||||
"RANGE",
|
||||
"SIZE",
|
||||
"STATE",
|
||||
"REMOVABLE",
|
||||
"BLOCK",
|
||||
col0 = col_widths[0],
|
||||
col1 = col_widths[1],
|
||||
col2 = col_widths[2],
|
||||
col3 = col_widths[3],
|
||||
col4 = col_widths[4],
|
||||
);
|
||||
let mut output = vec![];
|
||||
for (i, column) in opts.columns.iter().enumerate() {
|
||||
let formatted = if column.get_float_right() {
|
||||
format!("{:>width$}", column.get_name(), width = col_widths[i])
|
||||
} else {
|
||||
format!("{:<width$}", column.get_name(), width = col_widths[i])
|
||||
};
|
||||
output.push(formatted);
|
||||
}
|
||||
println!("{}", output.join(" "));
|
||||
}
|
||||
|
||||
for row in table_rows {
|
||||
let mut columns = vec![];
|
||||
columns.push(format!("{:<col0$}", row.range, col0 = col_widths[0]));
|
||||
columns.push(format!("{:>col1$}", row.size, col1 = col_widths[1]));
|
||||
columns.push(format!("{:>col2$}", row.state, col2 = col_widths[2]));
|
||||
columns.push(format!("{:>col3$}", row.removable, col3 = col_widths[3]));
|
||||
columns.push(format!("{:>col4$}", row.block, col4 = col_widths[4]));
|
||||
// Default version skips NODE and ZONES
|
||||
// columns.push(format!("{:>col5$}", row.node, col5 = col_widths[5]));
|
||||
// columns.push(format!("{:>col6$}", row.zones, col6 = col_widths[6]));
|
||||
println!("{}", columns.join(" "));
|
||||
let mut output = vec![];
|
||||
for (i, column) in opts.columns.iter().enumerate() {
|
||||
let formatted = if column.get_float_right() {
|
||||
format!("{:>width$}", row.get_value(column), width = col_widths[i])
|
||||
} else {
|
||||
format!("{:<width$}", row.get_value(column), width = col_widths[i])
|
||||
};
|
||||
output.push(formatted);
|
||||
}
|
||||
println!("{}", output.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,10 +657,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
opts.bytes = matches.get_flag(options::BYTES);
|
||||
opts.noheadings = matches.get_flag(options::NOHEADINGS);
|
||||
opts.json = matches.get_flag(options::JSON);
|
||||
opts.export = matches.get_flag(options::PAIRS);
|
||||
opts.pairs = matches.get_flag(options::PAIRS);
|
||||
opts.raw = matches.get_flag(options::RAW);
|
||||
opts.columns = matches
|
||||
.get_many::<Column>(options::OUTPUT)
|
||||
.unwrap_or_default()
|
||||
.map(|c| c.to_owned())
|
||||
.collect::<Vec<Column>>();
|
||||
|
||||
if opts.json || opts.export || opts.raw {
|
||||
// Only respect --output-all if no column list were provided.
|
||||
// --output takes priority over --output-all.
|
||||
if opts.columns.is_empty() {
|
||||
if matches.get_flag(options::OUTPUT_ALL) {
|
||||
opts.columns = Column::value_variants().to_vec();
|
||||
} else {
|
||||
opts.columns = DEFAULT_COLUMNS.to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
let split_columns = matches
|
||||
.get_many::<String>(options::SPLIT)
|
||||
.unwrap_or_default()
|
||||
.map(|c| c.to_owned())
|
||||
.collect::<Vec<String>>();
|
||||
opts.split_by_node = split_columns.contains(&Column::Node.get_name().to_string());
|
||||
opts.split_by_removable = split_columns.contains(&Column::Removable.get_name().to_string());
|
||||
opts.split_by_state = split_columns.contains(&Column::State.get_name().to_string());
|
||||
opts.split_by_zones = split_columns.contains(&Column::Zones.get_name().to_string());
|
||||
|
||||
if opts.json || opts.pairs || opts.raw {
|
||||
opts.want_summary = false;
|
||||
}
|
||||
|
||||
@ -609,7 +694,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
if opts.want_table {
|
||||
if opts.json {
|
||||
print_json(&lsmem, &opts);
|
||||
} else if opts.export {
|
||||
} else if opts.pairs {
|
||||
print_pairs(&lsmem, &opts);
|
||||
} else if opts.raw {
|
||||
print_raw(&lsmem, &opts);
|
||||
@ -636,27 +721,6 @@ pub fn uu_app() -> Command {
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
Arg::new(options::ALL)
|
||||
.short('a')
|
||||
.long("all")
|
||||
.help("list each individual memory block")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::BYTES)
|
||||
.short('b')
|
||||
.long("bytes")
|
||||
.help("print SIZE in bytes rather than in human readable format")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::NOHEADINGS)
|
||||
.short('n')
|
||||
.long("noheadings")
|
||||
.help("don't print headings")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::JSON)
|
||||
.short('J')
|
||||
@ -673,6 +737,44 @@ pub fn uu_app() -> Command {
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with_all([options::JSON, options::RAW]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ALL)
|
||||
.short('a')
|
||||
.long("all")
|
||||
.help("list each individual memory block")
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with(options::SPLIT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::BYTES)
|
||||
.short('b')
|
||||
.long("bytes")
|
||||
.help("print SIZE in bytes rather than in human readable format")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::NOHEADINGS)
|
||||
.short('n')
|
||||
.long("noheadings")
|
||||
.help("don't print headings")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::OUTPUT)
|
||||
.short('o')
|
||||
.long("output")
|
||||
.help("output columns")
|
||||
.ignore_case(true)
|
||||
.action(ArgAction::Set)
|
||||
.value_delimiter(',')
|
||||
.value_parser(EnumValueParser::<Column>::new()),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::OUTPUT_ALL)
|
||||
.long("output-all")
|
||||
.help("output all columns")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::RAW)
|
||||
.short('r')
|
||||
@ -681,9 +783,25 @@ pub fn uu_app() -> Command {
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with_all([options::JSON, options::PAIRS]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::SPLIT)
|
||||
.short('S')
|
||||
.long("split")
|
||||
.help("split ranges by specified columns")
|
||||
.conflicts_with(options::ALL)
|
||||
.ignore_case(true)
|
||||
.action(ArgAction::Set)
|
||||
.value_delimiter(',')
|
||||
.value_parser(PossibleValuesParser::new(
|
||||
SPLIT_COLUMNS
|
||||
.iter()
|
||||
.map(|col| col.to_possible_value().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
)),
|
||||
)
|
||||
.after_help(&format!(
|
||||
"Available output columns:\n{}",
|
||||
ALL_COLUMNS
|
||||
Column::value_variants()
|
||||
.iter()
|
||||
.map(|col| format!("{:>11} {}", col.get_name(), col.get_help()))
|
||||
.collect::<Vec<_>>()
|
||||
|
Loading…
x
Reference in New Issue
Block a user