├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── crates ├── loupe-derive │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── loupe │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── src │ ├── lib.rs │ └── memory_usage │ │ ├── alloc.rs │ │ ├── any.rs │ │ ├── box.rs │ │ ├── cell.rs │ │ ├── collection.rs │ │ ├── marker.rs │ │ ├── mod.rs │ │ ├── option.rs │ │ ├── path.rs │ │ ├── primitive.rs │ │ ├── ptr.rs │ │ ├── remote │ │ ├── indexmap.rs │ │ └── mod.rs │ │ ├── result.rs │ │ ├── slice.rs │ │ ├── string.rs │ │ └── sync.rs │ └── tests │ └── derive.rs └── image └── logo.jpg /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | test: 10 | name: Build and Test (stable) 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | 23 | - name: Run cargo test 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: test 27 | args: --all-features --all 28 | 29 | test-nightly: 30 | name: Build and Test (nightly) 31 | 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - name: Check out code 36 | uses: actions/checkout@v2 37 | 38 | - name: Set up Rust 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | toolchain: nightly 42 | override: true 43 | 44 | - name: Run cargo test 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: test 48 | args: --all-features --all 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/loupe", 4 | "crates/loupe-derive", 5 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | crates/loupe/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | crates/loupe/README.md -------------------------------------------------------------------------------- /crates/loupe-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "loupe-derive" 3 | version = "0.2.0" 4 | description = "Profiling tool for Rust, see the `loupe` crate" 5 | repository = "https://github.com/wasmerio/loupe" 6 | license = "MIT" 7 | edition = "2018" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = { version = "1.0", features = ["full"] } 14 | quote = "1.0" -------------------------------------------------------------------------------- /crates/loupe-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Companion of the [`loupe`](../loupe-derive/index.html) crate. 2 | 3 | use proc_macro::TokenStream; 4 | use quote::{format_ident, quote, quote_spanned}; 5 | use syn::{ 6 | parse, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, Index, 7 | }; 8 | 9 | /// Procedural macro to implement the `loupe::MemoryUsage` trait 10 | /// automatically for structs and enums. 11 | /// 12 | /// All struct fields and enum variants must implement `MemoryUsage` 13 | /// trait. If it's not possible, the `#[loupe(skip)]` attribute can be 14 | /// used on a field or a variant to instruct the derive procedural 15 | /// macro to skip that item. 16 | /// 17 | /// # Example 18 | /// 19 | /// ```rust,ignore 20 | /// #[derive(MemoryUsage)] 21 | /// struct Point { 22 | /// x: i32, 23 | /// y: i32, 24 | /// } 25 | /// 26 | /// struct Mystery { ptr: *const i32 } 27 | /// 28 | /// #[derive(MemoryUsage)] 29 | /// struct S { 30 | /// points: Vec, 31 | /// 32 | /// #[loupe(skip)] 33 | /// other: Mystery, 34 | /// } 35 | /// ``` 36 | #[proc_macro_derive(MemoryUsage, attributes(loupe))] 37 | pub fn derive_memory_usage(input: TokenStream) -> TokenStream { 38 | let derive_input: DeriveInput = parse(input).unwrap(); 39 | 40 | match derive_input.data { 41 | Data::Struct(ref struct_data) => { 42 | derive_memory_usage_for_struct(&derive_input.ident, struct_data, &derive_input.generics) 43 | } 44 | 45 | Data::Enum(ref enum_data) => { 46 | derive_memory_usage_for_enum(&derive_input.ident, enum_data, &derive_input.generics) 47 | } 48 | 49 | Data::Union(_) => panic!("unions are not yet implemented"), 50 | /* 51 | // TODO: unions. 52 | // We have no way of knowing which union member is active, so we should 53 | // refuse to derive an impl except for unions where all members are 54 | // primitive types or arrays of them. 55 | Data::Union(ref union_data) => { 56 | derive_memory_usage_union(union_data) 57 | }, 58 | */ 59 | } 60 | } 61 | 62 | // TODO: use Iterator::fold_first once it's stable. https://github.com/rust-lang/rust/pull/79805 63 | fn join_fold(mut iter: I, function: F, empty: B) -> B 64 | where 65 | I: Iterator, 66 | F: FnMut(B, I::Item) -> B, 67 | { 68 | if let Some(first) = iter.next() { 69 | iter.fold(first, function) 70 | } else { 71 | empty 72 | } 73 | } 74 | 75 | fn derive_memory_usage_for_struct( 76 | struct_name: &Ident, 77 | data: &DataStruct, 78 | generics: &Generics, 79 | ) -> TokenStream { 80 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 81 | 82 | let sum = join_fold( 83 | // Check all fields of the `struct`. 84 | match &data.fields { 85 | // Field has the form: 86 | // 87 | // F { x, y } 88 | Fields::Named(ref fields) => fields 89 | .named 90 | .iter() 91 | .filter_map(|field| { 92 | if must_skip(&field.attrs) { 93 | return None; 94 | } 95 | 96 | let ident = field.ident.as_ref().unwrap(); 97 | let span = ident.span(); 98 | 99 | Some(quote_spanned!( 100 | span => loupe::MemoryUsage::size_of_val(&self.#ident, visited) - std::mem::size_of_val(&self.#ident) 101 | )) 102 | }) 103 | .collect(), 104 | 105 | // Field has the form: 106 | // 107 | // F 108 | Fields::Unit => vec![], 109 | 110 | // Field has the form: 111 | // 112 | // F(x, y) 113 | Fields::Unnamed(ref fields) => fields 114 | .unnamed 115 | .iter() 116 | .enumerate() 117 | .filter_map(|(nth, field)| { 118 | if must_skip(&field.attrs) { 119 | return None; 120 | } 121 | 122 | let ident = Index::from(nth); 123 | 124 | Some(quote! { loupe::MemoryUsage::size_of_val(&self.#ident, visited) - std::mem::size_of_val(&self.#ident) }) 125 | }) 126 | .collect(), 127 | } 128 | .into_iter(), 129 | |x, y| quote! { #x + #y }, 130 | quote! { 0 }, 131 | ); 132 | 133 | // Implement the `MemoryUsage` trait for `struct_name`. 134 | (quote! { 135 | #[allow(dead_code)] 136 | impl #impl_generics loupe::MemoryUsage for #struct_name #ty_generics 137 | #where_clause 138 | { 139 | fn size_of_val(&self, visited: &mut loupe::MemoryUsageTracker) -> usize { 140 | std::mem::size_of_val(self) + #sum 141 | } 142 | } 143 | }) 144 | .into() 145 | } 146 | 147 | fn derive_memory_usage_for_enum( 148 | enum_name: &Ident, 149 | data: &DataEnum, 150 | generics: &Generics, 151 | ) -> TokenStream { 152 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 153 | 154 | let match_arms = join_fold( 155 | data.variants 156 | .iter() 157 | .map(|variant| { 158 | let ident = &variant.ident; 159 | let span = ident.span(); 160 | 161 | // Check all the variants of the `enum`. 162 | // 163 | // We want to generate something like this: 164 | // 165 | // Self::Variant ... => { ... } 166 | // ^^^^^^^ ^^^ ^^^ 167 | // | | | 168 | // | | given by the `sum` variable 169 | // | given by the `pattern` variable 170 | // given by the `ident` variable 171 | // 172 | // Let's compute the `pattern` and `sum` parts. 173 | let (pattern, mut sum) = match variant.fields { 174 | // Variant has the form: 175 | // 176 | // V { x, y } 177 | // 178 | // We want to generate: 179 | // 180 | // Self::V { x, y } => { /* memory usage of x + y */ } 181 | Fields::Named(ref fields) => { 182 | // Collect the identifiers. 183 | let identifiers = fields.named.iter().map(|field| { 184 | let ident = field.ident.as_ref().unwrap(); 185 | let span = ident.span(); 186 | 187 | quote_spanned!(span => #ident) 188 | }); 189 | 190 | // Generate the `pattern` part. 191 | let pattern = { 192 | let pattern = join_fold( 193 | identifiers.clone(), 194 | |x, y| quote! { #x , #y }, 195 | quote! {} 196 | ); 197 | 198 | quote! { { #pattern } } 199 | }; 200 | 201 | // Generate the `sum` part. 202 | let sum = { 203 | let sum = join_fold( 204 | identifiers.map(|ident| quote! { 205 | loupe::MemoryUsage::size_of_val(#ident, visited) - std::mem::size_of_val(#ident) 206 | }), 207 | |x, y| quote! { #x + #y }, 208 | quote! { 0 }, 209 | ); 210 | 211 | quote! { #sum } 212 | }; 213 | 214 | (pattern, sum) 215 | } 216 | 217 | // Variant has the form: 218 | // 219 | // V 220 | // 221 | // We want to generate: 222 | // 223 | // Self::V => { 0 } 224 | Fields::Unit => { 225 | let pattern = quote! {}; 226 | let sum = quote! { 0 }; 227 | 228 | (pattern, sum) 229 | }, 230 | 231 | // Variant has the form: 232 | // 233 | // V(x, y) 234 | // 235 | // We want to generate: 236 | // 237 | // Self::V(x, y) => { /* memory usage of x + y */ } 238 | Fields::Unnamed(ref fields) => { 239 | // Collect the identifiers. They are unnamed, 240 | // so let's use the `xi` convention where `i` 241 | // is the identifier index. 242 | let identifiers = fields 243 | .unnamed 244 | .iter() 245 | .enumerate() 246 | .map(|(nth, _field)| { 247 | let ident = format_ident!("x{}", Index::from(nth)); 248 | 249 | quote! { #ident } 250 | }); 251 | 252 | // Generate the `pattern` part. 253 | let pattern = { 254 | let pattern = join_fold( 255 | identifiers.clone(), 256 | |x, y| quote! { #x , #y }, 257 | quote! {} 258 | ); 259 | 260 | quote! { ( #pattern ) } 261 | }; 262 | 263 | // Generate the `sum` part. 264 | let sum = { 265 | let sum = join_fold( 266 | identifiers.map(|ident| quote! { 267 | loupe::MemoryUsage::size_of_val(#ident, visited) - std::mem::size_of_val(#ident) 268 | }), 269 | |x, y| quote! { #x + #y }, 270 | quote! { 0 }, 271 | ); 272 | 273 | quote! { #sum } 274 | }; 275 | 276 | (pattern, sum) 277 | } 278 | }; 279 | 280 | if must_skip(&variant.attrs) { 281 | sum = quote! { 0 }; 282 | } 283 | 284 | // At this step, `pattern` and `sum` are well 285 | // defined. Let's generate the full arm for the 286 | // `match` statement. 287 | quote_spanned! { span => Self::#ident#pattern => #sum } 288 | } 289 | ), 290 | |x, y| quote! { #x , #y }, 291 | quote! {}, 292 | ); 293 | 294 | // Implement the `MemoryUsage` trait for `enum_name`. 295 | (quote! { 296 | #[allow(dead_code)] 297 | impl #impl_generics loupe::MemoryUsage for #enum_name #ty_generics 298 | #where_clause 299 | { 300 | fn size_of_val(&self, visited: &mut loupe::MemoryUsageTracker) -> usize { 301 | std::mem::size_of_val(self) + match self { 302 | #match_arms 303 | } 304 | } 305 | } 306 | }) 307 | .into() 308 | } 309 | 310 | fn must_skip(attrs: &[Attribute]) -> bool { 311 | attrs.iter().any(|attr| { 312 | attr.path.is_ident("loupe") && matches!(attr.parse_args::(), Ok(a) if a == "skip") 313 | }) 314 | } 315 | -------------------------------------------------------------------------------- /crates/loupe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "loupe" 3 | version = "0.2.0" 4 | description = "Profiling tool for Rust" 5 | repository = "https://github.com/wasmerio/loupe" 6 | license = "MIT" 7 | edition = "2018" 8 | 9 | [dependencies] 10 | loupe-derive = { path = "../loupe-derive", version = "0.2.0", optional = true } 11 | indexmap = { version = "2", optional = true } 12 | rustversion = "1.0" 13 | 14 | [features] 15 | default = ["derive"] 16 | derive = ["loupe-derive"] 17 | enable-indexmap = ["indexmap"] -------------------------------------------------------------------------------- /crates/loupe/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present Wasmer, Inc. and its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/loupe/README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | loupe 4 |

