From a353b775b225757ede5530dbac92973de3247dbd Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 25 Jul 2023 16:12:59 +0200 Subject: [PATCH 1/2] Document elixir equivalents where erlang is used --- README.md | 12 ++++++++++- guides/configuration.md | 27 +++++++++++++++++------ guides/distributed-run.md | 22 ++++++++++++------- guides/local-run.md | 9 ++++++++ guides/scenario.md | 45 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 96 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 79003d83..cd924be2 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,23 @@ ---------------------------------------------------------------------------------------------- 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: +It can be used as a `rebar3` dependency: ```erlang {deps, [ {amoc, "3.0.0-rc1"} ]}. ``` + +or in `mix`: +```elixir +defp deps() do + [ + {:amoc, "~> 3.0.0-rc1"} + ] +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). diff --git a/guides/configuration.md b/guides/configuration.md index 7caeb083..ccf1c8b0 100644 --- a/guides/configuration.md +++ b/guides/configuration.md @@ -29,16 +29,29 @@ In the same manner you can also define your own entries to configure the scenari 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(...)` attributes) all the required parameters in advance. For more -information, see the example [scenario module](../integration_test/dummy_scenario.erl) +`-required_variable(...)`/`@required_variable ...` attributes) all the required parameters in advance. +For more information, see the example [scenario module](../integration_test/dummy_scenario.erl) Scenario configuration also can be set/updated at runtime using an API. ```erlang --required_variable(#{name => Name, description => Description, - default_value => Value, - update => UpdateFn, - verification => VerificationFn}). +-required_variable( + #{name => Name, description => Description, + default_value => Value, + update => UpdateMfa, + 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} + ] ``` where @@ -80,7 +93,7 @@ An action to take when the value of this variable is updated. It is triggered at ## Reasonale -NB: the reason why the `-required_variable(...)` is preferred over the usual behaviour +The reason why the `-required_variable(...)` is preferred over the usual behaviour 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: diff --git a/guides/distributed-run.md b/guides/distributed-run.md index b13f49f8..0639ea76 100644 --- a/guides/distributed-run.md +++ b/guides/distributed-run.md @@ -11,24 +11,31 @@ 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). +``` -Start `my_scenario` spawning 100 amoc users with IDs from the range [1,100] inclusive. +Start `my_scenario` spawning 100 amoc users with IDs from the range `[1, 100]` inclusive. In this case sessions are going to be distributed across all nodes except master. `Settings` is an optional proplist with scenario options that can be extracted using amoc_config module. -The values provided in this list shadow OS and APP environment variables. Note that these settings will be propagated - automatically among all the nodes in the amoc cluster. - +The values provided in this list shadow OS and APP environment variables. +Note that these settings will be propagated automatically among all the nodes in the amoc cluster. ```erlang amoc_dist:add(50). ``` +```elixir +: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). +``` Remove 50 sessions. Where ``Force`` is a boolean of value: @@ -36,10 +43,9 @@ 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) -All the users are ``temporary`` children of the ``simple_one_for_one`` supervisor with the ``shutdown`` -key set to ``2000``. +All the users are `temporary` children of the `simple_one_for_one` supervisor with the `shutdown` key set to `2000`. -Also all the user processes trap exit signal. +Also all the user processes trap exits. ## Don't stop scenario on exit diff --git a/guides/local-run.md b/guides/local-run.md index 80273c75..e4459e4e 100644 --- a/guides/local-run.md +++ b/guides/local-run.md @@ -8,16 +8,25 @@ Start `my_scenario` spawning 10 amoc users with IDs from range (1,10) inclusive. ```erlang amoc:do(my_scenario, 10, []). ``` +```elixir +:amoc.do(:my_scenario, 10, []). +``` Add 10 more user sessions. ```erlang amoc:add(10). ``` +```elixir +:amoc.add(10). +``` Remove 10 users. ```erlang amoc:remove(10, true). ``` +```elixir +:amoc.remove(10, true). +``` #### Many independent Amoc nodes diff --git a/guides/scenario.md b/guides/scenario.md index 19db3adf..aa8e991d 100644 --- a/guides/scenario.md +++ b/guides/scenario.md @@ -1,7 +1,6 @@ ## Developing a scenario -A scenario specification is an [Erlang](https://www.erlang.org/) module that implements -the `amoc_scenario` behaviour. +A scenario specification is an [Erlang](https://www.erlang.org/) or [Elixir](https://elixir-lang.org/) module that implements the `amoc_scenario` behaviour. It has to export two callback functions: - `init/0` - called only once per test run on every node, at the very beginning. It can be used for setting up initial (global) state: metrics, database connections, etc. @@ -34,8 +33,28 @@ start(Id) -> %% send some messages again ok. ``` -or, using the `start/2` function: +```elixir +defmodule LoadTest do + @behaviour :amoc_scenario + + def init do + ## initialize some metrics + settings = get_settings() + :ok + end + def start(id) do + ## connect user + ## fetch user's history + ## send some messages + ## wait a little bit + ## send some messages again + ok. + end + +end +``` +or, using the `start/2` function: ```erlang -module(my_scenario). @@ -55,7 +74,27 @@ start(Id, Settings) -> %% send some messages again ok. ``` +```elixir +defmodule LoadTest do + @behaviour :amoc_scenario + + def init do + ## initialize some metrics + settings = get_settings() + {:ok, Settings} + end + def start(id, settings) do + ## connect user using Settings + ## fetch user's history + ## send some messages + ## wait a little bit + ## send some messages again + ok. + end + +end +``` For developing XMPP scenarios, we recommend the [esl/escalus](https://github.com/esl/escalus) library. From 348c02bf8793befadc4a87a207911468b867b91f Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Fri, 1 Dec 2023 15:33:48 +0100 Subject: [PATCH 2/2] Fix simple link in docs --- guides/throttle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/throttle.md b/guides/throttle.md index 18d2cd9f..f1fad81e 100644 --- a/guides/throttle.md +++ b/guides/throttle.md @@ -41,7 +41,7 @@ user_loop(Id) -> ``` Here a system should be under a continuous load of 100 messages per minute. -Note that if we used something like `amoc_throttle:run(messages_rate, fun() -> send_message(Id) end)` instead of `send_and_wait/2` the system would be flooded with requests. +Note that if we used something like `amoc_throttle:run(messages_rate, fun() -> send_message(Id) end)` instead of `amoc_throttle:send_and_wait/2` the system would be flooded with requests. A test may of course be much more complicated. For example it can have the load changing in time.