diff --git a/Cargo.lock b/Cargo.lock index c337d7f..3679354 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,6 +162,7 @@ dependencies = [ "serde_cbor", "smallvec", "tokio", + "treemap", ] [[package]] @@ -781,6 +782,12 @@ dependencies = [ "syn", ] +[[package]] +name = "treemap" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1571f89da27a5e1aa83304ee1ab9519ea8c6432b4c8903aaaa6c9a9eecb6f36" + [[package]] name = "unicode-segmentation" version = "1.7.1" diff --git a/Cargo.toml b/Cargo.toml index 13ab3bd..d7f3df9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ codegen-units = 1 panic = "unwind" [features] -default = ["splash", "inspect", "defer-drop", "jemalloc", "prealloc"] +default = ["splash", "inspect", "defer-drop", "jemalloc", "prealloc", "treemap"] # When using the REPL to inspect graphs, save command history. save-history = [] @@ -52,3 +52,4 @@ serde = {version = "1.0.123", features=["derive"], optional=true} serde_cbor = {version = "0.11.1", optional=true} smallvec = "1.6.1" tokio = {version = "0.2", features=["full"]} +treemap = {version = "0.3.2", optional=true} diff --git a/src/arg/parsing.rs b/src/arg/parsing.rs index 1c802f2..ef218de 100644 --- a/src/arg/parsing.rs +++ b/src/arg/parsing.rs @@ -6,6 +6,33 @@ use std::fmt; #[cfg(feature="inspect")] use config::OutputSerialisationMode; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum InspectKind +{ + Treemap(Option<(u64, u64)>), +} + +impl fmt::Display for InspectKind +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::Treemap(None) => write!(f, "treemap"), + Self::Treemap(Some((x,y))) => write!(f, "treemap:{}:{}", x, y), // Width and height. + } + } +} + +impl std::str::FromStr for InspectKind +{ + type Err = eyre::Report; + + fn from_str(s: &str) -> Result + { + todo!() + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Argument { @@ -29,6 +56,8 @@ pub enum Argument StopReading, + Inspect(InspectKind), + Input(String), } @@ -70,6 +99,9 @@ impl Argument { use Argument::*; match self { + Inspect(InspectKind::Treemap(None)) => cfg.inspection.treemap = Some((640, 480)), + Inspect(InspectKind::Treemap(x)) => cfg.inspection.treemap = x, + LimitConcMaxProc => cfg.max_tasks = config::max_tasks_cpus(), LimitConc(max) => cfg.max_tasks = Some(max), UnlimitConc => cfg.max_tasks = None, @@ -108,6 +140,8 @@ impl fmt::Display for Argument use Argument::*; match self { + Inspect(ins) => write!(f, "--inspect {}", ins), + ModeChangeHelp => write!(f, "--help"), LimitConcMaxProc => write!(f, "-m"), LimitConc(limit) => write!(f, "--threads {}", limit), @@ -365,7 +399,12 @@ where I: Iterator let mut keep_reading = Continue::Yes; let item = match this.trim() { - "--threads" => { + "--inspect" => { + let ins = args.next().ok_or(eyre!("`--inspect` expects a parameter")) + .with_suggestion(suggestion_intended_arg.clone())?; + Argument::Inspect(ins.parse().wrap_err(eyre!("Failed to parse parameter for `--inspect`"))?) + }, + " --threads" => { let max = args.next().ok_or(eyre!("`--threads` expects a parameter")) .with_suggestion(suggestion_intended_arg.clone())?; match NonZeroUsize::new(max.parse::() diff --git a/src/config.rs b/src/config.rs index 98f2fb8..b8fbd4c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -121,6 +121,25 @@ impl OutputSerialisationMode } } +/// What to do with the graph afterwards? +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Inspection +{ + pub treemap: Option<(u64, u64)>, //w and h +} + +impl Default for Inspection +{ + #[inline] + fn default() -> Self + { + Self { + treemap: None + } + } +} + + /// Configuration for this run #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config @@ -133,6 +152,8 @@ pub struct Config #[cfg(feature="inspect")] pub serialise_output: Option, + + pub inspection: Inspection, } impl Config @@ -188,6 +209,8 @@ impl Default for Config output_level: Default::default(), #[cfg(feature="inspect")] serialise_output: None, + + inspection: Default::default(), } } } diff --git a/src/data/graph.rs b/src/data/graph.rs index e2dc052..102ff46 100644 --- a/src/data/graph.rs +++ b/src/data/graph.rs @@ -15,8 +15,123 @@ pub struct INodeInfoGraph children: HashMap>, //reverse lookup for directory INodes and their parent INodes } +#[derive(Debug, Clone)] +pub struct INodeInfoGraphEntry<'a> +{ + master: &'a INodeInfoGraph, + inode: INode, +} + +impl<'a> INodeInfoGraphEntry<'a> +{ + /// Create an iterator over children of this graph item. + /// + /// # Note + /// Returns `None` if this is not a directory item. + pub fn level(&self) -> Option> + { + match self.master.inodes.get(&self.inode) + { + Some(FsInfo::Directory(parent)) => { + Some( + Level{ + master: self.master, + inode: self.inode.clone(), + children: match self.master.children.get(&self.inode) { + Some(iter) => iter.iter(), + _ => [].iter(), + }, + } + ) + }, + Some(FsInfo::File(_, _)) => None, + None => { + panic!("No lookup information for inode {:?}", self.inode) + }//Some(Level{master: self.master, inode: None, children: [].iter()}), + } + } + + /// The `FsInfo` for this item. + pub fn info(&self) -> &FsInfo + { + self.master.inodes.get(&self.inode).unwrap() + } + /// The path for this inode + pub fn path(&self) -> &PathBuf + { + self.master.paths_reverse.get(&self.inode).unwrap() + } +} + +#[derive(Debug, Clone)] +pub struct Level<'a> +{ + master: &'a INodeInfoGraph, + inode: INode,// Should only ever be `Directory` for `Level`. + children: std::slice::Iter<'a, INode>, +} + +#[derive(Debug, Clone)] +pub struct TopLevel<'a> +{ + master: &'a INodeInfoGraph, + children: std::vec::IntoIter<&'a INode>, +} + +impl<'a> Iterator for TopLevel<'a> +{ + type Item = INodeInfoGraphEntry<'a>; + fn next(&mut self) -> Option + { + let master = self.master; + self.children.next().map(|inode| { + INodeInfoGraphEntry{ + master, + inode: inode.clone(), + } + }) + } +} + + +impl<'a> Iterator for Level<'a> +{ + type Item = INodeInfoGraphEntry<'a>; + fn next(&mut self) -> Option + { + let master = self.master; + self.children.next().map(|inode| { + INodeInfoGraphEntry{ + master, + inode: inode.clone(), + } + }) + } +} + impl INodeInfoGraph { + /// Total size of all files + pub fn total_size(&self) -> u64 + { + self.inodes.iter().map(|(_, v)| { + match v { + FsInfo::File(len, _) => *len, + _ => 0, + } + }).sum() + } + /// An iterator over the top level of items + pub fn top_level(&self) -> TopLevel<'_> + { + let top_level = self.inodes.iter().filter(|(node, _)| { + !self.inodes.contains_key(node) + }); + TopLevel { + master: self, + children: top_level.map(|(k, _)| k).collect::>().into_iter(), + } + } /// Create a new graph from these linked `HashMap`s #[inline] pub fn new(inodes: HashMap, paths: HashMap) -> Self { @@ -48,6 +163,11 @@ impl INodeInfoGraph self.paths_reverse.extend(self.paths.iter().map(|(x,y)| (*y,x.clone()))); self } + /// Total number of items in the graph + #[inline] pub fn len(&self) -> usize + { + self.children.len() + } /// Get the FsInfo of this `INode` #[inline] pub fn get_info(&self, node: impl Borrow) -> Option<&FsInfo> { @@ -57,17 +177,17 @@ impl INodeInfoGraph /// An iterator over top-level children of this node pub fn children_of(&self, node: impl Borrow) -> !//Children<'_> { - todo!(); - /*Children(self, match self.children.get(node.borrow()) { - Some(slc) => slc.iter(), - _ => [].iter(), - })*/ - } + todo!(); + /*Children(self, match self.children.get(node.borrow()) { + Some(slc) => slc.iter(), + _ => [].iter(), +})*/ +} /// An iterator over all the directories in this pub fn directories(&self) -> !//Directories<'_> { - todo!()//Directories(self, self.children.keys()) - }*/ + todo!()//Directories(self, self.children.keys()) +}*/ /// Convert into a hierarchical representation pub fn into_hierarchical(self) -> HierarchicalINodeGraph @@ -264,6 +384,12 @@ impl HierarchicalINodeGraph hi.len().map(|x| (path.as_path(), x)) }).max_by_key(|x| x.1) } + + /// Complete number of items in the tree + #[inline] pub fn len(&self) -> usize + { + self.table.len() + } } impl From for HierarchicalINodeGraph diff --git a/src/data/mod.rs b/src/data/mod.rs index 8aabf34..8b493e4 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -18,6 +18,7 @@ pub use graph::{ INodeInfoGraph, HierarchicalINodeGraph, FsObjKind as FsKind, + INodeInfoGraphEntry, }; /// A raw file or directory inode number diff --git a/src/ext.rs b/src/ext.rs index 90ab111..b301295 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -238,11 +238,11 @@ impl Iterator for OptionIterator type Item = I::Item; fn next(&mut self) -> Option { - self.0.map(|x| x.next()).flatten() + self.0.as_mut().map(|x| x.next()).flatten() } fn size_hint(&self) -> (usize, Option) { - match self.0 { + match self.0.as_ref() { Some(i) => i.size_hint(), _ => (0, Some(0)), } @@ -257,7 +257,7 @@ impl DoubleEndedIterator for OptionIterator where I: DoubleEndedIterator { fn next_back(&mut self) -> Option { - self.0.map(|x| x.next_back()).flatten() + self.0.as_mut().map(|x| x.next_back()).flatten() } } diff --git a/src/info/map.rs b/src/info/map.rs new file mode 100644 index 0000000..a9398c2 --- /dev/null +++ b/src/info/map.rs @@ -0,0 +1,124 @@ +use super::*; +use treemap::{ + Rect, + Mappable, + TreemapLayout +}; +use data::{FsInfo, INodeInfoGraph, INodeInfoGraphEntry}; + +/// A treemap of all **files** in the graph. +#[derive(Debug, Clone, PartialEq)] +pub struct Treemap +{ + //layout: TreemapLayout, + nodes: Vec, +} + +impl Treemap +{ + /// All nodes of the map. + #[inline] pub fn nodes(&self) -> &[MapNode] + { + &self.nodes[..] + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MapNode +{ + name: String, + + vw_size: f64, // Should be halved each iteration + vw_bounds: Rect, // should be Rect::new() before aligntment +} + +impl MapNode +{ + /// The calculated bounds of the node + #[inline] pub fn bounds(&self) -> &Rect + { + &self.vw_bounds + } + /// The name of the node + #[inline] pub fn name(&self) -> &str + { + &self.name[..] + } + + #[inline] fn size(&self) -> f64 //Is this useful for consumer? + { + self.vw_size + } + + #[inline] fn new(name: String) -> Self + { + Self { + vw_size: 1.0, + vw_bounds: Rect::new(), + name, + } + } +} + +/// Create a treemap from this graph. +pub fn treemap(_cfg: &Config, graph: &INodeInfoGraph, (w, h): (f64, f64)) -> Treemap +{ + let layout = TreemapLayout::new(); + let mut nodes = Vec::with_capacity(graph.len()); + //TODO: Recursively walk the graph, halving size with each iteration. (Maybe we need `INodeInfoGraph` here, not `Hierarchicalinodegraph`?) + let total_size = graph.total_size(); + + let size = 1.0; + + fn calc_path<'a, I: IntoIterator>>(insert: &'a mut Vec, from: I, total_size: u64, size: f64, scale: f64) + { + for top in from { + let path = top.path(); + match top.info() { + FsInfo::Directory(_) => { + //TODO: Do we add dir itself? I think not? + // Add children + let size = size * 0.5; + calc_path(insert, top.level().unwrap(), total_size, size, scale); + }, + &FsInfo::File(sz, _) => { + let fract = (sz as f64) / (total_size as f64); + insert.push(MapNode { + name: path.to_string_lossy().into_owned(), + vw_size: fract * scale, + vw_bounds: Rect::new(), + }) + }, + } + } + } + + calc_path(&mut nodes, graph.top_level(), total_size, size, 1.0); + + layout.layout_items(&mut nodes[..], Rect { + x: 0.0, + y: 0.0, + w, h, + }); + + Treemap { + //layout, + nodes + } +} + +impl Mappable for MapNode +{ + fn size(&self) -> f64 + { + self.vw_size + } + fn bounds(&self) -> &Rect + { + &self.vw_bounds + } + fn set_bounds(&mut self, bounds: Rect) + { + self.vw_bounds = bounds; + } +} diff --git a/src/info/mod.rs b/src/info/mod.rs index 27087a7..2a8b571 100644 --- a/src/info/mod.rs +++ b/src/info/mod.rs @@ -4,7 +4,7 @@ use super::*; use data::HierarchicalINodeGraph; use config::Config; -pub mod repl; +//pub mod repl; /// Print the most basic info pub fn print_basic_max_info(cfg: &Config, graph: &HierarchicalINodeGraph) @@ -13,3 +13,9 @@ pub fn print_basic_max_info(cfg: &Config, graph: &HierarchicalINodeGraph) 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="treemap")] +mod map; +#[cfg(feature="treemap")] +pub use map::*; diff --git a/src/main.rs b/src/main.rs index f264790..5715956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,7 +99,7 @@ async fn write_graph(graph: Arc) -> eyre::Result<( .wrap_err(eyre!("Failed to open file for mapping")) .with_section(|| format!("{:?}", output_file).header("File was"))?; let mut file = file.into_std().await; - cfg_eprintln!(Verbose; cfg, "Opened file for prealloc-write"); + cfg_eprintln!(Verbose; cfg, "Opened file for prealloc-write"); tokio::task::spawn_blocking(move || { serial::write_sync_map(&mut file, graph.as_ref()) }).await.wrap_err(eyre!("Prealloc panicked while dumping")) @@ -127,13 +127,31 @@ async fn normal(cfg: config::Config) -> eyre::Result<()> }; let cfg = cfg.make_global(); - let graph = tokio::task::spawn_blocking(move || { + let (treemap, graph) = tokio::task::spawn_blocking(move || { cfg_println!(Verbose; cfg, "Computing hierarchy..."); + + // Create treemap + #[cfg(feature="treemap")] + let treemap = { + //Check config for if the treemap flag is present. If it is, get the width and height from there too. + if let Some(&(x,y)) = cfg.inspection.treemap.as_ref() { + Some(info::treemap(cfg, &graph, ( + x as f64, + y as f64, + ))) + } else { + None + } + }; + #[cfg(not(feature="treemap"))] + let treemap = Option::::None; + + // Create recursive graph let mut graph = graph.into_hierarchical(); cfg_println!(Verbose; cfg, "Computing sizes..."); graph.compute_recursive_sizes(); - graph + (treemap, graph) }).await.expect("Failed to compute hierarchy from graph"); //#[cfg(debug_assertions)] cfg_eprintln!(Verbose; cfg, "{:?}", graph); @@ -155,6 +173,12 @@ async fn normal(cfg: config::Config) -> eyre::Result<()> } }; + #[cfg(feature="treemap")] + if let Some(treemap) = treemap { + //TODO: Print treemap + todo!("{:?}",treemap); + } + info::print_basic_max_info(&cfg, &graph); #[cfg(feature="inspect")]