diff --git a/analyze/analyses/paths.rs b/analyze/analyses/paths.rs deleted file mode 100644 index 1e35b425..00000000 --- a/analyze/analyses/paths.rs +++ /dev/null @@ -1,311 +0,0 @@ -use std::collections::BTreeSet; -use std::io; - -use csv; -use regex; - -use formats::json; -use formats::table::{Align, Table}; -use twiggy_ir as ir; -use twiggy_opt as opt; -use twiggy_traits as traits; - -struct Paths { - items: Vec, - opts: opt::Paths, -} - -impl traits::Emit for Paths { - #[cfg(feature = "emit_text")] - fn emit_text(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { - fn recursive_callers( - items: &ir::Items, - seen: &mut BTreeSet, - table: &mut Table, - depth: u32, - mut paths: &mut u32, - opts: &opt::Paths, - id: ir::Id, - ) { - if opts.max_paths() == *paths || depth > opts.max_depth() { - return; - } - - if seen.contains(&id) || items.meta_root() == id { - return; - } - - let item = &items[id]; - - let mut label = String::with_capacity(depth as usize * 4 + item.name().len()); - for _ in 1..depth { - label.push_str(" "); - } - if depth > 0 { - if opts.descending() { - label.push_str(" ↳ "); - } else { - label.push_str(" ⬑ "); - } - } - label.push_str(item.name()); - - table.add_row(vec![ - if depth == 0 { - item.size().to_string() - } else { - "".to_string() - }, - if depth == 0 { - let size_percent = (f64::from(item.size())) / (f64::from(items.size())) * 100.0; - format!("{:.2}%", size_percent) - } else { - "".to_string() - }, - label, - ]); - - seen.insert(id); - - if opts.descending() { - for callee in items.neighbors(id) { - *paths += 1; - recursive_callers(items, seen, table, depth + 1, &mut paths, &opts, callee); - } - } else { - for (i, caller) in items.predecessors(id).enumerate() { - if i > 0 { - *paths += 1; - } - recursive_callers(items, seen, table, depth + 1, &mut paths, &opts, caller); - } - } - - seen.remove(&id); - } - - let mut table = Table::with_header(vec![ - (Align::Right, "Shallow Bytes".to_string()), - (Align::Right, "Shallow %".to_string()), - (Align::Left, "Retaining Paths".to_string()), - ]); - - let opts = &self.opts; - - for id in &self.items { - let mut paths = 0 as u32; - let mut seen = BTreeSet::new(); - recursive_callers(items, &mut seen, &mut table, 0, &mut paths, &opts, *id); - } - - write!(dest, "{}", table)?; - Ok(()) - } - - #[cfg(feature = "emit_json")] - fn emit_json(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { - fn recursive_callers( - items: &ir::Items, - seen: &mut BTreeSet, - obj: &mut json::Object, - depth: u32, - mut paths: &mut u32, - opts: &opt::Paths, - id: ir::Id, - ) -> io::Result<()> { - let item = &items[id]; - - obj.field("name", item.name())?; - - let size = item.size(); - let size_percent = f64::from(size) / f64::from(items.size()) * 100.0; - obj.field("shallow_size", size)?; - obj.field("shallow_size_percent", size_percent)?; - - let mut callers = obj.array("callers")?; - - let depth = depth + 1; - if depth <= opts.max_depth() { - seen.insert(id); - for (i, caller) in items.predecessors(id).enumerate() { - if seen.contains(&caller) || items.meta_root() == caller { - continue; - } - - if i > 0 { - *paths += 1; - } - if opts.max_paths() == *paths { - break; - } - - let mut obj = callers.object()?; - recursive_callers(items, seen, &mut obj, depth, &mut paths, &opts, caller)?; - } - seen.remove(&id); - } - - Ok(()) - } - - let mut arr = json::array(dest)?; - for id in &self.items { - let mut paths = 0 as u32; - let mut seen = BTreeSet::new(); - let mut obj = arr.object()?; - recursive_callers(items, &mut seen, &mut obj, 0, &mut paths, &self.opts, *id)?; - } - - Ok(()) - } - - #[cfg(feature = "emit_csv")] - fn emit_csv(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { - let mut wtr = csv::Writer::from_writer(dest); - fn recursive_callers( - items: &ir::Items, - seen: &mut BTreeSet, - depth: u32, - mut paths: &mut u32, - opts: &opt::Paths, - id: ir::Id, - wtr: &mut csv::Writer<&mut io::Write>, - ) -> io::Result<()> { - #[derive(Serialize, Debug)] - #[serde(rename_all = "PascalCase")] - struct CsvRecord { - name: String, - shallow_size: u32, - shallow_size_percent: f64, - path: Option, - } - - let item = &items[id]; - let size = item.size(); - let size_percent = f64::from(size) / f64::from(items.size()) * 100.0; - let mut callers = items - .predecessors(id) - .into_iter() - .map(|i| items[i].name()) - .collect::>(); - callers.push(item.name()); - let path = callers.join(" -> "); - - let record = CsvRecord { - name: item.name().to_owned(), - shallow_size: size, - shallow_size_percent: size_percent, - path: Some(path), - }; - - wtr.serialize(record)?; - wtr.flush()?; - - let depth = depth + 1; - if depth <= opts.max_depth() { - seen.insert(id); - for (i, caller) in items.predecessors(id).enumerate() { - if seen.contains(&caller) || items.meta_root() == caller { - continue; - } - - if i > 0 { - *paths += 1; - } - if opts.max_paths() == *paths { - break; - } - - recursive_callers(items, seen, depth, &mut paths, &opts, caller, wtr)?; - } - seen.remove(&id); - } - - Ok(()) - } - - for id in &self.items { - let mut paths = 0 as u32; - let mut seen = BTreeSet::new(); - recursive_callers(items, &mut seen, 0, &mut paths, &self.opts, *id, &mut wtr)?; - } - - Ok(()) - } -} - -/// Find all retaining paths for the given items. -pub fn paths(items: &mut ir::Items, opts: &opt::Paths) -> Result, traits::Error> { - // The predecessor tree only needs to be computed if we are ascending - // through the retaining paths. - if !opts.descending() { - items.compute_predecessors(); - } - - // Initialize the collection of Id values whose retaining paths we will emit. - let opts = opts.clone(); - let items = get_items(&items, &opts)?; - let paths = Paths { items, opts }; - - Ok(Box::new(paths) as Box) -} - -/// This helper function is used to collect ir::Id values for the `items` member -/// of the `Paths` object, based on the given options. -pub fn get_items(items: &ir::Items, opts: &opt::Paths) -> Result, traits::Error> { - // Collect Id's if no arguments are given and we are ascending the retaining paths. - let get_functions_default = || -> Vec { - let mut sorted_items = items - .iter() - .filter(|item| item.id() != items.meta_root()) - .collect::>(); - sorted_items.sort_by(|a, b| b.size().cmp(&a.size())); - sorted_items.iter().map(|item| item.id()).collect() - }; - - // Collect Id's if no arguments are given and we are descending the retaining paths. - let get_functions_default_desc = || -> Vec { - let mut roots = items - .neighbors(items.meta_root()) - .map(|id| &items[id]) - .collect::>(); - roots.sort_by(|a, b| b.size().cmp(&a.size())); - roots.into_iter().map(|item| item.id()).collect() - }; - - // Collect Id's if arguments were given that should be used as regular expressions. - let get_regexp_matches = || -> Result, traits::Error> { - let regexps = regex::RegexSet::new(opts.functions())?; - let matches = items - .iter() - .filter(|item| regexps.is_match(&item.name())) - .map(|item| item.id()) - .collect(); - Ok(matches) - }; - - // Collect Id's if arguments were given that should be used as exact names. - let get_exact_matches = || -> Vec { - opts.functions() - .iter() - .filter_map(|s| items.get_item_by_name(s)) - .map(|item| item.id()) - .collect() - }; - - // Collect the starting positions based on the relevant options given. - // If arguments were given, search for matches depending on whether or - // not these should be treated as regular expressions. Otherwise, collect - // the starting positions based on the direction we will be traversing. - let args_given = !opts.functions().is_empty(); - let using_regexps = opts.using_regexps(); - let descending = opts.descending(); - let res = match (args_given, using_regexps, descending) { - (true, true, _) => get_regexp_matches()?, - (true, false, _) => get_exact_matches(), - (false, _, true) => get_functions_default_desc(), - (false, _, false) => get_functions_default(), - }; - - Ok(res) -} diff --git a/analyze/analyses/paths/mod.rs b/analyze/analyses/paths/mod.rs new file mode 100644 index 00000000..19bbabf2 --- /dev/null +++ b/analyze/analyses/paths/mod.rs @@ -0,0 +1,147 @@ +use std::collections::BTreeSet; + +use regex; + +use twiggy_ir as ir; +use twiggy_opt as opt; +use twiggy_traits as traits; + +mod paths_emit; +mod paths_entry; + +use self::paths_entry::PathsEntry; + +#[derive(Debug)] +struct Paths { + opts: opt::Paths, + entries: Vec, +} + +/// Find all retaining paths for the given items. +pub fn paths(items: &mut ir::Items, opts: &opt::Paths) -> Result, traits::Error> { + // The predecessor tree only needs to be computed if we are ascending + // through the retaining paths. + if !opts.descending() { + items.compute_predecessors(); + } + + // Initialize the collection of Id values whose retaining paths we will emit. + let opts = opts.clone(); + let entries = get_starting_positions(&items, &opts)? + .iter() + .map(|id| create_entry(*id, &items, &opts, &mut BTreeSet::new())) + .collect(); + + let paths = Paths { opts, entries }; + + Ok(Box::new(paths) as Box) +} + +/// This helper function is used to collect the `ir::Id` values for the top-most +/// path entries for the `Paths` object, based on the given options. +fn get_starting_positions( + items: &ir::Items, + opts: &opt::Paths, +) -> Result, traits::Error> { + // Collect Id's if no arguments are given and we are ascending the retaining paths. + let get_functions_default = || -> Vec { + let mut sorted_items = items + .iter() + .filter(|item| item.id() != items.meta_root()) + .collect::>(); + sorted_items.sort_by(|a, b| b.size().cmp(&a.size())); + sorted_items.iter().map(|item| item.id()).collect() + }; + + // Collect Id's if no arguments are given and we are descending the retaining paths. + let get_functions_default_desc = || -> Vec { + let mut roots = items + .neighbors(items.meta_root()) + .map(|id| &items[id]) + .collect::>(); + roots.sort_by(|a, b| b.size().cmp(&a.size())); + roots.into_iter().map(|item| item.id()).collect() + }; + + // Collect Id's if arguments were given that should be used as regular expressions. + let get_regexp_matches = || -> Result, traits::Error> { + let regexps = regex::RegexSet::new(opts.functions())?; + let matches = items + .iter() + .filter(|item| regexps.is_match(&item.name())) + .map(|item| item.id()) + .collect(); + Ok(matches) + }; + + // Collect Id's if arguments were given that should be used as exact names. + let get_exact_matches = || -> Vec { + opts.functions() + .iter() + .filter_map(|s| items.get_item_by_name(s)) + .map(|item| item.id()) + .collect() + }; + + // Collect the starting positions based on the relevant options given. + // If arguments were given, search for matches depending on whether or + // not these should be treated as regular expressions. Otherwise, collect + // the starting positions based on the direction we will be traversing. + let args_given = !opts.functions().is_empty(); + let using_regexps = opts.using_regexps(); + let descending = opts.descending(); + let res = match (args_given, using_regexps, descending) { + (true, true, _) => get_regexp_matches()?, + (true, false, _) => get_exact_matches(), + (false, _, true) => get_functions_default_desc(), + (false, _, false) => get_functions_default(), + }; + + Ok(res) +} + +/// Create a `PathsEntry` object for the given item. +fn create_entry( + id: ir::Id, + items: &ir::Items, + opts: &opt::Paths, + seen: &mut BTreeSet, +) -> PathsEntry { + // Determine the item's name and size. + let item = &items[id]; + let name = item.name().to_string(); + let size = item.size(); + + // Collect the `ir::Id` values of this entry's children, depending on + // whether we are ascending or descending the IR-tree. + let children_ids: Vec = if opts.descending() { + items + .neighbors(id) + .map(|id| id as ir::Id) + .filter(|id| !seen.contains(id)) + .filter(|&id| id != items.meta_root()) + .collect() + } else { + items + .predecessors(id) + .map(|id| id as ir::Id) + .filter(|id| !seen.contains(id)) + .filter(|&id| id != items.meta_root()) + .collect() + }; + + // Temporarily add the current item to the set of discovered nodes, and + // create an entry for each child. Collect these into a `children` vector. + seen.insert(id); + let children = children_ids + .into_iter() + .map(|id| create_entry(id, &items, &opts, seen)) + .collect(); + seen.remove(&id); + + PathsEntry { + name, + size, + children, + } +} diff --git a/analyze/analyses/paths/paths_emit.rs b/analyze/analyses/paths/paths_emit.rs new file mode 100644 index 00000000..1b21f1b1 --- /dev/null +++ b/analyze/analyses/paths/paths_emit.rs @@ -0,0 +1,300 @@ +use std::io; + +use csv; + +use formats::json; +use formats::table::{Align, Table}; +use twiggy_ir as ir; +use twiggy_traits as traits; + +use analyses::paths::Paths; + +impl traits::Emit for Paths { + #[cfg(feature = "emit_text")] + fn emit_text(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { + use self::emit_text_helpers::{process_entry, TableRow}; + + // Flat map each entry and its children into a sequence of table rows. + // Convert these `TableRow` objects into vectors of strings, and add + // each of these to the table before writing the table to `dest`. + let table = self + .entries + .iter() + .flat_map(|entry| { + process_entry(entry, 0, self.opts.max_paths() as usize, &items, &self.opts) + }).map( + |TableRow { + size, + size_percent, + name, + }| { + vec![ + size.map(|size| size.to_string()).unwrap_or("".to_string()), + size_percent + .map(|size_percent| format!("{:.2}%", size_percent)) + .unwrap_or("".to_string()), + name, + ] + }, + ).fold( + Table::with_header(vec![ + (Align::Right, "Shallow Bytes".to_string()), + (Align::Right, "Shallow %".to_string()), + (Align::Left, "Retaining Paths".to_string()), + ]), + |mut table, row| { + table.add_row(row); + table + }, + ); + + write!(dest, "{}", table)?; + Ok(()) + } + + #[cfg(feature = "emit_json")] + fn emit_json(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { + use self::emit_json_helpers::process_entry; + + // Initialize a JSON array. For each path entry, add a object to the + // array, and add that entry's information to the new JSON object. + let mut arr = json::array(dest)?; + for entry in &self.entries { + let mut obj = arr.object()?; + process_entry( + entry, + &mut obj, + 0, + self.opts.max_paths() as usize, + items, + &self.opts, + )?; + } + + Ok(()) + } + + #[cfg(feature = "emit_csv")] + fn emit_csv(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { + use self::emit_csv_helpers::process_entry; + + // First, initialize a CSV writer. Then, flat map each entry and its + // children into a sequence of `CsvRecord` objects. Send each record + // to the CSV writer to be serialized. + let mut wtr = csv::Writer::from_writer(dest); + for record in self.entries.iter().flat_map(|entry| { + process_entry(entry, 0, self.opts.max_paths() as usize, &items, &self.opts) + }) { + wtr.serialize(record)?; + wtr.flush()?; + } + + Ok(()) + } +} + +/// This module contains helper functions and structs used by the `emit_text` +/// method in Path's implementation of the `traits::Emit` trait. +mod emit_text_helpers { + use std::iter; + + use twiggy_ir::Items; + use twiggy_opt::Paths; + + use analyses::paths::paths_entry::PathsEntry; + + /// This structure represents a row in the emitted text table. Size, and size + /// percentage are only shown for the top-most rows. + pub(super) struct TableRow { + pub size: Option, + pub size_percent: Option, + pub name: String, + } + + /// Process a given path entry, and return an iterator of table rows, + /// representing its related call paths, according to the given options. + pub(super) fn process_entry<'a>( + entry: &'a PathsEntry, + depth: u32, + paths: usize, + items: &'a Items, + opts: &'a Paths, + ) -> Box + 'a> { + // Get the row's name and size columns using the current depth. + let name = get_indented_name(&entry.name, depth, opts.descending()); + let (size, size_percent) = if depth == 0 { + ( + Some(entry.size), + Some(f64::from(entry.size) / f64::from(items.size()) * 100.0), + ) + } else { + (None, None) + }; + + // Create an iterator containing the current entry's table row. + let row_iter = iter::once(TableRow { + size, + size_percent, + name, + }); + + if depth < opts.max_depth() { + // Process each child entry, and chain together the resulting iterators. + let children_iter = entry + .children + .iter() + .take(paths) + .flat_map(move |child_entry| { + process_entry(child_entry, depth + 1, paths, &items, &opts) + }); + Box::new(row_iter.chain(children_iter)) + } else if depth == opts.max_depth() { + // TODO: Create a summary row, and chain it to the row iterator. + Box::new(row_iter) + } else { + // If we are beyond the maximum depth, return an empty iterator. + Box::new(iter::empty()) + } + } + + /// Given the name of an item, its depth, and the traversal direction, + /// return an indented version of the name for its corresponding table row. + fn get_indented_name(name: &str, depth: u32, descending: bool) -> String { + (1..depth) + .map(|_| " ") + .chain(iter::once(if depth > 0 && descending { + " ↳ " + } else if depth > 0 { + " ⬑ " + } else { + "" + })).chain(iter::once(name)) + .fold( + String::with_capacity(depth as usize * 4 + name.len()), + |mut res, s| { + res.push_str(s); + res + }, + ) + } +} + +/// This module contains helper functions and structs used by the `emit_json` +/// method in Path's implementation of the `traits::Emit` trait. +mod emit_json_helpers { + use std::io; + + use formats::json::Object; + use twiggy_ir::Items; + use twiggy_opt::Paths; + + use analyses::paths::paths_entry::PathsEntry; + + // Process a paths entry, by adding its name and size to the given JSON object. + pub(super) fn process_entry( + entry: &PathsEntry, + obj: &mut Object, + depth: u32, + paths: usize, + items: &Items, + opts: &Paths, + ) -> io::Result<()> { + let PathsEntry { + name, + size, + children, + } = entry; + obj.field("name", name.as_str())?; + obj.field("shallow_size", *size)?; + let size_percent = f64::from(*size) / f64::from(items.size()) * 100.0; + obj.field("shallow_size_percent", size_percent)?; + + let mut callers = obj.array("callers")?; + if depth < opts.max_depth() { + for child in children.iter().take(paths) { + let mut obj = callers.object()?; + process_entry(child, &mut obj, depth + 1, paths, items, &opts)?; + } + } + + Ok(()) + } +} + +/// This module contains helper functions and structs used by the `emit_csv` +/// method in Path's implementation of the `traits::Emit` trait. +mod emit_csv_helpers { + use std::iter; + + use twiggy_ir::Items; + use twiggy_opt::Paths; + + use analyses::paths::paths_entry::PathsEntry; + + /// This structure represents a row in the CSV output. + #[derive(Serialize, Debug)] + #[serde(rename_all = "PascalCase")] + pub(super) struct CsvRecord { + pub name: String, + pub shallow_size: u32, + pub shallow_size_percent: f64, + pub path: Option, + } + + // Process a given entry and its children, returning an iterator of CSV records. + pub(super) fn process_entry<'a>( + entry: &'a PathsEntry, + depth: u32, + paths: usize, + items: &'a Items, + opts: &'a Paths, + ) -> Box + 'a> { + let name = entry.name.clone(); + let shallow_size = entry.size; + let shallow_size_percent = f64::from(entry.size) / f64::from(items.size()) * 100.0; + let path = get_path(entry); + + // Create an iterator containing the current entry's CSV record. + let record_iter = iter::once(CsvRecord { + name, + shallow_size, + shallow_size_percent, + path, + }); + + if depth < opts.max_depth() { + // Process each child entry, and chain together the resulting iterators. + let children_iter = entry + .children + .iter() + .take(paths) + .flat_map(move |child_entry| { + process_entry(child_entry, depth + 1, paths, &items, &opts) + }); + Box::new(record_iter.chain(children_iter)) + } else if depth == opts.max_depth() { + // Create a summary row, and chain it to the row iterator. + Box::new(record_iter) + } else { + // If we are beyond the maximum depth, return an empty iterator. + Box::new(iter::empty()) + } + } + + // Given a path entry, return the value for its corresponding CsvRecord's `path` field. + fn get_path(entry: &PathsEntry) -> Option { + if entry.children.is_empty() { + None + } else { + Some( + entry + .children + .iter() + .map(|child| child.name.as_str()) + .chain(iter::once(entry.name.as_str())) + .collect::>() + .join(" -> "), + ) + } + } +} diff --git a/analyze/analyses/paths/paths_entry.rs b/analyze/analyses/paths/paths_entry.rs new file mode 100644 index 00000000..5c861586 --- /dev/null +++ b/analyze/analyses/paths/paths_entry.rs @@ -0,0 +1,26 @@ +use std::cmp; + +#[derive(Debug, PartialEq, Eq)] +pub(super) struct PathsEntry { + pub name: String, + pub size: u32, + pub children: Vec, +} + +impl PathsEntry { + pub fn _count(&self) -> u32 { + 1 + self.children.iter().map(|c| c._count()).sum::() + } +} + +impl PartialOrd for PathsEntry { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +impl Ord for PathsEntry { + fn cmp(&self, rhs: &Self) -> cmp::Ordering { + rhs.size.cmp(&self.size).then(self.name.cmp(&rhs.name)) + } +} diff --git a/twiggy/tests/all/expectations/paths_error_test_no_max_paths b/twiggy/tests/all/expectations/paths_error_test_no_max_paths new file mode 100644 index 00000000..a7b4f1ed --- /dev/null +++ b/twiggy/tests/all/expectations/paths_error_test_no_max_paths @@ -0,0 +1,10 @@ + Shallow Bytes │ Shallow % │ Retaining Paths +───────────────┼───────────┼──────────────────────────────────────────────────────────────────────────────────────── + 339 ┊ 0.75% ┊ std::io::error::Error::new::h8c006d5367bc92ed + ┊ ┊ ⬑ func[15] + ┊ ┊ ⬑ std::io::impls::::write::h5d7e5ba58acd05fd + ┊ ┊ ⬑ func[33] + ┊ ┊ ⬑ elem[0] + ┊ ┊ ⬑ std::panicking::LOCAL_STDERR::__getit::h7827294b3348067a + ┊ ┊ ⬑ func[37] + ┊ ┊ ⬑ elem[0] diff --git a/twiggy/tests/all/expectations/paths_error_test_no_max_paths_csv b/twiggy/tests/all/expectations/paths_error_test_no_max_paths_csv new file mode 100644 index 00000000..792d28dd --- /dev/null +++ b/twiggy/tests/all/expectations/paths_error_test_no_max_paths_csv @@ -0,0 +1,9 @@ +Name,ShallowSize,ShallowSizePercent,Path +std::io::error::Error::new::h8c006d5367bc92ed,339,0.7494694021931375,func[15] -> std::io::error::Error::new::h8c006d5367bc92ed +func[15],1,0.0022108241952599928,std::io::impls::::write::h5d7e5ba58acd05fd -> std::panicking::LOCAL_STDERR::__getit::h7827294b3348067a -> func[15] +std::io::impls::::write::h5d7e5ba58acd05fd,311,0.6875663247258578,func[33] -> std::io::impls::::write::h5d7e5ba58acd05fd +func[33],1,0.0022108241952599928,elem[0] -> func[33] +elem[0],59,0.13043862752033958, +std::panicking::LOCAL_STDERR::__getit::h7827294b3348067a,16,0.035373187124159884,func[37] -> std::panicking::LOCAL_STDERR::__getit::h7827294b3348067a +func[37],1,0.0022108241952599928,elem[0] -> func[37] +elem[0],59,0.13043862752033958, diff --git a/twiggy/tests/all/expectations/paths_error_test_no_max_paths_json b/twiggy/tests/all/expectations/paths_error_test_no_max_paths_json new file mode 100644 index 00000000..88a42a56 --- /dev/null +++ b/twiggy/tests/all/expectations/paths_error_test_no_max_paths_json @@ -0,0 +1 @@ +[{"name":"std::io::error::Error::new::h8c006d5367bc92ed","shallow_size":339,"shallow_size_percent":0.7494694021931375,"callers":[{"name":"func[15]","shallow_size":1,"shallow_size_percent":0.0022108241952599928,"callers":[{"name":"std::io::impls::::write::h5d7e5ba58acd05fd","shallow_size":311,"shallow_size_percent":0.6875663247258578,"callers":[{"name":"func[33]","shallow_size":1,"shallow_size_percent":0.0022108241952599928,"callers":[{"name":"elem[0]","shallow_size":59,"shallow_size_percent":0.13043862752033958,"callers":[]}]}]},{"name":"std::panicking::LOCAL_STDERR::__getit::h7827294b3348067a","shallow_size":16,"shallow_size_percent":0.035373187124159884,"callers":[{"name":"func[37]","shallow_size":1,"shallow_size_percent":0.0022108241952599928,"callers":[{"name":"elem[0]","shallow_size":59,"shallow_size_percent":0.13043862752033958,"callers":[]}]}]}]}]}] diff --git a/twiggy/tests/all/expectations/paths_error_test_one_path b/twiggy/tests/all/expectations/paths_error_test_one_path new file mode 100644 index 00000000..26a4a243 --- /dev/null +++ b/twiggy/tests/all/expectations/paths_error_test_one_path @@ -0,0 +1,7 @@ + Shallow Bytes │ Shallow % │ Retaining Paths +───────────────┼───────────┼──────────────────────────────────────────────────────────────────────────────────────── + 339 ┊ 0.75% ┊ std::io::error::Error::new::h8c006d5367bc92ed + ┊ ┊ ⬑ func[15] + ┊ ┊ ⬑ std::io::impls::::write::h5d7e5ba58acd05fd + ┊ ┊ ⬑ func[33] + ┊ ┊ ⬑ elem[0] diff --git a/twiggy/tests/all/expectations/paths_error_test_one_path_csv b/twiggy/tests/all/expectations/paths_error_test_one_path_csv new file mode 100644 index 00000000..2e538e10 --- /dev/null +++ b/twiggy/tests/all/expectations/paths_error_test_one_path_csv @@ -0,0 +1,6 @@ +Name,ShallowSize,ShallowSizePercent,Path +std::io::error::Error::new::h8c006d5367bc92ed,339,0.7494694021931375,func[15] -> std::io::error::Error::new::h8c006d5367bc92ed +func[15],1,0.0022108241952599928,std::io::impls::::write::h5d7e5ba58acd05fd -> std::panicking::LOCAL_STDERR::__getit::h7827294b3348067a -> func[15] +std::io::impls::::write::h5d7e5ba58acd05fd,311,0.6875663247258578,func[33] -> std::io::impls::::write::h5d7e5ba58acd05fd +func[33],1,0.0022108241952599928,elem[0] -> func[33] +elem[0],59,0.13043862752033958, diff --git a/twiggy/tests/all/expectations/paths_error_test_one_path_json b/twiggy/tests/all/expectations/paths_error_test_one_path_json new file mode 100644 index 00000000..15a34e4a --- /dev/null +++ b/twiggy/tests/all/expectations/paths_error_test_one_path_json @@ -0,0 +1 @@ +[{"name":"std::io::error::Error::new::h8c006d5367bc92ed","shallow_size":339,"shallow_size_percent":0.7494694021931375,"callers":[{"name":"func[15]","shallow_size":1,"shallow_size_percent":0.0022108241952599928,"callers":[{"name":"std::io::impls::::write::h5d7e5ba58acd05fd","shallow_size":311,"shallow_size_percent":0.6875663247258578,"callers":[{"name":"func[33]","shallow_size":1,"shallow_size_percent":0.0022108241952599928,"callers":[{"name":"elem[0]","shallow_size":59,"shallow_size_percent":0.13043862752033958,"callers":[]}]}]}]}]}] diff --git a/twiggy/tests/all/expectations/paths_test_called_once_csv b/twiggy/tests/all/expectations/paths_test_called_once_csv index 309e100a..5fe871ba 100644 --- a/twiggy/tests/all/expectations/paths_test_called_once_csv +++ b/twiggy/tests/all/expectations/paths_test_called_once_csv @@ -3,4 +3,4 @@ calledOnce,5,3.4722222222222223,func[0] -> calledOnce func[0],1,0.6944444444444444,woof -> func[0] woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, diff --git a/twiggy/tests/all/expectations/paths_test_called_twice_csv b/twiggy/tests/all/expectations/paths_test_called_twice_csv index 838d8724..48c450d3 100644 --- a/twiggy/tests/all/expectations/paths_test_called_twice_csv +++ b/twiggy/tests/all/expectations/paths_test_called_twice_csv @@ -3,10 +3,10 @@ calledTwice,5,3.4722222222222223,func[1] -> calledTwice func[1],1,0.6944444444444444,bark -> woof -> func[1] bark,5,3.4722222222222223,func[2] -> bark func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, diff --git a/twiggy/tests/all/expectations/paths_test_default_output_csv b/twiggy/tests/all/expectations/paths_test_default_output_csv index 7a2ace3f..bae6f7e4 100644 --- a/twiggy/tests/all/expectations/paths_test_default_output_csv +++ b/twiggy/tests/all/expectations/paths_test_default_output_csv @@ -1,80 +1,80 @@ Name,ShallowSize,ShallowSizePercent,Path -"""function names"" subsection",44,30.555555555555557," -> ""function names"" subsection" +"""function names"" subsection",44,30.555555555555557, woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" -"export ""bark""",7,4.861111111111112," -> export ""bark""" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, +"export ""awoo""",7,4.861111111111112, +"export ""bark""",7,4.861111111111112, +"export ""woof""",7,4.861111111111112, calledOnce,5,3.4722222222222223,func[0] -> calledOnce func[0],1,0.6944444444444444,woof -> func[0] woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, calledTwice,5,3.4722222222222223,func[1] -> calledTwice func[1],1,0.6944444444444444,bark -> woof -> func[1] bark,5,3.4722222222222223,func[2] -> bark func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, bark,5,3.4722222222222223,func[2] -> bark func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, type[0],4,2.7777777777777777,func[0] -> func[1] -> func[2] -> func[3] -> func[4] -> type[0] func[0],1,0.6944444444444444,woof -> func[0] woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, func[1],1,0.6944444444444444,bark -> woof -> func[1] bark,5,3.4722222222222223,func[2] -> bark func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, func[0],1,0.6944444444444444,woof -> func[0] woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, func[1],1,0.6944444444444444,bark -> woof -> func[1] bark,5,3.4722222222222223,func[2] -> bark func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, woof,8,5.555555555555555,func[3] -> woof func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, func[2],1,0.6944444444444444,"export ""bark"" -> awoo -> func[2]" -"export ""bark""",7,4.861111111111112," -> export ""bark""" +"export ""bark""",7,4.861111111111112, awoo,5,3.4722222222222223,func[4] -> awoo func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, func[3],1,0.6944444444444444,"export ""woof"" -> func[3]" -"export ""woof""",7,4.861111111111112," -> export ""woof""" +"export ""woof""",7,4.861111111111112, func[4],1,0.6944444444444444,"export ""awoo"" -> func[4]" -"export ""awoo""",7,4.861111111111112," -> export ""awoo""" +"export ""awoo""",7,4.861111111111112, diff --git a/twiggy/tests/all/expectations/paths_wee_alloc_csv b/twiggy/tests/all/expectations/paths_wee_alloc_csv index 2fdb4798..17d44b3d 100644 --- a/twiggy/tests/all/expectations/paths_wee_alloc_csv +++ b/twiggy/tests/all/expectations/paths_wee_alloc_csv @@ -5,13 +5,13 @@ wee_alloc::alloc_with_refill::hb32c1bbce9ebda8e,152,5.395811146609868,func[2] -> func[2],1,0.03549875754348598, as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6 -> hello -> func[2] as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6,136,4.827831025914093,func[5] -> as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6 func[5],1,0.03549875754348598,elem[0] -> func[5] -elem[0],12,0.42598509052183176,elem[0] +elem[0],12,0.42598509052183176, hello,164,5.8217962371317,func[8] -> hello func[8],1,0.03549875754348598,"export ""hello"" -> func[8]" -"export ""hello""",8,0.2839900603478878," -> export ""hello""" +"export ""hello""",8,0.2839900603478878, hello,164,5.8217962371317,func[8] -> hello func[8],1,0.03549875754348598,"export ""hello"" -> func[8]" -"export ""hello""",8,0.2839900603478878," -> export ""hello""" +"export ""hello""",8,0.2839900603478878, goodbye,44,1.5619453319133831,func[9] -> goodbye func[9],1,0.03549875754348598,"export ""goodbye"" -> func[9]" -"export ""goodbye""",10,0.3549875754348598," -> export ""goodbye""" +"export ""goodbye""",10,0.3549875754348598, diff --git a/twiggy/tests/all/paths_tests.rs b/twiggy/tests/all/paths_tests.rs index 0e3f4e0e..ee071278 100644 --- a/twiggy/tests/all/paths_tests.rs +++ b/twiggy/tests/all/paths_tests.rs @@ -187,10 +187,65 @@ test!( "--regex" ); - test!( issue_16, "paths", "./fixtures/mappings.wasm", "compute_column_spans" ); + +test!( + paths_error_test_no_max_paths, + "paths", + "./fixtures/mappings.wasm", + "std::io::error::Error::new::h8c006d5367bc92ed" +); + +test!( + paths_error_test_no_max_paths_csv, + "paths", + "-f", + "csv", + "./fixtures/mappings.wasm", + "std::io::error::Error::new::h8c006d5367bc92ed" +); + +test!( + paths_error_test_no_max_paths_json, + "paths", + "-f", + "json", + "./fixtures/mappings.wasm", + "std::io::error::Error::new::h8c006d5367bc92ed" +); + +test!( + paths_error_test_one_path, + "paths", + "-r", + "1", + "./fixtures/mappings.wasm", + "std::io::error::Error::new::h8c006d5367bc92ed" +); + +test!( + paths_error_test_one_path_csv, + "paths", + "-f", + "csv", + "-r", + "1", + "./fixtures/mappings.wasm", + "std::io::error::Error::new::h8c006d5367bc92ed" +); + +test!( + paths_error_test_one_path_json, + "paths", + "-f", + "json", + "-r", + "1", + "./fixtures/mappings.wasm", + "std::io::error::Error::new::h8c006d5367bc92ed" +);