1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(memsup).
21-behaviour(gen_server).
22
23%% API
24-export([start_link/0]). % for supervisor
25-export([get_memory_data/0, get_system_memory_data/0,
26	 get_check_interval/0, set_check_interval/1,
27	 get_procmem_high_watermark/0, set_procmem_high_watermark/1,
28	 get_sysmem_high_watermark/0, set_sysmem_high_watermark/1,
29	 get_helper_timeout/0, set_helper_timeout/1,
30	 get_os_wordsize/0]).
31-export([dummy_reply/1, param_type/2, param_default/1]).
32
33%% gen_server callbacks
34-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
35	 terminate/2, code_change/3]).
36
37%% Other exports
38-export([format_status/2]).
39
40-include("memsup.hrl").
41
42-record(state,
43	{os,                  % {OSfamily,OSname} | OSfamily
44	 port_mode,           % bool()
45
46	 mem_usage,           % undefined | {Alloc, Total}
47	 worst_mem_user,      % undefined | {Pid, Alloc}
48
49	 sys_only,            % bool()  memsup_system_only
50	 timeout,             % int()   memory_check_interval, ms
51	 helper_timeout,      % int()   memsup_helper_timeout, ms
52	 sys_mem_watermark,   % float() system_memory_high_watermark, %
53	 proc_mem_watermark,  % float() process_memory_high_watermark, %
54
55	 pid,                 % undefined | pid()
56	 wd_timer,            % undefined | TimerRef
57	 ext_wd_timer,        % undefined | TimerRef
58	 pending = [],        % [reg | {reg,From} | {ext,From}]
59	 ext_pending = []     % [{ext,From}]
60	}).
61
62%%----------------------------------------------------------------------
63%% API
64%%----------------------------------------------------------------------
65
66start_link() ->
67    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
68
69get_os_wordsize() ->
70    os_mon:call(memsup, get_os_wordsize, infinity).
71
72get_memory_data() ->
73    os_mon:call(memsup, get_memory_data, infinity).
74
75get_system_memory_data() ->
76    os_mon:call(memsup, get_system_memory_data, infinity).
77
78get_check_interval() ->
79    os_mon:call(memsup, get_check_interval, infinity).
80set_check_interval(Minutes) ->
81    case param_type(memory_check_interval, Minutes) of
82	true ->
83	    MS = minutes_to_ms(Minutes), % for backwards compatibility
84	    os_mon:call(memsup, {set_check_interval, MS}, infinity);
85	false ->
86	    erlang:error(badarg)
87    end.
88
89get_procmem_high_watermark() ->
90    os_mon:call(memsup, get_procmem_high_watermark, infinity).
91set_procmem_high_watermark(Float) ->
92    case param_type(process_memory_high_watermark, Float) of
93	true ->
94	    os_mon:call(memsup, {set_procmem_high_watermark, Float},
95			infinity);
96	false ->
97	    erlang:error(badarg)
98    end.
99
100get_sysmem_high_watermark() ->
101    os_mon:call(memsup, get_sysmem_high_watermark, infinity).
102set_sysmem_high_watermark(Float) ->
103    case param_type(system_memory_high_watermark, Float) of
104	true ->
105	    os_mon:call(memsup, {set_sysmem_high_watermark, Float},
106			infinity);
107	false ->
108	    erlang:error(badarg)
109    end.
110
111get_helper_timeout() ->
112    os_mon:call(memsup, get_helper_timeout, infinity).
113set_helper_timeout(Seconds) ->
114    case param_type(memsup_helper_timeout, Seconds) of
115	true ->
116	    os_mon:call(memsup, {set_helper_timeout, Seconds}, infinity);
117	false ->
118	    erlang:error(badarg)
119    end.
120
121dummy_reply(get_memory_data) ->
122    dummy_reply(get_memory_data,
123		os_mon:get_env(memsup, memsup_system_only));
124dummy_reply(get_system_memory_data) ->
125    [];
126dummy_reply(get_os_wordsize) ->
127    0;
128dummy_reply(get_check_interval) ->
129    minutes_to_ms(os_mon:get_env(memsup, memory_check_interval));
130dummy_reply({set_check_interval, _}) ->
131    ok;
132dummy_reply(get_procmem_high_watermark) ->
133    trunc(100 * os_mon:get_env(memsup, process_memory_high_watermark));
134dummy_reply({set_procmem_high_watermark, _}) ->
135    ok;
136dummy_reply(get_sysmem_high_watermark) ->
137    trunc(100 * os_mon:get_env(memsup, system_memory_high_watermark));
138dummy_reply({set_sysmem_high_watermark, _}) ->
139    ok;
140dummy_reply(get_helper_timeout) ->
141    os_mon:get_env(memsup, memsup_helper_timeout);
142dummy_reply({set_helper_timeout, _}) ->
143    ok.
144dummy_reply(get_memory_data, true) ->
145    {0,0,undefined};
146dummy_reply(get_memory_data, false) ->
147    {0,0,{self(),0}}.
148
149param_type(memsup_system_only, Val) when Val==true; Val==false -> true;
150param_type(memory_check_interval, Val) when is_integer(Val),
151                                            Val>0 -> true;
152param_type(memsup_helper_timeout, Val) when is_integer(Val),
153                                            Val>0 -> true;
154param_type(system_memory_high_watermark, Val) when is_number(Val),
155						   0=<Val,
156						   Val=<1 -> true;
157param_type(process_memory_high_watermark, Val) when is_number(Val),
158						    0=<Val,
159						    Val=<1 -> true;
160param_type(_Param, _Val) -> false.
161
162param_default(memsup_system_only) -> false;
163param_default(memory_check_interval) -> 1;
164param_default(memsup_helper_timeout) -> 30;
165param_default(system_memory_high_watermark) -> 0.80;
166param_default(process_memory_high_watermark) -> 0.05.
167
168%%----------------------------------------------------------------------
169%% gen_server callbacks
170%%----------------------------------------------------------------------
171
172init([]) ->
173    process_flag(trap_exit, true),
174    process_flag(priority, low),
175
176    OS = os:type(),
177    PortMode = case OS of
178		   {unix, darwin} -> false;
179		   {unix, freebsd} -> false;
180		   {unix, dragonfly} -> false;
181		   % Linux supports this.
182		   {unix, linux} -> true;
183		   {unix, openbsd} -> true;
184		   {unix, netbsd} -> true;
185		   {unix, irix64} -> true;
186		   {unix, irix} -> true;
187		   {unix, sunos} -> true;
188		   {win32, _OSname} -> false;
189		   _ ->
190		       exit({unsupported_os, OS})
191	       end,
192    Pid = if
193	      PortMode ->
194		  spawn_link(fun() -> port_init() end);
195	      not PortMode ->
196		  undefined
197	  end,
198
199    %% Read the values of some configuration parameters
200    SysOnly = os_mon:get_env(memsup, memsup_system_only),
201    Timeout = os_mon:get_env(memsup, memory_check_interval),
202    HelperTimeout = os_mon:get_env(memsup, memsup_helper_timeout),
203    SysMem = os_mon:get_env(memsup, system_memory_high_watermark),
204    ProcMem = os_mon:get_env(memsup, process_memory_high_watermark),
205
206    %% Initiate first data collection
207    self() ! time_to_collect,
208
209    {ok, #state{os=OS, port_mode=PortMode,
210
211		sys_only           = SysOnly,
212		timeout            = minutes_to_ms(Timeout),
213		helper_timeout     = sec_to_ms(HelperTimeout),
214		sys_mem_watermark  = SysMem,
215		proc_mem_watermark = ProcMem,
216
217		pid=Pid}}.
218
219handle_call(get_os_wordsize, _From, State) ->
220    Wordsize = get_os_wordsize(State#state.os),
221    {reply, Wordsize, State};
222handle_call(get_memory_data, From, State) ->
223    %% Return result of latest memory check
224    case State#state.mem_usage of
225	{Alloc, Total} ->
226	    Worst = State#state.worst_mem_user,
227	    {reply, {Total, Alloc, Worst}, State};
228
229	%% Special case: get_memory_data called before any memory data
230	%% has been collected
231	undefined ->
232	    case State#state.wd_timer of
233		undefined ->
234		    WDTimer = erlang:send_after(State#state.timeout,
235						self(),
236						reg_collection_timeout),
237		    Pending = [{reg,From}],
238		    if
239			State#state.port_mode ->
240			    State#state.pid ! {self(), collect_sys},
241			    {noreply, State#state{wd_timer=WDTimer,
242						  pending=Pending}};
243			true ->
244			    OS = State#state.os,
245			    Self = self(),
246			    Pid = spawn_link(fun() ->
247						     MU = get_memory_usage(OS),
248						     Self ! {collected_sys,MU}
249					     end),
250			    {noreply, State#state{pid=Pid,
251						  wd_timer=WDTimer,
252						  pending=Pending}}
253		    end;
254		_TimerRef ->
255		    Pending = [{reg,From} | State#state.pending],
256		    {noreply, State#state{pending=Pending}}
257	    end
258    end;
259
260handle_call(get_system_memory_data,From,#state{port_mode=true}=State) ->
261    %% When using a port, the extensive memory collection is slightly
262    %% different than a regular one
263    case State#state.ext_wd_timer of
264	undefined ->
265	    WDTimer = erlang:send_after(State#state.helper_timeout,
266					self(),
267					ext_collection_timeout),
268	    State#state.pid ! {self(), collect_ext_sys},
269	    {noreply, State#state{ext_wd_timer=WDTimer,
270				  ext_pending=[{ext,From}]}};
271	_TimerRef ->
272	    Pending = [{ext,From} | State#state.ext_pending],
273	    {noreply, State#state{ext_pending=Pending}}
274    end;
275handle_call(get_system_memory_data, From, State) ->
276    %% When not using a port, the regular memory collection is used
277    %% for extensive memory data as well
278    case State#state.wd_timer of
279	undefined ->
280	    WDTimer = erlang:send_after(State#state.helper_timeout,
281					self(),
282					reg_collection_timeout),
283	    OS = State#state.os,
284	    Self = self(),
285	    Pid = spawn_link(fun() ->
286				     MemUsage = get_memory_usage(OS),
287				     Self ! {collected_sys, MemUsage}
288			     end),
289	    {noreply, State#state{pid=Pid, wd_timer=WDTimer,
290				  pending=[{ext,From}]}};
291	_TimerRef ->
292	    Pending = [{ext,From} | State#state.pending],
293	    {noreply, State#state{pending=Pending}}
294    end;
295
296handle_call(get_check_interval, _From, State) ->
297    {reply, State#state.timeout, State};
298handle_call({set_check_interval, MS}, _From, State) ->
299    {reply, ok, State#state{timeout=MS}};
300
301handle_call(get_procmem_high_watermark, _From, State) ->
302    {reply, trunc(100 * State#state.proc_mem_watermark), State};
303handle_call({set_procmem_high_watermark, Float}, _From, State) ->
304    {reply, ok, State#state{proc_mem_watermark=Float}};
305
306handle_call(get_sysmem_high_watermark, _From, State) ->
307    {reply, trunc(100 * State#state.sys_mem_watermark), State};
308handle_call({set_sysmem_high_watermark, Float}, _From, State) ->
309    {reply, ok, State#state{sys_mem_watermark=Float}};
310
311handle_call(get_helper_timeout, _From, State) ->
312    {reply, ms_to_sec(State#state.helper_timeout), State};
313handle_call({set_helper_timeout, Seconds}, _From, State) ->
314    {reply, ok, State#state{helper_timeout=sec_to_ms(Seconds)}};
315
316%% The following are only for test purposes (whitebox testing).
317handle_call({set_sys_hw, HW}, _From, State) ->
318    {reply, ok, State#state{sys_mem_watermark=HW}};
319handle_call({set_pid_hw, HW}, _From, State) ->
320    {reply, ok, State#state{proc_mem_watermark=HW}};
321handle_call(get_state, _From, State) ->
322    {reply, State, State}.
323
324handle_cast(_Msg, State) ->
325    {noreply, State}.
326
327%% It's time to check memory
328handle_info(time_to_collect, State) ->
329    case State#state.wd_timer of
330	undefined ->
331	    WDTimer = erlang:send_after(State#state.helper_timeout,
332					self(),
333					reg_collection_timeout),
334	    if
335		State#state.port_mode ->
336		    State#state.pid ! {self(), collect_sys},
337		    {noreply, State#state{wd_timer=WDTimer,
338					  pending=[reg]}};
339		true ->
340		    OS = State#state.os,
341		    Self = self(),
342		    Pid = spawn_link(fun() ->
343					     MU = get_memory_usage(OS),
344					     Self ! {collected_sys,MU}
345				     end),
346		    {noreply, State#state{pid=Pid, wd_timer=WDTimer,
347					  pending=[reg]}}
348	    end;
349	_TimerRef ->
350	    {noreply, State#state{pending=[reg|State#state.pending]}}
351    end;
352
353%% Memory data collected
354handle_info({collected_sys, {Alloc,Total}}, State) ->
355
356    %% Cancel watchdog timer (and as a security measure,
357    %% also flush any reg_collection_timeout message)
358    TimeSpent = case erlang:cancel_timer(State#state.wd_timer) of
359		    false ->
360			State#state.helper_timeout;
361		    TimeLeft ->
362			State#state.helper_timeout-TimeLeft
363		end,
364    flush(reg_collection_timeout),
365
366    %% First check if this is the result of a periodic memory check
367    %% and update alarms and State if this is the case
368    State2 =
369	case lists:member(reg, State#state.pending) of
370	    true ->
371
372		%% Check if system alarm should be set/cleared
373		if
374		    Alloc > State#state.sys_mem_watermark*Total ->
375			set_alarm(system_memory_high_watermark, []);
376		    true ->
377			clear_alarm(system_memory_high_watermark)
378		end,
379
380		%% Check if process data should be collected
381		case State#state.sys_only of
382		    false ->
383			{Pid, Bytes} = get_worst_memory_user(),
384			Threshold= State#state.proc_mem_watermark*Total,
385
386			%% Check if process alarm should be set/cleared
387			if
388			    Bytes > Threshold ->
389				set_alarm(process_memory_high_watermark,
390					  Pid);
391			    true ->
392				clear_alarm(process_memory_high_watermark)
393			end,
394
395			State#state{mem_usage={Alloc, Total},
396				    worst_mem_user={Pid, Bytes}};
397		    true ->
398			State#state{mem_usage={Alloc, Total}}
399		end;
400	    false ->
401		State
402	end,
403
404    %% Then send a reply to all waiting clients, in preserved time order
405    Worst = State2#state.worst_mem_user,
406    SysMemUsage = get_ext_memory_usage(State2#state.os, {Alloc,Total}),
407    reply(State2#state.pending, {Total,Alloc,Worst}, SysMemUsage),
408
409    %% Last, if this was a periodic check, start a timer for the next
410    %% one. New timeout = interval-time spent collecting,
411    _ = case lists:member(reg, State#state.pending) of
412            true ->
413                Time = case State2#state.timeout - TimeSpent of
414                           MS when MS<0 ->
415                               0;
416                           MS ->
417                               MS
418                       end,
419                erlang:send_after(Time, self(), time_to_collect);
420            false ->
421                ignore
422        end,
423    {noreply, State2#state{wd_timer=undefined, pending=[]}};
424handle_info({'EXIT', Pid, normal}, State) when is_pid(Pid) ->
425    %% Temporary pid terminating when job is done
426    {noreply, State};
427
428%% Timeout during data collection
429handle_info(reg_collection_timeout, State) ->
430
431    %% Cancel memory collection (and as a security measure,
432    %% also flush any collected_sys message)
433    if
434	State#state.port_mode -> State#state.pid ! cancel;
435	true -> exit(State#state.pid, cancel)
436    end,
437    flush(collected_sys),
438
439    %% Issue a warning message
440    Str = "OS_MON (memsup) timeout, no data collected~n",
441    error_logger:warning_msg(Str),
442
443    %% Send a dummy reply to all waiting clients, preserving time order
444    reply(State#state.pending,
445	  dummy_reply(get_memory_data, State#state.sys_only),
446	  dummy_reply(get_system_memory_data)),
447
448    %% If it is a periodic check which has timed out, start a timer for
449    %% the next one
450    %% New timeout = interval-helper timeout
451    _ = case lists:member(reg, State#state.pending) of
452            true ->
453                Time =
454                case State#state.timeout-State#state.helper_timeout of
455                    MS when MS<0 -> 0;
456                    MS -> MS
457                end,
458                erlang:send_after(Time, self(), time_to_collect);
459            false ->
460                ignore
461        end,
462    {noreply, State#state{wd_timer=undefined, pending=[]}};
463handle_info({'EXIT', Pid, cancel}, State) when is_pid(Pid) ->
464    %% Temporary pid terminating as ordered
465    {noreply, State};
466
467%% Extensive memory data collected (port_mode==true only)
468handle_info({collected_ext_sys, SysMemUsage}, State) ->
469
470    %% Cancel watchdog timer (and as a security mearure,
471    %% also flush any ext_collection_timeout message)
472    ok = erlang:cancel_timer(State#state.ext_wd_timer, [{async,true}]),
473    flush(ext_collection_timeout),
474
475    %% Send the reply to all waiting clients, preserving time order
476    reply(State#state.ext_pending, undef, SysMemUsage),
477
478    {noreply, State#state{ext_wd_timer=undefined, ext_pending=[]}};
479
480%% Timeout during ext memory data collection (port_mode==true only)
481handle_info(ext_collection_timeout, State) ->
482
483    %% Cancel memory collection (and as a security measure,
484    %% also flush any collected_ext_sys message)
485    State#state.pid ! ext_cancel,
486    flush(collected_ext_sys),
487
488    %% Issue a warning message
489    Str = "OS_MON (memsup) timeout, no data collected~n",
490    error_logger:warning_msg(Str),
491
492    %% Send a dummy reply to all waiting clients, preserving time order
493    SysMemUsage = dummy_reply(get_system_memory_data),
494    reply(State#state.ext_pending, undef, SysMemUsage),
495
496    {noreply, State#state{ext_wd_timer=undefined, ext_pending=[]}};
497
498%% Error in data collecting (port connected or temporary) process
499handle_info({'EXIT', Pid, Reason}, State) when is_pid(Pid) ->
500    {stop, Reason, State};
501
502handle_info(_Info, State) ->
503    {noreply, State}.
504
505terminate(_Reason, State) ->
506    if
507	State#state.port_mode -> State#state.pid ! close;
508	true -> ok
509    end,
510    clear_alarms(),
511    ok.
512
513%% os_mon-2.0.1
514%% For live downgrade to/upgrade from os_mon-1.8[.1] and -2.0
515code_change(Vsn, PrevState, "1.8") ->
516    case Vsn of
517
518	%% Downgrade from this version
519	{down, _Vsn} ->
520
521	    %% Kill the helper process, if there is one,
522	    %% and flush messages from it
523	    case PrevState#state.pid of
524		Pid when is_pid(Pid) ->
525		    unlink(Pid), % to prevent 'EXIT' message
526		    exit(Pid, cancel);
527		undefined -> ignore
528	    end,
529	    flush(collected_sys),
530	    flush(collected_ext_sys),
531
532	    %% Cancel timers, flush timeout messages
533	    %% and send dummy replies to any pending clients
534	    case PrevState#state.wd_timer of
535		undefined ->
536		    ignore;
537		TimerRef1 ->
538                    ok = erlang:cancel_timer(TimerRef1, [{async,true}]),
539		    SysOnly = PrevState#state.sys_only,
540		    MemUsage = dummy_reply(get_memory_data, SysOnly),
541		    SysMemUsage1 = dummy_reply(get_system_memory_data),
542		    reply(PrevState#state.pending,MemUsage,SysMemUsage1)
543	    end,
544	    case PrevState#state.ext_wd_timer of
545		undefined ->
546		    ignore;
547		TimerRef2 ->
548                    ok = erlang:cancel_timer(TimerRef2, [{async,true}]),
549		    SysMemUsage2 = dummy_reply(get_system_memory_data),
550		    reply(PrevState#state.pending, undef, SysMemUsage2)
551	    end,
552	    flush(reg_collection_timeout),
553	    flush(ext_collection_timeout),
554
555	    %% Downgrade to old state record
556	    State = {state,
557		     PrevState#state.timeout,
558		     PrevState#state.mem_usage,
559		     PrevState#state.worst_mem_user,
560		     PrevState#state.sys_mem_watermark,
561		     PrevState#state.proc_mem_watermark,
562		     not PrevState#state.sys_only, % collect_procmem
563		     undefined, % wd_timer
564		     [],        % pending
565		     undefined, % ext_wd_timer
566		     [],        % ext_pending
567		     PrevState#state.helper_timeout},
568	    {ok, State};
569
570	%% Upgrade to this version
571	_Vsn ->
572
573	    %% Old state record
574	    {state,
575	     Timeout, MemUsage, WorstMemUser,
576	     SysMemWatermark, ProcMemWatermark, CollProc,
577	     WDTimer, Pending, ExtWDTimer, ExtPending,
578	     HelperTimeout} = PrevState,
579	    SysOnly = not CollProc,
580
581	    %% Flush memsup_helper messages
582	    flush(collected_sys),
583	    flush(collected_proc),
584	    flush(collected_ext_sys),
585
586	    %% Cancel timers, flush timeout messages
587	    %% and send dummy replies to any pending clients
588	    case WDTimer of
589		undefined ->
590		    ignore;
591		TimerRef1 ->
592                    ok = erlang:cancel_timer(TimerRef1, [{async,true}]),
593		    MemUsage = dummy_reply(get_memory_data, SysOnly),
594		    Pending2 = lists:map(fun(From) -> {reg,From} end,
595					 Pending),
596		    reply(Pending2, MemUsage, undef)
597	    end,
598	    case ExtWDTimer of
599		undefined ->
600		    ignore;
601		TimerRef2 ->
602                    ok = erlang:cancel_timer(TimerRef2, [{async,true}]),
603		    SysMemUsage = dummy_reply(get_system_memory_data),
604		    ExtPending2 = lists:map(fun(From) -> {ext,From} end,
605					    ExtPending),
606		    reply(ExtPending2, undef, SysMemUsage)
607	    end,
608	    flush(reg_collection_timeout),
609	    flush(ext_collection_timeout),
610
611	    OS = os:type(),
612	    PortMode = case OS of
613			   {unix, darwin} -> false;
614			   {unix, freebsd} -> false;
615			   {unix, dragonfly} -> false;
616			   {unix, linux} -> false;
617			   {unix, openbsd} -> true;
618			   {unix, netbsd} -> true;
619			   {unix, sunos} -> true;
620			   {win32, _OSname} -> false
621		       end,
622	    Pid = if
623		      PortMode -> spawn_link(fun() -> port_init() end);
624		      not PortMode -> undefined
625		  end,
626
627	    %% Upgrade to this state record
628	    State = #state{os = OS,
629			   port_mode = PortMode,
630			   mem_usage = MemUsage,
631			   worst_mem_user = WorstMemUser,
632			   sys_only  = SysOnly,
633			   timeout         = Timeout,
634			   helper_timeout  = HelperTimeout,
635			   sys_mem_watermark = SysMemWatermark,
636			   proc_mem_watermark = ProcMemWatermark,
637			   pid                = Pid,
638			   wd_timer           = undefined,
639			   ext_wd_timer       = undefined,
640			   pending            = [],
641			   ext_pending        = []},
642	    {ok, State}
643    end;
644code_change(_Vsn, State, "2.0") ->
645
646    %% Restart the port process (it must use new memsup code)
647    Pid = case State#state.port_mode of
648	      true ->
649		  State#state.pid ! close,
650		  spawn_link(fun() -> port_init() end);
651	      false ->
652		  State#state.pid
653	  end,
654    {ok, State#state{pid=Pid}};
655
656code_change(_OldVsn, State, _Extra) ->
657    {ok, State}.
658
659%%----------------------------------------------------------------------
660%% Other exports
661%%----------------------------------------------------------------------
662
663format_status(_Opt, [_PDict, #state{timeout=Timeout, mem_usage=MemUsage,
664				    worst_mem_user=WorstMemUser}]) ->
665    {Allocated, Total} = MemUsage,
666    WorstMemFormat = case WorstMemUser of
667			 {Pid, Mem} ->
668			     [{"Pid", Pid}, {"Memory", Mem}];
669			 undefined ->
670			     undefined
671		     end,
672    [{data, [{"Timeout", Timeout}]},
673     {items, {"Memory Usage", [{"Allocated", Allocated},
674			       {"Total", Total}]}},
675     {items, {"Worst Memory User", WorstMemFormat}}].
676
677
678%%----------------------------------------------------------------------
679%% Internal functions
680%%----------------------------------------------------------------------
681
682%%-- Fetching kernel bit support ---------------------------------------
683
684get_os_wordsize({unix, sunos}) ->
685    String = clean_string(os:cmd("isainfo -b")),
686    erlang:list_to_integer(String);
687get_os_wordsize({unix, irix64})  -> 64;
688get_os_wordsize({unix, irix})    -> 32;
689get_os_wordsize({unix, linux})   -> get_os_wordsize_with_uname();
690get_os_wordsize({unix, darwin})  -> get_os_wordsize_with_uname();
691get_os_wordsize({unix, netbsd})  -> get_os_wordsize_with_uname();
692get_os_wordsize({unix, freebsd}) -> get_os_wordsize_with_uname();
693get_os_wordsize({unix, dragonfly}) -> get_os_wordsize_with_uname();
694get_os_wordsize({unix, openbsd}) -> get_os_wordsize_with_uname();
695get_os_wordsize(_)               -> unsupported_os.
696
697get_os_wordsize_with_uname() ->
698    String = clean_string(os:cmd("uname -m")),
699    case String of
700	"x86_64"  -> 64;
701	"sparc64" -> 64;
702	"amd64"   -> 64;
703	"ppc64"   -> 64;
704	"s390x"   -> 64;
705	_         -> 32
706    end.
707
708clean_string(String) -> lists:flatten(string:lexemes(String,[[$\r,$\n]|"\n\t "])).
709
710
711%%--Replying to pending clients-----------------------------------------
712
713reply(Pending, MemUsage, SysMemUsage) ->
714    lists:foreach(fun(reg) ->
715			  ignore;
716		     ({reg, From}) ->
717			  gen_server:reply(From, MemUsage);
718		     ({ext, From}) ->
719			  gen_server:reply(From, SysMemUsage)
720		  end,
721		  lists:reverse(Pending)).
722
723%%--Collect memory data, no port----------------------------------------
724
725%% get_memory_usage(OS) -> {Alloc, Total}
726
727%% Darwin:
728%% Uses vm_stat command.
729get_memory_usage({unix,darwin}) ->
730    Str1 = os:cmd("/usr/bin/vm_stat"),
731    PageSize = 4096,
732
733    {[Free],        Str2} = fread_value("Pages free:~d.", Str1),
734    {[Active],      Str3} = fread_value("Pages active:~d.", Str2),
735    {[Inactive],    Str4} = fread_value("Pages inactive:~d.", Str3),
736    {[Speculative], Str5} = fread_value("Pages speculative:~d.", Str4),
737    {[Wired],       _} = fread_value("Pages wired down:~d.", Str5),
738
739    NMemUsed  = (Wired + Active + Inactive) * PageSize,
740    NMemTotal = NMemUsed + (Free + Speculative) * PageSize,
741    {NMemUsed,NMemTotal};
742
743%% FreeBSD: Look in /usr/include/sys/vmmeter.h for the format of struct
744%% vmmeter
745get_memory_usage({unix,OSname}) when OSname == freebsd; OSname == dragonfly ->
746    PageSize  = freebsd_sysctl("vm.stats.vm.v_page_size"),
747    PageCount = freebsd_sysctl("vm.stats.vm.v_page_count"),
748    FreeCount = freebsd_sysctl("vm.stats.vm.v_free_count"),
749    NMemUsed  = (PageCount - FreeCount) * PageSize,
750    NMemTotal = PageCount * PageSize,
751    {NMemUsed, NMemTotal};
752
753%% Win32: Find out how much memory is in use by asking
754%% the os_mon_sysinfo process.
755get_memory_usage({win32,_OSname}) ->
756    [Result|_] = os_mon_sysinfo:get_mem_info(),
757    {ok, [_MemLoad, TotPhys, AvailPhys,
758	  _TotPage, _AvailPage, _TotV, _AvailV], _RestStr} =
759	io_lib:fread("~d~d~d~d~d~d~d", Result),
760    {TotPhys-AvailPhys, TotPhys}.
761
762fread_value(Format, Str0) ->
763    case io_lib:fread(Format, skip_to_eol(Str0)) of
764    	{error, {fread, input}} -> {[0], Str0};
765	{ok, Value, Str1} -> {Value, Str1}
766    end.
767
768skip_to_eol([]) -> [];
769skip_to_eol([$\n | T]) -> T;
770skip_to_eol([_ | T]) -> skip_to_eol(T).
771
772freebsd_sysctl(Def) ->
773    list_to_integer(os:cmd("/sbin/sysctl -n " ++ Def) -- "\n").
774
775%% get_ext_memory_usage(OS, {Alloc, Total}) -> [{Tag, Bytes}]
776get_ext_memory_usage(OS, {Alloc, Total}) ->
777    case OS of
778	{win32, _} ->
779	    [{total_memory, Total}, {free_memory, Total-Alloc},
780	     {system_total_memory, Total}];
781	{unix, linux} ->
782	    [{total_memory, Total}, {free_memory, Total-Alloc},
783	     %% corr. unless setrlimit() set
784	     {system_total_memory, Total}];
785	{unix, freebsd} ->
786	    [{total_memory, Total}, {free_memory, Total-Alloc},
787	     {system_total_memory, Total}];
788	{unix, dragonfly} ->
789	    [{total_memory, Total}, {free_memory, Total-Alloc},
790	     {system_total_memory, Total}];
791	{unix, darwin} ->
792	    [{total_memory, Total}, {free_memory, Total-Alloc},
793	     {system_total_memory, Total}];
794	_ -> % OSs using a port
795	    dummy % not sent anyway
796    end.
797
798%%--Collect memory data, using port-------------------------------------
799
800port_init() ->
801    process_flag(trap_exit, true),
802    Port = start_portprogram(),
803    port_idle(Port).
804
805start_portprogram() ->
806    os_mon:open_port("memsup",[{packet,1}]).
807
808%% The connected process loops are a bit awkward (several different
809%% functions doing almost the same thing) as
810%%   a) strategies for receiving regular memory data and extensive
811%%      memory data are different
812%%   b) memory collection can be cancelled, in which case the process
813%%      should still wait for port response (which should come
814%%      eventually!) but not receive any requests or cancellations
815%%      meanwhile to prevent getting out of synch.
816port_idle(Port) ->
817    receive
818	{Memsup, collect_sys} ->
819	    Port ! {self(), {command, [?SHOW_MEM]}},
820	    get_memory_usage(Port, undefined, Memsup);
821	{Memsup, collect_ext_sys} ->
822	    Port ! {self(), {command, [?SHOW_SYSTEM_MEM]}},
823	    get_ext_memory_usage(Port, [], Memsup);
824	cancel ->
825	    %% Received after reply already has been delivered...
826	    port_idle(Port);
827	ext_cancel ->
828	    %% Received after reply already has been delivered...
829	    port_idle(Port);
830	close ->
831	    port_close(Port);
832	{Port, {data, Data}} ->
833	    exit({port_error, Data});
834	{'EXIT', Port, Reason} ->
835	    exit({port_died, Reason});
836	{'EXIT', _Memsup, _Reason} ->
837	    port_close(Port)
838    end.
839
840get_memory_usage(Port, Alloc, Memsup) ->
841    receive
842	{Port, {data, Data}} when Alloc==undefined ->
843	    get_memory_usage(Port, erlang:list_to_integer(Data, 16), Memsup);
844	{Port, {data, Data}} ->
845	    Total = erlang:list_to_integer(Data, 16),
846	    Memsup ! {collected_sys, {Alloc, Total}},
847	    port_idle(Port);
848	cancel ->
849	    get_memory_usage_cancelled(Port, Alloc);
850	close ->
851	    port_close(Port);
852	{'EXIT', Port, Reason} ->
853	    exit({port_died, Reason});
854	{'EXIT', _Memsup, _Reason} ->
855	    port_close(Port)
856    end.
857get_memory_usage_cancelled(Port, Alloc) ->
858    receive
859	{Port, {data, _Data}} when Alloc==undefined ->
860	    get_memory_usage_cancelled(Port, 0);
861	{Port, {data, _Data}} ->
862	    port_idle(Port);
863	close ->
864	    port_close(Port);
865	{'EXIT', Port, Reason} ->
866	    exit({port_died, Reason});
867	{'EXIT', _Memsup, _Reason} ->
868	    port_close(Port)
869    end.
870
871get_ext_memory_usage(Port, Accum, Memsup) ->
872    Tab = [
873	    {?MEM_SYSTEM_TOTAL,   system_total_memory},
874	    {?MEM_TOTAL,          total_memory},
875	    {?MEM_FREE,           free_memory},
876	    {?MEM_BUFFERS,        buffered_memory},
877	    {?MEM_CACHED,         cached_memory},
878	    {?MEM_SHARED,         shared_memory},
879	    {?MEM_LARGEST_FREE,   largest_free},
880	    {?MEM_NUMBER_OF_FREE, number_of_free},
881	    {?SWAP_TOTAL,	  total_swap},
882	    {?SWAP_FREE,	  free_swap}
883	],
884    receive
885	{Port, {data, [?SHOW_SYSTEM_MEM_END]}} ->
886	    Memsup ! {collected_ext_sys, Accum},
887	    port_idle(Port);
888	{Port, {data, [Tag]}} ->
889	    case lists:keysearch(Tag, 1, Tab) of
890		{value, {Tag, ATag}} ->
891		    get_ext_memory_usage(ATag, Port, Accum, Memsup);
892		_ ->
893		    exit({memsup_port_error, {Port,[Tag]}})
894	    end;
895	ext_cancel ->
896	    get_ext_memory_usage_cancelled(Port);
897	close ->
898	    port_close(Port);
899	{'EXIT', Port, Reason} ->
900	    exit({port_died, Reason});
901	{'EXIT', _Memsup, _Reason} ->
902	    port_close(Port)
903    end.
904get_ext_memory_usage_cancelled(Port) ->
905    Tab = [
906	    {?MEM_SYSTEM_TOTAL,   system_total_memory},
907	    {?MEM_TOTAL,          total_memory},
908	    {?MEM_FREE,           free_memory},
909	    {?MEM_BUFFERS,        buffered_memory},
910	    {?MEM_CACHED,         cached_memory},
911	    {?MEM_SHARED,         shared_memory},
912	    {?MEM_LARGEST_FREE,   largest_free},
913	    {?MEM_NUMBER_OF_FREE, number_of_free},
914	    {?SWAP_TOTAL,	  total_swap},
915	    {?SWAP_FREE,	  free_swap}
916	],
917    receive
918	{Port, {data, [?SHOW_SYSTEM_MEM_END]}} ->
919	    port_idle(Port);
920	{Port, {data, [Tag]}} ->
921	    case lists:keysearch(Tag, 1, Tab) of
922		{value, {Tag, ATag}} ->
923		    get_ext_memory_usage_cancelled(ATag, Port);
924		_ ->
925		    exit({memsup_port_error, {Port,[Tag]}})
926	    end;
927	close ->
928	    port_close(Port);
929	{'EXIT', Port, Reason} ->
930	    exit({port_died, Reason});
931	{'EXIT', _Memsup, _Reason} ->
932	    port_close(Port)
933    end.
934
935get_ext_memory_usage(ATag, Port, Accum0, Memsup) ->
936    receive
937	{Port, {data, Data}} ->
938	    Accum = [{ATag,erlang:list_to_integer(Data, 16)}|Accum0],
939	    get_ext_memory_usage(Port, Accum, Memsup);
940	cancel ->
941	    get_ext_memory_usage_cancelled(ATag, Port);
942	close ->
943	    port_close(Port);
944	{'EXIT', Port, Reason} ->
945	    exit({port_died, Reason});
946	{'EXIT', _Memsup, _Reason} ->
947	    port_close(Port)
948    end.
949get_ext_memory_usage_cancelled(_ATag, Port) ->
950    receive
951	{Port, {data, _Data}} ->
952	    get_ext_memory_usage_cancelled(Port);
953	close ->
954	    port_close(Port);
955	{'EXIT', Port, Reason} ->
956	    exit({port_died, Reason});
957	{'EXIT', _Memsup, _Reason} ->
958	    port_close(Port)
959    end.
960
961%%--Collect process data------------------------------------------------
962
963%% get_worst_memory_user() -> {Pid, Bytes}
964get_worst_memory_user()  ->
965    get_worst_memory_user(processes(), self(), 0).
966
967get_worst_memory_user([Pid|Pids], MaxPid, MaxMemBytes) ->
968    case process_memory(Pid) of
969	undefined ->
970	    get_worst_memory_user(Pids, MaxPid, MaxMemBytes);
971	MemoryBytes when MemoryBytes>MaxMemBytes ->
972	    get_worst_memory_user(Pids, Pid, MemoryBytes);
973	_MemoryBytes ->
974	    get_worst_memory_user(Pids, MaxPid, MaxMemBytes)
975    end;
976get_worst_memory_user([], MaxPid, MaxMemBytes) ->
977    {MaxPid, MaxMemBytes}.
978
979process_memory(Pid) ->
980    case process_info(Pid, memory) of
981	{memory, Bytes} ->
982	    Bytes;
983	undefined -> % Pid must have died
984	    undefined
985    end.
986
987%%--Alarm handling------------------------------------------------------
988
989set_alarm(AlarmId, AlarmDescr) ->
990    case get(AlarmId) of
991	set ->
992	    ok;
993	undefined ->
994	    alarm_handler:set_alarm({AlarmId, AlarmDescr}),
995	    put(AlarmId, set)
996    end.
997
998clear_alarm(AlarmId) ->
999    case get(AlarmId) of
1000	set ->
1001	    alarm_handler:clear_alarm(AlarmId),
1002	    erase(AlarmId);
1003	_ ->
1004	    ok
1005    end.
1006
1007clear_alarms() ->
1008    lists:foreach(fun({system_memory_high_watermark = Id, set}) ->
1009			  alarm_handler:clear_alarm(Id);
1010		     ({process_memory_high_watermark = Id, set}) ->
1011			  alarm_handler:clear_alarm(Id);
1012		     (_Other) ->
1013			  ignore
1014		  end,
1015		  get()).
1016
1017%%--Auxiliary-----------------------------------------------------------
1018
1019%% Type conversions
1020minutes_to_ms(Minutes) -> trunc(60000*Minutes).
1021sec_to_ms(Sec) -> trunc(1000*Sec).
1022ms_to_sec(MS) -> MS div 1000.
1023
1024flush(Msg) ->
1025    receive
1026	{Msg, _} -> true;
1027	Msg -> true
1028    after 0 ->
1029	    true
1030    end.
1031