diff --git a/src/exometer_prometheus_fmt.erl b/src/exometer_prometheus_fmt.erl new file mode 100644 index 0000000..b0a15a4 --- /dev/null +++ b/src/exometer_prometheus_fmt.erl @@ -0,0 +1,103 @@ +-module(exometer_prometheus_fmt). + +-export([ + name/1, + type/1, + metrics/1 +]). + +-type prom_name() :: iolist(). +-type prom_type() :: iolist(). +-type prom_help() :: iolist(). +-type prom_text() :: binary(). +-type metric_info() :: {exometer:name(), + exometer:value(), + prom_name(), + prom_type(), + prom_help()}. + + +%% ------------------------------------------------------- +%% Public API +%% ------------------------------------------------------- + +-spec name(exometer:name()) -> binary(). +name(MetricId) -> + NameList = lists:join($_, lists:map(fun ioize/1, MetricId)), + NameBin = iolist_to_binary(NameList), + re:replace(NameBin, "-|\\.", "_", [global, {return,binary}]). + +-spec type(atom()) -> binary(). +type(undefined) -> <<"untyped">>; +type(counter) -> <<"gauge">>; +type(gauge) -> <<"gauge">>; +type(spiral) -> <<"gauge">>; +type(histogram) -> <<"summary">>; +type(function) -> <<"gauge">>; +type(Tuple) when is_tuple(Tuple) -> + case element(1, Tuple) of + function -> <<"gauge">>; + _Else -> <<"untyped">> + end. + +-spec metrics([metric_info()]) -> prom_text(). +metrics(Metrics) -> + Formatted = metrics(Metrics, []), + iolist_to_binary(Formatted). + + +%% ------------------------------------------------------- +%% Internal +%% ------------------------------------------------------- + +metrics([], Akk) -> + Akk; +metrics([{Metric, DataPoints, Name, Type, Help} | Metrics], Akk) -> + Payload = [[<<"# HELP ">>, Name, <<" ">>, Help, <<"\n">>, + <<"# TYPE ">>, Name, <<" ">>, Type, <<"\n">>] | + [[Name, map_datapoint(DPName), <<" ">>, ioize(Value), <<"\n">>] + || {DPName, Value} <- DataPoints, is_valid_datapoint(DPName)]], + Payload1 = maybe_add_sum(Payload, Name, Metric, Type), + metrics(Metrics, [Payload1, <<"\n">> | Akk]). + +ioize(Atom) when is_atom(Atom) -> + atom_to_binary(Atom, utf8); +ioize(Number) when is_float(Number) -> + float_to_binary(Number, [{decimals, 4}]); +ioize(Number) when is_integer(Number) -> + integer_to_binary(Number); +ioize(Something) -> + Something. + +maybe_add_sum(Payload, MetricName, Metric, <<"summary">>) -> + {ok, [{mean, Mean}, {n, N}]} = exometer:get_value(Metric, [mean, n]), + [Payload | [MetricName, <<"_sum ">>, ioize(Mean * N), <<"\n">>]]; +maybe_add_sum(Payload, _MetricName, _Metric, _Else) -> + Payload. + +map_datapoint(value) -> <<"">>; +map_datapoint(one) -> <<"">>; +map_datapoint(n) -> <<"_count">>; +map_datapoint(50) -> <<"{quantile=\"0.5\"}">>; +map_datapoint(90) -> <<"{quantile=\"0.9\"}">>; +map_datapoint(Integer) when is_integer(Integer) -> + Bin = ioize(Integer), + <<"{quantile=\"0.", Bin/binary, "\"}">>; +map_datapoint(Something) -> + %% this is for functions with alternative datapoints + Bin = ioize(Something), + <<"{datapoint=\"", Bin/binary, "\"}">>. + +is_valid_datapoint(value) -> true; +is_valid_datapoint(one) -> true; +is_valid_datapoint(n) -> true; +is_valid_datapoint(Number) when is_number(Number) -> true; +is_valid_datapoint(count) -> false; +is_valid_datapoint(mean) -> false; +is_valid_datapoint(min) -> false; +is_valid_datapoint(max) -> false; +is_valid_datapoint(median) -> false; +is_valid_datapoint(ms_since_reset) -> false; +%% this is for functions with alternative datapoints +is_valid_datapoint(_Else) -> true. + diff --git a/src/exometer_report_prometheus.erl b/src/exometer_report_prometheus.erl index a4728fb..c89c768 100644 --- a/src/exometer_report_prometheus.erl +++ b/src/exometer_report_prometheus.erl @@ -24,6 +24,7 @@ %% Public API %% ------------------------------------------------------- +-spec fetch() -> exometer_prometheus_fmt:prom_text(). fetch() -> exometer_report:call_reporter(exometer_report_prometheus, {request, fetch}). @@ -40,11 +41,11 @@ exometer_init(Opts) -> {ok, #state{}}. exometer_subscribe(Metric, DataPoints, _Interval, Opts, State = #state{entries=Entries}) -> - Name = make_metric_name(Metric), + Name = exometer_prometheus_fmt:name(Metric), Help = proplists:get_value(help, Opts, <<"undefined">>), Type = case proplists:get_value(type, Opts, undefined) of - undefined -> map_type(exometer:info(Metric, type)); - SomeType -> ioize(SomeType) + undefined -> exometer_prometheus_fmt:type(exometer:info(Metric, type)); + SomeType -> atom_to_binary(SomeType, latin1) end, Entry = {Metric, DataPoints, Name, Type, Help}, {ok, State#state{entries = Entries ++ [Entry]}}. @@ -71,7 +72,7 @@ exometer_terminate(_Reason, _) -> ignore. fetch_and_format_metrics(Entries) -> Metrics = fetch_metrics(Entries), - format_metrics(Metrics). + exometer_prometheus_fmt:metrics(Metrics). fetch_metrics(Entries) -> fetch_metrics(Entries, []). @@ -86,75 +87,3 @@ fetch_metrics([{Metric, DataPoints, Name, Type, Help} | Entries], Akk) -> fetch_metrics(Entries, Akk) end. -format_metrics(Metrics) -> - Formatted = format_metrics(Metrics, []), - iolist_to_binary(Formatted). - -format_metrics([], Akk) -> - Akk; -format_metrics([{Metric, DataPoints, Name, Type, Help} | Metrics], Akk) -> - Payload = [[<<"# HELP ">>, Name, <<" ">>, Help, <<"\n">>, - <<"# TYPE ">>, Name, <<" ">>, Type, <<"\n">>] | - [[Name, map_datapoint(DPName), <<" ">>, ioize(Value), <<"\n">>] - || {DPName, Value} <- DataPoints, is_valid_datapoint(DPName)]], - Payload1 = maybe_add_sum(Payload, Name, Metric, Type), - format_metrics(Metrics, [Payload1, <<"\n">> | Akk]). - -make_metric_name(Metric) -> - NameList = lists:join($_, lists:map(fun ioize/1, Metric)), - NameBin = iolist_to_binary(NameList), - re:replace(NameBin, "-|\\.", "_", [global, {return,binary}]). - -ioize(Atom) when is_atom(Atom) -> - atom_to_binary(Atom, utf8); -ioize(Number) when is_float(Number) -> - float_to_binary(Number, [{decimals, 4}]); -ioize(Number) when is_integer(Number) -> - integer_to_binary(Number); -ioize(Something) -> - Something. - -maybe_add_sum(Payload, MetricName, Metric, <<"summary">>) -> - {ok, [{mean, Mean}, {n, N}]} = exometer:get_value(Metric, [mean, n]), - [Payload | [MetricName, <<"_sum ">>, ioize(Mean * N), <<"\n">>]]; -maybe_add_sum(Payload, _MetricName, _Metric, _Else) -> - Payload. - -map_type(undefined) -> <<"untyped">>; -map_type(counter) -> <<"gauge">>; -map_type(gauge) -> <<"gauge">>; -map_type(spiral) -> <<"gauge">>; -map_type(histogram) -> <<"summary">>; -map_type(function) -> <<"gauge">>; -map_type(Tuple) when is_tuple(Tuple) -> - case element(1, Tuple) of - function -> <<"gauge">>; - _Else -> <<"untyped">> - end. - -map_datapoint(value) -> <<"">>; -map_datapoint(one) -> <<"">>; -map_datapoint(n) -> <<"_count">>; -map_datapoint(50) -> <<"{quantile=\"0.5\"}">>; -map_datapoint(90) -> <<"{quantile=\"0.9\"}">>; -map_datapoint(Integer) when is_integer(Integer) -> - Bin = ioize(Integer), - <<"{quantile=\"0.", Bin/binary, "\"}">>; -map_datapoint(Something) -> - %% this is for functions with alternative datapoints - Bin = ioize(Something), - <<"{datapoint=\"", Bin/binary, "\"}">>. - -is_valid_datapoint(value) -> true; -is_valid_datapoint(one) -> true; -is_valid_datapoint(n) -> true; -is_valid_datapoint(Number) when is_number(Number) -> true; -is_valid_datapoint(count) -> false; -is_valid_datapoint(mean) -> false; -is_valid_datapoint(min) -> false; -is_valid_datapoint(max) -> false; -is_valid_datapoint(median) -> false; -is_valid_datapoint(ms_since_reset) -> false; -%% this is for functions with alternative datapoints -is_valid_datapoint(_Else) -> true. -