diff --git a/Cargo.lock b/Cargo.lock index 53a77ce..70e5536 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,46 +115,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "clap" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - [[package]] name = "colorchoice" version = "1.0.0" @@ -211,12 +171,6 @@ dependencies = [ "log", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hostname" version = "0.3.1" @@ -429,7 +383,6 @@ name = "sonar" version = "0.10.0" dependencies = [ "chrono", - "clap", "csv", "env_logger", "hostname", @@ -442,12 +395,6 @@ dependencies = [ "subprocess", ] -[[package]] -name = "strsim" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" - [[package]] name = "subprocess" version = "0.2.9" diff --git a/Cargo.toml b/Cargo.toml index 758d65d..7eb0bbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" subprocess = "0.2" chrono = "0.4" hostname = "0.3" -clap = { version = "4.5", features = ["derive"] } csv = "1.3" log = "0.4" env_logger = "0.11" diff --git a/src/main.rs b/src/main.rs index da2b6d8..1fb36aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,5 @@ extern crate env_logger; -use clap::{Parser, Subcommand}; - mod amd; mod batchless; mod command; @@ -18,60 +16,41 @@ mod util; const TIMEOUT_SECONDS: u64 = 5; // For subprocesses -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] enum Commands { /// Take a snapshot of the currently running processes PS { /// Synthesize a job ID from the process tree in which a process finds itself - #[arg(long, default_value_t = false)] batchless: bool, /// Merge process records that have the same job ID and command name - #[arg(long, default_value_t = false)] rollup: bool, /// Include records for jobs that have on average used at least this percentage of CPU, /// note this is nonmonotonic [default: none] - #[arg(long)] min_cpu_percent: Option, /// Include records for jobs that presently use at least this percentage of real memory, /// note this is nonmonotonic [default: none] - #[arg(long)] min_mem_percent: Option, /// Include records for jobs that have used at least this much CPU time (in seconds) /// [default: none] - #[arg(long)] min_cpu_time: Option, /// Exclude records for system jobs (uid < 1000) - #[arg(long, default_value_t = false)] exclude_system_jobs: bool, /// Exclude records whose users match these comma-separated names [default: none] - #[arg(long)] exclude_users: Option, /// Exclude records whose commands start with these comma-separated names [default: none] - #[arg(long)] exclude_commands: Option, /// Create a per-host lockfile in this directory and exit early if the file exists on startup [default: none] - #[arg(long)] lockdir: Option, }, /// Extract system information Sysinfo {}, - /// Not yet implemented - Analyze {}, } fn main() { @@ -83,9 +62,7 @@ fn main() { env_logger::init(); - let cli = Cli::parse(); - - match &cli.command { + match &command_line() { Commands::PS { rollup, batchless, @@ -127,8 +104,151 @@ fn main() { Commands::Sysinfo {} => { sysinfo::show_system(×tamp); } - Commands::Analyze {} => { - println!("sonar analyze not yet completed"); + } +} + +// For the sake of simplicity: +// - allow repeated options to overwrite earlier values +// - all error reporting is via a generic "usage" message, without specificity as to what was wrong + +fn command_line() -> Commands { + let mut args = std::env::args(); + let _executable = args.next(); + if let Some(command) = args.next() { + match command.as_str() { + "ps" => { + let mut batchless = false; + let mut rollup = false; + let mut min_cpu_percent = None; + let mut min_mem_percent = None; + let mut min_cpu_time = None; + let mut exclude_system_jobs = false; + let mut exclude_users = None; + let mut exclude_commands = None; + let mut lockdir = None; + loop { + if let Some(arg) = args.next() { + match arg.as_str() { + "--batchless" => { + batchless = true; + } + "--rollup" => { + rollup = true; + } + "--exclude-system-jobs" => { + exclude_system_jobs = true; + } + "--exclude-users" => { + (args, exclude_users) = string_value(args); + } + "--exclude-commands" => { + (args, exclude_commands) = string_value(args); + } + "--lockdir" => { + (args, lockdir) = string_value(args); + } + "--min-cpu-percent" => { + (args, min_cpu_percent) = parsed_value::(args); + } + "--min-mem-percent" => { + (args, min_mem_percent) = parsed_value::(args); + } + "--min-cpu-time" => { + (args, min_cpu_time) = parsed_value::(args); + } + _ => { + usage(true); + } + } + } else { + break; + } + } + return Commands::PS { + batchless, + rollup, + min_cpu_percent, + min_mem_percent, + min_cpu_time, + exclude_system_jobs, + exclude_users, + exclude_commands, + lockdir, + } + } + "sysinfo" => { + return Commands::Sysinfo {} + } + "help" => { + usage(false); + } + _ => { + usage(true); + } + } + } else { + usage(true); + } +} + +fn string_value(mut args: std::env::Args) -> (std::env::Args, Option) { + if let Some(val) = args.next() { + (args, Some(val)) + } else { + usage(true); + } +} + +fn parsed_value(mut args: std::env::Args) -> (std::env::Args, Option) { + if let Some(val) = args.next() { + match val.parse::() { + Ok(value) => { + (args, Some(value)) + } + _ => { + usage(true); + } } + } else { + usage(true); } } + +fn usage(is_error: bool) -> ! { + let mut stdout = std::io::stdout(); + let mut stderr = std::io::stderr(); + let out: &mut dyn std::io::Write = if is_error { &mut stderr } else { &mut stdout }; + let _ = out.write(b"Usage: sonar + +Commands: + ps Take a snapshot of the currently running processes + sysinfo Extract system information + help Print this message + +Options for `ps`: + --batchless + Synthesize a job ID from the process tree in which a process finds itself + --rollup + Merge process records that have the same job ID and command name + --min-cpu-percent percentage + Include records for jobs that have on average used at least this + percentage of CPU, note this is nonmonotonic [default: none] + --min-mem-percent percentage + Include records for jobs that presently use at least this percentage of + real memory, note this is nonmonotonic [default: none] + --min-cpu-time seconds + Include records for jobs that have used at least this much CPU time + [default: none] + --exclude-system-jobs + Exclude records for system jobs (uid < 1000) + --exclude-users user,user,... + Exclude records whose users match these names [default: none] + --exclude-commands command,command,... + Exclude records whose commands start with these names [default: none] + --lockdir directory + Create a per-host lockfile in this directory and exit early if the file + exists on startup [default: none] +"); + let _ = out.flush(); + std::process::exit(if is_error { 2 } else { 0 }); +}