diff --git a/Cargo.lock b/Cargo.lock index e2c0ecf..a9d8eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,7 @@ dependencies = [ "futures", "lazy_static", "num_cpus", + "once_cell", "pin-project", "serde", "serde_cbor", diff --git a/Cargo.toml b/Cargo.toml index da9653a..c117705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ color-eyre = {version = "0.5.10", default-features=false} futures = "0.3.12" lazy_static = "1.4.0" num_cpus = "1.13.0" +once_cell = "1.5.2" pin-project = "1.0.5" serde = {version = "1.0.123", features=["derive"], optional=true} serde_cbor = {version = "0.11.1", optional=true} diff --git a/src/arg.rs b/src/arg.rs index 0eab1d7..715cfed 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -39,6 +39,8 @@ OPTIONS: -M Set number of parallel running tasks to unlimited. (Same as `--threads 0`). (default). -m Limit number of parallel tasks to the number of active CPU processors. -q Quiet mode. Don't output info messages about successful `stat`ing. + -Q Silent mode. Don't output any messages. + -v Verbose mode. Output extra information. --save Dump the collected data to this file for further inspection (only available when compiled with feature `inspect`) -D Dump the collected data to `stdout` (see `--save`) (only available when compiled with feature `inspect`) - Stop parsing arguments, treat all the rest as paths. @@ -104,7 +106,13 @@ fn parse>(args: I) -> eyre::Result cfg.max_tasks = config::max_tasks_cpus(); }, "-q" => { - cfg.silent = true; + cfg.output_level = config::OutputLevel::Quiet; + }, + "-Q" => { + cfg.output_level = config::OutputLevel::Silent; + }, + "-v" => { + cfg.output_level = config::OutputLevel::Verbose; }, #[cfg(feature="inspect")] "-D" => { cfg.serialise_output = Some(None); diff --git a/src/config.rs b/src/config.rs index 5e66e6c..42bc62b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,57 @@ use std::path::PathBuf; use std::num::NonZeroUsize; use std::{fmt,error}; +use once_cell::sync::OnceCell; + +static GLOBAL: OnceCell = OnceCell::new(); + +/// Get the global config instance, if it has been set. +#[inline] pub fn try_get_global() -> Option<&'static Config> +{ + GLOBAL.get() +} + +/// Get the global config instance. +/// # Panics +/// If one has not been set yet. +pub fn get_global() -> &'static Config +{ + try_get_global().expect("Tried to access global config when it had not been initialised") +} + +/// Try to set the global config instance, if it hasn't been already. +#[inline] pub fn try_set_global(cfg: Config) -> Result<(), Config> +{ + GLOBAL.set(cfg) +} + +/// Set the global config instance. +/// # Panics +/// If one has already been set. +pub fn set_global(cfg: Config) +{ + try_set_global(cfg).expect("Tried to set global config more than once") +} + +#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord)] +#[repr(u32)] +pub enum OutputLevel +{ + Silent = 0, + Quiet = 1, + Noisy = 2, + Verbose = 3, +} + +impl Default for OutputLevel +{ + #[inline] + fn default() -> Self + { + Self::Noisy + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Recursion @@ -54,7 +105,7 @@ pub struct Config pub recursive: Recursion, pub max_tasks: Option, - pub silent: bool, + pub output_level: OutputLevel, #[cfg(feature="inspect")] pub serialise_output: Option>, // Some(None) means dump to `stdout` @@ -62,6 +113,20 @@ pub struct Config impl Config { + /// Try to make this the global config instance, if one does not already exist. + #[inline] pub fn try_make_global(self) -> Result<&'static Self, Self> + { + try_set_global(self).map(|_| get_global()) + } + /// Make this the global config instance. + /// # Panics + /// If a global config instance has already been set. + #[inline] pub fn make_global(self) -> &'static Self + { + set_global(self); + get_global() + //GLOBAL.get_or_init(move || self) // This isn't okay to do here. As it would silently fail if there was already an instance, when we want it to panic in that case. + } /// Are we expected to dump data to `stdout`? #[inline] pub fn is_using_stdout(&self) -> bool { @@ -96,7 +161,7 @@ impl Default for Config paths: Vec::new(), recursive: Default::default(), max_tasks: None, //max_tasks_cpus(), - silent: false, + output_level: Default::default(), #[cfg(feature="inspect")] serialise_output: None, } @@ -149,11 +214,19 @@ impl fmt::Display for InvalidConfigError } } + /// Print an error line in accordance with `Config`'s output directives. #[macro_export] macro_rules! cfg_eprintln { ($cfg:expr, $fmt:literal $($tt:tt)*) => { { - if !$cfg.silent { + if $cfg.output_level > $crate::config::OutputLevel::Silent { + eprintln!($fmt $($tt)*); + } + } + }; + ($level:ident; $cfg:expr, $fmt:literal $($tt:tt)*) => { + { + if $cfg.output_level >= $crate::config::OutputLevel::$level { eprintln!($fmt $($tt)*); } } @@ -165,7 +238,7 @@ impl fmt::Display for InvalidConfigError ($cfg:expr, $fmt:literal $($tt:tt)*) => { { let cfg = &$cfg; - if !cfg.silent { + if cfg.output_level > $crate::config::OutputLevel::Quiet { if cfg.is_using_stdout() { eprintln!($fmt $($tt)*); } else { @@ -173,5 +246,18 @@ impl fmt::Display for InvalidConfigError } } } - } + }; + ($level:ident; $cfg:expr, $fmt:literal $($tt:tt)*) => { + { + let cfg = &$cfg; + if cfg.output_level >= $crate::config::OutputLevel::$level { + if cfg.is_using_stdout() { + eprintln!($fmt $($tt)*); + } else { + println!($fmt $($tt)*); + } + } + } + }; + } diff --git a/src/defer_drop.rs b/src/defer_drop.rs index ed46f5a..56dd685 100644 --- a/src/defer_drop.rs +++ b/src/defer_drop.rs @@ -1,4 +1,5 @@ //! Mechanism to defer dropping of large objects to background threads +use super::*; use futures::{ prelude::*, future::OptionFuture, @@ -16,7 +17,7 @@ where T: Send + 'static { let len_bytes = vec.len() * std::mem::size_of::(); OptionFuture::from(if len_bytes > DEFER_DROP_VEC_SIZE_FLOOR { - eprintln!("Size of vector ({} bytes, {} elements of {:?}) exceeds defer drop size floor {}. Moving vector to a seperate thread for de-allocation", len_bytes, vec.len(), std::any::type_name::(), DEFER_DROP_VEC_SIZE_FLOOR); + cfg_eprintln!(Verbose; config::get_global(), "Size of vector ({} bytes, {} elements of {:?}) exceeds defer drop size floor {}. Moving vector to a seperate thread for de-allocation", len_bytes, vec.len(), std::any::type_name::(), DEFER_DROP_VEC_SIZE_FLOOR); Some(async move { tokio::task::spawn_blocking(move || drop(vec)).await.expect("Child panic while dropping vector"); }) @@ -33,7 +34,7 @@ where T: Send + 'static { let len_bytes = vec.len() * std::mem::size_of::(); if len_bytes > DEFER_DROP_VEC_SIZE_FLOOR { - eprintln!("Size of vector ({} bytes, {} elements of {:?}) exceeds defer drop size floor {}. Moving vector to a seperate thread for de-allocation", len_bytes, vec.len(), std::any::type_name::(), DEFER_DROP_VEC_SIZE_FLOOR); + cfg_eprintln!(Verbose; config::get_global(), "Size of vector ({} bytes, {} elements of {:?}) exceeds defer drop size floor {}. Moving vector to a seperate thread for de-allocation", len_bytes, vec.len(), std::any::type_name::(), DEFER_DROP_VEC_SIZE_FLOOR); tokio::task::spawn_blocking(move || drop(vec)); } } diff --git a/src/main.rs b/src/main.rs index 9becbe9..c9b59dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,23 +42,22 @@ async fn normal(cfg: config::Config) -> eyre::Result<()> return Err(eyre!("Interrupt signalled, exiting")); } }; + + let cfg = cfg.make_global(); let graph = tokio::task::spawn_blocking(move || { + cfg_println!(Verbose; cfg, "Computing hierarchy..."); let mut graph = graph.into_hierarchical(); + cfg_println!(Verbose; cfg, "Computing sizes..."); + graph.compute_recursive_sizes(); graph }).await.expect("Failed to compute hierarchy from graph"); - #[cfg(debug_assertions)] eprintln!("{:?}", graph); + #[cfg(debug_assertions)] cfg_eprintln!(Verbose; cfg, "{:?}", graph); - if cfg.is_using_stdout() { - eprintln!("Max size file: {:?}", graph.path_max_size_for(data::FsKind::File)); - eprintln!("Max size dir: {:?}", graph.path_max_size_for(data::FsKind::Directory)); - eprintln!("Max size all: {:?}", graph.path_max_size()); - } else { - println!("Max size file: {:?}", graph.path_max_size_for(data::FsKind::File)); - println!("Max size dir: {:?}", graph.path_max_size_for(data::FsKind::Directory)); - println!("Max size all: {:?}", graph.path_max_size()); - } + cfg_println!(Quiet; cfg, "Max size file: {:?}", graph.path_max_size_for(data::FsKind::File)); + cfg_println!(Quiet; cfg, "Max size dir: {:?}", graph.path_max_size_for(data::FsKind::Directory)); + cfg_println!(Quiet; cfg, "Max size all: {:?}", graph.path_max_size()); #[cfg(feature="inspect")] match cfg.serialise_output.as_ref().map(|ser_out| { diff --git a/src/serial.rs b/src/serial.rs index 645653d..891c084 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -27,7 +27,7 @@ where W: AsyncWrite + Unpin { let mut stream = Compressor::new(&mut to); - eprintln!("Writing {} bytes of type {:?} to stream of type {:?}", vec.len(), std::any::type_name::(), std::any::type_name::()); + cfg_eprintln!(Verbose; config::get_global(), "Writing {} bytes of type {:?} to stream of type {:?}", vec.len(), std::any::type_name::(), std::any::type_name::()); stream.write_all(&vec[..]) .await diff --git a/src/work.rs b/src/work.rs index b95a615..6b06ee1 100644 --- a/src/work.rs +++ b/src/work.rs @@ -47,7 +47,7 @@ pub async fn work_on_all(state: State) -> (INodeInfoGraph, Config) .ok() }, Err(err) => { - eprintln!("Failed to stat root {:?}: {}", path, err); + cfg_eprintln!(state.config(), "Failed to stat root {:?}: {}", path, err); None }, } @@ -117,15 +117,15 @@ fn walk(state: State, root: PathBuf, root_ino: INode) -> BoxFuture<'static, Hash let mut cache = state.cache_sub(); cache.insert(ino, fsinfo).await; }, - Err(err) => eprintln!("Failed to stat {:?}: {}", entry.path(), err), + Err(err) => cfg_eprintln!(state.config(), "Failed to stat {:?}: {}", entry.path(), err), } } }, - Err(err) => eprintln!("Walking {:?} failed: {}", root, err), + Err(err) => cfg_eprintln!(state.config(), "Walking {:?} failed: {}", root, err), } } }, - Err(err) => eprintln!("Failed to walk {:?}: {}", root, err), + Err(err) => cfg_eprintln!(state.config(), "Failed to walk {:?}: {}", root, err), } // drop work guard here } @@ -136,7 +136,7 @@ fn walk(state: State, root: PathBuf, root_ino: INode) -> BoxFuture<'static, Hash { output.extend(map); } else { - eprintln!("Child panic"); + cfg_eprintln!(state.config(), "Child panic"); } }