Packages
mob_dev
0.3.33
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/mob.trace_otp.ex
defmodule Mix.Tasks.Mob.TraceOtp do
@shortdoc "Run the Elixir characterization harness under trace, dump touched MFAs"
@moduledoc """
Runs `MobDev.OtpTrace.Harness` under full call tracing and reports
the runtime modules + MFAs actually exercised.
This is the empirical complement to `mix mob.audit_otp` (which uses
static reachability). Combined, they give a high-confidence answer
to "what does any Elixir runtime actually need."
## Usage
mix mob.trace_otp # Run all phases, summary report
mix mob.trace_otp --phase otp # Just one phase
mix mob.trace_otp --json out.json # Machine-readable dump
## Phases
language — pattern match, comprehensions, structs, protocols
collections — Enum, List, Map, MapSet, Stream, Range
strings — String, Binary, charlist, codepoints
processes — spawn, send/receive, monitor, link, Task
otp — GenServer, Supervisor, Application, Logger
data — ETS, persistent_term, Date/Time, System
errors — raise/rescue, throw/catch, exit, exception structs
all — every phase (default)
## Output
Plain mode prints a summary + sorted module list. `--json` writes a
JSON file with the full MFA set, suitable for cross-referencing
against the static audit (`mix mob.audit_otp`) to find modules
shipped-but-never-called.
"""
use Mix.Task
alias MobDev.OtpTrace
alias MobDev.OtpTrace.Harness
@phases ~w(language collections strings processes otp data errors all)a
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
strict: [phase: :string, json: :string]
)
phase = parse_phase(opts[:phase] || "all")
Mix.Task.run("loadpaths")
Mix.Task.run("compile")
# Force-load the harness so its compile-time work doesn't appear
# in the trace — we want runtime behaviour only.
Code.ensure_all_loaded([
Harness,
Harness.HarnessStruct,
Harness.HarnessProto,
Harness.HarnessGS,
Harness.HarnessApp
])
Mix.shell().info("Running Elixir characterization harness under trace…")
Mix.shell().info(" Phase: #{phase}\n")
result = OtpTrace.capture(fn -> apply(Harness, phase, []) end)
if opts[:json] do
write_json(result, opts[:json])
Mix.shell().info("Wrote #{opts[:json]}")
else
print_summary(result, phase)
end
end
defp parse_phase(name) do
atom = String.to_existing_atom(name)
if atom in @phases do
atom
else
Mix.raise("Unknown phase: #{name}. Valid: #{Enum.join(@phases, ", ")}")
end
rescue
ArgumentError ->
Mix.raise("Unknown phase: #{name}. Valid: #{Enum.join(@phases, ", ")}")
end
defp print_summary(result, phase) do
h1 = IO.ANSI.bright()
dim = IO.ANSI.faint()
reset = IO.ANSI.reset()
Mix.shell().info("#{h1}=== Trace summary ==={reset}")
Mix.shell().info(" Phase exercised: #{phase}")
Mix.shell().info(" Modules touched: #{MapSet.size(result.modules)}")
Mix.shell().info(" Unique MFAs: #{MapSet.size(result.mfas)}")
Mix.shell().info(" Wall time: #{format_us(result.elapsed_us)}")
Mix.shell().info("\n#{h1}=== Modules touched (sorted) ==={reset}")
{elixir, erlang} =
result.modules
|> Enum.sort()
|> Enum.split_with(fn m -> String.starts_with?(to_string(m), "Elixir.") end)
Mix.shell().info("\n #{dim}Elixir modules (#{length(elixir)}):#{reset}")
for m <- elixir do
Mix.shell().info(" #{inspect(m)}")
end
Mix.shell().info("\n #{dim}Erlang modules (#{length(erlang)}):#{reset}")
for m <- erlang do
Mix.shell().info(" #{inspect(m)}")
end
Mix.shell().info("")
end
defp write_json(result, path) do
payload = %{
modules: result.modules |> Enum.sort() |> Enum.map(&to_string/1),
mfas:
result.mfas
|> Enum.sort()
|> Enum.map(fn {m, f, a} -> [to_string(m), to_string(f), a] end),
elapsed_us: result.elapsed_us,
module_count: MapSet.size(result.modules),
mfa_count: MapSet.size(result.mfas)
}
File.write!(path, Jason.encode!(payload, pretty: true))
end
defp format_us(us) when us < 1_000, do: "#{us}μs"
defp format_us(us) when us < 1_000_000, do: "#{Float.round(us / 1_000, 1)}ms"
defp format_us(us), do: "#{Float.round(us / 1_000_000, 2)}s"
end