-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Throttle api #184
base: master
Are you sure you want to change the base?
Throttle api #184
Changes from 7 commits
47e7f6c
af068a6
df4b9e4
a2f225d
2ae0719
e09fdf1
1d9b606
1672ec4
e8d91ef
764bce5
66cf647
2caa2c7
d1efeaa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,14 @@ See `amoc_throttle` | |
## Overview | ||
|
||
Amoc throttle is a module that allows limiting the number of users' actions per given interval, no matter how many users there are in a test. | ||
It works in both local and distributed environments, allows for dynamic rate changes during a test and exposes metrics which show the number of requests and executions. | ||
It works in both local and distributed environments, allows for dynamic rate changes during a test and exposes telemetry events showing the number of requests and executions. | ||
|
||
Amoc throttle allows to: | ||
|
||
- Setting the execution `Rate` per `Interval` | ||
- Limiting the number of parallel executions when `Rate` is set to `infinity` | ||
- Setting the `Interarrival` time between actions. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not different to execution rate, but just another form of setting it. |
||
|
||
Amoc throttle allows setting the execution `Rate` per `Interval` or limiting the number of parallel executions when `Interval` is set to `0`. | ||
Each `Rate` is identified with a `Name`. | ||
The rate limiting mechanism allows responding to a request only when it does not exceed the given `Rate`. | ||
Amoc throttle makes sure that the given `Rate` per `Interval` is maintained on a constant level. | ||
|
@@ -102,8 +107,8 @@ For every `Name`, a `NoOfProcesses` are created, each responsible for keeping ex | |
### Distributed environment | ||
|
||
#### Metrics | ||
In a distributed environment every Amoc node with a throttle started, exposes metrics showing the numbers of requests and executions. | ||
Those exposed by the master node show the sum of all metrics from all nodes. | ||
In a distributed environment every Amoc node with a throttle started, exposes telemetry events showing the numbers of requests and executions. | ||
Those exposed by the master node show the aggregate of all telemetry events from all nodes. | ||
This allows to quickly see the real rates across the whole system. | ||
|
||
#### Workflow | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,57 +3,63 @@ | |
-module(amoc_throttle). | ||
|
||
%% API | ||
-export([start/2, | ||
start/3, | ||
start/4, | ||
send/3, | ||
send/2, | ||
send_and_wait/2, | ||
wait/1, | ||
run/2, | ||
pause/1, | ||
resume/1, | ||
change_rate/3, | ||
change_rate_gradually/6, | ||
stop/1]). | ||
|
||
-deprecated([{send_and_wait, 2, "use wait/1 instead"}]). | ||
|
||
-define(DEFAULT_NO_PROCESSES, 10). | ||
-define(DEFAULT_INTERVAL, 60000). %% one minute | ||
-define(NONNEG_INT(N), (is_integer(N) andalso N >= 0)). | ||
-define(POS_INT(N), (is_integer(N) andalso N > 0)). | ||
-export([start/2, stop/1, | ||
send/2, send/3, wait/1, | ||
run/2, pause/1, resume/1, unlock/1, | ||
change_rate/2, change_rate_gradually/2]). | ||
|
||
-type name() :: atom(). | ||
-type rate() :: pos_integer(). | ||
%% Atom representing the name of the throttle. | ||
DenysGonchar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-type rate() :: infinity | non_neg_integer(). | ||
%% Number of events per given `t:interval/0', or infinity for effectively unlocking all throttling. | ||
%% Note that a rate of zero means effectively pausing the throttle. | ||
-type interarrival() :: infinity | non_neg_integer(). | ||
%% Time in milliseconds between two events, or infinity for effectively pausing the throttle. Note | ||
%% that an interarrival of zero means effectively unlocking all throttling. | ||
-type interval() :: non_neg_integer(). | ||
%% In milliseconds, defaults to 60000 (one minute) when not given. | ||
%% An interval of 0 means no delay at all, only the number of simultaneous executions will be | ||
%% controlled, which corresponds to the number of processes started | ||
-export_type([name/0, rate/0, interval/0]). | ||
|
||
%% @see start/4 | ||
-spec start(name(), rate()) -> ok | {error, any()}. | ||
start(Name, Rate) -> | ||
start(Name, Rate, ?DEFAULT_INTERVAL). | ||
%% In milliseconds, defaults to 60000 (one minute). | ||
-type throttle() :: #{rate := rate(), interval := interval()} | | ||
#{interarrival := interarrival()}. | ||
%% Throttle unit of measurement | ||
-type config() :: #{rate := rate(), | ||
interval => interval(), | ||
parallelism => non_neg_integer()} | ||
| #{interarrival := interarrival(), | ||
parallelism => non_neg_integer()}. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is the meaning of 0 |
||
%% Literal throttle configuration. | ||
|
||
-type gradual_rate_config() :: #{from_rate := non_neg_integer(), | ||
to_rate := non_neg_integer(), | ||
interval => interval(), | ||
step_interval => pos_integer(), | ||
step_size => pos_integer(), | ||
step_count => pos_integer(), | ||
duration => pos_integer()} | | ||
#{from_interarrival := interarrival(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems that |
||
to_interarrival := interarrival(), | ||
step_interval => pos_integer(), | ||
step_size => pos_integer(), | ||
step_count => pos_integer(), | ||
duration => pos_integer()}. | ||
%% Configuration for a gradual throttle rate change | ||
%% | ||
%% `From' and `To' rates are required. `interval' defaults to 1s and `step_size' to 1 (or -1 if applies), | ||
%% that is, the throttle will be changed in increments of 1. | ||
%% | ||
%% All other values can be calculated from the provided. | ||
|
||
%% @see start/4 | ||
-spec start(name(), rate(), non_neg_integer()) -> ok | {error, any()}. | ||
start(Name, Rate, Interval) -> | ||
start(Name, Rate, Interval, ?DEFAULT_NO_PROCESSES). | ||
-export_type([name/0, rate/0, interval/0, throttle/0, config/0, gradual_rate_config/0]). | ||
|
||
%% @doc Starts the throttle mechanism for a given `Name' with a given `Rate' per `Interval'. | ||
%% @doc Starts the throttle mechanism for a given `Name' with a given config. | ||
%% | ||
%% The optional arguments are an `Interval' (default is one minute) and a ` NoOfProcesses' (default is 10). | ||
%% `Name' is needed to identify the rate as a single test can have different rates for different tasks. | ||
%% `Interval' is given in milliseconds and can be changed to a different value for convenience or higher granularity. | ||
%% It also accepts a special value of `0' which limits the number of parallel executions associated with `Name' to `Rate'. | ||
-spec start(name(), rate(), interval(), pos_integer()) -> ok | {error, any()}. | ||
start(Name, Rate, Interval, NoOfProcesses) | ||
when is_atom(Name), ?POS_INT(Rate), ?NONNEG_INT(Interval), ?POS_INT(NoOfProcesses) -> | ||
amoc_throttle_controller:ensure_throttle_processes_started(Name, Rate, Interval, NoOfProcesses); | ||
start(_Name, _Rate, _Interval, _NoOfProcesses) -> | ||
{error, invalid_throttle}. | ||
-spec start(name(), config() | rate()) -> {ok, started | already_started} | {error, any()}. | ||
start(Name, #{} = Config) -> | ||
amoc_throttle_controller:ensure_throttle_processes_started(Name, Config); | ||
start(Name, Rate) -> | ||
amoc_throttle_controller:ensure_throttle_processes_started(Name, #{rate => Rate}). | ||
|
||
%% @doc Pauses executions for the given `Name' as if `Rate' was set to `0'. | ||
%% | ||
|
@@ -67,12 +73,17 @@ pause(Name) -> | |
resume(Name) -> | ||
amoc_throttle_controller:resume(Name). | ||
|
||
%% @doc Sets `Rate' and `Interval' for `Name' according to the given values. | ||
%% @doc Unlocks executions for the given `Name' as if `Rate' was set to `infinity'. | ||
-spec unlock(name()) -> ok | {error, any()}. | ||
unlock(Name) -> | ||
amoc_throttle_controller:unlock(Name). | ||
|
||
%% @doc Sets `Throttle' for `Name' according to the given values. | ||
%% | ||
%% Can change whether Amoc throttle limits `Name' to parallel executions or to `Rate' per `Interval', | ||
%% according to the given `Interval' value. | ||
-spec change_rate(name(), rate(), interval()) -> ok | {error, any()}. | ||
change_rate(Name, Rate, Interval) -> | ||
-spec change_rate(name(), throttle()) -> ok | {error, any()}. | ||
change_rate(Name, #{rate := Rate, interval := Interval}) -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't we want to support |
||
amoc_throttle_controller:change_rate(Name, Rate, Interval). | ||
|
||
%% @doc Allows to set a plan of gradual rate changes for a given `Name'. | ||
|
@@ -83,19 +94,17 @@ change_rate(Name, Rate, Interval) -> | |
%% The rate is calculated at each step in relation to the `RateInterval', which can also be `0'. | ||
%% There will be `NoOfSteps' steps, each taking `StepInterval' time in milliseconds. | ||
%% | ||
%% Be aware that, at first, the rate will be changed to `FromRate' per `RateInterval' and this is not considered a step. | ||
-spec change_rate_gradually(name(), rate(), rate(), interval(), pos_integer(), pos_integer()) -> | ||
%% Be aware that, at first, the rate will be changed to `FromRate' per `RateInterval' | ||
%% and this is not considered a step. | ||
-spec change_rate_gradually(name(), gradual_rate_config()) -> | ||
ok | {error, any()}. | ||
change_rate_gradually(Name, FromRate, ToRate, RateInterval, StepInterval, NoOfSteps) -> | ||
amoc_throttle_controller:change_rate_gradually( | ||
Name, FromRate, ToRate, RateInterval, StepInterval, NoOfSteps). | ||
change_rate_gradually(Name, Config) -> | ||
amoc_throttle_controller:change_rate_gradually(Name, Config). | ||
|
||
%% @doc Executes a given function `Fn' when it does not exceed the rate for `Name'. | ||
%% | ||
%% `Fn' is executed in the context of a new process spawned on the same node on which | ||
%% the process executing `run/2' runs, so a call to `run/2' is non-blocking. | ||
%% This function is used internally by both `send' and `send_and_wait/2' functions, | ||
%% so all those actions will be limited to the same rate when called with the same `Name'. | ||
%% | ||
%% Diagram showing function execution flow in distributed environment, | ||
%% generated using https://sequencediagram.org/: | ||
|
@@ -145,13 +154,6 @@ send(Name, Msg) -> | |
send(Name, Pid, Msg) -> | ||
amoc_throttle_runner:throttle(Name, {Pid, Msg}). | ||
|
||
%% @doc Sends and receives the given message `Msg'. | ||
%% | ||
%% Deprecated in favour of `wait/1' | ||
-spec send_and_wait(name(), any()) -> ok | {error, any()}. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this interface is actively used by amoc_arsenal_xmpp. if it's so, please add tickets for amoc_arsena/amoc_arsenal_xmpp |
||
send_and_wait(Name, _) -> | ||
amoc_throttle_runner:throttle(Name, wait). | ||
|
||
%% @doc Blocks the caller until the throttle mechanism allows. | ||
-spec wait(name()) -> ok | {error, any()}. | ||
wait(Name) -> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rate
is supposed to be the number of parrallel executions ifInterval
is 0