diff --git a/apps/emqx_management/src/emqx_mgmt_cache.erl b/apps/emqx_management/src/emqx_mgmt_cache.erl new file mode 100644 index 000000000..37f8e1367 --- /dev/null +++ b/apps/emqx_management/src/emqx_mgmt_cache.erl @@ -0,0 +1,104 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 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_mgmt_cache). + +-behaviour(gen_server). + +-define(SYS_MEMORY_KEY, sys_memory). +-define(EXPIRED_MS, 3000). +%% -100ms to early update cache +-define(REFRESH_MS, ?EXPIRED_MS - 100). +-define(DEFAULT_BAD_MEMORY, {0, 0}). + +-export([start_link/0, get_sys_memory/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +get_sys_memory() -> + Now = now_millisecond(), + {CacheMem, ExpiredAt} = get_memory_from_cache(), + case Now > ExpiredAt of + true -> + erlang:send(?MODULE, fresh_sys_memory), + CacheMem; + %% stale cache value, try to recalculate + false -> + get_sys_memory_sync() + end. + +get_sys_memory_sync() -> + try + gen_server:call(?MODULE, get_sys_memory, ?EXPIRED_MS) + catch + exit:{timeout, _} -> + ?DEFAULT_BAD_MEMORY + end. + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + ets:new(?MODULE, [set, named_table, public, {keypos, 1}]), + {ok, #{fresh_at => 0}}. + +handle_call(get_sys_memory, _From, State) -> + {Mem, NewState} = fresh_sys_memory(State), + {reply, Mem, NewState}; +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(fresh_sys_memory, State) -> + {_, NewState} = fresh_sys_memory(State), + {noreply, NewState}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +fresh_sys_memory(State = #{fresh_at := LastFreshAt}) -> + Now = now_millisecond(), + {Mem, ExpiredAt} = get_memory_from_cache(), + case Now >= ExpiredAt orelse Now - LastFreshAt >= ?REFRESH_MS of + true -> + %% NOTE: Now /= UpdateAt, because + %% load_ctl:get_sys_memory/0 maybe a heavy operation, + %% so record update_at timestamp after get_sys_memory/0. + NewMem = load_ctl:get_sys_memory(), + NewExpiredAt = now_millisecond() + ?EXPIRED_MS, + ets:insert(?MODULE, {?SYS_MEMORY_KEY, {NewMem, NewExpiredAt}}), + {NewMem, State#{fresh_at => Now}}; + false -> + {Mem, State} + end. + +get_memory_from_cache() -> + case ets:lookup(?MODULE, ?SYS_MEMORY_KEY) of + [] -> {?DEFAULT_BAD_MEMORY, 0}; + [{_, CacheVal}] -> CacheVal + end. + +now_millisecond() -> + erlang:system_time(millisecond). diff --git a/apps/emqx_management/src/emqx_mgmt_sup.erl b/apps/emqx_management/src/emqx_mgmt_sup.erl index fa49c02a6..2d9a9ba8a 100644 --- a/apps/emqx_management/src/emqx_mgmt_sup.erl +++ b/apps/emqx_management/src/emqx_mgmt_sup.erl @@ -26,7 +26,7 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - LC = child_spec(emqx_mgmt_sys_memory, 5000, worker), + LC = child_spec(emqx_mgmt_cache, 5000, worker), {ok, {{one_for_one, 1, 5}, [LC]}}. child_spec(Mod, Shutdown, Type) -> diff --git a/apps/emqx_management/src/emqx_mgmt_sys_memory.erl b/apps/emqx_management/src/emqx_mgmt_sys_memory.erl deleted file mode 100644 index cc4f987b5..000000000 --- a/apps/emqx_management/src/emqx_mgmt_sys_memory.erl +++ /dev/null @@ -1,79 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2023 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_mgmt_sys_memory). - --behaviour(gen_server). --define(SYS_MEMORY_CACHE_KEY, ?MODULE). --define(TIMEOUT, 2200). - --export([start_link/0, get_sys_memory/0, get_sys_memory/1]). --export([ - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3 -]). - -get_sys_memory() -> - get_sys_memory(?TIMEOUT). - -get_sys_memory(Timeout) -> - try - gen_server:call(?MODULE, get_sys_memory, Timeout) - catch - exit:{timeout, _} -> - get_memory_from_cache() - end. - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -init([]) -> - {ok, #{last_time => 0}}. - -handle_call(get_sys_memory, _From, State = #{last_time := LastTime}) -> - Now = erlang:system_time(millisecond), - case Now - LastTime >= ?TIMEOUT of - true -> - Memory = load_ctl:get_sys_memory(), - persistent_term:put(?SYS_MEMORY_CACHE_KEY, Memory), - {reply, Memory, State#{last_time => Now}}; - false -> - {reply, get_memory_from_cache(), State} - end; -handle_call(_Request, _From, State = #{}) -> - {reply, ok, State}. - -handle_cast(_Request, State = #{}) -> - {noreply, State}. - -handle_info(_Info, State = #{}) -> - {noreply, State}. - -terminate(_Reason, _State = #{}) -> - ok. - -code_change(_OldVsn, State = #{}, _Extra) -> - {ok, State}. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== - -get_memory_from_cache() -> - persistent_term:get(?SYS_MEMORY_CACHE_KEY, {0, 0}).