Merge pull request #12770 from thalesmg/mv-metrics-m-20240321
feat(message validation): implement metrics
This commit is contained in:
commit
cc37030265
|
@ -43,6 +43,7 @@
|
||||||
{emqx_management,4}.
|
{emqx_management,4}.
|
||||||
{emqx_management,5}.
|
{emqx_management,5}.
|
||||||
{emqx_metrics,1}.
|
{emqx_metrics,1}.
|
||||||
|
{emqx_metrics,2}.
|
||||||
{emqx_mgmt_api_plugins,1}.
|
{emqx_mgmt_api_plugins,1}.
|
||||||
{emqx_mgmt_api_plugins,2}.
|
{emqx_mgmt_api_plugins,2}.
|
||||||
{emqx_mgmt_cluster,1}.
|
{emqx_mgmt_cluster,1}.
|
||||||
|
|
|
@ -207,6 +207,10 @@
|
||||||
{counter, 'messages.publish'},
|
{counter, 'messages.publish'},
|
||||||
% Messages dropped due to no subscribers
|
% Messages dropped due to no subscribers
|
||||||
{counter, 'messages.dropped'},
|
{counter, 'messages.dropped'},
|
||||||
|
%% % Messages that failed validations
|
||||||
|
{counter, 'messages.validation_failed'},
|
||||||
|
%% % Messages that passed validations
|
||||||
|
{counter, 'messages.validation_succeeded'},
|
||||||
% QoS2 Messages expired
|
% QoS2 Messages expired
|
||||||
{counter, 'messages.dropped.await_pubrel_timeout'},
|
{counter, 'messages.dropped.await_pubrel_timeout'},
|
||||||
% Messages dropped
|
% Messages dropped
|
||||||
|
@ -712,4 +716,6 @@ reserved_idx('overload_protection.delay.timeout') -> 401;
|
||||||
reserved_idx('overload_protection.hibernation') -> 402;
|
reserved_idx('overload_protection.hibernation') -> 402;
|
||||||
reserved_idx('overload_protection.gc') -> 403;
|
reserved_idx('overload_protection.gc') -> 403;
|
||||||
reserved_idx('overload_protection.new_conn') -> 404;
|
reserved_idx('overload_protection.new_conn') -> 404;
|
||||||
|
reserved_idx('messages.validation_succeeded') -> 405;
|
||||||
|
reserved_idx('messages.validation_failed') -> 406;
|
||||||
reserved_idx(_) -> undefined.
|
reserved_idx(_) -> undefined.
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
introduced_in/0,
|
introduced_in/0,
|
||||||
|
deprecated_since/0,
|
||||||
|
|
||||||
get_metrics/4
|
get_metrics/4
|
||||||
]).
|
]).
|
||||||
|
@ -29,6 +30,9 @@
|
||||||
introduced_in() ->
|
introduced_in() ->
|
||||||
"5.1.0".
|
"5.1.0".
|
||||||
|
|
||||||
|
deprecated_since() ->
|
||||||
|
"5.7.0".
|
||||||
|
|
||||||
-spec get_metrics(
|
-spec get_metrics(
|
||||||
[node()],
|
[node()],
|
||||||
emqx_metrics_worker:handler_name(),
|
emqx_metrics_worker:handler_name(),
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_metrics_proto_v2).
|
||||||
|
|
||||||
|
-behaviour(emqx_bpapi).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
introduced_in/0,
|
||||||
|
|
||||||
|
get_metrics/4,
|
||||||
|
|
||||||
|
%% introduced in v2
|
||||||
|
reset_metrics/4
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include("bpapi.hrl").
|
||||||
|
|
||||||
|
introduced_in() ->
|
||||||
|
"5.7.0".
|
||||||
|
|
||||||
|
-spec get_metrics(
|
||||||
|
[node()],
|
||||||
|
emqx_metrics_worker:handler_name(),
|
||||||
|
emqx_metrics_worker:metric_id(),
|
||||||
|
timeout()
|
||||||
|
) -> emqx_rpc:erpc_multicall(emqx_metrics_worker:metrics()).
|
||||||
|
get_metrics(Nodes, HandlerName, MetricId, Timeout) ->
|
||||||
|
erpc:multicall(Nodes, emqx_metrics_worker, get_metrics, [HandlerName, MetricId], Timeout).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------------------
|
||||||
|
%% Introduced in V2
|
||||||
|
%%--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec reset_metrics(
|
||||||
|
[node()],
|
||||||
|
emqx_metrics_worker:handler_name(),
|
||||||
|
emqx_metrics_worker:metric_id(),
|
||||||
|
timeout()
|
||||||
|
) -> emqx_rpc:erpc_multicall(emqx_metrics_worker:metrics()).
|
||||||
|
reset_metrics(Nodes, HandlerName, MetricId, Timeout) ->
|
||||||
|
erpc:multicall(Nodes, emqx_metrics_worker, reset_metrics, [HandlerName, MetricId], Timeout).
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
-export([conf_get/2, conf_get/3, keys/2, filter/1]).
|
-export([conf_get/2, conf_get/3, keys/2, filter/1]).
|
||||||
-export([upgrade_raw_conf/1]).
|
-export([upgrade_raw_conf/1]).
|
||||||
|
-export([tr_prometheus_collectors/1]).
|
||||||
|
|
||||||
%% internal exports for `emqx_enterprise_schema' only.
|
%% internal exports for `emqx_enterprise_schema' only.
|
||||||
-export([ensure_unicode_path/2, convert_rotation/2, log_handler_common_confs/2]).
|
-export([ensure_unicode_path/2, convert_rotation/2, log_handler_common_confs/2]).
|
||||||
|
|
|
@ -65,6 +65,8 @@
|
||||||
%, received_bytes
|
%, received_bytes
|
||||||
sent,
|
sent,
|
||||||
%, sent_bytes
|
%, sent_bytes
|
||||||
|
validation_succeeded,
|
||||||
|
validation_failed,
|
||||||
dropped
|
dropped
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -83,6 +85,8 @@
|
||||||
%received_bytes => received_bytes_rate,
|
%received_bytes => received_bytes_rate,
|
||||||
%sent_bytes => sent_bytes_rate,
|
%sent_bytes => sent_bytes_rate,
|
||||||
sent => sent_msg_rate,
|
sent => sent_msg_rate,
|
||||||
|
validation_succeeded => validation_succeeded_rate,
|
||||||
|
validation_failed => validation_failed_rate,
|
||||||
dropped => dropped_msg_rate
|
dropped => dropped_msg_rate
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
-include("emqx_dashboard.hrl").
|
-include("emqx_dashboard.hrl").
|
||||||
|
|
||||||
|
-include_lib("snabbkaffe/include/trace.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
@ -186,6 +187,7 @@ handle_cast(_Request, State = #state{}) ->
|
||||||
handle_info({sample, Time}, State = #state{last = Last}) ->
|
handle_info({sample, Time}, State = #state{last = Last}) ->
|
||||||
Now = sample(Time),
|
Now = sample(Time),
|
||||||
{atomic, ok} = flush(Last, Now),
|
{atomic, ok} = flush(Last, Now),
|
||||||
|
?tp(dashboard_monitor_flushed, #{}),
|
||||||
sample_timer(),
|
sample_timer(),
|
||||||
{noreply, State#state{last = Now}};
|
{noreply, State#state{last = Now}};
|
||||||
handle_info(clean_expired, State) ->
|
handle_info(clean_expired, State) ->
|
||||||
|
@ -424,6 +426,8 @@ stats(received) -> emqx_metrics:val('messages.received');
|
||||||
stats(received_bytes) -> emqx_metrics:val('bytes.received');
|
stats(received_bytes) -> emqx_metrics:val('bytes.received');
|
||||||
stats(sent) -> emqx_metrics:val('messages.sent');
|
stats(sent) -> emqx_metrics:val('messages.sent');
|
||||||
stats(sent_bytes) -> emqx_metrics:val('bytes.sent');
|
stats(sent_bytes) -> emqx_metrics:val('bytes.sent');
|
||||||
|
stats(validation_succeeded) -> emqx_metrics:val('messages.validation_succeeded');
|
||||||
|
stats(validation_failed) -> emqx_metrics:val('messages.validation_failed');
|
||||||
stats(dropped) -> emqx_metrics:val('messages.dropped').
|
stats(dropped) -> emqx_metrics:val('messages.dropped').
|
||||||
|
|
||||||
%% -------------------------------------------------------------------------------------------------
|
%% -------------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -188,6 +188,10 @@ swagger_desc(sent_bytes) ->
|
||||||
swagger_desc_format("Sent bytes ");
|
swagger_desc_format("Sent bytes ");
|
||||||
swagger_desc(dropped) ->
|
swagger_desc(dropped) ->
|
||||||
swagger_desc_format("Dropped messages ");
|
swagger_desc_format("Dropped messages ");
|
||||||
|
swagger_desc(validation_succeeded) ->
|
||||||
|
swagger_desc_format("Message validations succeeded ");
|
||||||
|
swagger_desc(validation_failed) ->
|
||||||
|
swagger_desc_format("Message validations failed ");
|
||||||
swagger_desc(subscriptions) ->
|
swagger_desc(subscriptions) ->
|
||||||
<<"Subscriptions at the time of sampling.", ?APPROXIMATE_DESC>>;
|
<<"Subscriptions at the time of sampling.", ?APPROXIMATE_DESC>>;
|
||||||
swagger_desc(topics) ->
|
swagger_desc(topics) ->
|
||||||
|
@ -210,6 +214,10 @@ swagger_desc(sent_msg_rate) ->
|
||||||
%swagger_desc(sent_bytes_rate) -> swagger_desc_format("Sent bytes ", per);
|
%swagger_desc(sent_bytes_rate) -> swagger_desc_format("Sent bytes ", per);
|
||||||
swagger_desc(dropped_msg_rate) ->
|
swagger_desc(dropped_msg_rate) ->
|
||||||
swagger_desc_format("Dropped messages ", per);
|
swagger_desc_format("Dropped messages ", per);
|
||||||
|
swagger_desc(validation_succeeded_rate) ->
|
||||||
|
swagger_desc_format("Message validations succeeded ", per);
|
||||||
|
swagger_desc(validation_failed_rate) ->
|
||||||
|
swagger_desc_format("Message validations failed ", per);
|
||||||
swagger_desc(retained_msg_count) ->
|
swagger_desc(retained_msg_count) ->
|
||||||
<<"Retained messages count at the time of sampling.", ?APPROXIMATE_DESC>>;
|
<<"Retained messages count at the time of sampling.", ?APPROXIMATE_DESC>>;
|
||||||
swagger_desc(shared_subscriptions) ->
|
swagger_desc(shared_subscriptions) ->
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
-include("emqx_dashboard.hrl").
|
-include("emqx_dashboard.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
-define(SERVER, "http://127.0.0.1:18083").
|
-define(SERVER, "http://127.0.0.1:18083").
|
||||||
|
@ -54,16 +55,35 @@ all() ->
|
||||||
emqx_common_test_helpers:all(?MODULE).
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
ok = emqx_mgmt_api_test_util:init_suite([emqx, emqx_conf, emqx_retainer]),
|
emqx_common_test_helpers:clear_screen(),
|
||||||
|
Apps = emqx_cth_suite:start(
|
||||||
|
[
|
||||||
|
emqx,
|
||||||
|
emqx_conf,
|
||||||
|
{emqx_retainer, ?BASE_RETAINER_CONF},
|
||||||
|
emqx_management,
|
||||||
|
emqx_mgmt_api_test_util:emqx_dashboard(
|
||||||
|
"dashboard.listeners.http { enable = true, bind = 18083 }\n"
|
||||||
|
"dashboard.sample_interval = 1s"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
#{work_dir => emqx_cth_suite:work_dir(Config)}
|
||||||
|
),
|
||||||
|
{ok, _} = emqx_common_test_http:create_default_app(),
|
||||||
|
[{apps, Apps} | Config].
|
||||||
|
|
||||||
|
end_per_suite(Config) ->
|
||||||
|
Apps = ?config(apps, Config),
|
||||||
|
emqx_cth_suite:stop(Apps),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
ok = snabbkaffe:start_trace(),
|
||||||
|
ct:timetrap({seconds, 30}),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_testcase(_TestCase, _Config) ->
|
||||||
emqx_mgmt_api_test_util:end_suite([emqx_retainer]).
|
ok = snabbkaffe:stop(),
|
||||||
|
|
||||||
set_special_configs(emqx_retainer) ->
|
|
||||||
emqx_retainer:update_config(?BASE_RETAINER_CONF),
|
|
||||||
ok;
|
|
||||||
set_special_configs(_App) ->
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -71,7 +91,11 @@ set_special_configs(_App) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_monitor_samplers_all(_Config) ->
|
t_monitor_samplers_all(_Config) ->
|
||||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
{ok, _} =
|
||||||
|
snabbkaffe:block_until(
|
||||||
|
?match_n_events(2, #{?snk_kind := dashboard_monitor_flushed}),
|
||||||
|
infinity
|
||||||
|
),
|
||||||
Size = mnesia:table_info(emqx_dashboard_monitor, size),
|
Size = mnesia:table_info(emqx_dashboard_monitor, size),
|
||||||
All = emqx_dashboard_monitor:samplers(all, infinity),
|
All = emqx_dashboard_monitor:samplers(all, infinity),
|
||||||
All2 = emqx_dashboard_monitor:samplers(),
|
All2 = emqx_dashboard_monitor:samplers(),
|
||||||
|
@ -80,25 +104,39 @@ t_monitor_samplers_all(_Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_monitor_samplers_latest(_Config) ->
|
t_monitor_samplers_latest(_Config) ->
|
||||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
{ok, _} =
|
||||||
|
snabbkaffe:block_until(
|
||||||
|
?match_n_events(2, #{?snk_kind := dashboard_monitor_flushed}),
|
||||||
|
infinity
|
||||||
|
),
|
||||||
|
?retry(1_000, 10, begin
|
||||||
Samplers = emqx_dashboard_monitor:samplers(node(), 2),
|
Samplers = emqx_dashboard_monitor:samplers(node(), 2),
|
||||||
Latest = emqx_dashboard_monitor:samplers(node(), 1),
|
Latest = emqx_dashboard_monitor:samplers(node(), 1),
|
||||||
?assert(erlang:length(Samplers) == 2),
|
?assert(erlang:length(Samplers) == 2, #{samplers => Samplers}),
|
||||||
?assert(erlang:length(Latest) == 1),
|
?assert(erlang:length(Latest) == 1, #{latest => Latest}),
|
||||||
?assert(hd(Latest) == lists:nth(2, Samplers)),
|
?assert(hd(Latest) == lists:nth(2, Samplers))
|
||||||
|
end),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_monitor_sampler_format(_Config) ->
|
t_monitor_sampler_format(_Config) ->
|
||||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
{ok, _} =
|
||||||
|
snabbkaffe:block_until(
|
||||||
|
?match_event(#{?snk_kind := dashboard_monitor_flushed}),
|
||||||
|
infinity
|
||||||
|
),
|
||||||
Latest = hd(emqx_dashboard_monitor:samplers(node(), 1)),
|
Latest = hd(emqx_dashboard_monitor:samplers(node(), 1)),
|
||||||
SamplerKeys = maps:keys(Latest),
|
SamplerKeys = maps:keys(Latest),
|
||||||
[?assert(lists:member(SamplerName, SamplerKeys)) || SamplerName <- ?SAMPLER_LIST],
|
[?assert(lists:member(SamplerName, SamplerKeys)) || SamplerName <- ?SAMPLER_LIST],
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_monitor_api(_) ->
|
t_monitor_api(_) ->
|
||||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
{ok, _} =
|
||||||
{ok, Samplers} = request(["monitor"], "latest=20"),
|
snabbkaffe:block_until(
|
||||||
?assert(erlang:length(Samplers) >= 2),
|
?match_n_events(2, #{?snk_kind := dashboard_monitor_flushed}),
|
||||||
|
infinity
|
||||||
|
),
|
||||||
|
{ok, Samplers} = request(["monitor"], "latest=200"),
|
||||||
|
?assert(erlang:length(Samplers) >= 2, #{samplers => Samplers}),
|
||||||
Fun =
|
Fun =
|
||||||
fun(Sampler) ->
|
fun(Sampler) ->
|
||||||
Keys = [binary_to_atom(Key, utf8) || Key <- maps:keys(Sampler)],
|
Keys = [binary_to_atom(Key, utf8) || Key <- maps:keys(Sampler)],
|
||||||
|
@ -110,7 +148,11 @@ t_monitor_api(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_monitor_current_api(_) ->
|
t_monitor_current_api(_) ->
|
||||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
{ok, _} =
|
||||||
|
snabbkaffe:block_until(
|
||||||
|
?match_n_events(2, #{?snk_kind := dashboard_monitor_flushed}),
|
||||||
|
infinity
|
||||||
|
),
|
||||||
{ok, Rate} = request(["monitor_current"]),
|
{ok, Rate} = request(["monitor_current"]),
|
||||||
[
|
[
|
||||||
?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
||||||
|
@ -210,7 +252,11 @@ t_monitor_reset(_) ->
|
||||||
?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
?assert(maps:is_key(atom_to_binary(Key, utf8), Rate))
|
||||||
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST
|
|| Key <- maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST
|
||||||
],
|
],
|
||||||
timer:sleep(?DEFAULT_SAMPLE_INTERVAL * 2 * 1000 + 20),
|
{ok, _} =
|
||||||
|
snabbkaffe:block_until(
|
||||||
|
?match_n_events(1, #{?snk_kind := dashboard_monitor_flushed}),
|
||||||
|
infinity
|
||||||
|
),
|
||||||
{ok, Samplers} = request(["monitor"], "latest=1"),
|
{ok, Samplers} = request(["monitor"], "latest=1"),
|
||||||
?assertEqual(1, erlang:length(Samplers)),
|
?assertEqual(1, erlang:length(Samplers)),
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -111,6 +111,10 @@ fields(Name) ->
|
||||||
translations() ->
|
translations() ->
|
||||||
emqx_conf_schema:translations().
|
emqx_conf_schema:translations().
|
||||||
|
|
||||||
|
translation("prometheus") ->
|
||||||
|
[
|
||||||
|
{"collectors", fun tr_prometheus_collectors/1}
|
||||||
|
];
|
||||||
translation(Name) ->
|
translation(Name) ->
|
||||||
emqx_conf_schema:translation(Name).
|
emqx_conf_schema:translation(Name).
|
||||||
|
|
||||||
|
@ -189,3 +193,9 @@ audit_log_conf() ->
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
tr_prometheus_collectors(Conf) ->
|
||||||
|
[
|
||||||
|
{'/prometheus/message_validation', emqx_prometheus_message_validation}
|
||||||
|
| emqx_conf_schema:tr_prometheus_collectors(Conf)
|
||||||
|
].
|
||||||
|
|
|
@ -140,10 +140,13 @@ on_message_publish(Message = #message{topic = Topic, headers = Headers}) ->
|
||||||
Validations ->
|
Validations ->
|
||||||
case run_validations(Validations, Message) of
|
case run_validations(Validations, Message) of
|
||||||
ok ->
|
ok ->
|
||||||
|
emqx_metrics:inc('messages.validation_succeeded'),
|
||||||
{ok, Message};
|
{ok, Message};
|
||||||
drop ->
|
drop ->
|
||||||
|
emqx_metrics:inc('messages.validation_failed'),
|
||||||
{stop, Message#message{headers = Headers#{allow_publish => false}}};
|
{stop, Message#message{headers = Headers#{allow_publish => false}}};
|
||||||
disconnect ->
|
disconnect ->
|
||||||
|
emqx_metrics:inc('messages.validation_failed'),
|
||||||
{stop, Message#message{
|
{stop, Message#message{
|
||||||
headers = Headers#{
|
headers = Headers#{
|
||||||
allow_publish => false,
|
allow_publish => false,
|
||||||
|
@ -380,14 +383,17 @@ run_validations(Validations, Message) ->
|
||||||
emqx_rule_runtime:clear_rule_payload(),
|
emqx_rule_runtime:clear_rule_payload(),
|
||||||
Fun = fun(Validation, Acc) ->
|
Fun = fun(Validation, Acc) ->
|
||||||
#{name := Name} = Validation,
|
#{name := Name} = Validation,
|
||||||
|
emqx_message_validation_registry:inc_matched(Name),
|
||||||
case run_validation(Validation, Message) of
|
case run_validation(Validation, Message) of
|
||||||
ok ->
|
ok ->
|
||||||
|
emqx_message_validation_registry:inc_succeeded(Name),
|
||||||
{cont, Acc};
|
{cont, Acc};
|
||||||
ignore ->
|
ignore ->
|
||||||
trace_failure(Validation, "validation_failed", #{
|
trace_failure(Validation, "validation_failed", #{
|
||||||
validation => Name,
|
validation => Name,
|
||||||
action => ignore
|
action => ignore
|
||||||
}),
|
}),
|
||||||
|
emqx_message_validation_registry:inc_failed(Name),
|
||||||
run_message_validation_failed_hook(Message, Validation),
|
run_message_validation_failed_hook(Message, Validation),
|
||||||
{cont, Acc};
|
{cont, Acc};
|
||||||
FailureAction ->
|
FailureAction ->
|
||||||
|
@ -395,6 +401,7 @@ run_validations(Validations, Message) ->
|
||||||
validation => Name,
|
validation => Name,
|
||||||
action => FailureAction
|
action => FailureAction
|
||||||
}),
|
}),
|
||||||
|
emqx_message_validation_registry:inc_failed(Name),
|
||||||
run_message_validation_failed_hook(Message, Validation),
|
run_message_validation_failed_hook(Message, Validation),
|
||||||
{halt, FailureAction}
|
{halt, FailureAction}
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
'/message_validations'/2,
|
'/message_validations'/2,
|
||||||
'/message_validations/reorder'/2,
|
'/message_validations/reorder'/2,
|
||||||
'/message_validations/validation/:name'/2,
|
'/message_validations/validation/:name'/2,
|
||||||
|
'/message_validations/validation/:name/metrics'/2,
|
||||||
|
'/message_validations/validation/:name/metrics/reset'/2,
|
||||||
'/message_validations/validation/:name/enable/:enable'/2
|
'/message_validations/validation/:name/enable/:enable'/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
-define(TAGS, [<<"Message Validation">>]).
|
-define(TAGS, [<<"Message Validation">>]).
|
||||||
|
-define(METRIC_NAME, message_validation).
|
||||||
|
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
%% `minirest' and `minirest_trails' API
|
%% `minirest' and `minirest_trails' API
|
||||||
|
@ -47,6 +50,8 @@ paths() ->
|
||||||
"/message_validations",
|
"/message_validations",
|
||||||
"/message_validations/reorder",
|
"/message_validations/reorder",
|
||||||
"/message_validations/validation/:name",
|
"/message_validations/validation/:name",
|
||||||
|
"/message_validations/validation/:name/metrics",
|
||||||
|
"/message_validations/validation/:name/metrics/reset",
|
||||||
"/message_validations/validation/:name/enable/:enable"
|
"/message_validations/validation/:name/enable/:enable"
|
||||||
].
|
].
|
||||||
|
|
||||||
|
@ -173,6 +178,43 @@ schema("/message_validations/validation/:name") ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
schema("/message_validations/validation/:name/metrics") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/message_validations/validation/:name/metrics',
|
||||||
|
get => #{
|
||||||
|
tags => ?TAGS,
|
||||||
|
summary => <<"Get validation metrics">>,
|
||||||
|
description => ?DESC("get_validation_metrics"),
|
||||||
|
parameters => [param_path_name()],
|
||||||
|
responses =>
|
||||||
|
#{
|
||||||
|
200 =>
|
||||||
|
emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
ref(get_metrics),
|
||||||
|
#{
|
||||||
|
sample =>
|
||||||
|
#{value => example_return_metrics()}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
404 => error_schema('NOT_FOUND', "Validation not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/message_validations/validation/:name/metrics/reset") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/message_validations/validation/:name/metrics/reset',
|
||||||
|
post => #{
|
||||||
|
tags => ?TAGS,
|
||||||
|
summary => <<"Reset validation metrics">>,
|
||||||
|
description => ?DESC("reset_validation_metrics"),
|
||||||
|
parameters => [param_path_name()],
|
||||||
|
responses =>
|
||||||
|
#{
|
||||||
|
204 => <<"No content">>,
|
||||||
|
404 => error_schema('NOT_FOUND', "Validation not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
schema("/message_validations/validation/:name/enable/:enable") ->
|
schema("/message_validations/validation/:name/enable/:enable") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => '/message_validations/validation/:name/enable/:enable',
|
'operationId' => '/message_validations/validation/:name/enable/:enable',
|
||||||
|
@ -230,6 +272,22 @@ fields(before) ->
|
||||||
fields(reorder) ->
|
fields(reorder) ->
|
||||||
[
|
[
|
||||||
{order, mk(array(binary()), #{required => true, in => body})}
|
{order, mk(array(binary()), #{required => true, in => body})}
|
||||||
|
];
|
||||||
|
fields(get_metrics) ->
|
||||||
|
[
|
||||||
|
{metrics, mk(ref(metrics), #{})},
|
||||||
|
{node_metrics, mk(ref(node_metrics), #{})}
|
||||||
|
];
|
||||||
|
fields(metrics) ->
|
||||||
|
[
|
||||||
|
{matched, mk(non_neg_integer(), #{})},
|
||||||
|
{succeeded, mk(non_neg_integer(), #{})},
|
||||||
|
{failed, mk(non_neg_integer(), #{})}
|
||||||
|
];
|
||||||
|
fields(node_metrics) ->
|
||||||
|
[
|
||||||
|
{node, mk(binary(), #{})}
|
||||||
|
| fields(metrics)
|
||||||
].
|
].
|
||||||
|
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
|
@ -299,6 +357,47 @@ fields(reorder) ->
|
||||||
not_found()
|
not_found()
|
||||||
).
|
).
|
||||||
|
|
||||||
|
'/message_validations/validation/:name/metrics'(get, #{bindings := #{name := Name}}) ->
|
||||||
|
with_validation(
|
||||||
|
Name,
|
||||||
|
fun() ->
|
||||||
|
Nodes = emqx:running_nodes(),
|
||||||
|
Results = emqx_metrics_proto_v2:get_metrics(Nodes, ?METRIC_NAME, Name, 5_000),
|
||||||
|
NodeResults = lists:zip(Nodes, Results),
|
||||||
|
NodeErrors = [Result || Result = {_Node, {NOk, _}} <- NodeResults, NOk =/= ok],
|
||||||
|
NodeErrors == [] orelse
|
||||||
|
?SLOG(warning, #{
|
||||||
|
msg => "rpc_get_validation_metrics_errors",
|
||||||
|
errors => NodeErrors
|
||||||
|
}),
|
||||||
|
NodeMetrics = [format_metrics(Node, Metrics) || {Node, {ok, Metrics}} <- NodeResults],
|
||||||
|
Response = #{
|
||||||
|
metrics => aggregate_metrics(NodeMetrics),
|
||||||
|
node_metrics => NodeMetrics
|
||||||
|
},
|
||||||
|
?OK(Response)
|
||||||
|
end,
|
||||||
|
not_found()
|
||||||
|
).
|
||||||
|
|
||||||
|
'/message_validations/validation/:name/metrics/reset'(post, #{bindings := #{name := Name}}) ->
|
||||||
|
with_validation(
|
||||||
|
Name,
|
||||||
|
fun() ->
|
||||||
|
Nodes = emqx:running_nodes(),
|
||||||
|
Results = emqx_metrics_proto_v2:reset_metrics(Nodes, ?METRIC_NAME, Name, 5_000),
|
||||||
|
NodeResults = lists:zip(Nodes, Results),
|
||||||
|
NodeErrors = [Result || Result = {_Node, {NOk, _}} <- NodeResults, NOk =/= ok],
|
||||||
|
NodeErrors == [] orelse
|
||||||
|
?SLOG(warning, #{
|
||||||
|
msg => "rpc_reset_validation_metrics_errors",
|
||||||
|
errors => NodeErrors
|
||||||
|
}),
|
||||||
|
?NO_CONTENT
|
||||||
|
end,
|
||||||
|
not_found()
|
||||||
|
).
|
||||||
|
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
%% Internal fns
|
%% Internal fns
|
||||||
%%-------------------------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------------------------
|
||||||
|
@ -335,6 +434,10 @@ example_return_lookup() ->
|
||||||
%% TODO
|
%% TODO
|
||||||
#{}.
|
#{}.
|
||||||
|
|
||||||
|
example_return_metrics() ->
|
||||||
|
%% TODO
|
||||||
|
#{}.
|
||||||
|
|
||||||
error_schema(Code, Message) ->
|
error_schema(Code, Message) ->
|
||||||
error_schema(Code, Message, _ExtraFields = []).
|
error_schema(Code, Message, _ExtraFields = []).
|
||||||
|
|
||||||
|
@ -409,3 +512,51 @@ make_serializable(Validation) ->
|
||||||
} =
|
} =
|
||||||
hocon_tconf:make_serializable(Schema, RawConfig, #{}),
|
hocon_tconf:make_serializable(Schema, RawConfig, #{}),
|
||||||
Serialized.
|
Serialized.
|
||||||
|
|
||||||
|
format_metrics(Node, #{
|
||||||
|
counters := #{
|
||||||
|
'matched' := Matched,
|
||||||
|
'succeeded' := Succeeded,
|
||||||
|
'failed' := Failed
|
||||||
|
},
|
||||||
|
rate := #{
|
||||||
|
'matched' := #{
|
||||||
|
current := MatchedRate,
|
||||||
|
last5m := Matched5mRate,
|
||||||
|
max := MatchedMaxRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) ->
|
||||||
|
#{
|
||||||
|
metrics => #{
|
||||||
|
'matched' => Matched,
|
||||||
|
'succeeded' => Succeeded,
|
||||||
|
'failed' => Failed,
|
||||||
|
rate => MatchedRate,
|
||||||
|
rate_last5m => Matched5mRate,
|
||||||
|
rate_max => MatchedMaxRate
|
||||||
|
},
|
||||||
|
node => Node
|
||||||
|
};
|
||||||
|
format_metrics(Node, _) ->
|
||||||
|
#{
|
||||||
|
metrics => #{
|
||||||
|
'matched' => 0,
|
||||||
|
'succeeded' => 0,
|
||||||
|
'failed' => 0,
|
||||||
|
rate => 0,
|
||||||
|
rate_last5m => 0,
|
||||||
|
rate_max => 0
|
||||||
|
},
|
||||||
|
node => Node
|
||||||
|
}.
|
||||||
|
|
||||||
|
aggregate_metrics(NodeMetrics) ->
|
||||||
|
ErrorLogger = fun(_) -> ok end,
|
||||||
|
lists:foldl(
|
||||||
|
fun(#{metrics := Metrics}, Acc) ->
|
||||||
|
emqx_utils_maps:best_effort_recursive_sum(Metrics, Acc, ErrorLogger)
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
NodeMetrics
|
||||||
|
).
|
||||||
|
|
|
@ -15,6 +15,12 @@
|
||||||
|
|
||||||
matching_validations/1,
|
matching_validations/1,
|
||||||
|
|
||||||
|
%% metrics
|
||||||
|
get_metrics/1,
|
||||||
|
inc_matched/1,
|
||||||
|
inc_succeeded/1,
|
||||||
|
inc_failed/1,
|
||||||
|
|
||||||
start_link/0,
|
start_link/0,
|
||||||
metrics_worker_spec/0
|
metrics_worker_spec/0
|
||||||
]).
|
]).
|
||||||
|
@ -36,7 +42,7 @@
|
||||||
-define(METRIC_NAME, message_validation).
|
-define(METRIC_NAME, message_validation).
|
||||||
-define(METRICS, [
|
-define(METRICS, [
|
||||||
'matched',
|
'matched',
|
||||||
'passed',
|
'succeeded',
|
||||||
'failed'
|
'failed'
|
||||||
]).
|
]).
|
||||||
-define(RATE_METRICS, ['matched']).
|
-define(RATE_METRICS, ['matched']).
|
||||||
|
@ -102,6 +108,22 @@ matching_validations(Topic) ->
|
||||||
metrics_worker_spec() ->
|
metrics_worker_spec() ->
|
||||||
emqx_metrics_worker:child_spec(message_validation_metrics, ?METRIC_NAME).
|
emqx_metrics_worker:child_spec(message_validation_metrics, ?METRIC_NAME).
|
||||||
|
|
||||||
|
-spec get_metrics(validation_name()) -> emqx_metrics_worker:metrics().
|
||||||
|
get_metrics(Name) ->
|
||||||
|
emqx_metrics_worker:get_metrics(?METRIC_NAME, Name).
|
||||||
|
|
||||||
|
-spec inc_matched(validation_name()) -> ok.
|
||||||
|
inc_matched(Name) ->
|
||||||
|
emqx_metrics_worker:inc(?METRIC_NAME, Name, 'matched').
|
||||||
|
|
||||||
|
-spec inc_succeeded(validation_name()) -> ok.
|
||||||
|
inc_succeeded(Name) ->
|
||||||
|
emqx_metrics_worker:inc(?METRIC_NAME, Name, 'succeeded').
|
||||||
|
|
||||||
|
-spec inc_failed(validation_name()) -> ok.
|
||||||
|
inc_failed(Name) ->
|
||||||
|
emqx_metrics_worker:inc(?METRIC_NAME, Name, 'failed').
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% `gen_server' API
|
%% `gen_server' API
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -53,6 +53,7 @@ init_per_testcase(_TestCase, Config) ->
|
||||||
end_per_testcase(_TestCase, _Config) ->
|
end_per_testcase(_TestCase, _Config) ->
|
||||||
clear_all_validations(),
|
clear_all_validations(),
|
||||||
snabbkaffe:stop(),
|
snabbkaffe:stop(),
|
||||||
|
reset_all_global_metrics(),
|
||||||
emqx_common_test_helpers:call_janitor(),
|
emqx_common_test_helpers:call_janitor(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -70,6 +71,14 @@ clear_all_validations() ->
|
||||||
emqx_message_validation:list()
|
emqx_message_validation:list()
|
||||||
).
|
).
|
||||||
|
|
||||||
|
reset_all_global_metrics() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun({Name, _}) ->
|
||||||
|
emqx_metrics:set(Name, 0)
|
||||||
|
end,
|
||||||
|
emqx_metrics:all()
|
||||||
|
).
|
||||||
|
|
||||||
maybe_json_decode(X) ->
|
maybe_json_decode(X) ->
|
||||||
case emqx_utils_json:safe_decode(X, [return_maps]) of
|
case emqx_utils_json:safe_decode(X, [return_maps]) of
|
||||||
{ok, Decoded} -> Decoded;
|
{ok, Decoded} -> Decoded;
|
||||||
|
@ -196,6 +205,30 @@ disable(Name) ->
|
||||||
ct:pal("disable result:\n ~p", [Res]),
|
ct:pal("disable result:\n ~p", [Res]),
|
||||||
simplify_result(Res).
|
simplify_result(Res).
|
||||||
|
|
||||||
|
get_metrics(Name) ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path([api_root(), "validation", Name, "metrics"]),
|
||||||
|
Res = request(get, Path, _Params = []),
|
||||||
|
ct:pal("get metrics result:\n ~p", [Res]),
|
||||||
|
simplify_result(Res).
|
||||||
|
|
||||||
|
reset_metrics(Name) ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path([api_root(), "validation", Name, "metrics", "reset"]),
|
||||||
|
Res = request(post, Path, _Params = []),
|
||||||
|
ct:pal("reset metrics result:\n ~p", [Res]),
|
||||||
|
simplify_result(Res).
|
||||||
|
|
||||||
|
all_metrics() ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["metrics"]),
|
||||||
|
Res = request(get, Path, _Params = []),
|
||||||
|
ct:pal("all metrics result:\n ~p", [Res]),
|
||||||
|
simplify_result(Res).
|
||||||
|
|
||||||
|
monitor_metrics() ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["monitor"]),
|
||||||
|
Res = request(get, Path, _Params = []),
|
||||||
|
ct:pal("monitor metrics result:\n ~p", [Res]),
|
||||||
|
simplify_result(Res).
|
||||||
|
|
||||||
connect(ClientId) ->
|
connect(ClientId) ->
|
||||||
connect(ClientId, _IsPersistent = false).
|
connect(ClientId, _IsPersistent = false).
|
||||||
|
|
||||||
|
@ -363,6 +396,48 @@ trace_rule(Data, Envs, _Args) ->
|
||||||
get_traced_failures_from_rule_engine() ->
|
get_traced_failures_from_rule_engine() ->
|
||||||
ets:tab2list(?RECORDED_EVENTS_TAB).
|
ets:tab2list(?RECORDED_EVENTS_TAB).
|
||||||
|
|
||||||
|
assert_all_metrics(Line, Expected) ->
|
||||||
|
Keys = maps:keys(Expected),
|
||||||
|
?retry(
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
begin
|
||||||
|
Res = all_metrics(),
|
||||||
|
?assertMatch({200, _}, Res),
|
||||||
|
{200, [Metrics]} = Res,
|
||||||
|
?assertEqual(Expected, maps:with(Keys, Metrics), #{line => Line})
|
||||||
|
end
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-define(assertAllMetrics(Expected), assert_all_metrics(?LINE, Expected)).
|
||||||
|
|
||||||
|
%% check that dashboard monitor contains the success and failure metric keys
|
||||||
|
assert_monitor_metrics() ->
|
||||||
|
ok = snabbkaffe:start_trace(),
|
||||||
|
%% hack: force monitor to flush data now
|
||||||
|
{_, {ok, _}} =
|
||||||
|
?wait_async_action(
|
||||||
|
emqx_dashboard_monitor ! {sample, erlang:system_time(millisecond)},
|
||||||
|
#{?snk_kind := dashboard_monitor_flushed}
|
||||||
|
),
|
||||||
|
Res = monitor_metrics(),
|
||||||
|
?assertMatch({200, _}, Res),
|
||||||
|
{200, Metrics} = Res,
|
||||||
|
lists:foreach(
|
||||||
|
fun(M) ->
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"validation_failed">> := _,
|
||||||
|
<<"validation_succeeded">> := _
|
||||||
|
},
|
||||||
|
M
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
Metrics
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Testcases
|
%% Testcases
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -703,6 +778,236 @@ t_enable_disable_via_api_endpoint(_Config) ->
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_metrics(_Config) ->
|
||||||
|
%% extra validation that always passes at the head to check global metrics
|
||||||
|
Name0 = <<"bar">>,
|
||||||
|
Check0 = sql_check(<<"select 1 where true">>),
|
||||||
|
Validation0 = validation(Name0, [Check0]),
|
||||||
|
{201, _} = insert(Validation0),
|
||||||
|
|
||||||
|
Name1 = <<"foo">>,
|
||||||
|
Check1 = sql_check(<<"select payload.x as x where x > 5">>),
|
||||||
|
Validation1 = validation(Name1, [Check1]),
|
||||||
|
|
||||||
|
%% Non existent
|
||||||
|
?assertMatch({404, _}, get_metrics(Name1)),
|
||||||
|
?assertAllMetrics(#{
|
||||||
|
<<"messages.dropped">> => 0,
|
||||||
|
<<"messages.validation_failed">> => 0,
|
||||||
|
<<"messages.validation_succeeded">> => 0
|
||||||
|
}),
|
||||||
|
|
||||||
|
{201, _} = insert(Validation1),
|
||||||
|
|
||||||
|
?assertMatch(
|
||||||
|
{200, #{
|
||||||
|
<<"metrics">> :=
|
||||||
|
#{
|
||||||
|
<<"matched">> := 0,
|
||||||
|
<<"succeeded">> := 0,
|
||||||
|
<<"failed">> := 0,
|
||||||
|
<<"rate">> := _,
|
||||||
|
<<"rate_last5m">> := _,
|
||||||
|
<<"rate_max">> := _
|
||||||
|
},
|
||||||
|
<<"node_metrics">> :=
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"node">> := _,
|
||||||
|
<<"metrics">> := #{
|
||||||
|
<<"matched">> := 0,
|
||||||
|
<<"succeeded">> := 0,
|
||||||
|
<<"failed">> := 0,
|
||||||
|
<<"rate">> := _,
|
||||||
|
<<"rate_last5m">> := _,
|
||||||
|
<<"rate_max">> := _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
get_metrics(Name1)
|
||||||
|
),
|
||||||
|
?assertAllMetrics(#{
|
||||||
|
<<"messages.dropped">> => 0,
|
||||||
|
<<"messages.validation_failed">> => 0,
|
||||||
|
<<"messages.validation_succeeded">> => 0
|
||||||
|
}),
|
||||||
|
|
||||||
|
C = connect(<<"c1">>),
|
||||||
|
{ok, _, [_]} = emqtt:subscribe(C, <<"t/#">>),
|
||||||
|
|
||||||
|
ok = publish(C, <<"t/1">>, #{x => 10}),
|
||||||
|
?assertReceive({publish, _}),
|
||||||
|
|
||||||
|
?retry(
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
?assertMatch(
|
||||||
|
{200, #{
|
||||||
|
<<"metrics">> :=
|
||||||
|
#{
|
||||||
|
<<"matched">> := 1,
|
||||||
|
<<"succeeded">> := 1,
|
||||||
|
<<"failed">> := 0
|
||||||
|
},
|
||||||
|
<<"node_metrics">> :=
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"node">> := _,
|
||||||
|
<<"metrics">> := #{
|
||||||
|
<<"matched">> := 1,
|
||||||
|
<<"succeeded">> := 1,
|
||||||
|
<<"failed">> := 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
get_metrics(Name1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertAllMetrics(#{
|
||||||
|
<<"messages.dropped">> => 0,
|
||||||
|
<<"messages.validation_failed">> => 0,
|
||||||
|
<<"messages.validation_succeeded">> => 1
|
||||||
|
}),
|
||||||
|
|
||||||
|
ok = publish(C, <<"t/1">>, #{x => 5}),
|
||||||
|
?assertNotReceive({publish, _}),
|
||||||
|
|
||||||
|
?retry(
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
?assertMatch(
|
||||||
|
{200, #{
|
||||||
|
<<"metrics">> :=
|
||||||
|
#{
|
||||||
|
<<"matched">> := 2,
|
||||||
|
<<"succeeded">> := 1,
|
||||||
|
<<"failed">> := 1
|
||||||
|
},
|
||||||
|
<<"node_metrics">> :=
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"node">> := _,
|
||||||
|
<<"metrics">> := #{
|
||||||
|
<<"matched">> := 2,
|
||||||
|
<<"succeeded">> := 1,
|
||||||
|
<<"failed">> := 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
get_metrics(Name1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertAllMetrics(#{
|
||||||
|
<<"messages.dropped">> => 0,
|
||||||
|
<<"messages.validation_failed">> => 1,
|
||||||
|
<<"messages.validation_succeeded">> => 1
|
||||||
|
}),
|
||||||
|
|
||||||
|
?assertMatch({204, _}, reset_metrics(Name1)),
|
||||||
|
?retry(
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
?assertMatch(
|
||||||
|
{200, #{
|
||||||
|
<<"metrics">> :=
|
||||||
|
#{
|
||||||
|
<<"matched">> := 0,
|
||||||
|
<<"succeeded">> := 0,
|
||||||
|
<<"failed">> := 0,
|
||||||
|
<<"rate">> := _,
|
||||||
|
<<"rate_last5m">> := _,
|
||||||
|
<<"rate_max">> := _
|
||||||
|
},
|
||||||
|
<<"node_metrics">> :=
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"node">> := _,
|
||||||
|
<<"metrics">> := #{
|
||||||
|
<<"matched">> := 0,
|
||||||
|
<<"succeeded">> := 0,
|
||||||
|
<<"failed">> := 0,
|
||||||
|
<<"rate">> := _,
|
||||||
|
<<"rate_last5m">> := _,
|
||||||
|
<<"rate_max">> := _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
get_metrics(Name1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertAllMetrics(#{
|
||||||
|
<<"messages.dropped">> => 0,
|
||||||
|
<<"messages.validation_failed">> => 1,
|
||||||
|
<<"messages.validation_succeeded">> => 1
|
||||||
|
}),
|
||||||
|
|
||||||
|
%% updating a validation resets its metrics
|
||||||
|
ok = publish(C, <<"t/1">>, #{x => 5}),
|
||||||
|
?assertNotReceive({publish, _}),
|
||||||
|
ok = publish(C, <<"t/1">>, #{x => 10}),
|
||||||
|
?assertReceive({publish, _}),
|
||||||
|
?retry(
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
?assertMatch(
|
||||||
|
{200, #{
|
||||||
|
<<"metrics">> :=
|
||||||
|
#{
|
||||||
|
<<"matched">> := 2,
|
||||||
|
<<"succeeded">> := 1,
|
||||||
|
<<"failed">> := 1
|
||||||
|
},
|
||||||
|
<<"node_metrics">> :=
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"node">> := _,
|
||||||
|
<<"metrics">> := #{
|
||||||
|
<<"matched">> := 2,
|
||||||
|
<<"succeeded">> := 1,
|
||||||
|
<<"failed">> := 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
get_metrics(Name1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
{200, _} = update(Validation1),
|
||||||
|
?retry(
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
?assertMatch(
|
||||||
|
{200, #{
|
||||||
|
<<"metrics">> :=
|
||||||
|
#{
|
||||||
|
<<"matched">> := 0,
|
||||||
|
<<"succeeded">> := 0,
|
||||||
|
<<"failed">> := 0
|
||||||
|
},
|
||||||
|
<<"node_metrics">> :=
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"node">> := _,
|
||||||
|
<<"metrics">> := #{
|
||||||
|
<<"matched">> := 0,
|
||||||
|
<<"succeeded">> := 0,
|
||||||
|
<<"failed">> := 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
get_metrics(Name1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
assert_monitor_metrics(),
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
||||||
t_duplicated_schema_checks(_Config) ->
|
t_duplicated_schema_checks(_Config) ->
|
||||||
Name1 = <<"foo">>,
|
Name1 = <<"foo">>,
|
||||||
SerdeName = <<"myserde">>,
|
SerdeName = <<"myserde">>,
|
||||||
|
|
|
@ -22,12 +22,27 @@
|
||||||
-define(PROMETHEUS_AUTH_COLLECTOR, emqx_prometheus_auth).
|
-define(PROMETHEUS_AUTH_COLLECTOR, emqx_prometheus_auth).
|
||||||
-define(PROMETHEUS_DATA_INTEGRATION_REGISTRY, '/prometheus/data_integration').
|
-define(PROMETHEUS_DATA_INTEGRATION_REGISTRY, '/prometheus/data_integration').
|
||||||
-define(PROMETHEUS_DATA_INTEGRATION_COLLECTOR, emqx_prometheus_data_integration).
|
-define(PROMETHEUS_DATA_INTEGRATION_COLLECTOR, emqx_prometheus_data_integration).
|
||||||
|
-define(PROMETHEUS_MESSAGE_VALIDATION_REGISTRY, '/prometheus/message_validation').
|
||||||
|
-define(PROMETHEUS_MESSAGE_VALIDATION_COLLECTOR, emqx_prometheus_message_validation).
|
||||||
|
|
||||||
-define(PROMETHEUS_ALL_REGISTRYS, [
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-define(PROMETHEUS_EE_REGISTRIES, [
|
||||||
|
?PROMETHEUS_MESSAGE_VALIDATION_REGISTRY
|
||||||
|
]).
|
||||||
|
%% ELSE if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-else.
|
||||||
|
-define(PROMETHEUS_EE_REGISTRIES, []).
|
||||||
|
%% END if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-define(PROMETHEUS_ALL_REGISTRIES,
|
||||||
|
?PROMETHEUS_EE_REGISTRIES ++
|
||||||
|
[
|
||||||
?PROMETHEUS_DEFAULT_REGISTRY,
|
?PROMETHEUS_DEFAULT_REGISTRY,
|
||||||
?PROMETHEUS_AUTH_REGISTRY,
|
?PROMETHEUS_AUTH_REGISTRY,
|
||||||
?PROMETHEUS_DATA_INTEGRATION_REGISTRY
|
?PROMETHEUS_DATA_INTEGRATION_REGISTRY
|
||||||
]).
|
]
|
||||||
|
).
|
||||||
|
|
||||||
-define(PROM_DATA_MODE__NODE, node).
|
-define(PROM_DATA_MODE__NODE, node).
|
||||||
-define(PROM_DATA_MODE__ALL_NODES_AGGREGATED, all_nodes_aggregated).
|
-define(PROM_DATA_MODE__ALL_NODES_AGGREGATED, all_nodes_aggregated).
|
||||||
|
|
|
@ -43,11 +43,13 @@
|
||||||
namespace/0
|
namespace/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
%% handlers
|
||||||
-export([
|
-export([
|
||||||
setting/2,
|
setting/2,
|
||||||
stats/2,
|
stats/2,
|
||||||
auth/2,
|
auth/2,
|
||||||
data_integration/2
|
data_integration/2,
|
||||||
|
message_validation/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([lookup_from_local_nodes/3]).
|
-export([lookup_from_local_nodes/3]).
|
||||||
|
@ -67,7 +69,17 @@ paths() ->
|
||||||
"/prometheus/auth",
|
"/prometheus/auth",
|
||||||
"/prometheus/stats",
|
"/prometheus/stats",
|
||||||
"/prometheus/data_integration"
|
"/prometheus/data_integration"
|
||||||
].
|
] ++ paths_ee().
|
||||||
|
|
||||||
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
paths_ee() ->
|
||||||
|
["/prometheus/message_validation"].
|
||||||
|
%% ELSE if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-else.
|
||||||
|
paths_ee() ->
|
||||||
|
[].
|
||||||
|
%% END if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-endif.
|
||||||
|
|
||||||
schema("/prometheus") ->
|
schema("/prometheus") ->
|
||||||
#{
|
#{
|
||||||
|
@ -126,6 +138,19 @@ schema("/prometheus/data_integration") ->
|
||||||
responses =>
|
responses =>
|
||||||
#{200 => prometheus_data_schema()}
|
#{200 => prometheus_data_schema()}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
schema("/prometheus/message_validation") ->
|
||||||
|
#{
|
||||||
|
'operationId' => message_validation,
|
||||||
|
get =>
|
||||||
|
#{
|
||||||
|
description => ?DESC(get_prom_message_validation),
|
||||||
|
tags => ?TAGS,
|
||||||
|
parameters => [ref(mode)],
|
||||||
|
security => security(),
|
||||||
|
responses =>
|
||||||
|
#{200 => prometheus_data_schema()}
|
||||||
|
}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
security() ->
|
security() ->
|
||||||
|
@ -198,6 +223,9 @@ auth(get, #{headers := Headers, query_string := Qs}) ->
|
||||||
data_integration(get, #{headers := Headers, query_string := Qs}) ->
|
data_integration(get, #{headers := Headers, query_string := Qs}) ->
|
||||||
collect(emqx_prometheus_data_integration, collect_opts(Headers, Qs)).
|
collect(emqx_prometheus_data_integration, collect_opts(Headers, Qs)).
|
||||||
|
|
||||||
|
message_validation(get, #{headers := Headers, query_string := Qs}) ->
|
||||||
|
collect(emqx_prometheus_message_validation, collect_opts(Headers, Qs)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal funcs
|
%% Internal funcs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -123,7 +123,7 @@ all_collectors() ->
|
||||||
prometheus_registry:collectors(Registry) ++ AccIn
|
prometheus_registry:collectors(Registry) ++ AccIn
|
||||||
end,
|
end,
|
||||||
_InitAcc = [],
|
_InitAcc = [],
|
||||||
?PROMETHEUS_ALL_REGISTRYS
|
?PROMETHEUS_ALL_REGISTRIES
|
||||||
).
|
).
|
||||||
|
|
||||||
update_push_gateway(Prometheus) ->
|
update_push_gateway(Prometheus) ->
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_prometheus_message_validation).
|
||||||
|
|
||||||
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
%% for bpapi
|
||||||
|
-behaviour(emqx_prometheus_cluster).
|
||||||
|
|
||||||
|
%% Please don't remove this attribute, prometheus uses it to
|
||||||
|
%% automatically register collectors.
|
||||||
|
-behaviour(prometheus_collector).
|
||||||
|
|
||||||
|
-include("emqx_prometheus.hrl").
|
||||||
|
-include_lib("prometheus/include/prometheus.hrl").
|
||||||
|
|
||||||
|
-import(
|
||||||
|
prometheus_model_helpers,
|
||||||
|
[
|
||||||
|
create_mf/5,
|
||||||
|
gauge_metrics/1,
|
||||||
|
counter_metrics/1
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
deregister_cleanup/1,
|
||||||
|
collect_mf/2,
|
||||||
|
collect_metrics/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% `emqx_prometheus' API
|
||||||
|
-export([collect/1]).
|
||||||
|
|
||||||
|
%% `emqx_prometheus_cluster' API
|
||||||
|
-export([
|
||||||
|
fetch_from_local_node/1,
|
||||||
|
fetch_cluster_consistented_data/0,
|
||||||
|
aggre_or_zip_init_acc/0,
|
||||||
|
logic_sum_metrics/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Type definitions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(MG(K, MAP), maps:get(K, MAP)).
|
||||||
|
-define(MG0(K, MAP), maps:get(K, MAP, 0)).
|
||||||
|
|
||||||
|
-define(metrics_data_key, message_validation_metrics_data).
|
||||||
|
|
||||||
|
-define(key_enabled, emqx_message_validation_enable).
|
||||||
|
-define(key_matched, emqx_message_validation_matched).
|
||||||
|
-define(key_failed, emqx_message_validation_failed).
|
||||||
|
-define(key_succeeded, emqx_message_validation_succeeded).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% `emqx_prometheus_cluster' API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
fetch_from_local_node(Mode) ->
|
||||||
|
Validations = emqx_message_validation:list(),
|
||||||
|
{node(), #{
|
||||||
|
?metrics_data_key => to_validation_data(Mode, Validations)
|
||||||
|
}}.
|
||||||
|
|
||||||
|
fetch_cluster_consistented_data() ->
|
||||||
|
#{}.
|
||||||
|
|
||||||
|
aggre_or_zip_init_acc() ->
|
||||||
|
#{
|
||||||
|
?metrics_data_key => maps:from_keys(message_validation_metric(names), [])
|
||||||
|
}.
|
||||||
|
|
||||||
|
logic_sum_metrics() ->
|
||||||
|
[
|
||||||
|
?key_enabled
|
||||||
|
].
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Collector API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
deregister_cleanup(_) -> ok.
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
-spec collect_mf(_Registry, Callback) -> ok when
|
||||||
|
_Registry :: prometheus_registry:registry(),
|
||||||
|
Callback :: prometheus_collector:collect_mf_callback().
|
||||||
|
collect_mf(?PROMETHEUS_MESSAGE_VALIDATION_REGISTRY, Callback) ->
|
||||||
|
RawData = emqx_prometheus_cluster:raw_data(?MODULE, ?GET_PROM_DATA_MODE()),
|
||||||
|
|
||||||
|
%% Message Validation Metrics
|
||||||
|
RuleMetricDs = ?MG(?metrics_data_key, RawData),
|
||||||
|
ok = add_collect_family(Callback, message_validation_metrics_meta(), RuleMetricDs),
|
||||||
|
|
||||||
|
ok;
|
||||||
|
collect_mf(_, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
collect(<<"json">>) ->
|
||||||
|
RawData = emqx_prometheus_cluster:raw_data(?MODULE, ?GET_PROM_DATA_MODE()),
|
||||||
|
#{
|
||||||
|
message_validations => collect_json_data(?MG(?metrics_data_key, RawData))
|
||||||
|
};
|
||||||
|
collect(<<"prometheus">>) ->
|
||||||
|
prometheus_text_format:format(?PROMETHEUS_MESSAGE_VALIDATION_REGISTRY).
|
||||||
|
|
||||||
|
%%====================
|
||||||
|
%% API Helpers
|
||||||
|
|
||||||
|
add_collect_family(Callback, MetricWithType, Data) ->
|
||||||
|
_ = [add_collect_family(Name, Data, Callback, Type) || {Name, Type} <- MetricWithType],
|
||||||
|
ok.
|
||||||
|
|
||||||
|
add_collect_family(Name, Data, Callback, Type) ->
|
||||||
|
%% TODO: help document from Name
|
||||||
|
Callback(create_mf(Name, _Help = <<"">>, Type, ?MODULE, Data)).
|
||||||
|
|
||||||
|
collect_metrics(Name, Metrics) ->
|
||||||
|
collect_mv(Name, Metrics).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Collector
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%========================================
|
||||||
|
%% Message Validation Metrics
|
||||||
|
%%========================================
|
||||||
|
collect_mv(K = ?key_enabled, Data) -> gauge_metrics(?MG(K, Data));
|
||||||
|
collect_mv(K = ?key_matched, Data) -> counter_metrics(?MG(K, Data));
|
||||||
|
collect_mv(K = ?key_failed, Data) -> counter_metrics(?MG(K, Data));
|
||||||
|
collect_mv(K = ?key_succeeded, Data) -> counter_metrics(?MG(K, Data)).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%========================================
|
||||||
|
%% Message Validation Metrics
|
||||||
|
%%========================================
|
||||||
|
|
||||||
|
message_validation_metrics_meta() ->
|
||||||
|
[
|
||||||
|
{?key_enabled, gauge},
|
||||||
|
{?key_matched, counter},
|
||||||
|
{?key_failed, counter},
|
||||||
|
{?key_succeeded, counter}
|
||||||
|
].
|
||||||
|
|
||||||
|
message_validation_metric(names) ->
|
||||||
|
emqx_prometheus_cluster:metric_names(message_validation_metrics_meta()).
|
||||||
|
|
||||||
|
to_validation_data(Mode, Validations) ->
|
||||||
|
lists:foldl(
|
||||||
|
fun(#{name := Name} = Validation, Acc) ->
|
||||||
|
merge_acc_with_validations(Mode, Name, get_validation_metrics(Validation), Acc)
|
||||||
|
end,
|
||||||
|
maps:from_keys(message_validation_metric(names), []),
|
||||||
|
Validations
|
||||||
|
).
|
||||||
|
|
||||||
|
merge_acc_with_validations(Mode, Id, ValidationMetrics, PointsAcc) ->
|
||||||
|
maps:fold(
|
||||||
|
fun(K, V, AccIn) ->
|
||||||
|
AccIn#{K => [validation_point(Mode, Id, V) | ?MG(K, AccIn)]}
|
||||||
|
end,
|
||||||
|
PointsAcc,
|
||||||
|
ValidationMetrics
|
||||||
|
).
|
||||||
|
|
||||||
|
validation_point(Mode, Name, V) ->
|
||||||
|
{with_node_label(Mode, [{validation_name, Name}]), V}.
|
||||||
|
|
||||||
|
get_validation_metrics(#{name := Name, enable := Enabled} = _Rule) ->
|
||||||
|
#{counters := Counters} = emqx_message_validation_registry:get_metrics(Name),
|
||||||
|
#{
|
||||||
|
?key_enabled => emqx_prometheus_cluster:boolean_to_number(Enabled),
|
||||||
|
?key_matched => ?MG0('matched', Counters),
|
||||||
|
?key_failed => ?MG0('failed', Counters),
|
||||||
|
?key_succeeded => ?MG0('succeeded', Counters)
|
||||||
|
}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Collect functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%% merge / zip formatting funcs for type `application/json`
|
||||||
|
|
||||||
|
collect_json_data(Data) ->
|
||||||
|
emqx_prometheus_cluster:collect_json_data(Data, fun zip_json_message_validation_metrics/3).
|
||||||
|
|
||||||
|
zip_json_message_validation_metrics(Key, Points, [] = _AccIn) ->
|
||||||
|
lists:foldl(
|
||||||
|
fun({Labels, Metric}, AccIn2) ->
|
||||||
|
LabelsKVMap = maps:from_list(Labels),
|
||||||
|
Point = LabelsKVMap#{Key => Metric},
|
||||||
|
[Point | AccIn2]
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
Points
|
||||||
|
);
|
||||||
|
zip_json_message_validation_metrics(Key, Points, AllResultsAcc) ->
|
||||||
|
ThisKeyResult = lists:foldl(emqx_prometheus_cluster:point_to_map_fun(Key), [], Points),
|
||||||
|
lists:zipwith(fun maps:merge/2, AllResultsAcc, ThisKeyResult).
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%% Helper funcs
|
||||||
|
|
||||||
|
with_node_label(?PROM_DATA_MODE__NODE, Labels) ->
|
||||||
|
Labels;
|
||||||
|
with_node_label(?PROM_DATA_MODE__ALL_NODES_AGGREGATED, Labels) ->
|
||||||
|
Labels;
|
||||||
|
with_node_label(?PROM_DATA_MODE__ALL_NODES_UNAGGREGATED, Labels) ->
|
||||||
|
[{node, node()} | Labels].
|
||||||
|
|
||||||
|
%% END if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-endif.
|
|
@ -78,11 +78,12 @@ rule_engine {
|
||||||
">>).
|
">>).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
lists:flatten([
|
||||||
{group, '/prometheus/stats'},
|
{group, '/prometheus/stats'},
|
||||||
{group, '/prometheus/auth'},
|
{group, '/prometheus/auth'},
|
||||||
{group, '/prometheus/data_integration'}
|
{group, '/prometheus/data_integration'},
|
||||||
].
|
[{group, '/prometheus/message_validation'} || emqx_release:edition() == ee]
|
||||||
|
]).
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
TCs = emqx_common_test_helpers:all(?MODULE),
|
TCs = emqx_common_test_helpers:all(?MODULE),
|
||||||
|
@ -99,6 +100,7 @@ groups() ->
|
||||||
{'/prometheus/stats', ModeGroups},
|
{'/prometheus/stats', ModeGroups},
|
||||||
{'/prometheus/auth', ModeGroups},
|
{'/prometheus/auth', ModeGroups},
|
||||||
{'/prometheus/data_integration', ModeGroups},
|
{'/prometheus/data_integration', ModeGroups},
|
||||||
|
{'/prometheus/message_validation', ModeGroups},
|
||||||
{?PROM_DATA_MODE__NODE, AcceptGroups},
|
{?PROM_DATA_MODE__NODE, AcceptGroups},
|
||||||
{?PROM_DATA_MODE__ALL_NODES_AGGREGATED, AcceptGroups},
|
{?PROM_DATA_MODE__ALL_NODES_AGGREGATED, AcceptGroups},
|
||||||
{?PROM_DATA_MODE__ALL_NODES_UNAGGREGATED, AcceptGroups},
|
{?PROM_DATA_MODE__ALL_NODES_UNAGGREGATED, AcceptGroups},
|
||||||
|
@ -107,6 +109,7 @@ groups() ->
|
||||||
].
|
].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
emqx_common_test_helpers:clear_screen(),
|
||||||
meck:new(emqx_retainer, [non_strict, passthrough, no_history, no_link]),
|
meck:new(emqx_retainer, [non_strict, passthrough, no_history, no_link]),
|
||||||
meck:expect(emqx_retainer, retained_count, fun() -> 0 end),
|
meck:expect(emqx_retainer, retained_count, fun() -> 0 end),
|
||||||
meck:expect(
|
meck:expect(
|
||||||
|
@ -121,7 +124,7 @@ init_per_suite(Config) ->
|
||||||
|
|
||||||
application:load(emqx_auth),
|
application:load(emqx_auth),
|
||||||
Apps = emqx_cth_suite:start(
|
Apps = emqx_cth_suite:start(
|
||||||
[
|
lists:flatten([
|
||||||
emqx,
|
emqx,
|
||||||
{emqx_conf, ?EMQX_CONF},
|
{emqx_conf, ?EMQX_CONF},
|
||||||
emqx_auth,
|
emqx_auth,
|
||||||
|
@ -129,8 +132,12 @@ init_per_suite(Config) ->
|
||||||
emqx_rule_engine,
|
emqx_rule_engine,
|
||||||
emqx_bridge_http,
|
emqx_bridge_http,
|
||||||
emqx_connector,
|
emqx_connector,
|
||||||
{emqx_prometheus, emqx_prometheus_SUITE:legacy_conf_default()}
|
[
|
||||||
|
{emqx_message_validation, #{config => message_validation_config()}}
|
||||||
|
|| emqx_release:edition() == ee
|
||||||
],
|
],
|
||||||
|
{emqx_prometheus, emqx_prometheus_SUITE:legacy_conf_default()}
|
||||||
|
]),
|
||||||
#{
|
#{
|
||||||
work_dir => filename:join(?config(priv_dir, Config), ?MODULE)
|
work_dir => filename:join(?config(priv_dir, Config), ?MODULE)
|
||||||
}
|
}
|
||||||
|
@ -159,6 +166,8 @@ init_per_group('/prometheus/auth', Config) ->
|
||||||
[{module, emqx_prometheus_auth} | Config];
|
[{module, emqx_prometheus_auth} | Config];
|
||||||
init_per_group('/prometheus/data_integration', Config) ->
|
init_per_group('/prometheus/data_integration', Config) ->
|
||||||
[{module, emqx_prometheus_data_integration} | Config];
|
[{module, emqx_prometheus_data_integration} | Config];
|
||||||
|
init_per_group('/prometheus/message_validation', Config) ->
|
||||||
|
[{module, emqx_prometheus_message_validation} | Config];
|
||||||
init_per_group(?PROM_DATA_MODE__NODE, Config) ->
|
init_per_group(?PROM_DATA_MODE__NODE, Config) ->
|
||||||
[{mode, ?PROM_DATA_MODE__NODE} | Config];
|
[{mode, ?PROM_DATA_MODE__NODE} | Config];
|
||||||
init_per_group(?PROM_DATA_MODE__ALL_NODES_AGGREGATED, Config) ->
|
init_per_group(?PROM_DATA_MODE__ALL_NODES_AGGREGATED, Config) ->
|
||||||
|
@ -346,6 +355,8 @@ metric_meta(<<"emqx_schema_registrys_count">>) -> ?meta(0, 0, 0);
|
||||||
metric_meta(<<"emqx_rule_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
metric_meta(<<"emqx_rule_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
||||||
metric_meta(<<"emqx_action_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
metric_meta(<<"emqx_action_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
||||||
metric_meta(<<"emqx_connector_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
metric_meta(<<"emqx_connector_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
||||||
|
%% `/prometheus/message_validation`
|
||||||
|
metric_meta(<<"emqx_message_validation_", _Tail/binary>>) -> ?meta(1, 1, 2);
|
||||||
%% normal emqx metrics
|
%% normal emqx metrics
|
||||||
metric_meta(<<"emqx_", _Tail/binary>>) -> ?meta(0, 0, 1);
|
metric_meta(<<"emqx_", _Tail/binary>>) -> ?meta(0, 0, 1);
|
||||||
metric_meta(_) -> #{}.
|
metric_meta(_) -> #{}.
|
||||||
|
@ -810,5 +821,42 @@ assert_json_data__data_integration_overview(M, _) ->
|
||||||
).
|
).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
assert_json_data__message_validations(Ms, _) ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(M) ->
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
validation_name := _,
|
||||||
|
emqx_message_validation_enable := _,
|
||||||
|
emqx_message_validation_matched := _,
|
||||||
|
emqx_message_validation_failed := _,
|
||||||
|
emqx_message_validation_succeeded := _
|
||||||
|
},
|
||||||
|
M
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
Ms
|
||||||
|
).
|
||||||
|
|
||||||
|
message_validation_config() ->
|
||||||
|
Validation = #{
|
||||||
|
<<"enable">> => true,
|
||||||
|
<<"name">> => <<"my_validation">>,
|
||||||
|
<<"topics">> => [<<"t/#">>],
|
||||||
|
<<"strategy">> => <<"all_pass">>,
|
||||||
|
<<"failure_action">> => <<"drop">>,
|
||||||
|
<<"checks">> => [
|
||||||
|
#{
|
||||||
|
<<"type">> => <<"sql">>,
|
||||||
|
<<"sql">> => <<"select * where true">>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
<<"message_validation">> => #{
|
||||||
|
<<"validations">> => [Validation]
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
stop_apps(Apps) ->
|
stop_apps(Apps) ->
|
||||||
lists:foreach(fun application:stop/1, Apps).
|
lists:foreach(fun application:stop/1, Apps).
|
||||||
|
|
|
@ -540,7 +540,7 @@ printable_function_name(Mod, Func) ->
|
||||||
|
|
||||||
get_rule_metrics(Id) ->
|
get_rule_metrics(Id) ->
|
||||||
Nodes = emqx:running_nodes(),
|
Nodes = emqx:running_nodes(),
|
||||||
Results = emqx_metrics_proto_v1:get_metrics(Nodes, rule_metrics, Id, ?RPC_GET_METRICS_TIMEOUT),
|
Results = emqx_metrics_proto_v2:get_metrics(Nodes, rule_metrics, Id, ?RPC_GET_METRICS_TIMEOUT),
|
||||||
NodeResults = lists:zip(Nodes, Results),
|
NodeResults = lists:zip(Nodes, Results),
|
||||||
NodeMetrics = [format_metrics(Node, Metrics) || {Node, {ok, Metrics}} <- NodeResults],
|
NodeMetrics = [format_metrics(Node, Metrics) || {Node, {ok, Metrics}} <- NodeResults],
|
||||||
NodeErrors = [Result || Result = {_Node, {NOk, _}} <- NodeResults, NOk =/= ok],
|
NodeErrors = [Result || Result = {_Node, {NOk, _}} <- NodeResults, NOk =/= ok],
|
||||||
|
|
|
@ -21,6 +21,12 @@ emqx_message_validation_http_api {
|
||||||
enable_disable_validation.desc:
|
enable_disable_validation.desc:
|
||||||
"""Enable or disable a particular validation"""
|
"""Enable or disable a particular validation"""
|
||||||
|
|
||||||
|
get_validation_metrics.desc:
|
||||||
|
"""Get metrics for a particular validation"""
|
||||||
|
|
||||||
|
reset_validation_metrics.desc:
|
||||||
|
"""Reset metrics for a particular validation"""
|
||||||
|
|
||||||
param_path_name.desc:
|
param_path_name.desc:
|
||||||
"""Validation name"""
|
"""Validation name"""
|
||||||
|
|
||||||
|
|
|
@ -25,4 +25,9 @@ get_prom_data_integration_data.desc:
|
||||||
get_prom_data_integration_data.label:
|
get_prom_data_integration_data.label:
|
||||||
"""Prometheus Metrics for Data Integration"""
|
"""Prometheus Metrics for Data Integration"""
|
||||||
|
|
||||||
|
get_prom_message_validation.desc:
|
||||||
|
"""Get Prometheus Metrics for Message Validation"""
|
||||||
|
get_prom_message_validation.label:
|
||||||
|
"""Prometheus Metrics for Message Validation"""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,10 @@ matrix() {
|
||||||
entries+=("$(format_app_entry "$app" 1 emqx "$runner")")
|
entries+=("$(format_app_entry "$app" 1 emqx "$runner")")
|
||||||
entries+=("$(format_app_entry "$app" 1 emqx-enterprise "$runner")")
|
entries+=("$(format_app_entry "$app" 1 emqx-enterprise "$runner")")
|
||||||
;;
|
;;
|
||||||
|
apps/emqx_prometheus)
|
||||||
|
entries+=("$(format_app_entry "$app" 1 emqx "$runner")")
|
||||||
|
entries+=("$(format_app_entry "$app" 1 emqx-enterprise "$runner")")
|
||||||
|
;;
|
||||||
apps/emqx_rule_engine)
|
apps/emqx_rule_engine)
|
||||||
entries+=("$(format_app_entry "$app" 1 emqx "$runner")")
|
entries+=("$(format_app_entry "$app" 1 emqx "$runner")")
|
||||||
entries+=("$(format_app_entry "$app" 1 emqx-enterprise "$runner")")
|
entries+=("$(format_app_entry "$app" 1 emqx-enterprise "$runner")")
|
||||||
|
|
Loading…
Reference in New Issue