Skip to content

Commit

Permalink
Make all scenario callbacks telemetry spans
Browse files Browse the repository at this point in the history
  • Loading branch information
NelsonVides committed Dec 14, 2023
1 parent 38d1512 commit b834956
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 35 deletions.
33 changes: 20 additions & 13 deletions guides/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,33 @@ Amoc also exposes the following telemetry events:

## Scenario

Indicates the start of a scenario after having successfully return from the `init/0` callback:
All telemetry spans below contain an extra key `return` in the metadata for the `stop` event with the return value of the given callback.

A telemetry span of a scenario initialisation (i.e. the exported `init/0` function):
```erlang
event_name: [amoc, scenario, start]
measurements: #{count := 1}
metadata: #{monotonic_time := integer(), scenario := module()}
event_name: [amoc, scenario, init, _]
measurements: #{} %% As described in `telemetry:span/3`
metadata: #{scenario := module()} %% Plus as described in `telemetry:span/3`
```

Indicates termination of a scenario after having run the optional `terminate/` callback, and contains the return value of such:
A telemetry span of a full scenario execution for a user (i.e. the exported `start/1,2` function):
```erlang
event_name: [amoc, scenario, stop]
measurements: #{count := 1}
metadata: #{monotonic_time := integer(), scenario := module(), return := term()}
event_name: [amoc, scenario, start, _]
measurements: #{} %% As described in `telemetry:span/3`
metadata: #{scenario := module(), %% Running scenario
state := term(), %% The state as returned by `init/0`
user_id := non_neg_integer() %% User ID assigned to the running process
} %% Plus as described in `telemetry:span/3`
```

A telemetry span of a full scenario execution for a user (i.e. the exported `start/1,2` function):
A telemetry span of a full scenario execution for a user (i.e. the exported `terminate/1,2` function):
```erlang
event_name: [amoc, scenario, user, _]
measurements: #{} %% As described in `telemetry:span/3`
metadata: #{scenario := module(),
user_id := non_neg_integer()} %% Plus as described in `telemetry:span/3`
event_name: [amoc, scenario, terminate, _]
measurements: #{} %% As described in `telemetry:span/3`
metadata: #{scenario := module(), %% Running scenario
state := term(), %% The state as returned by `init/0`
user_id := non_neg_integer() %% User ID assigned to the running process
} %% Plus as described in `telemetry:span/3`
```

