diff --git a/apps/emqx/src/emqx_persistent_session_ds.erl b/apps/emqx/src/emqx_persistent_session_ds.erl index 3989516e5..483e36da1 100644 --- a/apps/emqx/src/emqx_persistent_session_ds.erl +++ b/apps/emqx/src/emqx_persistent_session_ds.erl @@ -1182,7 +1182,12 @@ maybe_set_offline_info(S, Id) -> case emqx_cm:lookup_client({clientid, Id}) of [{_Key, ChannelInfo, Stats}] -> emqx_persistent_session_ds_state:set_offline_info( - #{chan_info => ChannelInfo, stats => Stats}, + #{ + chan_info => ChannelInfo, + stats => Stats, + disconnected_at => erlang:system_time(millisecond), + last_connected_to => node() + }, S ); _ -> diff --git a/apps/emqx_management/src/emqx_mgmt_api_clients.erl b/apps/emqx_management/src/emqx_mgmt_api_clients.erl index 940f76b9a..d29deedce 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_clients.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_clients.erl @@ -1529,13 +1529,13 @@ do_persistent_session_query1(ResultAcc, QueryState, Iter0) -> check_for_live_and_expired(Rows) -> lists:filtermap( - fun({ClientId, Session}) -> + fun({ClientId, _Session}) -> case is_live_session(ClientId) of true -> false; false -> DSSession = emqx_persistent_session_ds_state:print_session(ClientId), - {true, {ClientId, DSSession#{is_expired => is_expired(Session)}}} + {true, {ClientId, DSSession}} end end, Rows @@ -1755,18 +1755,32 @@ format_channel_info(undefined, {ClientId, PSInfo0 = #{}}, _Opts) -> format_persistent_session_info(ClientId, PSInfo0). format_persistent_session_info( - _ClientId, #{metadata := #{offline_info := #{chan_info := ChanInfo, stats := Stats}}} = PSInfo + _ClientId, + #{ + metadata := #{offline_info := #{chan_info := ChanInfo, stats := Stats} = OfflineInfo} = + Metadata + } = + PSInfo ) -> Info0 = format_channel_info(_Node = undefined, {_Key = undefined, ChanInfo, Stats}, #{ fields => all }), - Info0#{ - connected => false, - durable => true, - is_persistent => true, - is_expired => maps:get(is_expired, PSInfo, false), - subscriptions_cnt => maps:size(maps:get(subscriptions, PSInfo, #{})) - }; + LastConnectedToNode = maps:get(last_connected_to, OfflineInfo, undefined), + DisconnectedAt = maps:get(disconnected_at, OfflineInfo, undefined), + %% `created_at' and `connected_at' have already been formatted by this point. + Info = result_format_time_fun( + disconnected_at, + Info0#{ + connected => false, + disconnected_at => DisconnectedAt, + durable => true, + is_persistent => true, + is_expired => is_expired(Metadata), + node => LastConnectedToNode, + subscriptions_cnt => maps:size(maps:get(subscriptions, PSInfo, #{})) + } + ), + result_format_undefined_to_null(Info); format_persistent_session_info(ClientId, PSInfo0) -> Metadata = maps:get(metadata, PSInfo0, #{}), {ProtoName, ProtoVer} = maps:get(protocol, Metadata), @@ -1786,7 +1800,7 @@ format_persistent_session_info(ClientId, PSInfo0) -> connected_at => CreatedAt, durable => true, ip_address => IpAddress, - is_expired => maps:get(is_expired, PSInfo0, false), + is_expired => is_expired(Metadata), is_persistent => true, port => Port, heap_size => 0, diff --git a/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl index a499aba34..a46be9c08 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_clients_SUITE.erl @@ -580,14 +580,33 @@ t_persistent_sessions6(Config) -> %% Wait for session to be considered expired but not GC'ed ct:sleep(2_000), assert_single_client(O#{node => N1, clientid => ClientId, status => disconnected}), + N1Bin = atom_to_binary(N1), ?retry( 100, 20, ?assertMatch( - {ok, {{_, 200, _}, _, #{<<"data">> := [#{<<"is_expired">> := true}]}}}, + {ok, + {{_, 200, _}, _, #{ + <<"data">> := [ + #{ + <<"is_expired">> := true, + <<"node">> := N1Bin, + <<"disconnected_at">> := <<_/binary>> + } + ] + }}}, list_request(APIPort) ) ), + ?assertMatch( + {ok, + {{_, 200, _}, _, #{ + <<"is_expired">> := true, + <<"node">> := N1Bin, + <<"disconnected_at">> := <<_/binary>> + }}}, + get_client_request(APIPort, ClientId) + ), C2 = connect_client(#{port => Port1, clientid => ClientId}), disconnect_and_destroy_session(C2),