
hammer-memory-profiler
by Ronin15
SDL3 2D game Engine/Template implementing DOD, multi-threading and SIMD, with a focus on memory safety, efficiency, and speed.
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 memcheckrun_massif_all_tests.sh- Run valgrind massif on all test executablesparse_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:
- Leak Detection - Find memory leaks before production
- Allocation Profiling - Identify per-frame allocation hotspots (frame spikes)
- Buffer Reuse Verification - Ensure CLAUDE.md buffer patterns followed
- System Breakdown - Track memory usage per manager (AI, Collision, etc.)
- Baseline Comparison - Monitor memory usage trends over time
- 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
# 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:
TEST_EXECUTABLES=( "./bin/debug/thread_system_tests" "./bin/debug/buffer_utilization_tests" "./bin/debug/event_manager_tests" ) -
AI System:
TEST_EXECUTABLES=( "./bin/debug/thread_safe_ai_manager_tests" "./bin/debug/ai_optimization_tests" "./bin/debug/behavior_functionality_tests" ) -
Collision/Pathfinding:
TEST_EXECUTABLES=( "./bin/debug/collision_system_tests" "./bin/debug/pathfinder_manager_tests" "./bin/debug/collision_pathfinding_integration_tests" ) -
All Systems:
TEST_EXECUTABLES=( # Run all test executables in bin/debug/ )
Valgrind Command Template:
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:
# 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
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
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:
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:
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:
// 🔴 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
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
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:
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
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
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:
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):
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:
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:
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
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)
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:
# 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:
class ParticleManager
{
std::vector<Particle> m_activeParticles; // Reusable buffer
// ... reserve in constructor
};
File: src/managers/ParticleManager.cpp:123
Before:
void ParticleManager::update(float deltaTime)
{
std::vector<Particle> activeParticles; // Allocation every frame!
// ... use activeParticles
}
After:
void ParticleManager::update(float deltaTime)
{
m_activeParticles.clear(); // Reuse capacity
// ... use m_activeParticles
}
File: src/managers/ParticleManager.cpp:34 (constructor)
Add:
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
- If critical issues: Fix immediately, re-run profile to verify
- If warnings: Review and plan fixes
- If clean: Update baseline (save as reference)
- Consider: Run full benchmark suite to measure performance impact
Re-run Profile:
# 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
- Always profile in Debug mode - Release optimizations hide issues
- Run on quiet system - Background processes affect results
- Compare against baseline - Trends matter more than absolutes
- Fix critical issues immediately - Don't accumulate memory debt
- Document patterns - Share good buffer reuse examples
Ready to profile HammerEngine memory usage. Ask user for profiling mode and scope.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon