Skip to content

Commit fef59ff

Browse files
committed
Add prometheus_array_format: export metrics as list of terms
Introduces a new format module that returns all collected metrics as a flat list of Erlang 4-tuples [{Name, Type, Labels, Value}] instead of a binary, enabling in-process consumption without text/protobuf parsing. Histogram samples are emitted with _bucket/_sum/_count suffixes; summary samples emit per-quantile entries plus _sum/_count. Labels are returned as [{BinaryName, BinaryValue}] proplists. https://claude.ai/code/session_01EG51CCtD9aMBzrNgtFxGCL
1 parent 09b95f8 commit fef59ff

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
%% @doc Exports all collected metrics as a flat list of Erlang terms.
2+
%%
3+
%% Each metric sample is represented as a 4-tuple:
4+
%% `{MetricName, Type, Labels, Value}'
5+
%%
6+
%% Where:
7+
%% <ul>
8+
%% <li>`MetricName' is a binary (histograms/summaries get `_bucket', `_sum',
9+
%% `_count' suffixes)</li>
10+
%% <li>`Type' is a lowercase atom: `counter', `gauge', `histogram',
11+
%% `summary', or `untyped'</li>
12+
%% <li>`Labels' is a proplist `[{BinaryName, BinaryValue}]'</li>
13+
%% <li>`Value' is an integer or float</li>
14+
%% </ul>
15+
%%
16+
%% Example:
17+
%% ```
18+
%% prometheus_array_format:format().
19+
%% [{<<"http_requests_total">>, counter, [{<<"method">>, <<"GET">>}], 42},
20+
%% {<<"latency_bucket">>, histogram, [{<<"le">>, <<"1.0">>}], 5},
21+
%% {<<"latency_sum">>, histogram, [], 2.3},
22+
%% {<<"latency_count">>, histogram, [], 10}]
23+
%% '''
24+
-module(prometheus_array_format).
25+
26+
-include("prometheus_model.hrl").
27+
28+
-export([format/0, format/1]).
29+
30+
%% @doc Exports metrics from the default registry.
31+
-spec format() -> [{binary(), atom(), [{binary(), binary()}], number()}].
32+
format() ->
33+
format(default).
34+
35+
%% @doc Exports metrics from the given registry.
36+
-spec format(Registry :: atom()) -> [{binary(), atom(), [{binary(), binary()}], number()}].
37+
format(Registry) ->
38+
MFs = collect_mfs(Registry),
39+
lists:flatmap(fun mf_to_tuples/1, MFs).
40+
41+
%%====================================================================
42+
%% Internal functions
43+
%%====================================================================
44+
45+
collect_mfs(Registry) ->
46+
put(?MODULE, []),
47+
Callback = fun(Reg, Collector) ->
48+
MFCallback = fun(MF) -> put(?MODULE, [MF | get(?MODULE)]) end,
49+
prometheus_collector:collect_mf(Reg, Collector, MFCallback)
50+
end,
51+
prometheus_registry:collect(Registry, Callback),
52+
Result = lists:reverse(get(?MODULE)),
53+
erase(?MODULE),
54+
Result.
55+
56+
mf_to_tuples(#'MetricFamily'{name = Name, type = Type, metric = Metrics}) ->
57+
T = atom_type(Type),
58+
NameBin = iolist_to_binary(Name),
59+
lists:flatmap(fun(M) -> metric_to_tuples(NameBin, T, M) end, Metrics).
60+
61+
atom_type('COUNTER') -> counter;
62+
atom_type('GAUGE') -> gauge;
63+
atom_type('HISTOGRAM') -> histogram;
64+
atom_type('SUMMARY') -> summary;
65+
atom_type(_) -> untyped.
66+
67+
metric_to_tuples(Name, T, #'Metric'{label = Labels, counter = #'Counter'{value = V}}) ->
68+
[{Name, T, extract_labels(Labels), V}];
69+
metric_to_tuples(Name, T, #'Metric'{label = Labels, gauge = #'Gauge'{value = V}}) ->
70+
[{Name, T, extract_labels(Labels), V}];
71+
metric_to_tuples(Name, T, #'Metric'{label = Labels, untyped = #'Untyped'{value = V}}) ->
72+
[{Name, T, extract_labels(Labels), V}];
73+
metric_to_tuples(Name, T, #'Metric'{label = Labels,
74+
summary = #'Summary'{sample_count = C, sample_sum = S, quantile = Qs}}) ->
75+
Base = extract_labels(Labels),
76+
QTerms = [{Name, T, Base ++ [{<<"quantile">>, format_float(Q)}], V}
77+
|| #'Quantile'{quantile = Q, value = V} <- Qs],
78+
QTerms ++ [
79+
{<<Name/binary, "_sum">>, T, Base, S},
80+
{<<Name/binary, "_count">>, T, Base, C}
81+
];
82+
metric_to_tuples(Name, T, #'Metric'{label = Labels,
83+
histogram = #'Histogram'{sample_count = C, sample_sum = S, bucket = Bs}}) ->
84+
Base = extract_labels(Labels),
85+
BTerms = [{<<Name/binary, "_bucket">>, T,
86+
Base ++ [{<<"le">>, format_bound(UB)}], CC}
87+
|| #'Bucket'{upper_bound = UB, cumulative_count = CC} <- Bs],
88+
BTerms ++ [
89+
{<<Name/binary, "_sum">>, T, Base, S},
90+
{<<Name/binary, "_count">>, T, Base, C}
91+
].
92+
93+
extract_labels(Labels) when is_list(Labels) ->
94+
[{iolist_to_binary(N), iolist_to_binary(V)}
95+
|| #'LabelPair'{name = N, value = V} <- Labels];
96+
extract_labels(_) ->
97+
[].
98+
99+
format_bound(infinity) ->
100+
<<"+Inf">>;
101+
format_bound(N) when is_integer(N) ->
102+
integer_to_binary(N);
103+
format_bound(N) ->
104+
float_to_binary(N, [{decimals, 10}, compact]).
105+
106+
format_float(N) when is_integer(N) ->
107+
integer_to_binary(N);
108+
format_float(N) ->
109+
float_to_binary(N, [{decimals, 10}, compact]).

0 commit comments

Comments
 (0)