Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/benchmark/reporter.ex
defmodule Raxol.Benchmark.Reporter do
alias Raxol.Core.Runtime.Log
@moduledoc """
Generates comprehensive performance reports from benchmark results.
Supports multiple output formats including console, HTML, JSON, and Markdown.
"""
@doc """
Generates a comprehensive report from benchmark results.
"""
def generate_comprehensive_report(results) do
generate_comprehensive_report(results, [])
end
def generate_comprehensive_report(results, opts) do
format = Keyword.get(opts, :format, :console)
output_path = Keyword.get(opts, :output_path, "bench/reports")
report_data = compile_report_data(results)
case format do
:console -> console_report(report_data)
:html -> html_report(report_data, output_path)
:json -> json_report(report_data, output_path)
:markdown -> markdown_report(report_data, output_path)
:all -> generate_all_formats(report_data, output_path)
_ -> {:error, :invalid_format}
end
end
@doc """
Generates a comparison report between two benchmark runs.
"""
def generate_comparison_report(results1, results2, opts \\ []) do
comparison = compare_results(results1, results2)
output_path = Keyword.get(opts, :output_path, "bench/comparisons")
filename = "comparison_#{timestamp()}"
# Generate comparison in multiple formats
write_comparison_html(
comparison,
Path.join(output_path, "#{filename}.html")
)
write_comparison_markdown(
comparison,
Path.join(output_path, "#{filename}.md")
)
Log.info("Comparison report generated at #{output_path}/#{filename}.*")
comparison
end
@doc """
Generates a performance dashboard with visualizations.
"""
def generate_dashboard(historical_data, opts \\ []) do
dashboard_data = %{
current_performance: extract_current_metrics(historical_data),
trends: calculate_trends(historical_data),
alerts: generate_alerts(historical_data),
recommendations: generate_recommendations(historical_data)
}
output_path = Keyword.get(opts, :output_path, "bench/dashboard")
write_dashboard_html(dashboard_data, Path.join(output_path, "index.html"))
dashboard_data
end
# Private functions
defp extract_current_metrics(historical_data) do
case List.last(historical_data) do
nil ->
%{}
latest ->
%{
timestamp: latest.timestamp,
average_time: latest.average_time,
memory_usage: latest.memory_usage
}
end
end
defp calculate_trends(historical_data) when length(historical_data) < 2 do
%{trend: "insufficient_data"}
end
defp calculate_trends(_historical_data) do
%{trend: "stable", direction: "neutral"}
end
defp generate_alerts(_historical_data) do
[]
end
defp generate_recommendations(_historical_data) do
[]
end
defp write_dashboard_html(dashboard_data, file_path) do
html_content = """
<!DOCTYPE html>
<html>
<head><title>Benchmark Dashboard</title></head>
<body>
<h1>Benchmark Dashboard</h1>
<pre>#{inspect(dashboard_data)}</pre>
</body>
</html>
"""
File.write!(file_path, html_content)
end
defp write_comparison_html(comparison_data, file_path) do
html_content = """
<!DOCTYPE html>
<html>
<head><title>Benchmark Comparison</title></head>
<body>
<h1>Benchmark Comparison</h1>
<pre>#{inspect(comparison_data)}</pre>
</body>
</html>
"""
File.write!(file_path, html_content)
end
defp write_comparison_markdown(comparison_data, file_path) do
markdown_content = """
# Benchmark Comparison
#{inspect(comparison_data)}
"""
File.write!(file_path, markdown_content)
end
defp generate_html_summary(summary) do
"""
<div class="summary">
<h2>Summary</h2>
<p>Total Suites: #{summary.total_suites}</p>
<p>Total Benchmarks: #{summary.total_benchmarks}</p>
</div>
"""
end
defp format_markdown_summary(summary) do
"""
## Summary
- Total Suites: #{summary.total_suites}
- Total Benchmarks: #{summary.total_benchmarks}
"""
end
defp format_markdown_metrics(metrics) do
"""
## Performance Metrics
#{inspect(metrics)}
"""
end
defp format_markdown_regressions(regressions) do
"""
## Regressions
#{inspect(regressions)}
"""
end
defp format_markdown_improvements(improvements) do
"""
## Improvements
#{inspect(improvements)}
"""
end
defp format_markdown_recommendations(recommendations) do
"""
## Recommendations
#{inspect(recommendations)}
"""
end
defp format_markdown_detailed_results(results) do
"""
## Detailed Results
#{inspect(results)}
"""
end
defp compare_results(_results1, _results2) do
%{
comparison: "placeholder",
differences: []
}
end
defp generate_html_charts(_data) do
""
end
defp generate_html_tables(_data) do
""
end
defp generate_chart_scripts(_data) do
""
end
defp compile_report_data(results) do
# Simplified version without analyzer dependency for now
%{
metadata: %{
timestamp: DateTime.utc_now(),
total_suites: length(results),
total_duration: calculate_total_duration(results),
environment: collect_environment_info()
},
summary: %{
total_benchmarks: length(results),
fastest_operations: [],
slowest_operations: []
},
performance_metrics: extract_performance_metrics(results),
regressions: [],
improvements: [],
recommendations: [],
detailed_results: format_detailed_results(results)
}
end
defp console_report(report_data) do
Log.console("""
╔══════════════════════════════════════════════════════════════════╗
║ Raxol Performance Benchmark Report ║
╚══════════════════════════════════════════════════════════════════╝
Generated: #{report_data.metadata.timestamp}
Total Duration: #{format_duration(report_data.metadata.total_duration)}
Environment: #{format_environment(report_data.metadata.environment)}
""")
# Summary Section
Log.console("[STATS] SUMMARY")
Log.console("=" |> String.duplicate(70))
print_summary(report_data.summary)
# Performance Highlights
Log.console("\n[FAST] PERFORMANCE HIGHLIGHTS")
Log.console("=" |> String.duplicate(70))
print_performance_highlights(report_data.performance_metrics)
# Regressions
print_regressions_section(report_data.regressions)
# Improvements
print_improvements_section(report_data.improvements)
# Recommendations
print_recommendations_section(report_data.recommendations)
# Detailed Results
Log.console("\n[TREND] DETAILED RESULTS")
Log.console("=" |> String.duplicate(70))
print_detailed_results(report_data.detailed_results)
:ok
end
defp html_report(report_data, output_path) do
html_content = generate_html_report(report_data)
File.mkdir_p!(output_path)
file_path = Path.join(output_path, "benchmark_report_#{timestamp()}.html")
File.write!(file_path, html_content)
Log.info("HTML report generated at #{file_path}")
{:ok, file_path}
end
defp json_report(report_data, output_path) do
File.mkdir_p!(output_path)
file_path = Path.join(output_path, "benchmark_report_#{timestamp()}.json")
json_content = Jason.encode!(report_data, pretty: true)
File.write!(file_path, json_content)
Log.info("JSON report generated at #{file_path}")
{:ok, file_path}
end
defp markdown_report(report_data, output_path) do
md_content = generate_markdown_report(report_data)
File.mkdir_p!(output_path)
file_path = Path.join(output_path, "benchmark_report_#{timestamp()}.md")
File.write!(file_path, md_content)
Log.info("Markdown report generated at #{file_path}")
{:ok, file_path}
end
defp generate_all_formats(report_data, output_path) do
results = [
console_report(report_data),
html_report(report_data, output_path),
json_report(report_data, output_path),
markdown_report(report_data, output_path)
]
{:ok, results}
end
defp generate_html_report(report_data) do
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raxol Benchmark Report - #{report_data.metadata.timestamp}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 30px;
border-radius: 8px;
margin-bottom: 30px;
}
.metric-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.metric-value {
font-size: 2em;
font-weight: bold;
color: #3498db;
}
.regression {
background: #fee;
border-left: 4px solid #e74c3c;
padding: 10px;
margin: 10px 0;
}
.improvement {
background: #efe;
border-left: 4px solid #27ae60;
padding: 10px;
margin: 10px 0;
}
.chart-container {
background: white;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
height: 400px;
}
table {
width: 100%;
border-collapse: collapse;
background: white;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background: #34495e;
color: white;
}
tr:hover {
background: #f5f5f5;
}
</style>
</head>
<body>
<div class="header">
<h1>Raxol Performance Benchmark Report</h1>
<p>Generated: #{report_data.metadata.timestamp}</p>
<p>Total Duration: #{format_duration(report_data.metadata.total_duration)}</p>
</div>
#{generate_html_summary(report_data)}
#{generate_html_charts(report_data)}
#{generate_html_tables(report_data)}
<script>
#{generate_chart_scripts(report_data)}
</script>
</body>
</html>
"""
end
defp generate_markdown_report(report_data) do
"""
# Raxol Performance Benchmark Report
**Generated:** #{report_data.metadata.timestamp}
**Total Duration:** #{format_duration(report_data.metadata.total_duration)}
**Environment:** #{format_environment(report_data.metadata.environment)}
## Summary
#{format_markdown_summary(report_data.summary)}
## Performance Metrics
#{format_markdown_metrics(report_data.performance_metrics)}
## Regressions
#{format_markdown_regressions(report_data.regressions)}
## Improvements
#{format_markdown_improvements(report_data.improvements)}
## Recommendations
#{format_markdown_recommendations(report_data.recommendations)}
## Detailed Results
#{format_markdown_detailed_results(report_data.detailed_results)}
"""
end
# Helper functions
defp calculate_total_duration(results) do
Enum.reduce(results, 0, &(&1.duration + &2))
end
defp collect_environment_info do
{family, name} = :os.type()
%{
elixir_version: System.version(),
otp_version: :erlang.system_info(:otp_release) |> List.to_string(),
os: "#{family}_#{name}",
cpu_count: System.schedulers_online(),
memory: :erlang.memory(:total)
}
end
defp extract_performance_metrics(results) do
all_scenarios = Enum.flat_map(results, & &1.results.scenarios)
%{
total_operations: length(all_scenarios),
average_time: calculate_average_time(all_scenarios),
fastest_operation: find_fastest(all_scenarios),
slowest_operation: find_slowest(all_scenarios),
memory_stats: calculate_memory_stats(all_scenarios)
}
end
defp format_detailed_results(results) do
Enum.map(results, fn suite ->
%{
suite_name: suite.suite_name,
duration: suite.duration,
scenarios: Enum.map(suite.results.scenarios, &format_scenario/1)
}
end)
end
defp format_scenario(scenario) do
%{
name: scenario.name,
average: scenario.run_time_data.statistics.average,
min: scenario.run_time_data.statistics.minimum,
max: scenario.run_time_data.statistics.maximum,
std_dev: scenario.run_time_data.statistics.std_dev,
memory: scenario.memory_usage_data.statistics.average
}
end
defp format_duration(ms) when ms < 1000, do: "#{ms}ms"
defp format_duration(ms), do: "#{Float.round(ms / 1000, 2)}s"
defp format_environment(env) do
"Elixir #{env.elixir_version} / OTP #{env.otp_version} / #{env.cpu_count} CPUs"
end
defp timestamp do
DateTime.utc_now()
|> DateTime.to_string()
|> String.replace(~r/[^\d]/, "_")
end
# Print helpers for console output
defp print_regressions_section([]), do: :ok
defp print_improvements_section([]), do: :ok
defp print_recommendations_section([]), do: :ok
defp print_summary(summary) do
Log.console("Total Benchmarks: #{summary.total_benchmarks}")
Log.console("Fastest Operations:")
Enum.each(summary.fastest_operations, fn op ->
Log.console(" • #{op.operation}: #{format_time(op.time)}")
end)
end
defp print_performance_highlights(metrics) do
Log.console("Average Operation Time: #{format_time(metrics.average_time)}")
Log.console(
"Fastest: #{metrics.fastest_operation.name} (#{format_time(metrics.fastest_operation.time)})"
)
Log.console(
"Slowest: #{metrics.slowest_operation.name} (#{format_time(metrics.slowest_operation.time)})"
)
end
defp print_detailed_results(results) do
Enum.each(results, fn suite ->
Log.console("\n#{suite.suite_name}")
Log.console("-" |> String.duplicate(String.length(suite.suite_name)))
Enum.each(suite.scenarios, fn scenario ->
Log.console(
" #{scenario.name}: #{format_time(scenario.average)} (±#{format_time(scenario.std_dev)})"
)
end)
end)
end
defp format_time(ns) when is_number(ns) do
Raxol.Benchmark.Analyzer.format_time(ns)
end
defp format_time(_), do: "N/A"
# Additional helper functions would go here...
defp calculate_average_time([]), do: 0
defp calculate_average_time(scenarios) do
total =
Enum.reduce(scenarios, 0, fn s, acc ->
acc + s.run_time_data.statistics.average
end)
total / length(scenarios)
end
defp find_fastest(scenarios) do
Enum.min_by(scenarios, & &1.run_time_data.statistics.average)
|> format_operation_summary()
end
defp find_slowest(scenarios) do
Enum.max_by(scenarios, & &1.run_time_data.statistics.average)
|> format_operation_summary()
end
defp format_operation_summary(scenario) do
%{
name: scenario.name,
time: scenario.run_time_data.statistics.average
}
end
defp calculate_memory_stats(scenarios) do
memories = Enum.map(scenarios, & &1.memory_usage_data.statistics.average)
%{
total: Enum.sum(memories),
average: Enum.sum(memories) / length(memories),
max: Enum.max(memories)
}
end
end