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 in tests/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:

  1. No native flamegraph export - Requires external tools (Brendan Gregg's scripts)
  2. Immediate callers only - Full stack traces not yet supported
  3. 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:

  1. Profile with --function-time --source --format json
  2. Convert JSON to folded stack format
  3. Generate flamegraph SVG with external tools
  4. Analyze and optimize

All export functionality tested in: tests/sprint22_html_output_tests.rs, function profiling in tests/sprint13_function_profiling_tests.rs