## Controller
Expand Down
5 changes: 1 addition & 4 deletions src/amoc_controller.erl
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ handle_start_scenario(Scenario, Settings, #state{status = idle} = State) ->
scenario = Scenario,
scenario_state = ScenarioState,
status = running},
amoc_telemetry:execute([scenario, start], #{count => 1}, #{scenario => Scenario}),
{ok, NewState};
{error, _} = Error ->
NewState = State#state{scenario = Scenario, status = Error},
Expand Down Expand Up @@ -316,9 +315,7 @@ init_scenario(Scenario, Settings) ->

-spec terminate_scenario(state()) -> ok | {ok, any()} | {error, any()}.
terminate_scenario(#state{scenario = Scenario, scenario_state = ScenarioState}) ->
Ret = amoc_scenario:terminate(Scenario, ScenarioState),
amoc_telemetry:execute([scenario, stop], #{count => 1}, #{scenario => Scenario, return => Ret}),
Ret.
amoc_scenario:terminate(Scenario, ScenarioState).

-spec maybe_start_timer(timer:tref() | undefined) -> timer:tref().
maybe_start_timer(undefined) ->
Expand Down
49 changes: 34 additions & 15 deletions src/amoc_scenario.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,31 @@
%%-------------------------------------------------------------------------

%% @doc Applies the `Scenario:init/0' callback
%%
%% Runs on the controller process and spans a `[amoc, scenario, init, _]' telemetry event.
-spec init(amoc:scenario()) -> {ok, state()} | {error, Reason :: term()}.
init(Scenario) ->
apply_safely(Scenario, init, []).
apply_safely(Scenario, init, [], #{scenario => Scenario}).

%% @doc Applies the `Scenario:terminate/0,1' callback
%%
%% `Scenario:terminate/0' and `Scenario:terminate/1' callbacks are optional.
%% If the scenario module exports both functions, `Scenario:terminate/1' is used.
%%
%% Runs on the controller process and spans a `[amoc, scenario, terminate, _]' telemetry event.
-spec terminate(amoc:scenario(), state()) -> ok | {ok, any()} | {error, Reason :: term()}.
terminate(Scenario, State) ->
Metadata = #{scenario => Scenario, state => State},
case {erlang:function_exported(Scenario, terminate, 1),
erlang:function_exported(Scenario, terminate, 0)} of
{true, _} ->
%% since we ignore Scenario:terminate/1 return value
%% we can use apply_safely/3 function
apply_safely(Scenario, terminate, [State]);
apply_safely(Scenario, terminate, [State], Metadata);
{_, true} ->
%% since we ignore Scenario:terminate/0 return value
%% we can use apply_safely/3 function
apply_safely(Scenario, terminate, []);
apply_safely(Scenario, terminate, [], Metadata);
_ ->
ok
end.
Expand All @@ -56,25 +61,39 @@ terminate(Scenario, State) ->
%%
%% Either `Scenario:start/1' or `Scenario:start/2' must be exported from the behaviour module.
%% if scenario module exports both functions, `Scenario:start/2' is used.
%%
%% Runs on the user process and spans a `[amoc, scenario, user, _]' telemetry event.
-spec start(amoc:scenario(), user_id(), state()) -> any().
start(Scenario, Id, State) ->
case {erlang:function_exported(Scenario, start, 2),
erlang:function_exported(Scenario, start, 1)} of
{true, _} ->
Scenario:start(Id, State);
{_, true} ->
Scenario:start(Id);
{false, false} ->
error("the scenario module must export either start/2 or start/1 function")
end.
Metadata = #{scenario => Scenario, state => State, user_id => Id},
Span = case {erlang:function_exported(Scenario, start, 2),
erlang:function_exported(Scenario, start, 1)} of
{true, _} ->
fun() ->
Ret = Scenario:start(Id, State),
{Ret, Metadata#{return => Ret}}
end;
{_, true} ->
fun() ->
Ret = Scenario:start(Id),
{Ret, Metadata#{return => Ret}}
end;
{false, false} ->
exit("the scenario module must export either start/2 or start/1 function")
end,
telemetry:span([amoc, scenario, start], Metadata, Span).

%% ------------------------------------------------------------------
%% internal functions
%% ------------------------------------------------------------------

-spec apply_safely(atom(), atom(), [term()]) -> {ok | error, term()}.
apply_safely(M, F, A) ->
try erlang:apply(M, F, A) of
-spec apply_safely(atom(), atom(), [term()], map()) -> {ok | error, term()}.
apply_safely(M, F, A, Metadata) ->
Span = fun() ->
Ret = erlang:apply(M, F, A),
{Ret, Metadata#{return => Ret}}
end,
try telemetry:span([amoc, scenario, F], Metadata, Span) of
{ok, RetVal} -> {ok, RetVal};
{error, Error} -> {error, Error};
Result -> {ok, Result}
Expand Down
4 changes: 1 addition & 3 deletions src/amoc_user.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,4 @@ stop(Pid, Force) when is_pid(Pid) ->
init(Parent, Scenario, Id, State) ->
proc_lib:init_ack(Parent, {ok, self()}),
process_flag(trap_exit, true),
Metadata = #{scenario => Scenario, user_id => Id},
ScenarioFun = fun() -> {amoc_scenario:start(Scenario, Id, State), Metadata} end,
telemetry:span([amoc, scenario, user], Metadata, ScenarioFun).
amoc_scenario:start(Scenario, Id, State).

0 comments on commit b834956

Please sign in to comment.