Flamegraph Visualization
Flamegraphs are a powerful visualization technique for profiling data, showing call stacks and their time distribution. This chapter shows how to export Renacer's profiling data and visualize it with external flamegraph tools.
TDD-Verified: Export functionality tested in
tests/sprint22_html_output_tests.rs, profiling intests/sprint13_function_profiling_tests.rs
Parent Chapter: See Function Profiling for overview and basic usage.
Overview
Flamegraphs visualize call stacks with:
- X-axis: Alphabetically sorted function names (not time!)
- Y-axis: Stack depth (call hierarchy)
- Width: Proportion of total time spent in function
- Color: Usually random (for visual distinction) or can indicate metrics
Why flamegraphs?
- Visual bottleneck identification - Wide bars = hot code paths
- Call hierarchy understanding - See parent-child relationships
- Interactive exploration - Click to zoom, search functions
- Shareable analysis - Export as SVG for reports
Example flamegraph:
┌─────────────────────────────────────────────┐ 100% (main)
│ main() │
├─────────────┬───────────────────────────────┤
│ process() │ network_call() │ 60%
│ 40% │ │
├─────┬───────┼───────┬───────────────────────┤
│read │write │connect│ send_request() │ 35%
│ 20% │ 20% │ 5% │ │
└─────┴───────┴───────┴───────────────────────┘
Interpretation: send_request() is the bottleneck (35% of total time).
Exporting Profiling Data
Renacer supports JSON export for post-processing:
$ renacer --function-time --source --format json -- ./myapp > profile.json
Tested by: test_html_format_flag_accepted (Sprint 22 - verifies format flag acceptance)
JSON Output Structure:
{
"syscalls": [...],
"function_profile": [
{
"function": "src/main.rs:42",
"calls": 150,
"total_time_us": 234567,
"avg_time_us": 1563,
"slow_io_count": 148
},
...
],
"summary": {
"total_syscalls": 500,
"total_duration_ms": 580.245
}
}
Generating Flamegraphs
Method 1: Using Brendan Gregg's Flamegraph Tools
Prerequisites:
$ git clone https://github.com/brendangregg/FlameGraph
$ cd FlameGraph
Step 1: Convert JSON to Folded Stack Format
Create a conversion script renacer_to_folded.py:
#!/usr/bin/env python3
import json
import sys
# Read Renacer JSON output
with open(sys.argv[1]) as f:
data = json.load(f)
# Convert function profile to folded stacks
for func in data.get('function_profile', []):
function_name = func['function']
# Use total time (microseconds) as sample count
samples = func['total_time_us']
# Format: function_name samples
print(f"{function_name} {samples}")
Step 2: Generate Flamegraph
# Convert Renacer JSON → folded stacks
$ python3 renacer_to_folded.py profile.json > profile.folded
# Generate flamegraph SVG
$ ./FlameGraph/flamegraph.pl profile.folded > flamegraph.svg
# Open in browser
$ firefox flamegraph.svg
Result: Interactive SVG flamegraph showing function time distribution!
Method 2: Using speedscope.app
Speedscope is a web-based flamegraph viewer:
Step 1: Export profiling data
$ renacer --function-time --source --format json -- ./myapp > profile.json
Step 2: Visit speedscope.app
$ firefox https://www.speedscope.app/
Step 3: Upload profile.json
Note: Speedscope expects specific JSON formats (Chrome timeline, Firefox profiler, etc.). Renacer's format may need conversion. Use Method 1 (Brendan Gregg's tools) for simplest workflow.
Method 3: Manual HTML Visualization
For small datasets, create a simple HTML visualization:
<!DOCTYPE html>
<html>
<head>
<title>Renacer Flamegraph</title>
<style>
.bar { margin: 2px; padding: 5px; background: #f90; }
.function { font-family: monospace; }
.time { float: right; color: #666; }
</style>
</head>
<body>
<h1>Function Profile</h1>
<div id="chart"></div>
<script>
// Load profile.json (served via http server)
fetch('profile.json')
.then(r => r.json())
.then(data => {
const total = data.summary.total_duration_ms * 1000; // Convert to μs
const chart = document.getElementById('chart');
data.function_profile
.sort((a, b) => b.total_time_us - a.total_time_us)
.forEach(func => {
const pct = (func.total_time_us / total * 100).toFixed(1);
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.width = pct + '%';
bar.innerHTML = `
<span class="function">${func.function}</span>
<span class="time">${func.total_time_us}μs (${pct}%)</span>
`;
chart.appendChild(bar);
});
});
</script>
</body>
</html>
Serve and view:
$ python3 -m http.server 8080
$ firefox http://localhost:8080/
Practical Examples
Example 1: Identify Database Bottlenecks
Scenario: Web application with database calls
# Profile application
$ renacer --function-time --source --format json -e trace=network -- ./webapp > profile.json
# Convert to flamegraph
$ python3 renacer_to_folded.py profile.json | ./FlameGraph/flamegraph.pl > db-bottlenecks.svg
Flamegraph shows:
- Wide bar for
execute_query()→ Database calls dominate execution time - Narrow bar for
cache_lookup()→ Fast cache hits - Medium bar for
json_serialize()→ Moderate CPU time
Action: Add caching layer to reduce wide execute_query() bar.
Example 2: Build System Analysis
Scenario: Slow cargo build
$ renacer --function-time --source --format json -e trace=file -- cargo build > build-profile.json
$ python3 renacer_to_folded.py build-profile.json | ./FlameGraph/flamegraph.pl > build-flame.svg
Flamegraph shows:
- Wide bar for
rustc:link→ Linking is the bottleneck - Narrow bars for
cargo:download_crate→ Dependencies already cached - Medium bar for
rustc:codegen→ Compilation time is acceptable
Action: Enable incremental compilation to reduce linking time.
Example 3: Comparing Before/After Optimizations
Before optimization:
$ renacer --function-time --source --format json -- ./app-v1 > before.json
$ python3 renacer_to_folded.py before.json | ./FlameGraph/flamegraph.pl > before.svg
After optimization:
$ renacer --function-time --source --format json -- ./app-v2 > after.json
$ python3 renacer_to_folded.py after.json | ./FlameGraph/flamegraph.pl > after.svg
Compare side-by-side:
$ firefox before.svg after.svg
Look for:
- Narrower bars (faster functions)
- Shorter stacks (reduced call depth)
- Fewer wide bars (eliminated bottlenecks)
Advanced Workflows
Filtering Hot Paths Only
Show only functions consuming >1% of total time:
#!/usr/bin/env python3
import json
import sys
with open(sys.argv[1]) as f:
data = json.load(f)
total_time = data['summary']['total_duration_ms'] * 1000 # μs
threshold = total_time * 0.01 # 1% threshold
for func in data.get('function_profile', []):
if func['total_time_us'] > threshold:
print(f"{func['function']} {func['total_time_us']}")
Usage:
$ python3 filter_hot_paths.py profile.json | ./FlameGraph/flamegraph.pl > hot-paths.svg
Result: Cleaner flamegraph focusing on significant bottlenecks.
Differential Flamegraphs
Compare two profiles to see what changed:
# Requires flamegraph-diff tool
$ python3 renacer_to_folded.py before.json > before.folded
$ python3 renacer_to_folded.py after.json > after.folded
$ ./FlameGraph/difffolded.pl before.folded after.folded | ./FlameGraph/flamegraph.pl > diff.svg
Red regions: Functions slower in after.json Blue regions: Functions faster in after.json Gray regions: No significant change
Color-Coding by Slow I/O
Customize flamegraph colors based on slow I/O count:
#!/usr/bin/env python3
import json
import sys
with open(sys.argv[1]) as f:
data = json.load(f)
for func in data.get('function_profile', []):
slow_io_pct = func['slow_io_count'] / max(func['calls'], 1) * 100
# Color code: red for >50% slow I/O, orange for >10%, default otherwise
color = 'red' if slow_io_pct > 50 else ('orange' if slow_io_pct > 10 else 'default')
print(f"{func['function']} {func['total_time_us']} # {color}")
Result: Flamegraph visually highlights I/O bottlenecks with color!
Troubleshooting
Empty Flamegraph
Problem: Flamegraph shows no data or "No function profiling data collected"
Cause: Function profiling not enabled or no debug symbols
Solution:
# Ensure both --function-time and --source are used
$ renacer --function-time --source --format json -- ./myapp > profile.json
# Verify debug symbols
$ file ./myapp # Should show "with debug_info, not stripped"
Incorrect Stack Heights
Problem: All functions appear at same level (no hierarchy)
Cause: Renacer currently exports immediate callers only, not full stacks
Workaround:
- Flamegraphs work best with full stack traces
- Renacer's function profiling shows caller-callee pairs
- For hierarchical visualization, manually construct stacks from profiling data
Conversion Errors
Problem: renacer_to_folded.py fails with JSON parse errors
Cause: Invalid JSON output or large files
Solution:
# Verify JSON is valid
$ jq '.' profile.json > /dev/null
# For large files, use streaming JSON parser
$ cat profile.json | jq -c '.function_profile[]' | while read line; do
# Process line by line
done
Limitations
Current limitations:
- No native flamegraph export - Requires external tools (Brendan Gregg's scripts)
- Immediate callers only - Full stack traces not yet supported
- Time-based only - Cannot flamegraph by call count or other metrics (without custom scripts)
Future enhancements:
- Native folded stack export
- Full stack trace capture (multi-level unwinding)
- Direct SVG flamegraph generation
Summary
Flamegraph visualization provides:
- ✅ Visual bottleneck identification via JSON export + external tools
- ✅ Interactive exploration with Brendan Gregg's flamegraph.pl
- ✅ Comparison workflows for before/after optimization analysis
- ✅ Custom color-coding based on slow I/O metrics
- ✅ Integration with existing flamegraph ecosystems
Workflow:
- Profile with
--function-time --source --format json - Convert JSON to folded stack format
- Generate flamegraph SVG with external tools
- Analyze and optimize
All export functionality tested in: tests/sprint22_html_output_tests.rs, function profiling in tests/sprint13_function_profiling_tests.rs
Related
- Function Profiling - Parent chapter with basic usage
- Call Graph Analysis - Understanding function relationships
- I/O Bottleneck Detection - Identifying slow I/O for visualization
- Export to JSON/CSV - Data export workflows