mem_dbg 0.4.3

Traits and associated procedural macros to display recursively the layout and memory usage of a value
Documentation
#![cfg(feature = "std")]
#![cfg(feature = "derive")]

use mem_dbg::*;
use std::rc::Rc;
use std::sync::Arc;

#[test]
fn test_box_in_struct() {
    #[derive(MemSize)]
    struct Test {
        boxed: Box<u64>,
    }

    let s = Test {
        boxed: Box::new(42),
    };
    let size = s.mem_size(SizeFlags::default());

    // Box adds its pointer size + the contained value
    let expected = std::mem::size_of::<Box<u64>>() + std::mem::size_of::<u64>();
    assert_eq!(size, expected);
}

#[test]
fn test_arc_in_struct() {
    // Mirror of the private `std::sync::ArcInner<T>`, used to compute
    // the expected size of the heap header behind an `Arc<T>`.
    #[allow(dead_code)]
    struct ArcInner<T> {
        strong: core::sync::atomic::AtomicUsize,
        weak: core::sync::atomic::AtomicUsize,
        data: T,
    }

    #[derive(MemSize)]
    struct Test {
        arc: Arc<u64>,
    }

    let s = Test { arc: Arc::new(42) };

    // Without FOLLOW_RCS: only the Arc handle is counted.
    let size = s.mem_size(SizeFlags::default());
    assert_eq!(size, std::mem::size_of::<Arc<u64>>());

    // With FOLLOW_RCS: handle + ArcInner header + (flat) payload.
    let size_follow = s.mem_size(SizeFlags::FOLLOW_RCS);
    assert_eq!(
        size_follow,
        std::mem::size_of::<Arc<u64>>() + std::mem::size_of::<ArcInner<u64>>()
    );
}

#[test]
fn test_arc_with_heap_payload() {
    #[allow(dead_code)]
    struct ArcInner<T> {
        strong: core::sync::atomic::AtomicUsize,
        weak: core::sync::atomic::AtomicUsize,
        data: T,
    }

    #[derive(MemSize)]
    struct Test {
        arc: Arc<String>,
    }

    let payload = String::from("hello");
    let payload_len = payload.len();
    let payload_cap = payload.capacity();
    let s = Test {
        arc: Arc::new(payload),
    };

    // Without FOLLOW_RCS: only the Arc handle.
    let size = s.mem_size(SizeFlags::default());
    assert_eq!(size, std::mem::size_of::<Arc<String>>());

    // With FOLLOW_RCS: handle + ArcInner header + String stack + heap (len).
    let size_follow = s.mem_size(SizeFlags::FOLLOW_RCS);
    assert_eq!(
        size_follow,
        std::mem::size_of::<Arc<String>>() + std::mem::size_of::<ArcInner<String>>() + payload_len
    );

    // FOLLOW_RCS | CAPACITY swaps len for capacity in the String accounting.
    let size_follow_cap = s.mem_size(SizeFlags::FOLLOW_RCS | SizeFlags::CAPACITY);
    assert_eq!(
        size_follow_cap,
        std::mem::size_of::<Arc<String>>() + std::mem::size_of::<ArcInner<String>>() + payload_cap
    );
}

#[test]
fn test_rc_deduplication() {
    #[derive(MemSize)]
    struct Test {
        rc1: Rc<[u8; 1000]>,
        rc2: Rc<[u8; 1000]>,
    }

    let shared = Rc::new([0u8; 1000]);
    let s = Test {
        rc1: Rc::clone(&shared),
        rc2: Rc::clone(&shared),
    };

    // Without FOLLOW_RCS: just the two Rc pointer sizes
    let size_no_follow = s.mem_size(SizeFlags::default());
    assert_eq!(size_no_follow, 2 * std::mem::size_of::<Rc<[u8; 1000]>>());

    // With FOLLOW_RCS: the shared data should only be counted once
    let size_with_follow = s.mem_size(SizeFlags::FOLLOW_RCS);

    // Two Rc pointers + one RcInner (which contains strong/weak counts + data)
    // The data (1000 bytes) should only appear once, not twice
    let rc_ptr_size = std::mem::size_of::<Rc<[u8; 1000]>>();

    // If deduplication works, size should be much less than if counted twice
    // (2 * rc_ptr_size + 2 * 1000 would be wrong)
    assert!(
        size_with_follow < 2 * rc_ptr_size + 2 * 1000,
        "Deduplication failed: size {} should be less than {}",
        size_with_follow,
        2 * rc_ptr_size + 2 * 1000
    );
}

