You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by to...@apache.org on 2021/04/14 00:23:44 UTC
[couchdb] 01/05: Add new app couch_prometheus
This is an automated email from the ASF dual-hosted git repository.
tonysun83 pushed a commit to branch port-prometheus-3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit a69766e47396fe0d5a059ff748c6573ae5774570
Author: Tony Sun <to...@gmail.com>
AuthorDate: Wed Mar 10 08:13:51 2021 -0800
Add new app couch_prometheus
This will be a new app add a _prometheus endpoint which will
return metrics information that adheres to the format described at
https://prometheus.io/.
Initial implementation of new _prometheus endpoint. A gen_server
waits for scraping calls while polling couch_stats:fetch and
other system info. The return value is constructed to adhere to
prometheus format and returned as text/plain. The format code
was originally written by @davisp.
---
rebar.config.script | 1 +
rel/reltool.config | 2 +
src/chttpd/src/chttpd_node.erl | 6 +
src/couch/src/couch.app.src | 3 +-
src/couch_prometheus/src/couch_prometheus.app.src | 20 +++
src/couch_prometheus/src/couch_prometheus.hrl | 13 ++
src/couch_prometheus/src/couch_prometheus_app.erl | 23 +++
.../src/couch_prometheus_server.erl | 168 +++++++++++++++++++++
src/couch_prometheus/src/couch_prometheus_sup.erl | 33 ++++
src/couch_prometheus/src/couch_prometheus_util.erl | 166 ++++++++++++++++++++
.../test/eunit/couch_prometheus_util_tests.erl | 67 ++++++++
11 files changed, 501 insertions(+), 1 deletion(-)
diff --git a/rebar.config.script b/rebar.config.script
index f12ef38..a878524 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -139,6 +139,7 @@ SubDirs = [
"src/rexi",
"src/setup",
"src/smoosh",
+ "src/couch_prometheus",
"rel"
].
diff --git a/rel/reltool.config b/rel/reltool.config
index 70f7bbc..c966e9d 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -61,6 +61,7 @@
setup,
smoosh,
snappy,
+ couch_prometheus,
%% extra
recon
]},
@@ -121,6 +122,7 @@
{app, setup, [{incl_cond, include}]},
{app, smoosh, [{incl_cond, include}]},
{app, snappy, [{incl_cond, include}]},
+ {app, couch_prometheus, [{incl_cond, include}]},
%% extra
{app, recon, [{incl_cond, include}]}
diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
index c48dfc0..063ef4b 100644
--- a/src/chttpd/src/chttpd_node.erl
+++ b/src/chttpd/src/chttpd_node.erl
@@ -117,6 +117,12 @@ handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_stats">> | Path]}=
chttpd:send_json(Req, EJSON1);
handle_node_req(#httpd{path_parts=[_, _Node, <<"_stats">>]}=Req) ->
send_method_not_allowed(Req, "GET");
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_prometheus">>]}=Req) ->
+ Metrics = call_node(Node, couch_prometheus_server, scrape, []),
+ Header = [{<<"Content-Type">>, <<"text/plain">>}],
+ chttpd:send_response(Req, 200, Header, Metrics);
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_prometheus">>]}=Req) ->
+ send_method_not_allowed(Req, "GET");
% GET /_node/$node/_system
handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_system">>]}=Req) ->
Stats = call_node(Node, chttpd_node, get_stats, []),
diff --git a/src/couch/src/couch.app.src b/src/couch/src/couch.app.src
index 6116c79..74674bb 100644
--- a/src/couch/src/couch.app.src
+++ b/src/couch/src/couch.app.src
@@ -45,7 +45,8 @@
couch_event,
ioq,
couch_stats,
- hyper
+ hyper,
+ couch_prometheus
]},
{env, [
{ httpd_global_handlers, [
diff --git a/src/couch_prometheus/src/couch_prometheus.app.src b/src/couch_prometheus/src/couch_prometheus.app.src
new file mode 100644
index 0000000..3080b29
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus.app.src
@@ -0,0 +1,20 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+{application, couch_prometheus, [
+ {description, "Aggregated metrics info for Prometheus consumption"},
+ {vsn, git},
+ {registered, []},
+ {applications, [kernel, stdlib, folsom, couch_stats]},
+ {mod, {couch_prometheus_app, []}},
+ {env, []}
+]}.
diff --git a/src/couch_prometheus/src/couch_prometheus.hrl b/src/couch_prometheus/src/couch_prometheus.hrl
new file mode 100644
index 0000000..e82fb90
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus.hrl
@@ -0,0 +1,13 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-define(REFRESH_INTERVAL, 60).
diff --git a/src/couch_prometheus/src/couch_prometheus_app.erl b/src/couch_prometheus/src/couch_prometheus_app.erl
new file mode 100644
index 0000000..232c16a
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus_app.erl
@@ -0,0 +1,23 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_prometheus_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+start(_StartType, _StartArgs) ->
+ couch_prometheus_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/src/couch_prometheus/src/couch_prometheus_server.erl b/src/couch_prometheus/src/couch_prometheus_server.erl
new file mode 100644
index 0000000..a0accba
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus_server.erl
@@ -0,0 +1,168 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_prometheus_server).
+
+-behaviour(gen_server).
+
+-import(couch_prometheus_util, [
+ couch_to_prom/3,
+ to_prom/3,
+ to_prom_summary/2
+]).
+
+-export([
+ start_link/0,
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ code_change/3,
+ terminate/2,
+
+ scrape/0
+]).
+
+-include("couch_prometheus.hrl").
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-record(st, {
+ metrics,
+ refresh
+}).
+
+init([]) ->
+ Metrics = refresh_metrics(),
+ RT = update_refresh_timer(),
+ {ok, #st{metrics=Metrics, refresh=RT}}.
+
+scrape() ->
+ {ok, Metrics} = gen_server:call(?MODULE, scrape),
+ Metrics.
+
+
+handle_call(scrape, _from, #st{metrics = Metrics}=State) ->
+ {reply, {ok, Metrics}, State};
+handle_call(refresh, _from, #st{refresh=OldRT} = State) ->
+ timer:cancel(OldRT),
+ Metrics = refresh_metrics(),
+ RT = update_refresh_timer(),
+ {reply, ok, State#st{metrics=Metrics, refresh=RT}};
+handle_call(Msg, _From, State) ->
+ {stop, {unknown_call, Msg}, error, State}.
+
+handle_cast(Msg, State) ->
+ {stop, {unknown_cast, Msg}, State}.
+
+handle_info(refresh, State) ->
+ Metrics = refresh_metrics(),
+ {noreply, State#st{metrics=Metrics}};
+handle_info(Msg, State) ->
+ {stop, {unknown_info, Msg}, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+refresh_metrics() ->
+ CouchDB = get_couchdb_stats(),
+ System = couch_stats_httpd:to_ejson(get_system_stats()),
+ couch_prometheus_util:to_bin(lists:map(fun(Line) ->
+ io_lib:format("~s~n", [Line])
+ end, CouchDB ++ System)).
+
+get_couchdb_stats() ->
+ Stats = lists:sort(couch_stats:fetch()),
+ lists:flatmap(fun({Path, Info}) ->
+ couch_to_prom(Path, Info, Stats)
+ end, Stats).
+
+get_system_stats() ->
+ lists:flatten([
+ get_uptime_stat(),
+ get_vm_stats(),
+ get_io_stats(),
+ get_message_queue_stats(),
+ get_run_queue_stats(),
+ get_vm_stats(),
+ get_ets_stats()
+ ]).
+
+get_uptime_stat() ->
+ to_prom(uptime_seconds, counter, couch_app:uptime() div 1000).
+
+get_vm_stats() ->
+ MemLabels = lists:map(fun({Type, Value}) ->
+ {[{memory_type, Type}], Value}
+ end, erlang:memory()),
+ {NumGCs, WordsReclaimed, _} = erlang:statistics(garbage_collection),
+ CtxSwitches = element(1, erlang:statistics(context_switches)),
+ Reds = element(1, erlang:statistics(reductions)),
+ ProcCount = erlang:system_info(process_count),
+ ProcLimit = erlang:system_info(process_limit),
+ [
+ to_prom(erlang_memory_bytes, gauge, MemLabels),
+ to_prom(erlang_gc_collections_total, counter, NumGCs),
+ to_prom(erlang_gc_words_reclaimed_total, counter, WordsReclaimed),
+ to_prom(erlang_context_switches_total, counter, CtxSwitches),
+ to_prom(erlang_reductions_total, counter, Reds),
+ to_prom(erlang_processes, gauge, ProcCount),
+ to_prom(erlang_process_limit, gauge, ProcLimit)
+ ].
+
+get_io_stats() ->
+ {{input, In}, {output, Out}} = erlang:statistics(io),
+ [
+ to_prom(erlang_io_recv_bytes_total, counter, In),
+ to_prom(erlang_io_sent_bytes_total, counter, Out)
+ ].
+
+get_message_queue_stats() ->
+ Queues = lists:map(fun(Name) ->
+ case process_info(whereis(Name), message_queue_len) of
+ {message_queue_len, N} ->
+ N;
+ _ ->
+ 0
+ end
+ end, registered()),
+ [
+ to_prom(erlang_message_queues, gauge, lists:sum(Queues)),
+ to_prom(erlang_message_queue_min, gauge, lists:min(Queues)),
+ to_prom(erlang_message_queue_max, gauge, lists:max(Queues))
+ ].
+
+get_run_queue_stats() ->
+ %% Workaround for https://bugs.erlang.org/browse/ERL-1355
+ {Normal, Dirty} = case erlang:system_info(dirty_cpu_schedulers) > 0 of
+ false ->
+ {statistics(run_queue), 0};
+ true ->
+ [DCQ | SQs] = lists:reverse(statistics(run_queue_lengths)),
+ {lists:sum(SQs), DCQ}
+ end,
+ [
+ to_prom(erlang_scheduler_queues, gauge, Normal),
+ to_prom(erlang_dirty_cpu_scheduler_queues, gauge, Dirty)
+ ].
+
+get_ets_stats() ->
+ NumTabs = length(ets:all()),
+ to_prom(erlang_ets_table, gauge, NumTabs).
+
+update_refresh_timer() ->
+ RefreshTime = 1000 * config:get_integer("couch_prometheus", "interval", ?REFRESH_INTERVAL),
+ erlang:send_after(RefreshTime, self(), refresh).
diff --git a/src/couch_prometheus/src/couch_prometheus_sup.erl b/src/couch_prometheus/src/couch_prometheus_sup.erl
new file mode 100644
index 0000000..09ed45f
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus_sup.erl
@@ -0,0 +1,33 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_prometheus_sup).
+
+-behaviour(supervisor).
+
+-export([
+ start_link/0,
+ init/1
+]).
+
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ {ok, {
+ {one_for_one, 5, 10}, [
+ ?CHILD(couch_prometheus_server, worker)
+ ]
+ }}.
+
diff --git a/src/couch_prometheus/src/couch_prometheus_util.erl b/src/couch_prometheus/src/couch_prometheus_util.erl
new file mode 100644
index 0000000..c3b58cb
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus_util.erl
@@ -0,0 +1,166 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_prometheus_util ).
+
+-export([
+ couch_to_prom/3,
+ to_bin/1,
+ to_prom/3,
+ to_prom_summary/2
+]).
+
+-include("couch_prometheus.hrl").
+
+couch_to_prom([couch_log, level, alert], Info, _All) ->
+ to_prom(couch_log_requests_total, counter, {[{level, alert}], val(Info)});
+couch_to_prom([couch_log, level, Level], Info, _All) ->
+ to_prom(couch_log_requests_total, {[{level, Level}], val(Info)});
+
+couch_to_prom([couch_replicator, checkpoints, failure], Info, _All) ->
+ to_prom(couch_replicator_checkpoints_failure_total, counter, val(Info));
+couch_to_prom([couch_replicator, checkpoints, success], Info, All) ->
+ Total = val(Info) + val([couch_replicator, checkpoints, failure], All),
+ to_prom(couch_replicator_checkpoints_total, counter, Total);
+couch_to_prom([couch_replicator, responses, failure], Info, _All) ->
+ to_prom(couch_replicator_responses_failure_total, counter, val(Info));
+couch_to_prom([couch_replicator, responses, success], Info, All) ->
+ Total = val(Info) + val([couch_replicator, responses, failure], All),
+ to_prom(couch_replicator_responses_total, counter, Total);
+couch_to_prom([couch_replicator, stream_responses, failure], Info, _All) ->
+ to_prom(couch_replicator_stream_responses_failure_total, counter, val(Info));
+couch_to_prom([couch_replicator, stream_responses, success], Info, All) ->
+ Total = val(Info) + val([couch_replicator, stream_responses, failure], All),
+ to_prom(couch_replicator_stream_responses_total, counter, Total);
+
+couch_to_prom([couchdb, auth_cache_hits], Info, All) ->
+ Total = val(Info) + val([couchdb, auth_cache_misses], All),
+ to_prom(auth_cache_requests_total, counter, Total);
+couch_to_prom([couchdb, auth_cache_misses], Info, _All) ->
+ to_prom(auth_cache_misses_total, counter, val(Info));
+couch_to_prom([couchdb, httpd_request_methods, 'COPY'], Info, _All) ->
+ to_prom(httpd_request_methods, counter, {[{method, 'COPY'}], val(Info)});
+couch_to_prom([couchdb, httpd_request_methods, Method], Info, _All) ->
+ to_prom(httpd_request_methods, {[{method, Method}], val(Info)});
+couch_to_prom([couchdb, httpd_status_codes, Code], Info, _All) ->
+ to_prom(httpd_status_codes, {[{code, Code}], val(Info)});
+
+couch_to_prom([ddoc_cache, hit], Info, All) ->
+ Total = val(Info) + val([ddoc_cache, miss], All),
+ to_prom(ddoc_cache_requests_total, counter, Total);
+couch_to_prom([ddoc_cache, miss], Info, _All) ->
+ to_prom(ddoc_cache_requests_failures_total, counter, val(Info));
+couch_to_prom([ddoc_cache, recovery], Info, _All) ->
+ to_prom(ddoc_cache_requests_recovery_total, counter, val(Info));
+
+couch_to_prom([fabric, read_repairs, failure], Info, _All) ->
+ to_prom(fabric_read_repairs_failures_total, counter, val(Info));
+couch_to_prom([fabric, read_repairs, success], Info, All) ->
+ Total = val(Info) + val([fabric, read_repairs, failure], All),
+ to_prom(fabric_read_repairs_total, counter, Total);
+
+couch_to_prom([rexi, streams, timeout, init_stream], Info, _All) ->
+ to_prom(rexi_streams_timeout_total, counter, {[{stage, init_stream}], val(Info)});
+couch_to_prom([rexi_streams, timeout, Stage], Info, _All) ->
+ to_prom(rexi_streams_timeout_total, {[{stage, Stage}], val(Info)});
+
+couch_to_prom([couchdb | Rest], Info, All) ->
+ couch_to_prom(Rest, Info, All);
+
+couch_to_prom(Path, Info, _All) ->
+ case lists:keyfind(type, 1, Info) of
+ {type, counter} ->
+ Metric = counter_metric(Path),
+ to_prom(Metric, counter, val(Info));
+ {type, gauge} ->
+ to_prom(path_to_name(Path), gauge, val(Info));
+ {type, histogram} ->
+ to_prom_summary(Path, Info)
+ end.
+
+to_prom(Metric, Type, Data) ->
+ TypeStr = to_bin(io_lib:format("# TYPE ~s ~s", [to_prom_name(Metric), Type])),
+ [TypeStr] ++ to_prom(Metric, Data).
+
+to_prom(Metric, Instances) when is_list(Instances) ->
+ lists:flatmap(fun(Inst) -> to_prom(Metric, Inst) end, Instances);
+to_prom(Metric, {Labels, Value}) ->
+ LabelParts = lists:map(fun({K, V}) ->
+ lists:flatten(io_lib:format("~s=\"~s\"", [to_bin(K), to_bin(V)]))
+ end, Labels),
+ MetricStr = case length(LabelParts) > 0 of
+ true ->
+ LabelStr = string:join(LabelParts, ", "),
+ lists:flatten(io_lib:format("~s{~s}", [to_prom_name(Metric), LabelStr]));
+ false ->
+ lists:flatten(io_lib:format("~s", [to_prom_name(Metric)]))
+ end,
+ [to_bin(io_lib:format("~s ~p", [MetricStr, Value]))];
+to_prom(Metric, Value) ->
+ [to_bin(io_lib:format("~s ~p", [to_prom_name(Metric), Value]))].
+
+to_prom_summary(Path, Info) ->
+ Metric = path_to_name(Path ++ ["seconds"]),
+ {value, Value} = lists:keyfind(value, 1, Info),
+ {arithmetic_mean, Mean} = lists:keyfind(arithmetic_mean, 1, Value),
+ {percentile, Percentiles} = lists:keyfind(percentile, 1, Value),
+ {n, Count} = lists:keyfind(n, 1, Value),
+ Quantiles = lists:map(fun({Perc, Val0}) ->
+ % Prometheus uses seconds, so we need to covert milliseconds to seconds
+ Val = Val0/1000,
+ case Perc of
+ 50 -> {[{quantile, <<"0.5">>}], Val};
+ 75 -> {[{quantile, <<"0.75">>}], Val};
+ 90 -> {[{quantile, <<"0.9">>}], Val};
+ 95 -> {[{quantile, <<"0.95">>}], Val};
+ 99 -> {[{quantile, <<"0.99">>}], Val};
+ 999 -> {[{quantile, <<"0.999">>}], Val}
+ end
+ end, Percentiles),
+ SumMetric = path_to_name(Path ++ ["seconds", "sum"]),
+ SumStat = to_prom(SumMetric, Count * Mean),
+ CountMetric = path_to_name(Path ++ ["seconds", "count"]),
+ CountStat = to_prom(CountMetric, Count),
+ to_prom(Metric, summary, Quantiles) ++ [SumStat, CountStat].
+
+to_prom_name(Metric) ->
+ to_bin(io_lib:format("couchdb_~s", [Metric])).
+
+path_to_name(Path) ->
+ Parts = lists:map(fun(Part) ->
+ io_lib:format("~s", [Part])
+ end, Path),
+ string:join(Parts, "_").
+
+counter_metric(Path) ->
+ Name = path_to_name(Path),
+ case string:find(Name, <<"_total">>, trailing) == <<"_total">> of
+ true -> Name;
+ false -> to_bin(io_lib:format("~s_total", [Name]))
+ end.
+
+to_bin(Data) when is_list(Data) ->
+ iolist_to_binary(Data);
+to_bin(Data) when is_atom(Data) ->
+ atom_to_binary(Data, utf8);
+to_bin(Data) when is_integer(Data) ->
+ integer_to_binary(Data);
+to_bin(Data) when is_binary(Data) ->
+ Data.
+
+val(Data) ->
+ {value, V} = lists:keyfind(value, 1, Data),
+ V.
+
+val(Key, Stats) ->
+ {Key, Data} = lists:keyfind(Key, 1, Stats),
+ val(Data).
\ No newline at end of file
diff --git a/src/couch_prometheus/test/eunit/couch_prometheus_util_tests.erl b/src/couch_prometheus/test/eunit/couch_prometheus_util_tests.erl
new file mode 100644
index 0000000..f45a8ec
--- /dev/null
+++ b/src/couch_prometheus/test/eunit/couch_prometheus_util_tests.erl
@@ -0,0 +1,67 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_prometheus_util_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+
+-import(couch_prometheus_util, [
+ to_prom/3,
+ to_prom_summary/2
+]).
+
+couch_prometheus_util_test_() ->
+ [
+ ?_assertEqual(<<"couchdb_ddoc_cache 10">>,
+ test_to_prom_output(ddoc_cache, counter, 10)),
+ ?_assertEqual(<<"couchdb_httpd_status_codes{code=\"200\"} 3">>,
+ test_to_prom_output(httpd_status_codes, counter, {[{code, 200}], 3})),
+ ?_assertEqual(<<"couchdb_temperature_celsius 36">>,
+ test_to_prom_output(temperature_celsius, gauge, 36)),
+ ?_assertEqual(<<"couchdb_mango_query_time_seconds{quantile=\"0.75\"} 4.5">>,
+ test_to_prom_sum_output([mango_query_time], [
+ {value,
+ [
+ {min,0.0},
+ {max,0.0},
+ {arithmetic_mean,0.0},
+ {geometric_mean,0.0},
+ {harmonic_mean,0.0},
+ {median,0.0},{variance,0.0},
+ {standard_deviation,0.0},
+ {skewness,0.0},{kurtosis,0.0},
+ {percentile,[
+ {50,0.0},
+ {75, 4500},
+ {90,0.0},
+ {95,0.0},
+ {99,0.0},
+ {999,0.0}]},
+ {histogram,[
+ {0,0}]},
+ {n,0}
+ ]
+ },
+ {type,histogram},
+ {desc, <<"length of time processing a mango query">>}
+ ]))
+ ].
+
+test_to_prom_output(Metric, Type, Val) ->
+ Out = to_prom(Metric, Type, Val),
+ lists:nth(2, Out).
+
+
+test_to_prom_sum_output(Metric, Info) ->
+ Out = to_prom_summary(Metric, Info),
+ ?debugMsg(Out),
+ lists:nth(3, Out).
\ No newline at end of file