Skip to content

Commit af3826b

Browse files
RoadRunnralbertored
authored andcommitted
separate httpd logic and prometheus reader
This makes is possible to reuse the prometheus reader logic from other handlers. Add cowboy handler as sample.
1 parent 44edc73 commit af3826b

File tree

3 files changed

+196
-81
lines changed

3 files changed

+196
-81
lines changed
Lines changed: 29 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
%%%------------------------------------------------------------------------
2-
%% Copyright 2022, OpenTelemetry Authors
2+
%% Copyright 2024, OpenTelemetry Authors
33
%% Licensed under the Apache License, Version 2.0 (the "License");
44
%% you may not use this file except in compliance with the License.
55
%% You may obtain a copy of the License at
@@ -17,103 +17,51 @@
1717
%%%-------------------------------------------------------------------------
1818
-module(otel_metric_reader_prometheus).
1919

20-
-behaviour(supervisor).
21-
2220
-export([start_link/3,
23-
collect/1,
21+
collect/0, collect/1,
2422
shutdown/1]).
2523

26-
-export([init/1,
27-
do/1]).
24+
-ignore_xref(?MODULE).
2825

2926
-include_lib("kernel/include/logger.hrl").
30-
-include_lib("inets/include/httpd.hrl").
3127
-include_lib("opentelemetry_api_experimental/include/otel_metrics.hrl").
3228

3329
-define(TEMPORALITY_MAPPING, #{
34-
?KIND_COUNTER => ?TEMPORALITY_CUMULATIVE,
35-
?KIND_OBSERVABLE_COUNTER => ?TEMPORALITY_CUMULATIVE,
36-
?KIND_HISTOGRAM => ?TEMPORALITY_CUMULATIVE,
37-
?KIND_OBSERVABLE_GAUGE => ?TEMPORALITY_CUMULATIVE,
38-
?KIND_UPDOWN_COUNTER => ?TEMPORALITY_CUMULATIVE,
39-
?KIND_OBSERVABLE_UPDOWNCOUNTER => ?TEMPORALITY_CUMULATIVE
40-
}).
30+
?KIND_COUNTER => ?TEMPORALITY_CUMULATIVE,
31+
?KIND_OBSERVABLE_COUNTER => ?TEMPORALITY_CUMULATIVE,
32+
?KIND_HISTOGRAM => ?TEMPORALITY_CUMULATIVE,
33+
?KIND_OBSERVABLE_GAUGE => ?TEMPORALITY_CUMULATIVE,
34+
?KIND_UPDOWN_COUNTER => ?TEMPORALITY_CUMULATIVE,
35+
?KIND_OBSERVABLE_UPDOWNCOUNTER => ?TEMPORALITY_CUMULATIVE
36+
}).
37+
38+
-define(SERVER, ?MODULE).
4139

42-
start_link(ReaderId, ProviderSup, Config) ->
43-
supervisor:start_link(?MODULE, [ReaderId, ProviderSup, Config]).
40+
%%%===================================================================
41+
%%% API
42+
%%%===================================================================
4443

45-
init([ReaderId, ProviderSup, Config]) ->
44+
start_link(ReaderId, ProviderSup, Config0) ->
4645
% TODO warning if default_temporality_mapping, export_interval_ms, exporter
4746
% are present in the configuration
48-
Config1 = maps:put(default_temporality_mapping, ?TEMPORALITY_MAPPING, Config),
49-
Config2 = maps:remove(export_interval_ms, Config1),
50-
Config3 = maps:put(exporter, {otel_metric_exporter_prometheus, Config2}, Config2),
51-
52-
SupFlags = #{strategy => one_for_one,
53-
intensity => 5,
54-
period => 10},
47+
Config1 = maps:remove(export_interval_ms, Config0),
48+
Config2 = Config1#{default_temporality_mapping => ?TEMPORALITY_MAPPING},
49+
Config = Config2#{exporter => {otel_metric_exporter_prometheus, Config2}},
5550

56-
ReaderChildSpec = #{
57-
id => ReaderId,
58-
start => {otel_metric_reader, start_link, [ReaderId, ProviderSup, Config3]},
59-
type => worker,
60-
restart => permanent,
61-
shutdown => 1000
62-
},
51+
case Config of
52+
#{server_name := ServerName} ->
53+
gen_server:start_link(ServerName, otel_metric_reader,
54+
[ReaderId, ProviderSup, Config], []);
55+
_ ->
56+
gen_server:start_link(otel_metric_reader,
57+
[ReaderId, ProviderSup, Config], [])
58+
end.
6359

