diff --git a/CHANGELOG.md b/CHANGELOG.md index b54cc68..1d86cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate ### Added - Show `__awaitee` type name ([#2]). +- Support expanding (`-p`) by field's type name. ### Changed - The `-h` parameter now filters also types, not only fields. - Remove a default value for `-l`, now output is unlimited by default. +- Now expanding (`-p`) doesn't use a field's size until the `--expand-by-size` flag is provided. ### Fixed - Support nightly after 2024-03-22 ([#4]). diff --git a/README.md b/README.md index 9ba7e17..a03f58e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Features: * Hides wrappers like `MaybeUninit` and custom ones (`-w`). * Filters by type names (`-f` and `-e`). * Limits output (`-l`). -* Expands specific types with children, heuristically (`-p`). +* Expands specific types with children (`-p`). ## Usage Firstly, install by using `cargo install top-type-sizes` or clone the repository and run `cargo build --release`. @@ -38,24 +38,65 @@ $ top-type-sizes --help ``` ```text -top-type-sizes 0.1.5 +top-type-sizes 0.2.0 USAGE: top-type-sizes [FLAGS] [OPTIONS] FLAGS: - --help Prints help information - -w, --remove-wrappers Removes wrappers like `MaybeUninit` - -r, --reverse Prints top `limit` types in ascending order - -s, --sort-fields Sorts fields by size and removes paddings - -V, --version Prints version information + --expand-by-size + Modify the -p/--expand option to expand also by field's size + + --help + Prints help information + + -w, --remove-wrappers + Hides wrappers like `MaybeUninit` and `ManuallyDrop`. + + This option removes types having the same layout as an inner type. + + -r, --reverse + Prints types in descending order. + + This option is applied after the -l/--limit option. + + -s, --sort-fields + Sorts fields by size and hides paddings. + + Note: enum variants are sorted and merged anyway. + + -V, --version + Prints version information + OPTIONS: - -e, --exclude ... Excludes types that match these patterns - -p, --expand ... Shows only types that match these patterns and their children, heuristically - -f, --filter ... Shows only types that match these patterns - -h, --hide-less Hides types and fields with size less than this value - -l, --limit Shows only this number of top types + -e, --exclude ... + Excludes types that match these patterns. + + Patterns are regex (in the regex crate's syntax). Can be provided multiple times. + -p, --expand ... + Shows only types that match these patterns and their children. + + It uses two mechanisms to expand types: + - by field's type name (requires at least nightly 24-03-22) + - by field's size if the `--expand_by_size` option is enabled + + Note: currently field's type names are provided only for `await`. + + Patterns are regex (in the regex crate's syntax). Can be provided multiple times. + + -f, --filter ... + Shows only types that match these patterns. + + Patterns are regex (in the regex crate's syntax). Can be provided multiple times. + + -h, --hide-less + Hides types and fields with size less than this value + + -l, --limit + Shows only this number of top types. + + This limit is applied after all other filters. ``` ## Examples @@ -64,27 +105,24 @@ For instance, let's analyze the [`tokio/chat`](https://github.com/tokio-rs/tokio RUSTFLAGS=-Zprint-type-sizes cargo +nightly build --example chat -j 1 > chat.txt ``` -Once the compiler's output is collected, you can perform multiple queries until results become representative. +Once the compiler's output is collected, we can perform multiple queries until results become representative. -Initially, find interesting entry functions: +Initially, show all types sorted by size and find interesting ones: ```sh -top-type-sizes -f chat.rs < chat.txt | less +top-type-sizes < chat.txt | less ``` -* `-f ` hides all types that doesn't match the provided pattern. Note, that `async fn` has a path in a type name. - +For instance, if we want to expand `async fn process()` function: ```text ... -1032 [async fn body@examples/chat.rs:174:33: 243:2] align=8 +1024 {async fn body of process()} align=8 ... ``` -Ok, it's the [`process`](https://github.com/tokio-rs/tokio/blob/4ea632005d689f850e87a116b9e535a0015a7a0f/examples/chat.rs#L170) function, let's check it and children types. - +We can use the `-p`/`--expand` option to show only this function and its children types: ```sh -top-type-sizes -w -s -h 33 -p body@examples/chat.rs:174:33 < chat.txt | less +top-type-sizes -ws -h33 -p 'process\(\)' < chat.txt | less ``` - * `-w` hides wrappers, e.g. ```text 1032 std::mem::MaybeUninit<[async fn body@examples/chat.rs:174:33: 243:2]> align=8 @@ -93,53 +131,73 @@ top-type-sizes -w -s -h 33 -p body@examples/chat.rs:174:33 < chat.txt | less ``` * `-s` sorts fields by size and hides paddings. * `-h ` hides all types and fields with size less than the provided size. -* `-p ` hides all types that aren't contained in `` types. Note that the compiler doesn't provide types of fields, so this parameter filters types recursively by field sizes and can leave a lot of irrelevant types for small sizes (because they are more frequent). But it's very useful anyway. +* `-p ` hides all types that aren't contained in `` types. Note that the compiler doesn't provide types of fields for all types, only for awaitees. It's possible to use `--expand-by-size` to expand also by field's size, but it can show also irrelevant types. Output: ```text -1032 [async fn body@examples/chat.rs:174:33: 243:2] align=8 - 1031 variant Suspend2 - 472 __awaitee align=8 +1024 {async fn body of process()} align=8 + 1023 variant Suspend2 + 472 __awaitee align=8 type={async fn body of Peer::new()} 144 lines - 40 stream - 671 variant Suspend3, Suspend7, Suspend9 + 40 stream (upvar) align=8 offset=0 + 663 variant Suspend3, Suspend7, Suspend9 152 peer 144 lines - 112 __awaitee align=8 - 40 stream - 647 variant Suspend4, Suspend8, Suspend10 + 112 __awaitee align=8 type={async fn body of tokio::sync::Mutex::lock()} + 40 stream (upvar) align=8 offset=0 + 639 variant Suspend4, Suspend8, Suspend10 152 peer 144 lines - 64 __awaitee - 40 stream - 623 variant Suspend5 + 64 __awaitee type={async fn body of Shared::broadcast()} + 40 stream (upvar) align=8 offset=0 + 615 variant Suspend5 152 peer 144 lines - 40 stream - 40 futures - 599 variant Suspend6 + 40 stream (upvar) align=8 offset=0 + 40 futures align=8 + 591 variant Suspend6 152 peer 144 lines - 40 stream - 583 variant Suspend0 + 40 stream (upvar) align=8 offset=0 + 575 variant Suspend0 144 lines - 40 stream - 567 variant Suspend1 + 40 stream (upvar) align=8 offset=0 + 559 variant Suspend1 144 lines - 40 stream - 552 variant Unresumed, Returned, Panicked - 40 stream + 40 stream (upvar) align=8 offset=0 + 80 variant Unresumed, Returned, Panicked + 40 stream (upvar) align=8 offset=0 -472 [async fn body@examples/chat.rs:155:27: 166:6] align=8 +472 {async fn body of Peer::new()} align=8 465 variant Suspend0 + 144 lines (upvar) align=8 offset=0 144 lines - 144 lines - 112 __awaitee - 464 variant Unresumed, Returned, Panicked - 144 lines align=8 -... + 112 __awaitee type={async fn body of tokio::sync::Mutex::lock()} + 152 variant Unresumed, Returned, Panicked + 144 lines (upvar) align=8 offset=0 + +112 {async fn body of tokio::sync::Mutex::lock()} align=8 + 104 variant Suspend0 + 88 __awaitee type={async block@tokio::sync::Mutex::lock::{closure#0}::{closure#0}} + 8 variant Unresumed, Returned, Panicked + +88 {async block@tokio::sync::Mutex::lock::{closure#0}::{closure#0}} align=8 + 80 variant Suspend0 + 72 __awaitee type={async fn body of tokio::sync::Mutex::acquire()} + 8 variant Unresumed, Returned, Panicked + +72 {async fn body of tokio::sync::Mutex::acquire()} align=8 + 64 variant Suspend0 + 56 __awaitee type=tokio::sync::batch_semaphore::Acquire<'_> + 8 variant Unresumed, Returned, Panicked + +64 {async fn body of Shared::broadcast()} align=8 + 56 variant Unresumed, Returned, Panicked + +56 tokio::sync::batch_semaphore::Acquire<'_> align=8 + 40 node ``` Note: `__awaitee` means awaiting on an inner future. -Then, you can use `-f` and `-e` to refine output even more. +Then, we can use `-f` and `-e` to refine output even more. diff --git a/src/options.rs b/src/options.rs index 24f07b7..a558f0b 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,30 +4,62 @@ use structopt::StructOpt; #[derive(Debug, Clone, StructOpt)] pub struct Options { /// Shows only this number of top types. + /// + /// This limit is applied after all other filters. + /// {n}{n}{n} #[structopt(short = "l", long)] pub limit: Option, - /// Prints top `limit` types in ascending order. + /// Prints types in descending order. + /// + /// This option is applied after the -l/--limit option. + /// {n}{n}{n} #[structopt(short = "r", long)] pub reverse: bool, - /// Removes wrappers like `MaybeUninit`. + /// Hides wrappers like `MaybeUninit` and `ManuallyDrop`. + /// + /// This option removes types having the same layout as an inner type. + /// {n}{n}{n} #[structopt(short = "w", long)] pub remove_wrappers: bool, /// Hides types and fields with size less than this value. #[structopt(short = "h", long)] pub hide_less: Option, - /// Sorts fields by size and removes paddings. + /// Sorts fields by size and hides paddings. + /// + /// Note: enum variants are sorted and merged anyway. + /// {n}{n}{n} #[structopt(short = "s", long)] pub sort_fields: bool, /// Shows only types that match these patterns. + /// + /// Patterns are regex (in the regex crate's syntax). + /// Can be provided multiple times. + /// {n}{n}{n} #[structopt(short = "f", long)] pub filter: Vec, /// Excludes types that match these patterns. + /// + /// Patterns are regex (in the regex crate's syntax). + /// Can be provided multiple times. + /// {n}{n}{n} #[structopt(short = "e", long)] pub exclude: Vec, - /// Shows only types that match these patterns and their children, - /// heuristically. + /// Shows only types that match these patterns and their children. + /// + /// It uses two mechanisms to expand types: {n} + /// - by field's type name (requires at least nightly 24-03-22) {n} + /// - by field's size if the `--expand_by_size` option is enabled + /// + /// Note: currently field's type names are provided only for `await`. + /// + /// Patterns are regex (in the regex crate's syntax). + /// Can be provided multiple times. + /// {n}{n}{n} #[structopt(short = "p", long)] pub expand: Vec, + /// Modify the -p/--expand option to expand also by field's size. + #[structopt(long)] + pub expand_by_size: bool, } impl Default for Options { diff --git a/src/transformer.rs b/src/transformer.rs index 32da08c..0882d7e 100644 --- a/src/transformer.rs +++ b/src/transformer.rs @@ -7,10 +7,6 @@ use regex::Regex; use crate::{options::Options, schema::*}; -// TODO: link large types (parent-child). -// TODO: merge types with common prefix and similar layouts. -// TODO: support whitelist and blacklist. - /// Filters all types by size, regex filters and wrappers. fn filter_types(types: &mut Vec, options: &Options) { // Skip filtering if no options are provided. @@ -68,35 +64,44 @@ fn is_wrapper(type_: &Type) -> bool { } } -/// Retains only types that match patterns and their children. Based on -/// hueristics. Types must be sorted in descending order. -fn expand(types: &mut Vec, patterns: &[Regex]) { +/// Retains only types that match patterns and their children using fields' +/// types when provided. If `use_size` is true, it also uses sizes in fields. +/// +/// Types must be sorted in descending order by size. +fn expand(types: &mut Vec, patterns: &[Regex], use_size: bool) { if patterns.is_empty() { return; } - let field_size = |f: &FieldOrPadding| match f { - FieldOrPadding::Field(f) => Some(f.size), - FieldOrPadding::Padding(_) => None, - }; - + let mut names = HashSet::new(); let mut sizes = HashSet::new(); types.retain(|type_| { - if !sizes.contains(&type_.size) && !patterns.iter().any(|p| p.is_match(&type_.name)) { + if !names.contains(&type_.name) + && !sizes.contains(&type_.size) + && !patterns.iter().any(|p| p.is_match(&type_.name)) + { return false; } - match &type_.kind { - TypeKind::Struct(s) => { - sizes.extend(s.items.iter().filter_map(field_size)); + let append = |f: &FieldOrPadding| match f { + FieldOrPadding::Field(f) => { + if let Some(name) = &f.local_type { + names.insert(name.clone()); + } else if use_size { + sizes.insert(f.size); + } } - TypeKind::Enum(e) => sizes.extend( - e.variants - .iter() - .flat_map(|variant| variant.items.iter()) - .filter_map(field_size), - ), + FieldOrPadding::Padding(_) => {} + }; + + match &type_.kind { + TypeKind::Struct(s) => s.items.iter().for_each(append), + TypeKind::Enum(e) => e + .variants + .iter() + .flat_map(|variant| variant.items.iter()) + .for_each(append), } true @@ -188,7 +193,7 @@ pub fn transform(mut types: Vec, options: &Options) -> Vec { types.sort_by(|a, b| (b.size, &b.name).cmp(&(a.size, &a.name))); types.dedup(); - expand(&mut types, &options.expand); + expand(&mut types, &options.expand, options.expand_by_size); if let Some(limit) = options.limit { types.truncate(limit);