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/mix/tasks/raxol.bench.memory_analysis.ex
defmodule Mix.Tasks.Raxol.Bench.MemoryAnalysis do
@moduledoc """
Advanced memory analysis task demonstrating Phase 3 capabilities.
This task showcases the integration of:
- MemoryAnalyzer for pattern analysis
- MemoryDSL for enhanced assertions
- MemoryDashboard for visual reporting
Usage:
mix raxol.bench.memory_analysis
mix raxol.bench.memory_analysis --scenario terminal_operations
mix raxol.bench.memory_analysis --with-dashboard
"""
use Mix.Task
alias Raxol.Benchmark.{MemoryAnalyzer, MemoryDashboard}
@shortdoc "Run advanced memory analysis benchmarks with pattern detection"
@spec run(list()) :: no_return()
def run(args) do
opts = parse_options(args)
_ = Application.ensure_all_started(:raxol)
config = build_config(opts)
print_config_info(config)
results = run_scenario(config.scenario, config.benchmark_config)
process_results(results, config)
end
defp parse_options(args) do
{opts, _, _} =
OptionParser.parse(args,
switches: [
scenario: :string,
with_dashboard: :boolean,
time: :integer,
memory_time: :integer,
output: :string
],
aliases: [
s: :scenario,
d: :with_dashboard,
t: :time,
m: :memory_time,
o: :output
]
)
opts
end
defp build_config(opts) do
%{
scenario: Keyword.get(opts, :scenario, "all"),
time: Keyword.get(opts, :time, 2),
memory_time: Keyword.get(opts, :memory_time, 1),
output_path:
Keyword.get(opts, :output, "bench/output/memory_analysis_report.html"),
with_dashboard: Keyword.get(opts, :with_dashboard, false),
benchmark_config: build_benchmark_config(opts)
}
end
defp build_benchmark_config(opts) do
time = Keyword.get(opts, :time, 2)
memory_time = Keyword.get(opts, :memory_time, 1)
output_path =
Keyword.get(opts, :output, "bench/output/memory_analysis_report.html")
[
time: time,
memory_time: memory_time,
warmup: 0.5,
formatters: [
Benchee.Formatters.Console,
{Benchee.Formatters.HTML, file: output_path}
]
]
end
defp print_config_info(config) do
Mix.shell().info("Running Memory Analysis Benchmarks...")
Mix.shell().info("Scenario: #{config.scenario}")
Mix.shell().info(
"Time: #{config.time}s, Memory Time: #{config.memory_time}s"
)
end
defp run_scenario(scenario, benchmark_config) do
case scenario do
"terminal_operations" ->
run_terminal_operations_analysis(benchmark_config)
"buffer_management" ->
run_buffer_management_analysis(benchmark_config)
"realistic_usage" ->
run_realistic_usage_analysis(benchmark_config)
"memory_patterns" ->
run_memory_pattern_analysis(benchmark_config)
"all" ->
run_comprehensive_analysis(benchmark_config)
_ ->
handle_unknown_scenario(scenario)
end
end
@spec handle_unknown_scenario(term()) :: no_return()
defp handle_unknown_scenario(scenario) do
Mix.shell().error("Unknown scenario: #{scenario}")
Mix.shell().info(
"Available scenarios: terminal_operations, buffer_management, realistic_usage, memory_patterns, all"
)
System.halt(1)
end
defp process_results(results, config) do
Mix.shell().info("\nAnalyzing Memory Patterns...")
analysis = MemoryAnalyzer.analyze_memory_patterns(results)
print_analysis_summary(analysis)
recommendations = MemoryAnalyzer.generate_recommendations(analysis)
print_recommendations(recommendations)
if config.with_dashboard do
generate_dashboard(results, analysis, recommendations, config.scenario)
end
end
defp generate_dashboard(results, analysis, recommendations, scenario) do
Mix.shell().info("\nGenerating Interactive Dashboard...")
dashboard_path = "bench/output/memory_analysis_dashboard.html"
dashboard_config = %{
title: "Raxol Memory Analysis Dashboard",
subtitle: "Advanced Memory Pattern Analysis - Scenario: #{scenario}",
benchmark_results: results,
analysis: analysis,
recommendations: recommendations
}
case MemoryDashboard.generate_dashboard(results,
output_path: dashboard_path,
config: dashboard_config
) do
{:ok, _} ->
Mix.shell().info("Dashboard generated: #{dashboard_path}")
Mix.shell().info(
"Open in browser: file://#{Path.expand(dashboard_path)}"
)
{:error, error} ->
Mix.shell().error("Dashboard generation failed: #{inspect(error)}")
end
end
# =============================================================================
# Benchmark Scenarios
# =============================================================================
defp run_terminal_operations_analysis(config) do
jobs = %{
"small_terminal_80x24" => fn ->
create_terminal_buffer(80, 24)
end,
"medium_terminal_132x43" => fn ->
create_terminal_buffer(132, 43)
end,
"large_terminal_1000x1000" => fn ->
create_terminal_buffer(1000, 1000)
end,
"multiple_buffers_5x" => fn ->
for _i <- 1..5 do
create_terminal_buffer(80, 24)
end
end
}
Benchee.run(jobs, config)
end
defp run_buffer_management_analysis(config) do
jobs = %{
"buffer_creation_destruction" => fn ->
buffer = create_terminal_buffer(100, 100)
clear_buffer(buffer)
end,
"buffer_modification_heavy" => fn ->
buffer = create_terminal_buffer(50, 50)
for row <- 1..50, col <- 1..50 do
update_cell(buffer, row, col, "X")
end
end,
"ansi_sequence_processing" => fn ->
sequences = [
# Clear screen
"\e[2J",
# Move to home
"\e[1;1H",
# Color text
"\e[31mRed\e[0m",
# Alternative buffer
"\e[?1049h",
# Normal buffer
"\e[?1049l"
]
for seq <- sequences do
process_ansi_sequence(seq)
end
end,
"scroll_operations_heavy" => fn ->
buffer = create_terminal_buffer(80, 24)
for _i <- 1..100 do
scroll_buffer_up(buffer)
end
end
}
Benchee.run(jobs, config)
end
defp run_realistic_usage_analysis(config) do
jobs = %{
"vim_session_simulation" => fn ->
simulate_vim_session()
end,
"log_streaming_simulation" => fn ->
simulate_log_streaming(1000)
end,
"multi_pane_terminal" => fn ->
simulate_multi_pane_terminal(4)
end,
"rapid_output_processing" => fn ->
process_rapid_output(500)
end
}
Benchee.run(jobs, config)
end
defp run_memory_pattern_analysis(config) do
jobs = %{
"linear_growth_pattern" => fn ->
# Simulate linear memory growth
data = for i <- 1..100, do: String.duplicate("data", i)
Enum.reduce(data, [], &[&1 | &2])
end,
"exponential_growth_pattern" => fn ->
# Simulate exponential memory growth
data =
Enum.reduce(1..10, ["start"], fn _i, acc ->
expanded = Enum.map(acc, &String.duplicate(&1, 2))
acc ++ expanded
end)
data
end,
"constant_memory_pattern" => fn ->
# Simulate constant memory usage
data = for _i <- 1..1000, do: "constant"
Enum.take(data, 100)
end,
"gc_pressure_pattern" => fn ->
# Create scenario that triggers GC
large_data =
for _i <- 1..1000 do
:crypto.strong_rand_bytes(1024)
end
# Force some allocation/deallocation cycles
Enum.chunk_every(large_data, 100)
|> Enum.map(&length/1)
end
}
Benchee.run(jobs, config)
end
defp run_comprehensive_analysis(config) do
# Run a subset of all scenarios for comprehensive analysis
jobs = %{
"terminal_80x24" => fn -> create_terminal_buffer(80, 24) end,
"terminal_1000x1000" => fn -> create_terminal_buffer(1000, 1000) end,
"buffer_heavy_modification" => fn ->
buffer = create_terminal_buffer(50, 50)
for row <- 1..50, col <- 1..50, do: update_cell(buffer, row, col, "X")
end,
"vim_simulation" => fn -> simulate_vim_session() end,
"log_streaming" => fn -> simulate_log_streaming(500) end,
"exponential_growth" => fn ->
Enum.reduce(1..8, ["start"], fn _i, acc ->
expanded = Enum.map(acc, &String.duplicate(&1, 2))
acc ++ expanded
end)
end
}
Benchee.run(jobs, config)
end
# =============================================================================
# Helper Functions
# =============================================================================
defp create_terminal_buffer(width, height) do
for _row <- 1..height do
for _col <- 1..width do
%{
char: " ",
fg: :white,
bg: :black,
style: %{bold: false, italic: false, underline: false}
}
end
end
end
defp clear_buffer(buffer) when is_list(buffer) do
Enum.map(buffer, fn row ->
Enum.map(row, fn _cell ->
%{char: " ", fg: :white, bg: :black, style: %{}}
end)
end)
end
defp update_cell(buffer, row, col, char) when is_list(buffer) do
case row <= length(buffer) do
true ->
buffer_row = Enum.at(buffer, row - 1)
update_cell_in_row(buffer, buffer_row, row, col, char)
false ->
buffer
end
end
defp process_ansi_sequence(sequence) do
# Simulate ANSI sequence processing
case sequence do
"\e[2J" -> :clear_screen
"\e[1;1H" -> :move_cursor_home
"\e[31m" <> rest -> {:set_color, :red, rest}
"\e[0m" -> :reset_attributes
"\e[?1049h" -> :enter_alt_buffer
"\e[?1049l" -> :exit_alt_buffer
_ -> {:unknown_sequence, sequence}
end
end
defp scroll_buffer_up(buffer) when is_list(buffer) do
case buffer do
[] ->
[]
[_first | rest] ->
empty_row =
for _col <- 1..80,
do: %{char: " ", fg: :white, bg: :black, style: %{}}
rest ++ [empty_row]
end
end
defp simulate_vim_session do
# Simulate vim operations: file loading, editing, syntax highlighting
file_content =
for _i <- 1..1000, do: "def function_#{:rand.uniform(1000)}, do: :ok"
# Simulate syntax highlighting (create colored tokens)
highlighted =
Enum.map(file_content, fn line ->
tokens = String.split(line, " ")
Enum.map(tokens, &colorize_token/1)
end)
# Simulate editing operations
modified =
Enum.take(highlighted, 500) ++
[%{text: "# New comment", color: :gray}] ++
Enum.drop(highlighted, 500)
modified
end
defp simulate_log_streaming(num_lines) do
# Simulate continuous log output
timestamps =
for i <- 1..num_lines do
{{2024, 1, 1}, {12, 0, i}} |> NaiveDateTime.from_erl!()
end
log_levels = [:info, :warn, :error, :debug]
for {timestamp, i} <- Enum.with_index(timestamps, 1) do
level = Enum.random(log_levels)
message =
"Log message #{i} with some detailed information about system state"
%{
timestamp: timestamp,
level: level,
message: message,
metadata: %{module: "System.Logger", pid: self()}
}
end
end
defp simulate_multi_pane_terminal(num_panes) do
# Simulate multiple terminal panes
for pane_id <- 1..num_panes do
%{
id: pane_id,
buffer: create_terminal_buffer(40, 12),
cursor: %{row: 1, col: 1},
title: "Pane #{pane_id}",
active: pane_id == 1
}
end
end
defp process_rapid_output(num_outputs) do
# Simulate rapid terminal output processing
outputs =
for i <- 1..num_outputs do
case rem(i, 4) do
0 -> "\e[31mError: Something went wrong #{i}\e[0m\n"
1 -> "\e[32mSuccess: Operation #{i} completed\e[0m\n"
2 -> "\e[33mWarning: Check configuration #{i}\e[0m\n"
3 -> "Info: Processing item #{i}\n"
end
end
# Process each output (simulate parsing and buffer updates)
for output <- outputs do
process_ansi_sequence(output)
end
end
# =============================================================================
# Analysis Reporting
# =============================================================================
defp print_analysis_summary(analysis) do
Mix.shell().info("\n=== Memory Analysis Summary ===")
Mix.shell().info("Peak Memory: #{format_bytes(analysis.peak_memory)}")
Mix.shell().info(
"Sustained Memory: #{format_bytes(analysis.sustained_memory)}"
)
Mix.shell().info("GC Collections: #{analysis.gc_collections}")
Mix.shell().info(
"Fragmentation Ratio: #{Float.round(analysis.fragmentation_ratio, 3)}"
)
Mix.shell().info(
"Efficiency Score: #{Float.round(analysis.efficiency_score, 3)}"
)
Mix.shell().info("Regression Detected: #{analysis.regression_detected}")
platform_info = analysis.platform_differences
Mix.shell().info("\nPlatform: #{platform_info.platform}")
Mix.shell().info("Architecture: #{platform_info.architecture}")
Mix.shell().info("Memory Allocator: #{platform_info.memory_allocator}")
end
defp print_recommendations(recommendations) do
if recommendations != [] do
Mix.shell().info("\n=== Optimization Recommendations ===")
for {recommendation, index} <- Enum.with_index(recommendations, 1) do
Mix.shell().info("#{index}. #{recommendation}")
end
else
Mix.shell().info("\n=== No Optimization Recommendations ===")
Mix.shell().info("Memory usage patterns appear optimal.")
end
end
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
defp update_cell_in_row(buffer, buffer_row, row, col, char) do
case col <= length(buffer_row) do
true ->
updated_row =
List.update_at(buffer_row, col - 1, fn cell ->
%{cell | char: char}
end)
List.update_at(buffer, row - 1, fn _ -> updated_row end)
false ->
buffer
end
end
defp colorize_token(token) do
color =
case token do
"def" -> :blue
"do:" -> :green
_ -> :white
end
%{text: token, color: color}
end
end