Current section

Files

Jump to
dala_dev lib mix tasks dala.trace.ex
Raw

lib/mix/tasks/dala.trace.ex

defmodule Mix.Tasks.Dala.Trace do
use Mix.Task
@shortdoc "Distributed tracing for dala Elixir clusters"
@moduledoc """
Interactive distributed tracing UI for dala Elixir clusters.
## Usage
mix dala.trace # interactive tracing UI
mix dala.trace --node dala_qa@192.168.1.5 # trace specific node
mix dala.trace --export trace.json # export to Chrome Tracing format
mix dala.trace --modules Dala.Screen,MyApp # trace specific modules
## Options
* `--node` - Target node (can be specified multiple times)
* `--export` - Export traces to file (Chrome Tracing JSON format)
* `--modules` - Comma-separated list of modules to trace
* `--functions` - Comma-separated list of module:function:arity to trace
* `--duration` - Trace duration in seconds (default: 60)
## Examples
# Start interactive tracing
mix dala.trace
# Trace specific modules on a node
mix dala.trace --node dala_qa@192.168.1.5 --modules MyApp,MyAppWeb
# Export to Chrome Tracing format
mix dala.trace --export trace.json --duration 30
"""
alias DalaDev.Tracing
@impl Mix.Task
def run(args) do
Mix.Task.run("app.config")
{parsed_opts, _} =
OptionParser.parse(args,
strict: [
node: :keep,
export: :string,
modules: :string,
functions: :string,
duration: :integer
]
)
nodes = Keyword.get_values(parsed_opts, :node)
export_path = Keyword.get(parsed_opts, :export)
modules_str = Keyword.get(parsed_opts, :modules, "")
functions_str = Keyword.get(parsed_opts, :functions, "")
duration = Keyword.get(parsed_opts, :duration, 60)
nodes = if nodes == [], do: [:all], else: Enum.map(nodes, &String.to_atom/1)
modules = parse_modules(modules_str)
functions = parse_functions(functions_str)
trace_opts = []
trace_opts =
if modules != [], do: Keyword.put(trace_opts, :modules, modules), else: trace_opts
trace_opts =
if functions != [], do: Keyword.put(trace_opts, :functions, functions), else: trace_opts
IO.puts("Starting trace on nodes: #{inspect(nodes)}")
IO.puts("Duration: #{duration} seconds")
case Tracing.start_trace(nodes, trace_opts) do
{:ok, trace_id} ->
IO.puts("Trace started with ID: #{inspect(trace_id)}")
Process.sleep(duration * 1000)
IO.puts("Collecting traces...")
traces = Tracing.get_events(trace_id)
IO.puts("Collected #{length(traces)} trace events")
if export_path do
case Tracing.export_chrome_trace(trace_id, export_path) do
:ok ->
IO.puts("Exported to: #{export_path}")
error ->
IO.puts("Export failed: #{inspect(error)}")
end
else
print_traces(traces)
end
Tracing.stop_trace(trace_id)
{:error, reason} ->
IO.puts("Failed to start trace: #{inspect(reason)}")
end
end
defp parse_modules(""), do: []
defp parse_modules(str),
do: str |> String.split(",") |> Enum.map(&String.trim/1) |> Enum.map(&String.to_atom/1)
defp parse_functions(""), do: []
defp parse_functions(str) do
str
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.map(fn func_spec ->
case String.split(func_spec, ":") do
[mod, fun, arity] -> {String.to_atom(mod), String.to_atom(fun), String.to_integer(arity)}
_ -> nil
end
end)
|> Enum.reject(&is_nil/1)
end
defp print_traces(traces) do
Enum.each(traces, fn trace ->
IO.puts("[#{trace[:type]}] #{trace[:module]}.#{trace[:function]} - #{trace[:pid]}")
end)
end
end