defmodule DalaDev.CrashDump do @moduledoc """ Parse BEAM crash dumps from devices. BEAM crash dumps are text files generated when an Erlang VM crashes. This module parses them to extract useful information for debugging dala Elixir nodes. ## Examples # Parse a crash dump file {:ok, info} = DalaDev.CrashDump.parse_file("erl_crash.dump") # Parse from a device {:ok, info} = DalaDev.CrashDump.fetch_from_device(node, "/path/to/crash.dump") # Generate a summary report report = DalaDev.CrashDump.summary(info) """ @type crash_info :: %{ header: map(), system_info: map(), process_info: [map()], ports: [map()], ets_tables: [map()], timers: [map()], error_info: map() | nil, memory: map(), summary: String.t() } @doc """ Parse a crash dump file. Returns a crash_info map with all parsed sections. """ @spec parse_file(String.t()) :: {:ok, crash_info()} | {:error, term()} def parse_file(path) when is_binary(path) do case File.read(path) do {:ok, content} -> parse(content) error -> error end end @doc """ Parse crash dump content from a string. Returns a crash_info map with all parsed sections. """ @spec parse(String.t()) :: {:ok, crash_info()} | {:error, term()} def parse(content) when is_binary(content) do try do lines = String.split(content, "\n") header = parse_header(lines) system_info = parse_system_info(lines) process_info = parse_processes(lines) ports = parse_ports(lines) ets_tables = parse_ets_tables(lines) timers = parse_timers(lines) error_info = parse_error_info(lines) memory = parse_memory(lines) info = %{ header: header, system_info: system_info, process_info: process_info, ports: ports, ets_tables: ets_tables, timers: timers, error_info: error_info, memory: memory, summary: generate_summary(header, error_info, memory) } {:ok, info} rescue e -> {:error, Exception.message(e)} end end @doc """ Fetch a crash dump from a remote device and parse it. Options: - `:timeout` - RPC timeout in ms (default: 30_000) """ @spec fetch_from_device(node(), String.t(), keyword()) :: {:ok, crash_info()} | {:error, term()} def fetch_from_device(node, remote_path, opts \\ []) do timeout = Keyword.get(opts, :timeout, 30_000) case :rpc.call(node, File, :read, [remote_path], timeout) do {:badrpc, reason} -> {:error, {:rpc_error, reason}} {:ok, content} -> parse(content) {:error, _} = error -> error content when is_binary(content) -> parse(content) end end @doc """ Generate a human-readable summary of the crash dump. """ @spec summary(crash_info()) :: String.t() def summary(info) do """ BEAM Crash Dump Summary ======================= #{summary_header(info.header)} #{summary_error(info.error_info)} #{summary_memory(info.memory)} #{summary_processes(info.process_info)} System Info: OTP Release: #{get_in(info.system_info, [:otp_release]) || "unknown"} Elixir Version: #{get_in(info.system_info, [:elixir_version]) || "unknown"} Node: #{get_in(info.system_info, [:node]) || "unknown"} Top Processes by Memory: #{top_processes_by_memory(info.process_info, 5)} ETS Tables: #{length(info.ets_tables)} Timers: #{length(info.timers)} """ end @doc """ Generate an HTML report from crash dump info. """ @spec html_report(crash_info()) :: String.t() def html_report(info) do """
No error information
" defp error_html(error) do """Type: #{Map.get(error, :type, "unknown")}
Reason: #{Map.get(error, :reason, "unknown")}
Process parsing not yet implemented.
" else # Would generate table "Found #{length(processes)} processes
" end end defp system_info_html(info) do """