Packages

Run WebAssembly from Elixir. Load WASM modules in Rust, Go, C — call them like native functions.

Current section

Files

Jump to
firebird lib mix tasks firebird.target.report.ex
Raw

lib/mix/tasks/firebird.target.report.ex

defmodule Mix.Tasks.Firebird.Target.Report do
@moduledoc """
Generate a compilation report for the WASM target.
Compiles all WASM sources and produces a detailed report with
per-module statistics, aggregate totals, and timing information.
## Usage
# Generate report to stdout
mix firebird.target.report
# Save report as JSON
mix firebird.target.report --json --output build_report.json
# Compare with previous report
mix firebird.target.report --compare previous_report.json
# Quick summary only
mix firebird.target.report --summary
## Output
The report includes:
- Per-module WAT/WASM sizes
- Export counts
- Compression ratio
- Compilation timing
- Configuration used
"""
use Mix.Task
@shortdoc "Generate a WASM compilation report"
@switches [
output: :string,
json: :boolean,
compare: :string,
summary: :boolean,
dirs: :string,
files: :string,
optimize: :boolean,
tco: :boolean,
inline: :boolean,
wat_only: :boolean
]
@aliases [
o: :output,
d: :dirs,
f: :files
]
@impl Mix.Task
def run(args) do
{opts, _rest, _invalid} = OptionParser.parse(args, switches: @switches, aliases: @aliases)
json_mode = Keyword.get(opts, :json, false)
summary_only = Keyword.get(opts, :summary, false)
config = Mix.Tasks.Firebird.Target.Compile.build_config(opts)
# Discover and compile
sources = Firebird.Target.discover_sources(config)
if Enum.empty?(sources) do
Mix.shell().info("No compilable sources found.")
:ok
else
start = System.monotonic_time(:millisecond)
{:ok, results} = Firebird.Target.compile(config)
elapsed = System.monotonic_time(:millisecond) - start
# Handle partial failure
results =
case results do
list when is_list(list) -> list
_ -> []
end
report = Firebird.Target.Report.generate(results, config, elapsed)
# Output
cond do
json_mode ->
json = Firebird.Target.Report.to_json(report)
if output = Keyword.get(opts, :output) do
File.write!(output, json)
Mix.shell().info("📋 Report saved to #{output}")
else
Mix.shell().info(json)
end
summary_only ->
print_summary(report)
true ->
Mix.shell().info(Firebird.Target.Report.format(report))
end
# Compare with previous if requested
if compare_path = Keyword.get(opts, :compare) do
compare_with(report, compare_path)
end
:ok
end
end
defp print_summary(report) do
t = report.totals
Mix.shell().info("📊 WASM Compilation Summary")
Mix.shell().info(" Modules: #{t.module_count}")
Mix.shell().info(" WAT: #{format_bytes(t.total_wat_bytes)}")
Mix.shell().info(" WASM: #{format_bytes(t.total_wasm_bytes)}")
if t.compression_ratio do
Mix.shell().info(" Ratio: #{t.compression_ratio}%")
end
Mix.shell().info(" Time: #{report.elapsed_ms}ms")
end
defp compare_with(current, path) do
case File.read(path) do
{:ok, json} ->
case Jason.decode(json) do
{:ok, prev_data} ->
prev_report = normalize_report(prev_data)
diff = Firebird.Target.Report.diff(prev_report, current)
Mix.shell().info("")
Mix.shell().info("📊 Comparison with #{path}:")
Mix.shell().info(" Modules: #{format_change(diff.module_count_change)}")
Mix.shell().info(
" WAT: #{format_bytes_change(diff.wat_bytes_change, diff.wat_percent_change)}"
)
Mix.shell().info(
" WASM: #{format_bytes_change(diff.wasm_bytes_change, diff.wasm_percent_change)}"
)
Mix.shell().info(" Time: #{format_time_change(diff.time_change_ms)}")
_ ->
Mix.shell().error("Could not parse #{path}")
end
{:error, _} ->
Mix.shell().error("Could not read #{path}")
end
end
defp normalize_report(data) when is_map(data) do
%{
timestamp: data["timestamp"] || "",
elapsed_ms: data["elapsed_ms"] || 0,
modules: data["modules"] || [],
totals: %{
module_count: get_in(data, ["totals", "module_count"]) || 0,
total_wat_bytes: get_in(data, ["totals", "total_wat_bytes"]) || 0,
total_wasm_bytes: get_in(data, ["totals", "total_wasm_bytes"]) || 0,
compression_ratio: get_in(data, ["totals", "compression_ratio"])
},
config: data["config"] || %{}
}
end
defp format_change(0), do: "no change"
defp format_change(n) when n > 0, do: "+#{n}"
defp format_change(n), do: "#{n}"
defp format_bytes_change(bytes, pct) do
sign = if bytes >= 0, do: "+", else: ""
pct_str = if pct, do: " (#{sign}#{pct}%)", else: ""
"#{sign}#{format_bytes(abs(bytes))}#{pct_str}"
end
defp format_time_change(0), do: "no change"
defp format_time_change(ms) when ms > 0, do: "+#{ms}ms (slower)"
defp format_time_change(ms), do: "#{ms}ms (faster)"
defp format_bytes(0), do: "0 B"
defp format_bytes(bytes) when bytes < 1024, do: "#{bytes} B"
defp format_bytes(bytes) when bytes < 1_048_576, do: "#{Float.round(bytes / 1024, 1)} KB"
defp format_bytes(bytes), do: "#{Float.round(bytes / 1_048_576, 1)} MB"
end