./ct_report/coverage/mongoose_instrument.COVER.html

1 -module(mongoose_instrument).
2
3 -behaviour(gen_server).
4
5 %% API
6 -export([start_link/0, persist/0,
7 set_up/1, set_up/3,
8 tear_down/1, tear_down/2,
9 span/4, span/5,
10 execute/3]).
11
12 %% gen_server callbacks
13 -export([init/1, handle_call/3, handle_cast/2, code_change/3, handle_info/2, terminate/2]).
14
15 -ignore_xref([start_link/0, set_up/3, tear_down/2, span/4, span/5, execute/3]).
16
17 -include("mongoose.hrl").
18
19 -type event_name() :: atom().
20 -type labels() :: #{host_type => mongooseim:host_type()}. % to be extended
21 -type metrics() :: #{atom() => spiral | histogram}. % to be extended
22 -type measurements() :: #{atom() => integer() | atom() | binary()}.
23 -type spec() :: {event_name(), labels(), config()}.
24 -type config() :: #{metrics => metrics()}. % to be extended
25 -type handler_fun() :: fun((event_name(), labels(), config(), measurements()) -> any()).
26 -type handlers() :: {[handler_fun()], config()}.
27 -type execution_time() :: integer().
28 -type measure_fun(Result) :: fun((execution_time(), Result) -> measurements()).
29
30 -callback set_up(event_name(), labels(), config()) -> boolean().
31 -callback handle_event(event_name(), labels(), config(), measurements()) -> any().
32
33 -export_type([event_name/0, labels/0, config/0, measurements/0, spec/0, handlers/0]).
34
35 %% API
36
37 -spec start_link() -> gen_server:start_ret().
38 start_link() ->
39 53 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
40
41 %% @doc Saves the state to a persistent term, improving performance of `execute' and `span'.
42 %% On the other hand, future calls to `set_up' or `tear_down' will update the persistent term,
43 %% which makes them less performant.
44 %% You should call this function only once - after the initial setup, but before handling any load.
45 -spec persist() -> ok.
46 persist() ->
47 53 gen_server:call(?MODULE, persist).
48
49 %% @doc Sets up instrumentation for multiple events.
50 %% @see set_up/3
51 -spec set_up([spec()]) -> ok.
52 set_up(Specs) ->
53
:-(
lists:foreach(fun({EventName, Labels, Config}) -> set_up(EventName, Labels, Config) end, Specs).
54
55 %% @doc Tears down instrumentation for multiple events.
56 %% @see tear_down/2
57 -spec tear_down([spec()]) -> ok.
58 tear_down(Specs) ->
59
:-(
lists:foreach(fun({EventName, Labels, _Config}) -> tear_down(EventName, Labels) end, Specs).
60
61 %% @doc Sets up instrumentation for an event identified by `EventName' and `Labels'
62 %% according to `Config'. Fails if the event is already registered, or if the keys of `Labels'
63 %% are different than for already registered events with `EventName'.
64 -spec set_up(event_name(), labels(), config()) -> ok.
65 set_up(EventName, Labels, Config) ->
66
:-(
case gen_server:call(?MODULE, {set_up, EventName, Labels, Config}) of
67
:-(
ok -> ok;
68
:-(
{error, ErrorMap} -> error(ErrorMap)
69 end.
70
71 %% @doc Tears down instrumentation for an event identified by `EventName' and `Labels'.
72 %% This operation is idempotent.
73 -spec tear_down(event_name(), labels()) -> ok.
74 tear_down(EventName, Labels) ->
75
:-(
gen_server:call(?MODULE, {tear_down, EventName, Labels}).
76
77 %% @doc Calls `F', measuring its result with `MeasureF', and calls attached event handlers.
78 %% @see span/5
79 -spec span(event_name(), labels(), fun(() -> Result), measure_fun(Result)) -> Result.
80 span(EventName, Labels, F, MeasureF) ->
81
:-(
span(EventName, Labels, F, [], MeasureF).
82
83 %% @doc Calls `F' with `Args', measuring its execution time.
84 %% The time and the result are passed to `MeasureF', which returns measurements.
85 %% The measurements are then passed to all handlers attached to
86 %% the event identified by `EventName' and `Labels'.
87 %% Fails without calling `F' if the event is not registered.
88 -spec span(event_name(), labels(), fun((...) -> Result), list(), measure_fun(Result)) -> Result.
89 span(EventName, Labels, F, Args, MeasureF) ->
90
:-(
Handlers = get_handlers(EventName, Labels),
91
:-(
{Time, Result} = timer:tc(F, Args),
92
:-(
handle_event(EventName, Labels, MeasureF(Time, Result), Handlers),
93
:-(
Result.
94
95 %% @doc Executes all handlers attached to the event identified by `EventName' and `Labels',
96 %% passing `Measurements' to them. Fails if the event is not registered.
97 -spec execute(event_name(), labels(), measurements()) -> ok.
98 execute(EventName, Labels, Measurements) ->
99
:-(
Handlers = get_handlers(EventName, Labels),
100
:-(
handle_event(EventName, Labels, Measurements, Handlers).
101
102 %% gen_server callbacks
103
104 -type state() :: #{event_name() => #{labels() => handlers()}}.
105
106 -spec init([]) -> {ok, state()}.
107 init([]) ->
108 53 erlang:process_flag(trap_exit, true), %% Make sure that terminate is called
109 53 {ok, #{}}.
110
111 -spec handle_call(any(), gen_server:from(), state()) ->
112 {reply, ok | {ok, handlers()} | {error, map()}, state()}.
113 handle_call({set_up, EventName, Labels, Config}, _From, State) ->
114
:-(
case set_up_and_register(EventName, Labels, Config, State) of
115 {error, _} = Error ->
116
:-(
{reply, Error, State};
117 NewState = #{} ->
118
:-(
update_if_persisted(State, NewState),
119
:-(
{reply, ok, NewState}
120 end;
121 handle_call({tear_down, EventName, Labels}, _From, State) ->
122
:-(
NewState = deregister(EventName, Labels, State),
123
:-(
update_if_persisted(State, NewState),
124
:-(
{reply, ok, NewState};
125 handle_call(persist, _From, State) ->
126 53 persistent_term:put(?MODULE, State),
127 53 {reply, ok, State};
128 handle_call({lookup, EventName, Labels}, _From, State) ->
129
:-(
{reply, lookup(EventName, Labels, State), State};
130 handle_call(Request, From, State) ->
131
:-(
?UNEXPECTED_CALL(Request, From),
132
:-(
{reply, {error, #{what => unexpected_call, request => Request}}, State}.
133
134 -spec handle_cast(any(), state()) -> {noreply, state()}.
135 handle_cast(Msg, State) ->
136
:-(
?UNEXPECTED_CAST(Msg),
137
:-(
{noreply, State}.
138
139 -spec handle_info(any(), state()) -> {noreply, state()}.
140 handle_info(Info, State) ->
141
:-(
?UNEXPECTED_INFO(Info),
142
:-(
{noreply, State}.
143
144 -spec terminate(any(), state()) -> ok.
145 terminate(_Reason, _State) ->
146 54 persistent_term:erase(?MODULE),
147 54 ok.
148
149 -spec code_change(any(), state(), any()) -> {ok, state()}.
150 code_change(_OldVsn, State, _Extra) ->
151
:-(
{ok, State}.
152
153 %% Internal functions
154
155 -spec update_if_persisted(state(), state()) -> ok.
156 update_if_persisted(State, NewState) ->
157
:-(
try persistent_term:get(?MODULE) of
158
:-(
State -> persistent_term:put(?MODULE, NewState)
159 catch
160
:-(
error:badarg -> ok
161 end.
162
163 -spec set_up_and_register(event_name(), labels(), config(), state()) -> state() | {error, map()}.
164 set_up_and_register(EventName, Labels, Config, State) ->
165
:-(
LabelKeys = label_keys(Labels),
166
:-(
case State of
167 #{EventName := #{Labels := _}} ->
168
:-(
{error, #{what => event_already_registered,
169 event_name => EventName, labels => Labels}};
170 #{EventName := HandlerMap} ->
171
:-(
{ExistingLabels, _, _} = maps:next(maps:iterator(HandlerMap)),
172
:-(
case label_keys(ExistingLabels) of
173 LabelKeys ->
174
:-(
Handlers = do_set_up(EventName, Labels, Config),
175
:-(
State#{EventName := HandlerMap#{Labels => Handlers}};
176 ExistingKeys ->
177
:-(
{error, #{what => inconsistent_labels,
178 event_name => EventName, labels => Labels,
179 existing_label_keys => ExistingKeys}}
180 end;
181 #{} ->
182
:-(
Handlers = do_set_up(EventName, Labels, Config),
183
:-(
State#{EventName => #{Labels => Handlers}}
184 end.
185
186 -spec do_set_up(event_name(), labels(), config()) -> handlers().
187 do_set_up(EventName, Labels, Config) ->
188
:-(
AllModules = handler_modules(),
189
:-(
UsedModules = lists:filter(fun(Mod) -> Mod:set_up(EventName, Labels, Config) end, AllModules),
190
:-(
{[fun Mod:handle_event/4 || Mod <- UsedModules], Config}.
191
192 -spec deregister(event_name(), labels(), state()) -> state().
193 deregister(EventName, Labels, State) ->
194
:-(
case State of
195 #{EventName := HandlerMap} ->
196
:-(
case maps:remove(Labels, HandlerMap) of
197 Empty when Empty =:= #{} ->
198
:-(
maps:remove(EventName, State);
199 NewHandlerMap ->
200
:-(
State#{EventName := NewHandlerMap}
201 end;
202 #{} ->
203
:-(
State
204 end.
205
206 -spec lookup(event_name(), labels()) -> {ok, handlers()} | {error, map()}.
207 lookup(EventName, Labels) ->
208
:-(
try persistent_term:get(?MODULE) of
209 State ->
210
:-(
lookup(EventName, Labels, State)
211 catch
212 %% Although persist/0 should be called before handling traffic,
213 %% some instrumented events might happen before that, and they shouldn't fail.
214 error:badarg ->
215
:-(
?LOG_INFO(#{what => mongoose_instrument_lookup_without_persistent_term,
216
:-(
event_name => EventName, Labels => Labels}),
217
:-(
gen_server:call(?MODULE, {lookup, EventName, Labels})
218 end.
219
220 -spec lookup(event_name(), labels(), state()) -> {ok, handlers()} | {error, map()}.
221 lookup(EventName, Labels, State) ->
222
:-(
case State of
223 #{EventName := #{Labels := Handlers}} ->
224
:-(
{ok, Handlers};
225 #{} ->
226
:-(
{error, #{what => event_not_registered, event_name => EventName, labels => Labels}}
227 end.
228
229 -spec label_keys(labels()) -> [atom()].
230 label_keys(Labels) ->
231
:-(
lists:sort(maps:keys(Labels)).
232
233 -spec get_handlers(event_name(), labels()) -> handlers().
234 get_handlers(EventName, Labels) ->
235
:-(
case lookup(EventName, Labels) of
236
:-(
{ok, Handlers} -> Handlers;
237
:-(
{error, ErrorMap} -> error(ErrorMap)
238 end.
239
240 -spec handle_event(event_name(), labels(), measurements(), handlers()) -> ok.
241 handle_event(Event, Labels, Measurements, {EventHandlers, Config}) ->
242
:-(
lists:foreach(fun(Handler) -> Handler(Event, Labels, Config, Measurements) end, EventHandlers).
243
244 -spec handler_modules() -> [module()].
245 handler_modules() ->
246
:-(
[list_to_existing_atom("mongoose_instrument_" ++ atom_to_list(Key))
247
:-(
|| Key <- maps:keys(mongoose_config:get_opt(instrumentation))].
Line Hits Source