# `LiveLoad.Cluster`
[🔗](https://github.com/probably-not/live-load/blob/v0.1.1/lib/live_load/cluster.ex#L1)

`LiveLoad.Cluster` defines the creation of an ephemeral cluster of nodes (via `FLAME`) that `LiveLoad` runs its scenarios on.

A cluster is started for each run of a scenario. It is initialized eagerly by placing `FLAME.Trackable` resources on each node
and ensuring that a node can have a maximum concurrency of 1, to ensure that only a single FLAME process is run on each node.

The cluster initialization goes through optimization phases by checking the expected resource usage of a browser and calculating
how many nodes will be necessary based on the node's actual resource availability. A `LiveLoad.Browser.Connection` must provide 3
callbacks: `c:LiveLoad.Browser.Connection.browser_contexts_per_core/0`, `c:LiveLoad.Browser.Connection.browser_memory_usage_bytes/0`
and `c:LiveLoad.Browser.Connection.context_memory_usage_bytes/0` to allow the cluster initialization to calculate how many nodes
are necessary in order to pre-warm the pool. If the amount of nodes necessary exceeds the configured `max_allowed_nodes`, an error
will be returned and the scenario will not be run.

# `cluster_initialization_error`

```elixir
@type cluster_initialization_error() ::
  scenario_already_running_error()
  | not_enough_nodes_error()
  | node_too_small_error()
  | cluster_node_creation_timeout_error()
  | {:error, term()}
```

# `cluster_name_invalid_error`

```elixir
@type cluster_name_invalid_error() :: {:error, :cluster_name_invalid}
```

An error returned from the cluster initialization when the creation of a cluster node crashes due to the `FLAME.Pool` name being invalid.

# `cluster_node_creation_timeout_error`

```elixir
@type cluster_node_creation_timeout_error() ::
  {:error, :cluster_node_creation_timeout}
```

An error returned from the cluster initialization when the creation of a cluster node takes longer than the configured
timeout for the `FLAME.Pool`.

# `flame_backend`

```elixir
@type flame_backend() :: module()
```

A module implementing the `FLAME.Backend` behaviour.

# `flame_backend_opts_opt`

```elixir
@type flame_backend_opts_opt() :: {:flame_backend_opts, Keyword.t()}
```

Options passed to the given `t:flame_backend/0` on initialization of the `FLAME.Pool`.

Defaults to an empty list.

# `flame_pool_opts_opt`

```elixir
@type flame_pool_opts_opt() :: {:flame_pool_opts, Keyword.t()}
```

Options passed to the `FLAME.Pool` on initialization.

The following options are automatically overridden by `LiveLoad.Cluster` and cannot be
manually passed in:
- `:name`
- `:max_concurrency`
- `:track_resources`
- `:min`
- `:max`
- `:single_use`

See `FLAME.Pool` for all other available options.

Defaults to an empty list.

# `max_allowed_nodes_opt`

```elixir
@type max_allowed_nodes_opt() :: {:max_allowed_nodes, pos_integer()}
```

Defines the maximum nodes that this load test can create when setting up a cluster.

Nodes are created eagerly on initialization of the cluster and maintained until the end
of the load test's run. During cluster initialization, an optimization phase will be run
to calculate the number of nodes required for this load test to complete based on the number
of users for this load test, the resources available on a cluster node, and the resources required
by the `LiveLoad.Browser.Connection` module used in this load test. Should the number of nodes required
exceed the given value, an error will be returned.

The value given to this option must be a positive integer. If a negative integer or 0 is passed in,
an ArgumentError will be raised.

Defaults to 100 nodes.

# `node_too_small_error`

```elixir
@type node_too_small_error() :: {:error, :node_too_small}
```

An error returned from the cluster initialization when the node started by the given `FLAME.Backend` implementation
and the given `t:flame_pool_opts_opt/0` options is too small to actually handle creating a browser on the node.

This is determined by the `LiveLoad.Cluster.Node` started during the cluster initialization.

# `not_enough_nodes_error`

```elixir
@type not_enough_nodes_error() ::
  {:error,
   {:necessary_nodes_exceeds_max_allowed_nodes, necessary :: pos_integer()}}
```

An error returned from the cluster initialization when the given `t:max_allowed_nodes_opt/0`
is smaller than the calculated necessary nodes needed to run the load test on the FLAME nodes
configured by the the given FLAME configurations.

The calculation of necessary nodes is a heuristic based on the expected resource usage per user process
determined by the `c:LiveLoad.Browser.Connection.browser_contexts_per_core/0`,
`c:LiveLoad.Browser.Connection.browser_memory_usage_bytes/0` and `c:LiveLoad.Browser.Connection.context_memory_usage_bytes/0`
callbacks on the selected `LiveLoad.Browser.Connection` implementation for this run.

# `option`

```elixir
@type option() ::
  flame_backend_opts_opt() | max_allowed_nodes_opt() | flame_pool_opts_opt()
```

Initialization options for running a `LiveLoad.Cluster`.

# `scenario_already_running_error`

```elixir
@type scenario_already_running_error() :: {:error, :scenario_is_already_running}
```

An error returned from the cluster initialization when the scenario is currently running and a new pool cannot be started.

This error is a limitation of FLAME, as FLAME requires atoms as pool names due to the usage of named ETS tables.

# `t`

```elixir
@type t() :: %LiveLoad.Cluster{
  pool_name: atom(),
  pool_node_names: [node()],
  pool_nodes: [LiveLoad.Cluster.Node.t()]
}
```

