diff --git a/analyze/analyze.rs b/analyze/analyze.rs index b28a1082..bdebef4d 100644 --- a/analyze/analyze.rs +++ b/analyze/analyze.rs @@ -126,35 +126,120 @@ struct Top { impl traits::Emit for Top { #[cfg(feature = "emit_text")] fn emit_text(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { - let sort_label = if self.opts.retained() { - "Retained" - } else { - "Shallow" + // A struct used to represent a row in the table that will be emitted. + struct TableRow { + size: u32, + size_percent: f64, + name: String, + }; + + // Helper function used to process an item, and return a struct + // representing a row containing its size and name. + fn process_item(id: ir::Id, items: &ir::Items, retained: bool) -> TableRow { + let item = &items[id]; + let size = if retained { + items.retained_size(id) + } else { + item.size() + }; + let size_percent = (f64::from(size)) / (f64::from(items.size())) * 100.0; + let name = item.name().to_string(); + TableRow { + size, + size_percent, + name, + } }; + // Helper function used to summnarize a sequence of table rows. This is + // used to generate the remaining summary and total rows. Returns a tuple + // containing the total size, total size percentage, and number of items. + fn summarize_rows(rows: impl Iterator) -> (u32, f64, u32) { + rows.fold( + (0, 0.0, 0), + |(total_size, total_percent, remaining_count), + TableRow { + size, + size_percent, + name: _, + }| { + ( + total_size + size, + total_percent + size_percent, + remaining_count + 1, + ) + }, + ) + } + + // Access the options that are relevant to emitting the correct output. + let max_items = self.opts.max_items() as usize; + let retained = self.opts.retained(); + let sort_label = if retained { "Retained" } else { "Shallow" }; + + // Initialize a new table. let mut table = Table::with_header(vec![ (Align::Right, format!("{} Bytes", sort_label)), (Align::Right, format!("{} %", sort_label)), (Align::Left, "Item".to_string()), ]); - for &id in &self.items { - let item = &items[id]; + // Process the number of items specified, and add them to the table. + self.items + .iter() + .take(max_items) + .map(|&id| process_item(id, items, retained)) + .for_each( + |TableRow { + size, + size_percent, + name, + }| { + table.add_row(vec![ + size.to_string(), + format!("{:.2}%", size_percent), + name, + ]) + }, + ); - let size = if self.opts.retained() { - items.retained_size(id) + // Find the summary statistics by processing the remaining items. + let remaining_rows = self + .items + .iter() + .skip(max_items) + .map(|&id| process_item(id, items, retained)); + let (rem_size, rem_size_percent, rem_count) = summarize_rows(remaining_rows); + + // If there were items remaining, add a summary row to the table. + if rem_count > 0 { + let rem_name_col = format!("... and {} more.", rem_count); + let (rem_size_col, rem_size_percent_col) = if retained { + ("...".to_string(), "...".to_string()) } else { - item.size() + (rem_size.to_string(), format!("{:.2}%", rem_size_percent)) }; - - let size_percent = (f64::from(size)) / (f64::from(items.size())) * 100.0; - table.add_row(vec![ - size.to_string(), - format!("{:.2}%", size_percent), - item.name().to_string(), - ]); + table.add_row(vec![rem_size_col, rem_size_percent_col, rem_name_col]); } + // Add a row containing the totals to the table. + let all_rows = self + .items + .iter() + .map(|&id| process_item(id, items, retained)); + let (total_size, total_size_percent, total_count) = summarize_rows(all_rows); + let total_name_col = format!("Σ [{} Total Rows]", total_count); + let (total_size_col, total_size_percent_col) = if retained { + ("...".to_string(), "...".to_string()) + } else { + ( + total_size.to_string(), + format!("{:.2}%", total_size_percent), + ) + }; + table.add_row(vec![total_size_col, total_size_percent_col, total_name_col]); + + // Write the generated table out to the destination and return. write!(dest, "{}", &table)?; Ok(()) } @@ -163,7 +248,10 @@ impl traits::Emit for Top { fn emit_json(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> { let mut arr = json::array(dest)?; - for &id in &self.items { + let max_items = self.opts.max_items() as usize; + let items_iter = self.items.iter(); + + for &id in items_iter.take(max_items) { let item = &items[id]; let mut obj = arr.object()?; @@ -199,7 +287,10 @@ impl traits::Emit for Top { retained_size_percent: Option, } - for &id in &self.items { + let max_items = self.opts.max_items() as usize; + let items_iter = self.items.iter(); + + for &id in items_iter.take(max_items) { let item = &items[id]; let (shallow_size, shallow_size_percent) = { @@ -252,8 +343,6 @@ pub fn top(items: &mut ir::Items, opts: &opt::Top) -> Result, .cmp(&items.retained_size(a.id())), }); - top_items.truncate(opts.number() as usize); - let top_items: Vec<_> = top_items.into_iter().map(|i| i.id()).collect(); let top = Top { diff --git a/opt/definitions.rs b/opt/definitions.rs index de5096a9..bfb9b108 100644 --- a/opt/definitions.rs +++ b/opt/definitions.rs @@ -43,7 +43,7 @@ pub enum Options { } /// List the top code size offenders in a binary. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] #[derive(StructOpt)] #[wasm_bindgen] pub struct Top { @@ -63,8 +63,8 @@ pub struct Top { output_format: traits::OutputFormat, /// The maximum number of items to display. - #[structopt(short = "n")] - number: Option, + #[structopt(short = "n", default_value = "4294967295")] + max_items: u32, /// Display retaining paths. #[structopt(short = "r", long = "retaining-paths")] @@ -75,6 +75,23 @@ pub struct Top { retained: bool, } +impl Default for Top { + fn default() -> Top { + Top { + #[cfg(feature = "cli")] + input: Default::default(), + #[cfg(feature = "cli")] + output_destination: Default::default(), + #[cfg(feature = "cli")] + output_format: Default::default(), + + max_items: 4294967295, + retaining_paths: false, + retained: false, + } + } +} + #[wasm_bindgen] impl Top { /// Construct a new, default `Top`. @@ -83,8 +100,8 @@ impl Top { } /// The maximum number of items to display. - pub fn number(&self) -> u32 { - self.number.unwrap_or(u32::MAX) + pub fn max_items(&self) -> u32 { + self.max_items } /// Display retaining paths. @@ -98,8 +115,8 @@ impl Top { } /// Set the maximum number of items to display. - pub fn set_number(&mut self, n: u32) { - self.number = Some(n); + pub fn set_max_items(&mut self, n: u32) { + self.max_items = n; } /// Set whether to display and compute retaining paths. diff --git a/twiggy/tests/expectations/elf_top_25_hello_world_rs b/twiggy/tests/expectations/elf_top_25_hello_world_rs index e40c5890..fcd688e7 100644 --- a/twiggy/tests/expectations/elf_top_25_hello_world_rs +++ b/twiggy/tests/expectations/elf_top_25_hello_world_rs @@ -25,3 +25,5 @@ 2158 ┊ 0.10% ┊ prof_tdata_destroy_locked 2041 ┊ 0.10% ┊ stats_print_helper 1897 ┊ 0.09% ┊ imemalign + 150168 ┊ 7.15% ┊ ... and 753 more. + 256639 ┊ 12.23% ┊ Σ [778 Total Rows] diff --git a/twiggy/tests/expectations/elf_top_hello_world_rs b/twiggy/tests/expectations/elf_top_hello_world_rs index b3428c52..7b285fc8 100644 --- a/twiggy/tests/expectations/elf_top_hello_world_rs +++ b/twiggy/tests/expectations/elf_top_hello_world_rs @@ -778,3 +778,4 @@ 0 ┊ 0.00% ┊ je_prof_boot1 0 ┊ 0.00% ┊ je_prof_boot2 0 ┊ 0.00% ┊ je_malloc_tsd_no_cleanup + 256639 ┊ 12.23% ┊ Σ [778 Total Rows] diff --git a/twiggy/tests/expectations/top_mappings b/twiggy/tests/expectations/top_mappings index fe4d67bc..4160dee5 100644 --- a/twiggy/tests/expectations/top_mappings +++ b/twiggy/tests/expectations/top_mappings @@ -10,3 +10,5 @@ 1219 ┊ 2.69% ┊ memset 1217 ┊ 2.69% ┊ __powidf2 1142 ┊ 2.52% ┊ __udivsi3 + 23762 ┊ 52.53% ┊ ... and 338 more. + 45181 ┊ 99.89% ┊ Σ [348 Total Rows] diff --git a/twiggy/tests/expectations/top_retained_mappings b/twiggy/tests/expectations/top_retained_mappings index aac57d84..cfcba035 100644 --- a/twiggy/tests/expectations/top_retained_mappings +++ b/twiggy/tests/expectations/top_retained_mappings @@ -10,3 +10,5 @@ 3149 ┊ 6.96% ┊ __powidf2 2722 ┊ 6.02% ┊ func[78] 2721 ┊ 6.02% ┊ __divsf3 + ... ┊ ... ┊ ... and 338 more. + ... ┊ ... ┊ Σ [348 Total Rows] diff --git a/twiggy/tests/expectations/top_retained_wee_alloc b/twiggy/tests/expectations/top_retained_wee_alloc index 4516c99a..3c00f7cf 100644 --- a/twiggy/tests/expectations/top_retained_wee_alloc +++ b/twiggy/tests/expectations/top_retained_wee_alloc @@ -10,3 +10,5 @@ 226 ┊ 8.02% ┊ func[3] 225 ┊ 7.99% ┊ wee_alloc::alloc_first_fit::h9a72de3af77ef93f 136 ┊ 4.83% ┊ as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6 + ... ┊ ... ┊ ... and 29 more. + ... ┊ ... ┊ Σ [39 Total Rows] diff --git a/twiggy/tests/expectations/top_wee_alloc b/twiggy/tests/expectations/top_wee_alloc index 2824b8ad..113e50cc 100644 --- a/twiggy/tests/expectations/top_wee_alloc +++ b/twiggy/tests/expectations/top_wee_alloc @@ -10,3 +10,5 @@ 44 ┊ 1.56% ┊ goodbye 25 ┊ 0.89% ┊ data[1] 25 ┊ 0.89% ┊ data[2] + 117 ┊ 4.15% ┊ ... and 29 more. + 2772 ┊ 98.40% ┊ Σ [39 Total Rows]