Merge branch 'dev/ee5.0' into refactor-mqtt-bridge

This commit is contained in:
JianBo He 2022-09-01 09:30:37 +08:00 committed by GitHub
commit a5ac5b6f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 647 additions and 230 deletions

View File

@ -173,7 +173,7 @@ get_metrics(Name, Id) ->
inc(Name, Id, Metric) -> inc(Name, Id, Metric) ->
inc(Name, Id, Metric, 1). inc(Name, Id, Metric, 1).
-spec inc(handler_name(), metric_id(), atom(), pos_integer()) -> ok. -spec inc(handler_name(), metric_id(), atom(), integer()) -> ok.
inc(Name, Id, Metric, Val) -> inc(Name, Id, Metric, Val) ->
counters:add(get_ref(Name, Id), idx_metric(Name, Id, Metric), Val). counters:add(get_ref(Name, Id), idx_metric(Name, Id, Metric), Val).

View File

@ -78,36 +78,149 @@ emqx_bridge_schema {
} }
} }
metric_batched {
desc {
en: """Count of messages that are currently accumulated in memory waiting for sending in one batch."""
zh: """当前积压在内存里,等待批量发送的消息个数"""
}
label: {
en: "Batched"
zh: "等待批量发送"
}
}
metric_dropped {
desc {
en: """Count of messages dropped."""
zh: """被丢弃的消息个数。"""
}
label: {
en: "Dropped"
zh: "丢弃"
}
}
metric_dropped_other {
desc {
en: """Count of messages dropped due to other reasons."""
zh: """因为其他原因被丢弃的消息个数。"""
}
label: {
en: "Dropped Other"
zh: "其他丢弃"
}
}
metric_dropped_queue_full {
desc {
en: """Count of messages dropped due to the queue is full."""
zh: """因为队列已满被丢弃的消息个数。"""
}
label: {
en: "Dropped Queue Full"
zh: "队列已满被丢弃"
}
}
metric_dropped_queue_not_enabled {
desc {
en: """Count of messages dropped due to the queue is not enabled."""
zh: """因为队列未启用被丢弃的消息个数。"""
}
label: {
en: "Dropped Queue Disabled"
zh: "队列未启用被丢弃"
}
}
metric_dropped_resource_not_found {
desc {
en: """Count of messages dropped due to the resource is not found."""
zh: """因为资源不存在被丢弃的消息个数。"""
}
label: {
en: "Dropped Resource NotFound"
zh: "资源不存在被丢弃"
}
}
metric_dropped_resource_stopped {
desc {
en: """Count of messages dropped due to the resource is stopped."""
zh: """因为资源已停用被丢弃的消息个数。"""
}
label: {
en: "Dropped Resource Stopped"
zh: "资源停用被丢弃"
}
}
metric_matched { metric_matched {
desc { desc {
en: """Count of this bridge is queried""" en: """Count of this bridge is matched and queried."""
zh: """Bridge 执行操作的次数""" zh: """Bridge 被匹配到(被请求)的次数。"""
} }
label: { label: {
en: "Bridge Matched" en: "Matched"
zh: "Bridge 执行操作的次数" zh: "匹配次数"
} }
} }
metric_success { metric_queued {
desc { desc {
en: """Count of query success""" en: """Count of messages that are currently queued."""
zh: """Bridge 执行操作成功的次数""" zh: """当前被缓存到磁盘队列的消息个数。"""
} }
label: { label: {
en: "Bridge Success" en: "Queued"
zh: "Bridge 执行操作成功的次数" zh: "被缓存"
}
}
metric_sent {
desc {
en: """Count of messages that are sent by this bridge."""
zh: """已经发送出去的消息个数。"""
}
label: {
en: "Sent"
zh: "已发送"
}
}
metric_sent_exception {
desc {
en: """Count of messages that were sent but exceptions occur."""
zh: """发送出现异常的消息个数。"""
}
label: {
en: "Sent Exception"
zh: "发送异常"
} }
} }
metric_failed { metric_sent_failed {
desc { desc {
en: """Count of query failed""" en: """Count of messages that sent failed."""
zh: """Bridge 执行操作失败的次数""" zh: """发送失败的消息个数。"""
} }
label: { label: {
en: "Bridge Failed" en: "Sent Failed"
zh: "Bridge 执行操作失败的次数" zh: "发送失败"
}
}
metric_sent_inflight {
desc {
en: """Count of messages that were sent asynchronously but ACKs are not received."""
zh: """已异步地发送但没有收到 ACK 的消息个数。"""
}
label: {
en: "Sent Inflight"
zh: "已发送未确认"
}
}
metric_sent_success {
desc {
en: """Count of messages that sent successfully."""
zh: """已经发送成功的消息个数。"""
}
label: {
en: "Sent Success"
zh: "发送成功"
} }
} }
@ -144,6 +257,17 @@ emqx_bridge_schema {
} }
} }
metric_received {
desc {
en: """Count of messages that is received from the remote system."""
zh: """从远程系统收到的消息个数。"""
}
label: {
en: "Received"
zh: "已接收"
}
}
desc_bridges { desc_bridges {
desc { desc {
en: """Configuration for MQTT bridges.""" en: """Configuration for MQTT bridges."""

View File

@ -0,0 +1,99 @@
-define(EMPTY_METRICS,
?METRICS(
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
)
).
-define(METRICS(
Batched,
Dropped,
DroppedOther,
DroppedQueueFull,
DroppedQueueNotEnabled,
DroppedResourceNotFound,
DroppedResourceStopped,
Matched,
Queued,
Sent,
SentExcpt,
SentFailed,
SentInflight,
SentSucc,
RATE,
RATE_5,
RATE_MAX,
Rcvd
),
#{
'batched' => Batched,
'dropped' => Dropped,
'dropped.other' => DroppedOther,
'dropped.queue_full' => DroppedQueueFull,
'dropped.queue_not_enabled' => DroppedQueueNotEnabled,
'dropped.resource_not_found' => DroppedResourceNotFound,
'dropped.resource_stopped' => DroppedResourceStopped,
'matched' => Matched,
'queued' => Queued,
'sent' => Sent,
'sent.exception' => SentExcpt,
'sent.failed' => SentFailed,
'sent.inflight' => SentInflight,
'sent.success' => SentSucc,
rate => RATE,
rate_last5m => RATE_5,
rate_max => RATE_MAX,
received => Rcvd
}
).
-define(metrics(
Batched,
Dropped,
DroppedOther,
DroppedQueueFull,
DroppedQueueNotEnabled,
DroppedResourceNotFound,
DroppedResourceStopped,
Matched,
Queued,
Sent,
SentExcpt,
SentFailed,
SentInflight,
SentSucc,
RATE,
RATE_5,
RATE_MAX,
Rcvd
),
#{
'batched' := Batched,
'dropped' := Dropped,
'dropped.other' := DroppedOther,
'dropped.queue_full' := DroppedQueueFull,
'dropped.queue_not_enabled' := DroppedQueueNotEnabled,
'dropped.resource_not_found' := DroppedResourceNotFound,
'dropped.resource_stopped' := DroppedResourceStopped,
'matched' := Matched,
'queued' := Queued,
'sent' := Sent,
'sent.exception' := SentExcpt,
'sent.failed' := SentFailed,
'sent.inflight' := SentInflight,
'sent.success' := SentSucc,
rate := RATE,
rate_last5m := RATE_5,
rate_max := RATE_MAX,
received := Rcvd
}
).
-define(METRICS_EXAMPLE, #{
metrics => ?EMPTY_METRICS,
node_metrics => [
#{
node => node(),
metrics => ?EMPTY_METRICS
}
]
}).

View File