64-
ChildSpecs = case maps:get(endpoint_port, Config, undefined) of
65-
undefined ->
66-
[ReaderChildSpec];
67-
HttpdPort when is_integer(HttpdPort) ->
68-
HttpdOpts = [
69-
{server_name, "OTel Prometheus exporter"},
70-
{server_tokens, {private, "TODO"}},
71-
{server_root, "/tmp"},
72-
{document_root, "/tmp"},
73-
{port, HttpdPort},
74-
{modules, [?MODULE]},
75-
{otel_metric_reader, {self(), ReaderId}},
76-
{pt_key, make_ref()}
77-
],
78-
HttpdChildSpec = #{
79-
id => make_ref(),
80-
start => {inets, start, [httpd, HttpdOpts, stand_alone]},
81-
type => worker,
82-
restart => permanent,
83-
shutdown => 1000
84-
},
85-
[ReaderChildSpec, HttpdChildSpec]
86-
end,
87-
88-
{ok, {SupFlags, ChildSpecs}}.
60+
collect() ->
61+
collect(?SERVER).
8962

9063
collect(ReaderPid) ->
9164
otel_metric_reader:collect(ReaderPid).
9265

9366
shutdown(ReaderPid) ->
9467
otel_metric_reader:shutdown(ReaderPid).
95-
96-
do(#mod{method="GET",request_uri="/metrics",config_db=ConfigDb}) ->
97-
ReaderPid = get_reader_pid(ConfigDb),
98-
Metrics = collect(ReaderPid),
99-
Headers = [
100-
{code, 200},
101-
{content_length, integer_to_list(iolist_size(Metrics))},
102-
{content_type, "text/plain; version=0.0.4"}
103-
],
104-
{proceed, [{response, {response, Headers, Metrics}}]};
105-
do(#mod{}) ->
106-
{proceed, [{response, {404, "Not found"}}]}.
107-
108-
get_reader_pid(ConfigDb) ->
109-
[PTKey] = ets:lookup_element(ConfigDb, pt_key, 2),
110-
case persistent_term:get(PTKey, undefined) of
111-
undefined ->
112-
[{ReaderSupPid, ReaderId}] = ets:lookup_element(ConfigDb, otel_metric_reader, 2),
113-
Children = supervisor:which_children(ReaderSupPid),
114-
{value, {_, ReaderPid, _, _}} = lists:search(fun({Id, _, _, _}) -> Id == ReaderId end, Children),
115-
persistent_term:put(PTKey, ReaderPid),
116-
ReaderPid;
117-
ReaderPid ->
118-
ReaderPid
119-
end.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
%%%------------------------------------------------------------------------
2+
%% Copyright 2022, OpenTelemetry Authors
3+
%% Licensed under the Apache License, Version 2.0 (the "License");
4+
%% you may not use this file except in compliance with the License.
5+
%% You may obtain a copy of the License at
6+
%%
7+
%% http://www.apache.org/licenses/LICENSE-2.0
8+
%%
9+
%% Unless required by applicable law or agreed to in writing, software
10+
%% distributed under the License is distributed on an "AS IS" BASIS,
11+
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
%% See the License for the specific language governing permissions and
13+
%% limitations under the License.
14+
%%
15+
%% @doc TODO
16+
%% @end
17+
%%%-------------------------------------------------------------------------
18+
-module(otel_metric_reader_prometheus_httpd).
19+
20+
-behaviour(supervisor).
21+
22+
-export([start_link/3,
23+
collect/1,
24+
shutdown/1]).
25+
26+
-export([init/1,
27+
do/1]).
28+
29+
-include_lib("kernel/include/logger.hrl").
30+
-include_lib("inets/include/httpd.hrl").
31+
32+
start_link(ReaderId, ProviderSup, Config) ->
33+
supervisor:start_link(?MODULE, [ReaderId, ProviderSup, Config]).
34+
35+
init([ReaderId, ProviderSup, Config]) ->
36+
SupFlags = #{strategy => one_for_one,
37+
intensity => 5,
38+
period => 10},
39+
40+
ReaderChildSpec = #{
41+
id => ReaderId,
42+
start => {otel_metric_reader_prometheus, start_link, [ReaderId, ProviderSup, Config]},
43+
type => worker,
44+
restart => permanent,
45+
shutdown => 1000
46+
},
47+
48+
ChildSpecs = case maps:get(endpoint_port, Config, undefined) of
49+
undefined ->
50+
[ReaderChildSpec];
51+
HttpdPort when is_integer(HttpdPort) ->
52+
HttpdOpts = [
53+
{server_name, "OTel Prometheus exporter"},
54+
{server_tokens, {private, "TODO"}},
55+
{server_root, "/tmp"},
56+
{document_root, "/tmp"},
57+
{port, HttpdPort},
58+
{modules, [?MODULE]},
59+
{otel_metric_reader, {self(), ReaderId}},
60+
{pt_key, make_ref()}
61+
],
62+
HttpdChildSpec = #{
63+
id => make_ref(),
64+
start => {inets, start, [httpd, HttpdOpts, stand_alone]},
65+
type => worker,
66+
restart => permanent,
67+
shutdown => 1000
68+
},
69+
[ReaderChildSpec, HttpdChildSpec]
70+
end,
71+
72+
{ok, {SupFlags, ChildSpecs}}.
73+
74+
collect(ReaderPid) ->
75+
otel_metric_reader:collect(ReaderPid).
76+
77+
shutdown(ReaderPid) ->
78+
otel_metric_reader:shutdown(ReaderPid).
79+
80+
do(#mod{method="GET",request_uri="/metrics",config_db=ConfigDb}) ->
81+
ReaderPid = get_reader_pid(ConfigDb),
82+
Metrics = collect(ReaderPid),
83+
Headers = [
84+
{code, 200},
85+
{content_length, integer_to_list(iolist_size(Metrics))},
86+
{content_type, "text/plain; version=0.0.4"}
87+
],
88+
{proceed, [{response, {response, Headers, Metrics}}]};
89+
do(#mod{}) ->
90+
{proceed, [{response, {404, "Not found"}}]}.
91+
92+
get_reader_pid(ConfigDb) ->
93+
[PTKey] = ets:lookup_element(ConfigDb, pt_key, 2),
94+
case persistent_term:get(PTKey, undefined) of
95+
undefined ->
96+
[{ReaderSupPid, ReaderId}] = ets:lookup_element(ConfigDb, otel_metric_reader, 2),
97+
Children = supervisor:which_children(ReaderSupPid),
98+
{value, {_, ReaderPid, _, _}} = lists:search(fun({Id, _, _, _}) -> Id == ReaderId end, Children),
99+
persistent_term:put(PTKey, ReaderPid),
100+
ReaderPid;
101+
ReaderPid ->
102+
ReaderPid
103+
end.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
%%%------------------------------------------------------------------------
2+
%% Copyright 2024, OpenTelemetry Authors
3+
%% Licensed under the Apache License, Version 2.0 (the "License");
4+
%% you may not use this file except in compliance with the License.
5+
%% You may obtain a copy of the License at
6+
%%
7+
%% http://www.apache.org/licenses/LICENSE-2.0
8+
%%
9+
%% Unless required by applicable law or agreed to in writing, software
10+
%% distributed under the License is distributed on an "AS IS" BASIS,
11+
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
%% See the License for the specific language governing permissions and
13+
%% limitations under the License.
14+
%%
15+
%% @doc
16+
%% configure opentelemetry_experimental with:
17+
%%
18+
%% {opentelemetry_experimental,
19+
%% [{readers,
20+
%% [
21+
%% #{module => erccn_otel_metric_reader_prometheus,
22+
%% config => #{add_scope_info => false,
23+
%% add_total_suffix => true
24+
%% server_name => otel_cowboy_prometheus_reader}
25+
%% }
26+
%% ]}
27+
%% ]}
28+
%%
29+
%% and add something like this to your cowboy routes:
30+
%%
31+
%% {"/metrics/[:registry]", otel_cowboy_prometheus_h, #{server_name => otel_cowboy_prometheus_reader}}
32+
%%
33+
%% @end
34+
%%%-------------------------------------------------------------------------
35+
-module(otel_cowboy_prometheus_h).
36+
37+
-behavior(cowboy_rest).
38+
39+
-export([init/2, content_types_provided/2,
40+
handle_request_text/2,
41+
allowed_methods/2]).
42+
43+
-ignore_xref([handle_request_text/2]).
44+
45+
init(Req, Opts) ->
46+
{cowboy_rest, Req, Opts}.
47+
48+
allowed_methods(Req, State) ->
49+
{[<<"GET">>], Req, State}.
50+
51+
content_types_provided(Req, State) ->
52+
{[{{<<"text">>, <<"plain">>, '*'} , handle_request_text}
53+
], Req, State}.
54+
55+
handle_request_text(Req0, #{server_name := ServerName} = State) ->
56+
Metrics = erccn_otel_metric_reader_prometheus:collect(ServerName),
57+
Body = iolist_to_binary(Metrics),
58+
59+
Req = cowboy_req:reply(200, #{}, Body, Req0),
60+
{stop, Req, State}.
61+
62+
%%%===================================================================
63+
%%% Internal functions
64+
%%%===================================================================

0 commit comments

Comments
 (0)