Packages
prometheus
4.11.0
6.1.3
6.1.2
6.1.1
6.1.0
6.0.3
6.0.2
6.0.1
6.0.0
5.1.1
5.1.0
5.0.0
4.13.0
retired
4.12.0
4.11.0
4.10.0
4.9.1
4.9.0
4.8.2
4.8.1
4.8.0
4.6.0
4.5.0
4.4.1
4.4.0
4.3.0
4.2.2
4.2.0
4.1.0
4.0.1
4.0.0
3.5.1
3.5.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.2
3.3.1
3.3.0
3.2.3
3.2.2
3.2.1
3.1.1
3.1.0
3.0.1
3.0.0
3.0.0-rc1
3.0.0-alpha9
3.0.0-alpha8
3.0.0-alpha7
3.0.0-alpha6
3.0.0-alpha5
3.0.0-alpha4
3.0.0-alpha3
3.0.0-alpha2
3.0.0-alpha10
3.0.0-alpha1
2.2.0
2.1.0
2.0.0
1.7.0
1.6.0
1.5.0
1.0.2
1.0.1
1.0.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Prometheus.io client in Erlang
Current section
Files
Jump to
Current section
Files
src/formats/prometheus_text_format.erl
%% @doc
%%
%% Serializes Prometheus registry using the latest
%% [text format](http://bit.ly/2cxSuJP).
%%
%% Example output:
%% <pre>
%% # TYPE http_request_duration_milliseconds histogram
%% # HELP http_request_duration_milliseconds Http Request execution time
%% http_request_duration_milliseconds_bucket{method="post",le="100"} 0
%% http_request_duration_milliseconds_bucket{method="post",le="300"} 1
%% http_request_duration_milliseconds_bucket{method="post",le="500"} 3
%% http_request_duration_milliseconds_bucket{method="post",le="750"} 4
%% http_request_duration_milliseconds_bucket{method="post",le="1000"} 5
%% http_request_duration_milliseconds_bucket{method="post",le="+Inf"} 6
%% http_request_duration_milliseconds_count{method="post"} 6
%% http_request_duration_milliseconds_sum{method="post"} 4350
%% </pre>
%% @end
-module(prometheus_text_format).
-export([content_type/0,
format/0,
format/1,
render_labels/1,
escape_label_value/1
]).
-ifdef(TEST).
-export([escape_metric_help/1,
emit_mf_prologue/2,
emit_mf_metrics/2
]).
-endif.
-include("prometheus_model.hrl").
-behaviour(prometheus_format).
-compile({inline, [add_brackets/1, render_label_pair/1]}).
%%====================================================================
%% Macros
%%====================================================================
%%====================================================================
%% Format API
%%====================================================================
-spec content_type() -> binary().
%% @doc
%% Returns content type of the latest [text format](http://bit.ly/2cxSuJP).
%% @end
content_type() ->
<<"text/plain; version=0.0.4">>.
%% @equiv format(default)
-spec format() -> binary().
%% @doc
%% Formats `default' registry using the latest text format.
%% @end
format() ->
format(default).
-spec format(Registry :: prometheus_registry:registry()) -> binary().
%% @doc
%% Formats `Registry' using the latest text format.
%% @end
format(Registry) ->
{ok, Fd} = ram_file:open("", [write, read, binary]),
Callback = fun (_, Collector) ->
registry_collect_callback(Fd, Registry, Collector)
end,
prometheus_registry:collect(Registry, Callback),
file:write(Fd, "\n"),
{ok, Size} = ram_file:get_size(Fd),
{ok, Str} = file:pread(Fd, 0, Size),
ok = file:close(Fd),
Str.
-spec escape_label_value(binary() | iolist()) -> binary().
%% @doc
%% Escapes the backslash (\), double-quote ("), and line feed (\n) characters
%% @end
escape_label_value(LValue) when is_binary(LValue) ->
case has_special_char(LValue) of
true ->
escape_string(fun escape_label_char/1, LValue);
false ->
LValue
end;
escape_label_value(LValue) when is_list(LValue) ->
escape_label_value(iolist_to_binary(LValue));
escape_label_value(Value) ->
erlang:error({invalid_value, Value}).
%%====================================================================
%% Private Parts
%%====================================================================
registry_collect_callback(Fd, Registry, Collector) ->
Callback = fun (MF) ->
emit_mf_prologue(Fd, MF),
emit_mf_metrics(Fd, MF)
end,
prometheus_collector:collect_mf(Registry, Collector, Callback).
%% @private
emit_mf_prologue(Fd, #'MetricFamily'{name=Name, help=Help, type=Type}) ->
Bytes = ["# TYPE ", Name, " ", string_type(Type), "\n# HELP ",
Name, " ", escape_metric_help(Help), "\n"],
file:write(Fd, Bytes).
%% @private
emit_mf_metrics(Fd, #'MetricFamily'{name=Name, metric = Metrics}) ->
%% file:write/2 is an expensive operation, as it goes through a port driver.
%% Instead a large chunk of bytes is being collected here, in a
%% way that triggers binary append optimization in ERTS.
Bytes = lists:foldl(fun (Metric, Blob) ->
<<Blob/binary, (render_metric(Name, Metric))/binary>>
end, <<>>, Metrics),
file:write(Fd, Bytes).
render_metric(Name, #'Metric'{label=Labels,
counter=#'Counter'{value=Value}}) ->
render_series(Name, render_labels(Labels), Value);
render_metric(Name, #'Metric'{label=Labels,
gauge=#'Gauge'{value=Value}}) ->
render_series(Name, render_labels(Labels), Value);
render_metric(Name, #'Metric'{label=Labels,
untyped=#'Untyped'{value=Value}}) ->
render_series(Name, render_labels(Labels), Value);
render_metric(Name, #'Metric'{label=Labels,
summary=#'Summary'{sample_count=Count,
sample_sum=Sum,
quantile=Quantiles}}) ->
LString = render_labels(Labels),
Bytes1 = render_series([Name, "_count"], LString, Count),
Bytes2 = <<Bytes1/binary, (render_series([Name, "_sum"], LString, Sum))/binary>>,
Bytes3 = lists:foldl(fun (#'Quantile'{quantile = QN, value = QV}, Blob) ->
Val = render_series(
[Name],
render_labels(
[LString,
#'LabelPair'{name="quantile",
value=io_lib:format("~p", [QN])}]),
QV),
<<Blob/binary, Val/binary>>
end, Bytes2, Quantiles),
Bytes3;
render_metric(Name, #'Metric'{label=Labels,
histogram=#'Histogram'{sample_count=Count,
sample_sum=Sum,
bucket=Buckets}}) ->
%% StringLabels = labels_stringify(Labels),
LString = render_labels(Labels),
Bytes1 = lists:foldl(fun (Bucket, Blob) ->
<<Blob/binary, (emit_histogram_bucket(Name, LString, Bucket))/binary>>
end, << >>, Buckets),
Bytes2 = <<Bytes1/binary, (render_series([Name, "_count"], LString, Count))/binary>>,
Bytes3 = <<Bytes2/binary, (render_series([Name, "_sum"], LString, Sum))/binary>>,
Bytes3.
emit_histogram_bucket(Name, LString, #'Bucket'{cumulative_count=BCount,
upper_bound=BBound}) ->
BLValue = bound_to_label_value(BBound),
render_series([Name, "_bucket"],
render_labels([LString, #'LabelPair'{name="le", value=BLValue}]),
BCount).
string_type('COUNTER') ->
"counter";
string_type('GAUGE') ->
"gauge";
string_type('SUMMARY') ->
"summary";
string_type('HISTOGRAM') ->
"histogram";
string_type('UNTYPED') ->
"untyped".
%% binary() in spec means 0 or more already rendered labels (name,
%% escaped value), joined with "," in between
-spec render_labels(binary() | [prometheus_model:'LabelPair'() | binary()]) -> binary().
-dialyzer({no_match, render_labels/1}).
render_labels([]) ->
<<>>;
%% This clause is the reason for `-dialyzer` attr. It's an
%% optimization, but it slightly violates the types automatically
%% generated from protobufs.
render_labels(B) when is_binary(B) ->
B;
render_labels([<<>>|Labels]) ->
render_labels(Labels);
render_labels([FirstLabel|Labels]) ->
Start = << (render_label_pair(FirstLabel))/binary >>,
B = lists:foldl(fun
(<<>>, Acc) ->
Acc;
(Label, Acc) ->
<<Acc/binary, ",", (render_label_pair(Label))/binary>>
end, Start, Labels),
<<B/binary>>.
-spec render_label_pair(prometheus_model:'LabelPair'() | binary()) -> binary().
render_label_pair(B) when is_binary(B) ->
B;
render_label_pair(#'LabelPair'{name=Name, value=Value}) ->
<< (iolist_to_binary(Name))/binary, "=\"", (escape_label_value(Value))/binary, "\"" >>.
add_brackets(<<>>) ->
<<>>;
add_brackets(LString) ->
<<"{", LString/binary, "}">>.
render_series(Name, LString, undefined) ->
<<(iolist_to_binary(Name))/binary, (add_brackets(LString))/binary, " NaN\n">>;
render_series(Name, LString, Value) when is_integer(Value) ->
<<(iolist_to_binary(Name))/binary,
(add_brackets(LString))/binary,
" ",
(integer_to_binary(Value))/binary , "\n">>;
render_series(Name, LString, Value) ->
<<(iolist_to_binary(Name))/binary,
(add_brackets(LString))/binary,
" ",
(iolist_to_binary(io_lib:format("~p", [Value])))/binary , "\n">>.
%% @private
escape_metric_help(Help) ->
escape_string(fun escape_help_char/1, Help).
%% @private
escape_help_char($\\ = X) ->
<<X, X>>;
escape_help_char($\n) ->
<<$\\, $n>>;
escape_help_char(X) ->
<<X>>.
bound_to_label_value(Bound) when is_integer(Bound) ->
integer_to_list(Bound);
bound_to_label_value(Bound) when is_float(Bound) ->
float_to_list(Bound);
bound_to_label_value(infinity) ->
"+Inf".
%% @private
escape_label_char($\\ = X) ->
<<X, X>>;
escape_label_char($\n) ->
<<$\\, $n>>;
escape_label_char($" = X) ->
<<$\\, X>>;
escape_label_char(X) ->
<<X>>.
%% @perivate
-spec has_special_char(binary()) -> boolean().
has_special_char(<<C:8, _/bitstring>>)
when C =:= $\\;
C =:= $\n;
C =:= $" ->
true;
has_special_char(<<_:8, Rest/bitstring>>) ->
has_special_char(Rest);
has_special_char(<<>>) ->
false.
%% @private
escape_string(Fun, Str) when is_binary(Str) ->
<< <<(Fun(X))/binary>> || <<X:8>> <= Str >>;
escape_string(Fun, Str) ->
escape_string(Fun, iolist_to_binary(Str)).