Current section
Files
Jump to
Current section
Files
src/prometheus_http_impl.erl
-module(prometheus_http_impl).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC("Internal module for `prometheus_httpd`.").
-export([reply/1, setup/0]).
-define(SCRAPE_DURATION, telemetry_scrape_duration_seconds).
-define(SCRAPE_SIZE, telemetry_scrape_size_bytes).
-define(SCRAPE_ENCODED_SIZE, telemetry_scrape_encoded_size_bytes).
?DOC("Renders metrics.").
-spec reply(#{
path => string(),
headers => fun((string(), string()) -> string()),
registry => prometheus_registry:registry(),
standalone => boolean()
}) -> {integer(), list(), iodata()} | false.
reply(#{
path := Path,
headers := Headers,
registry := Registry,
standalone := Standalone
}) ->
case prometheus_http_config:valid_path_and_registry(Path, Registry) of
{true, RealRegistry} ->
if_authorized(
Path,
Headers,
fun() ->
format_metrics(Headers, RealRegistry)
end
);
{registry_conflict, _ReqR, _ConfR} ->
{409, [], <<>>};
{registry_not_found, _ReqR} ->
{404, [], <<>>};
false ->
maybe_render_index(Standalone, Path, Headers)
end.
?DOC("""
Initializes telemetry metrics.
*NOTE:* If you plug `prometheus_httpd` in your existing httpd instance,
you have to call this function manually.
""").
-spec setup() -> any().
setup() ->
TelemetryRegistry = prometheus_http_config:telemetry_registry(),
ScrapeDuration = [
{name, ?SCRAPE_DURATION},
{help, "Scrape duration"},
{labels, ["registry", "content_type"]},
{registry, TelemetryRegistry}
],
ScrapeSize = [
{name, ?SCRAPE_SIZE},
{help, "Scrape size, not encoded"},
{labels, ["registry", "content_type"]},
{registry, TelemetryRegistry}
],
ScrapeEncodedSize = [
{name, ?SCRAPE_ENCODED_SIZE},
{help, "Scrape size, encoded"},
{labels, ["registry", "content_type", "encoding"]},
{registry, TelemetryRegistry}
],
prometheus_summary:declare(ScrapeDuration),
prometheus_summary:declare(ScrapeSize),
prometheus_summary:declare(ScrapeEncodedSize).
%% ===================================================================
%% Private Parts
%% ===================================================================
format_metrics(Headers, Registry) ->
Accept = Headers("accept", "text/plain"),
AcceptEncoding = Headers("accept-encoding", undefined),
format_metrics(Accept, AcceptEncoding, Registry).
maybe_render_index(Standalone, Path, Headers) ->
case Standalone of
true ->
case Path of
"/favicon.ico" ->
%% https://www.iconfinder.com/icons/85652/fire_torch_icon#size=30
%% http://www.iconbeast.com/
{200, [{"content-type", "image/png"}],
base64:decode(
"iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAABaklEQ"
"VRIS+2UsU0EQQxF3+UElAASBUAHUAACKgACcqjg6AByAqACOFEAJU"
"ABBHRAQg760liaG+2u7QF0CROd9sb/+dsez+g7G8AzcAE89kjMeoK"
"AbeClxF6XBFJSveAH4LAipXXSAcAVcN7YS+tkA26Bk4GaSmcXOIiW"
"PQMegyoP6Vj5d4BXr+FR8CUwnxC7qyqh36e/Bf4A1j2xzLBFHX8NQ"
"N/LN73p9ri67oWi2IIFVS/VVw3Vn4G1pWqAemh91dDVR0ltem2JOl"
"Y5tamsz3VcO+1HkTUaBcuA1qScC17HqRL6rmOV8AwvCbiXC1QOF6X"
"UitFCOS6Lw32/Bsk4jiQWvhMFWymjwnvexSjY00n/nwFHXbtulWUG"
"/ASsOdY+gf2I/QzYpndK976a9kl+BiyhG2BrRPENOIu4zZbaNIech"
"53+9B23gxYaqLoa2VJb7MrASsDgabe9PW5d/4NDL6p3uFba45CzsU"
"vfrgozHx5iraMAAAAASUVORK5CYII="
)};
_ ->
MetricsPath = prometheus_http_config:path(),
if_authorized(
Path,
Headers,
fun() ->
{200, [], prepare_index(MetricsPath)}
end
)
end;
false ->
false
end.
if_authorized(URI, Headers, Fun) ->
case prometheus_http_config:authorization() of
{invalid_authorize, _} ->
{500, [], <<>>};
Auth ->
case
Auth(#{
uri => URI,
headers => Headers
})
of
true ->
Fun();
false ->
{403, [], <<>>}
end
end.
prepare_index(Path) ->
FileName = filename:join([code:priv_dir(prometheus_httpd), "index.html"]),
{ok, Content} = file:read_file(FileName),
re:replace(Content, "M_E_T_R_I_C_S", Path, [global, {return, list}]).
format_metrics(Accept, AcceptEncoding, Registry) ->
case negotiate_format(Accept) of
undefined ->
{406, [], <<>>};
Format ->
{ContentType, Scrape} = render_format(Format, Registry),
case negotiate_encoding(AcceptEncoding) of
undefined ->
{406, [], <<>>};
Encoding ->
encode_format(ContentType, binary_to_list(Encoding), Scrape, Registry)
end
end.
negotiate_format(Accept) ->
case prometheus_http_config:format() of
auto ->
Alternatives = prometheus_http_config:allowed_formats(),
accept_header:negotiate(Accept, Alternatives);
Format0 ->
Format0
end.
negotiate_encoding(AcceptEncoding) ->
%% curl and other tools do not send Accept-Encoding field.
%% RFC says it means we can render any encoding.
%% But people want to see something meaningful in console!
accept_encoding_header:negotiate(AcceptEncoding, [
<<"identity">>,
<<"gzip">>,
<<"deflate">>
]).
render_format(Format, Registry) ->
ContentType = Format:content_type(),
TelemetryRegistry = prometheus_http_config:telemetry_registry(),
Scrape = prometheus_summary:observe_duration(
TelemetryRegistry,
?SCRAPE_DURATION,
[Registry, ContentType],
fun() -> Format:format(Registry) end
),
prometheus_summary:observe(
TelemetryRegistry,
?SCRAPE_SIZE,
[Registry, ContentType],
iolist_size(Scrape)
),
{ContentType, Scrape}.
encode_format(ContentType, Encoding, Scrape, Registry) ->
Encoded = encode_format_(Encoding, Scrape),
TelemetryRegistry = prometheus_http_config:telemetry_registry(),
prometheus_summary:observe(
TelemetryRegistry,
?SCRAPE_ENCODED_SIZE,
[Registry, ContentType, Encoding],
iolist_size(Encoded)
),
{200,
[
{content_type, binary_to_list(ContentType)},
{content_encoding, Encoding}
],
Encoded}.
encode_format_("gzip", Scrape) ->
zlib:gzip(Scrape);
encode_format_("deflate", Scrape) ->
ZStream = zlib:open(),
zlib:deflateInit(ZStream),
try
zlib:deflate(ZStream, Scrape, finish)
after
zlib:deflateEnd(ZStream)
end;
encode_format_("identity", Scrape) ->
Scrape.