Current section
Files
Jump to
Current section
Files
lib/loadex.ex
defmodule Loadex do
@moduledoc """
A simple distributed load test runner.
`Loadex` was created with two things in mind - genarating huge loads in a controlled manner, while being able to fully customize the test's flow.
These goals are achieved by using plain Elixir to create *scenarios* and then laveraging Elixir's massive concurrency capabilities to run them on one or multiple machines.
## Example:
defmodule ExampleScenario do
use Loadex.Scenario
setup do
1..100
end
scenario index do
loop_after 500, 10, iteration do
IO.puts("My number is \#{index}, iteration \#{iteration}!")
end
end
teardown index do
IO.puts("Bye from \#{index}!")
end
end
For detailed instructions on how to create a scenario please refer to `Loadex.Scenario`.
"""
@doc """
Runs scenarios.
Running a scenario means executing its `setup` callback and passing its results to the `scenario` implementation.
For more detailed information on how to create scenarios please refer to `Loadex.Scenario`.
When running in a distributed environment (see `join_cluster/1`), **the `setup` callback will be executed on a node `run/1` is called on** and its results will
be distributed along the cluster.
By default scenarios are loaded from `./scenarios` directory and executed all at the same time.
A single scenario can be specified by passing a `scenario` option.
Scenarios can be restarted after crashing or quitting by passing `restart: true` option.
Rate (per second), at which scenarios are started can be adjusted by passing a `rate` option. **Note:** this doesn't affect *restart* rate.
## Example:
iex> Loadex.run(scenario: "./scenarios/example_scenario.exs", rate: 30, restart: true)
"""
@spec run(opts :: [restart: boolean(), scenario: nil | binary(), rate: non_neg_integer()]) ::
{:ok, :scenarios_started}
def run(opts \\ [restart: false, scenario: nil, rate: 1000]) do
opts[:scenario]
|> load_scenarios()
|> IO.inspect(label: "Scenarios")
|> Stream.map(&Loadex.Runner.run(&1, opts[:restart], opts[:rate]))
|> Stream.run()
{:ok, :scenarios_started}
end
@doc """
Adds `nodes` into the `Loadex` cluster.
"""
@spec join_cluster(nodes :: [atom()]) :: [atom()]
def join_cluster(nodes) when is_list(nodes) do
nodes
|> Enum.map(fn node ->
case Node.ping(node) do
:pong ->
{node, :ok}
:pang ->
{node, :node_down}
end
end)
end
@doc """
Stops all scenarios.
"""
@spec stop_all :: :ok
def stop_all do
Loadex.Runner.Supervisor.restart()
end
defp load_scenarios(maybe_scenario) do
on_all_nodes(Loadex.Scenario.Loader, :load, [maybe_scenario])
end
defp on_all_nodes(mod, action, args) do
:rpc.multicall(mod, action, args) |> elem(0) |> List.first()
end
end