-module(prometheus_metric). -export([insert_new_mf/5, insert_new_mf/6, insert_mf/5, insert_mf/6, deregister_mf/2, check_mf_exists/4, mf_data/1, metrics/2, extract_common_params/1, extract_key_or_raise_missing/2]). -ifdef(TEST). -export([validate_metric_name/1, validate_metric_label_names/1, validate_metric_help/1]). -endif. -include("prometheus.hrl"). -callback new(Info :: list()) -> ok. -callback new(Info :: list(), Registry :: atom) -> ok. -callback declare(Info :: list()) -> boolean(). -callback declare(Info :: list(), Registry :: atom) -> boolean(). -callback reset(Name :: atom) -> ok. -callback reset(Name :: atom, LValues :: list()) -> ok. -callback reset(Registry :: atom, Name :: atom, LValues :: list()) -> ok. -callback value(Name :: atom) -> ok. -callback value(Name :: atom, LValues :: list()) -> ok. -callback value(Registry :: atom, Name :: atom, LValues :: list()) -> ok. insert_new_mf(Table, Registry, Name, Labels, Help) -> insert_new_mf(Table, Registry, Name, Labels, Help, undefined). insert_new_mf(Table, Registry, Name, Labels, Help, Data) -> case ets:insert_new(Table, {{Registry, mf, Name}, Labels, Help, Data}) of true -> true; false -> erlang:error({mf_already_exists, {Registry, Name}, "maybe you could try declare?"}) end. insert_mf(Table, Registry, Name, Labels, Help) -> insert_mf(Table, Registry, Name, Labels, Help, undefined). insert_mf(Table, Registry, Name, Labels, Help, Data) -> ets:insert_new(Table, {{Registry, mf, Name}, Labels, Help, Data}). deregister_mf(Table, Registry) -> ets:match_delete(Table, {{Registry, mf, '_'}, '_', '_', '_'}). check_mf_exists(Table, Registry, Name, LabelValues) -> case ets:lookup(Table, {Registry, mf, Name}) of [] -> erlang:error({unknown_metric, Registry, Name}); [{_, Labels, _, _} = MF] -> LVLength = length(LabelValues), case length(Labels) of LVLength -> MF; LabelsLength -> erlang:error({invalid_metric_arity}, LabelsLength, LabelValues) end end. mf_data(MF) -> element(4, MF). metrics(Table, Registry) -> ets:match(Table, {{Registry, mf, '$1'}, '$2', '$3', '$4'}). extract_common_params(Spec) -> RawName = extract_key_or_raise_missing(name, Spec), Name = validate_metric_name(RawName), RawLabels = extract_key_or_default(labels, Spec, []), Labels = validate_metric_label_names(RawLabels), RawHelp = extract_key_or_raise_missing(help, Spec), Help = validate_metric_help(RawHelp), {Name, Labels, Help}. extract_key_or_raise_missing(Key, Spec) -> case proplists:get_value(Key, Spec) of undefined -> erlang:error({missing_metric_spec_key, Key, Spec}); RawValue -> RawValue end. extract_key_or_default(Key, Spec, Default) -> proplists:get_value(Key, Spec, Default). validate_metric_name(RawName) when is_atom(RawName) -> validate_metric_name(atom_to_list(RawName)); validate_metric_name(RawName) when is_binary(RawName) -> validate_metric_name(binary_to_list(RawName)); validate_metric_name(RawName) when is_list(RawName) -> case io_lib:printable_unicode_list(RawName) of true -> case re:run(RawName, "^[a-zA-Z_:][a-zA-Z0-9_:]*$") of {match, _} -> list_to_atom(RawName); nomatch -> erlang:error({invalid_metric_name, RawName, "metric name doesn't match regex [a-zA-Z_:][a-zA-Z0-9_:]*"}) end; false -> erlang:error({invalid_metric_name, RawName, "metric name is invalid string"}) end; validate_metric_name(RawName) -> erlang:error({invalid_metric_name, RawName, "metric name is not a string"}). validate_metric_label_names(RawLabels) when is_list(RawLabels) -> lists:map(fun validate_metric_label_name/1, 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) -> case re:run(RawName, "^[a-zA-Z_][a-zA-Z0-9_]*$") of {match, _} -> RawName; nomatch -> erlang:error({invalid_metric_label_name, RawName, "metric label doesn't match regex [a-zA-Z_][a-zA-Z0-9_]*"}) end. validate_metric_help(RawHelp) when is_binary(RawHelp) -> 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"}).