diff --git a/rebar.config b/rebar.config index ce38c206..fcb7185b 100644 --- a/rebar.config +++ b/rebar.config @@ -20,7 +20,7 @@ ]}. {deps, [ - {quantile_estimator, "1.0.2"} + {ddskerl, "0.4.1"} ]}. {shell, [{apps, [prometheus]}]}. @@ -36,13 +36,14 @@ {dialyzer, [ {warnings, [no_return, error_handling, unknown]}, - {plt_extra_apps, [mnesia, quantile_estimator]} + {plt_extra_apps, [mnesia]} ]}. {profiles, [ {test, [ {erl_opts, [nowarn_missing_spec]}, {eunit_compile_opts, [{src_dirs, ["src", "test/eunit"]}]}, + {eunit_opts, [verbose]}, {covertool, [{coverdata_files, ["ct.coverdata"]}]}, {cover_opts, [verbose, {min_coverage, 95}]}, {cover_enabled, true}, @@ -129,7 +130,8 @@ ] }}, {elvis_style, god_modules, #{limit => 40}}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 15}} + {elvis_style, dont_repeat_yourself, #{min_complexity => 15}}, + {elvis_style, no_catch_expressions, disable} ], ruleset => erl_files }, diff --git a/rebar.lock b/rebar.lock index ebf7d6f8..a10c2286 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,8 +1,8 @@ {"1.2.0", -[{<<"quantile_estimator">>,{pkg,<<"quantile_estimator">>,<<"1.0.2">>},0}]}. +[{<<"ddskerl">>,{pkg,<<"ddskerl">>,<<"0.4.1">>},0}]}. [ {pkg_hash,[ - {<<"quantile_estimator">>, <<"ECD281D40110FDD9BA62685531E4435E0839A52FD1058DA5564F1763E4642EF7">>}]}, + {<<"ddskerl">>, <<"F0329E1F8FD3C6209F1645B540F6D4FC14C60B897B05E65B8A4122A362EF019A">>}]}, {pkg_hash_ext,[ - {<<"quantile_estimator">>, <<"DB404793D6384995A1AC6DD973E2CEE5BE9FCC128765BDBA53D87C564E296B64">>}]} + {<<"ddskerl">>, <<"0B68A1E53AF4CCF5F43D70F8EABA1F031388B3FCBD6405FA0CDF849FB47F1811">>}]} ]. diff --git a/src/metrics/prometheus_quantile_summary.erl b/src/metrics/prometheus_quantile_summary.erl index ad983db9..58305a35 100644 --- a/src/metrics/prometheus_quantile_summary.erl +++ b/src/metrics/prometheus_quantile_summary.erl @@ -45,33 +45,18 @@ request_size_bytes\{quantile=\"0.95\"\} ``` ### Configuration -The specs cannot have a key called `quantile`, as this key is reserved by the implementation. - -Passing `targets`, the quantile estimator can be fine-tuned, as in -`quantile_estimator:f_targeted/1`. See `default_targets/0` for the default values. - -Passing `compress_limit` we can configure how often to run compressions into the quantile summary -algorithm. - -### Notes - -The underlying algorithm implemented in `m:quantile_estimator` does not support mergeability of -summaries, therefore a summary in this implementation is linear, that is, all updates happen on the -same ets record. Because of this, race conditions can imply datapoint loss. -Statistically, this might not be relevant, but such impact is not ensured. - -Because of this, support for quantile summaries is rather experimental -and histograms are recommended instead. +See `t:ddskerl_ets:opts/0` for configuration options. These need to be passed as a proplist. """). +-define(WIDTH, 16). + %%% metric -export([ new/1, declare/1, - default_targets/0, + set_default/2, deregister/1, deregister/2, - set_default/2, observe/2, observe/3, observe/4, @@ -99,15 +84,10 @@ and histograms are recommended instead. -include("prometheus.hrl"). --include_lib("quantile_estimator/include/quantile_estimator.hrl"). - -behaviour(prometheus_metric). -behaviour(prometheus_collector). -define(TABLE, ?PROMETHEUS_QUANTILE_SUMMARY_TABLE). --define(SUM_POS, 3). --define(COUNTER_POS, 2). --define(QUANTILE_POS, 4). ?DOC(""" Creates a summary using `Spec`. @@ -142,6 +122,13 @@ declare(Spec) -> Spec1 = validate_summary_spec(Spec), prometheus_metric:insert_mf(?TABLE, ?MODULE, Spec1). +?DOC(false). +-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean(). +set_default(Registry, Name) -> + #{error := Error, bound := Bound} = get_configuration(Registry, Name), + Key = key(Registry, Name, []), + ddskerl_ets:new(?TABLE, Key, Error, Bound). + ?DOC(#{equiv => deregister(default, Name)}). -spec deregister(prometheus_metric:name()) -> {boolean(), boolean()}. deregister(Name) -> @@ -161,15 +148,6 @@ deregister(Registry, Name) -> NumDeleted = ets:select_delete(?TABLE, deregister_select(Registry, Name)), {MFR, NumDeleted > 0}. -?DOC(false). --spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean(). -set_default(Registry, Name) -> - Configuration = get_configuration(Registry, Name), - #{compress_limit := CompressLimit} = Configuration, - Key = key(Registry, Name, []), - Quantile = new_quantile(Configuration), - ets:insert_new(?TABLE, {Key, 0, 0, Quantile, CompressLimit}). - ?DOC(#{equiv => observe(default, Name, [], Value)}). -spec observe(prometheus_metric:name(), number()) -> ok. observe(Name, Value) -> @@ -195,13 +173,12 @@ Raises: Value :: number(). observe(Registry, Name, LabelValues, Value) when is_number(Value) -> Key = key(Registry, Name, LabelValues), - case ets:lookup(?TABLE, Key) of - [] -> - insert_metric(Registry, Name, LabelValues, Value, fun observe/4); - [{Key, Count, S, Q, CompressLimit}] -> - Quantile = quantile_add(Q, Value, CompressLimit), - Elem = {Key, Count + 1, S + Value, Quantile, CompressLimit}, - ets:insert(?TABLE, Elem) + case ets:member(?TABLE, Key) of + true -> + ddskerl_ets:insert(?TABLE, Key, Value); + false -> + insert_metric(Registry, Name, LabelValues, Key), + observe(Registry, Name, LabelValues, Value) end, ok; observe(_Registry, _Name, _LabelValues, Value) -> @@ -266,14 +243,11 @@ Raises: LabelValues :: prometheus_metric:label_values(). remove(Registry, Name, LabelValues) -> prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues), - case - lists:flatten([ - ets:take( - ?TABLE, - {Registry, Name, LabelValues} - ) - ]) - of + List = lists:flatten([ + ets:take(?TABLE, {Registry, Name, LabelValues, SId}) + || SId <- schedulers_seq() + ]), + case List of [] -> false; _ -> true end. @@ -300,30 +274,22 @@ Raises: Name :: prometheus_metric:name(), LabelValues :: prometheus_metric:label_values(). reset(Registry, Name, LabelValues) -> - MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues), - Configuration = prometheus_metric:mf_data(MF), - case - lists:usort([ - ets:update_element( - ?TABLE, - {Registry, Name, LabelValues}, - [{?COUNTER_POS, 0}, {?SUM_POS, 0}, {?QUANTILE_POS, new_quantile(Configuration)}] - ) - ]) - of - [_, _] -> true; - [true] -> true; - _ -> false - end. + _ = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues), + [ + catch ddskerl_ets:reset(?TABLE, {Registry, Name, LabelValues, SId}) + || SId <- schedulers_seq() + ], + true. ?DOC(#{equiv => value(default, Name, [])}). --spec value(prometheus_metric:name()) -> {integer(), number()} | undefined. +-spec value(prometheus_metric:name()) -> + {non_neg_integer(), number(), [{float(), float()}]} | undefined. value(Name) -> value(default, Name, []). ?DOC(#{equiv => value(default, Name, LabelValues)}). -spec value(prometheus_metric:name(), prometheus_metric:label_values()) -> - {integer(), number()} | undefined. + {non_neg_integer(), number(), [{float(), float()}]} | undefined. value(Name, LabelValues) -> value(default, Name, LabelValues). @@ -338,62 +304,65 @@ Raises: * `{unknown_metric, Registry, Name}` error if summary named `Name` can't be found in `Registry`. * `{invalid_metric_arity, Present, Expected}` error if labels count mismatch. """). --spec value(Registry, Name, LabelValues) -> {integer(), number()} | undefined when +-spec value(Registry, Name, LabelValues) -> Result when Registry :: prometheus_registry:registry(), Name :: prometheus_metric:name(), - LabelValues :: prometheus_metric:label_values(). + LabelValues :: prometheus_metric:label_values(), + Result :: {non_neg_integer(), number(), [{float(), float()}]} | undefined. value(Registry, Name, LabelValues) -> MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues), - DU = prometheus_metric:mf_duration_unit(MF), - #{quantiles := QNs} = prometheus_metric:mf_data(MF), - Spec = [{{{Registry, Name, LabelValues}, '$1', '$2', '$3', '_'}, [], ['$$']}], - case ets:select(?TABLE, Spec) of - [] -> + case + lists:any( + fun(SId) -> ets:member(?TABLE, {Registry, Name, LabelValues, SId}) end, schedulers_seq() + ) + of + false -> undefined; - Values -> - {Count, Sum, QE} = reduce_values(Values), - {Count, prometheus_time:maybe_convert_to_du(DU, Sum), quantile_values(QE, QNs)} + true -> + DU = prometheus_metric:mf_duration_unit(MF), + #{quantiles := QNs} = prometheus_metric:mf_data(MF), + [First | Rest] = lists:flatmap( + fun(SId) -> + ets:lookup(?TABLE, {Registry, Name, LabelValues, SId}) + end, + schedulers_seq() + ), + Total = lists:foldl( + fun(Elem, Acc) -> ddskerl_ets:merge_tuples(Acc, Elem) end, First, Rest + ), + case ddskerl_ets:total_tuple(Total) of + 0 -> + {0, 0, []}; + Count -> + Sum = ddskerl_ets:sum_tuple(Total), + DuSum = prometheus_time:maybe_convert_to_du(DU, Sum), + Values = [ + {QN, + prometheus_time:maybe_convert_to_du( + DU, + ddskerl_ets:quantile_tuple(Total, QN) + )} + || QN <- QNs + ], + {Count, DuSum, Values} + end end. +-spec default_quantiles() -> [float()]. +default_quantiles() -> + [0.5, 0.90, 0.95]. + -spec values(prometheus_registry:registry(), prometheus_metric:name()) -> - [prometheus_model:'Summary'()]. + [{[{atom(), dynamic()}], non_neg_integer(), infinity | number(), [{float(), float()}]}]. values(Registry, Name) -> case prometheus_metric:check_mf_exists(?TABLE, Registry, Name) of false -> []; MF -> - DU = prometheus_metric:mf_duration_unit(MF), Labels = prometheus_metric:mf_labels(MF), - #{quantiles := QNs} = Configuration = prometheus_metric:mf_data(MF), - MFValues = load_all_values(Registry, Name), - Foldl = fun - ([_, 0, _, _], ResAcc) -> - %% Ignore quantile evaluation if no data are provided - ResAcc; - ([L, C, S, QE], ResAcc) -> - {PrevCount, PrevSum, PrevQE} = maps:get( - L, ResAcc, {0, 0, new_quantile(Configuration)} - ), - ResAcc#{L => {PrevCount + C, PrevSum + S, quantile_merge(PrevQE, QE)}} - end, - ReducedMap = lists:foldl( - Foldl, - #{}, - MFValues - ), - ReducedMapList = lists:sort(maps:to_list(ReducedMap)), - Foldr = fun({LabelValues, {Count, Sum, QE}}, Acc) -> - [ - { - lists:zip(Labels, LabelValues), - Count, - prometheus_time:maybe_convert_to_du(DU, Sum), - quantile_values(QE, QNs) - } - | Acc - ] - end, - lists:foldr(Foldr, [], ReducedMapList) + CLabels = prometheus_metric:mf_constant_labels(MF), + Fun = fun value_summary_metric/6, + loop_through_keys(Name, Fun, CLabels, Labels, Registry) end. %%==================================================================== @@ -404,177 +373,126 @@ values(Registry, Name) -> -spec deregister_cleanup(prometheus_registry:registry()) -> ok. deregister_cleanup(Registry) -> prometheus_metric:deregister_mf(?TABLE, Registry), - true = ets:match_delete(?TABLE, {{Registry, '_', '_'}, '_', '_', '_', '_'}), + ets:select_delete(?TABLE, clean_registry_select(Registry)), ok. ?DOC(false). -spec collect_mf(prometheus_registry:registry(), prometheus_collector:collect_mf_callback()) -> ok. collect_mf(Registry, Callback) -> + Metrics = prometheus_metric:metrics(?TABLE, Registry), [ Callback(create_summary(Name, Help, {CLabels, Labels, Registry, DU, Data})) - || [Name, {Labels, Help}, CLabels, DU, Data] <- prometheus_metric:metrics(?TABLE, Registry) + || [Name, {Labels, Help}, CLabels, DU, Data] <- Metrics ], ok. ?DOC(false). -spec collect_metrics(prometheus_metric:name(), tuple()) -> [prometheus_model:'Metric'()]. -collect_metrics(Name, {CLabels, Labels, Registry, DU, Configuration}) -> - #{quantiles := QNs} = Configuration, - MFValues = load_all_values(Registry, Name), - Foldl = fun - ([_, 0, _, _], ResAcc) -> - %% Ignore quantile evaluation if no data are provided - ResAcc; - ([L, C, S, QE], ResAcc) -> - {PrevCount, PrevSum, PrevQE} = maps:get(L, ResAcc, {0, 0, new_quantile(Configuration)}), - ResAcc#{L => {PrevCount + C, PrevSum + S, quantile_merge(PrevQE, QE)}} - end, - ReducedMap = lists:foldl(Foldl, #{}, MFValues), - ReducedMapList = lists:sort(maps:to_list(ReducedMap)), - Foldr = fun({LabelValues, {Count, Sum, QE}}, Acc) -> - [ - prometheus_model_helpers:summary_metric( - CLabels ++ lists:zip(Labels, LabelValues), - Count, - prometheus_time:maybe_convert_to_du(DU, Sum), - quantile_values(QE, QNs) - ) - | Acc - ] - end, - lists:foldr(Foldr, [], ReducedMapList). +collect_metrics(Name, {CLabels, Labels, Registry, _DU, _Configuration}) -> + Fun = fun model_summary_metric/6, + loop_through_keys(Name, Fun, CLabels, Labels, Registry). + +loop_through_keys(Name, Fun, CLabels, Labels, Registry) -> + Sets = sets:new([{version, 2}]), + First = ets:first(?TABLE), + loop_through_keys(Name, Fun, CLabels, Labels, Registry, Sets, [], First). + +loop_through_keys(_, _, _, _, _, _, Acc, '$end_of_table') -> + Acc; +loop_through_keys( + Name, Fun, CLabels, Labels, Registry, Set, Acc, {Registry, Name, LabelValues, _} = CurrentKey +) -> + Key = {Registry, Name, LabelValues}, + case sets:is_element(Key, Set) of + true -> + NextKey = ets:next(?TABLE, CurrentKey), + loop_through_keys(Name, Fun, CLabels, Labels, Registry, Set, Acc, NextKey); + false -> + {Count, Sum, QNs} = value(Registry, Name, LabelValues), + Value = Fun(CLabels, Labels, LabelValues, Count, Sum, QNs), + NewAcc = [Value | Acc], + NewSet = sets:add_element(Key, Set), + NextKey = ets:next(?TABLE, CurrentKey), + loop_through_keys(Name, Fun, CLabels, Labels, Registry, NewSet, NewAcc, NextKey) + end; +loop_through_keys(Name, Fun, CLabels, Labels, Registry, Set, Acc, CurrentKey) -> + NextKey = ets:next(?TABLE, CurrentKey), + loop_through_keys(Name, Fun, CLabels, Labels, Registry, Set, Acc, NextKey). + +model_summary_metric(CLabels, Labels, LabelValues, Count, Sum, QNs) -> + Labs = CLabels ++ lists:zip(Labels, LabelValues), + prometheus_model_helpers:summary_metric(Labs, Count, Sum, QNs). + +value_summary_metric(_CLabels, Labels, LabelValues, Count, Sum, QNs) -> + {lists:zip(Labels, LabelValues), Count, Sum, QNs}. %%==================================================================== %% Private Parts %%==================================================================== +clean_registry_select(Registry) -> + [ + {'$1', + [ + {'is_tuple', {element, 1, '$1'}}, + {'==', Registry, {element, 1, {element, 1, '$1'}}} + ], + [true]} + ]. + deregister_select(Registry, Name) -> - [{{{Registry, Name, '_'}, '_', '_', '_', '_'}, [], [true]}]. + [ + {'$1', + [ + {'is_tuple', {element, 1, '$1'}}, + {'==', Registry, {element, 1, {element, 1, '$1'}}}, + {'==', Name, {element, 2, {element, 1, '$1'}}} + ], + [true]} + ]. validate_summary_spec(Spec) -> - Labels = prometheus_metric_spec:labels(Spec), - validate_summary_labels(Labels), - {Invariant, QNs} = invariant_and_quantiles_from_spec(Spec), - CompressLimit = compress_limit_from_spec(Spec), + QNs = prometheus_metric_spec:get_value(quantiles, Spec, default_quantiles()), + Error = prometheus_metric_spec:get_value(error, Spec, 0.01), + Bound = prometheus_metric_spec:get_value(bound, Spec, 2184), + validate_error(Error), + validate_bound(Bound), Data = #{ + ets_table => ?TABLE, quantiles => QNs, - invariant => Invariant, - compress_limit => CompressLimit + error => Error, + bound => Bound }, prometheus_metric_spec:add_value(data, Data, Spec). -validate_summary_labels(Labels) -> - [raise_error_if_quantile_label_found(Label) || Label <- Labels]. - -raise_error_if_quantile_label_found("quantile") -> - erlang:error( - {invalid_metric_label_name, "quantile", "summary cannot have a label named \"quantile\""} - ); -raise_error_if_quantile_label_found(Label) -> - Label. - -insert_metric(Registry, Name, LabelValues, Value, ConflictCB) -> +insert_metric(Registry, Name, LabelValues, Key) -> MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues), - Configuration = prometheus_metric:mf_data(MF), - #{compress_limit := CompressLimit} = Configuration, - Quantile = insert_into_new_quantile(Configuration, Value), - Elem = {key(Registry, Name, LabelValues), 1, Value, Quantile, CompressLimit}, - case ets:insert_new(?TABLE, Elem) of - %% some sneaky process already inserted - false -> - ConflictCB(Registry, Name, LabelValues, Value); - true -> - ok - end. - -load_all_values(Registry, Name) -> - ets:match(?TABLE, {{Registry, Name, '$1'}, '$2', '$3', '$4', '_'}). + Configuration0 = prometheus_metric:mf_data(MF), + Configuration = Configuration0#{name => Key}, + ddskerl_ets:new(Configuration). get_configuration(Registry, Name) -> MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name), prometheus_metric:mf_data(MF). key(Registry, Name, LabelValues) -> - {Registry, Name, LabelValues}. + X = erlang:system_info(scheduler_id), + Rnd = X band (?WIDTH - 1), + {Registry, Name, LabelValues, Rnd}. -reduce_values(Values) -> - { - lists:sum([C || [C, _, _] <- Values]), - lists:sum([S || [_, S, _] <- Values]), - fold_quantiles([Q || [_C, _S, Q] <- Values]) - }. +schedulers_seq() -> + lists:seq(0, ?WIDTH - 1). create_summary(Name, Help, Data) -> prometheus_model_helpers:create_mf(Name, Help, summary, ?MODULE, Data). -default_compress_limit() -> - 100. - -invariant_and_quantiles_from_spec(Spec) -> - Targets = prometheus_metric_spec:get_value(targets, Spec, default_targets()), - validate_targets(Targets), - {QNs, _} = lists:unzip(Targets), - Invariant = quantile_estimator:f_targeted(Targets), - {Invariant, QNs}. - -compress_limit_from_spec(Spec) -> - prometheus_metric_spec:get_value(compress_limit, Spec, default_compress_limit()). - -validate_targets(Targets) when is_list(Targets) -> - Fun = fun - ({Q, _E}) when not is_float(Q) -> - erlang:error({invalid_targets, "target quantile value should be float"}); - ({_Q, E}) when not is_float(E) -> - erlang:error({invalid_targets, "target error value should be float"}); - ({_, _}) -> - ok; - (_) -> - erlang:error({invalid_targets, "targets should be tuples of quantile and error"}) - end, - lists:foreach(Fun, Targets); -validate_targets(_Targets) -> - erlang:error({invalid_targets, "targets should be a list of tuples"}). - --spec default_targets() -> [{float(), float()}]. -default_targets() -> - [{0.5, 0.02}, {0.9, 0.01}, {0.95, 0.005}]. - -new_quantile(#{invariant := Invariant}) -> - quantile_estimator:new(Invariant). - -insert_into_new_quantile(Configuration, Val) -> - quantile_estimator:insert(Val, new_quantile(Configuration)). - -quantile_add(#quantile_estimator{inserts_since_compression = ISS} = Q, Val, CompressLimit) -> - Q1 = - case ISS > CompressLimit of - true -> quantile_estimator:compress(Q); - false -> Q - end, - quantile_estimator:insert(Val, Q1). - -%% Quantile estimator throws on empty stats -quantile_values(#quantile_estimator{data = []}, _QNs) -> - []; -quantile_values(Q, QNs) -> - [{QN, quantile_estimator:quantile(QN, Q)} || QN <- QNs]. - -fold_quantiles(QList) -> - Fun = fun - (Q, init) -> Q; - (Q1, Q2) -> quantile_merge(Q1, Q2) - end, - lists:foldl(Fun, init, QList). - -quantile_merge(QE1, QE2) -> - #quantile_estimator{samples_count = N1, data = Data1, invariant = Invariant} = QE1, - #quantile_estimator{samples_count = N2, data = Data2} = QE2, - quantile_estimator:compress(#quantile_estimator{ - %% Both these fields will be replaced by compression - data_count = 0, - inserts_since_compression = 0, - samples_count = N1 + N2, - data = Data1 ++ Data2, - invariant = Invariant - }). +validate_error(Error) when is_number(Error), 0.0 < Error, Error < 100.0 -> + ok; +validate_error(Error) -> + erlang:error({invalid_error, Error, "Error should be a percentage point in (0,100)"}). + +validate_bound(Bound) when is_integer(Bound), 0 < Bound -> + ok; +validate_bound(Bound) -> + erlang:error({invalid_bound, Bound, "Bound should be a positive integer"}). diff --git a/src/prometheus.app.src b/src/prometheus.app.src index 764fcb61..33b2dce0 100644 --- a/src/prometheus.app.src +++ b/src/prometheus.app.src @@ -3,7 +3,7 @@ {vsn, git}, {registered, []}, {mod, {prometheus, []}}, - {applications, [kernel, stdlib]}, + {applications, [kernel, stdlib, ddskerl]}, {env, []}, {modules, []}, {description, "Prometheus monitoring system and time series database client in Erlang."}, diff --git a/src/prometheus_buckets.erl b/src/prometheus_buckets.erl index 0caa5eba..7a48d208 100644 --- a/src/prometheus_buckets.erl +++ b/src/prometheus_buckets.erl @@ -9,7 +9,7 @@ -export([new/0, new/1, position/2, default/0]). --export([exponential/3, linear/3]). +-export([exponential/3, linear/3, ddsketch/2]). -type bucket_bound() :: number() | infinity. -type buckets() :: [bucket_bound(), ...]. @@ -29,6 +29,7 @@ You can also specify your own buckets if desired instead. | default | {linear, number(), number(), pos_integer()} | {exponential, number(), number(), pos_integer()} + | {ddsketch, float(), pos_integer()} | buckets(). -export_type([bucket_bound/0, buckets/0, config/0]). @@ -46,6 +47,8 @@ new(undefined) -> erlang:error({no_buckets, undefined}); new(default) -> default() ++ [infinity]; +new({ddsketch, Error, Bound}) -> + ddsketch(Error, Bound) ++ [infinity]; new({linear, Start, Step, Count}) -> linear(Start, Step, Count) ++ [infinity]; new({exponential, Start, Factor, Count}) -> @@ -86,6 +89,29 @@ Please note these buckets are floats and represent seconds so you'll have to use -spec default() -> buckets(). default() -> [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]. +?DOC(""" +Creates preallocated buckets according to the DDSketch algorithm. + +For example, if you measure microseconds and you expect no operation to take more than a day, +for a desired error of 1%, 1260 buckets is sufficient. + +```erlang +3> prometheus_buckets:ddsketch(0.01, 1260). +[1.0, 1.02020202020202, 1.040812162024283, 1.0618386703480058 |...] +``` + +The function raises `{invalid_value, Value, Message}` error if `Error` isn't positive, +or if `Bound` is less than or equals to 1. +"""). +-spec ddsketch(float(), pos_integer()) -> buckets(). +ddsketch(Error, _Bound) when Error < 0; 1 < Error -> + erlang:error({invalid_value, Error, "Buckets error should be a valid percentage point"}); +ddsketch(_Error, Bound) when Bound < 1 -> + erlang:error({invalid_value, Bound, "Buckets count should be positive"}); +ddsketch(Error, Bound) -> + Gamma = (1 + Error) / (1 - Error), + ddsketch(lists:seq(0, Bound), Gamma, []). + ?DOC(""" Creates `Count` buckets, where the lowest bucket has an upper bound of `Start` and each following bucket's upper bound is `Factor` times the previous bucket's upper bound. @@ -139,6 +165,12 @@ position(Buckets, Value) when is_list(Buckets), is_number(Value) -> position(Buckets, Value) when is_tuple(Buckets), 1 < tuple_size(Buckets), is_number(Value) -> find_position_in_tuple(Buckets, Value, 1, tuple_size(Buckets)). +ddsketch([], _, Acc) -> + lists:reverse(Acc); +ddsketch([I | Rest], Gamma, Acc) -> + LowerBound = math:pow(Gamma, I), + ddsketch(Rest, Gamma, [LowerBound | Acc]). + linear(_Current, _Step, 0, Acc) -> lists:reverse(Acc); linear(Current, Step, Count, Acc) -> diff --git a/src/prometheus_metric_spec.erl b/src/prometheus_metric_spec.erl index 0ae9d388..05d48cf7 100644 --- a/src/prometheus_metric_spec.erl +++ b/src/prometheus_metric_spec.erl @@ -115,7 +115,8 @@ extract_common_params(Spec) -> ?DOC(false). ?DOC(#{equiv => get_value(Key, Spec, undefined)}). --spec add_value(Key :: atom(), Value :: dynamic(), Spec :: prometheus_metric:spec()) -> term(). +-spec add_value(Key :: atom(), Value :: dynamic(), Spec :: prometheus_metric:spec()) -> + prometheus_metric:spec(). add_value(Key, Value, Spec) when is_list(Spec) -> [{Key, Value} | Spec]; add_value(Key, Value, Spec) when is_map(Spec) -> @@ -123,7 +124,7 @@ add_value(Key, Value, Spec) when is_map(Spec) -> ?DOC(false). ?DOC(#{equiv => get_value(Key, Spec, undefined)}). --spec get_value(Key :: atom(), Spec :: prometheus_metric:spec()) -> term(). +-spec get_value(Key :: atom(), Spec :: prometheus_metric:spec()) -> dynamic(). get_value(Key, Spec) -> get_value(Key, Spec, undefined). diff --git a/src/prometheus_sup.erl b/src/prometheus_sup.erl index b7798ac9..5c2df2bd 100644 --- a/src/prometheus_sup.erl +++ b/src/prometheus_sup.erl @@ -37,7 +37,7 @@ create_tables() -> {?PROMETHEUS_COUNTER_TABLE, [{write_concurrency, auto}]}, {?PROMETHEUS_GAUGE_TABLE, [{write_concurrency, auto}]}, {?PROMETHEUS_SUMMARY_TABLE, [{write_concurrency, auto}]}, - {?PROMETHEUS_QUANTILE_SUMMARY_TABLE, [{write_concurrency, auto}]}, + {?PROMETHEUS_QUANTILE_SUMMARY_TABLE, [{read_concurrency, true}, {write_concurrency, auto}]}, {?PROMETHEUS_HISTOGRAM_TABLE, [{read_concurrency, true}, {write_concurrency, auto}]}, {?PROMETHEUS_BOOLEAN_TABLE, [{write_concurrency, auto}]} ], diff --git a/src/prometheus_time.erl b/src/prometheus_time.erl index e5eba149..bb6b7a48 100644 --- a/src/prometheus_time.erl +++ b/src/prometheus_time.erl @@ -164,7 +164,7 @@ maybe_convert_to_du(_, undefined) -> maybe_convert_to_du(_, infinity) -> infinity; maybe_convert_to_du(DU, Value) -> - from_native(Value, DU). + from_native(DU, Value). %%==================================================================== %% Private Parts @@ -181,23 +181,23 @@ duration_unit_from_string(_, []) -> from_native(Value) -> erlang:convert_time_unit(trunc(Value), native, nanosecond). --spec from_native(number(), duration_unit()) -> number(). -from_native(Value, microseconds) -> +-spec from_native(duration_unit(), number()) -> number(). +from_native(microseconds, Value) -> Nanoseconds = from_native(Value), Nanoseconds / 1000; -from_native(Value, milliseconds) -> +from_native(milliseconds, Value) -> Nanoseconds = from_native(Value), Nanoseconds / 1000000; -from_native(Value, seconds) -> +from_native(seconds, Value) -> Nanoseconds = from_native(Value), Nanoseconds / 1000000000; -from_native(Value, minutes) -> +from_native(minutes, Value) -> Nanoseconds = from_native(Value), Nanoseconds / 60000000000; -from_native(Value, hours) -> +from_native(hours, Value) -> Nanoseconds = from_native(Value), Nanoseconds / 3600000000000; -from_native(Value, days) -> +from_native(days, Value) -> Nanoseconds = from_native(Value), Nanoseconds / 86400000000000. diff --git a/test/eunit/format/prometheus_protobuf_format_tests.erl b/test/eunit/format/prometheus_protobuf_format_tests.erl index e8be8d88..ce28a557 100644 --- a/test/eunit/format/prometheus_protobuf_format_tests.erl +++ b/test/eunit/format/prometheus_protobuf_format_tests.erl @@ -102,36 +102,30 @@ test_quantile_summary(_) -> ]), prometheus_quantile_summary:observe(orders_quantile_summary, 10), prometheus_quantile_summary:observe(orders_quantile_summary, 15), - + Format = prometheus_protobuf_format:format(), Expected = <<132, 1, 10, 23, 111, 114, 100, 101, 114, 115, 95, 113, 117, 97, 110, 116, 105, 108, 101, 95, 115, 117, 109, 109, 97, 114, 121, 18, 28, 84, 114, 97, 99, 107, 32, 111, 114, 100, 101, 114, 115, 32, 99, 111, 117, 110, 116, 47, 116, 111, 116, 97, 108, 32, 115, 117, 109, 24, 2, 34, 73, 34, 71, 8, 2, 17, 0, 0, 0, 0, 0, 0, 57, 64, 26, 18, 9, 0, 0, 0, 0, - 0, 0, 224, 63, 17, 0, 0, 0, 0, 0, 0, 46, 64, 26, 18, 9, 205, 204, 204, 204, 204, 204, - 236, 63, 17, 0, 0, 0, 0, 0, 0, 46, 64, 26, 18, 9, 102, 102, 102, 102, 102, 102, 238, 63, - 17, 0, 0, 0, 0, 0, 0, 46, 64>>, - ?_assertEqual( - Expected, - prometheus_protobuf_format:format() - ). + 0, 0, 224, 63, 17, 42, 36, 253, 164, 62, 38, 36, 64, 26, 18, 9, 205, 204, 204, 204, 204, + 204, 236, 63, 17, 51, 50, 53, 169, 76, 15, 46, 64, 26, 18, 9, 102, 102, 102, 102, 102, + 102, 238, 63, 17, 51, 50, 53, 169, 76, 15, 46, 64>>, + ?_assertEqual(Expected, Format). test_quantile_dsummary(_) -> prometheus_quantile_summary:new([{name, quantile_dsummary}, {help, "qwe"}]), prometheus_quantile_summary:observe(quantile_dsummary, 1.5), prometheus_quantile_summary:observe(quantile_dsummary, 2.7), - + Format = prometheus_protobuf_format:format(), Expected = <<101, 10, 17, 113, 117, 97, 110, 116, 105, 108, 101, 95, 100, 115, 117, 109, 109, 97, 114, 121, 18, 3, 113, 119, 101, 24, 2, 34, 73, 34, 71, 8, 2, 17, 205, 204, 204, 204, 204, - 204, 16, 64, 26, 18, 9, 0, 0, 0, 0, 0, 0, 224, 63, 17, 154, 153, 153, 153, 153, 153, 5, - 64, 26, 18, 9, 205, 204, 204, 204, 204, 204, 236, 63, 17, 154, 153, 153, 153, 153, 153, - 5, 64, 26, 18, 9, 102, 102, 102, 102, 102, 102, 238, 63, 17, 154, 153, 153, 153, 153, - 153, 5, 64>>, - ?_assertEqual( - Expected, - prometheus_protobuf_format:format() - ). + 204, 16, 64, 26, 18, 9, 0, 0, 0, 0, 0, 0, 224, 63, 17, 162, 167, 157, 142, 179, 27, 248, + 63, 26, 18, 9, 205, 204, 204, 204, 204, 204, 236, 63, 17, 133, 58, 170, 243, 141, 135, + 5, 64, 26, 18, 9, 102, 102, 102, 102, 102, 102, 238, 63, 17, 133, 58, 170, 243, 141, + 135, 5, 64>>, + ?_assertEqual(Expected, Format). test_histogram(_) -> prometheus_histogram:new([ diff --git a/test/eunit/format/prometheus_text_format_tests.erl b/test/eunit/format/prometheus_text_format_tests.erl index 7f139946..6d049452 100644 --- a/test/eunit/format/prometheus_text_format_tests.erl +++ b/test/eunit/format/prometheus_text_format_tests.erl @@ -169,37 +169,38 @@ test_quantile_summary(_) -> ]), prometheus_quantile_summary:observe(orders_quantile_summary, 10), prometheus_quantile_summary:observe(orders_quantile_summary, 15), + Format = prometheus_text_format:format(), ?_assertEqual( << "# TYPE orders_quantile_summary summary\n" "# HELP orders_quantile_summary Track orders count/total sum\n" "orders_quantile_summary_count 2\n" "orders_quantile_summary_sum 25\n" - "orders_quantile_summary{quantile=\"0.5\"} 15\n" - "orders_quantile_summary{quantile=\"0.9\"} 15\n" - "orders_quantile_summary{quantile=\"0.95\"} 15\n" + "orders_quantile_summary{quantile=\"0.5\"} 10.074696689511331\n" + "orders_quantile_summary{quantile=\"0.9\"} 15.029881751769699\n" + "orders_quantile_summary{quantile=\"0.95\"} 15.029881751769699\n" "\n" >>, - prometheus_text_format:format() + Format ). test_quantile_dsummary(_) -> prometheus_quantile_summary:new([{name, quantile_dsummary}, {labels, [host]}, {help, "qwe"}]), prometheus_quantile_summary:observe(quantile_dsummary, [123], 1.5), prometheus_quantile_summary:observe(quantile_dsummary, [123], 2.7), - + Format = prometheus_text_format:format(), ?_assertEqual( << "# TYPE quantile_dsummary summary\n" "# HELP quantile_dsummary qwe\n" "quantile_dsummary_count{host=\"123\"} 2\n" "quantile_dsummary_sum{host=\"123\"} 4.2\n" - "quantile_dsummary{host=\"123\",quantile=\"0.5\"} 2.7\n" - "quantile_dsummary{host=\"123\",quantile=\"0.9\"} 2.7\n" - "quantile_dsummary{host=\"123\",quantile=\"0.95\"} 2.7\n" + "quantile_dsummary{host=\"123\",quantile=\"0.5\"} 1.5067630358630386\n" + "quantile_dsummary{host=\"123\",quantile=\"0.9\"} 2.6911887203526157\n" + "quantile_dsummary{host=\"123\",quantile=\"0.95\"} 2.6911887203526157\n" "\n" >>, - prometheus_text_format:format() + Format ). test_histogram(_) -> diff --git a/test/eunit/metric/prometheus_histogram_tests.erl b/test/eunit/metric/prometheus_histogram_tests.erl index 2b44f518..287b9688 100644 --- a/test/eunit/metric/prometheus_histogram_tests.erl +++ b/test/eunit/metric/prometheus_histogram_tests.erl @@ -242,10 +242,31 @@ test_buckets(_) -> ]), ExpBuckets = prometheus_histogram:buckets("exp_buckets"), + prometheus_histogram:declare([ + {name, "ddsketch_buckets"}, + {help, ""}, + {buckets, {ddsketch, 0.01, 10}} + ]), + DDSketchBuckets = prometheus_histogram:buckets("ddsketch_buckets"), + CustomBuckets = prometheus_histogram:buckets( http_request_duration_milliseconds, [method] ), + ExpectedDDSketchBuckets = [ + 1.0, + 1.02020202020202, + 1.040812162024283, + 1.0618386703480058, + 1.0832899566176624, + 1.105174602205898, + 1.127501361846421, + 1.1502791671362476, + 1.1735171301086968, + 1.1972245468785696, + 1.2214109013609646, + infinity + ], [ ?_assertEqual( prometheus_buckets:default() ++ [infinity], @@ -257,7 +278,8 @@ test_buckets(_) -> ), ?_assertEqual([100, 300, 500, 750, 1000, infinity], CustomBuckets), ?_assertEqual([-15, -10, -5, 0, 5, 10, infinity], LinearBuckets), - ?_assertEqual([100, 120, 144, infinity], ExpBuckets) + ?_assertEqual([100, 120, 144, infinity], ExpBuckets), + ?_assertEqual(ExpectedDDSketchBuckets, DDSketchBuckets) ]. test_observe(_) -> diff --git a/test/eunit/metric/prometheus_quantile_summary_tests.erl b/test/eunit/metric/prometheus_quantile_summary_tests.erl index 7b3d2537..b9d43090 100644 --- a/test/eunit/metric/prometheus_quantile_summary_tests.erl +++ b/test/eunit/metric/prometheus_quantile_summary_tests.erl @@ -14,11 +14,15 @@ prometheus_format_test_() -> fun test_observe/1, fun test_observe_quantiles/1, fun test_observe_configured_quantiles/1, + fun test_observe_configured_quantiles_and_error/1, fun test_observe_duration_seconds/1, fun test_observe_duration_milliseconds/1, fun test_deregister/1, fun test_remove/1, fun test_default_value/1, + fun test_values_when_empty/1, + fun test_values_when_multiple_in_parallel/1, + fun test_values_when_non_existing/1, fun test_values/1, fun test_collector1/1, fun test_collector2/1, @@ -29,13 +33,23 @@ prometheus_format_test_() -> test_merge_logic_when_fetching_value(_) -> Name = ?FUNCTION_NAME, prometheus_quantile_summary:declare( - [{name, Name}, {labels, []}, {help, ""}, {compress_limit, 100}] + [{name, Name}, {labels, []}, {help, ""}, {error, 0.01}, {bound, 2184}] ), - % Observe many values - Fun = fun() -> prometheus_quantile_summary:observe(Name, 1) end, - Monitors = [spawn_monitor(Fun) || _ <- lists:seq(1, 1000)], - collect_monitors(Monitors), - [?_assertMatch({_, _, _}, prometheus_quantile_summary:value(Name))]. + parallel_observe_sequence_of_values(Name), + Value = prometheus_quantile_summary:value(Name), + [ + ?_assertMatch( + {100000, _, [ + {0.5, Q5}, + {0.90, Q90}, + {0.95, Q95} + ]} when + (abs(50 - Q5) =< 1) andalso + (abs(90 - Q90) =< 1) andalso + (abs(95 - Q95) =< 1), + Value + ) + ]. test_registration_as_list(_) -> Name = orders_summary, @@ -78,17 +92,25 @@ test_errors(_) -> prometheus_quantile_summary:new([{name, "qwe"}, {labels, 12}, {help, ""}]) ), ?_assertError( - {invalid_metric_label_name, "quantile", - "summary cannot have a label named \"quantile\""}, + {invalid_metric_help, 12, "metric help is not a string"}, + prometheus_quantile_summary:new([{name, "qwe"}, {help, 12}]) + ), + ?_assertError( + {invalid_bound, 3.141592, "Bound should be a positive integer"}, prometheus_quantile_summary:new([ {name, "qwe"}, - {labels, ["qua", "quantile"]}, + {bound, 3.141592}, {help, ""} ]) ), ?_assertError( - {invalid_metric_help, 12, "metric help is not a string"}, - prometheus_quantile_summary:new([{name, "qwe"}, {help, 12}]) + {invalid_error, 101, "Error should be a percentage point in (0,100)"}, + prometheus_quantile_summary:new([ + {name, "qwe"}, + {error, 101}, + {labels, ["qua", "quantile"]}, + {help, ""} + ]) ), %% mf/arity errors ?_assertError( @@ -164,14 +186,15 @@ test_observe(_) -> prometheus_quantile_summary:reset(orders_summary, [electronics]), RValue = prometheus_quantile_summary:value(orders_summary, [electronics]), [ - ?_assertMatch({4, Sum, _} when Sum > 29.1 andalso Sum < 29.3, Value), - ?_assertMatch({0, 0, _}, RValue) + ?_assertMatch({4, Sum, QNs} when Sum > 29.1 andalso Sum < 29.3 andalso is_list(QNs), Value), + ?_assertMatch({0, 0, []}, RValue) ]. test_observe_quantiles(_) -> prometheus_quantile_summary:new([ {name, orders_summary_q}, {labels, [department]}, + {quantiles, [0.0, 0.5, 0.75, 0.90, 0.95, 0.99, 0.999, 1.0]}, {help, "Track orders quantiles"} ]), [ @@ -183,7 +206,27 @@ test_observe_quantiles(_) -> prometheus_quantile_summary:reset(orders_summary_q, [electronics]), RValue = prometheus_quantile_summary:value(orders_summary_q, [electronics]), [ - ?_assertMatch({100, 5050, [{0.5, 53}, {0.9, 92}, {0.95, 96}]}, Value), + ?_assertMatch( + {100, 5050, [ + {+0.0, Q0}, + {0.5, Q5}, + {0.75, Q75}, + {0.90, Q90}, + {0.95, Q95}, + {0.99, Q99}, + {0.999, Q999}, + {1.0, Q1} + ]} when + (abs(1 - Q0) =< 1) andalso + (abs(50 - Q5) =< 1) andalso + (abs(75 - Q75) =< 1) andalso + (abs(90 - Q90) =< 1) andalso + (abs(95 - Q95) =< 1) andalso + (abs(99 - Q99) =< 1) andalso + (abs(100 - Q999) =< 1) andalso + (abs(100 - Q1) =< 1), + Value + ), ?_assertMatch({0, 0, []}, RValue) ]. @@ -192,7 +235,7 @@ test_observe_configured_quantiles(_) -> {name, orders_summary_q_custom}, {labels, [department]}, {help, "Track orders quantiles"}, - {targets, [{0.5, 0.05}, {0.75, 0.02}]} + {quantiles, [0.5, 0.75]} ]), [ prometheus_quantile_summary:observe(orders_summary_q_custom, [electronics], N) @@ -203,15 +246,46 @@ test_observe_configured_quantiles(_) -> prometheus_quantile_summary:reset(orders_summary_q_custom, [electronics]), RValue = prometheus_quantile_summary:value(orders_summary_q_custom, [electronics]), [ - ?_assertMatch({100, 5050, [{0.5, 55}, {0.75, 78}]}, Value), + ?_assertMatch( + {100, 5050, [{0.5, Q5}, {0.75, Q75}]} when + (abs(50 - Q5) =< 1) andalso + (abs(75 - Q75) =< 1), + Value + ), + ?_assertMatch({0, 0, []}, RValue) + ]. + +test_observe_configured_quantiles_and_error(_) -> + prometheus_quantile_summary:new([ + {name, orders_summary_q_custom}, + {labels, [department]}, + {help, "Track orders quantiles"}, + {quantiles, [0.5, 0.95]}, + {error, 0.005} + ]), + [ + prometheus_quantile_summary:observe(orders_summary_q_custom, [electronics], N) + || N <- lists:seq(1, 100) + ], + + Value = prometheus_quantile_summary:value(orders_summary_q_custom, [electronics]), + prometheus_quantile_summary:reset(orders_summary_q_custom, [electronics]), + RValue = prometheus_quantile_summary:value(orders_summary_q_custom, [electronics]), + [ + ?_assertMatch( + {100, 5050, [{0.5, Q5}, {0.95, Q95}]} when + (abs(50 - Q5) =< 0.5) andalso + (abs(95 - Q95) =< 0.5), + Value + ), ?_assertMatch({0, 0, []}, RValue) ]. test_observe_duration_seconds(_) -> prometheus_quantile_summary:new([ {name, <<"fun_duration_seconds">>}, - {help, ""}, - {duration_unit, seconds} + {duration_unit, seconds}, + {help, ""} ]), prometheus_quantile_summary:observe_duration(<<"fun_duration_seconds">>, fun() -> timer:sleep(1000) @@ -350,7 +424,48 @@ test_default_value(_) -> [ ?_assertEqual(undefined, UndefinedValue), ?_assertMatch([], EmptyMetric), - ?_assertMatch({0, 0, _}, SomethingValue) + ?_assertMatch({0, 0, []}, SomethingValue) + ]. + +test_values_when_empty(_) -> + prometheus_quantile_summary:new([ + {name, orders_summary}, + {labels, [department]}, + {help, "Track orders count/total sum"} + ]), + [ + ?_assertMatch( + [], + lists:sort(prometheus_quantile_summary:values(default, orders_summary)) + ) + ]. + +test_values_when_multiple_in_parallel(_) -> + prometheus_quantile_summary:new([ + {name, orders_summary}, + {labels, []}, + {help, "Track orders count/total sum"} + ]), + parallel_observe_sequence_of_values(orders_summary), + [ + ?_assertMatch( + [ + {[], 100000, 5050000, [ + {0.5, 49.90296094906653}, + {0.9, 89.13032933635913}, + {0.95, 94.64203039019942} + ]} + ], + lists:sort(prometheus_quantile_summary:values(default, orders_summary)) + ) + ]. + +test_values_when_non_existing(_) -> + [ + ?_assertMatch( + [], + lists:sort(prometheus_quantile_summary:values(default, orders_summary)) + ) ]. test_values(_) -> @@ -361,7 +476,6 @@ test_values(_) -> ]), prometheus_quantile_summary:observe(orders_summary, [electronics], 765.5), prometheus_quantile_summary:observe(orders_summary, [groceries], 112.3), - [ ?_assertMatch( [ @@ -486,6 +600,17 @@ test_collector3(_) -> ) ]. +parallel_observe_sequence_of_values(Name) -> + % Observe many values + Fun = fun() -> + [ + prometheus_quantile_summary:observe(Name, N) + || N <- lists:seq(1, 100) + ] + end, + Monitors = [spawn_monitor(Fun) || _ <- lists:seq(1, 1000)], + collect_monitors(Monitors). + collect_monitors([]) -> ok; collect_monitors([{Pid, Ref} | Monitors]) -> diff --git a/test/eunit/prometheus_buckets_tests.erl b/test/eunit/prometheus_buckets_tests.erl index 54887b21..1e1430c8 100644 --- a/test/eunit/prometheus_buckets_tests.erl +++ b/test/eunit/prometheus_buckets_tests.erl @@ -32,6 +32,60 @@ linear_test() -> ?assertEqual([-15, -10, -5, 0, 5, 10], prometheus_buckets:linear(-15, 5, 6)), ?assertEqual([1, 5.5, 10, 14.5, 19, 23.5], prometheus_buckets:linear(1, 4.5, 6)). +ddsketch_errors_test() -> + ?assertError( + {invalid_value, 5, "Buckets error should be a valid percentage point"}, + prometheus_buckets:ddsketch(5, 0) + ), + ?assertError( + {invalid_value, 0, "Buckets count should be positive"}, + prometheus_buckets:ddsketch(0.01, 0) + ). + +ddsketch_test() -> + ?assertEqual( + [ + 1.0, + 1.02020202020202, + 1.040812162024283, + 1.0618386703480058, + 1.0832899566176624, + 1.105174602205898, + 1.127501361846421, + 1.1502791671362476, + 1.1735171301086968, + 1.1972245468785696, + 1.2214109013609646 + ], + prometheus_buckets:ddsketch(0.01, 10) + ), + ?assertEqual( + [ + 1.0, + 1.2222222222222223, + 1.4938271604938274, + 1.825788751714678, + 2.231519585429051, + 2.7274128266355073, + 3.3335045658878424, + 4.074283358307364, + 4.979679660153445, + 6.086275140187544, + 7.438780726895887, + 9.09184311065053, + 11.112252690795092, + 13.581642177638448, + 16.599784883780327, + 20.288625969064846, + 24.797209517745923, + 30.307700521689465, + 37.042745082064904, + 45.274466211412665, + 55.335458702837705 + ], + prometheus_buckets:ddsketch(0.1, 20) + ). + exponential_errors_test() -> ?assertError( {invalid_value, 0, "Buckets count should be positive"}, diff --git a/test/eunit/prometheus_time_tests.erl b/test/eunit/prometheus_time_tests.erl index 94f6ad41..b9cc1de0 100644 --- a/test/eunit/prometheus_time_tests.erl +++ b/test/eunit/prometheus_time_tests.erl @@ -42,28 +42,28 @@ validate_duration_unit_test() -> from_native_test() -> NativeInUS = erlang:convert_time_unit(1, microsecond, native), - ?assertEqual(2.5, prometheus_time:from_native(2.5 * NativeInUS, microseconds)), - ?assertEqual(2.6, prometheus_time:from_native(2.6 * 1000 * NativeInUS, milliseconds)), - ?assertEqual(2.23, prometheus_time:from_native(2.23 * 1000000 * NativeInUS, seconds)), + ?assertEqual(2.5, prometheus_time:from_native(microseconds, 2.5 * NativeInUS)), + ?assertEqual(2.6, prometheus_time:from_native(milliseconds, 2.6 * 1000 * NativeInUS)), + ?assertEqual(2.23, prometheus_time:from_native(seconds, 2.23 * 1000000 * NativeInUS)), ?assertEqual( 3.4, prometheus_time:from_native( - 3.4 * 60 * 1000000 * NativeInUS, - minutes + minutes, + 3.4 * 60 * 1000000 * NativeInUS ) ), ?assertEqual( 0.4, prometheus_time:from_native( - 0.4 * 3600 * 1000000 * NativeInUS, - hours + hours, + 0.4 * 3600 * 1000000 * NativeInUS ) ), ?assertEqual( 0.1, prometheus_time:from_native( - 0.1 * 86400 * 1000000 * NativeInUS, - days + days, + 0.1 * 86400 * 1000000 * NativeInUS ) ).