@ -21,6 +21,7 @@
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx_resource/include/emqx_resource.hrl"). -include_lib("emqx_resource/include/emqx_resource.hrl").
-include_lib("emqx_bridge/include/emqx_bridge.hrl").
-import(hoconsc, [mk/2, array/1, enum/1]). -import(hoconsc, [mk/2, array/1, enum/1]).
@ -58,23 +59,6 @@
end end
). ).
-define(METRICS(MATCH, SUCC, FAILED, RATE, RATE_5, RATE_MAX), #{
matched => MATCH,
success => SUCC,
failed => FAILED,
rate => RATE,
rate_last5m => RATE_5,
rate_max => RATE_MAX
}).
-define(metrics(MATCH, SUCC, FAILED, RATE, RATE_5, RATE_MAX), #{
matched := MATCH,
success := SUCC,
failed := FAILED,
rate := RATE,
rate_last5m := RATE_5,
rate_max := RATE_MAX
}).
namespace() -> "bridge". namespace() -> "bridge".
api_spec() -> api_spec() ->
@ -194,11 +178,11 @@ method_example(_Type, put) ->
maybe_with_metrics_example(TypeNameExam, get) -> maybe_with_metrics_example(TypeNameExam, get) ->
TypeNameExam#{ TypeNameExam#{
metrics => ?METRICS(0, 0, 0, 0, 0, 0), metrics => ?EMPTY_METRICS,
node_metrics => [ node_metrics => [
#{ #{
node => node(), node => node(),
metrics => ?METRICS(0, 0, 0, 0, 0, 0) metrics => ?EMPTY_METRICS
} }
] ]
}; };
@ -218,7 +202,16 @@ info_example_basic(webhook) ->
ssl => #{enable => false}, ssl => #{enable => false},
local_topic => <<"emqx_webhook/#">>, local_topic => <<"emqx_webhook/#">>,
method => post, method => post,
body => <<"${payload}">> body => <<"${payload}">>,
resource_opts => #{
worker_pool_size => 1,
health_check_interval => 15000,
auto_restart_interval => 15000,
query_mode => sync,
async_inflight_window => 100,
enable_queue => true,
max_queue_bytes => 1024 * 1024 * 1024
}
}; };
info_example_basic(mqtt) -> info_example_basic(mqtt) ->
(mqtt_main_example())#{ (mqtt_main_example())#{
@ -627,19 +620,37 @@ collect_metrics(Bridges) ->
[maps:with([node, metrics], B) || B <- Bridges]. [maps:with([node, metrics], B) || B <- Bridges].
aggregate_metrics(AllMetrics) -> aggregate_metrics(AllMetrics) ->
InitMetrics = ?METRICS(0, 0, 0, 0, 0, 0), InitMetrics = ?EMPTY_METRICS,
lists:foldl( lists:foldl(
fun( fun(
#{metrics := ?metrics(Match1, Succ1, Failed1, Rate1, Rate5m1, RateMax1)}, #{
?metrics(Match0, Succ0, Failed0, Rate0, Rate5m0, RateMax0) metrics := ?metrics(
M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16, M17, M18
)
},
?metrics(
N1, N2, N3, N4, N5, N6, N7, N8, N9, N10, N11, N12, N13, N14, N15, N16, N17, N18
)
) -> ) ->
?METRICS( ?METRICS(
Match1 + Match0, M1 + N1,
Succ1 + Succ0, M2 + N2,
Failed1 + Failed0, M3 + N3,
Rate1 + Rate0, M4 + N4,
Rate5m1 + Rate5m0, M5 + N5,
RateMax1 + RateMax0 M6 + N6,
M7 + N7,
M8 + N8,
M9 + N9,
M10 + N10,
M11 + N11,
M12 + N12,
M13 + N13,
M14 + N14,
M15 + N15,
M16 + N16,
M17 + N17,
M18 + N18
) )
end, end,
InitMetrics, InitMetrics,
@ -668,12 +679,47 @@ format_resp(
}. }.
format_metrics(#{ format_metrics(#{
counters := #{failed := Failed, exception := Ex, matched := Match, success := Succ}, counters := #{
'batched' := Batched,
'dropped' := Dropped,
'dropped.other' := DroppedOther,
'dropped.queue_full' := DroppedQueueFull,
'dropped.queue_not_enabled' := DroppedQueueNotEnabled,
'dropped.resource_not_found' := DroppedResourceNotFound,
'dropped.resource_stopped' := DroppedResourceStopped,
'matched' := Matched,
'queued' := Queued,
'sent' := Sent,
'sent.exception' := SentExcpt,
'sent.failed' := SentFailed,
'sent.inflight' := SentInflight,
'sent.success' := SentSucc,
'received' := Rcvd
},
rate := #{ rate := #{
matched := #{current := Rate, last5m := Rate5m, max := RateMax} matched := #{current := Rate, last5m := Rate5m, max := RateMax}
} }
}) -> }) ->
?METRICS(Match, Succ, Failed + Ex, Rate, Rate5m, RateMax). ?METRICS(
Batched,
Dropped,
DroppedOther,
DroppedQueueFull,
DroppedQueueNotEnabled,
DroppedResourceNotFound,
DroppedResourceStopped,
Matched,
Queued,
Sent,
SentExcpt,
SentFailed,
SentInflight,
SentSucc,
Rate,
Rate5m,
RateMax,
Rcvd
).
fill_defaults(Type, RawConf) -> fill_defaults(Type, RawConf) ->
PackedConf = pack_bridge_conf(Type, RawConf), PackedConf = pack_bridge_conf(Type, RawConf),

View File

