Skip to content

Commit

Permalink
Merge pull request #4227 from esl/instrument/unit-test
Browse files Browse the repository at this point in the history
Instrument/unit test
  • Loading branch information
JanuszJakubiec authored Feb 22, 2024
2 parents eb17e73 + 6cedfb1 commit 257a371
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 7 deletions.
20 changes: 17 additions & 3 deletions src/metrics/mongoose_instrument.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ set_up(EventName, Labels, Config) ->
AllModules = handler_modules(),
UsedModules = lists:filter(fun(Mod) -> Mod:set_up(EventName, Labels, Config) end, AllModules),
HandlerFuns = [fun Mod:handle_event/4 || Mod <- UsedModules],
mongoose_instrument_registry:attach(EventName, Labels, {HandlerFuns, Config}).
case mongoose_instrument_registry:attach(EventName, Labels, {HandlerFuns, Config}) of
ok ->
ok;
{error, already_attached} ->
error(#{what => event_already_registered, event_name => EventName, labels => Labels})
end.

-spec tear_down(event_name(), labels()) -> ok.
tear_down(EventName, Labels) ->
Expand All @@ -48,16 +53,25 @@ span(Event, Labels, F, MeasureF) ->

-spec span(event_name(), labels(), fun((...) -> Result), list(), measure_fun(Result)) -> Result.
span(Event, Labels, F, Args, MeasureF) ->
{ok, Handlers} = mongoose_instrument_registry:lookup(Event, Labels),
Handlers = get_handlers(Event, Labels),
{Time, Result} = timer:tc(F, Args),
handle_event(Event, Labels, MeasureF(Time, Result), Handlers),
Result.

-spec execute(event_name(), labels(), measurements()) -> ok.
execute(Event, Labels, Measurements) ->
{ok, Handlers} = mongoose_instrument_registry:lookup(Event, Labels),
Handlers = get_handlers(Event, Labels),
handle_event(Event, Labels, Measurements, Handlers).

-spec get_handlers(event_name(), labels()) -> handlers().
get_handlers(EventName, Labels) ->
case mongoose_instrument_registry:lookup(EventName, Labels) of
{ok, Handlers} ->
Handlers;
{error, not_found} ->
error(#{what => event_not_registered, event_name => EventName, labels => Labels})
end.

-spec handle_event(event_name(), labels(), measurements(), handlers()) -> ok.
handle_event(Event, Labels, Measurements, {EventHandlers, Config}) ->
lists:foreach(fun(Handler) -> Handler(Event, Labels, Config, Measurements) end, EventHandlers).
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/mongoose_instrument_exometer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
-export([set_up/3, handle_event/4]).

-spec set_up(mongoose_instrument:event_name(), mongoose_instrument:labels(),
mongoose_instrument:config()) -> boolean().
mongoose_instrument:config()) -> boolean().
set_up(EventName, Labels, #{metrics := Metrics}) ->
maps:foreach(fun(MetricName, MetricType) ->
set_up_metric(EventName, Labels, MetricName, MetricType)
Expand Down
8 changes: 5 additions & 3 deletions src/metrics/mongoose_instrument_registry.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ start() ->
ok.

-spec attach(mongoose_instrument:event_name(), mongoose_instrument:labels(),
mongoose_instrument:handlers()) -> ok.
mongoose_instrument:handlers()) -> ok | {error, already_attached}.
attach(Event, Labels, Val) ->
ets:insert_new(?MODULE, {{Event, Labels}, Val}),
ok.
case ets:insert_new(?MODULE, {{Event, Labels}, Val}) of
true -> ok;
false -> {error, already_attached}
end.

-spec detach(mongoose_instrument:event_name(), mongoose_instrument:labels()) -> ok.
detach(Event, Labels) ->
Expand Down
167 changes: 167 additions & 0 deletions test/mongoose_instrument_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
-module(mongoose_instrument_SUITE).
-compile([export_all, nowarn_export_all]).

-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").

-define(HANDLER, mongoose_instrument_test_handler).
-define(INACTIVE_HANDLER, mongoose_instrument_inactive_handler).
-define(LABELS, #{key => value}).
-define(CFG, #{metrics => #{time => histogram}}).
-define(MEASUREMENTS, #{count => 1}).

%% Setup and teardown

all() ->
[{group, api}].

groups() ->
[{api, [parallel], [set_up_and_execute,
set_up_multiple_and_execute_one,
set_up_crashes_when_repeated,
set_up_and_tear_down,
set_up_and_tear_down_multiple,
execute_crashes_when_not_set_up,
set_up_and_span,
set_up_and_span_with_arg,
set_up_and_span_with_error,
span_crashes_when_not_set_up]}
].

init_per_suite(Config) ->
mongoose_config:set_opts(opts()),
mock_handler(?HANDLER, true),
mock_handler(?INACTIVE_HANDLER, false),
async_helper:start(Config, mongoose_instrument_registry, start, []).

mock_handler(Module, SetUpResult) ->
meck:new(Module, [non_strict, no_link]),
meck:expect(Module, set_up, fun(_Event, #{}, #{}) -> SetUpResult end),
meck:expect(Module, handle_event, fun(_Event, #{}, #{}, #{}) -> ok end).

end_per_suite(Config) ->
async_helper:stop_all(Config),
meck:unload(?HANDLER),
meck:unload(?INACTIVE_HANDLER),
mongoose_config:erase_opts().

init_per_testcase(Case, Config) ->
[{event, event_name(Case)} | Config].

end_per_testcase(_Case, _Config) ->
ok.

opts() ->
#{instrumentation => #{test_handler => #{}, inactive_handler => #{}}}.

event_name(Case) ->
list_to_atom(atom_to_list(Case) ++ "_event").

%% Test cases

set_up_and_execute(Config) ->
Event = ?config(event, Config),
ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
?assertEqual([{Event, ?LABELS, ?CFG, true}], history(?HANDLER, set_up)),
?assertEqual([{Event, ?LABELS, ?CFG, false}], history(?INACTIVE_HANDLER, set_up)),
ok = mongoose_instrument:execute(Event, ?LABELS, ?MEASUREMENTS),
?assertEqual([{Event, ?LABELS, ?CFG, ?MEASUREMENTS}], history(?HANDLER, handle_event)),
?assertEqual([], history(?INACTIVE_HANDLER, handle_event)).

set_up_multiple_and_execute_one(Config) ->
Event = ?config(event, Config),
Specs = [{Event, Labels1 = #{key1 => value1}, ?CFG},
{Event, Labels2 = #{key2 => value2}, ?CFG}],
ok = mongoose_instrument:set_up(Specs),
?assertEqual([{Event, Labels1, ?CFG, true},
{Event, Labels2, ?CFG, true}], history(?HANDLER, set_up)),
ok = mongoose_instrument:execute(Event, Labels1, ?MEASUREMENTS),
?assertEqual([{Event, Labels1, ?CFG, ?MEASUREMENTS}], history(?HANDLER, handle_event)).

set_up_crashes_when_repeated(Config) ->
Event = ?config(event, Config),
ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
?assertError(#{what := event_already_registered},
mongoose_instrument:set_up(Event, ?LABELS, ?CFG)).

set_up_and_tear_down(Config) ->
Event = ?config(event, Config),
ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
ok = mongoose_instrument:tear_down(Event, ?LABELS),
?assertError(#{what := event_not_registered},
mongoose_instrument:execute(Event, ?LABELS, ?MEASUREMENTS)),
[] = history(?HANDLER, handle_event),
ok = mongoose_instrument:tear_down(Event, ?LABELS). % idempotent

set_up_and_tear_down_multiple(Config) ->
Event = ?config(event, Config),
Specs = [{Event, Labels1 = #{key1 => value1}, ?CFG},
{Event, Labels2 = #{key2 => value2}, ?CFG}],
ok = mongoose_instrument:set_up(Specs),
ok = mongoose_instrument:tear_down(Specs),
?assertError(#{what := event_not_registered},
mongoose_instrument:execute(Event, Labels1, ?MEASUREMENTS)),
?assertError(#{what := event_not_registered},
mongoose_instrument:execute(Event, Labels2, ?MEASUREMENTS)),
[] = history(?HANDLER, handle_event).

execute_crashes_when_not_set_up(Config) ->
Event = ?config(event, Config),
?assertError(#{what := event_not_registered},
mongoose_instrument:execute(Event, ?LABELS, ?MEASUREMENTS)),
[] = history(?HANDLER, handle_event).

set_up_and_span(Config) ->
{Event, Labels, InstrConfig} = {?config(event, Config), ?LABELS, ?CFG},
ok = mongoose_instrument:set_up(Event, Labels, InstrConfig),
ok = mongoose_instrument:span(Event, Labels, fun test_op/0, fun measure_test_op/2),
[{Event, Labels, InstrConfig, #{time := Time, result := ok}}] = history(?HANDLER, handle_event),
?assert(Time > 1000).

set_up_and_span_with_arg(Config) ->
{Event, Labels, InstrConfig} = {?config(event, Config), ?LABELS, ?CFG},
ok = mongoose_instrument:set_up(Event, Labels, InstrConfig),
ok = mongoose_instrument:span(Event, Labels, fun test_op/1, [2], fun measure_test_op/2),
[{Event, Labels, InstrConfig, #{time := Time, result := ok}}] = history(?HANDLER, handle_event),
?assert(Time > 2000).

set_up_and_span_with_error(Config) ->
Event = ?config(event, Config),
ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
?assertError(simulated_error,
mongoose_instrument:span(Event, ?LABELS, fun crashing_op/0, fun measure_test_op/2)),
[] = history(?HANDLER, handle_event).

span_crashes_when_not_set_up(Config) ->
Event = ?config(event, Config),
Labels = #{key => value},
?assertError(#{what := event_not_registered},
mongoose_instrument:span(Event, Labels, fun test_op/0, fun measure_test_op/2)),
%% Also checks that the function is not executed - otherwise 'simulated_error' would be raised
?assertError(#{what := event_not_registered},
mongoose_instrument:span(Event, Labels, fun crashing_op/0, fun measure_test_op/2)),
[] = history(?HANDLER, handle_event).

%% Helpers

history(HandlerMod, WantedF) ->
[process_history(CalledF, Args, Result) ||
{_Pid, {_M, CalledF, Args}, Result} <- meck:history(HandlerMod, self()),
WantedF =:= CalledF].

process_history(set_up, [Event, Labels, InstrConfig], Result) ->
{Event, Labels, InstrConfig, Result};
process_history(handle_event, [Event, Labels, InstrConfig, Measurements], ok) ->
{Event, Labels, InstrConfig, Measurements}.

test_op(Delay) ->
timer:sleep(Delay).

test_op() ->
test_op(1).

crashing_op() ->
error(simulated_error).

measure_test_op(Time, Result) ->
#{time => Time, result => Result}.
Loading

0 comments on commit 257a371

Please sign in to comment.