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