@ -102,16 +102,31 @@ fields(bridges) ->
] ++ ee_fields_bridges(); ] ++ ee_fields_bridges();
fields("metrics") -> fields("metrics") ->
[ [
{"batched", mk(integer(), #{desc => ?DESC("metric_batched")})},
{"dropped", mk(integer(), #{desc => ?DESC("metric_dropped")})},
{"dropped.other", mk(integer(), #{desc => ?DESC("metric_dropped_other")})},
{"dropped.queue_full", mk(integer(), #{desc => ?DESC("metric_dropped_queue_full")})},
{"dropped.queue_not_enabled",
mk(integer(), #{desc => ?DESC("metric_dropped_queue_not_enabled")})},
{"dropped.resource_not_found",
mk(integer(), #{desc => ?DESC("metric_dropped_resource_not_found")})},
{"dropped.resource_stopped",
mk(integer(), #{desc => ?DESC("metric_dropped_resource_stopped")})},
{"matched", mk(integer(), #{desc => ?DESC("metric_matched")})}, {"matched", mk(integer(), #{desc => ?DESC("metric_matched")})},
{"success", mk(integer(), #{desc => ?DESC("metric_success")})}, {"queued", mk(integer(), #{desc => ?DESC("metric_queued")})},
{"failed", mk(integer(), #{desc => ?DESC("metric_failed")})}, {"sent", mk(integer(), #{desc => ?DESC("metric_sent")})},
{"sent.exception", mk(integer(), #{desc => ?DESC("metric_sent_exception")})},
{"sent.failed", mk(integer(), #{desc => ?DESC("metric_sent_failed")})},
{"sent.inflight", mk(integer(), #{desc => ?DESC("metric_sent_inflight")})},
{"sent.success", mk(integer(), #{desc => ?DESC("metric_sent_success")})},
{"rate", mk(float(), #{desc => ?DESC("metric_rate")})}, {"rate", mk(float(), #{desc => ?DESC("metric_rate")})},
{"rate_max", mk(float(), #{desc => ?DESC("metric_rate_max")})}, {"rate_max", mk(float(), #{desc => ?DESC("metric_rate_max")})},
{"rate_last5m", {"rate_last5m",
mk( mk(
float(), float(),
#{desc => ?DESC("metric_rate_last5m")} #{desc => ?DESC("metric_rate_last5m")}
)} )},
{"received", mk(float(), #{desc => ?DESC("metric_received")})}
]; ];
fields("node_metrics") -> fields("node_metrics") ->
[ [

View File

@ -66,15 +66,6 @@
} }
}). }).
-define(metrics(MATCH, SUCC, FAILED, SPEED, SPEED5M, SPEEDMAX), #{
<<"matched">> := MATCH,
<<"success">> := SUCC,
<<"failed">> := FAILED,
<<"rate">> := SPEED,
<<"rate_last5m">> := SPEED5M,
<<"rate_max">> := SPEEDMAX
}).
inspect(Selected, _Envs, _Args) -> inspect(Selected, _Envs, _Args) ->
persistent_term:put(?MODULE, #{inspect => Selected}). persistent_term:put(?MODULE, #{inspect => Selected}).
@ -185,6 +176,23 @@ t_mqtt_conn_bridge_ingress(_) ->
end end
), ),
%% verify the metrics of the bridge
{ok, 200, BridgeStr} = request(get, uri(["bridges", BridgeIDIngress]), []),
?assertMatch(
#{
<<"metrics">> := #{<<"matched">> := 0, <<"received">> := 1},
<<"node_metrics">> :=
[
#{
<<"node">> := _,
<<"metrics">> :=
#{<<"matched">> := 0, <<"received">> := 1}
}
]
},
jsx:decode(BridgeStr)
),
%% delete the bridge %% delete the bridge
{ok, 204, <<>>} = request(delete, uri(["bridges", BridgeIDIngress]), []), {ok, 204, <<>>} = request(delete, uri(["bridges", BridgeIDIngress]), []),
{ok, 200, <<"[]">>} = request(get, uri(["bridges"]), []), {ok, 200, <<"[]">>} = request(get, uri(["bridges"]), []),
@ -237,9 +245,15 @@ t_mqtt_conn_bridge_egress(_) ->
{ok, 200, BridgeStr} = request(get, uri(["bridges", BridgeIDEgress]), []), {ok, 200, BridgeStr} = request(get, uri(["bridges", BridgeIDEgress]), []),
?assertMatch( ?assertMatch(
#{ #{
<<"metrics">> := ?metrics(1, 1, 0, _, _, _), <<"metrics">> := #{<<"matched">> := 1, <<"sent.success">> := 1, <<"sent.failed">> := 0},
<<"node_metrics">> := <<"node_metrics">> :=
[#{<<"node">> := _, <<"metrics">> := ?metrics(1, 1, 0, _, _, _)}] [
#{
<<"node">> := _,
<<"metrics">> :=
#{<<"matched">> := 1, <<"sent.success">> := 1, <<"sent.failed">> := 0}
}
]
}, },
jsx:decode(BridgeStr) jsx:decode(BridgeStr)
), ),
@ -337,6 +351,23 @@ t_ingress_mqtt_bridge_with_rules(_) ->
persistent_term:get(?MODULE) persistent_term:get(?MODULE)
), ),
%% verify the metrics of the bridge
{ok, 200, BridgeStr} = request(get, uri(["bridges", BridgeIDIngress]), []),
?assertMatch(
#{
<<"metrics">> := #{<<"matched">> := 0, <<"received">> := 1},
<<"node_metrics">> :=
[
#{
<<"node">> := _,
<<"metrics">> :=
#{<<"matched">> := 0, <<"received">> := 1}
}
]
},
jsx:decode(BridgeStr)
),
{ok, 204, <<>>} = request(delete, uri(["rules", RuleId]), []), {ok, 204, <<>>} = request(delete, uri(["rules", RuleId]), []),
{ok, 204, <<>>} = request(delete, uri(["bridges", BridgeIDIngress]), []). {ok, 204, <<>>} = request(delete, uri(["bridges", BridgeIDIngress]), []).
@ -433,9 +464,16 @@ t_egress_mqtt_bridge_with_rules(_) ->
{ok, 200, BridgeStr} = request(get, uri(["bridges", BridgeIDEgress]), []), {ok, 200, BridgeStr} = request(get, uri(["bridges", BridgeIDEgress]), []),
?assertMatch( ?assertMatch(
#{ #{
<<"metrics">> := ?metrics(2, 2, 0, _, _, _), <<"metrics">> := #{<<"matched">> := 2, <<"sent.success">> := 2, <<"sent.failed">> := 0},
<<"node_metrics">> := <<"node_metrics">> :=
[#{<<"node">> := _, <<"metrics">> := ?metrics(2, 2, 0, _, _, _)}] [
#{
<<"node">> := _,
<<"metrics">> := #{
<<"matched">> := 2, <<"sent.success">> := 2, <<"sent.failed">> := 0
}
}
]
}, },
jsx:decode(BridgeStr) jsx:decode(BridgeStr)
), ),

View File

@ -275,7 +275,7 @@ on_query(
), ),
NRequest = formalize_request(Method, BasePath, Request), NRequest = formalize_request(Method, BasePath, Request),
case case
Result = ehttpc:request( ehttpc:request(
case KeyOrNum of case KeyOrNum of
undefined -> PoolName; undefined -> PoolName;
_ -> {PoolName, KeyOrNum} _ -> {PoolName, KeyOrNum}
@ -286,33 +286,42 @@ on_query(
Retry Retry
) )
of of
{error, Reason} -> {error, econnrefused} ->
?SLOG(warning, #{
msg => "http_connector_do_request_failed",
reason => econnrefused,
connector => InstId
}),
{recoverable_error, econnrefused};
{error, Reason} = Result ->
?SLOG(error, #{ ?SLOG(error, #{
msg => "http_connector_do_request_failed", msg => "http_connector_do_request_failed",
request => NRequest, request => NRequest,
reason => Reason, reason => Reason,
connector => InstId connector => InstId
}); }),
{ok, StatusCode, _} when StatusCode >= 200 andalso StatusCode < 300 -> Result;
ok; {ok, StatusCode, _} = Result when StatusCode >= 200 andalso StatusCode < 300 ->
{ok, StatusCode, _, _} when StatusCode >= 200 andalso StatusCode < 300 -> Result;
ok; {ok, StatusCode, _, _} = Result when StatusCode >= 200 andalso StatusCode < 300 ->
{ok, StatusCode, _} -> Result;
{ok, StatusCode, Headers} ->
?SLOG(error, #{ ?SLOG(error, #{
msg => "http connector do request, received error response", msg => "http connector do request, received error response",
request => NRequest, request => NRequest,
connector => InstId, connector => InstId,
status_code => StatusCode status_code => StatusCode
}); }),
{ok, StatusCode, _, _} -> {error, #{status_code => StatusCode, headers => Headers}};
{ok, StatusCode, Headers, Body} ->
?SLOG(error, #{ ?SLOG(error, #{
msg => "http connector do request, received error response", msg => "http connector do request, received error response",
request => NRequest, request => NRequest,
connector => InstId, connector => InstId,
status_code => StatusCode status_code => StatusCode
}) }),
end, {error, #{status_code => StatusCode, headers => Headers, body => Body}}
Result. end.
on_query_async(InstId, {send_message, Msg}, ReplyFunAndArgs, State) -> on_query_async(InstId, {send_message, Msg}, ReplyFunAndArgs, State) ->
case maps:get(request, State, undefined) of case maps:get(request, State, undefined) of

View File

@ -136,8 +136,7 @@ drop_bridge(Name) ->
%% When use this bridge as a data source, ?MODULE:on_message_received will be called %% When use this bridge as a data source, ?MODULE:on_message_received will be called
%% if the bridge received msgs from the remote broker. %% if the bridge received msgs from the remote broker.
on_message_received(Msg, HookPoint, ResId) -> on_message_received(Msg, HookPoint, ResId) ->
emqx_resource:inc_matched(ResId), emqx_resource:inc_received(ResId),
emqx_resource:inc_success(ResId),
emqx:run_hook(HookPoint, [Msg]). emqx:run_hook(HookPoint, [Msg]).
%% =================================================================== %% ===================================================================
@ -236,20 +235,20 @@ make_forward_confs(undefined) ->
make_forward_confs(FrowardConf) -> make_forward_confs(FrowardConf) ->
FrowardConf. FrowardConf.
basic_config(#{ basic_config(
#{
server := Server, server := Server,
reconnect_interval := ReconnIntv, reconnect_interval := ReconnIntv,
proto_ver := ProtoVer, proto_ver := ProtoVer,
bridge_mode := BridgeMode, bridge_mode := BridgeMode,
username := User,
password := Password,
clean_start := CleanStart, clean_start := CleanStart,
keepalive := KeepAlive, keepalive := KeepAlive,
retry_interval := RetryIntv, retry_interval := RetryIntv,
max_inflight := MaxInflight, max_inflight := MaxInflight,
ssl := #{enable := EnableSsl} = Ssl ssl := #{enable := EnableSsl} = Ssl
}) -> } = Conf
#{ ) ->
BaiscConf = #{
%% connection opts %% connection opts
server => Server, server => Server,
%% 30s %% 30s
@ -263,8 +262,6 @@ basic_config(#{
%% non-standard mqtt connection packets will be filtered out by LB. %% non-standard mqtt connection packets will be filtered out by LB.
%% So let's disable bridge_mode. %% So let's disable bridge_mode.
bridge_mode => BridgeMode, bridge_mode => BridgeMode,
username => User,
password => Password,
clean_start => CleanStart, clean_start => CleanStart,
keepalive => ms_to_s(KeepAlive), keepalive => ms_to_s(KeepAlive),
retry_interval => RetryIntv, retry_interval => RetryIntv,
@ -272,7 +269,20 @@ basic_config(#{
ssl => EnableSsl, ssl => EnableSsl,
ssl_opts => maps:to_list(maps:remove(enable, Ssl)), ssl_opts => maps:to_list(maps:remove(enable, Ssl)),
if_record_metrics => true if_record_metrics => true
}. },
maybe_put_fields([username, password], Conf, BaiscConf).
maybe_put_fields(Fields, Conf, Acc0) ->
lists:foldl(
fun(Key, Acc) ->
case maps:find(Key, Conf) of
error -> Acc;
{ok, Val} -> Acc#{Key => Val}
end
end,
Acc0,
Fields
).
ms_to_s(Ms) -> ms_to_s(Ms) ->
erlang:ceil(Ms / 1000). erlang:ceil(Ms / 1000).

View File

@ -414,6 +414,13 @@ on_sql_query(
LogMeta#{msg => "mysql_connector_prepare_query_failed", reason => not_prepared} LogMeta#{msg => "mysql_connector_prepare_query_failed", reason => not_prepared}
), ),
Error; Error;
{error, {1053, <<"08S01">>, Reason}} ->
%% mysql sql server shutdown in progress
?SLOG(
error,
LogMeta#{msg => "mysql_connector_do_sql_query_failed", reason => Reason}
),
{recoverable_error, Reason};
{error, Reason} -> {error, Reason} ->
?SLOG( ?SLOG(
error, error,

View File

@ -101,7 +101,7 @@ fields("server_configs") ->
mk( mk(
binary(), binary(),
#{ #{
default => "emqx", default => undefined,
desc => ?DESC("username") desc => ?DESC("username")
} }
)}, )},
@ -109,7 +109,7 @@ fields("server_configs") ->
mk( mk(
binary(), binary(),
#{ #{
default => "emqx", default => undefined,
format => <<"password">>, format => <<"password">>,
desc => ?DESC("password") desc => ?DESC("password")
} }

View File

@ -143,7 +143,7 @@ emqx_resource_schema {
} }
} }
queue_max_bytes { max_queue_bytes {
desc { desc {
en: """Maximum queue storage.""" en: """Maximum queue storage."""
zh: """消息队列的最大长度。""" zh: """消息队列的最大长度。"""

View File

@ -68,7 +68,7 @@
batch_size => pos_integer(), batch_size => pos_integer(),
batch_time => pos_integer(), batch_time => pos_integer(),
enable_queue => boolean(), enable_queue => boolean(),
queue_max_bytes => pos_integer(), max_queue_bytes => pos_integer(),
query_mode => query_mode(), query_mode => query_mode(),
resume_interval => pos_integer(), resume_interval => pos_integer(),
async_inflight_window => pos_integer() async_inflight_window => pos_integer()
@ -77,12 +77,15 @@
ok ok
| {ok, term()} | {ok, term()}
| {error, term()} | {error, term()}
| {resource_down, term()}. | {recoverable_error, term()}.
-define(WORKER_POOL_SIZE, 16). -define(WORKER_POOL_SIZE, 16).
-define(DEFAULT_QUEUE_SIZE, 1024 * 1024 * 1024). -define(DEFAULT_QUEUE_SEG_SIZE, 10 * 1024 * 1024).
-define(DEFAULT_QUEUE_SIZE_RAW, <<"1GB">>). -define(DEFAULT_QUEUE_SEG_SIZE_RAW, <<"10MB">>).
-define(DEFAULT_QUEUE_SIZE, 100 * 1024 * 1024 * 1024).
-define(DEFAULT_QUEUE_SIZE_RAW, <<"100GB">>).
%% count %% count
-define(DEFAULT_BATCH_SIZE, 100). -define(DEFAULT_BATCH_SIZE, 100).

View File

@ -110,7 +110,7 @@
list_group_instances/1 list_group_instances/1
]). ]).
-export([inc_metrics_funcs/1, inc_matched/1, inc_success/1, inc_failed/1]). -export([inc_received/1]).
-optional_callbacks([ -optional_callbacks([
on_query/3, on_query/3,
@ -443,19 +443,8 @@ check_and_do(ResourceType, RawConfig, Do) when is_function(Do) ->
%% ================================================================================= %% =================================================================================
inc_matched(ResId) -> inc_received(ResId) ->
emqx_metrics_worker:inc(?RES_METRICS, ResId, matched). emqx_metrics_worker:inc(?RES_METRICS, ResId, 'received').
inc_success(ResId) ->
emqx_metrics_worker:inc(?RES_METRICS, ResId, success).
inc_failed(ResId) ->
emqx_metrics_worker:inc(?RES_METRICS, ResId, failed).
filter_instances(Filter) -> filter_instances(Filter) ->
[Id || #{id := Id, mod := Mod} <- list_instances_verbose(), Filter(Id, Mod)]. [Id || #{id := Id, mod := Mod} <- list_instances_verbose(), Filter(Id, Mod)].
inc_metrics_funcs(ResId) ->
OnSucc = [{fun ?MODULE:inc_success/1, ResId}],
OnFailed = [{fun ?MODULE:inc_failed/1, ResId}],
{OnSucc, OnFailed}.

View File

@ -128,7 +128,23 @@ create(MgrId, ResId, Group, ResourceType, Config, Opts) ->
ok = emqx_metrics_worker:create_metrics( ok = emqx_metrics_worker:create_metrics(
?RES_METRICS, ?RES_METRICS,
ResId, ResId,
[matched, success, failed, exception, resource_down], [
'matched',
'sent',
'dropped',
'queued',
'batched',
'sent.success',
'sent.failed',
'sent.exception',
'sent.inflight',
'dropped.queue_not_enabled',
'dropped.queue_full',
'dropped.resource_not_found',
'dropped.resource_stopped',
'dropped.other',
'received'
],
[matched] [matched]
), ),
ok = emqx_resource_worker_sup:start_workers(ResId, Opts), ok = emqx_resource_worker_sup:start_workers(ResId, Opts),
@ -539,6 +555,7 @@ with_health_check(Data, Func) ->
HCRes = emqx_resource:call_health_check(Data#data.manager_id, Data#data.mod, Data#data.state), HCRes = emqx_resource:call_health_check(Data#data.manager_id, Data#data.mod, Data#data.state),
{Status, NewState, Err} = parse_health_check_result(HCRes, Data), {Status, NewState, Err} = parse_health_check_result(HCRes, Data),
_ = maybe_alarm(Status, ResId), _ = maybe_alarm(Status, ResId),
ok = maybe_resume_resource_workers(Status),
UpdatedData = Data#data{ UpdatedData = Data#data{
state = NewState, status = Status, error = Err state = NewState, status = Status, error = Err
}, },
@ -559,6 +576,16 @@ maybe_alarm(_Status, ResId) ->
<<"resource down: ", ResId/binary>> <<"resource down: ", ResId/binary>>
). ).
maybe_resume_resource_workers(connected) ->
lists:foreach(
fun({_, Pid, _, _}) ->
emqx_resource_worker:resume(Pid)
end,
supervisor:which_children(emqx_resource_worker_sup)
);
maybe_resume_resource_workers(_) ->
ok.
maybe_clear_alarm(<<?TEST_ID_PREFIX, _/binary>>) -> maybe_clear_alarm(<<?TEST_ID_PREFIX, _/binary>>) ->
ok; ok;
maybe_clear_alarm(ResId) -> maybe_clear_alarm(ResId) ->

View File

@ -77,23 +77,27 @@ start_link(Id, Index, Opts) ->
sync_query(Id, Request, Opts) -> sync_query(Id, Request, Opts) ->
PickKey = maps:get(pick_key, Opts, self()), PickKey = maps:get(pick_key, Opts, self()),
Timeout = maps:get(timeout, Opts, infinity), Timeout = maps:get(timeout, Opts, infinity),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'matched'),
pick_call(Id, PickKey, {query, Request, Opts}, Timeout). pick_call(Id, PickKey, {query, Request, Opts}, Timeout).
-spec async_query(id(), request(), query_opts()) -> Result :: term(). -spec async_query(id(), request(), query_opts()) -> Result :: term().
async_query(Id, Request, Opts) -> async_query(Id, Request, Opts) ->
PickKey = maps:get(pick_key, Opts, self()), PickKey = maps:get(pick_key, Opts, self()),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'matched'),
pick_cast(Id, PickKey, {query, Request, Opts}). pick_cast(Id, PickKey, {query, Request, Opts}).
%% simple query the resource without batching and queuing messages. %% simple query the resource without batching and queuing messages.
-spec simple_sync_query(id(), request()) -> Result :: term(). -spec simple_sync_query(id(), request()) -> Result :: term().
simple_sync_query(Id, Request) -> simple_sync_query(Id, Request) ->
Result = call_query(sync, Id, ?QUERY(self(), Request), #{}), Result = call_query(sync, Id, ?QUERY(self(), Request), #{}),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'matched'),
_ = handle_query_result(Id, Result, false), _ = handle_query_result(Id, Result, false),
Result. Result.
-spec simple_async_query(id(), request(), reply_fun()) -> Result :: term(). -spec simple_async_query(id(), request(), reply_fun()) -> Result :: term().
simple_async_query(Id, Request, ReplyFun) -> simple_async_query(Id, Request, ReplyFun) ->
Result = call_query(async, Id, ?QUERY(ReplyFun, Request), #{}), Result = call_query(async, Id, ?QUERY(ReplyFun, Request), #{}),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'matched'),
_ = handle_query_result(Id, Result, false), _ = handle_query_result(Id, Result, false),
Result. Result.
@ -119,13 +123,15 @@ init({Id, Index, Opts}) ->
true -> true ->
replayq:open(#{ replayq:open(#{
dir => disk_queue_dir(Id, Index), dir => disk_queue_dir(Id, Index),
seg_bytes => maps:get(queue_max_bytes, Opts, ?DEFAULT_QUEUE_SIZE), seg_bytes => maps:get(queue_seg_bytes, Opts, ?DEFAULT_QUEUE_SEG_SIZE),
max_total_bytes => maps:get(max_queue_bytes, Opts, ?DEFAULT_QUEUE_SIZE),
sizer => fun ?MODULE:estimate_size/1, sizer => fun ?MODULE:estimate_size/1,
marshaller => fun ?MODULE:queue_item_marshaller/1 marshaller => fun ?MODULE:queue_item_marshaller/1
}); });
false -> false ->
undefined undefined
end, end,
emqx_metrics_worker:inc(?RES_METRICS, Id, 'queued', queue_count(Queue)),
ok = inflight_new(Name), ok = inflight_new(Name),
St = #{ St = #{
id => Id, id => Id,
@ -149,8 +155,10 @@ running(cast, resume, _St) ->
keep_state_and_data; keep_state_and_data;
running(cast, block, St) -> running(cast, block, St) ->
{next_state, block, St}; {next_state, block, St};
running(cast, {block, [?QUERY(_, _) | _] = Batch}, #{queue := Q} = St) when is_list(Batch) -> running(cast, {block, [?QUERY(_, _) | _] = Batch}, #{id := Id, queue := Q} = St) when
Q1 = maybe_append_queue(Q, [?Q_ITEM(Query) || Query <- Batch]), is_list(Batch)
->
Q1 = maybe_append_queue(Id, Q, [?Q_ITEM(Query) || Query <- Batch]),
{next_state, block, St#{queue := Q1}}; {next_state, block, St#{queue := Q1}};
running({call, From}, {query, Request, _Opts}, St) -> running({call, From}, {query, Request, _Opts}, St) ->
query_or_acc(From, Request, St); query_or_acc(From, Request, St);
@ -169,8 +177,10 @@ blocked(enter, _, #{resume_interval := ResumeT} = _St) ->
{keep_state_and_data, {state_timeout, ResumeT, resume}}; {keep_state_and_data, {state_timeout, ResumeT, resume}};
blocked(cast, block, _St) -> blocked(cast, block, _St) ->
keep_state_and_data; keep_state_and_data;
blocked(cast, {block, [?QUERY(_, _) | _] = Batch}, #{queue := Q} = St) when is_list(Batch) -> blocked(cast, {block, [?QUERY(_, _) | _] = Batch}, #{id := Id, queue := Q} = St) when
Q1 = maybe_append_queue(Q, [?Q_ITEM(Query) || Query <- Batch]), is_list(Batch)
->
Q1 = maybe_append_queue(Id, Q, [?Q_ITEM(Query) || Query <- Batch]),
{keep_state, St#{queue := Q1}}; {keep_state, St#{queue := Q1}};
blocked(cast, resume, St) -> blocked(cast, resume, St) ->
do_resume(St); do_resume(St);
@ -179,12 +189,12 @@ blocked(state_timeout, resume, St) ->
blocked({call, From}, {query, Request, _Opts}, #{id := Id, queue := Q} = St) -> blocked({call, From}, {query, Request, _Opts}, #{id := Id, queue := Q} = St) ->
Error = ?RESOURCE_ERROR(blocked, "resource is blocked"), Error = ?RESOURCE_ERROR(blocked, "resource is blocked"),
_ = reply_caller(Id, ?REPLY(From, Request, Error)), _ = reply_caller(Id, ?REPLY(From, Request, Error)),
{keep_state, St#{queue := maybe_append_queue(Q, [?Q_ITEM(?QUERY(From, Request))])}}; {keep_state, St#{queue := maybe_append_queue(Id, Q, [?Q_ITEM(?QUERY(From, Request))])}};
blocked(cast, {query, Request, Opts}, #{id := Id, queue := Q} = St) -> blocked(cast, {query, Request, Opts}, #{id := Id, queue := Q} = St) ->
ReplayFun = maps:get(async_reply_fun, Opts, undefined), ReplayFun = maps:get(async_reply_fun, Opts, undefined),
Error = ?RESOURCE_ERROR(blocked, "resource is blocked"), Error = ?RESOURCE_ERROR(blocked, "resource is blocked"),
_ = reply_caller(Id, ?REPLY(ReplayFun, Request, Error)), _ = reply_caller(Id, ?REPLY(ReplayFun, Request, Error)),
{keep_state, St#{queue := maybe_append_queue(Q, [?Q_ITEM(?QUERY(ReplayFun, Request))])}}. {keep_state, St#{queue := maybe_append_queue(Id, Q, [?Q_ITEM(?QUERY(ReplayFun, Request))])}}.
terminate(_Reason, #{id := Id, index := Index}) -> terminate(_Reason, #{id := Id, index := Index}) ->
gproc_pool:disconnect_worker(Id, {Id, Index}). gproc_pool:disconnect_worker(Id, {Id, Index}).
@ -206,10 +216,10 @@ estimate_size(QItem) ->
Pid when is_pid(Pid) -> Pid when is_pid(Pid) ->
EXPR; EXPR;
_ -> _ ->
?RESOURCE_ERROR(not_created, "resource not created") ?RESOURCE_ERROR(worker_not_created, "resource not created")
catch catch
error:badarg -> error:badarg ->
?RESOURCE_ERROR(not_created, "resource not created"); ?RESOURCE_ERROR(worker_not_created, "resource not created");
exit:{timeout, _} -> exit:{timeout, _} ->
?RESOURCE_ERROR(timeout, "call resource timeout") ?RESOURCE_ERROR(timeout, "call resource timeout")
end end
@ -255,18 +265,20 @@ retry_first_sync(Id, FirstQuery, Name, Ref, Q, #{resume_interval := ResumeT} = S
inflight_drop(Name, Ref), inflight_drop(Name, Ref),
St0; St0;
_ -> _ ->
St0#{queue => drop_head(Q)} St0#{queue => drop_head(Id, Q)}
end, end,
{keep_state, St, {state_timeout, 0, resume}} {keep_state, St, {state_timeout, 0, resume}}
end. end.
drop_head(Q) -> drop_head(Id, Q) ->
{Q1, AckRef, _} = replayq:pop(Q, #{count_limit => 1}), {Q1, AckRef, _} = replayq:pop(Q, #{count_limit => 1}),
ok = replayq:ack(Q1, AckRef), ok = replayq:ack(Q1, AckRef),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'queued', -1),
Q1. Q1.
query_or_acc(From, Request, #{enable_batch := true, acc := Acc, acc_left := Left} = St0) -> query_or_acc(From, Request, #{enable_batch := true, acc := Acc, acc_left := Left, id := Id} = St0) ->
Acc1 = [?QUERY(From, Request) | Acc], Acc1 = [?QUERY(From, Request) | Acc],
emqx_metrics_worker:inc(?RES_METRICS, Id, 'batched'),
St = St0#{acc := Acc1, acc_left := Left - 1}, St = St0#{acc := Acc1, acc_left := Left - 1},
case Left =< 1 of case Left =< 1 of
true -> flush(St); true -> flush(St);
@ -277,18 +289,15 @@ query_or_acc(From, Request, #{enable_batch := false, queue := Q, id := Id} = St)
inflight_name => maps:get(name, St), inflight_name => maps:get(name, St),
inflight_window => maps:get(async_inflight_window, St) inflight_window => maps:get(async_inflight_window, St)
}, },
case send_query(From, Request, Id, QueryOpts) of Result = call_query(configured, Id, ?QUERY(From, Request), QueryOpts),
case reply_caller(Id, ?REPLY(From, Request, Result)) of
true -> true ->
Query = ?QUERY(From, Request), Query = ?QUERY(From, Request),
{next_state, blocked, St#{queue := maybe_append_queue(Q, [?Q_ITEM(Query)])}}; {next_state, blocked, St#{queue := maybe_append_queue(Id, Q, [?Q_ITEM(Query)])}};
false -> false ->
{keep_state, St} {keep_state, St}
end. end.
send_query(From, Request, Id, QueryOpts) ->
Result = call_query(configured, Id, ?QUERY(From, Request), QueryOpts),
reply_caller(Id, ?REPLY(From, Request, Result)).
flush(#{acc := []} = St) -> flush(#{acc := []} = St) ->
{keep_state, St}; {keep_state, St};
flush( flush(
@ -303,18 +312,39 @@ flush(
inflight_name => maps:get(name, St), inflight_name => maps:get(name, St),
inflight_window => maps:get(async_inflight_window, St) inflight_window => maps:get(async_inflight_window, St)
}, },
emqx_metrics_worker:inc(?RES_METRICS, Id, 'batched', -length(Batch)),
Result = call_query(configured, Id, Batch, QueryOpts), Result = call_query(configured, Id, Batch, QueryOpts),
St1 = cancel_flush_timer(St#{acc_left := Size, acc := []}), St1 = cancel_flush_timer(St#{acc_left := Size, acc := []}),
case batch_reply_caller(Id, Result, Batch) of case batch_reply_caller(Id, Result, Batch) of
true -> true ->
Q1 = maybe_append_queue(Q0, [?Q_ITEM(Query) || Query <- Batch]), Q1 = maybe_append_queue(Id, Q0, [?Q_ITEM(Query) || Query <- Batch]),
{next_state, blocked, St1#{queue := Q1}}; {next_state, blocked, St1#{queue := Q1}};
false -> false ->
{keep_state, St1} {keep_state, St1}
end. end.
maybe_append_queue(undefined, _Items) -> undefined; maybe_append_queue(Id, undefined, _Items) ->
maybe_append_queue(Q, Items) -> replayq:append(Q, Items). emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped'),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped.queue_not_enabled'),
undefined;
maybe_append_queue(Id, Q, Items) ->
Q2 =
case replayq:overflow(Q) of
Overflow when Overflow =< 0 ->
Q;
Overflow ->
PopOpts = #{bytes_limit => Overflow, count_limit => 999999999},
{Q1, QAckRef, Items2} = replayq:pop(Q, PopOpts),
ok = replayq:ack(Q1, QAckRef),
Dropped = length(Items2),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'queued', -Dropped),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped'),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped.queue_full'),
?SLOG(error, #{msg => drop_query, reason => queue_full, dropped => Dropped}),
Q1
end,
emqx_metrics_worker:inc(?RES_METRICS, Id, 'queued'),
replayq:append(Q2, Items).
batch_reply_caller(Id, BatchResult, Batch) -> batch_reply_caller(Id, BatchResult, Batch) ->
lists:foldl( lists:foldl(
@ -344,29 +374,41 @@ reply_caller(Id, ?REPLY(From, _, Result), BlockWorker) ->
handle_query_result(Id, Result, BlockWorker). handle_query_result(Id, Result, BlockWorker).
handle_query_result(Id, ?RESOURCE_ERROR_M(exception, _), BlockWorker) -> handle_query_result(Id, ?RESOURCE_ERROR_M(exception, _), BlockWorker) ->
emqx_metrics_worker:inc(?RES_METRICS, Id, exception), emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.exception'),
BlockWorker; BlockWorker;
handle_query_result(_Id, ?RESOURCE_ERROR_M(NotWorking, _), _) when handle_query_result(_Id, ?RESOURCE_ERROR_M(NotWorking, _), _) when
NotWorking == not_connected; NotWorking == blocked NotWorking == not_connected; NotWorking == blocked
-> ->
true; true;
handle_query_result(_Id, ?RESOURCE_ERROR_M(_, _), BlockWorker) -> handle_query_result(Id, ?RESOURCE_ERROR_M(not_found, _), BlockWorker) ->
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped'),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped.resource_not_found'),
BlockWorker;
handle_query_result(Id, ?RESOURCE_ERROR_M(stopped, _), BlockWorker) ->
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped'),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped.resource_stopped'),
BlockWorker;
handle_query_result(Id, ?RESOURCE_ERROR_M(Reason, _), BlockWorker) ->
?SLOG(error, #{msg => other_resource_error, reason => Reason}),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped'),
emqx_metrics_worker:inc(?RES_METRICS, Id, 'dropped.other'),
BlockWorker; BlockWorker;
handle_query_result(Id, {error, _}, BlockWorker) -> handle_query_result(Id, {error, _}, BlockWorker) ->
emqx_metrics_worker:inc(?RES_METRICS, Id, failed), emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.failed'),
BlockWorker; BlockWorker;
handle_query_result(Id, {resource_down, _}, _BlockWorker) -> handle_query_result(Id, {recoverable_error, _}, _BlockWorker) ->
emqx_metrics_worker:inc(?RES_METRICS, Id, resource_down), emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent', -1),
true; true;
handle_query_result(_Id, {async_return, inflight_full}, _BlockWorker) -> handle_query_result(_Id, {async_return, inflight_full}, _BlockWorker) ->
true; true;
handle_query_result(_Id, {async_return, {resource_down, _}}, _BlockWorker) -> handle_query_result(Id, {async_return, {error, _}}, BlockWorker) ->
true; emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.failed'),
BlockWorker;
handle_query_result(_Id, {async_return, ok}, BlockWorker) -> handle_query_result(_Id, {async_return, ok}, BlockWorker) ->
BlockWorker; BlockWorker;
handle_query_result(Id, Result, BlockWorker) -> handle_query_result(Id, Result, BlockWorker) ->
assert_ok_result(Result), assert_ok_result(Result),
emqx_metrics_worker:inc(?RES_METRICS, Id, success), emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.success'),
BlockWorker. BlockWorker.
call_query(QM0, Id, Query, QueryOpts) -> call_query(QM0, Id, Query, QueryOpts) ->
@ -390,8 +432,8 @@ call_query(QM0, Id, Query, QueryOpts) ->
-define(APPLY_RESOURCE(EXPR, REQ), -define(APPLY_RESOURCE(EXPR, REQ),
try try
%% if the callback module (connector) wants to return an error that %% if the callback module (connector) wants to return an error that
%% makes the current resource goes into the `error` state, it should %% makes the current resource goes into the `blocked` state, it should
%% return `{resource_down, Reason}` %% return `{recoverable_error, Reason}`
EXPR EXPR
catch catch
ERR:REASON:STACKTRACE -> ERR:REASON:STACKTRACE ->
@ -406,7 +448,7 @@ call_query(QM0, Id, Query, QueryOpts) ->
apply_query_fun(sync, Mod, Id, ?QUERY(_, Request) = _Query, ResSt, _QueryOpts) -> apply_query_fun(sync, Mod, Id, ?QUERY(_, Request) = _Query, ResSt, _QueryOpts) ->
?tp(call_query, #{id => Id, mod => Mod, query => _Query, res_st => ResSt}), ?tp(call_query, #{id => Id, mod => Mod, query => _Query, res_st => ResSt}),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, matched), ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent'),
?APPLY_RESOURCE(Mod:on_query(Id, Request, ResSt), Request); ?APPLY_RESOURCE(Mod:on_query(Id, Request, ResSt), Request);
apply_query_fun(async, Mod, Id, ?QUERY(_, Request) = Query, ResSt, QueryOpts) -> apply_query_fun(async, Mod, Id, ?QUERY(_, Request) = Query, ResSt, QueryOpts) ->
?tp(call_query_async, #{id => Id, mod => Mod, query => Query, res_st => ResSt}), ?tp(call_query_async, #{id => Id, mod => Mod, query => Query, res_st => ResSt}),
@ -418,7 +460,8 @@ apply_query_fun(async, Mod, Id, ?QUERY(_, Request) = Query, ResSt, QueryOpts) ->
?tp(inflight_full, #{id => Id, wind_size => WinSize}), ?tp(inflight_full, #{id => Id, wind_size => WinSize}),
{async_return, inflight_full}; {async_return, inflight_full};
false -> false ->
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, matched), ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent'),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.inflight'),
ReplyFun = fun ?MODULE:reply_after_query/6, ReplyFun = fun ?MODULE:reply_after_query/6,
Ref = make_message_ref(), Ref = make_message_ref(),
Args = [self(), Id, Name, Ref, Query], Args = [self(), Id, Name, Ref, Query],
@ -431,7 +474,7 @@ apply_query_fun(async, Mod, Id, ?QUERY(_, Request) = Query, ResSt, QueryOpts) ->
apply_query_fun(sync, Mod, Id, [?QUERY(_, _) | _] = Batch, ResSt, _QueryOpts) -> apply_query_fun(sync, Mod, Id, [?QUERY(_, _) | _] = Batch, ResSt, _QueryOpts) ->
?tp(call_batch_query, #{id => Id, mod => Mod, batch => Batch, res_st => ResSt}), ?tp(call_batch_query, #{id => Id, mod => Mod, batch => Batch, res_st => ResSt}),
Requests = [Request || ?QUERY(_From, Request) <- Batch], Requests = [Request || ?QUERY(_From, Request) <- Batch],
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, matched, length(Batch)), ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent', length(Batch)),
?APPLY_RESOURCE(Mod:on_batch_query(Id, Requests, ResSt), Batch); ?APPLY_RESOURCE(Mod:on_batch_query(Id, Requests, ResSt), Batch);
apply_query_fun(async, Mod, Id, [?QUERY(_, _) | _] = Batch, ResSt, QueryOpts) -> apply_query_fun(async, Mod, Id, [?QUERY(_, _) | _] = Batch, ResSt, QueryOpts) ->
?tp(call_batch_query_async, #{id => Id, mod => Mod, batch => Batch, res_st => ResSt}), ?tp(call_batch_query_async, #{id => Id, mod => Mod, batch => Batch, res_st => ResSt}),
@ -443,7 +486,8 @@ apply_query_fun(async, Mod, Id, [?QUERY(_, _) | _] = Batch, ResSt, QueryOpts) ->
?tp(inflight_full, #{id => Id, wind_size => WinSize}), ?tp(inflight_full, #{id => Id, wind_size => WinSize}),
{async_return, inflight_full}; {async_return, inflight_full};
false -> false ->
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, matched, length(Batch)), ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent', length(Batch)),
ok = emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.inflight'),
ReplyFun = fun ?MODULE:batch_reply_after_query/6, ReplyFun = fun ?MODULE:batch_reply_after_query/6,
Ref = make_message_ref(), Ref = make_message_ref(),
Args = {ReplyFun, [self(), Id, Name, Ref, Batch]}, Args = {ReplyFun, [self(), Id, Name, Ref, Batch]},
@ -457,14 +501,20 @@ apply_query_fun(async, Mod, Id, [?QUERY(_, _) | _] = Batch, ResSt, QueryOpts) ->
reply_after_query(Pid, Id, Name, Ref, ?QUERY(From, Request), Result) -> reply_after_query(Pid, Id, Name, Ref, ?QUERY(From, Request), Result) ->
case reply_caller(Id, ?REPLY(From, Request, Result)) of case reply_caller(Id, ?REPLY(From, Request, Result)) of
true -> ?MODULE:block(Pid); true ->
false -> inflight_drop(Name, Ref) ?MODULE:block(Pid);
false ->
emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.inflight', -1),
inflight_drop(Name, Ref)
end. end.
batch_reply_after_query(Pid, Id, Name, Ref, Batch, Result) -> batch_reply_after_query(Pid, Id, Name, Ref, Batch, Result) ->
case batch_reply_caller(Id, Result, Batch) of case batch_reply_caller(Id, Result, Batch) of
true -> ?MODULE:block(Pid); true ->
false -> inflight_drop(Name, Ref) ?MODULE:block(Pid);
false ->
emqx_metrics_worker:inc(?RES_METRICS, Id, 'sent.inflight', -length(Batch)),
inflight_drop(Name, Ref)
end. end.
%%============================================================================== %%==============================================================================
%% the inflight queue for async query %% the inflight queue for async query
@ -518,6 +568,11 @@ assert_ok_result(R) when is_tuple(R) ->
assert_ok_result(R) -> assert_ok_result(R) ->
error({not_ok_result, R}). error({not_ok_result, R}).
queue_count(undefined) ->
0;
queue_count(Q) ->
replayq:count(Q).
-spec name(id(), integer()) -> atom(). -spec name(id(), integer()) -> atom().
name(Id, Index) -> name(Id, Index) ->
Mod = atom_to_list(?MODULE), Mod = atom_to_list(?MODULE),

View File

@ -107,7 +107,7 @@ ensure_worker_started(ResId, Idx, Opts) ->
type => worker, type => worker,
modules => [Mod] modules => [Mod]
}, },
case supervisor:start_child(emqx_resource_sup, Spec) of case supervisor:start_child(?SERVER, Spec) of
{ok, _Pid} -> ok; {ok, _Pid} -> ok;
{error, {already_started, _}} -> ok; {error, {already_started, _}} -> ok;
{error, already_present} -> ok; {error, already_present} -> ok;
@ -116,9 +116,9 @@ ensure_worker_started(ResId, Idx, Opts) ->
ensure_worker_removed(ResId, Idx) -> ensure_worker_removed(ResId, Idx) ->
ChildId = ?CHILD_ID(emqx_resource_worker, ResId, Idx), ChildId = ?CHILD_ID(emqx_resource_worker, ResId, Idx),
case supervisor:terminate_child(emqx_resource_sup, ChildId) of case supervisor:terminate_child(?SERVER, ChildId) of
ok -> ok ->
Res = supervisor:delete_child(emqx_resource_sup, ChildId), Res = supervisor:delete_child(?SERVER, ChildId),
_ = gproc_pool:remove_worker(ResId, {ResId, Idx}), _ = gproc_pool:remove_worker(ResId, {ResId, Idx}),
Res; Res;
{error, not_found} -> {error, not_found} ->

View File

@ -53,7 +53,7 @@ fields("creation_opts") ->
{batch_size, fun batch_size/1}, {batch_size, fun batch_size/1},
{batch_time, fun batch_time/1}, {batch_time, fun batch_time/1},
{enable_queue, fun enable_queue/1}, {enable_queue, fun enable_queue/1},
{max_queue_bytes, fun queue_max_bytes/1} {max_queue_bytes, fun max_queue_bytes/1}
]. ].
worker_pool_size(type) -> pos_integer(); worker_pool_size(type) -> pos_integer();
@ -110,11 +110,11 @@ batch_time(default) -> ?DEFAULT_BATCH_TIME_RAW;
batch_time(required) -> false; batch_time(required) -> false;
batch_time(_) -> undefined. batch_time(_) -> undefined.
queue_max_bytes(type) -> emqx_schema:bytesize(); max_queue_bytes(type) -> emqx_schema:bytesize();
queue_max_bytes(desc) -> ?DESC("queue_max_bytes"); max_queue_bytes(desc) -> ?DESC("max_queue_bytes");
queue_max_bytes(default) -> ?DEFAULT_QUEUE_SIZE_RAW; max_queue_bytes(default) -> ?DEFAULT_QUEUE_SIZE_RAW;
queue_max_bytes(required) -> false; max_queue_bytes(required) -> false;
queue_max_bytes(_) -> undefined. max_queue_bytes(_) -> undefined.
desc("creation_opts") -> desc("creation_opts") ->
?DESC("creation_opts"). ?DESC("creation_opts").

View File

@ -96,7 +96,7 @@ on_query(_InstId, {inc_counter, N}, #{pid := Pid}) ->
Pid ! {From, {inc, N}}, Pid ! {From, {inc, N}},
receive receive
{ReqRef, ok} -> ok; {ReqRef, ok} -> ok;
{ReqRef, incorrect_status} -> {resource_down, incorrect_status} {ReqRef, incorrect_status} -> {recoverable_error, incorrect_status}
after 1000 -> after 1000 ->
{error, timeout} {error, timeout}
end; end;

View File

@ -268,7 +268,7 @@ t_query_counter_async_query(_) ->
end end
), ),
{ok, _, #{metrics := #{counters := C}}} = emqx_resource:get_instance(?ID), {ok, _, #{metrics := #{counters := C}}} = emqx_resource:get_instance(?ID),
?assertMatch(#{matched := 1002, success := 1002, failed := 0}, C), ?assertMatch(#{matched := 1002, 'sent.success' := 1002, 'sent.failed' := 0}, C),
ok = emqx_resource:remove_local(?ID). ok = emqx_resource:remove_local(?ID).
t_query_counter_async_callback(_) -> t_query_counter_async_callback(_) ->
@ -309,7 +309,7 @@ t_query_counter_async_callback(_) ->
end end
), ),
{ok, _, #{metrics := #{counters := C}}} = emqx_resource:get_instance(?ID), {ok, _, #{metrics := #{counters := C}}} = emqx_resource:get_instance(?ID),
?assertMatch(#{matched := 1002, success := 1002, failed := 0}, C), ?assertMatch(#{matched := 1002, sent := 1002, 'sent.success' := 1002, 'sent.failed' := 0}, C),
?assertMatch(1000, ets:info(Tab0, size)), ?assertMatch(1000, ets:info(Tab0, size)),
?assert( ?assert(
lists:all( lists:all(
@ -419,8 +419,8 @@ t_query_counter_async_inflight(_) ->
{ok, _, #{metrics := #{counters := C}}} = emqx_resource:get_instance(?ID), {ok, _, #{metrics := #{counters := C}}} = emqx_resource:get_instance(?ID),
ct:pal("metrics: ~p", [C]), ct:pal("metrics: ~p", [C]),
?assertMatch( ?assertMatch(
#{matched := M, success := S, exception := E, failed := F, resource_down := RD} when #{matched := M, sent := St, 'sent.success' := Ss, dropped := D} when
M >= Sent andalso M == S + E + F + RD, St == Ss andalso M == St + D,
C C
), ),
?assert( ?assert(

View File

@ -88,18 +88,18 @@
%% Logical operators %% Logical operators
-define(is_logical(Op), (Op =:= 'and' orelse Op =:= 'or')). -define(is_logical(Op), (Op =:= 'and' orelse Op =:= 'or')).
-define(RAISE(_EXP_, _ERROR_), -define(RAISE(EXP, ERROR),
?RAISE(_EXP_, _ = do_nothing, _ERROR_) ?RAISE(EXP, _ = do_nothing, ERROR)
). ).
-define(RAISE(_EXP_, _EXP_ON_FAIL_, _ERROR_), -define(RAISE(EXP, EXP_ON_FAIL, ERROR),
fun() -> fun() ->
try try
(_EXP_) (EXP)
catch catch
_EXCLASS_:_EXCPTION_:_ST_ -> EXCLASS:EXCPTION:ST ->
_EXP_ON_FAIL_, EXP_ON_FAIL,
throw(_ERROR_) throw(ERROR)
end end
end() end()
). ).

View File

@ -42,6 +42,10 @@
-type alias() :: atom(). -type alias() :: atom().
-type collection() :: {alias(), [term()]}. -type collection() :: {alias(), [term()]}.
-elvis([
{elvis_style, invalid_dynamic_call, #{ignore => [emqx_rule_runtime]}}
]).
-define(ephemeral_alias(TYPE, NAME), -define(ephemeral_alias(TYPE, NAME),
iolist_to_binary(io_lib:format("_v_~ts_~p_~p", [TYPE, NAME, erlang:system_time()])) iolist_to_binary(io_lib:format("_v_~ts_~p_~p", [TYPE, NAME, erlang:system_time()]))
). ).
@ -130,13 +134,13 @@ do_apply_rule(
) -> ) ->
{Selected, Collection} = ?RAISE( {Selected, Collection} = ?RAISE(
select_and_collect(Fields, Columns), select_and_collect(Fields, Columns),
{select_and_collect_error, {_EXCLASS_, _EXCPTION_, _ST_}} {select_and_collect_error, {EXCLASS, EXCPTION, ST}}
), ),
ColumnsAndSelected = maps:merge(Columns, Selected), ColumnsAndSelected = maps:merge(Columns, Selected),
case case
?RAISE( ?RAISE(
match_conditions(Conditions, ColumnsAndSelected), match_conditions(Conditions, ColumnsAndSelected),
{match_conditions_error, {_EXCLASS_, _EXCPTION_, _ST_}} {match_conditions_error, {EXCLASS, EXCPTION, ST}}
) )
of of
true -> true ->
@ -166,12 +170,12 @@ do_apply_rule(
) -> ) ->
Selected = ?RAISE( Selected = ?RAISE(
select_and_transform(Fields, Columns), select_and_transform(Fields, Columns),
{select_and_transform_error, {_EXCLASS_, _EXCPTION_, _ST_}} {select_and_transform_error, {EXCLASS, EXCPTION, ST}}
), ),
case case
?RAISE( ?RAISE(
match_conditions(Conditions, maps:merge(Columns, Selected)), match_conditions(Conditions, maps:merge(Columns, Selected)),
{match_conditions_error, {_EXCLASS_, _EXCPTION_, _ST_}} {match_conditions_error, {EXCLASS, EXCPTION, ST}}
) )
of of
true -> true ->
@ -245,7 +249,7 @@ filter_collection(Columns, InCase, DoEach, {CollKey, CollVal}) ->
case case
?RAISE( ?RAISE(
match_conditions(InCase, ColumnsAndItem), match_conditions(InCase, ColumnsAndItem),
{match_incase_error, {_EXCLASS_, _EXCPTION_, _ST_}} {match_incase_error, {EXCLASS, EXCPTION, ST}}
) )
of of
true when DoEach == [] -> {true, ColumnsAndItem}; true when DoEach == [] -> {true, ColumnsAndItem};
@ -253,7 +257,7 @@ filter_collection(Columns, InCase, DoEach, {CollKey, CollVal}) ->
{true, {true,
?RAISE( ?RAISE(
select_and_transform(DoEach, ColumnsAndItem), select_and_transform(DoEach, ColumnsAndItem),
{doeach_error, {_EXCLASS_, _EXCPTION_, _ST_}} {doeach_error, {EXCLASS, EXCPTION, ST}}
)}; )};
false -> false ->
false false
@ -271,7 +275,7 @@ match_conditions({'not', Var}, Data) ->
case eval(Var, Data) of case eval(Var, Data) of
Bool when is_boolean(Bool) -> Bool when is_boolean(Bool) ->
not Bool; not Bool;
_other -> _Other ->
false false
end; end;
match_conditions({in, Var, {list, Vals}}, Data) -> match_conditions({in, Var, {list, Vals}}, Data) ->
@ -506,12 +510,22 @@ nested_put(Alias, Val, Columns0) ->
-define(IS_RES_DOWN(R), R == stopped; R == not_connected; R == not_found). -define(IS_RES_DOWN(R), R == stopped; R == not_connected; R == not_found).
inc_action_metrics(ok, RuleId) -> inc_action_metrics(ok, RuleId) ->
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.success'); emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.success');
inc_action_metrics({ok, _}, RuleId) -> inc_action_metrics({recoverable_error, _}, RuleId) ->
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.success');
inc_action_metrics({resource_down, _}, RuleId) ->
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed.out_of_service'); emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed.out_of_service');
inc_action_metrics(?RESOURCE_ERROR_M(R, _), RuleId) when ?IS_RES_DOWN(R) -> inc_action_metrics(?RESOURCE_ERROR_M(R, _), RuleId) when ?IS_RES_DOWN(R) ->
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed.out_of_service'); emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed.out_of_service');
inc_action_metrics(_, RuleId) -> inc_action_metrics(R, RuleId) ->
case is_ok_result(R) of
false ->
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed'), emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed'),
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed.unknown'). emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.failed.unknown');
true ->
emqx_metrics_worker:inc(rule_metrics, RuleId, 'actions.success')
end.
is_ok_result(ok) ->
true;
is_ok_result(R) when is_tuple(R) ->
ok == erlang:element(1, R);
is_ok_result(ok) ->
false.

View File

@ -62,14 +62,16 @@ test_rule(Sql, Select, Context, EventTopics) ->
}, },
FullContext = fill_default_values(hd(EventTopics), emqx_rule_maps:atom_key_map(Context)), FullContext = fill_default_values(hd(EventTopics), emqx_rule_maps:atom_key_map(Context)),
try emqx_rule_runtime:apply_rule(Rule, FullContext, #{}) of try emqx_rule_runtime:apply_rule(Rule, FullContext, #{}) of
{ok, Data} -> {ok, flatten(Data)}; {ok, Data} ->
{error, Reason} -> {error, Reason} {ok, flatten(Data)};
{error, Reason} ->
{error, Reason}
after after
ok = emqx_rule_engine:clear_metrics_for_rule(RuleId) ok = emqx_rule_engine:clear_metrics_for_rule(RuleId)
end. end.
get_selected_data(Selected, _Envs, _Args) -> get_selected_data(Selected, _Envs, _Args) ->
Selected. {ok, Selected}.
is_publish_topic(<<"$events/", _/binary>>) -> false; is_publish_topic(<<"$events/", _/binary>>) -> false;
is_publish_topic(<<"$bridges/", _/binary>>) -> false; is_publish_topic(<<"$bridges/", _/binary>>) -> false;
@ -77,14 +79,14 @@ is_publish_topic(_Topic) -> true.
flatten([]) -> flatten([]) ->
[]; [];
flatten([D1]) -> flatten([{ok, D}]) ->
D1; D;
flatten([D1 | L]) when is_list(D1) -> flatten([D | L]) when is_list(D) ->
D1 ++ flatten(L). [D0 || {ok, D0} <- D] ++ flatten(L).
echo_action(Data, Envs) -> echo_action(Data, Envs) ->
?TRACE("RULE", "testing_rule_sql_ok", #{data => Data, envs => Envs}), ?TRACE("RULE", "testing_rule_sql_ok", #{data => Data, envs => Envs}),
Data. {ok, Data}.
fill_default_values(Event, Context) -> fill_default_values(Event, Context) ->
maps:merge(envs_examp(Event), Context). maps:merge(envs_examp(Event), Context).

View File

@ -1,18 +0,0 @@
-define(METRICS(MATCH, SUCC, FAILED, RATE, RATE_5, RATE_MAX), #{
matched => MATCH,
success => SUCC,
failed => FAILED,
rate => RATE,
rate_last5m => RATE_5,
rate_max => RATE_MAX
}).
-define(METRICS_EXAMPLE, #{
metrics => ?METRICS(0, 0, 0, 0, 0, 0),
node_metrics => [
#{
node => node(),
metrics => ?METRICS(0, 0, 0, 0, 0, 0)
}
]
}).

View File

@ -5,7 +5,7 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").
-include("emqx_ee_bridge.hrl"). -include_lib("emqx_bridge/include/emqx_bridge.hrl").
-import(hoconsc, [mk/2, enum/1, ref/2]). -import(hoconsc, [mk/2, enum/1, ref/2]).

View File

@ -3,7 +3,7 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_ee_bridge_influxdb). -module(emqx_ee_bridge_influxdb).
-include("emqx_ee_bridge.hrl"). -include_lib("emqx_bridge/include/emqx_bridge.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").

View File

@ -5,7 +5,7 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").
-include("emqx_ee_bridge.hrl"). -include_lib("emqx_bridge/include/emqx_bridge.hrl").
-include_lib("emqx_resource/include/emqx_resource.hrl"). -include_lib("emqx_resource/include/emqx_resource.hrl").
-import(hoconsc, [mk/2, enum/1, ref/2]). -import(hoconsc, [mk/2, enum/1, ref/2]).

View File

@ -135,13 +135,15 @@ start_client(InstId, Config) ->
do_start_client(InstId, Config) do_start_client(InstId, Config)
catch catch
E:R:S -> E:R:S ->
?SLOG(error, #{ Error = #{
msg => "start hstreamdb connector error", msg => "start hstreamdb connector error",
connector => InstId, connector => InstId,
error => E, error => E,
reason => R, reason => R,
stack => S stack => S
}) },
?SLOG(error, Error),
{error, Error}
end. end.
do_start_client(InstId, Config = #{url := Server, pool_size := PoolSize}) -> do_start_client(InstId, Config = #{url := Server, pool_size := PoolSize}) ->

View File

@ -85,18 +85,13 @@ on_batch_query_async(
InstId, InstId,
BatchData, BatchData,
{ReplayFun, Args}, {ReplayFun, Args},
State = #{write_syntax := SyntaxLines, client := Client} #{write_syntax := SyntaxLines, client := Client}
) -> ) ->
case on_get_status(InstId, State) of
connected ->
case parse_batch_data(InstId, BatchData, SyntaxLines) of case parse_batch_data(InstId, BatchData, SyntaxLines) of
{ok, Points} -> {ok, Points} ->
do_async_query(InstId, Client, Points, {ReplayFun, Args}); do_async_query(InstId, Client, Points, {ReplayFun, Args});
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
end;
disconnected ->
{resource_down, disconnected}
end. end.
on_get_status(_InstId, #{client := Client}) -> on_get_status(_InstId, #{client := Client}) ->