Current section

Files

Jump to
prometheus src prometheus_metric_spec.erl
Raw

src/prometheus_metric_spec.erl

-module(prometheus_metric_spec).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-export([
add_value/3,
get_value/2,
get_value/3,
fetch_value/2,
registry/1,
name/1,
labels/1,
help/1,
constant_labels/1,
duration_unit/1,
extract_common_params/1
]).
-ifdef(TEST).
-export([
validate_metric_name/1,
validate_metric_label_names/1,
validate_metric_help/1
]).
-endif.
?DOC(false).
-spec registry(Spec :: prometheus_metric:spec()) -> prometheus_registry:registry().
registry(Spec) ->
get_value(registry, Spec, default).
?DOC(false).
-spec name(Spec :: prometheus_metric:spec()) -> prometheus_metric:name().
name(Spec) ->
Name = fetch_value(name, Spec),
validate_metric_name(Name).
?DOC(false).
-spec labels(Spec :: prometheus_metric:spec()) -> prometheus_metric:labels().
labels(Spec) ->
Labels = get_value(labels, Spec, []),
validate_metric_label_names(Labels).
?DOC(false).
-spec help(Spec :: prometheus_metric:spec()) -> prometheus_metric:help().
help(Spec) ->
Help = fetch_value(help, Spec),
validate_metric_help(Help).
?DOC(false).
-spec data(Spec :: prometheus_metric:spec()) -> dynamic().
data(Spec) ->
get_value(data, Spec).
?DOC(false).
-spec constant_labels(Spec :: prometheus_metric:spec()) -> [{atom(), term()}].
constant_labels(Spec) ->
case get_value(constant_labels, Spec, #{}) of
CL when is_map(CL) ->
validate_metric_label_names(maps:keys(CL)),
maps:to_list(CL);
CL ->
erlang:error({invalid_value, CL, "constant labels is not a map"})
end.
?DOC(false).
-spec duration_unit(Spec :: prometheus_metric:spec()) -> prometheus_time:maybe_duration_unit().
duration_unit(Spec) ->
Name = to_string(name(Spec)),
NameDU = prometheus_time:duration_unit_from_string(Name),
case duration_unit_from_spec(Spec) of
false ->
undefined;
undefined ->
NameDU;
NameDU ->
NameDU;
DU ->
case NameDU of
undefined -> DU;
_ -> erlang:error({invalid_value, DU, "duration unit doesn't match metric name"})
end
end.
duration_unit_from_spec(Spec) ->
SDU = get_value(duration_unit, Spec, undefined),
prometheus_time:validate_duration_unit(SDU).
?DOC(false).
-spec extract_common_params(Spec :: prometheus_metric:spec()) -> Return when
Return :: {Registry, Name, Labels, Help, CallTimeout, DurationUnit, Data},
Registry :: prometheus_registry:registry(),
Name :: prometheus_metric:name(),
Labels :: prometheus_metric:labels(),
Help :: prometheus_metric:help(),
CallTimeout :: [{atom(), term()}],
DurationUnit :: prometheus_time:maybe_duration_unit(),
Data :: dynamic().
extract_common_params(Spec) ->
Registry = registry(Spec),
Name = name(Spec),
Labels = labels(Spec),
Help = help(Spec),
Data = data(Spec),
CallTimeout = constant_labels(Spec),
DurationUnit = duration_unit(Spec),
{Registry, Name, Labels, Help, CallTimeout, DurationUnit, Data}.
?DOC(false).
?DOC(#{equiv => get_value(Key, Spec, undefined)}).
-spec add_value(Key :: atom(), Value :: dynamic(), Spec :: prometheus_metric:spec()) -> term().
add_value(Key, Value, Spec) when is_list(Spec) ->
[{Key, Value} | Spec];
add_value(Key, Value, Spec) when is_map(Spec) ->
Spec#{Key => Value}.
?DOC(false).
?DOC(#{equiv => get_value(Key, Spec, undefined)}).
-spec get_value(Key :: atom(), Spec :: prometheus_metric:spec()) -> term().
get_value(Key, Spec) ->
get_value(Key, Spec, undefined).
?DOC(false).
-spec get_value(Key :: atom(), Spec :: prometheus_metric:spec(), Default :: dynamic()) -> dynamic().
get_value(Key, Spec, Default) when is_list(Spec) ->
proplists:get_value(Key, Spec, Default);
get_value(Key, Spec, Default) when is_map(Spec) ->
maps:get(Key, Spec, Default).
?DOC(false).
-spec fetch_value(Key :: atom(), Spec :: prometheus_metric:spec()) -> dynamic() | no_return().
fetch_value(Key, Spec) when is_list(Spec) ->
case proplists:get_value(Key, Spec) of
undefined ->
erlang:error({missing_metric_spec_key, Key, Spec});
Value ->
Value
end;
fetch_value(Key, Spec) ->
case maps:get(Key, Spec, undefined) of
undefined ->
erlang:error({missing_metric_spec_key, Key, Spec});
Value ->
Value
end.
%%====================================================================
%% Private Parts
%%===================================================================
-spec validate_metric_name
(atom()) -> atom();
(string()) -> string();
(binary()) -> binary().
validate_metric_name(RawName) when is_atom(RawName) ->
validate_metric_name(RawName, atom_to_list(RawName));
validate_metric_name(RawName) when is_binary(RawName) ->
validate_metric_name(RawName, binary_to_list(RawName));
validate_metric_name(RawName) when is_list(RawName) ->
validate_metric_name(RawName, RawName);
validate_metric_name(RawName) ->
erlang:error({invalid_metric_name, RawName, "metric name is not a string"}).
-spec validate_metric_name
(atom(), list()) -> atom();
(string(), list()) -> string();
(binary(), list()) -> binary().
validate_metric_name(RawName, ListName) ->
case io_lib:printable_unicode_list(ListName) of
true ->
Regex = "^[a-zA-Z_:][a-zA-Z0-9_:]*$",
case re:run(ListName, Regex) of
{match, _} ->
RawName;
nomatch ->
erlang:error(
{invalid_metric_name, RawName, "metric name doesn't match regex " ++ Regex}
)
end;
false ->
erlang:error({invalid_metric_name, RawName, "metric name is invalid string"})
end.
-spec validate_metric_label_names(list()) -> prometheus_metric:labels().
validate_metric_label_names(RawLabels) when is_list(RawLabels) ->
lists:foreach(fun validate_metric_label_name/1, RawLabels),
RawLabels;
validate_metric_label_names(RawLabels) ->
erlang:error({invalid_metric_labels, RawLabels, "not list"}).
validate_metric_label_name(RawName) when is_atom(RawName) ->
validate_metric_label_name(atom_to_list(RawName));
validate_metric_label_name(RawName) when is_binary(RawName) ->
validate_metric_label_name(binary_to_list(RawName));
validate_metric_label_name(RawName) when is_list(RawName) ->
case io_lib:printable_unicode_list(RawName) of
true ->
validate_metric_label_name_content(RawName);
false ->
erlang:error({invalid_metric_label_name, RawName, "metric label is invalid string"})
end;
validate_metric_label_name(RawName) ->
erlang:error({invalid_metric_label_name, RawName, "metric label is not a string"}).
validate_metric_label_name_content("__" ++ _Rest = RawName) ->
erlang:error({invalid_metric_label_name, RawName, "metric label can't start with __"});
validate_metric_label_name_content(RawName) ->
Regex = "^[a-zA-Z_][a-zA-Z0-9_]*$",
case re:run(RawName, Regex) of
{match, _} ->
RawName;
nomatch ->
erlang:error(
{invalid_metric_label_name, RawName, "metric label doesn't match regex " ++ Regex}
)
end.
-spec validate_metric_help(binary() | string()) -> binary() | string().
validate_metric_help(RawHelp) when is_binary(RawHelp) ->
iolist_to_binary(validate_metric_help(binary_to_list(RawHelp)));
validate_metric_help(RawHelp) when is_list(RawHelp) ->
case io_lib:printable_unicode_list(RawHelp) of
true -> RawHelp;
false -> erlang:error({invalid_metric_help, RawHelp, "metric help is invalid string"})
end;
validate_metric_help(RawHelp) ->
erlang:error({invalid_metric_help, RawHelp, "metric help is not a string"}).
to_string(Value) when is_atom(Value) ->
atom_to_list(Value);
to_string(Value) when is_binary(Value) ->
binary_to_list(Value);
to_string(Value) when is_list(Value) ->
Value.