|
@@ -98,7 +98,8 @@
|
|
|
]).
|
|
]).
|
|
|
|
|
|
|
|
-record(session, {
|
|
-record(session, {
|
|
|
- %% sessionID, fresh for all new sessions unless it is a resumed persistent session
|
|
|
|
|
|
|
+ %% Client's id
|
|
|
|
|
+ clientid :: emqx_types:clientid(),
|
|
|
id :: sessionID(),
|
|
id :: sessionID(),
|
|
|
%% Is this session a persistent session i.e. was it started with Session-Expiry > 0
|
|
%% Is this session a persistent session i.e. was it started with Session-Expiry > 0
|
|
|
is_persistent :: boolean(),
|
|
is_persistent :: boolean(),
|
|
@@ -128,9 +129,16 @@
|
|
|
%% Awaiting PUBREL Timeout (Unit: millsecond)
|
|
%% Awaiting PUBREL Timeout (Unit: millsecond)
|
|
|
await_rel_timeout :: timeout(),
|
|
await_rel_timeout :: timeout(),
|
|
|
%% Created at
|
|
%% Created at
|
|
|
- created_at :: pos_integer()
|
|
|
|
|
|
|
+ created_at :: pos_integer(),
|
|
|
|
|
+ %% Message deliver latency stats
|
|
|
|
|
+ latency_stats :: emqx_message_latency_stats:stats()
|
|
|
}).
|
|
}).
|
|
|
|
|
|
|
|
|
|
+%% in the previous code, we will replace the message record with the pubrel atom
|
|
|
|
|
+%% in the pubrec function, this will lose the creation time of the message,
|
|
|
|
|
+%% but now we need this time to calculate latency, so now pubrel atom is changed to this record
|
|
|
|
|
+-record(pubrel_await, {timestamp :: non_neg_integer()}).
|
|
|
|
|
+
|
|
|
-type(session() :: #session{}).
|
|
-type(session() :: #session{}).
|
|
|
|
|
|
|
|
-type(publish() :: {maybe(emqx_types:packet_id()), emqx_types:message()}).
|
|
-type(publish() :: {maybe(emqx_types:packet_id()), emqx_types:message()}).
|
|
@@ -157,7 +165,8 @@
|
|
|
mqueue_dropped,
|
|
mqueue_dropped,
|
|
|
next_pkt_id,
|
|
next_pkt_id,
|
|
|
awaiting_rel_cnt,
|
|
awaiting_rel_cnt,
|
|
|
- awaiting_rel_max
|
|
|
|
|
|
|
+ awaiting_rel_max,
|
|
|
|
|
+ latency_stats
|
|
|
]).
|
|
]).
|
|
|
|
|
|
|
|
-define(DEFAULT_BATCH_N, 1000).
|
|
-define(DEFAULT_BATCH_N, 1000).
|
|
@@ -170,6 +179,8 @@
|
|
|
, max_inflight => integer()
|
|
, max_inflight => integer()
|
|
|
, mqueue => emqx_mqueue:options()
|
|
, mqueue => emqx_mqueue:options()
|
|
|
, is_persistent => boolean()
|
|
, is_persistent => boolean()
|
|
|
|
|
+ , clientid => emqx_types:clientid()
|
|
|
|
|
+ , latency_stats => emqx_message_latency_stats:create_options()
|
|
|
}.
|
|
}.
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
@@ -185,6 +196,7 @@ init(Opts) ->
|
|
|
}, maps:get(mqueue, Opts, #{})),
|
|
}, maps:get(mqueue, Opts, #{})),
|
|
|
#session{
|
|
#session{
|
|
|
id = emqx_guid:gen(),
|
|
id = emqx_guid:gen(),
|
|
|
|
|
+ clientid = maps:get(clientid, Opts, <<>>),
|
|
|
is_persistent = maps:get(is_persistent, Opts, false),
|
|
is_persistent = maps:get(is_persistent, Opts, false),
|
|
|
max_subscriptions = maps:get(max_subscriptions, Opts, infinity),
|
|
max_subscriptions = maps:get(max_subscriptions, Opts, infinity),
|
|
|
subscriptions = #{},
|
|
subscriptions = #{},
|
|
@@ -196,7 +208,8 @@ init(Opts) ->
|
|
|
awaiting_rel = #{},
|
|
awaiting_rel = #{},
|
|
|
max_awaiting_rel = maps:get(max_awaiting_rel, Opts, 100),
|
|
max_awaiting_rel = maps:get(max_awaiting_rel, Opts, 100),
|
|
|
await_rel_timeout = maps:get(await_rel_timeout, Opts, 300000),
|
|
await_rel_timeout = maps:get(await_rel_timeout, Opts, 300000),
|
|
|
- created_at = erlang:system_time(millisecond)
|
|
|
|
|
|
|
+ created_at = erlang:system_time(millisecond),
|
|
|
|
|
+ latency_stats = emqx_message_latency_stats:new(maps:get(latency_stats, Opts, #{}))
|
|
|
}.
|
|
}.
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
@@ -252,7 +265,9 @@ info(awaiting_rel_max, #session{max_awaiting_rel = Max}) ->
|
|
|
info(await_rel_timeout, #session{await_rel_timeout = Timeout}) ->
|
|
info(await_rel_timeout, #session{await_rel_timeout = Timeout}) ->
|
|
|
Timeout;
|
|
Timeout;
|
|
|
info(created_at, #session{created_at = CreatedAt}) ->
|
|
info(created_at, #session{created_at = CreatedAt}) ->
|
|
|
- CreatedAt.
|
|
|
|
|
|
|
+ CreatedAt;
|
|
|
|
|
+info(latency_stats, #session{latency_stats = Stats}) ->
|
|
|
|
|
+ emqx_message_latency_stats:latency(Stats).
|
|
|
|
|
|
|
|
%% @doc Get stats of the session.
|
|
%% @doc Get stats of the session.
|
|
|
-spec(stats(session()) -> emqx_types:stats()).
|
|
-spec(stats(session()) -> emqx_types:stats()).
|
|
@@ -365,7 +380,8 @@ puback(PacketId, Session = #session{inflight = Inflight}) ->
|
|
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
|
|
{value, {Msg, _Ts}} when is_record(Msg, message) ->
|
|
{value, {Msg, _Ts}} when is_record(Msg, message) ->
|
|
|
Inflight1 = emqx_inflight:delete(PacketId, Inflight),
|
|
Inflight1 = emqx_inflight:delete(PacketId, Inflight),
|
|
|
- return_with(Msg, dequeue(Session#session{inflight = Inflight1}));
|
|
|
|
|
|
|
+ Session2 = update_latency(Msg, Session),
|
|
|
|
|
+ return_with(Msg, dequeue(Session2#session{inflight = Inflight1}));
|
|
|
{value, {_Pubrel, _Ts}} ->
|
|
{value, {_Pubrel, _Ts}} ->
|
|
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE};
|
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE};
|
|
|
none ->
|
|
none ->
|
|
@@ -388,9 +404,10 @@ return_with(Msg, {ok, Publishes, Session}) ->
|
|
|
pubrec(PacketId, Session = #session{inflight = Inflight}) ->
|
|
pubrec(PacketId, Session = #session{inflight = Inflight}) ->
|
|
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
|
|
{value, {Msg, _Ts}} when is_record(Msg, message) ->
|
|
{value, {Msg, _Ts}} when is_record(Msg, message) ->
|
|
|
- Inflight1 = emqx_inflight:update(PacketId, with_ts(pubrel), Inflight),
|
|
|
|
|
|
|
+ Update = with_ts(#pubrel_await{timestamp = Msg#message.timestamp}),
|
|
|
|
|
+ Inflight1 = emqx_inflight:update(PacketId, Update, Inflight),
|
|
|
{ok, Msg, Session#session{inflight = Inflight1}};
|
|
{ok, Msg, Session#session{inflight = Inflight1}};
|
|
|
- {value, {pubrel, _Ts}} ->
|
|
|
|
|
|
|
+ {value, {_Pubrel, _Ts}} ->
|
|
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE};
|
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE};
|
|
|
none ->
|
|
none ->
|
|
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}
|
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}
|
|
@@ -419,9 +436,10 @@ pubrel(PacketId, Session = #session{awaiting_rel = AwaitingRel}) ->
|
|
|
| {error, emqx_types:reason_code()}).
|
|
| {error, emqx_types:reason_code()}).
|
|
|
pubcomp(PacketId, Session = #session{inflight = Inflight}) ->
|
|
pubcomp(PacketId, Session = #session{inflight = Inflight}) ->
|
|
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
|
|
- {value, {pubrel, _Ts}} ->
|
|
|
|
|
|
|
+ {value, {Pubrel, _Ts}} when is_record(Pubrel, pubrel_await) ->
|
|
|
|
|
+ Session2 = update_latency(Pubrel, Session),
|
|
|
Inflight1 = emqx_inflight:delete(PacketId, Inflight),
|
|
Inflight1 = emqx_inflight:delete(PacketId, Inflight),
|
|
|
- dequeue(Session#session{inflight = Inflight1});
|
|
|
|
|
|
|
+ dequeue(Session2#session{inflight = Inflight1});
|
|
|
{value, _Other} ->
|
|
{value, _Other} ->
|
|
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE};
|
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE};
|
|
|
none ->
|
|
none ->
|
|
@@ -588,11 +606,16 @@ await(PacketId, Msg, Session = #session{inflight = Inflight}) ->
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
|
|
-spec(retry(session()) -> {ok, session()} | {ok, replies(), timeout(), session()}).
|
|
-spec(retry(session()) -> {ok, session()} | {ok, replies(), timeout(), session()}).
|
|
|
-retry(Session = #session{inflight = Inflight}) ->
|
|
|
|
|
|
|
+retry(Session = #session{inflight = Inflight, retry_interval = RetryInterval}) ->
|
|
|
case emqx_inflight:is_empty(Inflight) of
|
|
case emqx_inflight:is_empty(Inflight) of
|
|
|
true -> {ok, Session};
|
|
true -> {ok, Session};
|
|
|
- false -> retry_delivery(emqx_inflight:to_list(sort_fun(), Inflight),
|
|
|
|
|
- [], erlang:system_time(millisecond), Session)
|
|
|
|
|
|
|
+ false ->
|
|
|
|
|
+ Now = erlang:system_time(millisecond),
|
|
|
|
|
+ Session2 = check_expire_latency(Now, RetryInterval, Session),
|
|
|
|
|
+ retry_delivery(emqx_inflight:to_list(sort_fun(), Inflight),
|
|
|
|
|
+ [],
|
|
|
|
|
+ Now,
|
|
|
|
|
+ Session2)
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
retry_delivery([], Acc, _Now, Session = #session{retry_interval = Interval}) ->
|
|
retry_delivery([], Acc, _Now, Session = #session{retry_interval = Interval}) ->
|
|
@@ -619,8 +642,8 @@ retry_delivery(PacketId, Msg, Now, Acc, Inflight) when is_record(Msg, message) -
|
|
|
{[{PacketId, Msg1}|Acc], Inflight1}
|
|
{[{PacketId, Msg1}|Acc], Inflight1}
|
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
-retry_delivery(PacketId, pubrel, Now, Acc, Inflight) ->
|
|
|
|
|
- Inflight1 = emqx_inflight:update(PacketId, {pubrel, Now}, Inflight),
|
|
|
|
|
|
|
+retry_delivery(PacketId, Pubrel, Now, Acc, Inflight) ->
|
|
|
|
|
+ Inflight1 = emqx_inflight:update(PacketId, {Pubrel, Now}, Inflight),
|
|
|
{[{pubrel, PacketId}|Acc], Inflight1}.
|
|
{[{pubrel, PacketId}|Acc], Inflight1}.
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
@@ -664,7 +687,7 @@ resume(ClientInfo = #{clientid := ClientId}, Session = #session{subscriptions =
|
|
|
|
|
|
|
|
-spec(replay(session()) -> {ok, replies(), session()}).
|
|
-spec(replay(session()) -> {ok, replies(), session()}).
|
|
|
replay(Session = #session{inflight = Inflight}) ->
|
|
replay(Session = #session{inflight = Inflight}) ->
|
|
|
- Pubs = lists:map(fun({PacketId, {pubrel, _Ts}}) ->
|
|
|
|
|
|
|
+ Pubs = lists:map(fun({PacketId, {Pubrel, _Ts}}) when is_record(Pubrel, pubrel_await) ->
|
|
|
{pubrel, PacketId};
|
|
{pubrel, PacketId};
|
|
|
({PacketId, {Msg, _Ts}}) ->
|
|
({PacketId, {Msg, _Ts}}) ->
|
|
|
{PacketId, emqx_message:set_flag(dup, true, Msg)}
|
|
{PacketId, emqx_message:set_flag(dup, true, Msg)}
|
|
@@ -715,6 +738,35 @@ next_pkt_id(Session = #session{next_pkt_id = ?MAX_PACKET_ID}) ->
|
|
|
next_pkt_id(Session = #session{next_pkt_id = Id}) ->
|
|
next_pkt_id(Session = #session{next_pkt_id = Id}) ->
|
|
|
Session#session{next_pkt_id = Id + 1}.
|
|
Session#session{next_pkt_id = Id + 1}.
|
|
|
|
|
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+%% Message Latency Stats
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+update_latency(Msg,
|
|
|
|
|
+ #session{clientid = ClientId,
|
|
|
|
|
+ latency_stats = Stats,
|
|
|
|
|
+ created_at = CreateAt} = S) ->
|
|
|
|
|
+ case get_birth_timestamp(Msg, CreateAt) of
|
|
|
|
|
+ 0 -> S;
|
|
|
|
|
+ Ts ->
|
|
|
|
|
+ Latency = erlang:system_time(millisecond) - Ts,
|
|
|
|
|
+ Stats2 = emqx_message_latency_stats:update(ClientId, Latency, Stats),
|
|
|
|
|
+ S#session{latency_stats = Stats2}
|
|
|
|
|
+ end.
|
|
|
|
|
+
|
|
|
|
|
+check_expire_latency(Now, Interval,
|
|
|
|
|
+ #session{clientid = ClientId, latency_stats = Stats} = S) ->
|
|
|
|
|
+ Stats2 = emqx_message_latency_stats:check_expire(ClientId, Now, Interval, Stats),
|
|
|
|
|
+ S#session{latency_stats = Stats2}.
|
|
|
|
|
+
|
|
|
|
|
+get_birth_timestamp(#message{timestamp = Ts}, CreateAt) when CreateAt =< Ts ->
|
|
|
|
|
+ Ts;
|
|
|
|
|
+
|
|
|
|
|
+get_birth_timestamp(#pubrel_await{timestamp = Ts}, CreateAt) when CreateAt =< Ts ->
|
|
|
|
|
+ Ts;
|
|
|
|
|
+
|
|
|
|
|
+get_birth_timestamp(_, _) ->
|
|
|
|
|
+ 0.
|
|
|
|
|
+
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
%% Helper functions
|
|
%% Helper functions
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|