Memory Technologies Development Only Heaptrack - antimetal/system-agent GitHub Wiki

Heaptrack

Overview

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

Performance Characteristics

  • 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

Performance Comparison

  • 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

How It Works

Heaptrack uses several mechanisms to track memory allocations:

  1. LD_PRELOAD Interception: Uses LD_PRELOAD to intercept calls to core memory allocation functions (malloc, free, etc.)
  2. Backtrace Collection: Obtains and logs stack traces for each allocation event
  3. Event Logging: Records each individual malloc/free call with function arguments
  4. Compressed Data Storage: Minimizes data files by avoiding repeated backtrace information
  5. 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.

System-Agent Implementation Plan

Recommended Usage Scope

  • 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

Integration Strategy

  1. On-demand activation: Trigger profiling for specific troubleshooting sessions
  2. Container isolation: Run in separate debugging containers
  3. Resource monitoring: Ensure adequate RAM (extra 180MB minimum)
  4. Time-bounded sessions: Limit profiling duration to minimize impact

Features

Core Capabilities

  • 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

Advanced Features

  • 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

Installation & Setup

Package Installation

Ubuntu/Debian:

sudo apt install heaptrack heaptrack-gui

Fedora/RHEL:

sudo dnf install heaptrack heaptrack-gui

Building from Source

# 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

GUI Setup

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.

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

Usage

Basic Usage

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>

Analyzing Results

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

Report Generation

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

Code Examples

Basic Profiling Script

#!/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"

Automated Analysis Script

#!/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"

Integration with Testing

#!/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"

Report Parsing Script

#!/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)

GUI Features

Main Interface Components

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

Navigation Features

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

Interactive Analysis

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

Comparison with Alternatives

vs Valgrind Massif

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

vs ByteHound

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

vs jemalloc Profiling

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

Output Analysis

Understanding Reports

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

Identifying Leaks

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:

  1. Focus on largest leaked allocations first
  2. Check for patterns in allocation call stacks
  3. Correlate with application lifecycle events
  4. Verify with longer program runs

Finding Hotspots

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

Optimization Opportunities

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.

Use Cases

Development Profiling

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

Pre-production Testing

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

Performance Optimization

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

Leak Hunting

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

Limitations

Platform and Environment

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

Performance and Production Use

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

Language and Runtime Support

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 and Reporting

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

See Also

⚠️ **GitHub.com Fallback** ⚠️