defmodule Mix.Tasks.Raxol.Bench.MemoryAnalysis do @compile {:no_warn_undefined, Benchee} @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" => &benchmark_linear_growth/0, "exponential_growth_pattern" => &benchmark_exponential_growth/0, "constant_memory_pattern" => &benchmark_constant_memory/0, "gc_pressure_pattern" => &benchmark_gc_pressure/0 } Benchee.run(jobs, config) end defp benchmark_linear_growth do data = for i <- 1..100, do: String.duplicate("data", i) Enum.reduce(data, [], &[&1 | &2]) end defp benchmark_exponential_growth do Enum.reduce(1..10, ["start"], fn _i, acc -> expanded = Enum.map(acc, &String.duplicate(&1, 2)) acc ++ expanded end) end defp benchmark_constant_memory do data = for _i <- 1..1000, do: "constant" Enum.take(data, 100) end defp benchmark_gc_pressure do large_data = for _i <- 1..1000 do :crypto.strong_rand_bytes(1024) end Enum.chunk_every(large_data, 100) |> Enum.map(&length/1) 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: %{}} # credo:disable-for-next-line Credo.Check.Refactor.AppendSingleItem 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), do: Raxol.Utils.Format.format_bytes(bytes) 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