Skip to content

Commit 3067d4a

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 3067d4a

5 files changed

Lines changed: 216 additions & 53 deletions

File tree

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+
persist/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+
Persist 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+
persist() ->
155+
telemetry_handler_table:persist().
156+
145157
?DOC("""
146158
Removes the existing handler.
147159

src/telemetry_ets.erl

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

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

10-
all() ->
11-
[bad_event_names, duplicate_attach, invoke_handler,
10+
all() -> [persist_with_existing_handlers, {group, ets}, {group, persisted}].
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+
[{ets, [], Tests}, {persisted, [], Tests}].
1822

19-
init_per_suite(Config) ->
23+
init_per_group(Name, Config) ->
2024
application:ensure_all_started(telemetry),
25+
case Name of
26+
persisted -> ok = telemetry:persist();
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) ->
2735
HandlerId = crypto:strong_rand_bytes(16),
2836
[{id, HandlerId} | Config].
2937

38+
end_per_testcase(persist_with_existing_handlers, _Config) ->
39+
ok;
3040
end_per_testcase(_, Config) ->
3141
HandlerId = ?config(id, Config),
3242
telemetry:detach(HandlerId).
3343

44+
persist_with_existing_handlers(Config) ->
45+
application:ensure_all_started(telemetry),
46+
HandlerId = ?config(id, Config),
47+
Event = [a, test, event],
48+
HandlerConfig = #{send_to => self()},
49+
Measurements = #{data => 3},
50+
Metadata = #{some => metadata},
51+
telemetry:attach(HandlerId, Event, fun ?MODULE:echo_event/4, HandlerConfig),
52+
53+
telemetry:persist(),
54+
55+
telemetry:execute(Event, Measurements, Metadata),
56+
57+
try
58+
receive
59+
{event, Event, Measurements, Metadata, HandlerConfig} ->
60+
ok
61+
after
62+
1000 ->
63+
ct:fail(timeout_receive_echo)
64+
end
65+
after
66+
application:stop(telemetry)
67+
end.
68+
3469
bad_event_names(Config) ->
3570
HandlerId = ?config(id, Config),
3671
?assertError(badarg, telemetry:attach(HandlerId, ["some", event], fun ?MODULE:echo_event/4, [])),

0 commit comments

Comments
 (0)