Repository: google/OpenSK
File: tools/heapviz/src/main.rs (Rust)

Code before:
use regex::Regex;
use std::io::{BufRead, Write};
use std::thread::sleep;
use std::time::Duration;

struct Event {
    is_alloc: bool,
    start: usize,
    len: usize,
}

fn main() {
    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() {
        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::<usize>().unwrap();
            let start = usize::from_str_radix(&caps.get(3).unwrap().as_str(), 16).unwrap();
            events.push(Event {
                is_alloc: typ == "alloc",
                start,
                len,
            });
        }
    }

    let count_alloc = events.iter().filter(|e| e.is_alloc).count();
    let count_dealloc = events.len() - count_alloc;
    let start = events.iter().map(|e| e.start).min().unwrap_or(0);
    let end = events.iter().map(|e| e.start + e.len).max().unwrap_or(0);
    let mut usage = 0;
    let peak = events
        .iter()
        .map(|e| {
            if e.is_alloc {
                usage += e.len;
            } else {
                usage -= e.len;
            }
            usage
        })
        .max()
        .unwrap_or(0);
    let len = end - start;
    println!(
        "Observed {} allocations and {} deallocations",
        count_alloc, count_dealloc
    );
    println!("Start address: {:08x}", start);
    println!("End address: {:08x}", end);
    println!("Peak usage: {0} = {0:08x} bytes", peak);
    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!();

    let window = ncurses::initscr();
    ncurses::cbreak();
    ncurses::noecho();
    ncurses::intrflush(window, false);
    ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);

    let width = ncurses::getmaxx(window) as usize;

    for e in events.iter() {
        let position = e.start - start;
        ncurses::wmove(window, (position / width) as i32, (position % width) as i32);

        let mut s = Vec::with_capacity(e.len);
        if e.is_alloc {
            s.resize(e.len, b'#');
        } else {
            s.resize(e.len, b'.');
        }
        ncurses::addstr(std::str::from_utf8(s.as_slice()).unwrap());
        ncurses::refresh();
        sleep(Duration::from_millis(50));
    }

    ncurses::endwin();
}


Review comment (suggestion):
Considering that there's no actual resizing done here (length is still `e.len`), I guess you weren't using `s.fill()` instead because it's a nightly feature.

Code after:
    println!("Start address: {:08x}", start);
    println!("End address: {:08x}", end);
    println!("Peak usage: {0} = {0:08x} bytes", peak);
    println!("Peak consumption: {0} = {0:08x} bytes", len);
    println!("Fragmentation overhead: {0} = {0:08x} bytes", len - peak);

    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();
    ncurses::noecho();
    ncurses::intrflush(window, false);
    ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);

    let width = ncurses::getmaxx(window) as usize;

    for e in events.iter() {
        let position = e.start - start;
        ncurses::wmove(window, (position / width) as i32, (position % width) as i32);

        let mut s = Vec::with_capacity(e.len);
        if e.is_alloc {
            s.resize(e.len, b'#');
        } else {
            s.resize(e.len, b'.');
        }
        ncurses::addstr(std::str::from_utf8(s.as_slice()).unwrap());
        ncurses::refresh();
        sleep(Duration::from_nanos(1_000_000_000 / config.fps));
    }

    ncurses::endwin();
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_event_alloc() {
        assert_eq!(
            parse_event("alloc[256, 1] = 0x2002401c (2 ptrs, 384 bytes)"),
            Some(Event {
                is_alloc: true,
                start: 0x2002401c,
                len: 256,
            })
        );