aboutsummaryrefslogtreecommitdiffstats
path: root/lib/os_mon/src/memsup.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/os_mon/src/memsup.erl')
-rw-r--r--lib/os_mon/src/memsup.erl1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/lib/os_mon/src/memsup.erl b/lib/os_mon/src/memsup.erl
new file mode 100644
index 0000000000..822e1f939c
--- /dev/null
+++ b/lib/os_mon/src/memsup.erl
@@ -0,0 +1,1022 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(memsup).
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]). % for supervisor
+-export([get_memory_data/0, get_system_memory_data/0,
+ get_check_interval/0, set_check_interval/1,
+ get_procmem_high_watermark/0, set_procmem_high_watermark/1,
+ get_sysmem_high_watermark/0, set_sysmem_high_watermark/1,
+ get_helper_timeout/0, set_helper_timeout/1,
+ get_os_wordsize/0]).
+-export([dummy_reply/1, param_type/2, param_default/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% Other exports
+-export([format_status/2]).
+
+-include("memsup.hrl").
+
+-record(state,
+ {os, % {OSfamily,OSname} | OSfamily
+ port_mode, % bool()
+
+ mem_usage, % undefined | {Alloc, Total}
+ worst_mem_user, % undefined | {Pid, Alloc}
+
+ sys_only, % bool() memsup_system_only
+ timeout, % int() memory_check_interval, ms
+ helper_timeout, % int() memsup_helper_timeout, ms
+ sys_mem_watermark, % float() system_memory_high_watermark, %
+ proc_mem_watermark, % float() process_memory_high_watermark, %
+
+ pid, % undefined | pid()
+ wd_timer, % undefined | TimerRef
+ ext_wd_timer, % undefined | TimerRef
+ pending = [], % [reg | {reg,From} | {ext,From}]
+ ext_pending = [] % [{ext,From}]
+ }).
+
+%%----------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+get_os_wordsize() ->
+ os_mon:call(memsup, get_os_wordsize, infinity).
+
+get_memory_data() ->
+ os_mon:call(memsup, get_memory_data, infinity).
+
+get_system_memory_data() ->
+ os_mon:call(memsup, get_system_memory_data, infinity).
+
+get_check_interval() ->
+ os_mon:call(memsup, get_check_interval, infinity).
+set_check_interval(Minutes) ->
+ case param_type(memory_check_interval, Minutes) of
+ true ->
+ MS = minutes_to_ms(Minutes), % for backwards compatibility
+ os_mon:call(memsup, {set_check_interval, MS}, infinity);
+ false ->
+ erlang:error(badarg)
+ end.
+
+get_procmem_high_watermark() ->
+ os_mon:call(memsup, get_procmem_high_watermark, infinity).
+set_procmem_high_watermark(Float) ->
+ case param_type(process_memory_high_watermark, Float) of
+ true ->
+ os_mon:call(memsup, {set_procmem_high_watermark, Float},
+ infinity);
+ false ->
+ erlang:error(badarg)
+ end.
+
+get_sysmem_high_watermark() ->
+ os_mon:call(memsup, get_sysmem_high_watermark, infinity).
+set_sysmem_high_watermark(Float) ->
+ case param_type(system_memory_high_watermark, Float) of
+ true ->
+ os_mon:call(memsup, {set_sysmem_high_watermark, Float},
+ infinity);
+ false ->
+ erlang:error(badarg)
+ end.
+
+get_helper_timeout() ->
+ os_mon:call(memsup, get_helper_timeout, infinity).
+set_helper_timeout(Seconds) ->
+ case param_type(memsup_helper_timeout, Seconds) of
+ true ->
+ os_mon:call(memsup, {set_helper_timeout, Seconds});
+ false ->
+ erlang:error(badarg)
+ end.
+
+dummy_reply(get_memory_data) ->
+ dummy_reply(get_memory_data,
+ os_mon:get_env(memsup, memsup_system_only));
+dummy_reply(get_system_memory_data) ->
+ [];
+dummy_reply(get_os_wordsize) ->
+ 0;
+dummy_reply(get_check_interval) ->
+ minutes_to_ms(os_mon:get_env(memsup, memory_check_interval));
+dummy_reply({set_check_interval, _}) ->
+ ok;
+dummy_reply(get_procmem_high_watermark) ->
+ trunc(100 * os_mon:get_env(memsup, process_memory_high_watermark));
+dummy_reply({set_procmem_high_watermark, _}) ->
+ ok;
+dummy_reply(get_sysmem_high_watermark) ->
+ trunc(100 * os_mon:get_env(memsup, system_memory_high_watermark));
+dummy_reply({set_sysmem_high_watermark, _}) ->
+ ok;
+dummy_reply(get_helper_timeout) ->
+ os_mon:get_env(memsup, memsup_helper_timeout);
+dummy_reply({set_helper_timeout, _}) ->
+ ok.
+dummy_reply(get_memory_data, true) ->
+ {0,0,undefined};
+dummy_reply(get_memory_data, false) ->
+ {0,0,{self(),0}}.
+
+param_type(memsup_system_only, Val) when Val==true; Val==false -> true;
+param_type(memory_check_interval, Val) when is_integer(Val),
+ Val>0 -> true;
+param_type(memsup_helper_timeout, Val) when is_integer(Val),
+ Val>0 -> true;
+param_type(system_memory_high_watermark, Val) when is_number(Val),
+ 0=<Val,
+ Val=<1 -> true;
+param_type(process_memory_high_watermark, Val) when is_number(Val),
+ 0=<Val,
+ Val=<1 -> true;
+param_type(_Param, _Val) -> false.
+
+param_default(memsup_system_only) -> false;
+param_default(memory_check_interval) -> 1;
+param_default(memsup_helper_timeout) -> 30;
+param_default(system_memory_high_watermark) -> 0.80;
+param_default(process_memory_high_watermark) -> 0.05.
+
+%%----------------------------------------------------------------------
+%% gen_server callbacks
+%%----------------------------------------------------------------------
+
+init([]) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, low),
+
+ OS = os:type(),
+ PortMode = case OS of
+ {unix, darwin} -> false;
+ {unix, freebsd} -> false;
+ % Linux supports this.
+ {unix, linux} -> true;
+ {unix, openbsd} -> true;
+ {unix, irix64} -> true;
+ {unix, irix} -> true;
+ {unix, sunos} -> true;
+ {win32, _OSname} -> false;
+ vxworks -> true;
+ _ ->
+ exit({unsupported_os, OS})
+ end,
+ Pid = if
+ PortMode ->
+ spawn_link(fun() -> port_init() end);
+ not PortMode ->
+ undefined
+ end,
+
+ %% Read the values of some configuration parameters
+ SysOnly = os_mon:get_env(memsup, memsup_system_only),
+ Timeout = os_mon:get_env(memsup, memory_check_interval),
+ HelperTimeout = os_mon:get_env(memsup, memsup_helper_timeout),
+ SysMem = os_mon:get_env(memsup, system_memory_high_watermark),
+ ProcMem = os_mon:get_env(memsup, process_memory_high_watermark),
+
+ %% Initiate first data collection
+ self() ! time_to_collect,
+
+ {ok, #state{os=OS, port_mode=PortMode,
+
+ sys_only = SysOnly,
+ timeout = minutes_to_ms(Timeout),
+ helper_timeout = sec_to_ms(HelperTimeout),
+ sys_mem_watermark = SysMem,
+ proc_mem_watermark = ProcMem,
+
+ pid=Pid}}.
+
+handle_call(get_os_wordsize, _From, State) ->
+ Wordsize = get_os_wordsize(State#state.os),
+ {reply, Wordsize, State};
+handle_call(get_memory_data, From, State) ->
+ %% Return result of latest memory check
+ case State#state.mem_usage of
+ {Alloc, Total} ->
+ Worst = State#state.worst_mem_user,
+ {reply, {Total, Alloc, Worst}, State};
+
+ %% Special case: get_memory_data called before any memory data
+ %% has been collected
+ undefined ->
+ case State#state.wd_timer of
+ undefined ->
+ WDTimer = erlang:send_after(State#state.timeout,
+ self(),
+ reg_collection_timeout),
+ Pending = [{reg,From}],
+ if
+ State#state.port_mode ->
+ State#state.pid ! {self(), collect_sys},
+ {noreply, State#state{wd_timer=WDTimer,
+ pending=Pending}};
+ true ->
+ OS = State#state.os,
+ Self = self(),
+ Pid = spawn_link(fun() ->
+ MU = get_memory_usage(OS),
+ Self ! {collected_sys,MU}
+ end),
+ {noreply, State#state{pid=Pid,
+ wd_timer=WDTimer,
+ pending=Pending}}
+ end;
+ _TimerRef ->
+ Pending = [{reg,From} | State#state.pending],
+ {noreply, State#state{pending=Pending}}
+ end
+ end;
+
+handle_call(get_system_memory_data,From,#state{port_mode=true}=State) ->
+ %% When using a port, the extensive memory collection is slightly
+ %% different than a regular one
+ case State#state.ext_wd_timer of
+ undefined ->
+ WDTimer = erlang:send_after(State#state.helper_timeout,
+ self(),
+ ext_collection_timeout),
+ State#state.pid ! {self(), collect_ext_sys},
+ {noreply, State#state{ext_wd_timer=WDTimer,
+ ext_pending=[{ext,From}]}};
+ _TimerRef ->
+ Pending = [{ext,From} | State#state.ext_pending],
+ {noreply, State#state{ext_pending=Pending}}
+ end;
+handle_call(get_system_memory_data, From, State) ->
+ %% When not using a port, the regular memory collection is used
+ %% for extensive memory data as well
+ case State#state.wd_timer of
+ undefined ->
+ WDTimer = erlang:send_after(State#state.helper_timeout,
+ self(),
+ reg_collection_timeout),
+ OS = State#state.os,
+ Self = self(),
+ Pid = spawn_link(fun() ->
+ MemUsage = get_memory_usage(OS),
+ Self ! {collected_sys, MemUsage}
+ end),
+ {noreply, State#state{pid=Pid, wd_timer=WDTimer,
+ pending=[{ext,From}]}};
+ _TimerRef ->
+ Pending = [{ext,From} | State#state.pending],
+ {noreply, State#state{pending=Pending}}
+ end;
+
+handle_call(get_check_interval, _From, State) ->
+ {reply, State#state.timeout, State};
+handle_call({set_check_interval, MS}, _From, State) ->
+ {reply, ok, State#state{timeout=MS}};
+
+handle_call(get_procmem_high_watermark, _From, State) ->
+ {reply, trunc(100 * State#state.proc_mem_watermark), State};
+handle_call({set_procmem_high_watermark, Float}, _From, State) ->
+ {reply, ok, State#state{proc_mem_watermark=Float}};
+
+handle_call(get_sysmem_high_watermark, _From, State) ->
+ {reply, trunc(100 * State#state.sys_mem_watermark), State};
+handle_call({set_sysmem_high_watermark, Float}, _From, State) ->
+ {reply, ok, State#state{sys_mem_watermark=Float}};
+
+handle_call(get_helper_timeout, _From, State) ->
+ {reply, ms_to_sec(State#state.helper_timeout), State};
+handle_call({set_helper_timeout, Seconds}, _From, State) ->
+ {reply, ok, State#state{helper_timeout=sec_to_ms(Seconds)}};
+
+%% The following are only for test purposes (whitebox testing).
+handle_call({set_sys_hw, HW}, _From, State) ->
+ {reply, ok, State#state{sys_mem_watermark=HW}};
+handle_call({set_pid_hw, HW}, _From, State) ->
+ {reply, ok, State#state{proc_mem_watermark=HW}};
+handle_call(get_state, _From, State) ->
+ {reply, State, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%% It's time to check memory
+handle_info(time_to_collect, State) ->
+ case State#state.wd_timer of
+ undefined ->
+ WDTimer = erlang:send_after(State#state.helper_timeout,
+ self(),
+ reg_collection_timeout),
+ if
+ State#state.port_mode ->
+ State#state.pid ! {self(), collect_sys},
+ {noreply, State#state{wd_timer=WDTimer,
+ pending=[reg]}};
+ true ->
+ OS = State#state.os,
+ Self = self(),
+ Pid = spawn_link(fun() ->
+ MU = get_memory_usage(OS),
+ Self ! {collected_sys,MU}
+ end),
+ {noreply, State#state{pid=Pid, wd_timer=WDTimer,
+ pending=[reg]}}
+ end;
+ _TimerRef ->
+ {noreply, State#state{pending=[reg|State#state.pending]}}
+ end;
+
+%% Memory data collected
+handle_info({collected_sys, {Alloc,Total}}, State) ->
+
+ %% Cancel watchdog timer (and as a security measure,
+ %% also flush any reg_collection_timeout message)
+ TimeSpent = case erlang:cancel_timer(State#state.wd_timer) of
+ false ->
+ State#state.helper_timeout;
+ TimeLeft ->
+ State#state.helper_timeout-TimeLeft
+ end,
+ flush(reg_collection_timeout),
+
+ %% First check if this is the result of a periodic memory check
+ %% and update alarms and State if this is the case
+ State2 =
+ case lists:member(reg, State#state.pending) of
+ true ->
+
+ %% Check if system alarm should be set/cleared
+ if
+ Alloc > State#state.sys_mem_watermark*Total ->
+ set_alarm(system_memory_high_watermark, []);
+ true ->
+ clear_alarm(system_memory_high_watermark)
+ end,
+
+ %% Check if process data should be collected
+ case State#state.sys_only of
+ false ->
+ {Pid, Bytes} = get_worst_memory_user(),
+ Threshold= State#state.proc_mem_watermark*Total,
+
+ %% Check if process alarm should be set/cleared
+ if
+ Bytes > Threshold ->
+ set_alarm(process_memory_high_watermark,
+ Pid);
+ true ->
+ clear_alarm(process_memory_high_watermark)
+ end,
+
+ State#state{mem_usage={Alloc, Total},
+ worst_mem_user={Pid, Bytes}};
+ true ->
+ State#state{mem_usage={Alloc, Total}}
+ end;
+ false ->
+ State
+ end,
+
+ %% Then send a reply to all waiting clients, in preserved time order
+ Worst = State2#state.worst_mem_user,
+ SysMemUsage = get_ext_memory_usage(State2#state.os, {Alloc,Total}),
+ reply(State2#state.pending, {Total,Alloc,Worst}, SysMemUsage),
+
+ %% Last, if this was a periodic check, start a timer for the next
+ %% one. New timeout = interval-time spent collecting,
+ case lists:member(reg, State#state.pending) of
+ true ->
+ Time = case State2#state.timeout - TimeSpent of
+ MS when MS<0 ->
+ 0;
+ MS ->
+ MS
+ end,
+ erlang:send_after(Time, self(), time_to_collect);
+ false ->
+ ignore
+ end,
+ {noreply, State2#state{wd_timer=undefined, pending=[]}};
+handle_info({'EXIT', Pid, normal}, State) when is_pid(Pid) ->
+ %% Temporary pid terminating when job is done
+ {noreply, State};
+
+%% Timeout during data collection
+handle_info(reg_collection_timeout, State) ->
+
+ %% Cancel memory collection (and as a security measure,
+ %% also flush any collected_sys message)
+ if
+ State#state.port_mode -> State#state.pid ! cancel;
+ true -> exit(State#state.pid, cancel)
+ end,
+ flush(collected_sys),
+
+ %% Issue a warning message
+ Str = "OS_MON (memsup) timeout, no data collected~n",
+ error_logger:warning_msg(Str),
+
+ %% Send a dummy reply to all waiting clients, preserving time order
+ reply(State#state.pending,
+ dummy_reply(get_memory_data, State#state.sys_only),
+ dummy_reply(get_system_memory_data)),
+
+ %% If it is a periodic check which has timed out, start a timer for
+ %% the next one
+ %% New timeout = interval-helper timeout
+ case lists:member(reg, State#state.pending) of
+ true ->
+ Time =
+ case State#state.timeout-State#state.helper_timeout of
+ MS when MS<0 -> 0;
+ MS -> MS
+ end,
+ erlang:send_after(Time, self(), time_to_collect);
+ false ->
+ ignore
+ end,
+ {noreply, State#state{wd_timer=undefined, pending=[]}};
+handle_info({'EXIT', Pid, cancel}, State) when is_pid(Pid) ->
+ %% Temporary pid terminating as ordered
+ {noreply, State};
+
+%% Extensive memory data collected (port_mode==true only)
+handle_info({collected_ext_sys, SysMemUsage}, State) ->
+
+ %% Cancel watchdog timer (and as a security mearure,
+ %% also flush any ext_collection_timeout message)
+ erlang:cancel_timer(State#state.ext_wd_timer),
+ flush(ext_collection_timeout),
+
+ %% Send the reply to all waiting clients, preserving time order
+ reply(State#state.ext_pending, undef, SysMemUsage),
+
+ {noreply, State#state{ext_wd_timer=undefined, ext_pending=[]}};
+
+%% Timeout during ext memory data collection (port_mode==true only)
+handle_info(ext_collection_timeout, State) ->
+
+ %% Cancel memory collection (and as a security measure,
+ %% also flush any collected_ext_sys message)
+ State#state.pid ! ext_cancel,
+ flush(collected_ext_sys),
+
+ %% Issue a warning message
+ Str = "OS_MON (memsup) timeout, no data collected~n",
+ error_logger:warning_msg(Str),
+
+ %% Send a dummy reply to all waiting clients, preserving time order
+ SysMemUsage = dummy_reply(get_system_memory_data),
+ reply(State#state.ext_pending, undef, SysMemUsage),
+
+ {noreply, State#state{ext_wd_timer=undefined, ext_pending=[]}};
+
+%% Error in data collecting (port connected or temporary) process
+handle_info({'EXIT', Pid, Reason}, State) when is_pid(Pid) ->
+ {stop, Reason, State};
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ if
+ State#state.port_mode -> State#state.pid ! close;
+ true -> ok
+ end,
+ clear_alarms(),
+ ok.
+
+%% os_mon-2.0.1
+%% For live downgrade to/upgrade from os_mon-1.8[.1] and -2.0
+code_change(Vsn, PrevState, "1.8") ->
+ case Vsn of
+
+ %% Downgrade from this version
+ {down, _Vsn} ->
+
+ %% Kill the helper process, if there is one,
+ %% and flush messages from it
+ case PrevState#state.pid of
+ Pid when is_pid(Pid) ->
+ unlink(Pid), % to prevent 'EXIT' message
+ exit(Pid, cancel);
+ undefined -> ignore
+ end,
+ flush(collected_sys),
+ flush(collected_ext_sys),
+
+ %% Cancel timers, flush timeout messages
+ %% and send dummy replies to any pending clients
+ case PrevState#state.wd_timer of
+ undefined ->
+ ignore;
+ TimerRef1 ->
+ erlang:cancel_timer(TimerRef1),
+ SysOnly = PrevState#state.sys_only,
+ MemUsage = dummy_reply(get_memory_data, SysOnly),
+ SysMemUsage1 = dummy_reply(get_system_memory_data),
+ reply(PrevState#state.pending,MemUsage,SysMemUsage1)
+ end,
+ case PrevState#state.ext_wd_timer of
+ undefined ->
+ ignore;
+ TimerRef2 ->
+ erlang:cancel_timer(TimerRef2),
+ SysMemUsage2 = dummy_reply(get_system_memory_data),
+ reply(PrevState#state.pending, undef, SysMemUsage2)
+ end,
+ flush(reg_collection_timeout),
+ flush(ext_collection_timeout),
+
+ %% Downgrade to old state record
+ State = {state,
+ PrevState#state.timeout,
+ PrevState#state.mem_usage,
+ PrevState#state.worst_mem_user,
+ PrevState#state.sys_mem_watermark,
+ PrevState#state.proc_mem_watermark,
+ not PrevState#state.sys_only, % collect_procmem
+ undefined, % wd_timer
+ [], % pending
+ undefined, % ext_wd_timer
+ [], % ext_pending
+ PrevState#state.helper_timeout},
+ {ok, State};
+
+ %% Upgrade to this version
+ _Vsn ->
+
+ %% Old state record
+ {state,
+ Timeout, MemUsage, WorstMemUser,
+ SysMemWatermark, ProcMemWatermark, CollProc,
+ WDTimer, Pending, ExtWDTimer, ExtPending,
+ HelperTimeout} = PrevState,
+ SysOnly = not CollProc,
+
+ %% Flush memsup_helper messages
+ flush(collected_sys),
+ flush(collected_proc),
+ flush(collected_ext_sys),
+
+ %% Cancel timers, flush timeout messages
+ %% and send dummy replies to any pending clients
+ case WDTimer of
+ undefined ->
+ ignore;
+ TimerRef1 ->
+ erlang:cancel_timer(TimerRef1),
+ MemUsage = dummy_reply(get_memory_data, SysOnly),
+ Pending2 = lists:map(fun(From) -> {reg,From} end,
+ Pending),
+ reply(Pending2, MemUsage, undef)
+ end,
+ case ExtWDTimer of
+ undefined ->
+ ignore;
+ TimerRef2 ->
+ erlang:cancel_timer(TimerRef2),
+ SysMemUsage = dummy_reply(get_system_memory_data),
+ ExtPending2 = lists:map(fun(From) -> {ext,From} end,
+ ExtPending),
+ reply(ExtPending2, undef, SysMemUsage)
+ end,
+ flush(reg_collection_timeout),
+ flush(ext_collection_timeout),
+
+ OS = os:type(),
+ PortMode = case OS of
+ {unix, darwin} -> false;
+ {unix, freebsd} -> false;
+ {unix, linux} -> false;
+ {unix, openbsd} -> true;
+ {unix, sunos} -> true;
+ {win32, _OSname} -> false;
+ vxworks -> true
+ end,
+ Pid = if
+ PortMode -> spawn_link(fun() -> port_init() end);
+ not PortMode -> undefined
+ end,
+
+ %% Upgrade to this state record
+ State = #state{os = OS,
+ port_mode = PortMode,
+ mem_usage = MemUsage,
+ worst_mem_user = WorstMemUser,
+ sys_only = SysOnly,
+ timeout = Timeout,
+ helper_timeout = HelperTimeout,
+ sys_mem_watermark = SysMemWatermark,
+ proc_mem_watermark = ProcMemWatermark,
+ pid = Pid,
+ wd_timer = undefined,
+ ext_wd_timer = undefined,
+ pending = [],
+ ext_pending = []},
+ {ok, State}
+ end;
+code_change(_Vsn, State, "2.0") ->
+
+ %% Restart the port process (it must use new memsup code)
+ Pid = case State#state.port_mode of
+ true ->
+ State#state.pid ! close,
+ spawn_link(fun() -> port_init() end);
+ false ->
+ State#state.pid
+ end,
+ {ok, State#state{pid=Pid}};
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Other exports
+%%----------------------------------------------------------------------
+
+format_status(_Opt, [_PDict, #state{timeout=Timeout, mem_usage=MemUsage,
+ worst_mem_user=WorstMemUser}]) ->
+ {Allocated, Total} = MemUsage,
+ WorstMemFormat = case WorstMemUser of
+ {Pid, Mem} ->
+ [{"Pid", Pid}, {"Memory", Mem}];
+ undefined ->
+ undefined
+ end,
+ [{data, [{"Timeout", Timeout}]},
+ {items, {"Memory Usage", [{"Allocated", Allocated},
+ {"Total", Total}]}},
+ {items, {"Worst Memory User", WorstMemFormat}}].
+
+
+%%----------------------------------------------------------------------
+%% Internal functions
+%%----------------------------------------------------------------------
+
+%%-- Fetching kernel bit support ---------------------------------------
+
+get_os_wordsize({unix, sunos}) ->
+ String = clean_string(os:cmd("isainfo -b")),
+ erlang:list_to_integer(String);
+get_os_wordsize({unix, irix64}) -> 64;
+get_os_wordsize({unix, irix}) -> 32;
+get_os_wordsize({unix, linux}) -> get_os_wordsize_with_uname();
+get_os_wordsize({unix, darwin}) -> get_os_wordsize_with_uname();
+get_os_wordsize({unix, netbsd}) -> get_os_wordsize_with_uname();
+get_os_wordsize({unix, freebsd}) -> get_os_wordsize_with_uname();
+get_os_wordsize({unix, openbsd}) -> get_os_wordsize_with_uname();
+get_os_wordsize(_) -> unsupported_os.
+
+get_os_wordsize_with_uname() ->
+ String = clean_string(os:cmd("uname -m")),
+ case String of
+ "x86_64" -> 64;
+ "sparc64" -> 64;
+ _ -> 32
+ end.
+
+clean_string(String) -> lists:flatten(string:tokens(String,"\r\n\t ")).
+
+
+%%--Replying to pending clients-----------------------------------------
+
+reply(Pending, MemUsage, SysMemUsage) ->
+ lists:foreach(fun(reg) ->
+ ignore;
+ ({reg, From}) ->
+ gen_server:reply(From, MemUsage);
+ ({ext, From}) ->
+ gen_server:reply(From, SysMemUsage)
+ end,
+ lists:reverse(Pending)).
+
+%%--Collect memory data, no port----------------------------------------
+
+%% get_memory_usage(OS) -> {Alloc, Total}
+
+%% Darwin:
+%% Uses vm_stat command. This appears to lie about the page size in
+%% Mac OS X 10.2.2 - the pages given are based on 4000 bytes, but
+%% the vm_stat command tells us that it is 4096...
+get_memory_usage({unix,darwin}) ->
+ Str1 = os:cmd("/usr/bin/vm_stat"),
+
+ {[Free], Str2} = fread_value("Pages free:~d.", Str1),
+ {[Active], Str3} = fread_value("Pages active:~d.", Str2),
+ {[Inactive], Str4} = fread_value("Pages inactive:~d.", Str3),
+ {[_], Str5} = fread_value("Pages speculative:~d.", Str4),
+ {[Wired], _} = fread_value("Pages wired down:~d.", Str5),
+
+ NMemUsed = (Wired + Active + Inactive) * 4000,
+ NMemTotal = NMemUsed + Free * 4000,
+ {NMemUsed,NMemTotal};
+
+%% FreeBSD: Look in /usr/include/sys/vmmeter.h for the format of struct
+%% vmmeter
+get_memory_usage({unix,freebsd}) ->
+ PageSize = freebsd_sysctl("vm.stats.vm.v_page_size"),
+ PageCount = freebsd_sysctl("vm.stats.vm.v_page_count"),
+ FreeCount = freebsd_sysctl("vm.stats.vm.v_free_count"),
+ NMemUsed = (PageCount - FreeCount) * PageSize,
+ NMemTotal = PageCount * PageSize,
+ {NMemUsed, NMemTotal};
+
+%% Win32: Find out how much memory is in use by asking
+%% the os_mon_sysinfo process.
+get_memory_usage({win32,_OSname}) ->
+ [Result|_] = os_mon_sysinfo:get_mem_info(),
+ {ok, [_MemLoad, TotPhys, AvailPhys,
+ _TotPage, _AvailPage, _TotV, _AvailV], _RestStr} =
+ io_lib:fread("~d~d~d~d~d~d~d", Result),
+ {TotPhys-AvailPhys, TotPhys}.
+
+fread_value(Format, Str0) ->
+ case io_lib:fread(Format, skip_to_eol(Str0)) of
+ {error, {fread, input}} -> {[0], Str0};
+ {ok, Value, Str1} -> {Value, Str1}
+ end.
+
+skip_to_eol([]) -> [];
+skip_to_eol([$\n | T]) -> T;
+skip_to_eol([_ | T]) -> skip_to_eol(T).
+
+freebsd_sysctl(Def) ->
+ list_to_integer(os:cmd("/sbin/sysctl -n " ++ Def) -- "\n").
+
+%% get_ext_memory_usage(OS, {Alloc, Total}) -> [{Tag, Bytes}]
+get_ext_memory_usage(OS, {Alloc, Total}) ->
+ case OS of
+ {win32, _} ->
+ [{total_memory, Total}, {free_memory, Total-Alloc},
+ {system_total_memory, Total}];
+ {unix, linux} ->
+ [{total_memory, Total}, {free_memory, Total-Alloc},
+ %% corr. unless setrlimit() set
+ {system_total_memory, Total}];
+ {unix, freebsd} ->
+ [{total_memory, Total}, {free_memory, Total-Alloc},
+ {system_total_memory, Total}];
+ {unix, darwin} ->
+ [{total_memory, Total}, {free_memory, Total-Alloc},
+ {system_total_memory, Total}];
+ _ -> % OSs using a port
+ dummy % not sent anyway
+ end.
+
+%%--Collect memory data, using port-------------------------------------
+
+port_init() ->
+ process_flag(trap_exit, true),
+ Port = start_portprogram(),
+ port_idle(Port).
+
+start_portprogram() ->
+ Command = filename:join([code:priv_dir(os_mon), "bin", "memsup"]),
+ open_port({spawn, Command}, [{packet, 1}]).
+
+%% The connected process loops are a bit awkward (several different
+%% functions doing almost the same thing) as
+%% a) strategies for receiving regular memory data and extensive
+%% memory data are different
+%% b) memory collection can be cancelled, in which case the process
+%% should still wait for port response (which should come
+%% eventually!) but not receive any requests or cancellations
+%% meanwhile to prevent getting out of synch.
+port_idle(Port) ->
+ receive
+ {Memsup, collect_sys} ->
+ Port ! {self(), {command, [?SHOW_MEM]}},
+ get_memory_usage(Port, undefined, Memsup);
+ {Memsup, collect_ext_sys} ->
+ Port ! {self(), {command, [?SHOW_SYSTEM_MEM]}},
+ get_ext_memory_usage(Port, [], Memsup);
+ cancel ->
+ %% Received after reply already has been delivered...
+ port_idle(Port);
+ ext_cancel ->
+ %% Received after reply already has been delivered...
+ port_idle(Port);
+ close ->
+ port_close(Port);
+ {Port, {data, Data}} ->
+ exit({port_error, Data});
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+
+get_memory_usage(Port, Alloc, Memsup) ->
+ receive
+ {Port, {data, Data}} when Alloc==undefined ->
+ get_memory_usage(Port, erlang:list_to_integer(Data, 16), Memsup);
+ {Port, {data, Data}} ->
+ Total = erlang:list_to_integer(Data, 16),
+ Memsup ! {collected_sys, {Alloc, Total}},
+ port_idle(Port);
+ cancel ->
+ get_memory_usage_cancelled(Port, Alloc);
+ close ->
+ port_close(Port);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+get_memory_usage_cancelled(Port, Alloc) ->
+ receive
+ {Port, {data, _Data}} when Alloc==undefined ->
+ get_memory_usage_cancelled(Port, 0);
+ {Port, {data, _Data}} ->
+ port_idle(Port);
+ close ->
+ port_close(Port);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+
+get_ext_memory_usage(Port, Accum, Memsup) ->
+ Tab = [
+ {?MEM_SYSTEM_TOTAL, system_total_memory},
+ {?MEM_TOTAL, total_memory},
+ {?MEM_FREE, free_memory},
+ {?MEM_BUFFERS, buffered_memory},
+ {?MEM_CACHED, cached_memory},
+ {?MEM_SHARED, shared_memory},
+ {?MEM_LARGEST_FREE, largest_free},
+ {?MEM_NUMBER_OF_FREE, number_of_free},
+ {?SWAP_TOTAL, total_swap},
+ {?SWAP_FREE, free_swap}
+ ],
+ receive
+ {Port, {data, [?SHOW_SYSTEM_MEM_END]}} ->
+ Memsup ! {collected_ext_sys, Accum},
+ port_idle(Port);
+ {Port, {data, [Tag]}} ->
+ case lists:keysearch(Tag, 1, Tab) of
+ {value, {Tag, ATag}} ->
+ get_ext_memory_usage(ATag, Port, Accum, Memsup);
+ _ ->
+ exit({memsup_port_error, {Port,[Tag]}})
+ end;
+ ext_cancel ->
+ get_ext_memory_usage_cancelled(Port);
+ close ->
+ port_close(Port);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+get_ext_memory_usage_cancelled(Port) ->
+ Tab = [
+ {?MEM_SYSTEM_TOTAL, system_total_memory},
+ {?MEM_TOTAL, total_memory},
+ {?MEM_FREE, free_memory},
+ {?MEM_BUFFERS, buffered_memory},
+ {?MEM_CACHED, cached_memory},
+ {?MEM_SHARED, shared_memory},
+ {?MEM_LARGEST_FREE, largest_free},
+ {?MEM_NUMBER_OF_FREE, number_of_free},
+ {?SWAP_TOTAL, total_swap},
+ {?SWAP_FREE, free_swap}
+ ],
+ receive
+ {Port, {data, [?SHOW_SYSTEM_MEM_END]}} ->
+ port_idle(Port);
+ {Port, {data, [Tag]}} ->
+ case lists:keysearch(Tag, 1, Tab) of
+ {value, {Tag, ATag}} ->
+ get_ext_memory_usage_cancelled(ATag, Port);
+ _ ->
+ exit({memsup_port_error, {Port,[Tag]}})
+ end;
+ close ->
+ port_close(Port);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+
+get_ext_memory_usage(ATag, Port, Accum0, Memsup) ->
+ receive
+ {Port, {data, Data}} ->
+ Accum = [{ATag,erlang:list_to_integer(Data, 16)}|Accum0],
+ get_ext_memory_usage(Port, Accum, Memsup);
+ cancel ->
+ get_ext_memory_usage_cancelled(ATag, Port);
+ close ->
+ port_close(Port);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+get_ext_memory_usage_cancelled(_ATag, Port) ->
+ receive
+ {Port, {data, _Data}} ->
+ get_ext_memory_usage_cancelled(Port);
+ close ->
+ port_close(Port);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason});
+ {'EXIT', _Memsup, _Reason} ->
+ port_close(Port)
+ end.
+
+%%--Collect process data------------------------------------------------
+
+%% get_worst_memory_user() -> {Pid, Bytes}
+get_worst_memory_user() ->
+ get_worst_memory_user(processes(), self(), 0).
+
+get_worst_memory_user([Pid|Pids], MaxPid, MaxMemBytes) ->
+ case process_memory(Pid) of
+ undefined ->
+ get_worst_memory_user(Pids, MaxPid, MaxMemBytes);
+ MemoryBytes when MemoryBytes>MaxMemBytes ->
+ get_worst_memory_user(Pids, Pid, MemoryBytes);
+ _MemoryBytes ->
+ get_worst_memory_user(Pids, MaxPid, MaxMemBytes)
+ end;
+get_worst_memory_user([], MaxPid, MaxMemBytes) ->
+ {MaxPid, MaxMemBytes}.
+
+process_memory(Pid) ->
+ case process_info(Pid, memory) of
+ {memory, Bytes} ->
+ Bytes;
+ undefined -> % Pid must have died
+ undefined
+ end.
+
+%%--Alarm handling------------------------------------------------------
+
+set_alarm(AlarmId, AlarmDescr) ->
+ case get(AlarmId) of
+ set ->
+ ok;
+ undefined ->
+ alarm_handler:set_alarm({AlarmId, AlarmDescr}),
+ put(AlarmId, set)
+ end.
+
+clear_alarm(AlarmId) ->
+ case get(AlarmId) of
+ set ->
+ alarm_handler:clear_alarm(AlarmId),
+ erase(AlarmId);
+ _ ->
+ ok
+ end.
+
+clear_alarms() ->
+ lists:foreach(fun({system_memory_high_watermark = Id, set}) ->
+ alarm_handler:clear_alarm(Id);
+ ({process_memory_high_watermark = Id, set}) ->
+ alarm_handler:clear_alarm(Id);
+ (_Other) ->
+ ignore
+ end,
+ get()).
+
+%%--Auxiliary-----------------------------------------------------------
+
+%% Type conversions
+minutes_to_ms(Minutes) -> trunc(60000*Minutes).
+sec_to_ms(Sec) -> trunc(1000*Sec).
+ms_to_sec(MS) -> MS div 1000.
+
+flush(Msg) ->
+ receive
+ {Msg, _} -> true;
+ Msg -> true
+ after 0 ->
+ true
+ end.