diff --git a/tools/heapviz/Cargo.toml b/tools/heapviz/Cargo.toml index e173d17..4ab1466 100644 --- a/tools/heapviz/Cargo.toml +++ b/tools/heapviz/Cargo.toml @@ -8,5 +8,6 @@ license = "Apache-2.0" edition = "2018" [dependencies] +clap = "2.33.1" ncurses = "5.99.0" regex = "1" diff --git a/tools/heapviz/src/main.rs b/tools/heapviz/src/main.rs index 3bc4176..f129692 100644 --- a/tools/heapviz/src/main.rs +++ b/tools/heapviz/src/main.rs @@ -12,11 +12,52 @@ // See the License for the specific language governing permissions and // limitations under the License. +use clap::{App, Arg}; use regex::Regex; -use std::io::{BufRead, Write}; +use std::fs::File; +use std::io::{BufRead, BufReader, Read, Write}; use std::thread::sleep; use std::time::Duration; +/// Configuration, built from CLI parameters. +struct Config { + /// Handle to the log file containing allocation operations. + logfile: File, + /// Number of allocation operations to show per second. + fps: u64, +} + +fn parse_cli() -> Config { + let matches = App::new("Heap visualizer") + .version("0.1") + .author("Guillaume Endignoux ") + .about("Tool to visualize heap usage of libtock-rs applications") + .arg(Arg::with_name("logfile") + .short("f") + .long("logfile") + .value_name("FILE") + .help("Log file containing allocation info (deploy OpenSK with --debug-allocations to obtain it)") + .takes_value(true) + .required(true)) + .arg(Arg::with_name("fps") + .long("fps") + .value_name("FPS") + .help("Number of allocation operations to show per second") + .takes_value(true) + .default_value("20")) + .get_matches(); + + let logpath = matches.value_of("logfile").unwrap(); + let fps = matches + .value_of("fps") + .unwrap() + .parse::() + .expect("The --fps parameter must be an integer"); + let logfile = File::open(logpath).expect("Couldn't open --logfile for reading"); + + Config { logfile, fps } +} + /// An allocation or deallocation event. struct Event { /// Whether this even is an allocation (true) or a deallocation (false). @@ -28,30 +69,32 @@ struct Event { } fn main() { - /// The following regex matches lines looking like the following from OpenSK's output. Such - /// lines are printed to the console when the `--debug-allocations` feature is enabled in the - /// deploy script. - /// - /// ``` - /// alloc[256, 1] = 0x2002410c (2 ptrs, 384 bytes) - /// dealloc[256, 1] = 0x2002410c (1 ptrs, 512 bytes) - /// ``` - /// - /// The two integers between square brackets after the (de)alloc keywords represent the length - /// and alignement of the allocated block, respectively. The integer serialized in hexadecimal - /// after the equal sign represents the starting address of the allocated block. The two - /// integers within parentheses represent statistics about the total number of allocated blocks - /// and the total number of allocated bytes after the (de)allocation operation, respectively. - /// - /// This regex captures three elements, in this order. - /// - The keyword to know whether this operation is an allocation or a deallocation. - /// - The length of the allocated block. - /// - The starting address of the allocated block. + let config = parse_cli(); + + // The following regex matches lines looking like the following from OpenSK's output. Such lines + // are printed to the console when the `--debug-allocations` feature is enabled in the deploy + // script. + // + // ``` + // alloc[256, 1] = 0x2002410c (2 ptrs, 384 bytes) + // dealloc[256, 1] = 0x2002410c (1 ptrs, 512 bytes) + // ``` + // + // The two integers between square brackets after the (de)alloc keywords represent the length + // and alignement of the allocated block, respectively. The integer serialized in hexadecimal + // after the equal sign represents the starting address of the allocated block. The two + // integers within parentheses represent statistics about the total number of allocated blocks + // and the total number of allocated bytes after the (de)allocation operation, respectively. + // + // This regex captures three elements, in this order. + // - The keyword to know whether this operation is an allocation or a deallocation. + // - The length of the allocated block. + // - The starting address of the allocated block. let re = Regex::new(r"^(alloc|dealloc)\[(\d+), \d+\] = 0x([0-9a-f]+) \(\d+ ptrs, \d+ bytes\)$") .unwrap(); let mut events = Vec::new(); - for line in std::io::stdin().lock().lines() { + for line in BufReader::new(config.logfile).lines() { if let Some(caps) = re.captures(&line.unwrap()) { let typ = caps.get(1).unwrap().as_str(); let len = caps.get(2).unwrap().as_str().parse::().unwrap(); @@ -92,13 +135,10 @@ fn main() { println!("Peak consumption: {0} = {0:08x} bytes", len); println!("Fragmentation overhead: {0} = {0:08x} bytes", len - peak); - print!("\nLoading visualization"); - for _ in 0..30 { - print!("."); - std::io::stdout().flush().unwrap(); - sleep(Duration::from_millis(50)); - } - println!(); + print!("\nPress ENTER to start the visualization..."); + std::io::stdout().flush().unwrap(); + // Wait for ENTER, by reading a single byte and discarding it. + let _ = std::io::stdin().lock().read(&mut [0u8]).unwrap(); let window = ncurses::initscr(); ncurses::cbreak(); @@ -120,7 +160,7 @@ fn main() { } ncurses::addstr(std::str::from_utf8(s.as_slice()).unwrap()); ncurses::refresh(); - sleep(Duration::from_millis(50)); + sleep(Duration::from_nanos(1_000_000_000 / config.fps)); } ncurses::endwin();