Skip to content

Commit 566e985

Browse files
the-mikedavisNelsonVides
authored andcommitted
prometheus_text_format: Eliminate ram_file, add format_into/3
Building on the work in the parent commit, now that the data being passed to the `ram_file` is a binary, we can instead build the entire output gradually within the process. We pay in terms of I/O overhead from writing and then reading from the `ram_file` since `ram_file` is a port - all data is passed between the VM and the port driver. The memory consumed by a port driver is also invisible to the VM's allocator, so large port driver resource usage should be avoided where possible. Instead this change refactors the `registry_collect_callback` to fold over collectors and build an accumulator. The `create_mf` callback's return of `ok` forces us to store this rather than pass and return it. So it's a little less hygienic but is more efficient than passing data in/out of a port. This also introduces a function `format_into/3` which can use this folding function directly. This can be used to avoid collecting the entire response in one binary. Instead the response can be streamed with `cowboy_req:stream_body/3` for example.
1 parent 65be0bb commit 566e985

File tree

1 file changed

+45
-32
lines changed

1 file changed

+45
-32
lines changed

src/formats/prometheus_text_format.erl

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ http_request_duration_milliseconds_sum{method=\"post\"} 4350
2626
```
2727
""").
2828

29-
-export([content_type/0, format/0, format/1, render_labels/1, escape_label_value/1]).
29+
-export([content_type/0, format/0, format/1, format_into/3, render_labels/1, escape_label_value/1]).
3030

3131
-ifdef(TEST).
3232
-export([escape_metric_help/1]).
@@ -57,35 +57,35 @@ format() ->
5757
?DOC("Formats `Registry` using the latest text format.").
5858
-spec format(Registry :: prometheus_registry:registry()) -> binary().
5959
format(Registry) ->
60-
{ok, Fd} = ram_file:open("", [write, read, binary]),
61-
Callback = fun(_, Collector) ->
62-
registry_collect_callback(Fd, Registry, Collector)
63-
end,
64-
prometheus_registry:collect(Registry, Callback),
65-
file:write(Fd, "\n"),
66-
{ok, Size} = ram_file:get_size(Fd),
67-
{ok, Str} = file:pread(Fd, 0, Size),
68-
ok = file:close(Fd),
69-
Str.
60+
format_into(Registry, fun format_into_binary/2, <<>>).
61+
62+
format_into_binary(Acc, Data) ->
63+
<<Acc/binary, Data/binary>>.
7064

7165
?DOC("""
72-
Escapes the backslash (\\), double-quote (\"), and line feed (\\n) characters
66+
Formats `Registry` using the latest text format, passing the binary data for
67+
each collector to the format function.
7368
""").
74-
-spec escape_label_value(binary() | iolist()) -> binary().
75-
escape_label_value(LValue) when is_binary(LValue) ->
76-
case has_special_char(LValue) of
77-
true ->
78-
escape_string(fun escape_label_char/1, LValue);
79-
false ->
80-
LValue
81-
end;
82-
escape_label_value(LValue) when is_list(LValue) ->
83-
escape_label_value(iolist_to_binary(LValue));
84-
escape_label_value(Value) ->
85-
erlang:error({invalid_value, Value}).
69+
-spec format_into(
70+
Registry :: prometheus_registry:registry(), fun((term(), binary()) -> term()), term()
71+
) -> term().
72+
format_into(Registry, Fmt, State) ->
73+
State1 = lists:foldl(
74+
format_into_collector_fn(Registry, Fmt), State, prometheus_registry:collectors(Registry)
75+
),
76+
Fmt(State1, <<"\n">>).
77+
78+
format_into_collector_fn(Registry, Fmt) ->
79+
fun(Collector, Acc) ->
80+
put(?MODULE, Acc),
81+
prometheus_collector:collect_mf(
82+
Registry, Collector, format_into_create_mf_callback_fn(Fmt)
83+
),
84+
erase(?MODULE)
85+
end.
8686

87-
registry_collect_callback(Fd, Registry, Collector) ->
88-
Callback = fun(#'MetricFamily'{name = Name0, help = Help, type = Type, metric = Metrics}) ->
87+
format_into_create_mf_callback_fn(Fmt) ->
88+
fun(#'MetricFamily'{name = Name0, help = Help, type = Type, metric = Metrics}) ->
8989
%% eagerly convert the name to a binary so we can copy more efficiently
9090
%% in `render_metrics/3`
9191
Name = iolist_to_binary(Name0),
@@ -100,12 +100,25 @@ registry_collect_callback(Fd, Registry, Collector) ->
100100
(escape_metric_help(Help))/binary,
101101
"\n"
102102
>>,
103-
%% file:write/2 is an expensive operation, as it goes through a port driver.
104-
%% Instead a large chunk of bytes is being collected here, in a
105-
%% way that triggers binary append optimization in ERTS.
106-
file:write(Fd, render_metrics(Name, Metrics, Prologue))
107-
end,
108-
prometheus_collector:collect_mf(Registry, Collector, Callback).
103+
Bin = render_metrics(Prologue, Name, Metrics),
104+
put(?MODULE, Fmt(Bin, erase(?MODULE)))
105+
end.
106+
107+
?DOC("""
108+
Escapes the backslash (\\), double-quote (\"), and line feed (\\n) characters
109+
""").
110+
-spec escape_label_value(binary() | iolist()) -> binary().
111+
escape_label_value(LValue) when is_binary(LValue) ->
112+
case has_special_char(LValue) of
113+
true ->
114+
escape_string(fun escape_label_char/1, LValue);
115+
false ->
116+
LValue
117+
end;
118+
escape_label_value(LValue) when is_list(LValue) ->
119+
escape_label_value(iolist_to_binary(LValue));
120+
escape_label_value(Value) ->
121+
erlang:error({invalid_value, Value}).
109122

110123
render_metrics(Bytes, _Name, []) ->
111124
Bytes;

0 commit comments

Comments
 (0)