5 | 6 | [![crates.io](https://img.shields.io/crates/v/loupe)](https://crates.io/crates/loupe) 7 | [![documentation](https://img.shields.io/badge/doc-loupe-green)](https://docs.rs/loupe) 8 | 9 | 🔎 `loupe` is a set of tools to analyse and to profile Rust code. For the 10 | moment, it only provides tools about memory usage. It's mostly driven 11 | by [Wasmer]'s needs, but feel free to propose new features! 12 | 13 | `loupe` is a French word to express _magnifying glass_, and can be 14 | pronounced exactly like _loop_. The bird above is a _Fauvette à 15 | lunettes_ (_Curruca conspicillata_, Spectacled Warbler). 16 | 17 | ## Install 18 | 19 | The classical `Cargo` step! Add the following line to your 20 | `Cargo.toml` file: 21 | 22 | ```toml 23 | [dependencies] 24 | loupe = "0.1" 25 | ``` 26 | 27 | ## Memory Usage 28 | 29 | `loupe` provides the `MemoryUsage` trait. It allows to know the size 30 | of a value in bytes, _recursively_. So it traverses most of the types 31 | (some are missing, feel free to contribute!), and its fields or 32 | variants as deep as possible. Hopefully, it tracks already visited 33 | values so that it doesn't enter an infinite ~~_loupe_~~ loop. The 34 | trait looks like this: 35 | 36 | ```rust 37 | pub trait MemoryUsage { 38 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize; 39 | } 40 | ``` 41 | 42 | `loupe` provides a `size_of_val` function that is a close sibling of 43 | [`std::mem::size_of_val`](https://doc.rust-lang.org/std/mem/fn.size_of_val.html). It 44 | can be used the same way. 45 | 46 | `loupe` exports its best companion: handful procedural macros from the 47 | `loupe-derive` crate, to automatically implement the `MemoryUsage` 48 | trait if possible, on `struct`s and `enum`s. 49 | 50 | Thus, one only needs to write: 51 | 52 | ```rust 53 | use loupe::MemoryUsage; 54 | use std::mem; 55 | 56 | #[derive(MemoryUsage)] 57 | struct S { 58 | x: Vec, 59 | y: Vec, 60 | } 61 | 62 | fn main() { 63 | let s = S { 64 | x: vec![1, 2, 3], 65 | y: vec![1, 2, 3], 66 | }; 67 | 68 | assert_eq!(48, mem::size_of_val(&s)); 69 | assert_eq!(72, loupe::size_of_val(&s)); 70 | } 71 | ``` 72 | 73 | In the example above, we see that each elements of `Vec` has been 74 | counted in the size of the value `s`. In [Wasmer], it is possible to 75 | get the size of an `Instance`, which traverses `Module`, `Store`, 76 | `Engine`, `Compiler` etc. It's an entire tree of values that is 77 | traversed and the size of each value is summed. 78 | 79 | ### Opinionated implementations 80 | 81 | Even if `MemoryUsage` is already implemented for common types, some 82 | types are missing. We happily welcome more implementations! However, 83 | implementations of `MemoryUsage`: 84 | 85 | * must never alter the values, 86 | * must never panic of fail, 87 | * must be deterministic as much as possible (ideally, everytime). 88 | 89 | In the same spirit, our implementation of `MemoryUsage` for `*const T` 90 | or `*mut T` (and other pointer types, like `NonNull`, `UnsafeCell` 91 | etc.) just returns the size of the pointer, but it doesn't dereference 92 | the pointer as it's unsafe. It doesn't mean one must not do that: It's 93 | totally possible if it's sure that the pointer can be safely 94 | dereferenced. 95 | 96 | Remember that a user can implement `MemoryUsage` by hand; no need to 97 | try to have a default implementation for all the standard types. 98 | 99 | Finally, our implementations are certainly not perfect! Feel free to 100 | challenge it and come to discuss! 101 | 102 | ## License 103 | 104 | `MIT` License, see `LICENSE`. 105 | 106 | 107 | [Wasmer]: https://github.com/wasmerio/wasmer 108 | -------------------------------------------------------------------------------- /crates/loupe/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 🔎 `loupe` is a set of tools to analyse and to profile Rust 2 | //! code. For the moment, it only provides tools about memory 3 | //! usage. It's mostly driven by 4 | //! [Wasmer](https://github.com/wasmerio/wasmer)'s needs, but feel 5 | //! free to propose new features! 6 | //! 7 | //! `loupe` is a French word to express _magnifying glass_, and can be 8 | //! pronounced exactly like _loop_. 9 | //! 10 | //! ## Memory Usage 11 | //! 12 | //! `loupe` provides the [`MemoryUsage`] trait. It allows to know the 13 | //! size of a value in bytes, _recursively_. So it traverses most of 14 | //! the types (some are missing, feel free to contribute!), and its 15 | //! fields or variants as deep as possible. Hopefully, it tracks 16 | //! already visited values so that it doesn't enter an infinite 17 | //! ~~_loupe_~~ loop. The trait looks like this: 18 | //! 19 | //! ```rust,ignore 20 | //! pub trait MemoryUsage { 21 | //! fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize; 22 | //! } 23 | //! ``` 24 | //! 25 | //! `loupe` provides a [`size_of_val`] function that is a close sibling 26 | //! of 27 | //! [`std::mem::size_of_val`](https://doc.rust-lang.org/std/mem/fn.size_of_val.html). It 28 | //! can be used the same way. 29 | //! 30 | //! `loupe` exports its best companion: handful procedural macros from 31 | //! the [`loupe-derive`](../loupe_derive/index.html) crate, to 32 | //! automatically implement the [`MemoryUsage`] trait if possible, on 33 | //! `struct`s and `enum`s. 34 | //! 35 | //! Thus, one only needs to write: 36 | //! 37 | //! ```rust 38 | //! use loupe::MemoryUsage; 39 | //! use std::mem; 40 | //! 41 | //! #[derive(MemoryUsage)] 42 | //! struct S { 43 | //! x: Vec, 44 | //! y: Vec, 45 | //! } 46 | //! 47 | //! fn main() { 48 | //! let s = S { 49 | //! x: vec![1, 2, 3], 50 | //! y: vec![1, 2, 3], 51 | //! }; 52 | //! 53 | //! assert_eq!(48, mem::size_of_val(&s)); 54 | //! assert_eq!(72, loupe::size_of_val(&s)); 55 | //! } 56 | //! ``` 57 | //! 58 | //! In the example above, we see that each elements of `Vec` has 59 | //! been counted in the size of the value `s`. In 60 | //! [Wasmer](https://github.com/wasmerio/wasmer), it is possible to 61 | //! get the size of an `Instance`, which traverses `Module`, `Store`, 62 | //! `Engine`, `Compiler` etc. It's an entire tree of values that is 63 | //! traversed and the size of each value is summed. 64 | //! 65 | //! ### Opinionated implementations 66 | //! 67 | //! Even if [`MemoryUsage`] is already implemented for common types, 68 | //! some types are missing. We happily welcome more implementations! 69 | //! However, implementations of `MemoryUsage`: 70 | //! 71 | //! * must never alter the values, 72 | //! * must never panic of fail, 73 | //! * must be deterministic as much as possible (ideally, everytime). 74 | //! 75 | //! In the same spirit, our implementation of [`MemoryUsage`] for 76 | //! `*const T` or `*mut T` (and other pointer types, like `NonNull`, 77 | //! `UnsafeCell` etc.) just returns the size of the pointer, but it 78 | //! doesn't dereference the pointer as it's unsafe. It doesn't mean 79 | //! one must not do that: It's totally possible if it's sure that the 80 | //! pointer can be safely dereferenced. 81 | //! 82 | //! Remember that a user can implement [`MemoryUsage`] by hand; no 83 | //! need to try to have a default implementation for all the standard 84 | //! types. 85 | //! 86 | //! Finally, our implementations are certainly not perfect! Feel free to 87 | //! challenge it and come to discuss! 88 | 89 | mod memory_usage; 90 | 91 | #[cfg(feature = "derive")] 92 | pub use loupe_derive::*; 93 | pub use memory_usage::*; 94 | 95 | use std::collections::BTreeSet; 96 | 97 | /// Returns the size of the pointer-to value in bytes. The size is 98 | /// calculated with `MemoryUsage::size_of_val`. 99 | /// 100 | /// # Example 101 | /// 102 | /// ```rust 103 | /// use loupe::MemoryUsage; 104 | /// use std::mem; 105 | /// 106 | /// #[derive(MemoryUsage)] 107 | /// struct S { 108 | /// x: Vec, 109 | /// y: Vec, 110 | /// } 111 | /// 112 | /// fn main() { 113 | /// let s = S { 114 | /// x: vec![1, 2, 3], 115 | /// y: vec![1, 2, 3], 116 | /// }; 117 | /// 118 | /// assert_eq!(48, mem::size_of_val(&s)); 119 | /// assert_eq!(72, loupe::size_of_val(&s)); 120 | /// } 121 | /// ``` 122 | pub fn size_of_val(value: &T) -> usize { 123 | ::size_of_val(value, &mut BTreeSet::new()) 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::*; 129 | 130 | #[test] 131 | fn test_size_of_val_helper() { 132 | assert_eq!(size_of_val(&"abc"), 2 * POINTER_BYTE_SIZE + 1 * 3); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/alloc.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::alloc::Layout; 5 | use std::mem; 6 | 7 | impl MemoryUsage for Layout { 8 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 9 | mem::size_of_val(self) 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod test_alloc_types { 15 | use super::*; 16 | 17 | #[test] 18 | fn test_layout() { 19 | let layout = Layout::new::(); 20 | assert_size_of_val_eq!(layout, 2 * POINTER_BYTE_SIZE); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/any.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::any::Any; 5 | use std::mem; 6 | 7 | impl MemoryUsage for dyn Any { 8 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 9 | mem::size_of_val(self) 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod test_any_types { 15 | use super::*; 16 | 17 | #[test] 18 | fn test_boxed_any() { 19 | let b: Box = Box::new(1i8); 20 | assert_size_of_val_eq!(b, 2 * POINTER_BYTE_SIZE + 1); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/box.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | 6 | impl MemoryUsage for Box 7 | where 8 | T: MemoryUsage + ?Sized, 9 | { 10 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 11 | let reference = self.as_ref(); 12 | 13 | mem::size_of_val(self) 14 | + if tracker.track(reference as *const _ as *const ()) { 15 | reference.size_of_val(tracker) 16 | } else { 17 | 0 18 | } 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod test_box_types { 24 | use super::*; 25 | 26 | #[test] 27 | fn test_box() { 28 | let b: Box = Box::new(1); 29 | assert_size_of_val_eq!(b, POINTER_BYTE_SIZE + 1); 30 | 31 | let b: Box = Box::new(1); 32 | assert_size_of_val_eq!(b, POINTER_BYTE_SIZE + 4); 33 | 34 | let b: Box<&str> = Box::new("abc"); 35 | assert_size_of_val_eq!(b, POINTER_BYTE_SIZE + 2 * POINTER_BYTE_SIZE + 1 * 3); 36 | 37 | let b: Box<(i8, i16)> = Box::new((1, 2)); 38 | assert_size_of_val_eq!( 39 | b, 40 | POINTER_BYTE_SIZE + 1 /* i8 */ + 2 /* i16 */ + 1, /* padding */ 41 | ); 42 | } 43 | 44 | #[test] 45 | fn test_boxed_slice() { 46 | let b: Box<[u8]> = vec![].into_boxed_slice(); 47 | assert_size_of_val_eq!(b, 2 * POINTER_BYTE_SIZE); 48 | 49 | let b: Box<[u8]> = vec![1, 2, 3].into_boxed_slice(); 50 | assert_size_of_val_eq!(b, 2 * POINTER_BYTE_SIZE + 1 * 3); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/cell.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::assert_size_of_val_eq; 3 | use crate::{MemoryUsage, MemoryUsageTracker, POINTER_BYTE_SIZE}; 4 | use std::cell::{RefCell, UnsafeCell}; 5 | use std::mem; 6 | 7 | impl MemoryUsage for UnsafeCell { 8 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 9 | mem::size_of_val(self) 10 | + if tracker.track(self.get() as *const ()) { 11 | POINTER_BYTE_SIZE 12 | } else { 13 | 0 14 | } 15 | } 16 | } 17 | 18 | impl MemoryUsage for RefCell 19 | where 20 | T: MemoryUsage, 21 | { 22 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 23 | mem::size_of_val(self) 24 | + match self.try_borrow() { 25 | Ok(borrowed) if tracker.track(self.as_ptr() as *const _ as *const ()) => { 26 | borrowed.size_of_val(tracker) 27 | } 28 | 29 | _ => 0, 30 | } 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod test_cell_types { 36 | use super::*; 37 | use crate::size_of_val; 38 | 39 | #[test] 40 | fn test_unsafecell() { 41 | let cell = UnsafeCell::::new(1); 42 | assert_size_of_val_eq!(cell, mem::size_of_val(&cell) + POINTER_BYTE_SIZE); 43 | } 44 | 45 | #[test] 46 | fn test_refcell() { 47 | let cell = RefCell::>::new(vec![]); 48 | 49 | { 50 | cell.borrow_mut().push(1); 51 | } 52 | 53 | let cell_size = size_of_val(&cell); 54 | 55 | { 56 | let mut vec = cell.borrow_mut(); 57 | vec.push(2); 58 | vec.push(3); 59 | 60 | assert_size_of_val_eq!(cell, mem::size_of_val(&cell)); 61 | } 62 | 63 | assert_size_of_val_eq!(cell, cell_size + 2); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/collection.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::collections::{BTreeMap, HashMap}; 5 | use std::mem; 6 | 7 | impl MemoryUsage for Vec 8 | where 9 | T: MemoryUsage, 10 | { 11 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 12 | mem::size_of_val(self) 13 | + self 14 | .iter() 15 | .map(|value| value.size_of_val(tracker)) 16 | .sum::() 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod test_vec_types { 22 | use super::*; 23 | 24 | #[test] 25 | fn test_vec() { 26 | let empty_vec_size = mem::size_of_val(&Vec::::new()); 27 | 28 | let mut vec: Vec = Vec::new(); 29 | assert_size_of_val_eq!(vec, empty_vec_size + 1 * 0); 30 | 31 | vec.push(1); 32 | assert_size_of_val_eq!(vec, empty_vec_size + 1 * 1); 33 | 34 | vec.push(2); 35 | assert_size_of_val_eq!(vec, empty_vec_size + 1 * 2); 36 | } 37 | 38 | #[test] 39 | fn test_vec_not_unique() { 40 | let empty_vec_size = mem::size_of_val(&Vec::<&i32>::new()); 41 | 42 | let mut vec: Vec<&i32> = Vec::new(); 43 | assert_size_of_val_eq!(vec, empty_vec_size); 44 | 45 | let one: i32 = 1; 46 | vec.push(&one); 47 | assert_size_of_val_eq!(vec, empty_vec_size + POINTER_BYTE_SIZE + 4); 48 | 49 | let two: i32 = 2; 50 | vec.push(&two); 51 | assert_size_of_val_eq!( 52 | vec, 53 | empty_vec_size + POINTER_BYTE_SIZE + 4 + POINTER_BYTE_SIZE + 4 54 | ); 55 | 56 | // Push a reference to an item that already exists! 57 | vec.push(&one); 58 | assert_size_of_val_eq!( 59 | vec, 60 | empty_vec_size + POINTER_BYTE_SIZE + 4 + POINTER_BYTE_SIZE + 4 + POINTER_BYTE_SIZE + 0 /* no string content */ 61 | ); 62 | } 63 | } 64 | 65 | impl MemoryUsage for HashMap 66 | where 67 | K: MemoryUsage, 68 | V: MemoryUsage, 69 | { 70 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 71 | mem::size_of_val(self) 72 | + self 73 | .iter() 74 | .map(|(key, value)| key.size_of_val(tracker) + value.size_of_val(tracker)) 75 | .sum::() 76 | } 77 | } 78 | 79 | impl MemoryUsage for BTreeMap 80 | where 81 | K: MemoryUsage, 82 | V: MemoryUsage, 83 | { 84 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 85 | mem::size_of_val(self) 86 | + self 87 | .iter() 88 | .map(|(key, value)| key.size_of_val(tracker) + value.size_of_val(tracker)) 89 | .sum::() 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod test_collection_types { 95 | use super::*; 96 | 97 | #[test] 98 | fn test_hashmap() { 99 | let mut hashmap: HashMap = HashMap::new(); 100 | let empty_hashmap_size = mem::size_of_val(&hashmap); 101 | assert_size_of_val_eq!(hashmap, empty_hashmap_size + 1 * 0 + 4 * 0); 102 | 103 | hashmap.insert(1, 1); 104 | assert_size_of_val_eq!(hashmap, empty_hashmap_size + 1 * 1 + 4 * 1); 105 | 106 | hashmap.insert(2, 2); 107 | assert_size_of_val_eq!(hashmap, empty_hashmap_size + 1 * 2 + 4 * 2); 108 | } 109 | 110 | #[test] 111 | fn test_hashmap_not_unique() { 112 | let mut hashmap: HashMap = HashMap::new(); 113 | let empty_hashmap_size = mem::size_of_val(&hashmap); 114 | assert_size_of_val_eq!( 115 | hashmap, 116 | empty_hashmap_size + 1 * 0 + (POINTER_BYTE_SIZE + 4) * 0 117 | ); 118 | 119 | let one: i32 = 1; 120 | hashmap.insert(1, &one); 121 | assert_size_of_val_eq!( 122 | hashmap, 123 | empty_hashmap_size + 1 * 1 + (POINTER_BYTE_SIZE + 4) * 1 124 | ); 125 | 126 | let two: i32 = 2; 127 | hashmap.insert(2, &two); 128 | assert_size_of_val_eq!( 129 | hashmap, 130 | empty_hashmap_size + 1 * 2 + (POINTER_BYTE_SIZE + 4) * 2 131 | ); 132 | 133 | // Push a reference to an item that already exists! 134 | hashmap.insert(3, &one); 135 | assert_size_of_val_eq!( 136 | hashmap, 137 | empty_hashmap_size + 1 * 3 + (POINTER_BYTE_SIZE + 4) * 2 + POINTER_BYTE_SIZE + 0 /* no i32 */ 138 | ); 139 | } 140 | 141 | #[test] 142 | fn test_btreemap() { 143 | let mut btreemap: BTreeMap = BTreeMap::new(); 144 | let empty_btreemap_size = mem::size_of_val(&btreemap); 145 | assert_size_of_val_eq!(btreemap, empty_btreemap_size + 1 * 0 + 4 * 0); 146 | 147 | btreemap.insert(1, 1); 148 | assert_size_of_val_eq!(btreemap, empty_btreemap_size + 1 * 1 + 4 * 1); 149 | 150 | btreemap.insert(2, 2); 151 | assert_size_of_val_eq!(btreemap, empty_btreemap_size + 1 * 2 + 4 * 2); 152 | } 153 | 154 | #[test] 155 | fn test_btreemap_not_unique() { 156 | let mut btreemap: BTreeMap = BTreeMap::new(); 157 | let empty_btreemap_size = mem::size_of_val(&btreemap); 158 | assert_size_of_val_eq!( 159 | btreemap, 160 | empty_btreemap_size + 1 * 0 + (POINTER_BYTE_SIZE + 4) * 0 161 | ); 162 | 163 | let one: i32 = 1; 164 | btreemap.insert(1, &one); 165 | assert_size_of_val_eq!( 166 | btreemap, 167 | empty_btreemap_size + 1 * 1 + (POINTER_BYTE_SIZE + 4) * 1 168 | ); 169 | 170 | let two: i32 = 2; 171 | btreemap.insert(2, &two); 172 | assert_size_of_val_eq!( 173 | btreemap, 174 | empty_btreemap_size + 1 * 2 + (POINTER_BYTE_SIZE + 4) * 2 175 | ); 176 | 177 | // Push a reference to an item that already exists! 178 | btreemap.insert(3, &one); 179 | assert_size_of_val_eq!( 180 | btreemap, 181 | empty_btreemap_size + 1 * 3 + (POINTER_BYTE_SIZE + 4) * 2 + POINTER_BYTE_SIZE + 0 /* no i32 */ 182 | ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/marker.rs: -------------------------------------------------------------------------------- 1 | use crate::{MemoryUsage, MemoryUsageTracker}; 2 | use std::marker::PhantomData; 3 | 4 | impl MemoryUsage for PhantomData { 5 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 6 | 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/mod.rs: -------------------------------------------------------------------------------- 1 | mod alloc; 2 | mod any; 3 | mod r#box; 4 | mod cell; 5 | mod collection; 6 | mod marker; 7 | mod option; 8 | mod path; 9 | mod primitive; 10 | mod ptr; 11 | mod remote; 12 | mod result; 13 | mod slice; 14 | mod string; 15 | mod sync; 16 | 17 | pub use alloc::*; 18 | pub use any::*; 19 | pub use cell::*; 20 | pub use collection::*; 21 | pub use marker::*; 22 | pub use option::*; 23 | pub use path::*; 24 | pub use primitive::*; 25 | pub use ptr::*; 26 | pub use r#box::*; 27 | pub use remote::*; 28 | pub use result::*; 29 | pub use slice::*; 30 | pub use string::*; 31 | pub use sync::*; 32 | 33 | /// Size of a pointer for the compilation target. 34 | pub const POINTER_BYTE_SIZE: usize = if cfg!(target_pointer_width = "16") { 35 | 2 36 | } else if cfg!(target_pointer_width = "32") { 37 | 4 38 | } else { 39 | 8 40 | }; 41 | 42 | /// Represent a bucket that can track memory addresses that have 43 | /// already been visited by `MemoryUsage`. 44 | pub trait MemoryUsageTracker { 45 | /// When first called on a given address returns true, false otherwise. 46 | fn track(&mut self, address: *const ()) -> bool; 47 | } 48 | 49 | impl MemoryUsageTracker for std::collections::BTreeSet<*const ()> { 50 | fn track(&mut self, address: *const ()) -> bool { 51 | self.insert(address) 52 | } 53 | } 54 | 55 | impl MemoryUsageTracker for std::collections::HashSet<*const ()> { 56 | fn track(&mut self, address: *const ()) -> bool { 57 | self.insert(address) 58 | } 59 | } 60 | 61 | /// Traverse a value and collect its memory usage. 62 | pub trait MemoryUsage { 63 | /// Returns the size of the referenced value in bytes. 64 | /// 65 | /// Recursively visits the value and any children returning the sum of their 66 | /// sizes. The size always includes any tail padding if applicable. 67 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize; 68 | } 69 | 70 | /// Alias to `assert_eq!(loupe::MemoryUsage::size_of_val(&$value), $expected)`. 71 | #[macro_export] 72 | macro_rules! assert_size_of_val_eq { 73 | ($value:expr, $expected:expr $(,)*) => { 74 | assert_size_of_val_eq!($value, $expected, &mut std::collections::BTreeSet::new()); 75 | }; 76 | 77 | ($value:expr, $expected:expr, $tracker:expr $(,)*) => { 78 | assert_eq!( 79 | $crate::MemoryUsage::size_of_val(&$value, $tracker), 80 | $expected 81 | ); 82 | }; 83 | } 84 | 85 | // TODO: 86 | // 87 | // * Cell 88 | // * Pin (is a Pin always referenceable?) 89 | // * Rc 90 | // * Ref 91 | // * RefCell 92 | // * RefMut 93 | // * PhantomPinned 94 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/option.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | 6 | impl MemoryUsage for Option 7 | where 8 | T: MemoryUsage, 9 | { 10 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 11 | mem::size_of_val(self) 12 | + self 13 | .iter() 14 | .map(|value| value.size_of_val(tracker)) 15 | .sum::() 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod test_option_types { 21 | use super::*; 22 | 23 | #[test] 24 | fn test_option() { 25 | let option: Option = None; 26 | assert_size_of_val_eq!(option, 1 /* variant */ + 1 /* padding */); 27 | 28 | let option: Option = Some(1); 29 | assert_size_of_val_eq!(option, 1 /* variant */ + 1 /* padding */ + 1 /* i8 */); 30 | 31 | let option: Option = None; 32 | assert_size_of_val_eq!(option, 1 /* variant */ + 7 /* padding */); 33 | 34 | let option: Option = Some(1); 35 | assert_size_of_val_eq!(option, 1 /* variant */ + 7 /* padding */ + 4 /* i32 */); 36 | 37 | let option: Option<&str> = None; 38 | assert_size_of_val_eq!(option, 1 /* variant */ + 15 /* padding */); 39 | 40 | let option: Option<&str> = Some("abc"); 41 | assert_size_of_val_eq!( 42 | option, 43 | 1 /* variant */ + 15 /* padding */ + 2 * POINTER_BYTE_SIZE + 1 * 3, /* &str */ 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/path.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::assert_size_of_val_eq; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | use std::path::PathBuf; 6 | 7 | impl MemoryUsage for PathBuf { 8 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 9 | mem::size_of_val(self) + self.capacity() 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod test_path_types { 15 | use super::*; 16 | 17 | #[test] 18 | fn test_pathbuf() { 19 | let mut path = PathBuf::new(); 20 | let empty_path_size = mem::size_of_val(&path); 21 | 22 | path.push("foo"); 23 | assert_size_of_val_eq!(path, empty_path_size + 8); 24 | 25 | path.push("foobar"); 26 | assert_size_of_val_eq!(path, empty_path_size + 16); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/primitive.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | 6 | macro_rules! impl_memory_usage_for_numeric { 7 | ( $type:ty ) => { 8 | impl MemoryUsage for $type { 9 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 10 | mem::size_of_val(self) 11 | } 12 | } 13 | }; 14 | 15 | ( $( $type:ty ),+ $(,)* ) => { 16 | $( impl_memory_usage_for_numeric!( $type ); )+ 17 | } 18 | } 19 | 20 | impl_memory_usage_for_numeric!( 21 | bool, char, f32, f64, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize 22 | ); 23 | 24 | #[cfg(test)] 25 | mod test_numeric_types { 26 | use super::*; 27 | 28 | macro_rules! test_memory_usage_for_numeric { 29 | ($test_name:ident: ($value:expr) == $expected:expr) => { 30 | #[test] 31 | fn $test_name() { 32 | assert_size_of_val_eq!($value, $expected); 33 | } 34 | }; 35 | 36 | ( $( $test_name:ident: ($value:expr) == $expected:expr );+ $(;)* ) => { 37 | $( test_memory_usage_for_numeric!( $test_name: ($value) == $expected); )+ 38 | } 39 | } 40 | 41 | test_memory_usage_for_numeric!( 42 | test_bool: (true) == 1; 43 | test_char: ('a') == 4; 44 | test_f32: (4.2f32) == 4; 45 | test_f64: (4.2f64) == 8; 46 | test_i8: (1i8) == 1; 47 | test_i16: (1i16) == 2; 48 | test_i32: (1i32) == 4; 49 | test_i64: (1i64) == 8; 50 | test_isize: (1isize) == POINTER_BYTE_SIZE; 51 | test_u8: (1u8) == 1; 52 | test_u16: (1u16) == 2; 53 | test_u32: (1u32) == 4; 54 | test_u64: (1u64) == 8; 55 | test_usize: (1usize) == POINTER_BYTE_SIZE; 56 | ); 57 | } 58 | 59 | #[rustversion::any(stable(1.51), since(2021-02-01))] 60 | impl MemoryUsage for [T; N] 61 | where 62 | T: MemoryUsage, 63 | { 64 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 65 | mem::size_of_val(self) 66 | + self 67 | .iter() 68 | .map(|value| value.size_of_val(tracker) - mem::size_of_val(value)) 69 | .sum::() 70 | } 71 | } 72 | 73 | #[rustversion::any(stable(1.51), since(2021-02-01))] 74 | #[cfg(test)] 75 | mod test_array_types { 76 | use super::*; 77 | 78 | #[test] 79 | fn test_array() { 80 | let array: [i16; 0] = [0; 0]; 81 | assert_size_of_val_eq!(array, 2 * 0); 82 | 83 | let array: [i16; 1] = [0; 1]; 84 | assert_size_of_val_eq!(array, 2 * 1); 85 | 86 | let array: [i16; 2] = [0; 2]; 87 | assert_size_of_val_eq!(array, 2 * 2); 88 | 89 | let array: [i16; 3] = [0; 3]; 90 | assert_size_of_val_eq!(array, 2 * 3); 91 | 92 | let array: [[i16; 3]; 5] = [[0; 3]; 5]; 93 | assert_size_of_val_eq!(array, 2 * 3 * 5); 94 | } 95 | } 96 | 97 | impl MemoryUsage for () { 98 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 99 | 0 100 | } 101 | } 102 | 103 | macro_rules! impl_memory_usage_for_tuple { 104 | ( $first_type:ident $(,)* ) => {}; 105 | 106 | ( $first_type:ident $( , $types:ident )+ $(,)* ) => { 107 | impl< $first_type $( , $types )+ > MemoryUsage for ( $first_type $( , $types )+ ) 108 | where 109 | $first_type: MemoryUsage, 110 | $( $types: MemoryUsage ),* 111 | { 112 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 113 | #[allow(non_snake_case)] 114 | let ( $first_type $( , $types )+ ) = self; 115 | 116 | mem::size_of_val(self) 117 | + $first_type.size_of_val(tracker) - mem::size_of_val($first_type) 118 | $( + $types.size_of_val(tracker) - mem::size_of_val($types) )+ 119 | } 120 | } 121 | 122 | impl_memory_usage_for_tuple!( $( $types ),+ ); 123 | }; 124 | } 125 | 126 | impl_memory_usage_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L); 127 | 128 | #[cfg(test)] 129 | mod test_tuple_types { 130 | use super::*; 131 | 132 | #[test] 133 | fn test_empty_tuple() { 134 | let empty = (); 135 | assert_size_of_val_eq!(empty, 0); 136 | } 137 | 138 | #[test] 139 | fn test_tuple() { 140 | let tuple: (i8, i8) = (1, 2); 141 | assert_size_of_val_eq!(tuple, 1 /* i8 */ + 1 /* i8 */); 142 | 143 | let tuple: (i8, i16) = (1, 2); 144 | assert_size_of_val_eq!(tuple, 1 /* i8 */ + 2 /* i16 */ + 1 /* padding */); 145 | 146 | let tuple: (i8, i16, i32) = (1, 2, 3); 147 | assert_size_of_val_eq!( 148 | tuple, 149 | 1 /* i8 */ + 2 /* i16 */ + 4 /* i32 */ + 1, /* padding */ 150 | ); 151 | 152 | let tuple: (i32, i32) = (1, 2); 153 | assert_size_of_val_eq!(tuple, 4 /* i32 */ + 4 /* i32 */); 154 | 155 | let tuple: (&str, &str) = ("", ""); 156 | assert_size_of_val_eq!( 157 | tuple, 158 | 2 * POINTER_BYTE_SIZE + 1 * 0 /* str */ + 2 * POINTER_BYTE_SIZE + 1 * 0, /* str */ 159 | ); 160 | 161 | let tuple: (&str, &str) = ("a", "bc"); 162 | assert_size_of_val_eq!( 163 | tuple, 164 | 2 * POINTER_BYTE_SIZE + 1 * 1 /* str */ + 2 * POINTER_BYTE_SIZE + 1 * 2, /* str */ 165 | ); 166 | 167 | let tuple: (&str, (i64, i64, i8)) = ("abc", (1, 2, 3)); 168 | assert_size_of_val_eq!( 169 | tuple, 170 | 2 * POINTER_BYTE_SIZE + 1 * 3 /* str */ + 8 /* i64 */ + 8 /* i64 */ + 1 /* i8 */ + 7, /* padding */ 171 | ); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/ptr.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::assert_size_of_val_eq; 3 | use crate::{MemoryUsage, MemoryUsageTracker, POINTER_BYTE_SIZE}; 4 | use std::mem; 5 | use std::ptr::NonNull; 6 | 7 | impl MemoryUsage for *const T { 8 | fn size_of_val(&self, _tracker: &mut dyn MemoryUsageTracker) -> usize { 9 | POINTER_BYTE_SIZE 10 | } 11 | } 12 | 13 | impl MemoryUsage for *mut T { 14 | fn size_of_val(&self, _tracker: &mut dyn MemoryUsageTracker) -> usize { 15 | POINTER_BYTE_SIZE 16 | } 17 | } 18 | 19 | impl MemoryUsage for NonNull { 20 | fn size_of_val(&self, _tracker: &mut dyn MemoryUsageTracker) -> usize { 21 | POINTER_BYTE_SIZE 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod test_pointer_types { 27 | use super::*; 28 | 29 | #[test] 30 | fn test_pointer() { 31 | let x = 1i8; 32 | let ptr = &x as *const _; 33 | assert_size_of_val_eq!(ptr, POINTER_BYTE_SIZE); 34 | } 35 | 36 | #[test] 37 | fn test_mutable_pointer() { 38 | let mut x = 1i8; 39 | let ptr = &mut x as *mut _; 40 | assert_size_of_val_eq!(ptr, POINTER_BYTE_SIZE); 41 | } 42 | 43 | #[test] 44 | fn test_nonnull_pointer() { 45 | let mut x = 1i8; 46 | let ptr = NonNull::new(&mut x as *mut _).unwrap(); 47 | assert_size_of_val_eq!(ptr, POINTER_BYTE_SIZE); 48 | } 49 | } 50 | 51 | impl MemoryUsage for &T 52 | where 53 | T: MemoryUsage, 54 | { 55 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 56 | mem::size_of_val(self) 57 | + if tracker.track(*self as *const T as *const ()) { 58 | (*self).size_of_val(tracker) 59 | } else { 60 | 0 61 | } 62 | } 63 | } 64 | 65 | impl MemoryUsage for &mut T 66 | where 67 | T: MemoryUsage, 68 | { 69 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 70 | mem::size_of_val(self) 71 | + if tracker.track(*self as *const T as *const ()) { 72 | MemoryUsage::size_of_val(*self, tracker) 73 | } else { 74 | 0 75 | } 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod test_reference_types { 81 | use super::*; 82 | 83 | #[test] 84 | fn test_reference() { 85 | assert_size_of_val_eq!(&1i8, POINTER_BYTE_SIZE + 1); 86 | assert_size_of_val_eq!(&1i64, POINTER_BYTE_SIZE + 8); 87 | } 88 | 89 | #[test] 90 | fn test_mutable_reference() { 91 | assert_size_of_val_eq!(&mut 1i8, POINTER_BYTE_SIZE + 1); 92 | assert_size_of_val_eq!(&mut 1i64, POINTER_BYTE_SIZE + 8); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/remote/indexmap.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use indexmap::IndexMap; 5 | use std::mem; 6 | 7 | impl MemoryUsage for IndexMap 8 | where 9 | V: MemoryUsage, 10 | { 11 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 12 | mem::size_of_val(self) 13 | + self 14 | .into_iter() 15 | .map(|(_key, value)| value.size_of_val(tracker)) 16 | .sum::() 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod test_indexmap_types { 22 | use super::*; 23 | 24 | #[test] 25 | fn test_indexmap() { 26 | let mut map = IndexMap::with_capacity(1); 27 | let empty_map_size = mem::size_of_val(&map); 28 | 29 | let one = 1i8; 30 | map.insert("a", &one); 31 | assert_size_of_val_eq!(map, empty_map_size + (POINTER_BYTE_SIZE + 1) * 1); 32 | 33 | map.insert("b", &2i8); 34 | map.insert("c", &3i8); 35 | assert_size_of_val_eq!(map, empty_map_size + (POINTER_BYTE_SIZE + 1) * 3); 36 | 37 | map.insert("d", &one); 38 | assert_size_of_val_eq!( 39 | map, 40 | empty_map_size + (POINTER_BYTE_SIZE + 1) * 3 + POINTER_BYTE_SIZE + 0 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/remote/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains implementation of `MemoryUsage` for very common external 2 | //! crates. Each of them must be enable with the `enable-` 3 | //! feature. 4 | 5 | #[cfg(feature = "enable-indexmap")] 6 | mod indexmap; 7 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/result.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::assert_size_of_val_eq; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | 6 | impl MemoryUsage for Result 7 | where 8 | T: MemoryUsage, 9 | E: MemoryUsage, 10 | { 11 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 12 | mem::size_of_val(self) 13 | + match self.as_ref() { 14 | Ok(value) => value.size_of_val(tracker), 15 | Err(value) => value.size_of_val(tracker), 16 | } 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod test_result_types { 22 | use super::*; 23 | 24 | #[test] 25 | fn test_result() { 26 | let result: Result = Err(2); 27 | assert_size_of_val_eq!(result, 1 /* variant */ + 3 /* padding */ + 2 /* i16 */); 28 | 29 | let result: Result = Ok(1); 30 | assert_size_of_val_eq!(result, 1 /* variant */ + 3 /* padding */ + 1 /* i8 */); 31 | 32 | let result: Result = Ok(1); 33 | assert_size_of_val_eq!(result, 1 /* variant */ + 7 /* padding */ + 4 /* i32 */); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/slice.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | 6 | impl MemoryUsage for [T] 7 | where 8 | T: MemoryUsage, 9 | { 10 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 11 | mem::size_of_val(self) 12 | + self 13 | .iter() 14 | .map(|value| value.size_of_val(tracker) - mem::size_of_val(value)) 15 | .sum::() 16 | } 17 | } 18 | 19 | impl MemoryUsage for &[T] 20 | where 21 | T: MemoryUsage, 22 | { 23 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 24 | mem::size_of_val(self) 25 | + if tracker.track(*self as *const [T] as *const ()) { 26 | MemoryUsage::size_of_val(*self, tracker) 27 | } else { 28 | 0 29 | } 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod test_slice_types { 35 | use super::*; 36 | 37 | #[rustversion::any(stable(1.51), since(2021-02-01))] 38 | #[test] 39 | fn test_slice() { 40 | assert_size_of_val_eq!([1i16], 2 * 1); 41 | assert_size_of_val_eq!([1i16, 2], 2 * 2); 42 | assert_size_of_val_eq!([1i16, 2, 3], 2 * 3); 43 | } 44 | 45 | #[test] 46 | fn test_slice_dynamically_sized() { 47 | let slice: &[i16] = &[]; 48 | assert_size_of_val_eq!(slice, 2 * POINTER_BYTE_SIZE + 2 * 0); 49 | 50 | let slice: &[i16] = &[1]; 51 | assert_size_of_val_eq!(slice, 2 * POINTER_BYTE_SIZE + 2 * 1); 52 | 53 | let slice: &[i16] = &[1, 2]; 54 | assert_size_of_val_eq!(slice, 2 * POINTER_BYTE_SIZE + 2 * 2); 55 | 56 | let slice: &[i16] = &[1, 2, 3]; 57 | assert_size_of_val_eq!(slice, 2 * POINTER_BYTE_SIZE + 2 * 3); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/string.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | 6 | impl MemoryUsage for &str { 7 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 8 | mem::size_of_val(self) + self.as_bytes().size_of_val(tracker) 9 | } 10 | } 11 | 12 | impl MemoryUsage for String { 13 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 14 | self.as_str().size_of_val(tracker) 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod test_string_types { 20 | use super::*; 21 | 22 | #[test] 23 | fn test_str() { 24 | let string: &str = ""; 25 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 0); 26 | 27 | let string: &str = "a"; 28 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 1); 29 | 30 | let string: &str = "ab"; 31 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 2); 32 | 33 | let string: &str = "abc"; 34 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 3); 35 | 36 | let string: &str = "…"; 37 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 3); 38 | } 39 | 40 | #[test] 41 | fn test_string() { 42 | let string: String = "".to_string(); 43 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 0); 44 | 45 | let string: String = "a".to_string(); 46 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 1); 47 | 48 | let string: String = "ab".to_string(); 49 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 2); 50 | 51 | let string: String = "abc".to_string(); 52 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 3); 53 | 54 | let string: String = "…".to_string(); 55 | assert_size_of_val_eq!(string, 2 * POINTER_BYTE_SIZE + 1 * 3); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/loupe/src/memory_usage/sync.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::{assert_size_of_val_eq, POINTER_BYTE_SIZE}; 3 | use crate::{MemoryUsage, MemoryUsageTracker}; 4 | use std::mem; 5 | use std::sync::{ 6 | atomic::{ 7 | AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, 8 | AtomicU64, AtomicU8, AtomicUsize, 9 | }, 10 | Arc, Mutex, RwLock, Weak, 11 | }; 12 | 13 | macro_rules! impl_memory_usage_for_numeric { 14 | ( $type:ty ) => { 15 | impl MemoryUsage for $type { 16 | fn size_of_val(&self, _: &mut dyn MemoryUsageTracker) -> usize { 17 | mem::size_of_val(self) 18 | } 19 | } 20 | }; 21 | 22 | ( $( $type:ty ),+ $(,)* ) => { 23 | $( impl_memory_usage_for_numeric!( $type ); )+ 24 | } 25 | } 26 | 27 | impl_memory_usage_for_numeric!( 28 | AtomicBool, 29 | AtomicI8, 30 | AtomicI16, 31 | AtomicI32, 32 | AtomicI64, 33 | AtomicIsize, 34 | AtomicU8, 35 | AtomicU16, 36 | AtomicU32, 37 | AtomicU64, 38 | AtomicUsize, 39 | ); 40 | 41 | impl MemoryUsage for Arc 42 | where 43 | T: MemoryUsage + ?Sized, 44 | { 45 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 46 | mem::size_of_val(self) 47 | + if tracker.track(Arc::as_ptr(self) as *const ()) { 48 | self.as_ref().size_of_val(tracker) 49 | } else { 50 | 0 51 | } 52 | } 53 | } 54 | 55 | impl MemoryUsage for Weak 56 | where 57 | T: MemoryUsage + ?Sized, 58 | { 59 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 60 | mem::size_of_val(self) 61 | + if tracker.track(Weak::as_ptr(self) as *const ()) { 62 | Weak::upgrade(self) 63 | .map(|arc| arc.as_ref().size_of_val(tracker)) 64 | .unwrap_or(0) 65 | } else { 66 | 0 67 | } 68 | } 69 | } 70 | 71 | impl MemoryUsage for Mutex 72 | where 73 | T: MemoryUsage + ?Sized, 74 | { 75 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 76 | mem::size_of_val(self) + self.lock().unwrap().size_of_val(tracker) 77 | } 78 | } 79 | 80 | impl MemoryUsage for RwLock 81 | where 82 | T: MemoryUsage + ?Sized, 83 | { 84 | fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { 85 | mem::size_of_val(self) + self.read().unwrap().size_of_val(tracker) 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod test_sync_types { 91 | use super::*; 92 | 93 | macro_rules! test_memory_usage_for_numeric { 94 | ($test_name:ident: ($value:expr) == $expected:expr) => { 95 | #[test] 96 | fn $test_name() { 97 | assert_size_of_val_eq!($value, $expected); 98 | } 99 | }; 100 | 101 | ( $( $test_name:ident: ($value:expr) == $expected:expr );+ $(;)* ) => { 102 | $( test_memory_usage_for_numeric!( $test_name: ($value) == $expected); )+ 103 | } 104 | } 105 | 106 | test_memory_usage_for_numeric!( 107 | test_atomic_bool: (AtomicBool::new(true)) == 1; 108 | test_atomic_i8: (AtomicI8::new(1i8)) == 1; 109 | test_atomic_i16: (AtomicI16::new(1i16)) == 2; 110 | test_atomic_i32: (AtomicI32::new(1i32)) == 4; 111 | test_atomic_i64: (AtomicI64::new(1i64)) == 8; 112 | test_atomic_isize: (AtomicIsize::new(1isize)) == POINTER_BYTE_SIZE; 113 | test_atomic_u8: (AtomicU8::new(1u8)) == 1; 114 | test_atomic_u16: (AtomicU16::new(1u16)) == 2; 115 | test_atomic_u32: (AtomicU32::new(1u32)) == 4; 116 | test_atomic_u64: (AtomicU64::new(1u64)) == 8; 117 | test_atomic_usize: (AtomicUsize::new(1usize)) == POINTER_BYTE_SIZE; 118 | ); 119 | 120 | #[test] 121 | fn test_arc() { 122 | let empty_arc_size = mem::size_of_val(&Arc::new(())); 123 | 124 | let arc: Arc = Arc::new(1); 125 | assert_size_of_val_eq!(arc, empty_arc_size + 4); 126 | 127 | let arc: Arc> = Arc::new(Some(1)); 128 | assert_size_of_val_eq!(arc, empty_arc_size + POINTER_BYTE_SIZE + 4); 129 | } 130 | 131 | #[test] 132 | fn test_weak() { 133 | let empty_weak_size = mem::size_of_val(&Arc::downgrade(&Arc::new(()))); 134 | 135 | let arc: Arc = Arc::new(1); 136 | let weak: Weak = Arc::downgrade(&arc); 137 | assert_size_of_val_eq!(weak, empty_weak_size + 4); 138 | 139 | let arc: Arc> = Arc::new(Some(1)); 140 | let weak: Weak> = Arc::downgrade(&arc); 141 | assert_size_of_val_eq!(weak, empty_weak_size + POINTER_BYTE_SIZE + 4); 142 | 143 | let weak: Weak = { 144 | let arc: Arc = Arc::new(5); 145 | Arc::downgrade(&arc) 146 | }; 147 | assert_size_of_val_eq!(weak, empty_weak_size); 148 | } 149 | 150 | #[test] 151 | fn test_mutex() { 152 | let empty_mutex_size = mem::size_of_val(&Mutex::new(())); 153 | 154 | let mutex: Mutex = Mutex::new(1); 155 | assert_size_of_val_eq!(mutex, empty_mutex_size + 4); 156 | 157 | let mutex: Mutex> = Mutex::new(Some(1)); 158 | assert_size_of_val_eq!(mutex, empty_mutex_size + 2 * POINTER_BYTE_SIZE + 4); 159 | } 160 | 161 | #[test] 162 | fn test_rwlock() { 163 | let empty_rwlock_size = mem::size_of_val(&RwLock::new(())); 164 | 165 | let rwlock: RwLock = RwLock::new(1); 166 | assert_size_of_val_eq!(rwlock, empty_rwlock_size + 4); 167 | 168 | let rwlock: RwLock> = RwLock::new(Some(1)); 169 | assert_size_of_val_eq!(rwlock, empty_rwlock_size + 2 * POINTER_BYTE_SIZE + 4); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /crates/loupe/tests/derive.rs: -------------------------------------------------------------------------------- 1 | use loupe::{size_of_val, MemoryUsage, POINTER_BYTE_SIZE}; 2 | 3 | macro_rules! assert_size_of_val_eq { 4 | ($expected:expr, $value:expr) => { 5 | assert_eq!($expected, size_of_val(&$value),); 6 | }; 7 | } 8 | 9 | #[test] 10 | fn test_struct_flat() { 11 | #[derive(MemoryUsage)] 12 | struct Point { 13 | x: i32, 14 | y: i32, 15 | } 16 | 17 | assert_size_of_val_eq!(8, Point { x: 1, y: 2 }); 18 | } 19 | 20 | #[test] 21 | fn test_struct_field_ignored() { 22 | #[derive(MemoryUsage)] 23 | struct S { 24 | x: Vec, 25 | y: Vec, 26 | } 27 | 28 | #[derive(MemoryUsage)] 29 | #[allow(unused)] 30 | struct T { 31 | x: Vec, 32 | #[loupe(skip)] 33 | y: Vec, 34 | } 35 | 36 | assert_size_of_val_eq!( 37 | 72, 38 | S { 39 | x: vec![1, 2, 3], 40 | y: vec![1, 2, 3] 41 | } 42 | ); 43 | assert_size_of_val_eq!( 44 | 60, 45 | T { 46 | x: vec![1, 2, 3], 47 | y: vec![1, 2, 3] 48 | } 49 | ); 50 | } 51 | 52 | #[test] 53 | fn test_tuple() { 54 | #[derive(MemoryUsage)] 55 | struct Tuple(i32, i32); 56 | 57 | assert_size_of_val_eq!(8, Tuple(1, 2)); 58 | 59 | #[derive(MemoryUsage)] 60 | #[repr(transparent)] 61 | struct Ptr(*const usize); 62 | 63 | assert_size_of_val_eq!(8, Ptr(&1)); 64 | } 65 | 66 | #[test] 67 | fn test_struct_with_generic() { 68 | #[derive(MemoryUsage)] 69 | struct Generic 70 | where 71 | T: MemoryUsage, 72 | { 73 | x: T, 74 | y: T, 75 | } 76 | 77 | assert_size_of_val_eq!(16, Generic { x: 1i64, y: 2i64 }); 78 | } 79 | 80 | #[test] 81 | fn test_struct_with_inlined_generic() { 82 | #[derive(MemoryUsage)] 83 | struct Generic { 84 | x: T, 85 | y: T, 86 | } 87 | 88 | assert_size_of_val_eq!(16, Generic { x: 1i64, y: 2i64 }); 89 | } 90 | 91 | #[test] 92 | fn test_struct_empty() { 93 | #[derive(MemoryUsage)] 94 | struct Empty; 95 | 96 | assert_size_of_val_eq!(0, Empty); 97 | } 98 | 99 | #[test] 100 | fn test_struct_padding() { 101 | // This struct is packed in order because 'y: i32' requires 32-bit 102 | // alignment but x and z do not. It starts with bytes 'x...yyyy' then adds 'z' in 103 | // the first place it fits producing 'xz..yyyy' and not 12 bytes 'x...yyyyz...'. 104 | #[derive(MemoryUsage)] 105 | struct Padding { 106 | x: i8, 107 | y: i32, 108 | z: i8, 109 | } 110 | 111 | assert_size_of_val_eq!(8, Padding { x: 1, y: 2, z: 3 }); 112 | } 113 | 114 | #[test] 115 | fn test_enum() { 116 | #[derive(MemoryUsage)] 117 | struct Point { 118 | x: i32, 119 | y: i32, 120 | } 121 | 122 | #[derive(MemoryUsage)] 123 | enum Things { 124 | A, 125 | B(), 126 | C(i32), 127 | D { x: i32 }, 128 | E(i32, i32), 129 | F { x: i32, y: i32 }, 130 | Points(Vec), 131 | } 132 | 133 | assert_size_of_val_eq!(24, Things::A); 134 | assert_size_of_val_eq!(24, Things::B()); 135 | assert_size_of_val_eq!(24, Things::C(1)); 136 | assert_size_of_val_eq!(24, Things::D { x: 1 }); 137 | assert_size_of_val_eq!(24, Things::E(1, 2)); 138 | assert_size_of_val_eq!(24, Things::F { x: 1, y: 2 }); 139 | 140 | assert_size_of_val_eq!(8, Point { x: 1, y: 2 }); 141 | assert_size_of_val_eq!(40, vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]); 142 | assert_size_of_val_eq!( 143 | 40, 144 | Things::Points(vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]) 145 | ); 146 | } 147 | 148 | #[test] 149 | fn test_enum_with_generic() { 150 | #[derive(MemoryUsage)] 151 | enum Generic 152 | where 153 | T: MemoryUsage, 154 | { 155 | A(T), 156 | B(T), 157 | } 158 | 159 | assert_size_of_val_eq!(16, Generic::::A(1)); 160 | assert_size_of_val_eq!(16, Generic::::B(2)); 161 | } 162 | 163 | #[test] 164 | fn test_enum_with_inlined_generic() { 165 | #[derive(MemoryUsage)] 166 | enum Generic { 167 | A(T), 168 | B(T), 169 | } 170 | 171 | assert_size_of_val_eq!(16, Generic::::A(1)); 172 | assert_size_of_val_eq!(16, Generic::::B(2)); 173 | } 174 | 175 | #[test] 176 | fn test_enum_variant_ignored() { 177 | #[derive(MemoryUsage)] 178 | struct Point { 179 | x: i32, 180 | y: i32, 181 | } 182 | 183 | #[derive(MemoryUsage)] 184 | enum E { 185 | A, 186 | Points(Vec), 187 | } 188 | 189 | assert_size_of_val_eq!(24, E::A); 190 | assert_size_of_val_eq!( 191 | 40, 192 | E::Points(vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]) 193 | ); 194 | 195 | #[derive(MemoryUsage)] 196 | #[allow(unused)] 197 | enum F { 198 | A, 199 | #[loupe(skip)] 200 | Points(Vec), 201 | } 202 | 203 | assert_size_of_val_eq!(24, F::A); 204 | assert_size_of_val_eq!( 205 | 24, 206 | F::Points(vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]) 207 | ); 208 | } 209 | 210 | #[test] 211 | fn test_ptr() { 212 | #[derive(MemoryUsage)] 213 | #[repr(C)] 214 | struct X(u8); 215 | 216 | #[derive(MemoryUsage)] 217 | #[repr(transparent)] 218 | struct P(*const X); 219 | 220 | #[derive(MemoryUsage)] 221 | struct Q { 222 | ptr: *const X, 223 | } 224 | 225 | let x = X(42); 226 | let ptr = P(&x as *const _); 227 | assert_size_of_val_eq!(POINTER_BYTE_SIZE, ptr); 228 | 229 | let ptr = Q { 230 | ptr: &x as *const _, 231 | }; 232 | assert_size_of_val_eq!(POINTER_BYTE_SIZE, ptr); 233 | } 234 | -------------------------------------------------------------------------------- /image/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmerio/loupe/c81bc30e584b4cada6242989f862b5694bbe469f/image/logo.jpg --------------------------------------------------------------------------------