Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ for i in "${!benchmark_names[@]}"; do

# Run multiple iterations and collect timing data
iteration_results=()
iteration_pass_data=()
for ((iter=1; iter<=ITERATIONS; iter++)); do
output_binary="$TEMP_DIR/bench_output_$$"

Expand All @@ -180,6 +181,8 @@ for i in "${!benchmark_names[@]}"; do
total_ms=$(echo "$timing_json" | grep -o '"total_ms":[0-9.]*' | head -1 | cut -d: -f2)
if [[ -n "$total_ms" ]]; then
iteration_results+=("$total_ms")
# Store full JSON for pass data extraction
iteration_pass_data+=("$timing_json")
fi

rm -f "$output_binary"
Expand All @@ -190,7 +193,7 @@ for i in "${!benchmark_names[@]}"; do
continue
fi

# Calculate mean and stddev
# Calculate mean and stddev for total time
sum=0
for val in "${iteration_results[@]}"; do
sum=$(echo "$sum + $val" | bc -l)
Expand All @@ -210,8 +213,36 @@ for i in "${!benchmark_names[@]}"; do

log_info " $name: mean=${mean}ms, std=${stddev}ms (n=$count)"

# Store result for later
all_results+=("{\"name\":\"$name\",\"iterations\":$count,\"mean_ms\":$mean,\"std_ms\":$stddev}")
# Extract and aggregate per-pass timing data
# Use Python to parse JSON and compute per-pass means
pass_json=$(python3 -c "
import json
import sys

pass_data = {}
for json_str in sys.argv[1:]:
try:
data = json.loads(json_str)
for p in data.get('passes', []):
name = p['name']
duration = p['duration_ms']
if name not in pass_data:
pass_data[name] = []
pass_data[name].append(duration)
except:
pass

# Calculate means
result = {}
for name, durations in pass_data.items():
mean = sum(durations) / len(durations) if durations else 0
result[name] = {'mean_ms': round(mean, 3)}

print(json.dumps(result))
" "${iteration_pass_data[@]}" 2>/dev/null || echo "{}")

# Store result with pass data
all_results+=("{\"name\":\"$name\",\"iterations\":$count,\"mean_ms\":$mean,\"std_ms\":$stddev,\"passes\":$pass_json}")
done

# Get metadata
Expand Down
71 changes: 61 additions & 10 deletions scripts/generate-charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,45 @@ def scale_y(v: float) -> float:
return "\n".join(svg_parts)


def generate_breakdown_chart(runs: list[dict]) -> str:
"""Generate stacked bar chart showing time per compiler pass."""
def get_benchmark_names(runs: list[dict]) -> list[str]:
"""Get list of all benchmark names from runs."""
names = set()
for run in runs:
for bench in run.get("benchmarks", []):
if "name" in bench:
names.add(bench["name"])
return sorted(names)


def get_pass_times_for_benchmark(run: dict, benchmark_name: str) -> dict[str, float]:
"""Extract pass timing for a specific benchmark from a run."""
for bench in run.get("benchmarks", []):
if bench.get("name") == benchmark_name and "passes" in bench:
passes = bench["passes"]
return {
name: passes.get(name, {}).get("mean_ms", 0)
for name in PASS_ORDER
}
return {}


def generate_breakdown_chart(runs: list[dict], benchmark_name: Optional[str] = None) -> str:
"""Generate stacked bar chart showing time per compiler pass.

If benchmark_name is provided, shows data for that specific benchmark.
Otherwise, shows aggregate data across all benchmarks.
"""
if not runs:
return generate_empty_chart(BREAKDOWN_WIDTH, BREAKDOWN_HEIGHT, "No benchmark data available yet")

# Get the most recent run with pass data
pass_times: Optional[dict[str, float]] = None
commit = ""
for run in reversed(runs):
pt = get_pass_times(run)
if benchmark_name:
pt = get_pass_times_for_benchmark(run, benchmark_name)
else:
pt = get_pass_times(run)
if pt and any(v > 0 for v in pt.values()):
pass_times = pt
commit = short_commit(run.get("commit", ""))
Expand Down Expand Up @@ -254,7 +283,7 @@ def generate_breakdown_chart(runs: list[dict]) -> str:
}
</style>''',
f' <rect class="chart-bg" width="{BREAKDOWN_WIDTH}" height="{BREAKDOWN_HEIGHT}" rx="8"/>',
f' <text class="chart-title" x="{BREAKDOWN_WIDTH/2}" y="25" text-anchor="middle" font-size="16">Compilation Time by Pass</text>',
f' <text class="chart-title" x="{BREAKDOWN_WIDTH/2}" y="25" text-anchor="middle" font-size="16">Compilation Time by Pass{" - " + escape_xml(benchmark_name) if benchmark_name else ""}</text>',
f' <text class="chart-text" x="{BREAKDOWN_WIDTH/2}" y="42" text-anchor="middle" font-size="11">(commit: {escape_xml(commit)})</text>',
]

Expand Down Expand Up @@ -320,22 +349,44 @@ def main():
# Ensure output directory exists
output_dir.mkdir(parents=True, exist_ok=True)

# Generate charts
# Generate timeline chart
timeline_svg = generate_timeline_chart(runs)
breakdown_svg = generate_breakdown_chart(runs)

# Write output files
timeline_path = output_dir / "timeline.svg"
breakdown_path = output_dir / "breakdown.svg"

with open(timeline_path, "w") as f:
f.write(timeline_svg)
print(f"Generated {timeline_path}")

# Generate aggregate breakdown chart (for backwards compatibility)
breakdown_svg = generate_breakdown_chart(runs)
breakdown_path = output_dir / "breakdown.svg"
with open(breakdown_path, "w") as f:
f.write(breakdown_svg)
print(f"Generated {breakdown_path}")

# Generate per-benchmark breakdown charts
benchmark_names = get_benchmark_names(runs)
print(f"Found {len(benchmark_names)} benchmarks: {', '.join(benchmark_names)}")

for bench_name in benchmark_names:
bench_svg = generate_breakdown_chart(runs, bench_name)
# Use sanitized filename
safe_name = bench_name.replace(" ", "_").replace("/", "_")
bench_path = output_dir / f"breakdown_{safe_name}.svg"
with open(bench_path, "w") as f:
f.write(bench_svg)
print(f"Generated {bench_path}")

# Write metadata JSON for the website to consume
metadata = {
"benchmarks": benchmark_names,
"run_count": len(runs),
"latest_commit": short_commit(runs[-1].get("commit", "")) if runs else None,
}
metadata_path = output_dir / "metadata.json"
with open(metadata_path, "w") as f:
json.dump(metadata, f, indent=2)
print(f"Generated {metadata_path}")


if __name__ == "__main__":
main()
56 changes: 54 additions & 2 deletions website/templates/performance.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,68 @@ <h2 class="text-xl font-semibold mb-4">Compilation Time Trend</h2>

<!-- Breakdown Chart -->
<section class="mb-12">
<h2 class="text-xl font-semibold mb-4">Time by Compiler Pass</h2>
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold">Time by Compiler Pass</h2>
<div class="flex items-center gap-2">
<label for="benchmark-select" class="text-sm text-muted">Program:</label>
<select id="benchmark-select" class="bg-surface border border-themed rounded px-3 py-1 text-sm">
<option value="">All (aggregate)</option>
<!-- Options populated by JavaScript from metadata.json -->
</select>
</div>
</div>
<p class="text-muted mb-4">
Breakdown of where compilation time is spent in the most recent benchmark run.
</p>
<div class="benchmark-chart-container bg-surface border border-themed rounded-lg p-4">
<div id="breakdown-chart-container" class="benchmark-chart-container bg-surface border border-themed rounded-lg p-4">
{% set breakdown = load_data(path="static/benchmarks/breakdown.svg", format="plain") %}
{{ breakdown | safe }}
</div>
</section>

<script>
(function() {
const select = document.getElementById('benchmark-select');
const container = document.getElementById('breakdown-chart-container');

// Load metadata to populate dropdown
fetch('/benchmarks/metadata.json')
.then(r => r.json())
.then(data => {
if (data.benchmarks && data.benchmarks.length > 0) {
data.benchmarks.forEach(name => {
const option = document.createElement('option');
const safeName = name.replace(/ /g, '_').replace(/\//g, '_');
option.value = safeName;
option.textContent = name;
select.appendChild(option);
});
}
})
.catch(() => {
// No metadata available, hide selector
select.parentElement.style.display = 'none';
});

// Handle selection changes
select.addEventListener('change', function() {
const value = this.value;
const svgPath = value
? `/benchmarks/breakdown_${value}.svg`
: '/benchmarks/breakdown.svg';

fetch(svgPath)
.then(r => r.text())
.then(svg => {
container.innerHTML = svg;
})
.catch(() => {
container.innerHTML = '<p class="text-muted text-center py-8">Failed to load chart</p>';
});
});
})();
</script>

<!-- Methodology -->
<section class="prose">
<h2>Methodology</h2>
Expand Down