Memory Technologies Development Only Heaptrack - antimetal/system-agent GitHub Wiki
Heaptrack is a heap memory profiler for Linux that traces all memory allocations and annotates these events with stack traces. Developed by the KDE project, it aims to be faster than Valgrind's massif while providing more comprehensive data for memory analysis.
Key Characteristics:
- Heap memory profiler for Linux
- 50-100% overhead (1.5-2x slowdown)
- Tracks allocations, peaks, and leaks
- GUI for analysis and visualization
- Multi-threaded application support without serialization
- Overhead: 50-100% performance impact (approximately 2x slowdown)
- Accuracy: High - tracks individual allocation/deallocation events
- False Positives: Low - direct tracking reduces false leak detection
- Production Ready: No - overhead too high for production use
- Platform: Linux only
- Memory Usage: Upfront extra 180MB of RAM for the profiler process
- vs Valgrind: ~12x faster execution, no thread serialization
- vs Massif: Significantly lower overhead in both time and memory
- CPU Impact: Only affects memory allocation calls, not CPU-intensive calculations
Heaptrack uses several mechanisms to track memory allocations:
- LD_PRELOAD Interception: Uses LD_PRELOAD to intercept calls to core memory allocation functions (malloc, free, etc.)
- Backtrace Collection: Obtains and logs stack traces for each allocation event
- Event Logging: Records each individual malloc/free call with function arguments
- Compressed Data Storage: Minimizes data files by avoiding repeated backtrace information
- No Aggregation: Unlike Massif, doesn't aggregate data until analysis phase
The profiler logs allocation events to compressed files (typically /tmp/heaptrack.APP.PID.gz
) for later analysis.
- Development/staging use only - Never deploy in production
- Short debugging sessions - Limited time windows due to overhead
- Not for continuous monitoring - Use sampling-based tools instead
- Emergency debugging tool - When other methods fail to identify leaks
- On-demand activation: Trigger profiling for specific troubleshooting sessions
- Container isolation: Run in separate debugging containers
- Resource monitoring: Ensure adequate RAM (extra 180MB minimum)
- Time-bounded sessions: Limit profiling duration to minimize impact
- Memory Consumption Tracking: Monitor heap usage over time
- Leak Detection: Identify memory that's allocated but never freed
- Allocation Hotspots: Find code locations with frequent allocations
- Flame Graphs: Visual representation of call stacks leading to allocations
- Peak Memory Analysis: Identify maximum memory usage points
- Temporary Allocation Detection: Find allocations immediately followed by deallocations
- Process Attachment: Can attach to already running processes
- Multi-threaded Support: No thread serialization unlike Valgrind
- Real-time Monitoring: Track heap while program is running
- Timeline Analysis: View memory usage patterns over time
- Export Capabilities: Convert to Massif format or flamegraph data
Ubuntu/Debian:
sudo apt install heaptrack heaptrack-gui
Fedora/RHEL:
sudo dnf install heaptrack heaptrack-gui
# Clone repository
git clone https://github.com/KDE/heaptrack.git
cd heaptrack
# Create build directory
mkdir build && cd build
# Configure with CMake
cmake -DCMAKE_BUILD_TYPE=Release ..
# Build
make -j$(nproc)
# Install
sudo make install
Dependencies:
- Qt 5
- KDE Frameworks 5 (KF5)
- CMake
- libunwind
- zlib
For minimal installation (embedded/older systems): Build only the data collector on the target system, then analyze data on a machine with GUI dependencies.
Runtime Dependencies:
- libunwind (for backtraces)
- zlib (for compression)
- gdb (for runtime process attachment)
Build Dependencies:
- CMake
- Qt5 development packages
- KF5 development packages
- C++ compiler with C++11 support
Profile from application start:
heaptrack <your_application> [application_args]
Attach to running process:
heaptrack --pid <process_id>
Profile with custom output location:
heaptrack --output /path/to/output.gz <application>
GUI Analysis (Recommended):
heaptrack_gui /tmp/heaptrack.APP.PID.gz
Command-line Analysis:
heaptrack_print /tmp/heaptrack.APP.PID.gz
Auto-analysis after profiling:
heaptrack --analyze /tmp/heaptrack.APP.PID.gz
Generate ASCII report:
heaptrack_print --print-ascii /path/to/data.gz
Convert to Massif format:
heaptrack_print --print-massif /path/to/data.gz > massif.out
Generate flamegraph data:
heaptrack_print --print-flamegraph /path/to/data.gz > flamegraph.txt
#!/bin/bash
# profile-app.sh - Basic heaptrack profiling script
APP_NAME="$1"
OUTPUT_DIR="/tmp/heaptrack-results"
if [ -z "$APP_NAME" ]; then
echo "Usage: $0 <application_path> [args...]"
exit 1
fi
# Create output directory
mkdir -p "$OUTPUT_DIR"
# Run heaptrack
echo "Starting heaptrack profiling..."
heaptrack --output "$OUTPUT_DIR/$(basename $APP_NAME).$(date +%Y%m%d_%H%M%S).gz" "$@"
echo "Profiling complete. Results in: $OUTPUT_DIR"
echo "Analyze with: heaptrack_gui $OUTPUT_DIR/*.gz"
#!/bin/bash
# analyze-heap.sh - Automated heaptrack analysis
DATA_FILE="$1"
REPORT_DIR="$(dirname "$DATA_FILE")/reports"
if [ ! -f "$DATA_FILE" ]; then
echo "Data file not found: $DATA_FILE"
exit 1
fi
mkdir -p "$REPORT_DIR"
echo "Generating ASCII report..."
heaptrack_print --print-ascii "$DATA_FILE" > "$REPORT_DIR/summary.txt"
echo "Generating flamegraph data..."
heaptrack_print --print-flamegraph "$DATA_FILE" > "$REPORT_DIR/flamegraph.txt"
echo "Converting to Massif format..."
heaptrack_print --print-massif "$DATA_FILE" > "$REPORT_DIR/massif.out"
echo "Analysis complete. Reports in: $REPORT_DIR"
#!/bin/bash
# test-with-profiling.sh - Run tests with memory profiling
TEST_BINARY="./my_test_suite"
OUTPUT_BASE="/tmp/test-profiling"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Profile test execution
heaptrack --output "$OUTPUT_BASE/test_run_$TIMESTAMP.gz" "$TEST_BINARY"
# Generate summary report
heaptrack_print --print-ascii "$OUTPUT_BASE/test_run_$TIMESTAMP.gz" | head -50
# Check for memory leaks
LEAKS=$(heaptrack_print "$OUTPUT_BASE/test_run_$TIMESTAMP.gz" | grep -c "leaked")
if [ "$LEAKS" -gt 0 ]; then
echo "WARNING: Memory leaks detected!"
exit 1
fi
echo "Memory profiling completed successfully"
#!/usr/bin/env python3
# parse_heaptrack.py - Parse heaptrack output for CI integration
import re
import sys
import subprocess
def parse_heaptrack_output(data_file):
"""Parse heaptrack data and extract key metrics"""
# Run heaptrack_print to get text output
result = subprocess.run(['heaptrack_print', data_file],
capture_output=True, text=True)
if result.returncode != 0:
print(f"Error running heaptrack_print: {result.stderr}")
return None
output = result.stdout
metrics = {}
# Extract peak memory usage
peak_match = re.search(r'peak heap memory consumption:\s+(\d+(?:\.\d+)?)\s*([KMGT]?B)', output)
if peak_match:
value, unit = peak_match.groups()
metrics['peak_memory'] = f"{value}{unit}"
# Extract total allocations
alloc_match = re.search(r'total heap allocations:\s+(\d+)', output)
if alloc_match:
metrics['total_allocations'] = int(alloc_match.group(1))
# Extract leaked memory
leak_match = re.search(r'leaked heap memory:\s+(\d+(?:\.\d+)?)\s*([KMGT]?B)', output)
if leak_match:
value, unit = leak_match.groups()
metrics['leaked_memory'] = f"{value}{unit}"
return metrics
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python3 parse_heaptrack.py <heaptrack_data_file>")
sys.exit(1)
metrics = parse_heaptrack_output(sys.argv[1])
if metrics:
print("Heaptrack Analysis Results:")
for key, value in metrics.items():
print(f" {key}: {value}")
else:
print("Failed to parse heaptrack data")
sys.exit(1)
Summary View:
- Peak heap memory consumption
- Total number of allocations
- Memory leaked at program termination
- Temporary allocations statistics
Charts and Visualizations:
- Memory Timeline: Heap consumption over time
- Allocation Timeline: Number of allocations over time
- Temporary Allocations: Short-lived allocation patterns
- Flame Graph: Interactive call stack visualization
Tree Views:
- Bottom-up: Shows functions that allocate the most memory
- Top-down: Shows call paths leading to allocations
- Caller/Callee: Aggregated caller and callee relationships
Double-click Integration:
- Double-click locations in GUI to open source code in editor
- Requires running GUI from project root for relative path resolution
Filtering and Search:
- Filter by allocation size thresholds
- Search for specific function names
- Focus on temporary vs persistent allocations
Data Export:
- Export filtered data to various formats
- Generate reports for specific time ranges
- Save analysis sessions
Drill-down Capabilities:
- Click on flame graph sections to focus on specific call paths
- Expand/collapse tree view nodes
- Filter by allocation frequency or size
Timeline Scrubbing:
- Navigate through program execution timeline
- Correlate memory usage with program phases
- Identify memory usage patterns
Heaptrack Advantages:
- 12x faster execution time
- No thread serialization (better for multi-threaded apps)
- More detailed data (individual allocations vs aggregated)
- Better temporary allocation detection
- Real-time monitoring capability
Massif Advantages:
- Part of mature Valgrind suite
- Can profile stack memory (heaptrack cannot)
- More widely supported and documented
- Integration with other Valgrind tools
Performance Comparison:
- Heaptrack: ~50-100% overhead
- Massif: 10-50x slower, serializes threads
Similarities:
- Both designed for lower overhead than Valgrind
- Focus on heap profiling
- Provide detailed allocation tracking
Differences:
- ByteHound: ~33% less overhead than heaptrack
- Heaptrack: More mature, better GUI
- ByteHound: Better Rust integration
- Heaptrack: Better KDE/Qt integration
Heaptrack vs jemalloc:
- Scope: Heaptrack works with any allocator, jemalloc only with jemalloc
- Overhead: Similar performance impact
- Detail: Heaptrack provides more detailed stack traces
- Integration: jemalloc profiling requires linking/configuration changes
When to Use Each:
- Heaptrack: When you need detailed analysis with minimal setup
- jemalloc: When already using jemalloc and want integrated profiling
- Complete vs Statistical: Heaptrack tracks all allocations vs jemalloc's sampling
Key Metrics to Monitor:
peak heap memory consumption: 128.3MB
total heap allocations: 1,234,567
total heap deallocations: 1,234,500
leaked heap memory: 67KB (remaining allocations: 67)
Interpreting Results:
- Peak Memory: Maximum heap usage during execution
- Allocation Count: Total number of malloc calls
- Leaked Memory: Allocations without corresponding frees
- Temporary Allocations: Allocations immediately followed by frees
True Memory Leaks:
- Allocations with no corresponding deallocation
- Growing memory usage over time
- Memory not reachable at program termination
False Positives:
- Static allocations that persist until program end
- Allocations managed by cleanup handlers
- Memory pools that defer deallocation
Analysis Strategy:
- Focus on largest leaked allocations first
- Check for patterns in allocation call stacks
- Correlate with application lifecycle events
- Verify with longer program runs
Allocation Hotspots:
- Functions with highest allocation frequency
- Call paths leading to large allocations
- Code locations with repeated temporary allocations
Optimization Opportunities:
- Reduce allocation frequency in hot paths
- Pool allocations for frequently used objects
- Eliminate unnecessary temporary allocations
- Use stack allocation where possible
Performance Bottlenecks:
- Functions appearing frequently in flame graphs
- Deep call stacks leading to allocations
- Allocation patterns that fragment memory
Memory Usage Reduction:
- Identify largest memory consumers
- Find opportunities for object reuse
- Eliminate duplicate allocations
- Optimize data structure sizes
Performance Improvements:
- Reduce allocation frequency in critical paths
- Batch allocations where possible
- Use memory pools for fixed-size objects
- Eliminate temporary allocations in loops
Real-world Example: After fixing code identified by heaptrack, peak memory use dropped from 400MB to <100MB, leveling out at about 60MB instead of 380MB.
Local Development:
- Profile applications during development cycles
- Identify memory inefficiencies before code review
- Validate memory optimization changes
- Debug memory-related crashes
Integration Testing:
- Profile complete application workflows
- Test memory usage under realistic loads
- Verify memory cleanup in long-running tests
- Catch regressions in memory usage
Staging Environment Profiling:
- Validate memory usage with production-like data
- Test memory behavior under load
- Identify scaling bottlenecks
- Verify fixes before production deployment
Performance Testing:
- Baseline memory usage for performance tests
- Compare memory usage across application versions
- Identify memory usage patterns under different loads
- Validate memory limits and thresholds
Memory Footprint Reduction:
- Identify largest memory consumers in applications
- Find opportunities to reduce allocation frequency
- Optimize data structures for memory efficiency
- Eliminate memory waste in critical code paths
Application Tuning:
- Profile memory usage in different application modes
- Optimize memory usage for specific use cases
- Balance memory usage vs performance trade-offs
- Validate memory optimizations
Debugging Memory Leaks:
- Identify sources of memory leaks in development
- Track down hard-to-find leaks with detailed stack traces
- Validate leak fixes with before/after profiling
- Analyze leak patterns to prevent future issues
Regression Detection:
- Catch new memory leaks in continuous integration
- Compare memory usage across code versions
- Identify changes that introduce memory problems
- Maintain memory usage baselines
Linux-only Support:
- Cannot profile applications on Windows or macOS
- Limited to GNU/Linux distributions
- Requires specific system libraries (libunwind, etc.)
Heap Memory Focus:
- Cannot profile stack memory consumption (use Massif for stack profiling)
- Limited insight into kernel memory usage
- No analysis of memory-mapped files
- Cannot track memory usage of child processes automatically
High Overhead:
- 50-100% performance impact prevents production use
- Significant memory overhead (180MB+ for profiler process)
- May cause container OOM kills if insufficient memory
- Can timeout applications with strict runtime limits
Application Restart Required:
- Cannot profile already running applications without attachment
- Requires LD_PRELOAD or explicit attachment
- May interfere with applications that manipulate LD_PRELOAD
- Cannot profile applications with specific runtime requirements
Limited Language Support:
- Primarily designed for C/C++ applications
- Limited Swift support (may not capture Swift-specific memory management)
- Cannot profile garbage-collected languages effectively
- May miss language-specific allocation patterns
Runtime Dependencies:
- Requires specific versions of system libraries
- May not work with statically linked applications
- Cannot profile applications with custom allocators without modification
- Limited support for applications using multiple allocators
Analysis Limitations:
- Large data files can be difficult to process
- GUI requires X11/Wayland display for remote analysis
- Limited automated analysis capabilities
- No built-in integration with monitoring systems
Data Storage:
- Profiling data can consume significant disk space
- No automatic cleanup of old profiling data
- Compressed format requires specific tools to read
- Cannot stream profiling data for real-time analysis
- Valgrind Massif Documentation - Alternative memory profiler comparison
- ByteHound Documentation - Lower overhead alternative
- jemalloc Profiling - Allocator-integrated profiling
- Memory Analysis Overview - Complete memory profiling toolkit