#![doc = include_str!("../README.md")]
#![deny(unconditional_recursion)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[doc(hidden)]
#[cfg(not(feature = "std"))]
pub use alloc::string::String;
#[doc(hidden)]
#[cfg(feature = "std")]
pub use std::string::String;
#[doc(hidden)]
pub use hashbrown::HashMap;
#[doc(hidden)]
pub use hashbrown::HashSet;
#[cfg(feature = "derive")]
pub use mem_dbg_derive::{MemDbg, MemSize};
mod impl_mem_dbg;
mod impl_mem_size;
#[doc(hidden)]
pub use impl_mem_size::LinkedListNode;
mod utils;
pub use utils::*;
pub trait Boolean {
type And<B: Boolean>: Boolean;
const VALUE: bool;
#[doc(hidden)]
type FlatCheck: Copy;
#[doc(hidden)]
const FLAT_CHECK: Self::FlatCheck;
}
#[must_use = "this field is not flat, so the enclosing `#[mem_size(flat)]` type under-counts its size: use `#[mem_size(rec)]`, or implement `FlatType` by hand. This will become a hard error in a future release."]
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct NonFlatField;
pub struct True {}
impl Boolean for True {
type And<B: Boolean> = B;
const VALUE: bool = true;
type FlatCheck = ();
const FLAT_CHECK: () = ();
}
pub struct False {}
impl Boolean for False {
type And<B: Boolean> = False;
const VALUE: bool = false;
type FlatCheck = NonFlatField;
const FLAT_CHECK: NonFlatField = NonFlatField;
}
#[derive(Clone, Copy)]
pub enum RefDisplay {
None,
FirstEncounter(usize),
BackReference(usize),
}
pub trait FlatType {
type Flat: Boolean;
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SizeFlags: u32 {
const FOLLOW_REFS = 1 << 0;
const CAPACITY = 1 << 1;
const FOLLOW_RCS = 1 << 2;
}
}
impl Default for SizeFlags {
fn default() -> Self {
Self::empty()
}
}
pub trait MemSize {
fn mem_size(&self, flags: SizeFlags) -> usize {
let mut refs = HashMap::new();
let base = self.mem_size_rec(flags, &mut refs);
base + refs.into_values().sum::<usize>()
}
fn mem_size_rec(&self, flags: SizeFlags, refs: &mut HashMap<usize, usize>) -> usize;
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DbgFlags: u32 {
const FOLLOW_REFS = 1 << 0;
const HUMANIZE = 1 << 1;
const PERCENTAGE = 1 << 2;
const TYPE_NAME = 1 << 3;
const CAPACITY = 1 << 4;
const SEPARATOR = 1 << 5;
const RUST_LAYOUT = 1 << 6;
const COLOR = 1 << 7;
const FOLLOW_RCS = 1 << 8;
}
}
impl DbgFlags {
pub const fn to_size_flags(&self) -> SizeFlags {
let mut flags = SizeFlags::empty();
if self.contains(DbgFlags::FOLLOW_REFS) {
flags = flags.union(SizeFlags::FOLLOW_REFS);
}
if self.contains(DbgFlags::CAPACITY) {
flags = flags.union(SizeFlags::CAPACITY);
}
if self.contains(DbgFlags::FOLLOW_RCS) {
flags = flags.union(SizeFlags::FOLLOW_RCS);
}
flags
}
}
impl Default for DbgFlags {
fn default() -> Self {
Self::TYPE_NAME | Self::SEPARATOR | Self::PERCENTAGE
}
}
pub trait MemDbg: MemDbgImpl {
#[cfg(feature = "std")]
fn mem_dbg(&self, flags: DbgFlags) -> core::fmt::Result {
self._mem_dbg_depth(
<Self as MemSize>::mem_size(self, flags.to_size_flags()),
usize::MAX,
core::mem::size_of_val(self),
flags,
)
}
fn mem_dbg_on(&self, writer: &mut impl core::fmt::Write, flags: DbgFlags) -> core::fmt::Result {
let mut dbg_refs = HashSet::new();
self._mem_dbg_depth_on(
writer,
<Self as MemSize>::mem_size(self, flags.to_size_flags()),
usize::MAX,
&mut String::new(),
Some("⏺"),
true,
core::mem::size_of_val(self),
flags,
&mut dbg_refs,
)
}
#[cfg(feature = "std")]
fn mem_dbg_depth(&self, max_depth: usize, flags: DbgFlags) -> core::fmt::Result {
self._mem_dbg_depth(
<Self as MemSize>::mem_size(self, flags.to_size_flags()),
max_depth,
core::mem::size_of_val(self),
flags,
)
}
fn mem_dbg_depth_on(
&self,
writer: &mut impl core::fmt::Write,
max_depth: usize,
flags: DbgFlags,
) -> core::fmt::Result {
let mut dbg_refs = HashSet::new();
self._mem_dbg_depth_on(
writer,
<Self as MemSize>::mem_size(self, flags.to_size_flags()),
max_depth,
&mut String::new(),
Some("⏺"),
true,
core::mem::size_of_val(self),
flags,
&mut dbg_refs,
)
}
}
impl<T: MemDbgImpl> MemDbg for T {}
#[allow(clippy::too_many_arguments)]
pub trait MemDbgImpl: MemSize {
fn _mem_dbg_rec_on(
&self,
_writer: &mut impl core::fmt::Write,
_total_size: usize,
_max_depth: usize,
_prefix: &mut String,
_is_last: bool,
_flags: DbgFlags,
_dbg_refs: &mut HashSet<usize>,
) -> core::fmt::Result {
Ok(())
}
#[cfg(feature = "std")]
#[doc(hidden)]
fn _mem_dbg_depth(
&self,
total_size: usize,
max_depth: usize,
padded_size: usize,
flags: DbgFlags,
) -> core::fmt::Result {
struct Wrapper<'a>(std::io::StderrLock<'a>);
impl core::fmt::Write for Wrapper<'_> {
#[inline(always)]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
use std::io::Write;
self.0.write_all(s.as_bytes()).map_err(|_| core::fmt::Error)
}
}
let stderr = std::io::stderr();
let mut dbg_refs = HashSet::new();
self._mem_dbg_depth_on(
&mut Wrapper(stderr.lock()),
total_size,
max_depth,
&mut String::new(),
Some("⏺"),
true,
padded_size,
flags,
&mut dbg_refs,
)
}
fn _mem_dbg_depth_on(
&self,
writer: &mut impl core::fmt::Write,
total_size: usize,
max_depth: usize,
prefix: &mut String,
field_name: Option<&str>,
is_last: bool,
padded_size: usize,
flags: DbgFlags,
dbg_refs: &mut HashSet<usize>,
) -> core::fmt::Result {
self._mem_dbg_depth_on_impl(
writer,
total_size,
max_depth,
prefix,
field_name,
is_last,
padded_size,
flags,
dbg_refs,
RefDisplay::None,
)
}
#[allow(clippy::too_many_arguments)]
fn _mem_dbg_depth_on_impl(
&self,
writer: &mut impl core::fmt::Write,
total_size: usize,
max_depth: usize,
prefix: &mut String,
field_name: Option<&str>,
is_last: bool,
padded_size: usize,
flags: DbgFlags,
dbg_refs: &mut HashSet<usize>,
ref_display: RefDisplay,
) -> core::fmt::Result {
if prefix.chars().count() / 2 > max_depth {
return Ok(());
}
let is_backref = matches!(ref_display, RefDisplay::BackReference(_));
let display_size = if is_backref {
core::mem::size_of_val(self)
} else {
<Self as MemSize>::mem_size(self, flags.to_size_flags())
};
if flags.contains(DbgFlags::COLOR) {
let color = utils::color(display_size);
writer.write_fmt(format_args!("{color}"))?;
};
if flags.contains(DbgFlags::HUMANIZE) {
let (value, uom) = crate::utils::humanize_float(display_size);
if uom == " B" {
writer.write_fmt(format_args!("{:>5} B ", display_size))?;
} else {
let precision = if value >= 100.0 {
1
} else if value >= 10.0 {
2
} else if value >= 1.0 {
3
} else {
4
};
writer.write_fmt(format_args!("{0:>4.1$} {2} ", value, precision, uom))?;
}
} else if flags.contains(DbgFlags::SEPARATOR) {
let mut align = crate::utils::n_of_digits(total_size);
let mut size_for_sep = display_size;
align += align / 3;
let mut digits = crate::utils::n_of_digits(size_for_sep);
let digit_align = digits + digits / 3;
for _ in digit_align..align {
writer.write_char(' ')?;
}
let first_digits = digits % 3;
let mut multiplier = 10_usize.pow((digits - first_digits) as u32);
if first_digits != 0 {
writer.write_fmt(format_args!("{}", size_for_sep / multiplier))?;
} else {
multiplier /= 1000;
digits -= 3;
writer.write_fmt(format_args!(" {}", size_for_sep / multiplier))?;
}
while digits >= 3 {
size_for_sep %= multiplier;
multiplier /= 1000;
writer.write_fmt(format_args!("_{:03}", size_for_sep / multiplier))?;
digits -= 3;
}
writer.write_str(" B ")?;
} else {
let align = crate::utils::n_of_digits(total_size);
writer.write_fmt(format_args!("{:>align$} B ", display_size))?;
}
if flags.contains(DbgFlags::PERCENTAGE) {
writer.write_fmt(format_args!(
"{:>6.2}% ",
if total_size == 0 {
100.0
} else {
100.0 * display_size as f64 / total_size as f64
}
))?;
}
if flags.contains(DbgFlags::COLOR) {
let reset_color = utils::reset_color();
writer.write_fmt(format_args!("{reset_color}"))?;
};
if !prefix.is_empty() {
let start_byte = prefix
.char_indices()
.nth(2) .map(|(idx, _)| idx)
.unwrap_or(prefix.len());
writer.write_str(&prefix[start_byte..])?;
if is_last {
writer.write_char('╰')?;
} else {
writer.write_char('├')?;
}
writer.write_char('╴')?;
}
if let Some(field_name) = field_name {
writer.write_fmt(format_args!("{}", field_name))?;
}
if flags.contains(DbgFlags::TYPE_NAME) {
if flags.contains(DbgFlags::COLOR) {
writer.write_fmt(format_args!("{}", utils::type_color()))?;
}
writer.write_fmt(format_args!(": {}", core::any::type_name::<Self>()))?;
if flags.contains(DbgFlags::COLOR) {
writer.write_fmt(format_args!("{}", utils::reset_color()))?;
}
}
match ref_display {
RefDisplay::FirstEncounter(ptr) => {
if flags.contains(DbgFlags::COLOR) {
writer.write_fmt(format_args!("{}", utils::ref_color()))?;
}
writer.write_fmt(format_args!(
" @ 0x{:0width$x}",
ptr,
width = 2 * core::mem::size_of::<usize>()
))?;
if flags.contains(DbgFlags::COLOR) {
writer.write_fmt(format_args!("{}", utils::reset_color()))?;
}
}
RefDisplay::BackReference(ptr) => {
if flags.contains(DbgFlags::COLOR) {
writer.write_fmt(format_args!("{}", utils::backref_color()))?;
}
writer.write_fmt(format_args!(
" → 0x{:0width$x}",
ptr,
width = 2 * core::mem::size_of::<usize>()
))?;
if flags.contains(DbgFlags::COLOR) {
writer.write_fmt(format_args!("{}", utils::reset_color()))?;
}
}
RefDisplay::None => {}
}
if !is_backref {
let padding = padded_size.saturating_sub(core::mem::size_of_val(self));
if padding != 0 {
writer.write_fmt(format_args!(" [{}B]", padding))?;
}
}
writer.write_char('\n')?;
if !is_backref {
if is_last {
prefix.push_str(" ");
} else {
prefix.push_str("│ ");
}
self._mem_dbg_rec_on(
writer, total_size, max_depth, prefix, is_last, flags, dbg_refs,
)?;
prefix.pop();
prefix.pop();
}
Ok(())
}
}