Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/benchmark/suite_registry.ex
defmodule Raxol.Benchmark.SuiteRegistry do
@moduledoc """
Central registry for all benchmark suites.
Manages suite discovery, registration, and execution.
"""
use Raxol.Core.Behaviours.BaseManager
# Client API
@doc """
Register a benchmark suite module.
"""
def register_suite(module) when is_atom(module) do
GenServer.call(__MODULE__, {:register_suite, module})
end
@doc """
List all registered benchmark suites.
"""
def list_suites do
GenServer.call(__MODULE__, :list_suites)
end
@doc """
Get details for a specific suite.
"""
def get_suite(name) do
GenServer.call(__MODULE__, {:get_suite, name})
end
@doc """
Run all registered benchmark suites.
"""
def run_all(opts \\ []) do
GenServer.call(__MODULE__, {:run_all, opts}, :infinity)
end
@doc """
Run specific benchmark suites by name or tag.
"""
def run_suites(filter, opts \\ []) do
GenServer.call(__MODULE__, {:run_suites, filter, opts}, :infinity)
end
@doc """
Discover and automatically register benchmark suites in the codebase.
"""
def discover_suites do
GenServer.call(__MODULE__, :discover_suites)
end
@doc """
Get performance history for a suite.
"""
def get_history(suite_name, limit \\ 10) do
GenServer.call(__MODULE__, {:get_history, suite_name, limit})
end
@doc """
Compare current results with baseline.
"""
def compare_with_baseline(suite_name, baseline_version) do
GenServer.call(
__MODULE__,
{:compare_baseline, suite_name, baseline_version}
)
end
# Server Callbacks
@impl Raxol.Core.Behaviours.BaseManager
def init_manager(_opts) do
state = %{
suites: %{},
results: %{},
baselines: %{},
metadata: %{
registered_at: DateTime.utc_now(),
last_run: nil,
total_runs: 0
}
}
# Auto-discover suites on startup
schedule_discovery()
{:ok, state}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call({:register_suite, module}, _from, state) do
case validate_suite_module(module) do
{:ok, suite_info} ->
name = suite_info.name
updated_suites = Map.put(state.suites, name, suite_info)
{:reply, {:ok, name}, %{state | suites: updated_suites}}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_manager_call(:list_suites, _from, state) do
suite_list =
state.suites
|> Enum.map(fn {name, info} ->
%{
name: name,
module: info.module,
scenario_count: length(info.scenarios),
tags: info.tags,
last_run: get_last_run(state.results, name)
}
end)
|> Enum.sort_by(& &1.name)
{:reply, suite_list, state}
end
def handle_manager_call({:get_suite, name}, _from, state) do
case Map.get(state.suites, name) do
nil -> {:reply, {:error, :not_found}, state}
suite -> {:reply, {:ok, suite}, state}
end
end
def handle_manager_call({:run_all, opts}, _from, state) do
results = run_all_suites(state.suites, opts)
updated_state =
state
|> update_results(results)
|> update_metadata()
{:reply, {:ok, results}, updated_state}
end
def handle_manager_call({:run_suites, filter, opts}, _from, state) do
filtered_suites = filter_suites(state.suites, filter)
results = run_all_suites(filtered_suites, opts)
updated_state =
state
|> update_results(results)
|> update_metadata()
{:reply, {:ok, results}, updated_state}
end
def handle_manager_call(:discover_suites, _from, state) do
updated_state = apply_discovered_suites(state)
count = map_size(updated_state.suites) - map_size(state.suites)
{:reply, {:ok, count}, updated_state}
end
def handle_manager_call({:get_history, suite_name, limit}, _from, state) do
history = get_suite_history(state.results, suite_name, limit)
{:reply, history, state}
end
def handle_manager_call(
{:compare_baseline, suite_name, baseline_version},
_from,
state
) do
comparison = compare_results(state, suite_name, baseline_version)
{:reply, comparison, state}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_info(:auto_discover, state) do
updated_state = apply_discovered_suites(state)
{:noreply, updated_state}
end
# Private Functions
defp apply_discovered_suites(state) do
discovered = discover_benchmark_modules()
Enum.reduce(discovered, state, fn module, acc_state ->
case validate_suite_module(module) do
{:ok, suite_info} ->
name = suite_info.name
updated_suites = Map.put(acc_state.suites, name, suite_info)
%{acc_state | suites: updated_suites}
_ ->
acc_state
end
end)
end
defp validate_suite_module(module) do
cond do
not Code.ensure_loaded?(module) ->
{:error, :module_not_loaded}
not function_exported?(module, :list_suites, 0) ->
{:error, :invalid_suite_module}
true ->
extract_suite_info(module)
end
end
defp extract_suite_info(module) do
try do
suites = module.list_suites()
suite_info = %{
module: module,
name: module |> Module.split() |> List.last(),
scenarios: extract_scenarios(suites),
tags: extract_tags(suites),
registered_at: DateTime.utc_now()
}
{:ok, suite_info}
rescue
_ -> {:error, :extraction_failed}
end
end
defp extract_scenarios(suites) do
suites
|> Enum.flat_map(fn suite ->
Map.get(suite, :scenarios, [])
|> Enum.map(fn scenario ->
%{
name: scenario.name,
tags: Map.get(scenario, :tags, [])
}
end)
end)
end
defp extract_tags(suites) do
suites
|> Enum.flat_map(fn suite ->
suite.scenarios
|> Enum.flat_map(fn scenario -> Map.get(scenario, :tags, []) end)
end)
|> Enum.uniq()
end
defp run_all_suites(suites, opts) do
Task.async_stream(
suites,
fn {name, suite_info} ->
run_suite(name, suite_info, opts)
end,
max_concurrency: Keyword.get(opts, :max_concurrency, 1),
timeout: :infinity
)
|> Enum.map(fn {:ok, result} -> result end)
|> Enum.into(%{})
end
defp run_suite(name, suite_info, opts) do
start_time = System.monotonic_time(:microsecond)
result =
try do
suite_info.module.run_benchmarks(opts)
rescue
error ->
{:error, Exception.format(:error, error, __STACKTRACE__)}
end
end_time = System.monotonic_time(:microsecond)
duration = end_time - start_time
{name,
%{
result: result,
duration_us: duration,
timestamp: DateTime.utc_now(),
opts: opts
}}
end
defp filter_suites(suites, filter) when is_list(filter) do
tags = Keyword.get(filter, :tags, [])
names = Keyword.get(filter, :names, [])
suites
|> Enum.filter(fn {name, suite_info} ->
matches_name = Enum.empty?(names) or name in names
matches_tags =
Enum.empty?(tags) or Enum.any?(tags, &(&1 in suite_info.tags))
matches_name and matches_tags
end)
|> Enum.into(%{})
end
defp filter_suites(suites, filter) when is_binary(filter) do
regex = Regex.compile!(filter, "i")
suites
|> Enum.filter(fn {name, _} ->
Regex.match?(regex, name)
end)
|> Enum.into(%{})
end
defp filter_suites(suites, _), do: suites
defp update_results(state, new_results) do
updated_results =
Enum.reduce(new_results, state.results, fn {suite_name, result}, acc ->
history = Map.get(acc, suite_name, [])
Map.put(acc, suite_name, [result | history])
end)
%{state | results: updated_results}
end
defp update_metadata(state) do
metadata =
state.metadata
|> Map.put(:last_run, DateTime.utc_now())
|> Map.update(:total_runs, 1, &(&1 + 1))
%{state | metadata: metadata}
end
defp get_last_run(results, suite_name) do
case Map.get(results, suite_name) do
nil -> nil
[] -> nil
[latest | _] -> latest.timestamp
end
end
defp get_suite_history(results, suite_name, limit) do
case Map.get(results, suite_name) do
nil -> []
history -> Enum.take(history, limit)
end
end
defp compare_results(state, suite_name, baseline_version) do
current = get_latest_result(state.results, suite_name)
baseline = get_baseline(state.baselines, suite_name, baseline_version)
case {current, baseline} do
{nil, _} ->
{:error, :no_current_results}
{_, nil} ->
{:error, :baseline_not_found}
{current_result, baseline_result} ->
{:ok, calculate_comparison(current_result, baseline_result)}
end
end
defp get_latest_result(results, suite_name) do
case Map.get(results, suite_name) do
nil -> nil
[] -> nil
[latest | _] -> latest
end
end
defp get_baseline(baselines, suite_name, version) do
baselines
|> Map.get(suite_name, %{})
|> Map.get(version)
end
defp calculate_comparison(current, baseline) do
%{
current: current,
baseline: baseline,
performance_change: calculate_performance_change(current, baseline),
memory_change: calculate_memory_change(current, baseline)
}
end
defp calculate_performance_change(_current, _baseline) do
# Implementation would calculate actual performance differences
%{percent_change: 0, direction: :stable}
end
defp calculate_memory_change(_current, _baseline) do
# Implementation would calculate actual memory differences
%{percent_change: 0, direction: :stable}
end
defp discover_benchmark_modules do
# Find all modules that use Raxol.Benchmark.DSL
{:ok, modules} = :application.get_key(:raxol, :modules)
modules
|> Enum.filter(fn module ->
module_str = Atom.to_string(module)
String.contains?(module_str, "Benchmark") or
String.contains?(module_str, "Bench")
end)
|> Enum.filter(fn module ->
Code.ensure_loaded?(module) and
function_exported?(module, :list_suites, 0)
end)
end
defp schedule_discovery do
# Auto-discover suites after a short delay
_ = Process.send_after(self(), :auto_discover, 1000)
end
end