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/memory_dashboard.ex
defmodule Raxol.Benchmark.MemoryDashboard do
alias Raxol.Benchmark.Statistics
@moduledoc """
Advanced memory benchmarking dashboard and reporting system.
Phase 3 Implementation: Provides comprehensive visual memory analysis:
- Interactive memory usage charts
- Memory regression trend analysis
- Cross-platform memory comparison
- Memory optimization recommendations
- AI-powered memory insights
"""
alias Raxol.Benchmark.MemoryAnalyzer
@type dashboard_config :: %{
output_dir: String.t(),
include_charts: boolean(),
include_regression_analysis: boolean(),
include_recommendations: boolean(),
template: atom()
}
# =============================================================================
# Dashboard Generation
# =============================================================================
@doc """
Generates a comprehensive memory analysis dashboard.
"""
@spec generate_dashboard(map(), keyword()) ::
{:ok, String.t()} | {:error, String.t()}
def generate_dashboard(benchmark_results, opts \\ []) do
config = parse_dashboard_config(opts)
# Analyze memory patterns
analysis = MemoryAnalyzer.analyze_memory_patterns(benchmark_results, opts)
# Generate dashboard content
dashboard_data = %{
timestamp: DateTime.utc_now(),
results: benchmark_results,
analysis: analysis,
recommendations: MemoryAnalyzer.generate_recommendations(analysis),
charts:
if(config.include_charts,
do: generate_chart_data(benchmark_results),
else: %{}
),
regression_data:
if(config.include_regression_analysis,
do: generate_regression_data(benchmark_results, opts),
else: %{}
)
}
# Generate HTML dashboard
html_content = render_dashboard_template(dashboard_data, config)
# Write dashboard file
output_path = Path.join(config.output_dir, "memory_dashboard.html")
case File.write(output_path, html_content) do
:ok -> {:ok, output_path}
{:error, reason} -> {:error, "Failed to write dashboard: #{reason}"}
end
end
@doc """
Generates memory trend analysis over multiple benchmark runs.
"""
@spec generate_trend_analysis(list(map()), keyword()) :: map()
def generate_trend_analysis(historical_results, opts \\ []) do
window_size = Keyword.get(opts, :window_size, 10)
trend_data = %{
memory_trends: analyze_memory_trends(historical_results),
regression_points: detect_regression_points(historical_results),
optimization_opportunities:
identify_optimization_opportunities(historical_results),
forecast: forecast_memory_usage(historical_results, window_size)
}
trend_data
end
# =============================================================================
# Chart Data Generation
# =============================================================================
defp generate_chart_data(benchmark_results) do
%{
memory_usage_over_time: generate_memory_timeline_data(benchmark_results),
memory_distribution: generate_memory_distribution_data(benchmark_results),
scenario_comparison: generate_scenario_comparison_data(benchmark_results),
efficiency_heatmap: generate_efficiency_heatmap_data(benchmark_results)
}
end
defp generate_memory_timeline_data(benchmark_results) do
# Extract time-series memory data
scenarios = extract_scenarios(benchmark_results)
scenarios
|> Enum.map(fn {name, data} ->
%{
name: name,
data: extract_memory_samples(data)
}
end)
end
defp generate_memory_distribution_data(benchmark_results) do
scenarios = extract_scenarios(benchmark_results)
scenarios
|> Enum.map(fn {name, data} ->
memory_values = extract_memory_values(data)
%{
scenario: name,
min: Enum.min(memory_values, fn -> 0 end),
max: Enum.max(memory_values, fn -> 0 end),
median: calculate_median(memory_values),
percentiles: calculate_percentiles(memory_values)
}
end)
end
defp generate_scenario_comparison_data(benchmark_results) do
scenarios = extract_scenarios(benchmark_results)
scenarios
|> Enum.map(fn {name, data} ->
analysis = MemoryAnalyzer.analyze_memory_patterns(%{name => data})
%{
scenario: name,
peak_memory: analysis.peak_memory,
sustained_memory: analysis.sustained_memory,
efficiency_score: analysis.efficiency_score,
fragmentation_ratio: analysis.fragmentation_ratio
}
end)
end
defp generate_efficiency_heatmap_data(benchmark_results) do
scenarios = extract_scenarios(benchmark_results)
# Create a heatmap of memory efficiency across different dimensions
dimensions = [
:allocation_speed,
:deallocation_speed,
:fragmentation,
:gc_pressure
]
scenarios
|> Enum.map(fn {name, data} ->
efficiency_scores = calculate_efficiency_scores(data, dimensions)
%{
scenario: name,
scores: efficiency_scores
}
end)
end
# =============================================================================
# Regression Analysis
# =============================================================================
defp generate_regression_data(benchmark_results, opts) do
baseline = Keyword.get(opts, :baseline)
if baseline do
%{
baseline_comparison: compare_with_baseline(benchmark_results, baseline),
regression_severity:
calculate_regression_severity(benchmark_results, baseline),
affected_scenarios:
identify_affected_scenarios(benchmark_results, baseline)
}
else
%{}
end
end
defp analyze_memory_trends(historical_results) do
historical_results
|> Enum.with_index()
|> Enum.map(fn {result, index} ->
analysis = MemoryAnalyzer.analyze_memory_patterns(result)
%{
run_number: index + 1,
peak_memory: analysis.peak_memory,
sustained_memory: analysis.sustained_memory,
efficiency_score: analysis.efficiency_score
}
end)
end
defp detect_regression_points(historical_results) do
trends = analyze_memory_trends(historical_results)
trends
|> Enum.chunk_every(2, 1, :discard)
|> Enum.with_index()
|> Enum.filter(fn {[prev, curr], _index} ->
# Detect significant increases in memory usage
# 10% increase
curr.peak_memory > prev.peak_memory * 1.1
end)
|> Enum.map(fn {[_prev, curr], index} ->
%{
run_number: curr.run_number,
regression_type: :memory_increase,
severity: calculate_severity(index)
}
end)
end
defp identify_optimization_opportunities(historical_results) do
trends = analyze_memory_trends(historical_results)
opportunities = []
# Identify consistently high memory usage
opportunities =
if Enum.all?(trends, fn trend -> trend.efficiency_score < 0.6 end) do
["Consistently low memory efficiency across all runs" | opportunities]
else
opportunities
end
# Identify increasing memory trends
opportunities =
if increasing_trend?(Enum.map(trends, & &1.peak_memory)) do
["Memory usage shows increasing trend over time" | opportunities]
else
opportunities
end
opportunities
end
defp forecast_memory_usage(historical_results, window_size) do
trends = analyze_memory_trends(historical_results)
if length(trends) >= window_size do
recent_trends = Enum.take(trends, -window_size)
peak_memories = Enum.map(recent_trends, & &1.peak_memory)
%{
next_run_prediction: predict_next_value(peak_memories),
trend_direction: determine_trend_direction(peak_memories),
confidence: calculate_prediction_confidence(peak_memories)
}
else
%{
next_run_prediction: nil,
trend_direction: :insufficient_data,
confidence: 0.0
}
end
end
# =============================================================================
# Dashboard Template Rendering
# =============================================================================
defp render_dashboard_template(dashboard_data, config) do
case config.template do
:comprehensive -> render_comprehensive_template(dashboard_data)
:minimal -> render_minimal_template(dashboard_data)
_ -> render_default_template(dashboard_data)
end
end
defp render_comprehensive_template(dashboard_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 Memory Analysis Dashboard</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
#{dashboard_css()}
</style>
</head>
<body>
<header class="dashboard-header">
<h1>Raxol Memory Analysis Dashboard</h1>
<p class="timestamp">Generated: #{dashboard_data.timestamp}</p>
</header>
<main class="dashboard-main">
#{render_summary_section(dashboard_data)}
#{render_charts_section(dashboard_data)}
#{render_analysis_section(dashboard_data)}
#{render_recommendations_section(dashboard_data)}
</main>
<script>
#{dashboard_javascript(dashboard_data)}
</script>
</body>
</html>
"""
end
defp render_minimal_template(dashboard_data) do
"""
<!DOCTYPE html>
<html>
<head>
<title>Memory Analysis Summary</title>
<style>#{minimal_css()}</style>
</head>
<body>
<h1>Memory Analysis Summary</h1>
#{render_summary_section(dashboard_data)}
#{render_recommendations_section(dashboard_data)}
</body>
</html>
"""
end
defp render_default_template(dashboard_data) do
render_comprehensive_template(dashboard_data)
end
# =============================================================================
# Template Sections
# =============================================================================
defp render_summary_section(dashboard_data) do
analysis = dashboard_data.analysis
"""
<section class="summary-section">
<h2>Memory Analysis Summary</h2>
<div class="summary-grid">
<div class="summary-card">
<h3>Peak Memory</h3>
<p class="metric">#{format_bytes(analysis.peak_memory)}</p>
</div>
<div class="summary-card">
<h3>Sustained Memory</h3>
<p class="metric">#{format_bytes(analysis.sustained_memory)}</p>
</div>
<div class="summary-card">
<h3>Efficiency Score</h3>
<p class="metric">#{Float.round(analysis.efficiency_score, 3)}</p>
</div>
<div class="summary-card">
<h3>GC Collections</h3>
<p class="metric">#{analysis.gc_collections}</p>
</div>
</div>
</section>
"""
end
defp render_charts_section(dashboard_data) do
if map_size(dashboard_data.charts) > 0 do
"""
<section class="charts-section">
<h2>Memory Usage Charts</h2>
<div class="charts-grid">
<div id="memory-timeline-chart"></div>
<div id="memory-distribution-chart"></div>
<div id="scenario-comparison-chart"></div>
<div id="efficiency-heatmap-chart"></div>
</div>
</section>
"""
else
""
end
end
defp render_analysis_section(dashboard_data) do
analysis = dashboard_data.analysis
regression_status =
if analysis.regression_detected, do: "[WARN] Detected", else: "[OK] None"
fragmentation_level =
cond do
analysis.fragmentation_ratio > 0.5 -> "[HIGH] High"
analysis.fragmentation_ratio > 0.2 -> "[MED] Moderate"
true -> "[LOW] Low"
end
"""
<section class="analysis-section">
<h2>Detailed Analysis</h2>
<div class="analysis-grid">
<div class="analysis-item">
<h4>Memory Regression</h4>
<p>#{regression_status}</p>
</div>
<div class="analysis-item">
<h4>Fragmentation Level</h4>
<p>#{fragmentation_level}</p>
</div>
<div class="analysis-item">
<h4>Platform</h4>
<p>#{analysis.platform_differences.platform}</p>
</div>
</div>
</section>
"""
end
defp render_recommendations_section(dashboard_data) do
recommendations = dashboard_data.recommendations
recommendation_items =
Enum.map_join(recommendations, "\n", fn rec -> "<li>#{rec}</li>" end)
"""
<section class="recommendations-section">
<h2>Optimization Recommendations</h2>
<ul class="recommendations-list">
#{recommendation_items}
</ul>
</section>
"""
end
# =============================================================================
# Helper Functions
# =============================================================================
defp parse_dashboard_config(opts) do
%{
output_dir: Keyword.get(opts, :output_dir, "bench/output"),
include_charts: Keyword.get(opts, :include_charts, true),
include_regression_analysis:
Keyword.get(opts, :include_regression_analysis, true),
include_recommendations:
Keyword.get(opts, :include_recommendations, true),
template: Keyword.get(opts, :template, :comprehensive)
}
end
defp extract_scenarios(benchmark_results) do
case benchmark_results do
%{scenarios: scenarios} -> scenarios
%{} -> benchmark_results
end
|> Enum.to_list()
end
defp extract_memory_values(data) do
case data do
%{memory_usage_data: %{samples: samples}} -> samples
%{memory_usage_data: %{statistics: %{average: avg}}} -> [avg]
_ -> [0]
end
end
defp extract_memory_samples(data) do
memory_values = extract_memory_values(data)
memory_values
|> Enum.with_index()
|> Enum.map(fn {value, index} -> %{x: index, y: value} end)
end
defp calculate_median(values) when length(values) > 0 do
sorted = Enum.sort(values)
length = length(sorted)
if rem(length, 2) == 0 do
(Enum.at(sorted, div(length, 2) - 1) + Enum.at(sorted, div(length, 2))) /
2
else
Enum.at(sorted, div(length, 2))
end
end
defp calculate_median(_), do: 0
defp calculate_percentiles(values) when length(values) > 0 do
sorted = Enum.sort(values)
length = length(sorted)
%{
p25: Enum.at(sorted, trunc(length * 0.25)),
p50: calculate_median(values),
p75: Enum.at(sorted, trunc(length * 0.75)),
p90: Enum.at(sorted, trunc(length * 0.90)),
p95: Enum.at(sorted, trunc(length * 0.95)),
p99: Enum.at(sorted, trunc(length * 0.99))
}
end
defp calculate_percentiles(_), do: %{}
defp calculate_efficiency_scores(_data, dimensions) do
# Mock efficiency calculation for different dimensions
dimensions
|> Enum.map(fn dimension ->
# Random for demonstration
{dimension, :rand.uniform()}
end)
|> Enum.into(%{})
end
defp compare_with_baseline(_benchmark_results, _baseline) do
# Placeholder for baseline comparison
%{
memory_difference: 0,
performance_difference: 0,
regression_risk: :low
}
end
defp calculate_regression_severity(_benchmark_results, _baseline) do
# Placeholder for regression severity calculation
:low
end
defp identify_affected_scenarios(_benchmark_results, _baseline) do
# Placeholder for affected scenarios identification
[]
end
defp calculate_severity(_index), do: :medium
defp increasing_trend?(values) when length(values) < 3, do: false
defp increasing_trend?(values) do
differences =
values
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [a, b] -> b - a end)
positive_differences = Enum.count(differences, &(&1 > 0))
positive_differences > length(differences) / 2
end
defp predict_next_value(values) when length(values) >= 2 do
# Simple linear prediction
last_two = Enum.take(values, -2)
[second_last, last] = last_two
trend = last - second_last
last + trend
end
defp predict_next_value(_), do: nil
defp determine_trend_direction(values) do
if increasing_trend?(values) do
:increasing
else
first = List.first(values)
last = List.last(values)
if last < first, do: :decreasing, else: :stable
end
end
defp calculate_prediction_confidence(values) when length(values) >= 3 do
# Calculate confidence based on variance
variance = calculate_variance(values)
mean = Enum.sum(values) / length(values)
if mean > 0 do
coefficient_of_variation = :math.sqrt(variance) / mean
max(0.0, 1.0 - coefficient_of_variation)
else
0.0
end
end
defp calculate_prediction_confidence(_), do: 0.0
defp calculate_variance(values), do: Statistics.calculate_variance(values)
defp format_bytes(bytes) when bytes >= 1_000_000_000 do
"#{Float.round(bytes / 1_000_000_000, 2)} GB"
end
defp format_bytes(bytes) when bytes >= 1_000_000 do
"#{Float.round(bytes / 1_000_000, 2)} MB"
end
defp format_bytes(bytes) when bytes >= 1_000 do
"#{Float.round(bytes / 1_000, 2)} KB"
end
defp format_bytes(bytes) do
"#{bytes} B"
end
# =============================================================================
# CSS and JavaScript
# =============================================================================
defp dashboard_css do
"""
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
.dashboard-header { background: #2c3e50; color: white; padding: 20px; text-align: center; }
.dashboard-header h1 { margin: 0; font-size: 2.5em; }
.timestamp { margin: 10px 0 0 0; opacity: 0.8; }
.dashboard-main { padding: 20px; max-width: 1200px; margin: 0 auto; }
.summary-section { margin-bottom: 30px; }
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; }
.summary-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center; }
.summary-card h3 { margin: 0 0 10px 0; color: #34495e; }
.metric { font-size: 2em; font-weight: bold; color: #2980b9; margin: 0; }
.charts-section { margin-bottom: 30px; }
.charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.charts-grid > div { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); height: 400px; }
.analysis-section { margin-bottom: 30px; }
.analysis-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
.analysis-item { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.analysis-item h4 { margin: 0 0 10px 0; color: #34495e; }
.recommendations-section { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.recommendations-list { list-style-type: none; padding: 0; }
.recommendations-list li { padding: 10px; margin: 10px 0; background: #ecf0f1; border-radius: 4px; border-left: 4px solid #3498db; }
"""
end
defp minimal_css do
"""
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.summary-section { background: #f9f9f9; padding: 20px; border-radius: 5px; }
.summary-grid { display: flex; gap: 20px; }
.summary-card { flex: 1; text-align: center; }
.metric { font-size: 1.5em; font-weight: bold; color: #0066cc; }
"""
end
defp dashboard_javascript(dashboard_data) do
"""
// Initialize charts when page loads
document.addEventListener('DOMContentLoaded', function() {
if (typeof Plotly !== 'undefined') {
initializeCharts(#{Jason.encode!(dashboard_data.charts)});
}
});
function initializeCharts(chartData) {
// Memory timeline chart
if (document.getElementById('memory-timeline-chart') && chartData.memory_usage_over_time) {
Plotly.newPlot('memory-timeline-chart', chartData.memory_usage_over_time, {
title: 'Memory Usage Over Time',
xaxis: { title: 'Time' },
yaxis: { title: 'Memory (bytes)' }
});
}
// Memory distribution chart
if (document.getElementById('memory-distribution-chart') && chartData.memory_distribution) {
Plotly.newPlot('memory-distribution-chart', chartData.memory_distribution, {
title: 'Memory Distribution',
xaxis: { title: 'Scenario' },
yaxis: { title: 'Memory (bytes)' }
});
}
// Scenario comparison chart
if (document.getElementById('scenario-comparison-chart') && chartData.scenario_comparison) {
Plotly.newPlot('scenario-comparison-chart', chartData.scenario_comparison, {
title: 'Scenario Comparison',
xaxis: { title: 'Scenario' },
yaxis: { title: 'Value' }
});
}
// Efficiency heatmap
if (document.getElementById('efficiency-heatmap-chart') && chartData.efficiency_heatmap) {
Plotly.newPlot('efficiency-heatmap-chart', chartData.efficiency_heatmap, {
title: 'Memory Efficiency Heatmap',
type: 'heatmap'
});
}
}
"""
end
end