datasize-0.2.13/.cargo_vcs_info.json0000644000000001460000000000100127240ustar { "git": { "sha1": "e04c3251eb5473651a0abf55c18869acaef635c1" }, "path_in_vcs": "datasize" }datasize-0.2.13/Cargo.toml0000644000000027220000000000100107240ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "datasize" version = "0.2.13" authors = ["Marc Brinkmann "] description = "A simplified heap memory size estimator" documentation = "https://docs.rs/datasize" license = "MIT OR Apache-2.0" repository = "https://github.com/casperlabs/datasize-rs" [package.metadata.docs.rs] all-features = true [dependencies.datasize_derive] version = "0.2.13" [dependencies.fake_instant] version = "0.4.0" optional = true [dependencies.futures] version = "0.3.5" optional = true [dependencies.serde] version = "1" features = ["derive"] optional = true [dependencies.smallvec] version = "1.4.2" optional = true [dependencies.tokio] version = "0.2.22" features = [ "rt-core", "sync", ] optional = true default-features = false [features] default = ["std"] detailed = [ "std", "serde", "datasize_derive/detailed", ] fake_clock-types = ["fake_instant"] futures-types = ["futures"] smallvec-types = [ "smallvec", "std", ] std = [] tokio-types = ["tokio"] datasize-0.2.13/Cargo.toml.orig000064400000000000000000000017151046102023000144060ustar 00000000000000[package] name = "datasize" version = "0.2.13" authors = [ "Marc Brinkmann " ] edition = "2018" license = "MIT OR Apache-2.0" description = "A simplified heap memory size estimator" documentation = "https://docs.rs/datasize" repository = "https://github.com/casperlabs/datasize-rs" [features] default = [ "std" ] detailed = [ "std", "serde", "datasize_derive/detailed" ] fake_clock-types = [ "fake_instant" ] futures-types = [ "futures" ] smallvec-types = [ "smallvec", "std" ] std = [] tokio-types = [ "tokio" ] [dependencies] datasize_derive = { version = "0.2.13" } fake_instant = { version = "0.4.0", optional = true } futures = { version = "0.3.5", optional = true } serde = { version = "1", optional = true, features = [ "derive" ] } smallvec = { version = "1.4.2", optional = true } tokio = { version = "0.2.22", default-features = false, optional = true, features = [ "rt-core", "sync", ] } [package.metadata.docs.rs] all-features = true datasize-0.2.13/src/fake_clock.rs000064400000000000000000000001571046102023000147340ustar 00000000000000use super::{non_dynamic_const_heap_size, DataSize}; non_dynamic_const_heap_size!(fake_instant::FakeClock, 0); datasize-0.2.13/src/futures.rs000064400000000000000000000013231046102023000143440ustar 00000000000000use super::DataSize; use core::mem::size_of; impl DataSize for futures::channel::oneshot::Sender { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } // Note: We attribute the value to be sent/received to the receiver. This is still wildly inaccurate // though. impl DataSize for futures::channel::oneshot::Receiver { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = size_of::() + 3 * size_of::(); #[inline] fn estimate_heap_size(&self) -> usize { // Missing: Lock>, approximated by three pointers each. Self::STATIC_HEAP_SIZE } } datasize-0.2.13/src/lib.rs000064400000000000000000000463601046102023000134270ustar 00000000000000//! Heap data estimator. //! //! The `datasize` crate allows estimating the amount of heap memory used by a value. It does so by //! providing or deriving an implementation of the `DataSize` trait, which knows how to calculate //! the size for many `std` types and primitives. //! //! The aim is to get a reasonable approximation of memory usage, especially with variably sized //! types like `Vec`s. While it is acceptable to be a few bytes off in some cases, any user should //! be able to easily tell whether their memory is growing linearly or logarithmically by glancing //! at the reported numbers. //! //! The crate does not take alignment or memory layouts into account, or unusual behavior or //! optimizations of allocators. It is depending entirely on the data inside the type, thus the name //! of the crate. //! //! # General usage //! //! For any type that implements `DataSize`, the `data_size` convenience function can be used to //! guess the size of its heap allocation: //! //! ```rust //! use datasize::data_size; //! //! let data: Vec = vec![1, 2, 3]; //! assert_eq!(data_size(&data), 24); //! ``` //! //! Types implementing the trait also provide two additional constants, `IS_DYNAMIC` and //! `STATIC_HEAP_SIZE`. //! //! `IS_DYNAMIC` indicates whether a value's size can change over time: //! //! ```rust //! use datasize::DataSize; //! //! // A `Vec` of any kind may have elements added or removed, so it changes size. //! assert!(Vec::::IS_DYNAMIC); //! //! // The elements of type `u64` in it are not dynamic. This allows the implementation to //! // simply estimate the size as number_of_elements * size_of::. //! assert!(!u64::IS_DYNAMIC); //! ``` //! //! Additionally, `STATIC_HEAP_SIZE` indicates the amount of heap memory a type will always use. A //! good example is a `Box` -- it will always use 8 bytes of heap memory, but not change in //! size: //! //! //! ```rust //! use datasize::DataSize; //! //! assert_eq!(Box::::STATIC_HEAP_SIZE, 8); //! assert!(!Box::::IS_DYNAMIC); //! ``` //! //! # Overriding derived data size calculation for single fields. //! //! On structs (but not enums!) the calculation for heap size can be overriden for single fields, //! which is useful when dealing with third-party crates whose fields do not implement `DataSize` by //! simply annotating it with `#[data_size(with = ...)]` and pointing to a `Fn(T) -> usize` //! function: //! //! ```rust //! use datasize::DataSize; //! //! // Let's pretend this type is from a foreign crate. //! struct ThirdPartyType; //! //! fn estimate_third_party_type(value: &Vec) -> usize { //! // We assume every item is 512 bytes in heap size. //! value.len() * 512 //! } //! //! #[derive(DataSize)] //! struct MyStruct { //! items: Vec, //! #[data_size(with = estimate_third_party_type)] //! other_stuff: Vec, //! } //! ``` //! //! This automatically marks the whole struct as always dynamic, so the custom estimation function //! is called every time `MyStruct` is sized. //! //! # Implementing `DataSize` for custom types //! //! The `DataSize` trait can be implemented for custom types manually: //! //! ```rust //! # use datasize::{DataSize, data_size}; //! struct MyType { //! items: Vec, //! flag: bool, //! counter: Box, //! } //! //! impl DataSize for MyType { //! // `MyType` contains a `Vec`, so `IS_DYNAMIC` is set to true. //! const IS_DYNAMIC: bool = true; //! //! // The only always present heap item is the `counter` value, which is 8 bytes. //! const STATIC_HEAP_SIZE: usize = 8; //! //! #[inline] //! fn estimate_heap_size(&self) -> usize { //! // We can be lazy here and delegate to all the existing implementations: //! data_size(&self.items) + data_size(&self.flag) + data_size(&self.counter) //! } //! } //! //! let my_data = MyType { //! items: vec![1, 2, 3], //! flag: true, //! counter: Box::new(42), //! }; //! //! // Three i64 and one u64 on the heap sum up to 32 bytes: //! assert_eq!(data_size(&my_data), 32); //! ``` //! //! Since implementing this for `struct` types is cumbersome and repetitive, the crate provides a //! `DataSize` macro for convenience: //! //! ``` //! # use datasize::{DataSize, data_size}; //! // Equivalent to the manual implementation above: //! #[derive(DataSize)] //! struct MyType { //! items: Vec, //! flag: bool, //! counter: Box, //! } //! # let my_data = MyType { //! # items: vec![1, 2, 3], //! # flag: true, //! # counter: Box::new(42), //! # }; //! # assert_eq!(data_size(&my_data), 32); //! ``` //! //! See the `DataSize` macro documentation in the `datasize_derive` crate for details. //! //! ## Performance considerations //! //! Determining the full size of data can be quite expensive, especially if multiple nested levels //! of dynamic types are used. The crate uses `IS_DYNAMIC` and `STATIC_HEAP_SIZE` to optimize when //! it can, so in many cases not every element of a vector needs to be checked individually. //! //! However, if the contained types are dynamic, every element must (and will) be checked, so keep //! this in mind when performance is an issue. //! //! ## Handlings references, `Arc`s and similar types //! //! Any reference will be counted as having a data size of 0, as it does not own the value. There //! are some special reference-like types like `Arc`, which are discussed below. //! //! ### `Arc` and `Rc` //! //! Currently `Arc`s are not supported. A planned development is to allow users to mark an instance //! of an `Arc` as "primary" and have its heap memory usage counted, but currently this is not //! implemented. //! //! Any `Arc` will be estimated to have a heap size of `0`, to avoid cycles resulting in infinite //! loops. //! //! The `Rc` type is handled in the same manner. //! //! ## Additional types //! //! Some additional types from external crates are available behind feature flags. //! //! * `fake_clock-types`: Support for the `fake_instant::FakeClock` type. //! * `futures-types`: Some types from the `futures` crate. //! * `smallvec-types`: Support for the `smallvec::SmallVec` type. //! * `tokio-types`: Some types from the `tokio` crate. //! //! ## `no_std` support //! //! Although slightly paradoxical due to the fact that without `std` or at least `alloc` there won't //! be any heap in most cases, the crate supports a `no_std` environment. Disabling the "std" //! feature (by disabling default features) will produce a version of the crate that does not rely //! on the standard library. This can be used to derive the `DataSize` trait for types without //! boilerplate, even though their heap size will usually be 0. //! //! ## Known issues //! //! The derive macro currently does not support generic structs with inline type bounds, e.g. //! //! ```ignore //! struct Foo { ... } //! ``` //! //! This can be worked around by using an equivalent `where` clause: //! //! ```ignore //! struct Foo //! where T: Copy //! { ... } //! ``` #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::assertions_on_constants)] #[cfg(feature = "fake_clock-types")] mod fake_clock; #[cfg(feature = "futures-types")] mod futures; #[cfg(feature = "smallvec-types")] mod smallvec; #[cfg(feature = "std")] mod std; #[cfg(feature = "tokio-types")] mod tokio; pub use datasize_derive::DataSize; /// A `const fn` variant of the `min` function. pub const fn min(a: usize, b: usize) -> usize { [a, b][(a > b) as usize] } /// Indicates that a type knows how to approximate its memory usage. pub trait DataSize { /// If `true`, the type has a heap size that can vary at runtime, depending on the actual value. const IS_DYNAMIC: bool; /// The amount of space a value of the type _always_ occupies. If `IS_DYNAMIC` is false, this is /// the total amount of heap memory occupied by the value. Otherwise this is a lower bound. const STATIC_HEAP_SIZE: usize; /// Estimates the size of heap memory taken up by this value. /// /// Does not include data on the stack, which is usually determined using `mem::size_of`. fn estimate_heap_size(&self) -> usize; #[cfg(feature = "detailed")] /// Create a tree of memory estimations. /// /// Similar to `estimate_heap_size`, but the returned value is a tree that typically reports /// memory used by structs individually. /// /// Requires the `detailed` feature to be enabled. #[inline] fn estimate_detailed_heap_size(&self) -> MemUsageNode { MemUsageNode::Size(self.estimate_heap_size()) } } #[cfg(feature = "detailed")] /// A node in a memory reporting tree. #[derive(Debug, serde::Serialize, PartialEq)] pub enum MemUsageNode { Size(usize), Detailed(::std::collections::HashMap<&'static str, MemUsageNode>), } #[cfg(feature = "detailed")] impl MemUsageNode { /// Calculate the total memory usage given by detailed estimate #[inline] pub fn total(&self) -> usize { match self { MemUsageNode::Size(sz) => *sz, MemUsageNode::Detailed(members) => members.values().map(MemUsageNode::total).sum(), } } } /// Estimates allocated heap data from data of value. /// /// Checks if `T` is dynamic; if it is not, returns `T::STATIC_HEAP_SIZE`. Otherwise delegates to /// `T::estimate_heap_size`. #[inline] pub fn data_size(value: &T) -> usize where T: DataSize, { value.estimate_heap_size() } #[cfg(feature = "detailed")] /// Estimates allocated heap data from data of value. #[inline] pub fn data_size_detailed(value: &T) -> MemUsageNode where T: DataSize, { value.estimate_detailed_heap_size() } /// Helper macro to define a heap size for one or more non-dynamic types. #[macro_export] macro_rules! non_dynamic_const_heap_size { ($($ty:ty)*, $sz:expr) => { $(impl DataSize for $ty { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = $sz; #[inline] fn estimate_heap_size(&self) -> usize { $sz } })* }; } // Hack to allow `+` to be used to join macro arguments. macro_rules! strip_plus { (+ $($rest: tt)*) => { $($rest)* } } macro_rules! tuple_heap_size { ($($n:tt $name:ident);+) => { impl<$($name),*> DataSize for ($($name),*) where $($name: DataSize),* { const IS_DYNAMIC: bool = $($name::IS_DYNAMIC)|*; const STATIC_HEAP_SIZE: usize = strip_plus!($(+ $name::STATIC_HEAP_SIZE)+); #[inline] fn estimate_heap_size(&self) -> usize { strip_plus!($(+ self.$n.estimate_heap_size())+) } } }; } macro_rules! array_heap_size { ($($n:tt)+) => { $( impl DataSize for [T; $n] where T: DataSize, { const IS_DYNAMIC: bool = T::IS_DYNAMIC; const STATIC_HEAP_SIZE: usize = T::STATIC_HEAP_SIZE * $n; #[inline] fn estimate_heap_size(&self) -> usize { if T::IS_DYNAMIC { (&self[..]).iter().map(DataSize::estimate_heap_size).sum() } else { T::STATIC_HEAP_SIZE * $n } } } )* }; } // Primitives non_dynamic_const_heap_size!(() u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize bool char f32 f64, 0); // Assorted heapless `core` types non_dynamic_const_heap_size!(core::time::Duration, 0); tuple_heap_size!(0 T0; 1 T1); tuple_heap_size!(0 T0; 1 T1; 2 T2); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12; 13 T13); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12; 13 T13; 14 T14); tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12; 13 T13; 14 T14; 15 T15); impl DataSize for (T0,) where T0: DataSize, { const IS_DYNAMIC: bool = T0::IS_DYNAMIC; const STATIC_HEAP_SIZE: usize = T0::STATIC_HEAP_SIZE; #[inline] fn estimate_heap_size(&self) -> usize { self.0.estimate_heap_size() } } array_heap_size!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 128 192 256 384 512 1024 2048 4096 8192 16384 1048576 2097152 3145728 4194304); // REFERENCES impl DataSize for &T { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } impl DataSize for &mut T { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } // COMMONLY USED NON-PRIMITIVE TYPES impl DataSize for Option where T: DataSize, { // Options are only not dynamic if their type has no heap data at all and is not dynamic. const IS_DYNAMIC: bool = (T::IS_DYNAMIC || T::STATIC_HEAP_SIZE > 0); const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { match self { Some(val) => data_size(val), None => 0, } } } impl DataSize for Result where T: DataSize, E: DataSize, { // Results are only not dynamic if their types have no heap data at all and are not dynamic. const IS_DYNAMIC: bool = (T::IS_DYNAMIC || E::IS_DYNAMIC || (T::STATIC_HEAP_SIZE != E::STATIC_HEAP_SIZE)); const STATIC_HEAP_SIZE: usize = min(T::STATIC_HEAP_SIZE, E::STATIC_HEAP_SIZE); #[inline] fn estimate_heap_size(&self) -> usize { match self { Ok(val) => data_size(val), Err(err) => data_size(err), } } } impl DataSize for core::marker::PhantomData { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } impl DataSize for core::ops::Range { const IS_DYNAMIC: bool = T::IS_DYNAMIC; const STATIC_HEAP_SIZE: usize = 2 * T::STATIC_HEAP_SIZE; #[inline] fn estimate_heap_size(&self) -> usize { self.start.estimate_heap_size() + self.end.estimate_heap_size() } } #[cfg(test)] mod tests { use crate as datasize; // Required for the derive macro. use crate::{data_size, DataSize}; #[test] fn test_for_simple_builtin_types() { // We only sample some, as they are all macro generated. assert_eq!(1u8.estimate_heap_size(), 0); assert_eq!(1u16.estimate_heap_size(), 0); } #[test] fn test_newtype_struct() { #[derive(DataSize)] struct Foo(u32); assert!(!Foo::IS_DYNAMIC); assert_eq!(Foo::STATIC_HEAP_SIZE, 0); assert_eq!(data_size(&Foo(123)), 0); } #[test] fn test_tuple_struct() { #[derive(DataSize)] struct Foo(u32, u8); assert!(!Foo::IS_DYNAMIC); assert_eq!(Foo::STATIC_HEAP_SIZE, 0); assert_eq!(data_size(&Foo(123, 45)), 0); } #[test] fn test_tuple_with_one_element() { type Foo = (u32,); assert!(!Foo::IS_DYNAMIC); assert_eq!(Foo::STATIC_HEAP_SIZE, 0); let foo: Foo = (456,); assert_eq!(data_size(&foo), 0); } #[test] fn test_result() { assert_eq!(Result::::STATIC_HEAP_SIZE, 0); assert!(!Result::::IS_DYNAMIC); assert_eq!(Result::::STATIC_HEAP_SIZE, 0); assert!(!Result::::IS_DYNAMIC); assert_eq!(Result::>::STATIC_HEAP_SIZE, 0); assert!(Result::>::IS_DYNAMIC); assert_eq!(Result::, u16>::STATIC_HEAP_SIZE, 0); assert!(Result::, u16>::IS_DYNAMIC); assert_eq!(Result::, Box>::STATIC_HEAP_SIZE, 1); assert!(Result::, Box>::IS_DYNAMIC); assert_eq!(Result::, Box>::STATIC_HEAP_SIZE, 2); assert!(!Result::, Box>::IS_DYNAMIC); assert_eq!(Result::>::STATIC_HEAP_SIZE, 0); assert!(Result::>::IS_DYNAMIC); assert_eq!(Result::, u16>::STATIC_HEAP_SIZE, 0); assert!(Result::, u16>::IS_DYNAMIC); assert_eq!(Result::, Vec>::STATIC_HEAP_SIZE, 0); assert!(Result::, Vec>::IS_DYNAMIC); } #[test] fn test_empty_struct() { #[derive(DataSize)] struct Foo {} #[derive(DataSize)] struct Bar; assert!(!Foo::IS_DYNAMIC); assert!(!Bar::IS_DYNAMIC); assert_eq!(Foo::STATIC_HEAP_SIZE, 0); assert_eq!(Bar::STATIC_HEAP_SIZE, 0); assert_eq!(data_size(&Foo {}), 0); assert_eq!(data_size(&Bar), 0); } #[test] fn test_empty_enum() { #[derive(DataSize)] enum Foo {} assert!(!Foo::IS_DYNAMIC); assert_eq!(Foo::STATIC_HEAP_SIZE, 0); // We cannot instantiate empty enums. } #[test] fn macro_does_not_panic_on_foreign_attributes() { #[derive(DataSize)] /// This docstring shows up as `#[doc = ""]`... struct Foo { /// This docstring shows up as `#[doc = ""]`... dummy: u8, } } // TODO: This does not work, the equivalent should be constructed using `trybuild`. // #[test] // #[should_panic = "unexpected datasize attribute"] // fn macro_panics_on_invalid_data_size_attribute() { // #[derive(DataSize)] // /// This docstring shows up as `#[doc = ""]`... // struct Foo { // #[data_size(invalid)] // /// This docstring shows up as `#[doc = ""]`... // dummy: u8, // } // } #[test] fn keeps_where_clauses_on_structs() { #[allow(dead_code)] #[derive(DataSize)] struct Foo where T: Copy, { field: T, } } #[test] fn keeps_where_clauses_on_enums() { #[allow(dead_code)] #[derive(DataSize)] enum Foo where T: Copy, { Value(T), } } #[test] fn use_with_annotation() { fn ds_for_field_b(value: &u32) -> usize { assert_eq!(*value, 2); // set in the example 1234 } #[derive(DataSize)] struct Foo { field_a: u32, #[data_size(with = ds_for_field_b)] field_b: u32, field_c: u32, } let value = Foo { field_a: 1, field_b: 2, field_c: 3, }; assert_eq!(value.estimate_heap_size(), 1234); } } datasize-0.2.13/src/smallvec.rs000064400000000000000000000012661046102023000144630ustar 00000000000000use super::DataSize; use core::mem::size_of; impl DataSize for smallvec::SmallVec where A: smallvec::Array, A::Item: DataSize, { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { if !self.spilled() { return 0; } // At this point, we're very similar to a regular `Vec`. let sz_base = self.capacity() * size_of::(); let sz_used = if A::Item::IS_DYNAMIC { self.iter().map(DataSize::estimate_heap_size).sum() } else { self.len() * A::Item::STATIC_HEAP_SIZE }; sz_base + sz_used } } datasize-0.2.13/src/std.rs000064400000000000000000000333041046102023000134450ustar 00000000000000use super::{data_size, non_dynamic_const_heap_size, DataSize}; use core::mem::size_of; use std::{borrow::Cow, boxed::Box, string::String, vec::Vec}; non_dynamic_const_heap_size!( std::net::Ipv4Addr std::net::Ipv6Addr std::net::SocketAddrV4 std::net::SocketAddrV6 std::net::IpAddr std::net::SocketAddr std::time::Instant std::time::SystemTime, 0 ); impl DataSize for Box where T: DataSize, { const IS_DYNAMIC: bool = T::IS_DYNAMIC; const STATIC_HEAP_SIZE: usize = size_of::(); #[inline] fn estimate_heap_size(&self) -> usize { // Total size is the struct itself + its children. size_of::() + data_size::(self) } } impl<'a, T> DataSize for Cow<'a, T> where T: 'a + ToOwned + ?Sized, ::Owned: DataSize, { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { match self { Cow::Borrowed(_) => 0, Cow::Owned(inner) => inner.estimate_heap_size(), } } } // Please see the notes in the module docs on why Arcs are not counted. impl DataSize for std::sync::Arc { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } impl DataSize for std::rc::Rc { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } // CONTAINERS impl DataSize for Vec where T: DataSize, { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { // We do not include the `STATIC_HEAP_SIZE`, since the heap data has not been allocated yet. let sz_base = self.capacity() * size_of::(); let sz_used = if T::IS_DYNAMIC { self.iter().map(DataSize::estimate_heap_size).sum() } else { self.len() * T::STATIC_HEAP_SIZE }; sz_base + sz_used } } impl DataSize for std::collections::VecDeque where T: DataSize, { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { // We can treat a `VecDeque` exactly the same as a `Vec`. let sz_base = self.capacity() * size_of::(); let sz_used = if T::IS_DYNAMIC { self.iter().map(DataSize::estimate_heap_size).sum() } else { self.len() * T::STATIC_HEAP_SIZE }; sz_base + sz_used } } impl DataSize for String { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; fn estimate_heap_size(&self) -> usize { self.capacity() } } impl DataSize for std::path::PathBuf { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { self.capacity() } } impl DataSize for std::ffi::OsString { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { self.capacity() } } impl DataSize for std::collections::BTreeMap where K: DataSize, V: DataSize, { // Approximation directly taken from // https://github.com/servo/heapsize/blob/f565dda63cc12c2a088bc9974a1b584cddec4382/src/lib.rs#L295-L306 const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { let mut size = 0; if K::IS_DYNAMIC || V::IS_DYNAMIC { for (key, value) in self.iter() { size += size_of::<(K, V)>() + key.estimate_heap_size() + value.estimate_heap_size(); } } else { size += self.len() * (size_of::<(K, V)>() + K::STATIC_HEAP_SIZE + V::STATIC_HEAP_SIZE); } size } } impl DataSize for std::collections::BTreeSet where T: DataSize, { // A BTreeSet is implemented as BTreeMap in the standard library, so we use the same // estimate as above. const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { if T::IS_DYNAMIC { self.len() * size_of::() + self.iter().map(T::estimate_heap_size).sum::() } else { self.len() * (size_of::() + T::STATIC_HEAP_SIZE) } } } fn estimate_hashbrown_rawtable(capacity: usize) -> usize { // https://github.com/rust-lang/hashbrown/blob/v0.12.3/src/raw/mod.rs#L185 let buckets = if capacity < 8 { if capacity < 4 { 4 } else { 8 } } else { (capacity * 8 / 7).next_power_of_two() }; // https://github.com/rust-lang/hashbrown/blob/v0.12.3/src/raw/mod.rs#L242 let size = size_of::(); // `Group` is u32, u64, or __m128i depending on the CPU architecture. // Return a lower bound, ignoring its constant contributions // (through ctrl_align and Group::WIDTH, at most 31 bytes). let ctrl_offset = size * buckets; // Add one byte of "control" metadata per bucket ctrl_offset + buckets } impl DataSize for std::collections::HashMap where K: DataSize, V: DataSize, { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { let size = estimate_hashbrown_rawtable::<(K, V)>(self.capacity()); if K::IS_DYNAMIC || V::IS_DYNAMIC { size + self .iter() .map(|(k, v)| k.estimate_heap_size() + v.estimate_heap_size()) .sum::() } else { size + self.len() * (K::STATIC_HEAP_SIZE + V::STATIC_HEAP_SIZE) } } } impl DataSize for std::collections::HashSet where T: DataSize, { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { // HashSet is based on HashMap let size = estimate_hashbrown_rawtable::<(T, ())>(self.capacity()); if T::IS_DYNAMIC { size + self.iter().map(T::estimate_heap_size).sum::() } else { size + self.len() * T::STATIC_HEAP_SIZE } } } #[cfg(test)] mod tests { use crate as datasize; // Required for the derive macro. use crate::{data_size, DataSize}; use std::borrow::Cow; #[test] fn test_box() { let value: Box = Box::new(1234); assert_eq!(data_size::>(&value), 8); assert_eq!(data_size(&value), 8); } #[test] fn test_option_box() { let value_none: Option> = None; let value_some: Option> = Some(Box::new(12345)); assert_eq!(data_size::>>(&value_none), 0); assert_eq!(data_size::>>(&value_some), 8); } #[test] fn test_cow() { let value: Cow<'static, str> = Cow::from("hello"); assert_eq!(data_size(&value), 0); let value_owned: Cow<'static, str> = Cow::from("hello".to_owned()); assert_eq!(data_size(&value_owned), data_size(&"hello".to_owned())); } #[test] fn test_string() { let value = "abcdef".to_string(); assert_eq!(data_size(&value), 6); } #[test] fn test_struct() { #[derive(DataSize)] struct Example { count: usize, my_data: Vec, warning: Option>, #[data_size(skip)] #[allow(dead_code)] skipped: Box, } #[derive(DataSize)] struct MyStruct { count: u64, } // Start with a small example struct. let mut ex = Example { count: 99, my_data: vec![], warning: None, skipped: Default::default(), }; // We expect a heap size of 0, as the vec is empty and no box allocated. assert_eq!(data_size(&ex), 0); // Add a `warning` will cause a heap allocation. ex.warning = Some(Box::new(12345)); assert_eq!(data_size(&ex), 4); // Let's reserve some capacity on `my_data`. ex.my_data.reserve_exact(10); assert_eq!(data_size(&ex), 4 + 10 * 8) } #[test] fn test_enum() { #[derive(DataSize)] enum Foo { Bar, Baz { boxed: Box, nonheap: u8, #[data_size(skip)] #[allow(dead_code)] _extra: Box, }, Bert(Vec, #[data_size(skip)] Vec), #[data_size(skip)] Skipped(Vec), } let bar = Foo::Bar; assert_eq!(data_size(&bar), 0); let baz = Foo::Baz { boxed: Box::new(123), nonheap: 99, _extra: Box::new(456), }; assert_eq!(data_size(&baz), 4); let bert = Foo::Bert(vec![5, 6, 7, 8, 9], vec![1, 2, 3, 4, 5]); assert_eq!(data_size(&bert), 5 * 4); let skipped = Foo::Skipped(vec![-1, 1, 99, 100]); assert_eq!(data_size(&skipped), 0); } #[test] fn test_generic_struct() { #[derive(DataSize)] struct Example { a: Option, b: Option, c: u8, } let none: Example, Box> = Example { a: None, b: None, c: 123, }; assert_eq!(data_size(&none), 0); let a: Example, Box> = Example { a: Some(Box::new(0)), b: None, c: 123, }; assert_eq!(data_size(&a), 4); let both: Example, Box> = Example { a: Some(Box::new(0)), b: Some(Box::new(0)), c: 123, }; assert_eq!(data_size(&both), 5); } #[test] fn test_enum_variant_with_single_skipped_field() { #[derive(DataSize)] enum Skipper { OnlyVariant { #[data_size(skip)] #[allow(dead_code)] skip_me: Box, }, } let specimen = Skipper::OnlyVariant { skip_me: Box::new(123u8), }; assert_eq!(data_size(&specimen), 0); } #[test] fn test_data_size_inner_box() { #[derive(Clone, DataSize)] struct Inner { value: Box, // sz: ptr, heap: 8 dummy: u8, // sz: ptr } // total: sz 16, heap 8 let inner = Inner { value: Box::new(0), // sz ptr, heap: 2*ptr+8 dummy: 0, // sz ptr }; // total: 24 let boxed = Box::new(inner.clone()); let inner_size = core::mem::size_of::(); assert_eq!(8, data_size(&inner)); assert_eq!(8 + inner_size, data_size(&boxed)); } #[test] #[cfg(feature = "detailed")] fn test_nested_detailed_struct() { #[derive(DataSize)] struct Inner { value: Box, dummy: u8, } #[derive(DataSize)] struct Outer { a: Box, b: Inner, c: u8, } let fixture = Outer { a: Box::new(Inner { value: Box::new(1), dummy: 42, }), b: Inner { value: Box::new(2), dummy: 42, }, c: 3, }; let detailed = datasize::data_size_detailed(&fixture); use datasize::MemUsageNode; use std::collections::HashMap; let mut inner_map = HashMap::new(); inner_map.insert("value", MemUsageNode::Size(8)); inner_map.insert("dummy", MemUsageNode::Size(0)); let mut outer_map = HashMap::new(); outer_map.insert("a", MemUsageNode::Size(24)); outer_map.insert("b", MemUsageNode::Detailed(inner_map)); outer_map.insert("c", MemUsageNode::Size(0)); let expected = MemUsageNode::Detailed(outer_map); assert_eq!(detailed, expected); assert_eq!(data_size(&fixture), detailed.total()); } #[test] fn test_generic_enum() { #[derive(DataSize)] enum Foo { Baz { boxed: Box, #[data_size(skip)] #[allow(dead_code)] extra: Box, }, Bert(Vec, #[data_size(skip)] Vec, Box), #[data_size(skip)] Skipped(Vec), } let baz: Foo = Foo::Baz { boxed: Box::new(123), extra: Box::new(456), }; assert_eq!(data_size(&baz), 1); let bert: Foo = Foo::Bert(vec![5, 6, 7, 8, 9], vec![1, 2, 3, 4, 5], Box::new(1)); assert_eq!(data_size(&bert), 5 + 1); let skipped: Foo = Foo::Skipped(vec![1, 1, 99, 100]); assert_eq!(data_size(&skipped), 0); } #[test] fn test_generic_newtype_struct() { #[derive(DataSize)] struct Foo(T); assert!(!Foo::>::IS_DYNAMIC); assert_eq!(Foo::>::STATIC_HEAP_SIZE, 4); assert_eq!(data_size(&Foo(Box::new(123u32))), 4); } #[test] fn test_generic_tuple_struct() { #[derive(DataSize)] struct Foo(T, Box, #[data_size(skip)] Box); assert!(!Foo::>::IS_DYNAMIC); assert_eq!(Foo::>::STATIC_HEAP_SIZE, 5); assert_eq!( data_size(&Foo(Box::new(123u32), Box::new(45), Box::new(0))), 5 ); } } datasize-0.2.13/src/tokio.rs000064400000000000000000000012021046102023000137700ustar 00000000000000use super::DataSize; impl DataSize for tokio::sync::oneshot::Sender { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } impl DataSize for tokio::sync::oneshot::Receiver { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } } impl DataSize for tokio::task::JoinHandle { const IS_DYNAMIC: bool = false; const STATIC_HEAP_SIZE: usize = 0; #[inline] fn estimate_heap_size(&self) -> usize { 0 } }