Packages
Secure BEAM sandbox runtime for LLM code mode and MCP aggregation. Run concurrent LLM/tool clients safely while agents orchestrate approved tools, call upstream MCP/OpenAPI servers, and transform data.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/bench.check.ex
defmodule Mix.Tasks.Bench.Check do
@shortdoc "Check deterministic Lisp eval performance against a baseline"
@moduledoc """
Checks deterministic PTC-Lisp eval metrics against a committed baseline.
mix bench.check
mix bench.check --write-baseline
This task gates `eval_reductions` collected from the sandbox child process.
Child `memory_bytes` and wall-clock duration are reported as informational
metrics; memory regressions are covered by the release soak tests.
"""
use Mix.Task
alias PtcRunner.Lisp
@default_baseline Path.join(["bench", "baselines", "lisp_eval.json"])
@default_threshold 1.07
@default_samples 7
@default_warmup 20
@scenarios [
%{
name: "arithmetic",
program: "(+ 1 (* 2 3) (- 10 4) (* (+ 1 1) 5))"
},
%{
name: "collection_hofs",
program: "(reduce + 0 (map (fn [x] (* x x)) (filter odd? (range 0 40))))"
},
%{
name: "map_string",
program:
~S|(let [m {:a "alpha" :b "beta" :c "gamma"}] (clojure.string/join "," (map clojure.string/upper-case [(:a m) (:b m) (:c m)])))|
},
%{
name: "closure_apply",
program: "(let [mk (fn [x] (fn [y] (+ x y)))] (reduce + 0 (map (mk 10) [1 2 3 4 5])))"
},
%{
name: "context_filter",
program: "(->> data/orders (filter #(> (:total %) 20)) (map :total) (reduce + 0))",
opts: [
context: %{
"orders" => [
%{"id" => 1, "total" => 10},
%{"id" => 2, "total" => 25},
%{"id" => 3, "total" => 40}
]
}
]
}
]
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
opts = parse_args!(args)
baseline_path = opts[:baseline]
threshold = opts[:threshold]
samples = opts[:samples]
warmup = opts[:warmup]
results = measure_all(samples, warmup)
if opts[:write_baseline] do
write_baseline!(baseline_path, threshold, samples, warmup, results)
else
check_baseline!(baseline_path, threshold, results)
end
end
defp parse_args!(args) do
{opts, rest, invalid} =
OptionParser.parse(args,
strict: [
baseline: :string,
threshold: :float,
samples: :integer,
warmup: :integer,
write_baseline: :boolean
],
aliases: [b: :baseline]
)
if rest != [] or invalid != [] do
Mix.raise("invalid arguments: #{Enum.join(args, " ")}")
end
[
baseline: Keyword.get(opts, :baseline, @default_baseline),
threshold: Keyword.get(opts, :threshold, @default_threshold),
samples: Keyword.get(opts, :samples, @default_samples),
warmup: Keyword.get(opts, :warmup, @default_warmup),
write_baseline: Keyword.get(opts, :write_baseline, false)
]
|> validate_opts!()
end
defp validate_opts!(opts) do
threshold = opts[:threshold]
samples = opts[:samples]
warmup = opts[:warmup]
cond do
not is_float(threshold) or threshold < 1.0 ->
Mix.raise("--threshold must be a float >= 1.0")
not is_integer(samples) or samples < 1 ->
Mix.raise("--samples must be a positive integer")
not is_integer(warmup) or warmup < 0 ->
Mix.raise("--warmup must be a non-negative integer")
true ->
opts
end
end
defp measure_all(samples, warmup) do
Mix.shell().info("==> measuring PTC-Lisp eval performance")
Enum.map(@scenarios, fn scenario ->
warmup!(scenario, warmup)
measurements =
for _ <- 1..samples do
measure_once!(scenario)
end
%{
name: scenario.name,
program: scenario.program,
samples: samples,
eval_reductions: median(Enum.map(measurements, & &1.eval_reductions)),
memory_bytes: Enum.max(Enum.map(measurements, & &1.memory_bytes)),
duration_ms: median(Enum.map(measurements, & &1.duration_ms))
}
end)
end
defp warmup!(_scenario, 0), do: :ok
defp warmup!(scenario, warmup) do
for _ <- 1..warmup, do: measure_once!(scenario)
:ok
end
defp measure_once!(scenario) do
case Lisp.run(scenario.program, Map.get(scenario, :opts, [])) do
{:ok, step} ->
%{
eval_reductions: Map.fetch!(step.usage, :eval_reductions),
memory_bytes: Map.fetch!(step.usage, :memory_bytes),
duration_ms: Map.fetch!(step.usage, :duration_ms)
}
{:error, step} ->
Mix.raise("benchmark scenario #{scenario.name} failed: #{step.fail.message}")
end
end
defp write_baseline!(path, threshold, samples, warmup, results) do
File.mkdir_p!(Path.dirname(path))
baseline = %{
"version" => 1,
"threshold" => threshold,
"samples" => samples,
"warmup" => warmup,
"elixir" => System.version(),
"otp" => System.otp_release(),
"scenarios" => Enum.map(results, &encode_result/1)
}
File.write!(path, Jason.encode!(baseline, pretty: true) <> "\n")
Mix.shell().info("Wrote #{path}")
end
defp check_baseline!(path, threshold, results) do
baseline = read_baseline!(path)
baseline_by_name = Map.new(baseline["scenarios"], &{&1["name"], &1})
failures =
results
|> Enum.flat_map(fn result ->
expected = Map.fetch!(baseline_by_name, result.name)
check_result(result, expected, threshold)
end)
print_results(results, baseline_by_name, threshold)
if failures != [] do
Mix.raise("performance regression detected:\n" <> Enum.join(failures, "\n"))
end
Mix.shell().info("Performance check passed.")
end
defp read_baseline!(path) do
case File.read(path) do
{:ok, json} ->
Jason.decode!(json)
{:error, :enoent} ->
Mix.raise("missing baseline #{path}; run mix bench.check --write-baseline")
{:error, reason} ->
Mix.raise("could not read baseline #{path}: #{inspect(reason)}")
end
end
defp check_result(result, expected, threshold) do
Enum.flat_map([:eval_reductions], fn metric ->
actual = Map.fetch!(result, metric)
baseline = Map.fetch!(expected, Atom.to_string(metric))
allowed = Float.ceil(baseline * threshold, 1)
if actual <= allowed do
[]
else
["#{result.name} #{metric}: #{actual} > #{allowed} (baseline #{baseline})"]
end
end)
end
defp print_results(results, baseline_by_name, threshold) do
Mix.shell().info("")
Mix.shell().info("| Scenario | Eval reductions | Memory bytes | Duration ms |")
Mix.shell().info("|---|---:|---:|---:|")
Enum.each(results, fn result ->
baseline = Map.fetch!(baseline_by_name, result.name)
Mix.shell().info(
"| #{result.name} | #{format_metric(result.eval_reductions, baseline["eval_reductions"], threshold)} | " <>
"#{format_informational(result.memory_bytes, baseline["memory_bytes"])} | #{result.duration_ms} |"
)
end)
Mix.shell().info("")
end
defp format_metric(actual, baseline, threshold) do
allowed = Float.ceil(baseline * threshold, 1)
"#{actual} (baseline #{baseline}, max #{allowed})"
end
defp format_informational(actual, baseline), do: "#{actual} (baseline #{baseline}, info)"
defp encode_result(result) do
%{
"name" => result.name,
"program" => result.program,
"eval_reductions" => result.eval_reductions,
"memory_bytes" => result.memory_bytes,
"duration_ms" => result.duration_ms
}
end
defp median(values) do
sorted = Enum.sort(values)
Enum.at(sorted, div(length(sorted), 2))
end
end