diff --git a/src/metrics/prometheus_histogram.erl b/src/metrics/prometheus_histogram.erl
index 692f693a..7bcad791 100644
--- a/src/metrics/prometheus_histogram.erl
+++ b/src/metrics/prometheus_histogram.erl
@@ -29,6 +29,14 @@
%% [Method], Time).
%%
%%
+%%
+%% The `prometheus_histogram:observe_n/3,4,5' adds limited support for the
+%% "weighted" histograms. It accepts the extra integer argument "Count" to
+%% update the number of observations in the bucket by adding that number.
+%% This allows for better accuracy in the case of irregular measurements,
+%% assuming that the "Count" conveys the observation time interval (for
+%% example, the number of time ticks when the recent value was observed).
+%%
%% @end
-module(prometheus_histogram).
@@ -42,6 +50,9 @@
observe/2,
observe/3,
observe/4,
+ observe_n/3,
+ observe_n/4,
+ observe_n/5,
pobserve/6,
observe_duration/2,
observe_duration/3,
@@ -184,38 +195,58 @@ observe(Name, LabelValues, Value) ->
%% @doc Observes the given `Value'.
%%
-%% Raises `{invalid_value, Value, Message}' if `Value'
-%% isn't an integer.
+%% Raises `{invalid_value, Value, Message}' if `Value' isn't a number.
+%% Raises `{unknown_metric, Registry, Name}' error if histogram with named
+%% `Name' can't be found in `Registry'.
+%% Raises `{invalid_metric_arity, Present, Expected}' error if labels count
+%% mismatch.
+%% @end
+observe(Registry, Name, LabelValues, Value) when is_number(Value) ->
+ observe_n(Registry, Name, LabelValues, Value, 1);
+observe(_Registry, _Name, _LabelValues, Value) ->
+ erlang:error({invalid_value, Value, "observe accepts only numbers"}).
+
+%% @equiv observe_n(default, Name, [], Value, Count)
+observe_n(Name, Value, Count) ->
+ observe_n(default, Name, [], Value, Count).
+
+%% @equiv observe_n(default, Name, LabelValues, Value, Count)
+observe_n(Name, LabelValues, Value, Count) ->
+ observe_n(default, Name, LabelValues, Value, Count).
+
+%% @doc Observes the given `Value' `Count' times.
+%%
+%% Raises `{invalid_value, Value, Message}' if `Value' isn't a number.
+%% Raises `{invalid_count, Count, Message}' if `Count' isn't integer.
%% Raises `{unknown_metric, Registry, Name}' error if histogram with named
%% `Name' can't be found in `Registry'.
%% Raises `{invalid_metric_arity, Present, Expected}' error if labels count
%% mismatch.
%% @end
-observe(Registry, Name, LabelValues, Value) when is_integer(Value) ->
+observe_n(Registry, Name, LabelValues, Value, Count) when is_integer(Value), is_integer(Count) ->
Key = key(Registry, Name, LabelValues),
case ets:lookup(?TABLE, Key) of
[Metric] ->
BucketPosition = calculate_histogram_bucket_position(Metric, Value),
ets:update_counter(?TABLE, Key,
- [{?ISUM_POS, Value},
- {?BUCKETS_START + BucketPosition, 1}]);
+ [{?ISUM_POS, Value * Count},
+ {?BUCKETS_START + BucketPosition, Count}]);
[] ->
- insert_metric(Registry, Name, LabelValues, Value, fun observe/4)
+ insert_metric(Registry, Name, LabelValues, Value, Count, fun observe_n/5)
end,
ok;
-observe(Registry, Name, LabelValues, Value) when is_number(Value) ->
+observe_n(Registry, Name, LabelValues, Value, Count) when is_number(Value), is_integer(Count) ->
Key = key(Registry, Name, LabelValues),
case ets:lookup(?TABLE, Key) of
[Metric] ->
- fobserve_impl(Key, Metric, Value);
+ fobserve_impl(Key, Metric, Value, Count);
[] ->
- insert_metric(Registry, Name, LabelValues, Value,
- fun(_, _, _, _) ->
- observe(Registry, Name, LabelValues, Value)
- end)
+ insert_metric(Registry, Name, LabelValues, Value, Count, fun observe_n/5)
end;
-observe(_Registry, _Name, _LabelValues, Value) ->
- erlang:error({invalid_value, Value, "observe accepts only numbers"}).
+observe_n(_Registry, _Name, _LabelValues, Value, Count) when is_number(Value) ->
+ erlang:error({invalid_count, Count, "observe_n accepts only integer counts"});
+observe_n(_Registry, _Name, _LabelValues, Value, _Count) ->
+ erlang:error({invalid_value, Value, "observe_n accepts only number values"}).
%% @private
pobserve(Registry, Name, LabelValues, Buckets, BucketPos, Value) when is_integer(Value) ->
@@ -234,11 +265,11 @@ pobserve(Registry, Name, LabelValues, Buckets, BucketPos, Value) when is_integer
pobserve(Registry, Name, LabelValues, Buckets, BucketPos, Value) when is_number(Value) ->
Key = key(Registry, Name, LabelValues),
case
- fobserve_impl(Key, Buckets, BucketPos, Value) of
+ fobserve_impl(Key, Buckets, BucketPos, Value, 1) of
0 ->
insert_metric(Registry, Name, LabelValues, Value,
fun(_, _, _, _) ->
- fobserve_impl(Key, Buckets, BucketPos, Value)
+ fobserve_impl(Key, Buckets, BucketPos, Value, 1)
end);
1 ->
ok
@@ -431,13 +462,17 @@ insert_metric(Registry, Name, LabelValues, Value, CB) ->
insert_placeholders(Registry, Name, LabelValues),
CB(Registry, Name, LabelValues, Value).
-fobserve_impl(Key, Metric, Value) ->
+insert_metric(Registry, Name, LabelValues, Value, Count, CB) ->
+ insert_placeholders(Registry, Name, LabelValues),
+ CB(Registry, Name, LabelValues, Value, Count).
+
+fobserve_impl(Key, Metric, Value, Count) ->
Buckets = metric_buckets(Metric),
BucketPos = calculate_histogram_bucket_position(Metric, Value),
- fobserve_impl(Key, Buckets, BucketPos, Value).
+ fobserve_impl(Key, Buckets, BucketPos, Value, Count).
-fobserve_impl(Key, Buckets, BucketPos, Value) ->
- ets:select_replace(?TABLE, generate_select_replace(Key, Buckets, BucketPos, Value)).
+fobserve_impl(Key, Buckets, BucketPos, Value, Count) ->
+ ets:select_replace(?TABLE, generate_select_replace(Key, Buckets, BucketPos, Value, Count)).
insert_placeholders(Registry, Name, LabelValues) ->
MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues),
@@ -456,13 +491,13 @@ calculate_histogram_bucket_position(Metric, Value) ->
Buckets = metric_buckets(Metric),
prometheus_buckets:position(Buckets, Value).
-generate_select_replace(Key, Bounds, BucketPos, Value) ->
+generate_select_replace(Key, Bounds, BucketPos, Value, Count) ->
BoundPlaceholders = gen_query_bound_placeholders(Bounds),
HistMatch = list_to_tuple([Key, '$2', '$3', '$4'] ++ BoundPlaceholders),
BucketUpdate = lists:sublist(BoundPlaceholders, BucketPos)
- ++ [{'+', gen_query_placeholder(?BUCKETS_START + BucketPos), 1}]
+ ++ [{'+', gen_query_placeholder(?BUCKETS_START + BucketPos), Count}]
++ lists:nthtail(BucketPos + 1, BoundPlaceholders),
- HistUpdate = list_to_tuple([{Key}, '$2', '$3', {'+', '$4', Value}] ++ BucketUpdate),
+ HistUpdate = list_to_tuple([{Key}, '$2', '$3', {'+', '$4', Value * Count}] ++ BucketUpdate),
[{HistMatch,
[],
[{HistUpdate}]}].
diff --git a/test/eunit/metric/prometheus_histogram_tests.erl b/test/eunit/metric/prometheus_histogram_tests.erl
index 61774284..9594113b 100644
--- a/test/eunit/metric/prometheus_histogram_tests.erl
+++ b/test/eunit/metric/prometheus_histogram_tests.erl
@@ -12,6 +12,7 @@ prometheus_format_test_() ->
fun test_errors/1,
fun test_buckets/1,
fun test_observe/1,
+ fun test_observe_n/0,
fun test_observe_duration_seconds/1,
fun test_observe_duration_milliseconds/1,
fun test_deregister/1,
@@ -56,8 +57,12 @@ test_errors(_) ->
%% mf/arity errors
?_assertError({unknown_metric, default, unknown_metric},
prometheus_histogram:observe(unknown_metric, 1)),
+ ?_assertError({unknown_metric, default, unknown_metric},
+ prometheus_histogram:observe_n(unknown_metric, 1, 1)),
?_assertError({invalid_metric_arity, 2, 1},
prometheus_histogram:observe(db_query_duration, [repo, db], 1)),
+ ?_assertError({invalid_metric_arity, 2, 1},
+ prometheus_histogram:observe_n(db_query_duration, [repo, db], 1, 1)),
?_assertError({unknown_metric, default, unknown_metric},
prometheus_histogram:observe_duration(unknown_metric, fun() -> 1 end)),
?_assertError({invalid_metric_arity, 2, 1},
@@ -100,6 +105,10 @@ test_errors(_) ->
{buckets, [1, 3, 2]}])),
?_assertError({invalid_value, "qwe", "observe accepts only numbers"},
prometheus_histogram:observe(request_duration, "qwe")),
+ ?_assertError({invalid_value, "qwe", "observe_n accepts only number values"},
+ prometheus_histogram:observe_n(request_duration, "qwe", 3)),
+ ?_assertError({invalid_count, "qwe", "observe_n accepts only integer counts"},
+ prometheus_histogram:observe_n(request_duration, 300, "qwe")),
?_assertError({invalid_value, "qwe", "observe_duration accepts only functions"},
prometheus_histogram:observe_duration(pool_size, "qwe"))
].
@@ -167,6 +176,21 @@ test_observe(_) ->
when Sum > 6974.5 andalso Sum < 6974.55, Value),
?_assertEqual({[0, 0, 0, 0, 0, 0], 0}, RValue)].
+test_observe_n() ->
+ prometheus_histogram:new([{name, temp}, {help, "temp"}, {buckets, [10, 20, 30, 40, 50]}]),
+ ?assertEqual({[0, 0, 0, 0, 0, 0], 0}, prometheus_histogram:value(temp)),
+
+ prometheus_histogram:observe_n(temp, 5.5, 2), Sum1 = 5.5 * 2,
+ ?assertEqual({[2, 0, 0, 0, 0, 0], Sum1}, prometheus_histogram:value(temp)),
+
+ prometheus_histogram:observe(temp, 15.5), Sum2 = Sum1 + 15.5,
+ ?assertEqual({[2, 1, 0, 0, 0, 0], Sum2}, prometheus_histogram:value(temp)),
+
+ prometheus_histogram:observe(temp, 1), Sum3 = Sum2 + 1,
+ ?assertEqual({[3, 1, 0, 0, 0, 0], Sum3}, prometheus_histogram:value(temp)),
+
+ ok.
+
test_observe_duration_seconds(_) ->
prometheus_histogram:new([{name, fun_duration_seconds},
{buckets, [0.5, 1.1]},