diff --git a/src/scan_manager.rs b/src/scan_manager.rs index 013eb57..371f015 100644 --- a/src/scan_manager.rs +++ b/src/scan_manager.rs @@ -574,7 +574,7 @@ pub struct FeroxState { /// Known responses responses: &'static FeroxResponses, - statistics: Arc, + statistics: Arc>, } /// FeroxSerialize implementation for FeroxState @@ -594,7 +594,7 @@ impl FeroxSerialize for FeroxState { /// that representation to seconds and then wait for those seconds to elapse. Once that period /// of time has elapsed, kill all currently running scans and dump a state file to disk that can /// be used to resume any unfinished scan. -pub async fn start_max_time_thread(time_spec: &str, stats: Arc) { +pub async fn start_max_time_thread(time_spec: &str, stats: Arc>) { log::trace!("enter: start_max_time_thread({})", time_spec); // as this function has already made it through the parser, which calls is_match on @@ -636,7 +636,7 @@ pub async fn start_max_time_thread(time_spec: &str, stats: Arc) { } /// Writes the current state of the program to disk (if save_state is true) and then exits -fn sigint_handler(stats: Arc) { +fn sigint_handler(stats: Arc>) { log::trace!("enter: sigint_handler"); let ts = SystemTime::now() diff --git a/src/statistics.rs b/src/statistics.rs index 955712f..9519bb0 100644 --- a/src/statistics.rs +++ b/src/statistics.rs @@ -1,12 +1,10 @@ -// todo needs to be serializable and added to scan save/resume/output // todo consider batch size for stats update/display (if display is used) // todo are there more metrics to capture? // - domains redirected to? -// - number of links extracted vs busted? +// - number of busted? // - number of borked urls? // - total time to run // - time per directory -// - wildcards filtered // todo integration test that hits some/all of the errors in make_request // todo create a summary report to be shown when the scan ends, should present the accumulated data in a way that makes interpretation easy @@ -22,7 +20,7 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::sync::{ atomic::{AtomicUsize, Ordering}, - Arc, + Arc, RwLock, }; use tokio::{ sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, @@ -52,7 +50,7 @@ macro_rules! atomic_load { /// Data collection of statistics related to a scan #[derive(Deserialize, Debug, Serialize)] pub struct Stats { - #[serde(rename = "type", default = "stats_kind")] + #[serde(rename = "type")] /// Name of this type of struct, used for serialization, i.e. `{"type":"statistics"}` kind: String, @@ -60,7 +58,7 @@ pub struct Stats { timeouts: AtomicUsize, /// tracker for total number of requests sent by the client - requests: AtomicUsize, + requests: usize, /// tracker for total number of requests expected to send if the scan runs to completion /// @@ -103,11 +101,9 @@ pub struct Stats { /// tracker for overall number of all filtered responses responses_filtered: AtomicUsize, -} -/// default value for `Stats::kind` -fn stats_kind() -> String { - String::from("statistics") + /// tracker for each directory's total scan time in seconds as a float + directory_scan_times: Vec, } /// Default implementation for Stats @@ -130,6 +126,7 @@ impl Default for Stats { status_403s: Default::default(), wildcards_filtered: Default::default(), responses_filtered: Default::default(), + directory_scan_times: Vec::new(), } } } @@ -248,6 +245,7 @@ impl Stats { } } + /// Build out the summary string of a `Stats` object fn summary(&self) -> String { let results_bottom = "───────────────────────────┬──────────────────────"; let results_top = "──────────────────────────────────────────────────"; @@ -264,15 +262,13 @@ impl Stats { let mut lines = Vec::new(); - let results_header = format!(" 📊{}📊", pad_str("Results", 44, Alignment::Center, None)); + let padded_results = pad_str("Results", 44, Alignment::Center, None); + let results_header = format!("\u{0020}📊{}📊\u{0020}", padded_results); + lines.push(results_top.to_string()); lines.push(results_header); lines.push(results_bottom.to_string()); - // printer.println(format!("{}", results_top)); - // printer.println(results_header); - // printer.println(format!("{}", results_bottom)); - let responses = format_summary_item!( "Requests Sent / Expected", format!( @@ -306,6 +302,8 @@ impl Stats { } } + // todo scan time stuff + lines.push(bottom.to_string()); lines.join("\n") @@ -392,7 +390,7 @@ pub enum StatField { /// The consumer simply receives `StatCommands` and updates the given `Stats` object as appropriate pub async fn spawn_statistics_handler( mut stats_channel: UnboundedReceiver, - stats: Arc, + stats: Arc>, ) { log::trace!( "enter: spawn_statistics_handler({:?}, {:?})", @@ -403,7 +401,7 @@ pub async fn spawn_statistics_handler( while let Some(command) = stats_channel.recv().await { match command as StatCommand { StatCommand::AddError(err) => { - stats.add_error(err); + stats.read().unwrap().add_error(err); } StatCommand::AddStatus(status) => { stats.add_status_code(status); @@ -422,10 +420,14 @@ pub async fn spawn_statistics_handler( /// Initialize new `Stats` object and the sc side of an mpsc channel that is responsible for /// updates to the aforementioned object. -pub fn initialize() -> (Arc, UnboundedSender, JoinHandle<()>) { +pub fn initialize() -> ( + Arc>, + UnboundedSender, + JoinHandle<()>, +) { log::trace!("enter: initialize"); - let stats_tracker = Arc::new(Stats::default()); + let stats_tracker = Arc::new(RwLock::new(Stats::default())); let cloned = stats_tracker.clone(); let (tx_stats, rx_stats): FeroxChannel = mpsc::unbounded_channel(); let stats_thread = @@ -446,7 +448,11 @@ mod tests { use super::*; /// simple helper to reduce code reuse - fn setup_stats_test() -> (Arc, UnboundedSender, JoinHandle<()>) { + fn setup_stats_test() -> ( + Arc>, + UnboundedSender, + JoinHandle<()>, + ) { initialize() }