diff --git a/src/main.rs b/src/main.rs index 43bcda9..2a1b4fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,11 @@ use clap::ArgAction; use clap::Parser; -use inferno::flamegraph; -use std::fmt::Write; -use std::fs::File; +use primal::is_prime; use std::ops::RangeInclusive; -use tracing::Level; -use tracing_subscriber::fmt::format::FmtSpan; -use tracing_subscriber::FmtSubscriber; -use lightswitch::object::build_id; -use lightswitch::profiler::Collector; -use lightswitch::profiler::Profiler; -use lightswitch::unwind_info::{compact_printing_callback, UnwindInfoBuilder}; -use primal::is_prime; use std::error::Error; use std::path::PathBuf; - use std::time::Duration; const SAMPLE_FREQ_RANGE: RangeInclusive = 1..=1009; @@ -38,19 +27,11 @@ fn sample_freq_in_range(s: &str) -> Result { )); } if !is_prime(sample_freq.try_into().unwrap()) { - return Err(format!("sample frequency is not prime")); + return Err("sample frequency is not prime".to_string()); } Ok(sample_freq as u16) } -// Used to prevent the default value for duration from being dropped before -// we parse the command line args - probably a better way to do this -fn max_duration_as_str() -> &'static str { - let max_duration: &'static str = - Box::leak(Duration::MAX.as_secs().to_string().into_boxed_str()); - max_duration -} - #[derive(Parser, Debug)] struct Cli { /// Specific PIDs to profile @@ -72,13 +53,15 @@ struct Cli { )] show_info: Option, /// How long this agent will run in seconds - #[arg(short='D', long, default_value = max_duration_as_str(), value_parser = parse_duration)] + // #[arg(short='D', long, default_value = max_duration_as_str(), value_parser = parse_duration)] + #[arg(short='D', long, default_value = Duration::MAX.as_secs().to_string(), + value_parser = parse_duration)] duration: Duration, - // Enable TRACE (max) level logging - defaults to INFO level otherwise + /// Enable TRACE (max) level logging - defaults to INFO level otherwise #[arg(long, action=ArgAction::SetFalse)] filter_logs: bool, // Verification for this option guarantees the only possible selections - // are prime numbers up to and including 1001 + // are prime numbers up to and including 1009 /// Per-CPU Sampling Frequency in Hz #[arg(long, default_value_t = 19, value_name = "SAMPLE_FREQ_IN_HZ", value_parser = sample_freq_in_range, @@ -90,68 +73,14 @@ struct Cli { } fn main() -> Result<(), Box> { - let args = Cli::parse(); - - let subscriber = FmtSubscriber::builder() - .with_max_level(if args.filter_logs { - Level::TRACE - } else { - Level::INFO - }) - .with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE) - .finish(); - - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - if let Some(path) = args.show_unwind_info { - UnwindInfoBuilder::with_callback(&path, compact_printing_callback)?.process()?; - return Ok(()); - } - - if let Some(path) = args.show_info { - println!("build id {:?}", build_id(&PathBuf::from(path.clone()))); - let unwind_info: Result, anyhow::Error> = - UnwindInfoBuilder::with_callback(&path, |_| {}); - println!("unwind info {:?}", unwind_info.unwrap().process()); - - return Ok(()); - } - - let collector = Collector::new(); - - let mut p: Profiler<'_> = Profiler::new(false, args.duration, args.sample_freq); - p.profile_pids(args.pids); - p.run(collector.clone()); - - let profiles = collector.lock().unwrap().finish(); - - let mut folded = String::new(); - for profile in profiles { - for sample in profile { - let stack = sample - .ustack - .clone() - .into_iter() - .rev() - .collect::>(); - let stack = stack.join(";"); - let count: String = sample.count.to_string(); - - writeln!(folded, "{} {}", stack, count).unwrap(); - } - } - - let mut options: flamegraph::Options<'_> = flamegraph::Options::default(); - let data = folded.as_bytes(); - let flame_path = args.flamegraph_file; - let f = File::create(flame_path).unwrap(); - flamegraph::from_reader(&mut options, data, f).unwrap(); + let _args = Cli::parse(); Ok(()) } #[cfg(test)] mod tests { + use super::Cli; use super::*; use assert_cmd::Command; use expect_test::expect; @@ -163,5 +92,62 @@ mod tests { } #[test] - fn duration_arg() {} + fn cli_help() { + let mut cmd = Command::cargo_bin("claptests").unwrap(); + + cmd.arg("--help"); + let expected = expect![[r#" + "Usage: claptests [OPTIONS]\n\nOptions:\n --pids \n Specific PIDs to profile\n --tid \n Specific TIDs to profile (these can be outside the PIDs selected above)\n --show-unwind-info \n \n --show-info \n \n -D, --duration \n How long this agent will run in seconds [default: 18446744073709551615]\n --filter-logs\n \n --sample-freq \n Per-CPU Sampling Frequency in Hz [default: 19]\n -o, --output-file \n Output file for Flame Graph in SVG format [default: flame.svg]\n -h, --help\n Print help\n" + "#]]; + cmd.assert().success(); + let actual = String::from_utf8(cmd.unwrap().stdout).unwrap(); + expected.assert_debug_eq(&actual); + // cmd.assert().stdout(predicate::str::contains("junk")); + } + + #[test] + fn sample_freq_test() { + let _expected = expect![""]; + let argname = "--sample-freq"; + let myargs = vec!["claptests", "--sample-freq", "19"]; + + let result = Cli::try_parse_from(myargs); + let args = match result { + Ok(args) => args, + Err(error) => { + println!("Error: {:?}", error); + return; + } + }; + // let args = result.unwrap(); + // assert_eq!(val.sample_freq, 19); + // assert!(result.is_ok()); + + // Test a frequency below the range, which is also prime + if Cli::try_parse_from(vec![argname, "-101"].iter()).is_ok() { + panic!("sample frequency below range should fail"); + } + if Cli::try_parse_from([argname, "19"].iter()).is_err() { + panic!("sample frequency in range and prime should succeed"); + } + if Cli::try_parse_from(vec![argname, "20"].iter()).is_ok() { + panic!("sample frequency in range but not prime should fail"); + } + if Cli::try_parse_from(vec![argname, "49"].iter()).is_ok() { + panic!("sample frequency in range but not prime should fail"); + } + if Cli::try_parse_from(vec![argname, "101"].iter()).is_err() { + panic!("sample frequency in range and prime should succeed"); + } + if Cli::try_parse_from(vec![argname, "1009"].iter()).is_err() { + panic!("sample frequency in range and prime should succeed"); + } + if Cli::try_parse_from(vec![argname, "1010"].iter()).is_ok() { + panic!("sample frequency out of range and not prime should fail"); + } + if Cli::try_parse_from(vec![argname, "1013"].iter()).is_ok() { + panic!("sample frequency out of range and prime should fail"); + } + // Expected output/results for various inputs + } }