#[test]
fn test_arc_deduplication() {
    #[derive(MemSize)]
    struct Test {
        arc1: Arc<[u8; 1000]>,
        arc2: Arc<[u8; 1000]>,
    }

    let shared = Arc::new([0u8; 1000]);
    let s = Test {
        arc1: Arc::clone(&shared),
        arc2: Arc::clone(&shared),
    };

    // Without FOLLOW_RCS: just the two Arc pointer sizes
    let size_no_follow = s.mem_size(SizeFlags::default());
    assert_eq!(size_no_follow, 2 * std::mem::size_of::<Arc<[u8; 1000]>>());

    // With FOLLOW_RCS: the shared data should only be counted once
    let size_with_follow = s.mem_size(SizeFlags::FOLLOW_RCS);

    let arc_ptr_size = std::mem::size_of::<Arc<[u8; 1000]>>();

    // If deduplication works, size should be much less than if counted twice
    assert!(
        size_with_follow < 2 * arc_ptr_size + 2 * 1000,
        "Deduplication failed: size {} should be less than {}",
        size_with_follow,
        2 * arc_ptr_size + 2 * 1000
    );
}

#[test]
fn test_reference_then_rc_counts_rc_allocation() {
    #[derive(MemSize)]
    #[mem_size(rec)]
    struct Test<'a> {
        borrowed: &'a String,
        shared: Rc<String>,
    }

    let shared = Rc::new(String::from("hello"));
    let s = Test {
        borrowed: shared.as_ref(),
        shared: Rc::clone(&shared),
    };
    let flags = SizeFlags::FOLLOW_REFS | SizeFlags::FOLLOW_RCS;
    let shared_allocation =
        Rc::clone(&shared).mem_size(SizeFlags::FOLLOW_RCS) - core::mem::size_of::<Rc<String>>();

    assert_eq!(
        s.mem_size(flags),
        core::mem::size_of_val(&s) + shared_allocation
    );
}

#[test]
fn test_reference_then_arc_counts_arc_allocation() {
    #[derive(MemSize)]
    #[mem_size(rec)]
    struct Test<'a> {
        borrowed: &'a String,
        shared: Arc<String>,
    }

    let shared = Arc::new(String::from("hello"));
    let s = Test {
        borrowed: shared.as_ref(),
        shared: Arc::clone(&shared),
    };
    let flags = SizeFlags::FOLLOW_REFS | SizeFlags::FOLLOW_RCS;
    let shared_allocation =
        Arc::clone(&shared).mem_size(SizeFlags::FOLLOW_RCS) - core::mem::size_of::<Arc<String>>();

    assert_eq!(
        s.mem_size(flags),
        core::mem::size_of_val(&s) + shared_allocation
    );
}

#[test]
fn test_reference_deduplication() {
    #[derive(MemSize)]
    struct Test<'a> {
        ref1: &'a [u8; 1000],
        ref2: &'a [u8; 1000],
    }

    let data = [0u8; 1000];
    let s = Test {
        ref1: &data,
        ref2: &data,
    };

    // Without FOLLOW_REFS: just the two reference sizes
    let size_no_follow = s.mem_size(SizeFlags::default());
    assert_eq!(size_no_follow, 2 * std::mem::size_of::<&[u8; 1000]>());

    // With FOLLOW_REFS: the shared data should only be counted once
    let size_with_follow = s.mem_size(SizeFlags::FOLLOW_REFS);

    let ref_size = std::mem::size_of::<&[u8; 1000]>();

    // If deduplication works, size should be much less than if counted twice
    // Expected: 2 * ref_size + 1000 (data counted once)
    // Wrong:    2 * ref_size + 2000 (data counted twice)
    assert!(
        size_with_follow < 2 * ref_size + 2 * 1000,
        "Deduplication failed: size {} should be less than {}",
        size_with_follow,
        2 * ref_size + 2 * 1000
    );

    // More precisely, should be exactly 2 refs + 1000 bytes of data
    assert_eq!(size_with_follow, 2 * ref_size + 1000);
}