Current section
Files
Jump to
Current section
Files
src/themis@histogram.erl
-module(themis@histogram).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/themis/histogram.gleam").
-export([new/3, observe/3, print/0]).
-export_type([histogram_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type histogram_error() :: {metric_error, themis@internal@metric:metric_error()} |
{store_error, themis@internal@store:store_error()} |
invalid_bucket_value |
cannot_observe_na_n |
{label_error, themis@internal@label:label_error()}.
-file("src/themis/histogram.gleam", 258).
-spec group_histogram_records(
gleam@dict:dict(themis@internal@label:label_set(), themis@number:number_()),
gleam@dict:dict(themis@internal@label:label_set(), themis@number:number_()),
gleam@dict:dict(themis@internal@label:label_set(), themis@number:number_())
) -> gleam@dict:dict(themis@internal@label:label_set(), {gleam@dict:dict(binary(), themis@number:number_()),
themis@number:number_(),
themis@number:number_()}).
group_histogram_records(Buckets, Sum, Count) ->
gleam@list:fold(
begin
_pipe = Buckets,
maps:to_list(_pipe)
end,
maps:new(),
fun(Result, Bucket_entry) ->
{Bucket_labels, Bucket_value} = Bucket_entry,
{Le_value, Bucket_labels@1} = begin
_pipe@1 = themis@internal@label:to_dict(Bucket_labels),
_pipe@2 = maps:to_list(_pipe@1),
_pipe@3 = gleam@list:key_pop(_pipe@2, <<"le"/utf8>>),
gleam@result:lazy_unwrap(
_pipe@3,
fun() -> erlang:error(#{gleam_error => panic,
message => <<"could not find label `le` for bucket"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"group_histogram_records"/utf8>>,
line => 272}) end
)
end,
Labels = begin
_pipe@4 = Bucket_labels@1,
_pipe@5 = maps:from_list(_pipe@4),
_pipe@6 = themis@internal@label:from_dict(_pipe@5),
gleam@result:lazy_unwrap(
_pipe@6,
fun() -> erlang:error(#{gleam_error => panic,
message => <<"failed to recombine bucket labels after extracting le label"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"group_histogram_records"/utf8>>,
line => 282}) end
)
end,
{Found_buckets, Found_sum, Found_count} = case gleam_stdlib:map_get(
Result,
Labels
) of
{error, _} ->
Record_sum_value = begin
_pipe@7 = gleam_stdlib:map_get(Sum, Labels),
gleam@result:lazy_unwrap(
_pipe@7,
fun() -> erlang:error(#{gleam_error => panic,
message => <<"recombined bucket labels did not match any found sum record"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"group_histogram_records"/utf8>>,
line => 289}) end
)
end,
Record_count_value = begin
_pipe@8 = gleam_stdlib:map_get(Count, Labels),
gleam@result:lazy_unwrap(
_pipe@8,
fun() -> erlang:error(#{gleam_error => panic,
message => <<"recombined bucket labels did not match any found count record"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"group_histogram_records"/utf8>>,
line => 294}) end
)
end,
{maps:new(), Record_sum_value, Record_count_value};
{ok, Record} ->
Record
end,
New_buckets = gleam@dict:insert(
Found_buckets,
Le_value,
Bucket_value
),
gleam@dict:insert(
Result,
Labels,
{New_buckets, Found_sum, Found_count}
)
end
).
-file("src/themis/histogram.gleam", 304).
-spec buckets_to_list_float(gleam@set:set(themis@number:number_())) -> {ok,
list(float())} |
{error, histogram_error()}.
buckets_to_list_float(Buckets) ->
_pipe@1 = begin
gleam@list:map(
begin
_pipe = Buckets,
gleam@set:to_list(_pipe)
end,
fun(Bucket) -> case Bucket of
{dec, Val} ->
{ok, Val};
{int, Val@1} ->
{ok, erlang:float(Val@1)};
na_n ->
{error, invalid_bucket_value};
pos_inf ->
{error, invalid_bucket_value};
neg_inf ->
{error, invalid_bucket_value}
end end
)
end,
gleam@result:all(_pipe@1).
-file("src/themis/histogram.gleam", 318).
-spec buckets_floats_to_numbers(list(float())) -> list(themis@number:number_()).
buckets_floats_to_numbers(Buckets) ->
_pipe@1 = gleam@list:map(
Buckets,
fun(Bucket) ->
Bucket_decimal@1 = case gleam@float:modulo(Bucket, 1.0) of
{ok, Bucket_decimal} -> Bucket_decimal;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"buckets_floats_to_numbers"/utf8>>,
line => 320,
value => _assert_fail,
start => 10349,
'end' => 10406,
pattern_start => 10360,
pattern_end => 10378})
end,
case Bucket_decimal@1 =:= +0.0 of
false ->
themis@number:decimal(Bucket);
true ->
themis@number:integer(
begin
_pipe = Bucket,
erlang:trunc(_pipe)
end
)
end
end
),
_pipe@2 = lists:append(_pipe@1, [themis@number:positive_infinity()]),
_pipe@3 = gleam@list:sort(_pipe@2, fun themis@number:unsafe_compare/2),
lists:reverse(_pipe@3).
-file("src/themis/histogram.gleam", 33).
?DOC(
" Registers a new histogram metric to the store.\n"
" The \"Buckets\" argument is a set of histogram buckets\n"
" \"le\" values, as described here\n"
" https://prometheus.io/docs/practices/histograms/\n"
" Will return an error if the metric name is invalid,\n"
" already used, or if any of the buckets have an\n"
" invalid value (\"PosInf, NegInf, NaN\")\n"
).
-spec new(binary(), binary(), gleam@set:set(themis@number:number_())) -> {ok,
nil} |
{error, histogram_error()}.
new(Name, Description, Buckets) ->
case themis@internal@metric:new_name(Name, [<<"histogram"/utf8>>]) of
{error, E} ->
{error, {metric_error, E}};
{ok, Metric_name} ->
gleam@result:'try'(
begin
_pipe = Buckets,
buckets_to_list_float(_pipe)
end,
fun(Buckets@1) ->
case themis@internal@store:new_metric(
Metric_name,
Description,
<<"histogram"/utf8>>,
Buckets@1
) of
{error, E@1} ->
{error, {store_error, E@1}};
{ok, _} ->
{ok, nil}
end
end
)
end.
-file("src/themis/histogram.gleam", 62).
?DOC(
" Records a datapoint into the histogram for the given metric name.\n"
" Will return an error if the name is invalid, not a registered metric\n"
" or not of the correct metric type.\n"
" Will return an error if any of the labels have an invalid key.\n"
" Will return an error if the value is number.NaN\n"
).
-spec observe(
binary(),
gleam@dict:dict(binary(), binary()),
themis@number:number_()
) -> {ok, nil} | {error, histogram_error()}.
observe(Name, Labels, Value) ->
gleam@bool:guard(
Value =:= na_n,
{error, cannot_observe_na_n},
fun() ->
gleam@result:'try'(
begin
_pipe = themis@internal@metric:new_name(
Name,
[<<"histogram"/utf8>>]
),
gleam@result:try_recover(
_pipe,
fun(E) -> {error, {metric_error, E}} end
)
end,
fun(Name@1) ->
gleam@result:'try'(
begin
_pipe@1 = themis@internal@label:from_dict(Labels),
gleam@result:map_error(
_pipe@1,
fun(E@1) -> {label_error, E@1} end
)
end,
fun(Labels@1) ->
gleam@result:'try'(
begin
_pipe@2 = themis@internal@store:find_metric(
Name@1,
<<"histogram"/utf8>>
),
gleam@result:map_error(
_pipe@2,
fun(E@2) -> {store_error, E@2} end
)
end,
fun(_use0) ->
{_, _, Buckets} = _use0,
Le_labels = buckets_floats_to_numbers(
Buckets
),
{Bucket_name, Count_name, Sum_name} = themis@internal@metric:make_histogram_metric_names(
Name@1
),
Posinf_labels@1 = case begin
_pipe@3 = Labels@1,
_pipe@4 = themis@internal@label:to_dict(
_pipe@3
),
_pipe@5 = gleam@dict:insert(
_pipe@4,
<<"le"/utf8>>,
<<"+Inf"/utf8>>
),
themis@internal@label:from_dict(_pipe@5)
end of
{ok, Posinf_labels} -> Posinf_labels;
_assert_fail ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"observe"/utf8>>,
line => 96,
value => _assert_fail,
start => 2998,
'end' => 3104,
pattern_start => 3009,
pattern_end => 3026}
)
end,
Must_initialize_records = begin
_pipe@6 = themis@internal@store:find_record(
Bucket_name,
Posinf_labels@1
),
gleam@result:is_error(_pipe@6)
end,
gleam@list:fold_until(
Le_labels,
nil,
fun(_, Bucket) ->
Value_belongs_to_bucket = themis@number:unsafe_compare(
Value,
Bucket
)
/= gt,
{Stop, By} = case {Value_belongs_to_bucket,
Must_initialize_records} of
{true, _} ->
{false,
themis@number:integer(1)};
{false, false} ->
{true,
themis@number:integer(1)};
{false, true} ->
{false,
themis@number:integer(0)}
end,
gleam@bool:guard(
Stop,
{stop, nil},
fun() ->
Labels@3 = case begin
_pipe@9 = gleam@dict:insert(
begin
_pipe@7 = Labels@1,
themis@internal@label:to_dict(
_pipe@7
)
end,
<<"le"/utf8>>,
begin
_pipe@8 = Bucket,
themis@number:print(
_pipe@8
)
end
),
themis@internal@label:from_dict(
_pipe@9
)
end of
{error, _} ->
erlang:error(
#{gleam_error => panic,
message => <<"rebuilding labels after only adding the \"le\" label should work"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"observe"/utf8>>,
line => 120}
);
{ok, Labels@2} ->
Labels@2
end,
case themis@internal@store:increment_record_by(
Bucket_name,
Labels@3,
By
) of
{error, E@3} ->
erlang:error(
#{gleam_error => panic,
message => (<<<<<<"failed to increment a bucket record : \nName: "/utf8,
(begin
_pipe@10 = Bucket_name,
themis@internal@metric:name_to_string(
_pipe@10
)
end)/binary>>/binary,
"\nError: "/utf8>>/binary,
(gleam@string:inspect(
E@3
))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"observe"/utf8>>,
line => 125}
);
{ok, _} ->
{continue, nil}
end
end
)
end
),
case themis@internal@store:increment_record(
Count_name,
Labels@1
) of
{error, E@4} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<"failed to increment a count record : \nName: "/utf8,
(begin
_pipe@11 = Count_name,
themis@internal@metric:name_to_string(
_pipe@11
)
end)/binary>>/binary,
"\nError: "/utf8>>/binary,
(gleam@string:inspect(
E@4
))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"observe"/utf8>>,
line => 137});
{ok, _} ->
nil
end,
case themis@internal@store:increment_record_by(
Sum_name,
Labels@1,
Value
) of
{error, E@5} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<"failed to increment a sum record : \nName: "/utf8,
(begin
_pipe@12 = Sum_name,
themis@internal@metric:name_to_string(
_pipe@12
)
end)/binary>>/binary,
"\nError: "/utf8>>/binary,
(gleam@string:inspect(
E@5
))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"observe"/utf8>>,
line => 148});
{ok, _} ->
nil
end,
{ok, nil}
end
)
end
)
end
)
end
).
-file("src/themis/histogram.gleam", 162).
?DOC(
" Formats all histogram metrics in the store \n"
" as a Prometheus-compatible text string.\n"
).
-spec print() -> {ok, binary()} | {error, histogram_error()}.
print() ->
gleam@result:'try'(
begin
_pipe = themis@internal@store:match_metrics(<<"histogram"/utf8>>),
gleam@result:try_recover(
_pipe,
fun(E) -> {error, {store_error, E}} end
)
end,
fun(Metrics) ->
R = begin
gleam@list:try_fold(
Metrics,
[],
fun(Metrics_strings, _use1) ->
{Name_string, Description, _} = _use1,
gleam@result:'try'(
begin
_pipe@1 = themis@internal@metric:new_name(
Name_string,
[<<"histogram"/utf8>>]
),
gleam@result:try_recover(
_pipe@1,
fun(E@1) -> {error, {metric_error, E@1}} end
)
end,
fun(Name) ->
{Buckets_name, Count_name, Sum_name} = themis@internal@metric:make_histogram_metric_names(
Name
),
gleam@result:'try'(
begin
_pipe@2 = themis@internal@store:match_records(
Buckets_name
),
gleam@result:try_recover(
_pipe@2,
fun(E@2) ->
{error, {store_error, E@2}}
end
)
end,
fun(Bucket_records) ->
gleam@result:'try'(
begin
_pipe@3 = themis@internal@store:match_records(
Sum_name
),
gleam@result:try_recover(
_pipe@3,
fun(E@3) ->
{error,
{store_error, E@3}}
end
)
end,
fun(Sum_records) ->
gleam@result:'try'(
begin
_pipe@4 = themis@internal@store:match_records(
Count_name
),
gleam@result:try_recover(
_pipe@4,
fun(E@4) ->
{error,
{store_error,
E@4}}
end
)
end,
fun(Count_records) ->
Records = group_histogram_records(
Bucket_records,
Sum_records,
Count_records
),
Help_string = <<<<<<<<"# HELP "/utf8,
Name_string/binary>>/binary,
" "/utf8>>/binary,
Description/binary>>/binary,
"\n"/utf8>>,
Type_string = <<<<<<"# TYPE "/utf8,
Name_string/binary>>/binary,
" "/utf8>>/binary,
"histogram\n"/utf8>>,
Records_strings = begin
gleam@list:fold(
maps:to_list(
Records
),
[],
fun(
Lines,
_use1@1
) ->
{Labels,
{Buckets,
Sum,
Count}} = _use1@1,
Sum_line = <<<<<<<<(begin
_pipe@5 = Sum_name,
themis@internal@metric:name_to_string(
_pipe@5
)
end)/binary,
(themis@internal@label:print(
Labels
))/binary>>/binary,
" "/utf8>>/binary,
(themis@number:print(
Sum
))/binary>>/binary,
"\n"/utf8>>,
Count_line = <<<<<<<<(begin
_pipe@6 = Count_name,
themis@internal@metric:name_to_string(
_pipe@6
)
end)/binary,
(themis@internal@label:print(
Labels
))/binary>>/binary,
" "/utf8>>/binary,
(themis@number:print(
Count
))/binary>>/binary,
"\n"/utf8>>,
Bucket_lines = begin
gleam@list:map(
maps:to_list(
Buckets
),
fun(
_use0
) ->
{Bucket_label,
Bucket_value} = _use0,
Bucket_labels = begin
_pipe@7 = themis@internal@label:add_label(
Labels,
<<"le"/utf8>>,
Bucket_label
),
gleam@result:lazy_unwrap(
_pipe@7,
fun(
) ->
erlang:error(
#{gleam_error => panic,
message => (<<"bucket label should be `le` and should not throw an error"/utf8>>),
file => <<?FILEPATH/utf8>>,
module => <<"themis/histogram"/utf8>>,
function => <<"print"/utf8>>,
line => 221}
)
end
)
end,
<<<<<<<<(begin
_pipe@8 = Buckets_name,
themis@internal@metric:name_to_string(
_pipe@8
)
end)/binary,
(themis@internal@label:print(
Bucket_labels
))/binary>>/binary,
" "/utf8>>/binary,
(themis@number:print(
Bucket_value
))/binary>>/binary,
"\n"/utf8>>
end
)
end,
_pipe@9 = [<<"\n"/utf8>>,
Count_line,
Sum_line |
Bucket_lines],
lists:append(
_pipe@9,
Lines
)
end
)
end,
{ok,
[<<"\n"/utf8>>,
Type_string,
Help_string |
lists:append(
Records_strings,
Metrics_strings
)]}
end
)
end
)
end
)
end
)
end
)
end,
gleam@result:map(
R,
fun(Metrics_strings@1) -> _pipe@10 = Metrics_strings@1,
_pipe@11 = gleam_stdlib:identity(_pipe@10),
unicode:characters_to_binary(_pipe@11) end
)
end
).