Skip to content

Commit 21c2f13

Browse files
committed
Implement persistent term storage for Telemetry handlers
This adds new `telemetry:lock/0` function that allows locking telemetry handlers to improve execution performance at cost of slowing down attaching and detaching handlerrs.
1 parent ec2ae35 commit 21c2f13

File tree

5 files changed

+183
-53
lines changed

5 files changed

+183
-53
lines changed

src/telemetry.erl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
-export([attach/4,
44
attach_many/4,
5+
lock/0,
56
detach/1,
67
list_handlers/1,
78
execute/2,
@@ -142,6 +143,17 @@ attach_many(HandlerId, EventNames, Function, Config) when is_function(Function,
142143
end,
143144
telemetry_handler_table:insert(HandlerId, EventNames, Function, Config).
144145

146+
?DOC("""
147+
Lock telemetry handlers.
148+
149+
This will improve performance of calling Telemetry handlers at the cost of
150+
reducing performance of attaching or detaching new handlers.
151+
152+
This function should be used with care.
153+
""").
154+
lock() ->
155+
telemetry_handler_table:lock().
156+
145157
?DOC("""
146158
Removes the existing handler.
147159

src/telemetry_ets.erl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
-module(telemetry_ets).
2+
3+
-include("telemetry.hrl").
4+
5+
-export([lock/1,
6+
list_for_event/2,
7+
list_by_prefix/2,
8+
insert/5,
9+
delete/2]).
10+
11+
lock(TID) ->
12+
Handlers = ets:tab2list(TID),
13+
Map = maps:groups_from_list(fun (Handler) -> Handler#handler.event_name end,
14+
Handlers),
15+
{ok, Map}.
16+
17+
list_for_event(TID, EventName) ->
18+
try
19+
ets:lookup(TID, EventName)
20+
catch
21+
error:badarg ->
22+
[]
23+
end.
24+
25+
list_by_prefix(TID, EventPrefix) ->
26+
Pattern = match_pattern_for_prefix(EventPrefix),
27+
try
28+
ets:match_object(TID, Pattern)
29+
catch
30+
error:badarg ->
31+
[]
32+
end.
33+
34+
match_pattern_for_prefix(EventPrefix) ->
35+
#handler{event_name=match_for_prefix(EventPrefix),
36+
_='_'}.
37+
38+
-dialyzer({nowarn_function, match_for_prefix/1}).
39+
match_for_prefix([]) ->
40+
'_';
41+
match_for_prefix([Segment | Rest]) ->
42+
[Segment | match_for_prefix(Rest)].
43+
44+
insert(TID, HandlerId, EventNames, Function, Config) ->
45+
case ets:match(TID, #handler{id=HandlerId,
46+
_='_'}) of
47+
[] ->
48+
Objects = [#handler{id=HandlerId,
49+
event_name=EventName,
50+
function=Function,
51+
config=Config} || EventName <- EventNames],
52+
ets:insert(TID, Objects),
53+
{ok, TID};
54+
_ ->
55+
{error, already_exists}
56+
end.
57+
58+
delete(TID, HandlerId) ->
59+
case ets:select_delete(TID, [{#handler{id=HandlerId,
60+
_='_'}, [], [true]}]) of
61+
0 -> {error, not_found};
62+
_ -> {ok, TID}
63+
end.

src/telemetry_handler_table.erl

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
-behaviour(gen_server).
1414

1515
-export([start_link/0,
16+
lock/0,
1617
insert/4,
1718
delete/1,
1819
list_for_event/1,
@@ -25,6 +26,8 @@
2526
code_change/3,
2627
terminate/2]).
2728

29+
-compile({inline, [impl_get/0]}).
30+
2831
-include("telemetry.hrl").
2932

3033
start_link() ->
@@ -42,21 +45,23 @@ insert(HandlerId, EventNames, Function, Config) ->
4245
delete(HandlerId) ->
4346
gen_server:call(?MODULE, {delete, HandlerId}).
4447

48+
lock() ->
49+
{Mod, State} = impl_get(),
50+
case Mod:lock(State) of
51+
{ok, NewState} ->
52+
persistent_term:put(telemetry, {telemetry_pt, NewState}),
53+
ok;
54+
_ ->
55+
ok
56+
end.
57+
4558
impl_get() -> persistent_term:get(telemetry).
4659

4760
-spec list_for_event(telemetry:event_name()) -> [#handler{}].
4861
list_for_event(EventName) ->
4962
case impl_get() of
50-
{ets, TID} ->
51-
try
52-
ets:lookup(TID, EventName)
53-
catch
54-
error:badarg ->
55-
persistent_term:erase(telemetry),
56-
?LOG_WARNING("Failed to lookup telemetry handlers. "
57-
"Ensure the telemetry application has been started. ", []),
58-
[]
59-
end;
63+
{Mod, State} ->
64+
Mod:list_for_event(State, EventName);
6065
_ ->
6166
?LOG_WARNING("Failed to lookup telemetry handlers. "
6267
"Ensure the telemetry application has been started. ", []),
@@ -66,9 +71,8 @@ list_for_event(EventName) ->
6671
-spec list_by_prefix(telemetry:event_prefix()) -> [#handler{}].
6772
list_by_prefix(EventPrefix) ->
6873
case impl_get() of
69-
{ets, TID} ->
70-
Pattern = match_pattern_for_prefix(EventPrefix),
71-
ets:match_object(TID, Pattern);
74+
{Mod, State} ->
75+
Mod:list_by_prefix(State, EventPrefix);
7276
_ ->
7377
?LOG_WARNING("Failed to lookup telemetry handlers. "
7478
"Ensure the telemetry application has been started. ", []),
@@ -78,36 +82,27 @@ list_by_prefix(EventPrefix) ->
7882
init([]) ->
7983
TID = create_table(),
8084

81-
persistent_term:put(telemetry, {ets, TID}),
85+
persistent_term:put(telemetry, {telemetry_ets, TID}),
8286

8387
{ok, []}.
8488

8589
handle_call({insert, HandlerId, EventNames, Function, Config}, _From, State) ->
86-
case impl_get() of
87-
{ets, TID} ->
88-
case ets:match(TID, #handler{id=HandlerId,
89-
_='_'}) of
90-
[] ->
91-
Objects = [#handler{id=HandlerId,
92-
event_name=EventName,
93-
function=Function,
94-
config=Config} || EventName <- EventNames],
95-
ets:insert(TID, Objects),
96-
{reply, ok, State};
97-
_ ->
98-
{reply, {error, already_exists}, State}
99-
end
90+
{Mod, MState} = impl_get(),
91+
case Mod:insert(MState, HandlerId, EventNames, Function, Config) of
92+
{ok, NewState} ->
93+
persistent_term:put(telemetry, {Mod, NewState}),
94+
{reply, ok, State};
95+
{error, _} = Error ->
96+
{reply, Error, State}
10097
end;
10198
handle_call({delete, HandlerId}, _From, State) ->
102-
case impl_get() of
103-
{ets, TID} ->
104-
case ets:select_delete(TID, [{#handler{id=HandlerId,
105-
_='_'}, [], [true]}]) of
106-
0 ->
107-
{reply, {error, not_found}, State};
108-
_ ->
109-
{reply, ok, State}
110-
end
99+
{Mod, MState} = impl_get(),
100+
case Mod:delete(MState, HandlerId) of
101+
{ok, NewState} ->
102+
persistent_term:put(telemetry, {Mod, NewState}),
103+
{reply, ok, State};
104+
{error, _} = Error ->
105+
{reply, Error, State}
111106
end.
112107

113108
handle_cast(_Msg, State) ->
@@ -126,15 +121,5 @@ terminate(_Reason, _State) ->
126121
%%
127122

128123
create_table() ->
129-
ets:new(?MODULE, [duplicate_bag, protected, named_table,
124+
ets:new(?MODULE, [duplicate_bag, protected,
130125
{keypos, #handler.event_name}, {read_concurrency, true}]).
131-
132-
match_pattern_for_prefix(EventPrefix) ->
133-
#handler{event_name=match_for_prefix(EventPrefix),
134-
_='_'}.
135-
136-
-dialyzer({nowarn_function, match_for_prefix/1}).
137-
match_for_prefix([]) ->
138-
'_';
139-
match_for_prefix([Segment | Rest]) ->
140-
[Segment | match_for_prefix(Rest)].

src/telemetry_pt.erl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
-module(telemetry_pt).
2+
3+
-include("telemetry.hrl").
4+
5+
-export([lock/1,
6+
list_for_event/2,
7+
list_by_prefix/2,
8+
insert/5,
9+
delete/2]).
10+
11+
lock(_Map) -> error.
12+
13+
list_for_event(Map, EventName) ->
14+
case Map of
15+
#{EventName := Handlers} -> Handlers;
16+
_ -> []
17+
end.
18+
19+
list_by_prefix(Map, EventPrefix) ->
20+
[Handler ||
21+
{EventName, Handlers} <- maps:to_list(Map),
22+
starts_with(EventName, EventPrefix),
23+
Handler <- Handlers].
24+
25+
starts_with(_Haystack, []) -> true;
26+
starts_with([A | Haystack], [A | Needle]) -> starts_with(Haystack, Needle);
27+
starts_with(_Haystack, _Needle) -> false.
28+
29+
insert(Map, _HandlerId, [], _Function, _Config) ->
30+
{ok, Map};
31+
insert(Map, HandlerId, [EventName | Rest], Function, Config) ->
32+
Handler = #handler{id=HandlerId,
33+
event_name=EventName,
34+
function=Function,
35+
config=Config},
36+
OldHandlers = maps:get(EventName, Map, []),
37+
case OldHandlers of
38+
#{HandlerId := _} -> {error, already_exists};
39+
_ ->
40+
case put_new(Handler, OldHandlers) of
41+
{ok, NewHandlers} ->
42+
NewMap = Map#{EventName => NewHandlers},
43+
insert(NewMap, HandlerId, Rest, Function, Config);
44+
{error, _} = Error ->
45+
Error
46+
end
47+
end.
48+
49+
put_new(Handler, List) ->
50+
case lists:keymember(Handler#handler.id, #handler.id, List) of
51+
true -> {error, already_exists};
52+
false -> {ok, [Handler | List]}
53+
end.
54+
55+
delete(Map, HandlerId) ->
56+
Filtered = [{Event, lists:keydelete(HandlerId, #handler.id, Handlers)}
57+
|| {Event, Handlers} <- maps:to_list(Map)],
58+
NewMap = maps:from_list(Filtered),
59+
case NewMap =:= Map of
60+
true -> {error, not_found};
61+
false -> {ok, NewMap}
62+
end.

test/telemetry_SUITE.erl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,28 @@
77

88
-include("telemetry.hrl").
99

10-
all() ->
11-
[bad_event_names, duplicate_attach, invoke_handler,
10+
all() -> [{group, unlocked}, {group, locked}].
11+
12+
groups() ->
13+
Tests = [bad_event_names, duplicate_attach, invoke_handler,
1214
list_handlers, list_for_prefix, detach_on_exception,
1315
no_execute_detached, no_execute_on_prefix, no_execute_on_specific,
1416
handler_on_multiple_events, remove_all_handler_on_failure,
1517
list_handler_on_many, detach_from_all, old_execute, default_metadata,
1618
off_execute, invoke_successful_span_handlers, invoke_exception_span_handlers,
17-
spans_generate_unique_default_contexts, logs_on_local_function].
19+
spans_generate_unique_default_contexts, logs_on_local_function],
20+
21+
[{unlocked, [], Tests}, {locked, [], Tests}].
1822

19-
init_per_suite(Config) ->
23+
init_per_group(Name, Config) ->
2024
application:ensure_all_started(telemetry),
25+
case Name of
26+
locked -> ok = telemetry:lock();
27+
_ -> ok
28+
end,
2129
Config.
2230

23-
end_per_suite(_Config) ->
31+
end_per_group(_, _Config) ->
2432
application:stop(telemetry).
2533

2634
init_per_testcase(_, Config) ->

0 commit comments

Comments
 (0)