Skip to content

Commit

Permalink
Merge pull request #181 from esl/guides
Browse files Browse the repository at this point in the history
Fix docs
  • Loading branch information
NelsonVides authored Mar 4, 2024
2 parents 7b1ea3d + 349cee0 commit f1d9152
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 52 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/amoc/)
[![codecov](https://codecov.io/github/esl/amoc/graph/badge.svg?token=R1zXAjO7H7)](https://codecov.io/github/esl/amoc)

----------------------------------------------------------------------------------------------
---

A Murder of Crows, aka amoc, is a simple framework for running massively parallel tests in a distributed environment.

It can be used as a `rebar3` dependency:
Expand All @@ -16,6 +17,7 @@ It can be used as a `rebar3` dependency:
```

or in `mix`:

```elixir
defp deps() do
[
Expand All @@ -27,11 +29,12 @@ end
[MongooseIM](https://github.com/esl/MongooseIM) is continuously being load tested with Amoc.
All the XMPP scenarios can be found [here](https://github.com/esl/amoc-arsenal-xmpp).

---------------------------------------------------------------------
---

In order to implement and run locally your scenarios, follow the chapters about
[developing](guides/scenario.md) and [running](guides/local-run.md) a scenario
locally.
Before [setting up the distributed environment](guides/distributed.md),
[developing](https://hexdocs.pm/amoc/scenario.html) and [running](https://hexdocs.pm/amoc/local-run.html)
a scenario locally.
Before [setting up the distributed environment](https://hexdocs.pm/amoc/distributed.html),
please read through the configuration overview.

To see the full documentation, see [hexdocs](https://hexdocs.pm/amoc).
Expand Down
52 changes: 32 additions & 20 deletions guides/configuration.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
## Configuration

Amoc is configured through environment variables (uppercase with prefix ``AMOC_``).
Amoc is configured through environment variables (uppercase with prefix `AMOC_`).
Note that by default, environment variables values are deserialized as Erlang terms. This behavior can be customized by providing an alternative parsing module in the `config_parser_mod` configuration parameter for the amoc application. It can be done using the [application:set_env/4](https://www.erlang.org/doc/man/application#set_env-4) interface or via a [config file](https://www.erlang.org/doc/man/config). The custom parsing module must implement the `amoc_config_env` behavior.

Amoc supports the following generic configuration parameters:

* ``nodes`` - required for the distributed scenario execution, a list of nodes that should be clustered together:
* `nodes` - required for the distributed scenario execution, a list of nodes that should be clustered together:
* default value - empty list (`[]`)
* example: `AMOC_NODES="['amoc@amoc-1', 'amoc@amoc-2']"`

* ``api_port`` - a port for the amoc REST interfaces:
* `api_port` - a port for the amoc REST interfaces:
* default value - 4000
* example: `AMOC_API_PORT="4000"`

* ``interarrival`` - a delay (in ms, for each node in the cluster independently) between creating the processes
* `interarrival` - a delay (in ms, for each node in the cluster independently) between creating the processes
for two consecutive users:
* default value - 50 ms.
* example: `AMOC_INTERARRIVAL="50"`
* this parameter can be updated at runtime (in the same way as scenario configuration).

* ``extra_code_paths`` - a list of paths that should be included using `code:add_pathsz/1` interface
* `extra_code_paths` - a list of paths that should be included using `code:add_pathsz/1` interface
* default value - empty list (`[]`)
* example: `AMOC_EXTRA_CODE_PATHS='["/some/path", "/another/path"]'`

In the same manner you can also define your own entries to configure the scenario.

## Required Variables

The ``amoc_config:get/1`` and ``amoc_config:get/2`` interfaces can be used to get
The `amoc_config:get/1` and `amoc_config:get/2` interfaces can be used to get
parameters required for your scenario, however every scenario must declare (using
`-required_variable(...)`/`@required_variable ...` attributes) all the required parameters in advance.
For more information, see the example [scenario module](../integration_test/dummy_scenario.erl)
For more information, see the example `dummy_scenario` in integration tests.

Scenario configuration also can be set/updated at runtime using an API.

Expand All @@ -42,37 +42,45 @@ Scenario configuration also can be set/updated at runtime using an API.
verification => VerificationMfa}
).
```

```elixir
## Note that the attribute needs to be marked as persisted
## for the Elixir compiler to store it in the generated BEAM file.
Module.register_attribute(__MODULE__, :required_variable, persist: true)
@required_variable [
%{:name => name, :description => description,
:default_value => 6,
:update => updateMfa,
:verification => verificationMfa}
]
Module.register_attribute(__MODULE__, :required_variable, accumulate: true, persist: true)

@required_variable %{
name: name,
description: description,
default_value: 6,
update: updated_mfa,
verification: verification_mfa
}
```

where

### `name`

* **Syntax:** atom
* **Example:** `name = var1`
* **Example:** `var1`
* **Default:** this field is mandatory

### `description`

* **Syntax:** A string describing how this variable is used, can be extracted by APIs to document the behavior
* **Example:** `description = "a description of this variable"`
* **Example:** `"a description of this variable"`
* **Default:** this field is mandatory

### `default_value`

* **Syntax:** value of the expected type
* **Example:** `default_value = 10`
* **Example:** `10`
* **Default:** `undefined`

### `verification`

* **Syntax:** `none`, a list of allowed values, or an `mfa` of arity `1`
* **Example:** `verification = {?MODULE, is_binary, 1}`
* **Example:** `{?MODULE, is_binary, 1}`
* **Default:** `none`

A verification function that will check the given value is correct. It is trigger for verifying the initial values, including the default value, and before updated values are applied.
Expand All @@ -82,8 +90,9 @@ A verification function that will check the given value is correct. It is trigge
must be pure and return a boolean or a `{true, NewValue} | {false, Reason}`. It can also be used for preprocessing of the input value by returning `{true, NewValue}`.

### `update`

* **Syntax:** `read_only`, `none`, or an `mfa` of arity 2
* **Example:** `update = {?MODULE, update, 2}`
* **Example:** `{?MODULE, update, 2}`
* **Default:** `read_only`

An action to take when the value of this variable is updated. It is triggered at runtime when updates to the value are applied.
Expand All @@ -97,15 +106,18 @@ The reason why the `-required_variable(...)` is preferred over the usual behavio
callback is because the orchestration tools can easily extract the attributes even
without the compilation, while configuring via a callback, requires a successful
compilation of the module. As an example, a module:

```erlang
-module(example).
-include("some_unavailable_header.hrl").
-some_attr({"some", value}).
-some_attr([{another, "value"},
{yet, <<"another">>, "value"}]).
```
cannot be compiled without the ``some_unavailable_header.hrl`` file, but we still

cannot be compiled without the `some_unavailable_header.hrl` file, but we still
can parse it and extract the attributes:

```
Eshell V14.0 (press Ctrl+G to abort, type help(). for help)
1> c(example).
Expand Down
5 changes: 3 additions & 2 deletions guides/coordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ This scenario shows how the `users` interact with `amoc_coordinator`:
```erlang
-module(example).

-behaviour(amoc_scenario).

-export([init/0]).
-export([start/2]).

Expand Down Expand Up @@ -80,7 +82,6 @@ start(Id, _Settings) ->
ok.
```


To run it:

```bash
Expand All @@ -90,7 +91,7 @@ $ _build/default/rel/amoc/bin/amoc console
1> amoc:do(example, 5, []).
```

Filtered, formated and explained output:
Filtered, formatted and explained output:

```erlang
User = 1 % First user is started
Expand Down
18 changes: 11 additions & 7 deletions guides/distributed-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ and start scenarios on all known nodes (except master).
```erlang
amoc_dist:do(my_scenario, 100, Settings).
```

```elixir
:amoc_dist.do(:my_scenario, 100, settings).
:amoc_dist.do(:my_scenario, 100, settings)
```

Start `my_scenario` spawning 100 amoc users with IDs from the range `[1, 100]` inclusive.
Expand All @@ -25,31 +26,34 @@ Note that these settings will be propagated automatically among all the nodes in
```erlang
amoc_dist:add(50).
```

```elixir
:amoc_dist.add(50).
:amoc_dist.add(50)
```

Add 50 more users to the currently started scenario.

```erlang
amoc_dist:remove(50, Force).
```

```elixir
:amoc_dist.remove(50, force).
:amoc_dist.remove(50, force)
```

Remove 50 sessions.

Where ``Force`` is a boolean of value:
Where `Force` is a boolean of value:

* ``true`` - to kill the user processes using ``supervisor:terminate_child/2`` function
* ``false`` - to send ``exit(User,shutdown)`` signal to the user process (can be ignored by the user)
* `true` - to kill the user processes using `supervisor:terminate_child/2` function
* `false` - to send `exit(User, shutdown)` signal to the user process (can be ignored by the user)

All the users are `temporary` children of a `simple_one_for_one` supervisor with the `shutdown` key set to `2000`.

Note that removal operation is asynchronous, and if we call `amoc_controller:remove_users/2` two times in a row, it may select the same users for removal.

Also all the user processes trap exits.


## Don't stop scenario on exit

There is one problem with the `bin/amoc console` command. When you exit the Erlang
Expand Down
12 changes: 9 additions & 3 deletions guides/distributed.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
To build a Docker image with Amoc, run the following command from the root of
the repository:

```
docker build -f Dockerfile -t amoc_image:tag .
docker build -t amoc_image:tag .
```

It is important to start building at project root
(it is indicated with dot `.` at the end of command).
It will set build context at the project root.
Expand All @@ -13,15 +15,18 @@ Dockerfile commands expects a context to be set like that:
When the image is ready you can start either a single instance of Amoc or configure a distributed environment,
for which you should follow the steps described below.

Before running Amoc containers, create a network and start a Graphite instance to collect and visualise some metrics.
Before running Amoc containers, create a network and start a Graphite instance to collect and visualize some metrics.

```
docker network create amoc-test-network
docker run --rm -d --name=graphite --network amoc-test-network \
-p 2003:2003 -p 8080:80 graphiteapp/graphite-statsd
```

Start two Amoc containers, export all of the necessary environmental variables so that the nodes can communicate with each other and send metrics to Graphite.
In order to use Amoc HTTP API for uploading and starting scenarios, port 4000 should be published.

```
docker run --rm -t -d --name amoc-1 -h amoc-1 \
--network amoc-test-network \
Expand All @@ -44,7 +49,8 @@ docker run --rm -t -d --name amoc-2 -h amoc-2 \
amoc_image:tag
```

Connect to Amoc console and go to the [next](../doc/distributed-run.md) section.
Connect to Amoc console and go to the [next](distributed-run.md) section.

```
docker exec -it amoc-1 /home/amoc/amoc/bin/amoc remote_console
```
8 changes: 4 additions & 4 deletions guides/local-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ Start `my_scenario` spawning 10 amoc users with IDs from range (1,10) inclusive.
amoc:do(my_scenario, 10, []).
```
```elixir
:amoc.do(:my_scenario, 10, []).
:amoc.do(:my_scenario, 10, [])
```

Add 10 more user sessions.
```erlang
amoc:add(10).
```
```elixir
:amoc.add(10).
:amoc.add(10)
```

Remove 10 users.
```erlang
amoc:remove(10, true).
```
```elixir
:amoc.remove(10, true).
:amoc.remove(10, true)
```

Note that removal operation is asynchronous, and if we call `amoc_controller:remove_users/2` two times in a row, it may select the same users for removal.
Expand All @@ -38,7 +38,7 @@ Sometimes a need arises to run several Amoc nodes independently from each other.
In this case we would like to be able to run different ranges of user ids on every node.
To do so, the following trick could be applied:

1. `amoc:start(my_scenario,0,[]).`
1. `amoc:start(my_scenario, 0, []).`
2. `amoc_controller:add_users(StartId, StopId).`

NODE: in case of independent Amoc nodes, it's also possible to run different scenarios on different nodes.
Loading

0 comments on commit f1d9152

Please sign in to comment.