Hone logo
Hone
Problems

Build a Basic Memory Profiler in Rust

Memory profiling is a crucial technique for understanding how an application uses memory, identifying potential leaks, and optimizing performance. This challenge will guide you in building a fundamental memory profiler in Rust to track memory allocations and deallocations for specific data structures.

Problem Description

Your task is to create a Rust library that can track memory allocations and deallocations for user-defined types. The profiler should provide a mechanism to register types for profiling and then report on the total memory allocated and currently in use for each registered type. This will help developers understand the memory footprint of their data structures.

Key Requirements:

  • Type Registration: Allow users to register specific types (e.g., Vec, HashMap, custom structs) to be profiled.
  • Allocation Tracking: Intercept malloc-like operations (simulated within Rust) and record the size of the allocation, associating it with the registered type.
  • Deallocation Tracking: Intercept free-like operations (simulated) and record the deallocation, decrementing the tracked memory.
  • Reporting: Provide a function to retrieve a summary of memory usage per registered type, including:
    • Total memory allocated so far.
    • Current memory in use.
  • Thread Safety: The profiler should be thread-safe, allowing multiple threads to allocate/deallocate data concurrently without corrupting the profiling data.

Expected Behavior:

When a profiled type is allocated, its current usage and total allocation counts should increase. When it's deallocated, its current usage should decrease. The report should accurately reflect these changes over time.

Edge Cases:

  • Unregistered Types: Allocations of unregistered types should be ignored by the profiler.
  • Zero-Sized Types: Handle types that have a size of 0 bytes gracefully.
  • Concurrent Access: Ensure that multiple threads can perform allocations and deallocations simultaneously without data races.

Examples

Example 1:

// Assume a simple profiler setup where Vec<i32> is registered.

// Allocation 1: vec1 of size 10 * sizeof(i32)
let mut vec1 = Vec::with_capacity(10);
// ... potentially more elements added ...

// Allocation 2: vec2 of size 5 * sizeof(i32)
let mut vec2 = Vec::with_capacity(5);

// Profiler Report:
// Type: Vec<i32>
// Total Allocated: (10 * sizeof(i32)) + (5 * sizeof(i32))
// Current In Use: (10 * sizeof(i32)) + (5 * sizeof(i32))

Explanation: Two vectors are created. The profiler should register Vec<i32> and track the memory for each allocation. The report shows the total memory ever allocated and the memory currently occupied by these vectors.

Example 2:

// Assume Vec<i32> is registered.

let mut vec = Vec::with_capacity(20);
// ... add elements ...
let original_ptr = vec.as_ptr(); // Simulate getting pointer

// When vec is dropped:
// Deallocation of memory associated with vec (size 20 * sizeof(i32))

// Profiler Report:
// Type: Vec<i32>
// Total Allocated: 20 * sizeof(i32)
// Current In Use: 0

Explanation: A vector is allocated and then dropped. The profiler should decrement the "Current In Use" count to zero and reflect the total allocation in "Total Allocated".

Example 3: Mixed Allocations

// Assume Vec<String> and Vec<i32> are registered.

// Allocation 1: vec_str of size 3 * sizeof(String)
let mut vec_str = Vec::with_capacity(3);
vec_str.push("hello".to_string());
vec_str.push("world".to_string());

// Allocation 2: vec_int of size 5 * sizeof(i32)
let mut vec_int = Vec::with_capacity(5);

// Deallocation of vec_str
drop(vec_str);

// Profiler Report:
// Type: Vec<String>
// Total Allocated: 3 * sizeof(String)
// Current In Use: 0

// Type: Vec<i32>
// Total Allocated: 5 * sizeof(i32)
// Current In Use: 5 * sizeof(i32)

Explanation: Different types are profiled. When vec_str is dropped, its memory usage is accounted for, while vec_int continues to consume memory.

Constraints

  • The profiler should be implemented as a Rust crate.
  • You should avoid using external crates that directly provide memory profiling capabilities. You can use standard library features and potentially unsafe Rust for low-level memory manipulation if absolutely necessary for simulation.
  • The performance overhead of the profiler itself should be minimal, though for this challenge, absolute performance isn't the primary concern.
  • The solution should compile with the latest stable Rust toolchain.

Notes

  • To simulate malloc and free without actually using the system allocator directly for profiling, you can wrap data structures or use custom allocation methods that call into your profiler.
  • Consider how you will uniquely identify different types for profiling. You might need to use TypeId or a similar mechanism.
  • For thread safety, think about synchronization primitives like Mutex or RwLock.
  • The reporting mechanism can be a simple function that prints to stdout or returns a structured data type.
Loading editor...
rust