Go Heap Profiler Implementation
Heap profiling is a crucial tool for diagnosing memory leaks and optimizing memory usage in Go applications. This challenge asks you to create a simplified heap profiler that captures heap allocations and provides basic statistics. Understanding and implementing a heap profiler will significantly improve your ability to write efficient and robust Go code.
Problem Description
You are tasked with creating a basic heap profiler in Go. The profiler should track all memory allocations made by the program and provide statistics such as total allocated memory, number of allocations, and potentially a simple breakdown by allocation size. The profiler should be implemented as a middleware that intercepts new calls and tracks the allocated memory. It should not rely on external profiling tools like pprof for this exercise; the goal is to understand the underlying mechanics.
Key Requirements:
- Allocation Tracking: Intercept calls to
newand record the size of the allocated memory. - Statistics Gathering: Maintain statistics on total allocated memory and the number of allocations.
- Simple Reporting: Provide a function to print the collected statistics.
- Non-Intrusive: The profiler should be relatively easy to enable and disable without significant code modification. Consider using a global variable to control profiling.
Expected Behavior:
When enabled, the profiler should track all memory allocations made using new. When disabled, allocations should proceed normally without profiling overhead. The reporting function should output the total allocated memory and the number of allocations.
Edge Cases to Consider:
- Zero-sized allocations: Handle cases where
newmight be used in a way that results in a zero-sized allocation (though this is unlikely in typical usage). - Concurrent access: If the profiler is used in a concurrent environment, ensure that access to the statistics is properly synchronized to avoid race conditions. (For simplicity, assume a single-threaded environment for this challenge).
- Garbage Collection: The profiler should track allocations before garbage collection. It doesn't need to account for memory being freed.
Examples
Example 1:
Input: A Go program that allocates several objects using `new`.
Output:
Total Allocated Memory: 128 bytes
Number of Allocations: 4
Explanation: The program allocated 4 objects, totaling 128 bytes. (Assuming each object is 32 bytes).
Example 2:
Input: A Go program with no allocations using `new`.
Output:
Total Allocated Memory: 0 bytes
Number of Allocations: 0
Explanation: No memory was allocated using `new`.
Example 3: (Edge Case)
Input: A Go program that allocates a large number of small objects.
Output:
Total Allocated Memory: 10240 bytes
Number of Allocations: 1000
Explanation: Demonstrates the profiler's ability to track a large number of allocations.
Constraints
- Memory Overhead: The profiler's memory overhead should be minimal.
- Performance Impact: The profiler should introduce a reasonable performance impact when enabled. Avoid excessive locking or complex data structures.
- No External Libraries: Do not use external profiling libraries like
pprof. The goal is to implement the core logic yourself. - Go Version: The code should be compatible with Go 1.18 or later.
Notes
- Consider using a global variable to enable/disable the profiler.
- You can use a simple
structto store the allocation statistics. - The interception of
newcan be achieved using a custom allocator or by modifying thenewfunction itself (though the latter is generally discouraged). A custom allocator is the preferred approach for this challenge. - Focus on the core functionality of tracking allocations and providing basic statistics. Advanced features like call stack tracing or detailed allocation breakdowns are beyond the scope of this challenge.
- Think about how to make the profiler's impact on the program's performance as small as possible.