Current section

Files

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

lib/mix/tasks/dala.bench.ex

defmodule Mix.Tasks.Dala.Bench do
use Mix.Task
@shortdoc "Run benchmarks on dala nodes"
@moduledoc """
Run performance benchmarks on dala Elixir nodes.
## Examples
# Run standard benchmarks
mix dala.bench
# Run custom benchmark script
mix dala.bench --test test/my_bench.exs
# Compare performance across nodes
mix dala.bench --compare node1@host,node2@host
# Generate HTML report
mix dala.bench --report report.html --format html
## Options
* `--test` - Path to custom benchmark script
* `--compare` - Comma-separated list of nodes to compare
* `--report` - Output file for report
* `--format` - Report format (text, html, json)
* `--iterations` - Number of iterations per benchmark (default: 3)
"""
alias DalaDev.Benchmark
@impl Mix.Task
def run(args) do
{opts, _} =
OptionParser.parse!(args,
strict: [
test: :string,
compare: :string,
report: :string,
format: :string,
iterations: :integer
],
aliases: [
t: :test,
c: :compare,
r: :report,
f: :format,
i: :iterations
]
)
cond do
Keyword.has_key?(opts, :compare) ->
run_comparison(opts)
Keyword.has_key?(opts, :test) ->
run_custom_benchmark(opts)
true ->
run_standard_benchmark(opts)
end
end
defp run_standard_benchmark(opts) do
iterations = Keyword.get(opts, :iterations, 3)
format = Keyword.get(opts, :format, "text")
report_path = Keyword.get(opts, :report)
Mix.shell().info("Running standard benchmarks...")
Mix.shell().info("Iterations: #{iterations}")
# Benchmark: List operations
Mix.shell().info("\nBenchmarking list operations...")
result1 =
Benchmark.measure(
Node.self(),
fn ->
Enum.map(1..1000, &(&1 * 2))
end,
iterations: iterations
)
case result1 do
{:ok, _, stats} ->
print_benchmark_result("List mapping (1000 items)", stats)
{:error, reason} ->
Mix.shell().error("Benchmark failed: #{inspect(reason)}")
end
# Benchmark: String operations
Mix.shell().info("\nBenchmarking string operations...")
result2 =
Benchmark.measure(
Node.self(),
fn ->
String.duplicate("a", 1000)
end,
iterations: iterations
)
case result2 do
{:ok, _, stats} ->
print_benchmark_result("String duplicate (1000 chars)", stats)
{:error, reason} ->
Mix.shell().error("Benchmark failed: #{inspect(reason)}")
end
# Generate report if requested
if report_path do
results = collect_results([result1, result2])
generate_report(results, format, report_path)
end
end
defp run_comparison(opts) do
nodes_str = Keyword.get(opts, :compare, "")
iterations = Keyword.get(opts, :iterations, 3)
nodes =
nodes_str
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_atom/1)
Mix.shell().info("Comparing performance across nodes: #{inspect(nodes)}")
# Define a simple benchmark module
benchmark_fn = fn ->
Enum.map(1..1000, &(&1 * 2))
end
results =
Enum.map(nodes, fn node ->
Mix.shell().info("\nBenchmarking #{node}...")
case Benchmark.measure(node, benchmark_fn, iterations: iterations) do
{:ok, _, stats} ->
print_benchmark_result("Node: #{node}", stats)
stats
{:error, reason} ->
Mix.shell().error("Failed on #{node}: #{inspect(reason)}")
nil
end
end)
|> Enum.filter(&(&1 != nil))
# Generate report if requested
if Keyword.has_key?(opts, :report) do
generate_report(results, "text", Keyword.get(opts, :report))
end
end
defp run_custom_benchmark(opts) do
test_path = Keyword.get(opts, :test)
if File.exists?(test_path) do
Mix.shell().info("Running custom benchmark: #{test_path}")
try do
Code.eval_file(test_path)
rescue
e -> Mix.shell().error("Failed to run benchmark: #{Exception.message(e)}")
end
else
Mix.shell().error("Benchmark file not found: #{test_path}")
end
end
defp print_benchmark_result(name, stats) do
Mix.shell().info("""
#{name}:
Wall time: #{stats.wall_time} μs
Reductions: #{stats.reductions}
Memory: #{format_bytes(stats.memory)}
Process count: #{stats.process_count}
Message queue: #{stats.message_queue_len}
""")
end
defp collect_results(results) do
Enum.flat_map(results, fn
{:ok, _, stats} -> [stats]
_ -> []
end)
end
defp generate_report(results, format, path) do
format_atom = String.to_atom(format)
case Benchmark.report(results, format: format_atom, output: path) do
{:ok, :ok} ->
Mix.shell().info("\nReport saved to: #{path}")
{:error, reason} ->
Mix.shell().error("Failed to generate report: #{inspect(reason)}")
end
end
defp format_bytes(bytes) when bytes < 1024, do: "#{bytes} B"
defp format_bytes(bytes) when bytes < 1024 * 1024,
do: "#{Float.round(bytes / 1024, 1)} KB"
defp format_bytes(bytes),
do: "#{Float.round(bytes / (1024 * 1024), 1)} MB"
end