From b8c81571101e444047621a9fc2f3d3702e2aa2de Mon Sep 17 00:00:00 2001 From: Kevin Valerio Date: Thu, 26 Sep 2024 16:44:48 +0200 Subject: [PATCH] Adding elems to dashboard --- abc.out | 1 - src/cli/config.rs | 98 +++++++++++++++++ src/cli/ui/custom.rs | 3 +- src/cli/ui/monitor/corpus.rs | 58 +++++++--- src/cli/ui/monitor/logs.rs | 10 +- src/cli/ui/ratatui.rs | 204 +++++++++++++++++++++++++++++------ src/lib.rs | 1 + 7 files changed, 324 insertions(+), 51 deletions(-) delete mode 100644 abc.out diff --git a/abc.out b/abc.out deleted file mode 100644 index 4106e79..0000000 --- a/abc.out +++ /dev/null @@ -1 +0,0 @@ -0000000001fa80c2f600 diff --git a/src/cli/config.rs b/src/cli/config.rs index 0e3386a..c78a66f 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -15,12 +15,35 @@ use crate::{ }; use anyhow::Context; use frame_support::weights::Weight; +use ratatui::{ + layout::Rect, + style::{ + Color, + Modifier, + Style, + }, + text::{ + Line, + Span, + }, + widgets::{ + Block, + Borders, + List, + ListItem, + }, + Frame, +}; use serde_derive::{ Deserialize, Serialize, }; use sp_core::crypto::AccountId32; use std::{ + fmt::{ + Display, + Formatter, + }, fs, fs::File, io::Write, @@ -70,6 +93,12 @@ pub struct Configuration { pub show_ui: bool, } +// impl Display for Configuration { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// write!(f) +// } +// } + impl Default for Configuration { fn default() -> Self { Self { @@ -157,6 +186,75 @@ impl TryFrom<&PathBuf> for Configuration { } impl Configuration { + pub fn render_config(&self, f: &mut Frame, area: Rect) { + // Helper function to create list items for optional fields + fn format_option<'a, T: std::fmt::Debug>( + label: &'a str, + option: &'a Option, + ) -> ListItem<'a> { + let opt = format!("{:?}", option).replace("Some", ""); + ListItem::new(Line::from(vec![ + Span::raw(format!("{}: ", label)), + Span::styled(opt, Style::default().fg(Color::Yellow)), + ])) + } + + let items = vec![ + format_option("\nCores", &self.cores), + ListItem::new(Line::from(vec![ + Span::raw("Use Honggfuzz: "), + Span::styled( + format!("{}", self.use_honggfuzz), + Style::default().fg(Color::Yellow), + ), + ])), + format_option("Deployer Address", &self.deployer_address), + format_option("Max Messages Per Exec", &self.max_messages_per_exec), + format_option("Report Path", &self.report_path), + ListItem::new(Line::from(vec![ + Span::raw("Fuzz Origin: "), + Span::styled( + format!("{}", self.fuzz_origin), + Style::default().fg(Color::Yellow), + ), + ])), + format_option("Default Gas Limit", &self.default_gas_limit), + format_option("Storage Deposit Limit", &self.storage_deposit_limit), + format_option("Instantiate Initial Value", &self.instantiate_initial_value), + format_option("Constructor Payload", &self.constructor_payload), + ListItem::new(Line::from(vec![ + Span::raw("Verbose: "), + Span::styled( + format!("{}", self.verbose), + Style::default().fg(Color::Yellow), + ), + ])), + format_option( + "Instrumented Contract Path", + &self.instrumented_contract_path, + ), + format_option("Fuzz Output", &self.fuzz_output), + ListItem::new(Line::from(vec![ + Span::raw("Show UI: "), + Span::styled( + format!("{}", self.show_ui), + Style::default().fg(Color::Yellow), + ), + ])), + ]; + + let config_list = List::new(items) + .block( + Block::default() + .borders(Borders::ALL) + .title("Configuration"), + ) + .style(Style::default().fg(Color::White)) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)) + .highlight_symbol("> "); + + f.render_widget(config_list, area); + } pub fn should_fuzz_origin(&self) -> OriginFuzzingOption { match self.fuzz_origin { true => EnableOriginFuzzing, diff --git a/src/cli/ui/custom.rs b/src/cli/ui/custom.rs index 75cf9d7..d7505de 100644 --- a/src/cli/ui/custom.rs +++ b/src/cli/ui/custom.rs @@ -72,8 +72,7 @@ impl CustomManager { rx.recv()??; - let ratatui = - CustomUI::new(cloned_config.fuzz_output()).context("Couldn't create the custom UI ")?; + let ratatui = CustomUI::new(&cloned_config).context("Couldn't create the custom UI ")?; ratatui.initialize_tui()?; Ok(()) diff --git a/src/cli/ui/monitor/corpus.rs b/src/cli/ui/monitor/corpus.rs index 5b6aafa..81ad96b 100644 --- a/src/cli/ui/monitor/corpus.rs +++ b/src/cli/ui/monitor/corpus.rs @@ -3,11 +3,9 @@ use crate::cli::config::{ PhinkFiles, }; use anyhow::bail; -use chrono::Utc; use std::{ fs, path::PathBuf, - slice::Iter, time::UNIX_EPOCH, }; @@ -55,23 +53,49 @@ impl CorpusWatcher { self.data().iter().map(|entry| (entry.x, entry.y)).collect() } + // pub fn data(&mut self) -> Vec { + // let mut data: Vec = Vec::new(); + // if let Ok(entries) = fs::read_dir(&self.corpus_folder) { + // let entries: Vec<_> = entries.filter_map(Result::ok).collect(); + // for (i, entry) in entries.into_iter().enumerate() { + // if let Ok(metadata) = entry.metadata() { + // if let Ok(created_time) = metadata.created() { + // if let Ok(duration_since_epoch) = created_time.duration_since(UNIX_EPOCH) + // { // println!("{:?}", entry.path()); + // data.push(PlotEntry::new( + // duration_since_epoch.as_millis_f64(), + // i as f64, + // )); + // } + // } + // } + // } + // } + // data.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap()); + // data + // } pub fn data(&mut self) -> Vec { let mut data: Vec = Vec::new(); if let Ok(entries) = fs::read_dir(&self.corpus_folder) { - let entries: Vec<_> = entries.filter_map(Result::ok).collect(); - let count = entries.len() as f64; - for entry in entries { - if let Ok(metadata) = entry.metadata() { - if let Ok(created_time) = metadata.created() { - if let Ok(duration_since_epoch) = created_time.duration_since(UNIX_EPOCH) { - let x = duration_since_epoch.as_secs() as f64; - data.push(PlotEntry::new(x, count)); - } - } - } + let mut entries: Vec<_> = entries.filter_map(Result::ok).collect(); + + // Sort entries by their creation time + entries.sort_by_key(|entry| entry.metadata().unwrap().created().unwrap()); + + for (i, entry) in entries.into_iter().enumerate() { + let x = entry + .metadata() + .unwrap() + .created() + .unwrap() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs_f64(); + + data.push(PlotEntry::new(x, i as f64)); } } - data.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap()); + data } } @@ -127,7 +151,7 @@ mod tests { sleep(Duration::from_secs(1)); // Sleep to ensure different timestamp let data_after_one_file = watcher.data(); assert_eq!(data_after_one_file.len(), 1217); - assert_eq!(data_after_one_file.first().unwrap().y, 1217f64); // One file, so y should be 1 + assert_eq!(data_after_one_file.get(3).unwrap().y, 3f64); // One file, so y should be 1 // Add another file and check again let mut temp_file = NamedTempFile::new_in(corpus_path.clone())?; @@ -136,12 +160,12 @@ mod tests { sleep(Duration::from_secs(1)); // Sleep to ensure different timestamp let data_after_one_file = watcher.data(); assert_eq!(data_after_one_file.len(), 1218); - assert_eq!(data_after_one_file.get(2).unwrap().y, 1218f64); // Two files, so y should be 2 + assert_eq!(data_after_one_file.get(2).unwrap().y, 2f64); // Two files, so y should be 2 // Check that x values (timestamps) are increasing let second = data_after_one_file.get(40).unwrap().x; // we do 40 because if we take 2, it'll have the same timestamp let first = data_after_one_file.first().unwrap().x; - // println!("second: {} & first: {}", second, first); + println!("second: {} & first: {}", second, first); assert!(second > first); Ok(()) } diff --git a/src/cli/ui/monitor/logs.rs b/src/cli/ui/monitor/logs.rs index 8413664..2e7d799 100644 --- a/src/cli/ui/monitor/logs.rs +++ b/src/cli/ui/monitor/logs.rs @@ -18,6 +18,7 @@ pub struct AFLProperties { pub(crate) corpus_count: u32, pub(crate) saved_crashes: u32, pub(crate) exec_speed: u32, + pub(crate) stability: f64, } impl AFLProperties { @@ -65,6 +66,12 @@ impl FromStr for AFLProperties { props.last_saved_crash = cap[1].to_string(); } + if let Some(cap) = Regex::new(r"stability : (.+?)\s+│").unwrap().captures(s) { + let percentage_str = cap[1].to_string().replace("%", ""); + let percentage: f64 = percentage_str.parse().unwrap(); + props.stability = percentage / 100.0; + } + props.corpus_count = extract_value(s, r"corpus count : (\d+)").unwrap_or_default(); props.saved_crashes = extract_value(s, r"saved crashes : (\d+)").unwrap_or_default(); props.exec_speed = extract_value(s, r"exec speed : (\d+)").unwrap_or_default(); @@ -197,7 +204,7 @@ mod tests { │ arithmetics : 0/0, 0/0, 0/0 │ pend fav : 0 │ │ known ints : 0/0, 0/0, 0/0 │ own finds : 0 │ │ dictionary : 0/0, 0/0, 0/0, 0/0 │ imported : 4 │ -│havoc/splice : 0/514k, 0/992k │ stability : 100.00% │ +│havoc/splice : 0/514k, 0/992k │ stability : 97.42% │ │py/custom/rq : unused, unused, unused, unused ├───────────────────────┘ │ trim/eff : disabled, n/a │ [cpu023: 51%] └─ strategy: explore ────────── state: started :-) ──┘ @@ -217,6 +224,7 @@ mod tests { assert_eq!(properties.last_new_find, "0 days, 0 hrs, 2 min, 49 sec"); assert_eq!(properties.last_saved_crash, "none seen yet"); assert_eq!(properties.corpus_count, 5); + assert_eq!(properties.stability, 0.9742000000000001); Ok(()) } diff --git a/src/cli/ui/ratatui.rs b/src/cli/ui/ratatui.rs index 8558f76..6deab00 100644 --- a/src/cli/ui/ratatui.rs +++ b/src/cli/ui/ratatui.rs @@ -1,6 +1,12 @@ -use crate::cli::ui::monitor::{ - corpus::CorpusWatcher, - logs::AFLDashboard, +use crate::cli::{ + ui::monitor::{ + corpus::CorpusWatcher, + logs::{ + AFLDashboard, + AFLProperties, + }, + }, + ziggy::ZiggyConfig, }; use crossterm::{ event, @@ -19,33 +25,46 @@ use ratatui::{ Style, }, symbols, + text::{ + Line, + Span, + Text, + }, widgets::{ Axis, Block, Borders, Chart, Dataset, + Gauge, + LineGauge, + List, + ListItem, Paragraph, + Sparkline, }, Frame, }; use std::{ - path::PathBuf, + borrow::Borrow, thread, time::Duration, }; #[derive(Clone, Debug)] pub struct CustomUI { + ziggy_config: ZiggyConfig, afl_dashboard: AFLDashboard, corpus_watcher: CorpusWatcher, } impl CustomUI { const REFRESH_MS: u64 = 500; - pub fn new(output: PathBuf) -> anyhow::Result { + pub fn new(ziggy_config: &ZiggyConfig) -> anyhow::Result { + let output = ziggy_config.clone().fuzz_output(); Ok(Self { - afl_dashboard: AFLDashboard::from_output(output.to_owned())?, + ziggy_config: ziggy_config.clone(), + afl_dashboard: AFLDashboard::from_output(output.clone())?, corpus_watcher: CorpusWatcher::from_output(output)?, }) } @@ -56,9 +75,9 @@ impl CustomUI { .margin(1) .constraints( [ - Constraint::Length(3), + Constraint::Length(10), Constraint::Length(8), - Constraint::Min(10), + Constraint::Min(8), ] .as_ref(), ) @@ -66,14 +85,75 @@ impl CustomUI { self.clone().render_title(f, chunks[0]); self.clone().render_stats(f, chunks[1]); - self.render_chart(f, chunks[2]); + self.clone().render_chart_and_config(f, chunks[2]); + } + + fn render_chart_and_config(self, f: &mut Frame, area: Rect) { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(area); + + self.clone().render_chart(f, chunks[0]); + self.ziggy_config.config.render_config(f, chunks[1]); + // self.render_config(f, chunks[1]); } + // fn render_config(&self, f: &mut Frame, area: Rect) { + // let item = ListItem::new(Line::from(vec![ + // Span::raw("Cores: "), + // Span::styled( + // format!("{:?}", self.ziggy_config.config.cores), + // Style::default().fg(Color::Yellow), + // ), + // ])); + // + // let item2 = ListItem::new(Line::from(vec![ + // Span::raw("Use Honggfuzz: "), + // Span::styled( + // format!("{}", self.ziggy_config.config.use_honggfuzz), + // Style::default().fg(Color::Yellow), + // ), + // ])); + // let items: Vec = vec![item, item2]; + // + // let config_list = List::new(items) + // .block( + // Block::default() + // .borders(Borders::ALL) + // .title("Configuration"), + // ) + // .style(Style::default().fg(Color::White)) + // .highlight_style(Style::default().add_modifier(Modifier::BOLD)) + // .highlight_symbol("> "); + // + // f.render_widget(config_list, area); + // } + fn render_octopus(self, f: &mut Frame, area: Rect) { + let ascii_art = r#" +,---. +( @ @ ) + ).-.( +'/|||\` + '|` + "#; + + let octopus = Paragraph::new(ascii_art) + .style( + Style::default() + .fg(Color::Magenta) + .add_modifier(Modifier::BOLD), + ) + .alignment(ratatui::layout::Alignment::Center); + + f.render_widget(octopus, area); + } fn render_title(self, f: &mut Frame, area: Rect) { - let title = Paragraph::new("Fuzzing Dashboard") + self.render_octopus(f, area); + let title = Paragraph::new("Phink Fuzzing Dashboard") .style( Style::default() - .fg(Color::Cyan) + .fg(Color::Magenta) .add_modifier(Modifier::BOLD), ) .alignment(ratatui::layout::Alignment::Center); @@ -88,22 +168,88 @@ impl CustomUI { .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(area); - let left_stats = Paragraph::new(format!( - "Run Time: {}\nLast New Find: {}\nLast Saved Crash: {}", - data.run_time, data.last_new_find, data.last_saved_crash - )) - .block(Block::default().borders(Borders::ALL).title("Stats")); - - let right_stats = Paragraph::new(format!( - "Corpus Count: {}\nSaved Crashes: {}\nExec Speed: {} exec/sec", - data.corpus_count, data.saved_crashes, data.exec_speed - )) - .block(Block::default().borders(Borders::ALL).title("Metrics")); + let left_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(0)].as_ref()) + .split(chunks[0]); - f.render_widget(left_stats, chunks[0]); + let right_stats = self.clone().metrics_right(&data); + self.stats_left(f, data.borrow(), left_chunks[0]); f.render_widget(right_stats, chunks[1]); } + fn stats_left(&self, frame: &mut Frame, data: &AFLProperties, area: Rect) { + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([Constraint::Percentage(70), Constraint::Min(1)].as_ref()) + .split(area); + + let paragraph = Paragraph::new(Vec::from([ + Line::from(vec![ + Span::raw("Running for: "), + Span::styled( + data.run_time.clone(), + Style::default().add_modifier(Modifier::BOLD), + ), + ]), + Line::from(vec![ + Span::raw("Last new find: "), + Span::styled( + data.last_new_find.clone(), + Style::default().add_modifier(Modifier::BOLD), + ), + ]), + Line::from(vec![ + Span::raw("Last saved crash: "), + Span::styled( + data.last_saved_crash.clone(), + Style::default().add_modifier(Modifier::BOLD), + ), + ]), + ])) + .block(Block::default().borders(Borders::ALL).title("Statistics")); + + frame.render_widget(paragraph, chunks[0]); + + // Create the gauge for stability + let label = format!("Stability: {:.2}%", data.stability * 100.0); + let gauge = Gauge::default() + .gauge_style(Style::default().fg(Color::DarkGray).bg(Color::White)) + .use_unicode(true) + .label(label) + .ratio(data.stability); + + frame.render_widget(gauge, chunks[1]); + } + + fn metrics_right(self, data: &AFLProperties) -> Paragraph { + Paragraph::new(Vec::from([ + Line::from(vec![ + Span::raw("Corpus count: "), + Span::styled( + data.corpus_count.to_string(), + Style::default().add_modifier(Modifier::BOLD), + ), + ]), + Line::from(vec![ + Span::raw("Saved crashes: "), + Span::styled( + data.saved_crashes.to_string(), + Style::default().add_modifier(Modifier::BOLD), + ), + ]), + Line::from(vec![ + Span::raw("Execution speed: "), + Span::styled( + format!("{} exec/sec", data.exec_speed), + Style::default().add_modifier(Modifier::BOLD), + ), + ]), + ])) + .block(Block::default().borders(Borders::ALL).title("Metrics")) + } + fn render_chart(mut self, f: &mut Frame, area: Rect) { let chunks = Layout::default() .direction(Direction::Vertical) @@ -112,9 +258,11 @@ impl CustomUI { let corpus_counter: &[(f64, f64)] = &self.corpus_watcher.as_tuple_slice(); + // println!("{:?}", corpus_counter); + let dataset = vec![Dataset::default() .name("Executions") - .marker(symbols::Marker::Braille) + .marker(symbols::Marker::Dot) .style(Style::default().fg(Color::Cyan)) .data(corpus_counter)]; @@ -127,14 +275,12 @@ impl CustomUI { .x_axis( Axis::default() .title("Time") - .style(Style::default().fg(Color::Gray)) - .bounds([0.0, 6.0]), + .style(Style::default().fg(Color::Gray)), ) .y_axis( Axis::default() .title("Executions/sec") - .style(Style::default().fg(Color::Gray)) - .bounds([0.0, 10.0]), + .style(Style::default().fg(Color::Gray)), ); f.render_widget(chart, chunks[0]); @@ -154,9 +300,7 @@ impl CustomUI { loop { terminal.draw(|f| self.clone().ui(f))?; - thread::sleep(Duration::from_millis(Self::REFRESH_MS)); - if event::poll(Duration::from_millis(Self::REFRESH_MS))? { if let Event::Key(key) = crossterm::event::read()? { if key.kind == event::KeyEventKind::Press diff --git a/src/lib.rs b/src/lib.rs index 2f197dd..1c773a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![feature(os_str_display)] +#![feature(duration_millis_float)] #![recursion_limit = "1024"] extern crate core;