Skip to content
Back to skills

Hammer Memory Profiler

BSecurity

Simplified memory profiling and leak detection for SDL3 HammerEngine using valgrind memcheck, AddressSanitizer, and massif. Identifies memory leaks, allocation hotspots, buffer reuse violations, and provides system-by-system memory breakdown with optimization suggestions. Use after performance-critical changes or when investigating memory issues.

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
testinggobashtestinggitperformance

Security analysis

B80/100
  • highPerforms destructive filesystem operations
  • criticalImpersonates system messages to override safety constraints

Pro shows the line behind each finding and how to fix it

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill hammer-memory-profiler --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hammer Memory Profiler?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hammer Memory Profiler
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-hammer-memory-profiler/badge)](https://www.skillsdirectory.com/skills/majiayu000-hammer-memory-profiler)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hammer-memory-profiler
description: Simplified memory profiling and leak detection for SDL3 HammerEngine using valgrind memcheck, AddressSanitizer, and massif. Identifies memory leaks, allocation hotspots, buffer reuse violations, and provides system-by-system memory breakdown with optimization suggestions. Use after performance-critical changes or when investigating memory issues.
allowed-tools: [Bash, Read, Write, Grep, Glob]
---

# HammerEngine Memory Profiler

Comprehensive memory profiling and leak detection for SDL3 HammerEngine. Identifies memory leaks, per-frame allocation hotspots, buffer reuse violations, and provides actionable optimization recommendations following CLAUDE.md patterns.

## Available Scripts

This skill includes utility scripts in `.claude/skills/hammer-memory-profiler/scripts/`:

- **`run_leak_check.sh`** - Quick memory leak detection with valgrind memcheck
- **`run_massif_all_tests.sh`** - Run valgrind massif on all test executables
- **`parse_massif.py`** - Parse massif reports and generate comprehensive analysis

Use these scripts directly or let the skill invoke them automatically.

## Purpose

Memory management is critical for HammerEngine's performance targets (10K+ entities @ 60 FPS). This Skill automates:

1. **Leak Detection** - Find memory leaks before production
2. **Allocation Profiling** - Identify per-frame allocation hotspots (frame spikes)
3. **Buffer Reuse Verification** - Ensure CLAUDE.md buffer patterns followed
4. **System Breakdown** - Track memory usage per manager (AI, Collision, etc.)
5. **Baseline Comparison** - Monitor memory usage trends over time
6. **Optimization Suggestions** - Provide specific fixes based on project patterns

## Profiling Modes

### Mode 1: Quick Leak Check (2-5 minutes)
- Run core tests with valgrind memcheck
- Detect definite leaks and invalid access
- Generate summary report
- **Use when:** Daily development, before commits

### Mode 2: Allocation Profiling (5-10 minutes)
- Build with AddressSanitizer
- Run targeted tests (AI, Collision, Pathfinding)
- Identify per-frame allocation patterns
- **Use when:** Investigating frame spikes, performance issues

**Note:** For thread safety validation (data races, deadlocks), use ThreadSanitizer instead of AddressSanitizer. See TSAN section below.

### Mode 3: Full Memory Profile (15-30 minutes)
- Run valgrind massif (heap profiler)
- Detailed memory usage over time
- Peak memory identification
- System-by-system breakdown
- **Use when:** Release preparation, major optimizations

### Mode 4: Buffer Reuse Audit (10-15 minutes)
- Scan code for buffer reuse patterns
- Verify member variables for hot-path buffers
- Check for `clear()` vs reconstruction
- Identify missing `reserve()` calls
- **Use when:** After adding new managers, performance optimization

## Step 1: Gather User Input

Use AskUserQuestion to determine profiling scope:

**Question 1: Profiling Mode**
- Header: "Mode"
- Question: "What type of memory profiling do you want?"
- Options:
  - "Quick Leak Check" (2-5 min, daily use)
  - "Allocation Profiling" (5-10 min, frame spike investigation)
  - "Full Memory Profile" (15-30 min, comprehensive analysis)
  - "Buffer Reuse Audit" (10-15 min, pattern verification)
- multiSelect: false

**Question 2: Test Scope**
- Header: "Scope"
- Question: "Which systems should be profiled?"
- Options:
  - "Core Tests Only" (Thread, Buffer, Event tests)
  - "AI System" (AI optimization, behavior tests)
  - "Collision/Pathfinding" (Collision, pathfinding tests)
  - "All Systems" (Full test suite)
- multiSelect: false

**Question 3: Baseline Comparison**
- Header: "Baseline"
- Question: "Compare against baseline memory metrics?"
- Options:
  - "Yes - Compare" (shows trends)
  - "No - Just current analysis"
  - "Create new baseline" (save current as baseline)
- multiSelect: false

## Step 2: Execute Profiling Based on Mode

### Mode 1: Quick Leak Check

**2a. Ensure Debug Build Exists**
```bash
# Check if debug build exists
if [ ! -f "./bin/debug/thread_system_tests" ]; then
    echo "Debug build not found. Building..."
    cmake -B build/ -G Ninja -DCMAKE_BUILD_TYPE=Debug && ninja -C build
fi
```

**2b. Run Valgrind Memcheck**

**Test Selection Based on Scope:**
- **Core Tests Only:**
  ```bash
  TEST_EXECUTABLES=(
      "./bin/debug/thread_system_tests"
      "./bin/debug/buffer_utilization_tests"
      "./bin/debug/event_manager_tests"
  )
  ```

- **AI System:**
  ```bash
  TEST_EXECUTABLES=(
      "./bin/debug/thread_safe_ai_manager_tests"
      "./bin/debug/ai_optimization_tests"
      "./bin/debug/behavior_functionality_tests"
  )
  ```

- **Collision/Pathfinding:**
  ```bash
  TEST_EXECUTABLES=(
      "./bin/debug/collision_system_tests"
      "./bin/debug/pathfinder_manager_tests"
      "./bin/debug/collision_pathfinding_integration_tests"
  )
  ```

- **All Systems:**
  ```bash
  TEST_EXECUTABLES=(
      # Run all test executables in bin/debug/
  )
  ```

**Valgrind Command Template:**
```bash
OUTPUT_DIR="test_results/memory_profiles"
mkdir -p "$OUTPUT_DIR"

for TEST_EXEC in "${TEST_EXECUTABLES[@]}"; do
    TEST_NAME=$(basename "$TEST_EXEC")
    echo "Running valgrind on $TEST_NAME..."

    valgrind \
        --leak-check=full \
        --show-leak-kinds=all \
        --track-origins=yes \
        --verbose \
        --log-file="$OUTPUT_DIR/${TEST_NAME}_memcheck.log" \
        "$TEST_EXEC" --log_level=test_suite \
        2>&1 | tee "$OUTPUT_DIR/${TEST_NAME}_output.txt"
done
```

**Valgrind Flags Explained:**
- `--leak-check=full`: Detailed leak information
- `--show-leak-kinds=all`: Show all leak types (definite, indirect, possible, reachable)
- `--track-origins=yes`: Track origin of uninitialized values
- `--verbose`: Detailed output
- `--log-file`: Save valgrind output to file

**2c. Parse Valgrind Output**

**Extract Key Metrics:**
```bash
# Parse all memcheck logs
for LOG in "$OUTPUT_DIR"/*_memcheck.log; do
    TEST_NAME=$(basename "$LOG" _memcheck.log)

    echo "=== $TEST_NAME ==="

    # Definite leaks (CRITICAL)
    DEFINITE_LEAKS=$(grep "definitely lost:" "$LOG" | tail -1 | awk '{print $4, $5}')
    echo "Definite leaks: $DEFINITE_LEAKS"

    # Indirect leaks
    INDIRECT_LEAKS=$(grep "indirectly lost:" "$LOG" | tail -1 | awk '{print $4, $5}')
    echo "Indirect leaks: $INDIRECT_LEAKS"

    # Possible leaks
    POSSIBLE_LEAKS=$(grep "possibly lost:" "$LOG" | tail -1 | awk '{print $4, $5}')
    echo "Possible leaks: $POSSIBLE_LEAKS"

    # Still reachable (not critical)
    REACHABLE=$(grep "still reachable:" "$LOG" | tail -1 | awk '{print $4, $5}')
    echo "Still reachable: $REACHABLE"

    # Total heap usage
    TOTAL_HEAP=$(grep "total heap usage:" "$LOG" | tail -1)
    echo "Heap usage: $TOTAL_HEAP"

    # Invalid reads/writes (CRITICAL)
    INVALID_READ=$(grep -c "Invalid read" "$LOG")
    INVALID_WRITE=$(grep -c "Invalid write" "$LOG")
    echo "Invalid reads: $INVALID_READ"
    echo "Invalid writes: $INVALID_WRITE"

    echo ""
done
```

**Severity Classification:**
- **CRITICAL (Block merge):**
  - Definite leaks > 0 bytes
  - Invalid reads/writes > 0
  - Use after free

- **WARNING (Review required):**
  - Indirect leaks > 100 bytes
  - Possible leaks > 1 KB
  - Uninitialized value usage

- **INFO (Monitor):**
  - Still reachable < 10 KB (static globals, SDL resources)

---

### Mode 2: Allocation Profiling

**2a. Build with AddressSanitizer**

```bash
echo "Building with AddressSanitizer..."

# Clean build
rm -rf build/

# Configure with ASan
cmake -B build/ -G Ninja \
    -DCMAKE_BUILD_TYPE=Debug \
    -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG -fsanitize=address -fno-omit-frame-pointer -g" \
    -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
    -DUSE_MOLD_LINKER=OFF

# Build
ninja -C build
```

**Why AddressSanitizer for Allocation Profiling:**
- Tracks every allocation with stack traces
- Detects heap-buffer-overflow (buffer overruns)
- Catches use-after-free
- Identifies double-free
- ~2x slowdown (acceptable for profiling)

**2b. Run Tests with ASan**

```bash
OUTPUT_DIR="test_results/memory_profiles"
mkdir -p "$OUTPUT_DIR"

# Set ASan options
export ASAN_OPTIONS="detect_leaks=1:symbolize=1:log_path=$OUTPUT_DIR/asan"

for TEST_EXEC in "${TEST_EXECUTABLES[@]}"; do
    TEST_NAME=$(basename "$TEST_EXEC")
    echo "Running ASan on $TEST_NAME..."

    "$TEST_EXEC" --log_level=test_suite 2>&1 | tee "$OUTPUT_DIR/${TEST_NAME}_asan_output.txt"
done

unset ASAN_OPTIONS
```

**2c. Parse ASan Output**

**Look for allocation patterns:**
```bash
for OUTPUT in "$OUTPUT_DIR"/*_asan_output.txt; do
    TEST_NAME=$(basename "$OUTPUT" _asan_output.txt)

    echo "=== $TEST_NAME ASan Analysis ==="

    # Heap buffer overflow
    BUFFER_OVERFLOW=$(grep -c "heap-buffer-overflow" "$OUTPUT")
    if [ "$BUFFER_OVERFLOW" -gt 0 ]; then
        echo "🔴 CRITICAL: $BUFFER_OVERFLOW heap buffer overflows detected"
        grep -A 10 "heap-buffer-overflow" "$OUTPUT"
    fi

    # Use after free
    USE_AFTER_FREE=$(grep -c "heap-use-after-free" "$OUTPUT")
    if [ "$USE_AFTER_FREE" -gt 0 ]; then
        echo "🔴 CRITICAL: $USE_AFTER_FREE use-after-free detected"
        grep -A 10 "heap-use-after-free" "$OUTPUT"
    fi

    # Double free
    DOUBLE_FREE=$(grep -c "attempting double-free" "$OUTPUT")
    if [ "$DOUBLE_FREE" -gt 0 ]; then
        echo "🔴 CRITICAL: $DOUBLE_FREE double-free detected"
        grep -A 10 "attempting double-free" "$OUTPUT"
    fi

    # Allocation summary
    grep "alloc-dealloc-mismatch" "$OUTPUT" || echo "✅ No alloc-dealloc mismatches"

    echo ""
done
```

**2d. Identify Per-Frame Allocation Hotspots**

**Search for hot-path allocations in code:**
```bash
echo "=== Per-Frame Allocation Hotspot Analysis ==="

# Check for allocations in update loops
echo "Searching for potential per-frame allocations..."

# AIManager update loop
grep -n "std::vector" src/managers/AIManager.cpp | grep -i "update\|process" || echo "✅ AIManager: No obvious vector allocations in update"

# CollisionManager
grep -n "std::vector" src/managers/CollisionManager.cpp | grep -i "update\|detect" || echo "✅ CollisionManager: No obvious vector allocations in update"

# ParticleManager
grep -n "std::vector" src/managers/ParticleManager.cpp | grep -i "update\|render" || echo "✅ ParticleManager: No obvious vector allocations in update"

# Look for allocations inside loops
echo ""
echo "Checking for allocations inside loops (MAJOR ISSUE)..."
grep -A 5 "for\|while" src/managers/*.cpp | grep "std::vector\|std::make" | head -20
```

**Per-Frame Allocation Patterns to Flag:**
```cpp
// 🔴 BAD: Allocates every frame
void update() {
    std::vector<Data> buffer;  // Fresh allocation
    buffer.reserve(entityCount);
    // ... use buffer
}  // Deallocation

// 🔴 BAD: Allocation in loop
for (size_t i = 0; i < count; ++i) {
    std::vector<Item> items;  // Allocation per iteration!
    // ...
}

// 🔴 BAD: No reserve before push_back loop
std::vector<Entity> entities;
for (...) {
    entities.push_back(entity);  // Incremental reallocations
}
```

---

### Mode 2b: Thread Safety Validation (ThreadSanitizer)

**Use ThreadSanitizer (TSAN) for:**
- Data race detection in multi-threaded code
- Deadlock detection
- Thread synchronization issues
- **Use when:** Testing threading systems (AIManager, EventManager, ParticleManager threading tests)

**Important:** ThreadSanitizer and AddressSanitizer are **mutually exclusive** - use one or the other, not both.

**2a. Build with ThreadSanitizer**

```bash
echo "Building with ThreadSanitizer..."

# Clean build
rm -rf build/

# Configure with TSan
cmake -B build/ -G Ninja \
    -DCMAKE_BUILD_TYPE=Debug \
    -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG -fsanitize=thread -fno-omit-frame-pointer -g" \
    -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" \
    -DUSE_MOLD_LINKER=OFF

# Build
ninja -C build
```

**Why ThreadSanitizer:**
- Detects data races at runtime (reads/writes without synchronization)
- Finds deadlocks and lock order violations
- Validates thread-safe container usage
- ~5-15x slowdown (acceptable for thread safety validation)

**2b. Run Threading Tests with TSan**

```bash
OUTPUT_DIR="test_results/memory_profiles"
mkdir -p "$OUTPUT_DIR"

# Threading-focused tests
THREAD_TESTS=(
    "./bin/debug/thread_system_tests"
    "./bin/debug/thread_safe_ai_manager_tests"
    "./bin/debug/thread_safe_ai_integration_tests"
    "./bin/debug/particle_manager_threading_tests"
    "./bin/debug/event_coordination_integration_tests"
)

for TEST_EXEC in "${THREAD_TESTS[@]}"; do
    TEST_NAME=$(basename "$TEST_EXEC")
    echo "Running TSan on $TEST_NAME..."

    "$TEST_EXEC" --log_level=test_suite 2>&1 | tee "$OUTPUT_DIR/${TEST_NAME}_tsan_output.txt"
done
```

**2c. Parse TSan Output**

**Look for thread safety violations:**
```bash
for OUTPUT in "$OUTPUT_DIR"/*_tsan_output.txt; do
    TEST_NAME=$(basename "$OUTPUT" _tsan_output.txt)

    echo "=== $TEST_NAME TSan Analysis ==="

    # Data races
    DATA_RACES=$(grep -c "WARNING: ThreadSanitizer: data race" "$OUTPUT")
    if [ "$DATA_RACES" -gt 0 ]; then
        echo "🔴 CRITICAL: $DATA_RACES data race(s) detected"
        grep -A 15 "WARNING: ThreadSanitizer: data race" "$OUTPUT"
    fi

    # Deadlocks
    DEADLOCKS=$(grep -c "WARNING: ThreadSanitizer: lock-order-inversion" "$OUTPUT")
    if [ "$DEADLOCKS" -gt 0 ]; then
        echo "🔴 CRITICAL: $DEADLOCKS potential deadlock(s) detected"
        grep -A 15 "WARNING: ThreadSanitizer: lock-order-inversion" "$OUTPUT"
    fi

    # Thread leaks
    THREAD_LEAKS=$(grep -c "WARNING: ThreadSanitizer: thread leak" "$OUTPUT")
    if [ "$THREAD_LEAKS" -gt 0 ]; then
        echo "⚠️  WARNING: $THREAD_LEAKS thread leak(s) detected"
    fi

    if [ "$DATA_RACES" -eq 0 ] && [ "$DEADLOCKS" -eq 0 ] && [ "$THREAD_LEAKS" -eq 0 ]; then
        echo "✅ No thread safety issues detected"
    fi

    echo ""
done
```

**Severity Classification:**
- **CRITICAL (Block merge):**
  - Data races (concurrent reads/writes without synchronization)
  - Deadlocks or lock-order inversions

- **WARNING (Review required):**
  - Thread leaks (threads not properly joined)
  - Signal-unsafe function calls

---

### Mode 3: Full Memory Profile (Massif)

**3a. Run Valgrind Massif**

```bash
OUTPUT_DIR="test_results/memory_profiles"
mkdir -p "$OUTPUT_DIR"

for TEST_EXEC in "${TEST_EXECUTABLES[@]}"; do
    TEST_NAME=$(basename "$TEST_EXEC")
    echo "Running massif on $TEST_NAME..."

    valgrind \
        --tool=massif \
        --massif-out-file="$OUTPUT_DIR/${TEST_NAME}_massif.out" \
        --time-unit=ms \
        --detailed-freq=1 \
        --max-snapshots=100 \
        --threshold=0.1 \
        "$TEST_EXEC" --log_level=test_suite
done
```

**Massif Flags:**
- `--tool=massif`: Heap profiler
- `--time-unit=ms`: Time in milliseconds
- `--detailed-freq=1`: Detailed snapshot frequency
- `--max-snapshots=100`: Store up to 100 snapshots
- `--threshold=0.1`: Capture 0.1% heap changes

**3b. Parse Massif Output**

```bash
for MASSIF in "$OUTPUT_DIR"/*_massif.out; do
    TEST_NAME=$(basename "$MASSIF" _massif.out)

    echo "=== $TEST_NAME Massif Analysis ==="

    # Generate text report
    ms_print "$MASSIF" > "$OUTPUT_DIR/${TEST_NAME}_massif_report.txt"

    # Extract peak memory
    PEAK_MEM=$(grep "peak" "$MASSIF" | head -1)
    echo "Peak memory: $PEAK_MEM"

    # Extract allocation sites (top 10)
    echo ""
    echo "Top 10 allocation sites:"
    ms_print "$MASSIF" | grep -A 1 "->.*%" | head -20

    echo ""
done
```

**3c. System-by-System Breakdown**

**Extract memory usage by manager:**
```bash
echo "=== Memory Usage by System ==="

# Parse massif reports for specific managers
for REPORT in "$OUTPUT_DIR"/*_massif_report.txt; do
    echo ""
    echo "Report: $(basename "$REPORT")"

    # AIManager allocations
    AI_ALLOCS=$(grep -c "AIManager" "$REPORT")
    echo "  AIManager allocations: $AI_ALLOCS"

    # CollisionManager allocations
    COLLISION_ALLOCS=$(grep -c "CollisionManager" "$REPORT")
    echo "  CollisionManager allocations: $COLLISION_ALLOCS"

    # PathfinderManager allocations
    PATHFINDER_ALLOCS=$(grep -c "PathfinderManager" "$REPORT")
    echo "  PathfinderManager allocations: $PATHFINDER_ALLOCS"

    # EventManager allocations
    EVENT_ALLOCS=$(grep -c "EventManager" "$REPORT")
    echo "  EventManager allocations: $EVENT_ALLOCS"

    # ParticleManager allocations
    PARTICLE_ALLOCS=$(grep -c "ParticleManager" "$REPORT")
    echo "  ParticleManager allocations: $PARTICLE_ALLOCS"
done
```

---

### Mode 4: Buffer Reuse Audit

**4a. Scan for Buffer Reuse Patterns**

**Search for reusable buffers (member variables):**
```bash
echo "=== Buffer Reuse Pattern Audit ==="

# Find all manager classes
MANAGERS=$(find include/managers -name "*.hpp" -type f)

for MANAGER in $MANAGERS; do
    MANAGER_NAME=$(basename "$MANAGER" .hpp)
    echo ""
    echo "=== $MANAGER_NAME ==="

    # Check for member vector variables (potential reuse buffers)
    echo "Member vectors (should be reused):"
    grep "std::vector" "$MANAGER" | grep "m_" | head -10

    # Check implementation for clear() usage
    CPP_FILE="src/managers/${MANAGER_NAME}.cpp"
    if [ -f "$CPP_FILE" ]; then
        CLEAR_COUNT=$(grep -c "\.clear()" "$CPP_FILE")
        echo "clear() calls: $CLEAR_COUNT (good - reuses capacity)"

        # Flag missing reserve() calls
        RESERVE_COUNT=$(grep -c "\.reserve(" "$CPP_FILE")
        echo "reserve() calls: $RESERVE_COUNT"

        if [ "$RESERVE_COUNT" -eq 0 ]; then
            echo "⚠️  WARNING: No reserve() calls found - check for incremental reallocations"
        fi
    fi
done
```

**4b. Check for Buffer Reuse Anti-Patterns**

**Common anti-patterns:**
```bash
echo ""
echo "=== Checking for Anti-Patterns ==="

# Anti-pattern 1: Local vectors in update functions
echo "1. Local vectors in update functions (should be members):"
grep -n "void.*update\|void.*process" src/managers/*.cpp | while read -r line; do
    FILE=$(echo "$line" | cut -d: -f1)
    LINE_NUM=$(echo "$line" | cut -d: -f2)

    # Check 20 lines after function definition for std::vector
    sed -n "${LINE_NUM},$((LINE_NUM+20))p" "$FILE" | grep -n "std::vector" | while read -r vec_line; do
        echo "  $FILE:$((LINE_NUM + $(echo "$vec_line" | cut -d: -f1))) - Local vector in update"
    done
done

# Anti-pattern 2: Vector reconstruction instead of clear()
echo ""
echo "2. Vector reconstruction (use clear() instead):"
for CPP in src/managers/*.cpp; do
    # Look for pattern: vectorName = std::vector<Type>();
    grep -n "= std::vector<" "$CPP" | head -5
done

# Anti-pattern 3: Push_back without reserve
echo ""
echo "3. push_back loops without prior reserve():"
for CPP in src/managers/*.cpp; do
    echo "  Checking $CPP..."
    # This is approximate - look for push_back in loops
    grep -B 5 "push_back" "$CPP" | grep "for\|while" | head -3
done
```

**4c. Verify CLAUDE.md Buffer Patterns**

**Good patterns from CLAUDE.md:**
```bash
echo ""
echo "=== Verifying CLAUDE.md Buffer Patterns ==="

# Pattern 1: Member variables for hot-path buffers
echo "1. Checking for member buffer variables..."
for MANAGER in include/managers/*.hpp; do
    MANAGER_NAME=$(basename "$MANAGER" .hpp)
    MEMBER_BUFFERS=$(grep "m_.*Buffer\|m_.*Cache\|m_.*Results" "$MANAGER" | wc -l)
    echo "  $MANAGER_NAME: $MEMBER_BUFFERS reusable buffers"
done

# Pattern 2: clear() over reconstruction
echo ""
echo "2. Checking clear() usage (capacity preservation)..."
for CPP in src/managers/*.cpp; do
    CLEAR_COUNT=$(grep -c "\.clear()" "$CPP")
    RECONSTRUCT_COUNT=$(grep -c "= std::vector" "$CPP")
    echo "  $(basename "$CPP"): clear() = $CLEAR_COUNT, reconstruct = $RECONSTRUCT_COUNT"

    if [ "$RECONSTRUCT_COUNT" -gt "$CLEAR_COUNT" ]; then
        echo "    ⚠️  More reconstructions than clears - check for capacity loss"
    fi
done

# Pattern 3: reserve() before loops
echo ""
echo "3. Checking reserve() before insertion loops..."
for CPP in src/managers/*.cpp; do
    echo "  $(basename "$CPP"):"
    grep -B 3 "for.*push_back\|while.*push_back" "$CPP" | grep "reserve(" || echo "    ⚠️  No reserve() found before push_back loops"
done
```

---

## Step 3: Baseline Comparison (if requested)

**3a. Load Baseline Metrics**

```bash
BASELINE_DIR="test_results/memory_profiles/baseline"

if [ -d "$BASELINE_DIR" ] && [ "$COMPARE_BASELINE" = "Yes" ]; then
    echo "=== Baseline Comparison ==="

    # Compare leak counts
    for LOG in "$OUTPUT_DIR"/*_memcheck.log; do
        TEST_NAME=$(basename "$LOG" _memcheck.log)
        BASELINE_LOG="$BASELINE_DIR/${TEST_NAME}_memcheck.log"

        if [ -f "$BASELINE_LOG" ]; then
            echo ""
            echo "Test: $TEST_NAME"

            # Current leaks
            CURRENT_LEAKS=$(grep "definitely lost:" "$LOG" | tail -1 | awk '{print $4}')
            CURRENT_LEAKS=${CURRENT_LEAKS:-0}

            # Baseline leaks
            BASELINE_LEAKS=$(grep "definitely lost:" "$BASELINE_LOG" | tail -1 | awk '{print $4}')
            BASELINE_LEAKS=${BASELINE_LEAKS:-0}

            # Compare
            if [ "$CURRENT_LEAKS" -gt "$BASELINE_LEAKS" ]; then
                DELTA=$((CURRENT_LEAKS - BASELINE_LEAKS))
                echo "  🔴 REGRESSION: +$DELTA bytes leaked (was $BASELINE_LEAKS, now $CURRENT_LEAKS)"
            elif [ "$CURRENT_LEAKS" -lt "$BASELINE_LEAKS" ]; then
                DELTA=$((BASELINE_LEAKS - CURRENT_LEAKS))
                echo "  🟢 IMPROVEMENT: -$DELTA bytes leaked (was $BASELINE_LEAKS, now $CURRENT_LEAKS)"
            else
                echo "  ⚪ STABLE: $CURRENT_LEAKS bytes leaked (unchanged)"
            fi

            # Compare heap usage
            CURRENT_HEAP=$(grep "total heap usage:" "$LOG" | tail -1 | awk '{print $5}')
            BASELINE_HEAP=$(grep "total heap usage:" "$BASELINE_LOG" | tail -1 | awk '{print $5}')

            if [ ! -z "$CURRENT_HEAP" ] && [ ! -z "$BASELINE_HEAP" ]; then
                HEAP_DELTA=$((CURRENT_HEAP - BASELINE_HEAP))
                echo "  Total allocs: $CURRENT_HEAP (baseline: $BASELINE_HEAP, delta: $HEAP_DELTA)"
            fi
        fi
    done
fi
```

**3b. Save as New Baseline (if requested)**

```bash
if [ "$BASELINE_MODE" = "Create new baseline" ]; then
    echo ""
    echo "=== Saving New Baseline ==="

    mkdir -p "$BASELINE_DIR"

    # Copy current results to baseline
    cp "$OUTPUT_DIR"/*_memcheck.log "$BASELINE_DIR/" 2>/dev/null || true
    cp "$OUTPUT_DIR"/*_massif.out "$BASELINE_DIR/" 2>/dev/null || true

    # Save metadata
    cat > "$BASELINE_DIR/baseline_metadata.txt" <<EOF
Baseline created: $(date)
Branch: $(git rev-parse --abbrev-ref HEAD)
Commit: $(git rev-parse HEAD)
Tests included: $(ls "$OUTPUT_DIR"/*_memcheck.log | wc -l)
EOF

    echo "✅ Baseline saved to $BASELINE_DIR"
fi
```

---

## Step 4: Generate Memory Profile Report

**Report Structure:**

```markdown
# HammerEngine Memory Profile Report

**Generated:** YYYY-MM-DD HH:MM:SS
**Branch:** <current-branch>
**Commit:** <commit-hash>
**Profiling Mode:** <mode>
**Test Scope:** <scope>

---

## Executive Summary

**Overall Status:** ✅ CLEAN / ⚠️ WARNINGS / 🔴 CRITICAL ISSUES

**Key Findings:**
- [Finding 1]
- [Finding 2]
- [Finding 3]

**Memory Health:** [Excellent/Good/Fair/Poor]

---

## Leak Detection Results

### Critical Leaks (BLOCKING)

| Test | Definite Leaks | Invalid Access | Status |
|------|----------------|----------------|--------|
| [Test 1] | [X bytes] | [N violations] | 🔴/✅ |
| [Test 2] | [X bytes] | [N violations] | 🔴/✅ |

**Total Definite Leaks:** [X bytes] (Target: 0 bytes)

### Leak Details

[For each test with leaks, include:]

**Test:** [test_name]
**Leak Location:** [file:line]
**Stack Trace:**
```
[valgrind stack trace]
```

**Likely Cause:** [Analysis]
**Suggested Fix:** [Specific code change]

---

## Allocation Profiling (if Mode 2)

### Per-Frame Allocation Hotspots

| System | Allocations/Frame | Impact | Status |
|--------|-------------------|--------|--------|
| AIManager | [N] | [Frame spike: Xms] | ⚠️/✅ |
| CollisionManager | [N] | [Frame spike: Xms] | ⚠️/✅ |
| ParticleManager | [N] | [Frame spike: Xms] | ⚠️/✅ |

**Total Per-Frame Allocations:** [N] (Target: 0 in hot paths)

### Anti-Pattern Violations

**1. Local Vectors in Update Functions:**
- `AIManager.cpp:123` - `std::vector<Data> localBuffer;` in `processBatch()`
  - **Fix:** Make `m_processingBuffer` member variable, use `clear()` per frame

**2. Push_back Without Reserve:**
- `CollisionManager.cpp:456` - Loop with `results.push_back()` without `reserve()`
  - **Fix:** Add `results.reserve(expectedCount);` before loop

**3. Vector Reconstruction:**
- `PathfinderManager.cpp:789` - `m_pathCache = std::vector<Path>();`
  - **Fix:** Replace with `m_pathCache.clear();` to preserve capacity

---

## Memory Usage by System (if Mode 3)

### Peak Memory

| System | Peak Allocation | % of Total | Trend |
|--------|----------------|------------|-------|
| AIManager | [X MB] | [%] | 📈/📉/➡️ |
| CollisionManager | [X MB] | [%] | 📈/📉/➡️ |
| PathfinderManager | [X MB] | [%] | 📈/📉/➡️ |
| EventManager | [X MB] | [%] | 📈/📉/➡️ |
| ParticleManager | [X MB] | [%] | 📈/📉/➡️ |
| **Total** | **[X MB]** | **100%** | - |

### Top Allocation Sites

1. **AIManager::processBatch()** - [X MB] ([%] of total)
   - [N] allocations
   - Stack trace: [abbreviated]

2. **CollisionManager::detectCollisions()** - [X MB] ([%] of total)
   - [N] allocations
   - Stack trace: [abbreviated]

---

## Buffer Reuse Audit (if Mode 4)

### Pattern Compliance

| Manager | Member Buffers | clear() Usage | reserve() Usage | Grade |
|---------|----------------|---------------|-----------------|-------|
| AIManager | ✅ Yes | ✅ Correct | ✅ Present | A |
| CollisionManager | ✅ Yes | ⚠️ Partial | ❌ Missing | C |
| ParticleManager | ❌ No | ❌ Local vars | ❌ Missing | F |

**Overall Compliance:** [%] (Target: 100%)

### Recommendations

**AIManager:**
- ✅ Excellent buffer reuse pattern
- Document as reference implementation

**CollisionManager:**
- ⚠️ Add `reserve()` calls in `detectCollisions()` before `results.push_back()` loop
- Estimated improvement: -50 allocs/frame

**ParticleManager:**
- 🔴 Critical: Replace local `std::vector<Particle> activeParticles;` with member `m_activeParticles`
- 🔴 Add `m_activeParticles.clear()` at start of `update()`
- 🔴 Add `reserve(maxParticles)` in constructor
- Estimated improvement: -200 allocs/frame

---

## Baseline Comparison (if applicable)

### Leak Trend

| Test | Baseline | Current | Change | Status |
|------|----------|---------|--------|--------|
| [Test 1] | [X bytes] | [X bytes] | [+/-] | 🔴/🟢/⚪ |

### Memory Usage Trend

[Chart or table showing memory usage over time]

**Overall Trend:** [Improving/Stable/Degrading]

---

## Optimization Opportunities

### High Priority (Immediate Fix)

1. **ParticleManager: Eliminate per-frame allocations**
   - **Current:** 200 allocs/frame (~128 KB/frame)
   - **Impact:** Frame spikes of 5-10ms
   - **Fix:** [Specific code changes]
   - **Expected Improvement:** -5ms frame time

2. **CollisionManager: Add reserve() calls**
   - **Current:** Incremental reallocations in query results
   - **Impact:** 1-2ms overhead
   - **Fix:** [Specific code changes]
   - **Expected Improvement:** -1ms query time

### Medium Priority

3. **AIManager: Increase batch buffer size**
   - **Current:** 1024 entities, reallocs when exceeded
   - **Fix:** Increase to 2048 or make dynamic
   - **Expected Improvement:** Eliminate rare reallocs

### Low Priority

4. **EventManager: Consider event pool**
   - **Current:** Event objects allocated per dispatch
   - **Fix:** Implement object pool for event reuse
   - **Expected Improvement:** -10% event dispatch time

---

## Specific Code Fixes

### Fix 1: ParticleManager Buffer Reuse

**File:** `include/managers/ParticleManager.hpp:45`

**Before:**
```cpp
class ParticleManager
{
    // ... no reusable buffer
};
```

**After:**
```cpp
class ParticleManager
{
    std::vector<Particle> m_activeParticles;  // Reusable buffer
    // ... reserve in constructor
};
```

**File:** `src/managers/ParticleManager.cpp:123`

**Before:**
```cpp
void ParticleManager::update(float deltaTime)
{
    std::vector<Particle> activeParticles;  // Allocation every frame!
    // ... use activeParticles
}
```

**After:**
```cpp
void ParticleManager::update(float deltaTime)
{
    m_activeParticles.clear();  // Reuse capacity
    // ... use m_activeParticles
}
```

**File:** `src/managers/ParticleManager.cpp:34` (constructor)

**Add:**
```cpp
ParticleManager::ParticleManager()
{
    m_activeParticles.reserve(MAX_PARTICLES);  // Pre-allocate
}
```

---

## Test Results Summary

**Tests Run:** [N]
**Tests Passed:** [N]
**Critical Issues:** [N]
**Warnings:** [N]

**Status:** [✅ CLEAN / ⚠️ NEEDS REVIEW / 🔴 FIX REQUIRED]

---

## Action Items

### Critical (Fix Before Commit)
- [ ] [Critical issue 1]
- [ ] [Critical issue 2]

### Important (Fix Soon)
- [ ] [Important issue 1]
- [ ] [Important issue 2]

### Optional (Consider)
- [ ] [Optional improvement 1]
- [ ] [Optional improvement 2]

---

## Files Modified (Recommended)

Based on findings, these files should be modified:

```
include/managers/ParticleManager.hpp      (add member buffer)
src/managers/ParticleManager.cpp          (use buffer, add clear/reserve)
src/managers/CollisionManager.cpp         (add reserve calls)
```

---

## Next Steps

1. **If critical issues:** Fix immediately, re-run profile to verify
2. **If warnings:** Review and plan fixes
3. **If clean:** Update baseline (save as reference)
4. **Consider:** Run full benchmark suite to measure performance impact

**Re-run Profile:**
```bash
# After fixes, re-run to verify
[Command to re-invoke this skill]
```

---

**Report Generated By:** hammer-memory-profiler Skill
**Report Saved To:** `test_results/memory_profiles/memory_profile_YYYY-MM-DD.md`
```

**Save report to:**
```bash
REPORT_FILE="test_results/memory_profiles/memory_profile_$(date +%Y-%m-%d_%H-%M-%S).md"
cat > "$REPORT_FILE" <<'EOF'
[Generated markdown report]
EOF

echo "✅ Memory profile report saved to: $REPORT_FILE"
```

---

## Step 5: Console Summary

**Output to user:**

```
=== HammerEngine Memory Profile ===

Mode: [Mode Name]
Scope: [Test Scope]
Duration: [Time taken]

Overall Status: [✅ CLEAN / ⚠️ WARNINGS / 🔴 CRITICAL]

Critical Issues: [N]
Warnings: [N]

[If critical:]
🔴 CRITICAL ISSUES FOUND - DO NOT COMMIT
  - [Issue 1]
  - [Issue 2]

[If warnings:]
⚠️  WARNINGS DETECTED - REVIEW RECOMMENDED
  - [Warning 1]
  - [Warning 2]

[If clean:]
✅ NO MEMORY ISSUES DETECTED
  - 0 bytes leaked
  - 0 invalid access violations
  - Buffer reuse patterns correct

Memory Usage:
  - Peak: [X MB]
  - Total allocations: [N]
  - Per-frame allocations: [N] (Target: 0)

Top Allocation Sites:
  1. [System] - [X MB]
  2. [System] - [X MB]
  3. [System] - [X MB]

Baseline Comparison: [If applicable]
  - Leaks: [+/-X bytes]
  - Allocations: [+/-N]
  - Trend: [Improving/Stable/Degrading]

Full Report: test_results/memory_profiles/memory_profile_YYYY-MM-DD.md

Next Steps:
  [If critical] - Fix issues and re-run profile
  [If clean] - Update baseline: "update memory baseline"
```

---

## Usage Examples

When the user says:
- "profile memory usage"
- "check for memory leaks"
- "analyze memory allocations"
- "audit buffer reuse patterns"
- "find allocation hotspots"
- "check per-frame allocations"
- "memory profile AI system"

Activate this Skill automatically.

---

## Integration with Development Workflow

**Use this Skill:**

### Daily Development
- Quick leak check before commits
- Catches leaks early (cheaper to fix)

### Performance Investigation
- Allocation profiling when diagnosing frame spikes
- Identifies per-frame allocation culprits

### Major Changes
- Full profile after adding new managers
- Verify memory usage within budget

### Release Preparation
- Comprehensive profile before releases
- Ensure no regressions since last baseline

### Periodic Audits
- Monthly buffer reuse audit
- Maintain code quality over time

---

## Common Memory Issues in HammerEngine

### Issue 1: Per-Frame Allocations (Frame Spikes)

**Symptom:** Periodic frame drops every 1-2 seconds
**Cause:** Heap allocations in update loop triggering OS paging
**Solution:** Member buffer + clear() pattern from CLAUDE.md

### Issue 2: Missing reserve() Calls

**Symptom:** Gradual frame time increase with entity count
**Cause:** Incremental vector reallocations (2x growth pattern)
**Solution:** Pre-calculate size, call reserve() before loop

### Issue 3: SDL Resource Leaks

**Symptom:** "Still reachable" leaks from SDL
**Cause:** Missing SDL_Destroy calls in destructors
**Solution:** Ensure proper cleanup in manager destructors

### Issue 4: Thread-Safe Container Allocations

**Symptom:** Allocation contention visible in profiler
**Cause:** Multiple threads allocating from same heap
**Solution:** Thread-local buffers or per-thread allocators

### Issue 5: Smart Pointer Overhead

**Symptom:** High allocation rate despite buffer reuse
**Cause:** Unnecessary shared_ptr copies (atomic ref-count ops)
**Solution:** Use raw pointers in hot paths (see CLAUDE.md)

---

## Performance Expectations

- **Quick Leak Check:** 2-5 minutes (3-5 core tests)
- **Allocation Profiling:** 5-10 minutes (rebuild + targeted tests)
- **Full Memory Profile:** 15-30 minutes (massif + all systems)
- **Buffer Reuse Audit:** 10-15 minutes (code scanning)

**Manual Equivalent:** 45-90 minutes per profiling session

---

## Exit Codes

- **0:** No memory issues detected
- **1:** Critical leaks detected (BLOCKING)
- **2:** Warnings detected (review required)
- **3:** Buffer reuse violations (performance impact)
- **4:** Baseline comparison shows regression

---

## Important Notes

1. **Always profile in Debug mode** - Release optimizations hide issues
2. **Run on quiet system** - Background processes affect results
3. **Compare against baseline** - Trends matter more than absolutes
4. **Fix critical issues immediately** - Don't accumulate memory debt
5. **Document patterns** - Share good buffer reuse examples

---

**Ready to profile HammerEngine memory usage. Ask user for profiling mode and scope.**

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.