aboutsummaryrefslogtreecommitdiffstats
path: root/lib/os_mon/src
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/os_mon/src
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/os_mon/src')
-rw-r--r--lib/os_mon/src/Makefile110
-rw-r--r--lib/os_mon/src/cpu_sup.erl776
-rw-r--r--lib/os_mon/src/disksup.erl369
-rw-r--r--lib/os_mon/src/memsup.erl1022
-rw-r--r--lib/os_mon/src/nteventlog.erl162
-rw-r--r--lib/os_mon/src/os_mon.app.src32
-rw-r--r--lib/os_mon/src/os_mon.appup.src41
-rw-r--r--lib/os_mon/src/os_mon.erl179
-rw-r--r--lib/os_mon/src/os_mon_mib.erl250
-rw-r--r--lib/os_mon/src/os_mon_sysinfo.erl147
-rw-r--r--lib/os_mon/src/os_sup.erl258
11 files changed, 3346 insertions, 0 deletions
diff --git a/lib/os_mon/src/Makefile b/lib/os_mon/src/Makefile
new file mode 100644
index 0000000000..9a75446a89
--- /dev/null
+++ b/lib/os_mon/src/Makefile
@@ -0,0 +1,110 @@
+#
+# %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%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(OS_MON_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+MODULES= disksup memsup cpu_sup os_mon os_mon_mib os_sup os_mon_sysinfo \
+ nteventlog
+
+INCLUDE=../include
+CSRC=../c_src
+
+MEMSUP_HRL=$(INCLUDE)/memsup.hrl
+
+HRL_FILES= $(MEMSUP_HRL)
+ERL_FILES= $(MODULES:%=%.erl)
+
+APP_FILE= os_mon.app
+APP_SRC= $(APP_FILE).src
+APP_TARGET=$(EBIN)/$(APP_FILE)
+
+APPUP_FILE= os_mon.appup
+
+APPUP_SRC= $(APPUP_FILE).src
+APPUP_TARGET= ../ebin/$(APPUP_FILE)
+
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += +warn_obsolete_guard -I$(INCLUDE)
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core *~
+
+docs:
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+#-------------------------------------------------------
+# Special dependencies
+#-------------------------------------------------------
+$(EBIN)/memsup.$(EMULATOR): $(MEMSUP_HRL)
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
+
+release_docs_spec:
+
+
+
+
+
+
+
diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl
new file mode 100644
index 0000000000..742e20b1fa
--- /dev/null
+++ b/lib/os_mon/src/cpu_sup.erl
@@ -0,0 +1,776 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(cpu_sup).
+
+%% API
+-export([start_link/0, start/0, stop/0]).
+-export([nprocs/0, avg1/0, avg5/0, avg15/0, util/0, util/1]).
+-export([dummy_reply/1]).
+
+%% For testing
+-export([ping/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% Internal protocol with the port program
+-define(nprocs,"n").
+-define(avg1,"1").
+-define(avg5,"5").
+-define(avg15,"f").
+-define(quit,"q").
+-define(ping,"p").
+-define(util,"u").
+
+-define(cu_cpu_id, 0).
+-define(cu_user, 1).
+-define(cu_nice_user, 2).
+-define(cu_kernel, 3).
+-define(cu_io_wait, 4).
+-define(cu_idle, 5).
+-define(cu_hard_irq, 6).
+-define(cu_soft_irq, 7).
+-define(cu_steal, 8).
+
+-define(INT32(D3,D2,D1,D0),
+ (((D3) bsl 24) bor ((D2) bsl 16) bor ((D1) bsl 8) bor (D0))).
+
+-define(MAX_UINT32, ((1 bsl 32) - 1)).
+
+-record(cpu_util, {cpu, busy = [], non_busy = []}).
+
+-record(state, {server, os_type}).
+%-record(state, {server, port = not_used, util = [], os_type}).
+
+-record(internal, {port = not_used, util = [], os_type}).
+
+%%----------------------------------------------------------------------
+%% Contract specifications
+%%----------------------------------------------------------------------
+
+-type(util_cpus() :: 'all' | integer() | [integer()]).
+-type(util_state() ::
+ 'user' |
+ 'nice_user' |
+ 'kernel' |
+ 'wait' |
+ 'idle').
+-type(util_value() :: {util_state(), float()} | float()).
+-type(util_desc() :: {util_cpus(), util_value(), util_value(), []}).
+
+%%----------------------------------------------------------------------
+%% Exported functions
+%%----------------------------------------------------------------------
+
+start() ->
+ gen_server:start({local, cpu_sup}, cpu_sup, [], []).
+
+start_link() ->
+ gen_server:start_link({local, cpu_sup}, cpu_sup, [], []).
+
+stop() ->
+ gen_server:call(cpu_sup, ?quit, infinity).
+
+-spec(nprocs/0 :: () -> integer() | {'error', any()}).
+
+nprocs() ->
+ os_mon:call(cpu_sup, ?nprocs, infinity).
+
+-spec(avg1/0 :: () -> integer() | {'error', any()}).
+
+avg1() ->
+ os_mon:call(cpu_sup, ?avg1, infinity).
+
+-spec(avg5/0 :: () -> integer() | {'error', any()}).
+
+avg5() ->
+ os_mon:call(cpu_sup, ?avg5, infinity).
+
+-spec(avg15/0 :: () -> integer() | {'error', any()}).
+
+avg15() ->
+ os_mon:call(cpu_sup, ?avg15, infinity).
+
+-spec(util/1 :: ([ 'detailed' | 'per_cpu']) ->
+ util_desc() | [util_desc()] | {'error', any()}).
+
+util(Args) when is_list (Args) ->
+ % Get arguments
+ case lists:foldl(
+ fun (detailed, {_ , PC}) -> {true, PC };
+ (per_cpu , {D , _ }) -> {D , true};
+ (_ , _ ) -> badarg
+ end, {false, false}, Args) of
+ badarg ->
+ erlang:error(badarg);
+ {Detailed, PerCpu} ->
+ os_mon:call(cpu_sup, {?util, Detailed, PerCpu}, infinity)
+ end;
+util(_) ->
+ erlang:error(badarg).
+
+-spec(util/0 :: () -> float()).
+
+util() ->
+ case util([]) of
+ {all, Busy, _, _} -> Busy;
+ Error -> Error
+ end.
+
+dummy_reply(?nprocs) -> 0;
+dummy_reply(?avg1) -> 0;
+dummy_reply(?avg5) -> 0;
+dummy_reply(?avg15) -> 0;
+dummy_reply({?util,_,_}) -> {all, 0, 0, []}.
+
+%%----------------------------------------------------------------------
+%% For testing
+%%----------------------------------------------------------------------
+
+ping() ->
+ gen_server:call(cpu_sup,?ping).
+
+%%----------------------------------------------------------------------
+%% gen_server callbacks
+%%----------------------------------------------------------------------
+
+%% init
+init([]) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, low),
+ {ok,
+ #state{ os_type = os:type(),
+ server = measurement_server_start()
+ }
+ }.
+handle_call(?quit, _From, State) ->
+ {stop, normal, ok, State};
+handle_call({?util, D, PC}, {Client, _Tag},
+ #state{os_type = {unix, Flavor}} = State)
+ when Flavor == sunos;
+ Flavor == linux ->
+ case measurement_server_call(State#state.server, {?util, D, PC, Client}) of
+ {error, Reason} ->
+ { reply,
+ {error, Reason},
+ State#state{server=measurement_server_restart(State#state.server)}
+ };
+ Result -> {reply, Result, State}
+ end;
+handle_call({?util, Detailed, PerCpu}, _From, State) ->
+ String = "OS_MON (cpu_sup), util/1 unavailable for this OS~n",
+ error_logger:warning_msg(String),
+ {reply, dummy_reply({?util, Detailed, PerCpu}), State};
+handle_call(Request, _From, State) when Request==?nprocs;
+ Request==?avg1;
+ Request==?avg5;
+ Request==?avg15;
+ Request==?ping ->
+ case measurement_server_call(State#state.server, Request) of
+ {error, Reason} ->
+ { reply,
+ {error, Reason},
+ State#state{server=measurement_server_restart(State#state.server)}
+ };
+ Result -> {reply, Result, State}
+ end.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+handle_info({'EXIT', _Port, Reason}, State) ->
+ {stop, {server_died, Reason}, State#state{server=not_used}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ exit(State#state.server, normal).
+
+%% os_mon-2.0
+%% For live downgrade to/upgrade from os_mon-1.8[.1]
+code_change(Vsn, PrevState, "1.8") ->
+ case Vsn of
+
+ %% Downgrade from this version
+ {down, _Vsn} ->
+ process_flag(trap_exit, false);
+
+ %% Upgrade to this version
+ _Vsn ->
+ process_flag(trap_exit, true)
+ end,
+ {ok, PrevState};
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% internal functions
+%%----------------------------------------------------------------------
+
+get_uint32_measurement(Request, #internal{port = P, os_type = {unix, sunos}}) ->
+ port_server_call(P, Request);
+get_uint32_measurement(Request, #internal{os_type = {unix, linux}}) ->
+ {ok,F} = file:open("/proc/loadavg",[read,raw]),
+ {ok,D} = file:read(F,24),
+ ok = file:close(F),
+ {ok,[Load1,Load5,Load15,_PRun,PTotal],_} = io_lib:fread("~f ~f ~f ~d/~d", D),
+ case Request of
+ ?avg1 -> sunify(Load1);
+ ?avg5 -> sunify(Load5);
+ ?avg15 -> sunify(Load15);
+ ?ping -> 4711;
+ ?nprocs -> PTotal
+ end;
+get_uint32_measurement(Request, #internal{os_type = {unix, freebsd}}) ->
+ D = os:cmd("/sbin/sysctl -n vm.loadavg") -- "\n",
+ {ok,[Load1,Load5,Load15],_} = io_lib:fread("{ ~f ~f ~f }", D),
+ %% We could count the lines from the ps command as well
+ case Request of
+ ?avg1 -> sunify(Load1);
+ ?avg5 -> sunify(Load5);
+ ?avg15 -> sunify(Load15);
+ ?ping -> 4711;
+ ?nprocs ->
+ Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"),
+ {ok, [N], _} = io_lib:fread("~d", Ps),
+ N-1
+ end;
+get_uint32_measurement(Request, #internal{os_type = {unix, dragonfly}}) ->
+ D = os:cmd("/sbin/sysctl -n vm.loadavg") -- "\n",
+ {ok,[Load1,Load5,Load15],_} = io_lib:fread("{ ~f ~f ~f }", D),
+ %% We could count the lines from the ps command as well
+ case Request of
+ ?avg1 -> sunify(Load1);
+ ?avg5 -> sunify(Load5);
+ ?avg15 -> sunify(Load15);
+ ?ping -> 4711;
+ ?nprocs ->
+ Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"),
+ {ok, [N], _} = io_lib:fread("~d", Ps),
+ N-1
+ end;
+get_uint32_measurement(Request, #internal{os_type = {unix, openbsd}}) ->
+ D = os:cmd("/sbin/sysctl -n vm.loadavg") -- "\n",
+ {ok, [L1, L5, L15], _} = io_lib:fread("~f ~f ~f", D),
+ case Request of
+ ?avg1 -> sunify(L1);
+ ?avg5 -> sunify(L5);
+ ?avg15 -> sunify(L15);
+ ?ping -> 4711;
+ ?nprocs ->
+ Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"),
+ {ok, [N], _} = io_lib:fread("~d", Ps),
+ N-1
+ end;
+get_uint32_measurement(Request, #internal{os_type = {unix, darwin}}) ->
+ %% Get the load average using uptime, overriding Locale setting.
+ D = os:cmd("LANG=C LC_ALL=C uptime") -- "\n",
+ %% Here is a sample uptime string from Mac OS 10.3.8 (C Locale):
+ %% "11:17 up 12 days, 20:39, 2 users, load averages: 1.07 0.95 0.66"
+ %% The safest way to extract the load averages seems to be grab everything
+ %% after the last colon and then do an fread on that.
+ Avg = lists:reverse(hd(string:tokens(lists:reverse(D), ":"))),
+ {ok,[L1,L5,L15],_} = io_lib:fread("~f ~f ~f", Avg),
+
+ case Request of
+ ?avg1 -> sunify(L1);
+ ?avg5 -> sunify(L5);
+ ?avg15 -> sunify(L15);
+ ?ping -> 4711;
+ ?nprocs ->
+ Ps = os:cmd("/bin/ps -ax | /usr/bin/wc -l"),
+ {ok, [N], _} = io_lib:fread("~d", Ps),
+ N-1
+ end;
+get_uint32_measurement(Request, #internal{os_type = {unix, Sys}}) when Sys == irix64;
+ Sys == irix ->
+ %% Get the load average using uptime.
+ %% "8:01pm up 2 days, 22:12, 4 users, load average: 0.70, 0.58, 0.43"
+ D = os:cmd("uptime") -- "\n",
+ Avg = lists:reverse(hd(string:tokens(lists:reverse(D), ":"))),
+ {ok, [L1, L5, L15], _} = io_lib:fread("~f, ~f, ~f", Avg),
+ case Request of
+ ?avg1 -> sunify(L1);
+ ?avg5 -> sunify(L5);
+ ?avg15 -> sunify(L15);
+ ?ping -> 4711;
+ ?nprocs ->
+ {ok, ProcList} = file:list_dir("/proc/pinfo"),
+ length(ProcList)
+ end;
+get_uint32_measurement(_, _) ->
+ throw(not_implemented).
+
+
+get_util_measurement(?util, #internal{port = P }) ->
+ case port_server_call(P, ?util) of
+ {error, Error} -> {error, Error};
+ NewCpuUtil -> NewCpuUtil
+ end;
+get_util_measurement(_,_) ->
+ throw(not_implemented).
+
+%%----------------------------------------------------------------------
+%% BEGIN: tainted internal functions
+%%----------------------------------------------------------------------
+
+sunify(Val) ->
+ round(Val*256). % Note that Solaris and Linux load averages are
+ % measured quite differently anyway
+
+
+keysearchdelete(_, _, []) ->
+ {false, []};
+keysearchdelete(K, N, [T|Ts]) when element(N, T) == K ->
+ {{value, T}, Ts};
+keysearchdelete(K, N, [T|Ts]) ->
+ {X, NTs} = keysearchdelete(K, N, Ts),
+ {X, [T|NTs]}.
+
+%% Internal cpu utilization functions
+
+%% cpu_util_diff(New, Old) takes a list of new cpu_util records as first
+%% argument and a list of old cpu_util records as second argument. The
+%% two lists have to be sorted on cpu index in ascending order.
+%%
+%% The returned value is a difference list in descending order.
+cpu_util_diff(New, Old) ->
+ cpu_util_diff(New, Old, []).
+
+cpu_util_diff([], [], Acc) ->
+ Acc;
+cpu_util_diff([#cpu_util{cpu = Cpu,
+ busy = NewBusy,
+ non_busy = NewNonBusy} | NewCpuUtils],
+ [#cpu_util{cpu = Cpu,
+ busy = OldBusy,
+ non_busy = OldNonBusy} | OldCpuUtils],
+ Acc) ->
+ {PreBusy, GotBusy} = state_list_diff(NewBusy, OldBusy),
+ {NonBusy, GotNonBusy} = state_list_diff(NewNonBusy, OldNonBusy),
+ Busy = case GotBusy orelse GotNonBusy of
+ true ->
+ PreBusy;
+ false ->
+ %% This can happen if cpu_sup:util/[0,1] is called
+ %% again immediately after the previous call has
+ %% returned. Because the user obviously is doing
+ %% something we charge "user".
+ lists:map(fun ({user, 0}) -> {user, 1};
+ ({_, 0} = StateTup) -> StateTup
+ end,
+ PreBusy)
+ end,
+cpu_util_diff(NewCpuUtils, OldCpuUtils, [#cpu_util{cpu = Cpu,
+ busy = Busy,
+ non_busy = NonBusy}
+ | Acc]);
+
+%% A new cpu appeared
+cpu_util_diff([#cpu_util{cpu = NC}|_] = New,
+ [#cpu_util{cpu = OC}|_] = Old,
+ Acc) when NC < OC ->
+cpu_util_diff(New, [#cpu_util{cpu = NC}|Old], Acc);
+cpu_util_diff([#cpu_util{cpu = NC}|_] = New, [], Acc) ->
+cpu_util_diff(New, [#cpu_util{cpu = NC}], Acc);
+
+%% An old cpu disappeared
+cpu_util_diff([#cpu_util{cpu = NC}|Ns],
+ [#cpu_util{cpu = OC}|_] = Old,
+ Acc) when NC > OC ->
+cpu_util_diff(Ns, Old, Acc);
+cpu_util_diff([], _Old, Acc) ->
+cpu_util_diff([], [], Acc).
+
+cpu_util_rel(NewCpuUtils, OldCpuUtils, Detailed, PerCpu) ->
+ cpu_util_rel(cpu_util_diff(NewCpuUtils, OldCpuUtils), Detailed, PerCpu).
+
+%%
+%% cpu_util_rel/3 takes a difference list of cpu_util records as first
+%% argument, a boolean determining if the result should be detailed as
+%% second argument, and a boolean determining if the result should be
+%% per cpu as third argument. The first argument (the difference list)
+%% has to be sorted on cpu index in descending order.
+%%
+cpu_util_rel(CUDiff, false, false) ->
+ {B, T} = lists:foldl(fun (#cpu_util{busy = BusyList,
+ non_busy = NonBusyList},
+ {BusyAcc, TotAcc}) ->
+ Busy = state_list_sum(BusyList),
+ NonBusy = state_list_sum(NonBusyList),
+ {BusyAcc+Busy, TotAcc+Busy+NonBusy}
+ end,
+ {0, 0},
+ CUDiff),
+ BRel = B/T*100,
+ {all, BRel, 100-BRel, []};
+cpu_util_rel(CUDiff, true, false) ->
+ cpu_util_rel_det(CUDiff, #cpu_util{cpu = [], busy = [], non_busy = []});
+cpu_util_rel(CUDiff, false, true) ->
+ cpu_util_rel_pcpu(CUDiff, []);
+cpu_util_rel(CUDiff, true, true) ->
+ cpu_util_rel_det_pcpu(CUDiff, []).
+
+cpu_util_rel_pcpu([], Acc) ->
+ Acc;
+cpu_util_rel_pcpu([#cpu_util{cpu = C,
+ busy = BusyList,
+ non_busy = NonBusyList} | Rest], Acc) ->
+ Busy = state_list_sum(BusyList),
+ NonBusy = state_list_sum(NonBusyList),
+ Tot = Busy + NonBusy,
+ cpu_util_rel_pcpu(Rest, [{C, Busy/Tot*100, NonBusy/Tot*100, []}|Acc]).
+
+cpu_util_rel_det([], #cpu_util{cpu = CpuAcc,
+ busy = BusyAcc,
+ non_busy = NonBusyAcc}) ->
+ Total = state_list_sum(BusyAcc) + state_list_sum(NonBusyAcc),
+ {CpuAcc, mk_rel_states(BusyAcc,Total), mk_rel_states(NonBusyAcc,Total), []};
+cpu_util_rel_det([#cpu_util{cpu = Cpu,
+ busy = Busy,
+ non_busy = NonBusy} | Rest],
+ #cpu_util{cpu = CpuAcc,
+ busy = BusyAcc,
+ non_busy = NonBusyAcc}) ->
+ cpu_util_rel_det(Rest, #cpu_util{cpu = [Cpu|CpuAcc],
+ busy = state_list_add(Busy,
+ BusyAcc),
+ non_busy = state_list_add(NonBusy,
+ NonBusyAcc)}).
+
+cpu_util_rel_det_pcpu([], Acc) ->
+ Acc;
+cpu_util_rel_det_pcpu([#cpu_util{cpu = Cpu,
+ busy = Busy,
+ non_busy = NonBusy}| Rest], Acc) ->
+ Total = state_list_sum(Busy) + state_list_sum(NonBusy),
+ cpu_util_rel_det_pcpu(Rest,
+ [{Cpu,
+ mk_rel_states(Busy, Total),
+ mk_rel_states(NonBusy, Total),
+ []} | Acc]).
+
+mk_rel_states(States, Total) ->
+ lists:map(fun ({State, Value}) -> {State, 100*Value/Total} end, States).
+
+state_list_sum(StateList) ->
+ lists:foldl(fun ({_, X}, Acc) -> Acc+X end, 0, StateList).
+
+state_list_diff([],[]) ->
+ {[], false};
+state_list_diff([{State,ValueNew}|RestNew], []) ->
+ state_list_diff([{State, ValueNew} | RestNew], [{State, 0}]);
+state_list_diff([{State,ValueNew}|RestNew], [{State,ValueOld}|RestOld]) ->
+ ValDiff = val_diff(State, ValueNew, ValueOld),
+ {RestStateDiff, FoundDiff} = state_list_diff(RestNew, RestOld),
+ {[{State, ValDiff} | RestStateDiff], FoundDiff orelse ValDiff /= 0}.
+
+state_list_add([],[]) ->
+ [];
+state_list_add([{State, ValueA}|RestA], []) ->
+ [{State, ValueA} | state_list_add(RestA, [])];
+state_list_add([{State, ValueA} | RestA], [{State, ValueB} | RestB]) ->
+ [{State, ValueA + ValueB} | state_list_add(RestA, RestB)].
+
+one_step_backwards(State, New, Old) ->
+ case os:type() of
+ {unix, linux} ->
+ %% This should never happen! But values sometimes takes a step
+ %% backwards on linux. We'll ignore it as long as it's only
+ %% one step...
+ 0;
+ _ ->
+ val_diff2(State, New, Old)
+ end.
+
+val_diff(State, New, Old) when New == Old - 1 ->
+ one_step_backwards(State, New, Old);
+val_diff(State, ?MAX_UINT32, 0) ->
+ one_step_backwards(State, ?MAX_UINT32, 0);
+val_diff(State, New, Old) ->
+ val_diff2(State, New, Old).
+
+val_diff2(State, New, Old) when New > ?MAX_UINT32; Old > ?MAX_UINT32 ->
+ %% We obviously got uints > 32 bits
+ ensure_positive_diff(State, New - Old);
+val_diff2(State, New, Old) when New < Old ->
+ %% 32-bit integer wrapped
+ ensure_positive_diff(State, (?MAX_UINT32 + 1) + New - Old);
+val_diff2(_State, New, Old) ->
+ New - Old.
+
+ensure_positive_diff(_State, Diff) when Diff >= 0 ->
+ Diff;
+ensure_positive_diff(State, Diff) ->
+ throw({error, {negative_diff, State, Diff}}).
+%%----------------------------------------------------------------------
+%% END: tainted internal functions
+%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% cpu_sup measurement server wrapper
+%%----------------------------------------------------------------------
+
+measurement_server_call(Pid, Request) ->
+ Timeout = 5000,
+ Pid ! {self(), Request},
+ receive
+ {data, Data} -> Data
+ after Timeout ->
+ {error, timeout}
+ end.
+
+measurement_server_restart(Pid) ->
+ exit(Pid, kill),
+ measurement_server_start().
+
+measurement_server_start() ->
+ spawn(fun() -> measurement_server_init() end).
+
+measurement_server_init() ->
+ process_flag(trap_exit, true),
+ OS = os:type(),
+ Server = case OS of
+ {unix, Flavor} when Flavor==sunos;
+ Flavor==linux ->
+ port_server_start();
+ {unix, Flavor} when Flavor==darwin;
+ Flavor==freebsd;
+ Flavor==dragonfly;
+ Flavor==openbsd;
+ Flavor==irix64;
+ Flavor==irix ->
+ not_used;
+ _ ->
+ exit({unsupported_os, OS})
+ end,
+ measurement_server_loop(#internal{port=Server, os_type=OS}).
+
+measurement_server_loop(State) ->
+ receive
+ {_, quit} ->
+ State#internal.port ! {self(), ?quit},
+ ok;
+ {'DOWN',Monitor,process,_,_} ->
+ measurement_server_loop(State#internal{ util = lists:keydelete(
+ Monitor,
+ 2,
+ State#internal.util)});
+ {Pid, {?util, D, PC, Client}} ->
+ {Monitor, OldCpuUtil, Utils2} = case keysearchdelete(Client, 1, State#internal.util) of
+ {{value, {Client, Mon, U}}, Us} -> {Mon, U, Us};
+ {false, Us} -> {erlang:monitor(process, Client), [], Us}
+ end,
+ try get_util_measurement(?util, State) of
+ NewCpuUtil ->
+ Result = cpu_util_rel(NewCpuUtil, OldCpuUtil, D, PC),
+ Pid ! {data, Result},
+ measurement_server_loop(State#internal{util=[{Client,Monitor,NewCpuUtil}|Utils2]})
+ catch
+ Error ->
+ Pid ! {error, Error},
+ measurement_server_loop(State)
+ end;
+ {Pid, Request} ->
+ try get_uint32_measurement(Request, State) of
+ Result -> Pid ! {data, Result}
+ catch
+ Error -> Pid ! {error, Error}
+ end,
+ measurement_server_loop(State);
+ {'EXIT', Pid, _n} when State#internal.port == Pid ->
+ measurement_server_loop(State#internal{port = port_server_start()});
+ _Other ->
+ measurement_server_loop(State)
+ end.
+
+%%----------------------------------------------------------------------
+%% cpu_sup port program server wrapper
+%%----------------------------------------------------------------------
+
+port_server_call(Pid, Command) ->
+ Pid ! {self(), Command},
+ receive
+ {Pid, {data, Result}} -> Result;
+ {Pid, {error, Reason}} -> {error, Reason}
+ end.
+
+port_server_start() ->
+ Timeout = 6000,
+ Pid = spawn_link(fun() -> port_server_init(Timeout) end),
+ Pid ! {self(), ?ping},
+ receive
+ {Pid, {data,4711}} -> Pid;
+ {error,Reason} -> {error, Reason}
+ after Timeout ->
+ {error, timeout}
+ end.
+
+port_server_init(Timeout) ->
+ Port = start_portprogram(),
+ port_server_loop(Port, Timeout).
+
+port_server_loop(Port, Timeout) ->
+ receive
+
+ % Adjust timeout
+ {Pid, {timeout, Timeout}} ->
+ Pid ! {data, Timeout},
+ port_server_loop(Port, Timeout);
+ % Number of processors
+ {Pid, ?nprocs} ->
+ port_command(Port, ?nprocs),
+ Result = port_receive_uint32(Port, Timeout),
+ Pid ! {self(), {data, Result}},
+ port_server_loop(Port, Timeout);
+
+ % Average load for the past minute
+ {Pid, ?avg1} ->
+ port_command(Port, ?avg1),
+ Result = port_receive_uint32(Port, Timeout),
+ Pid ! {self(), {data, Result}},
+ port_server_loop(Port, Timeout);
+
+ % Average load for the past five minutes
+ {Pid, ?avg5} ->
+ port_command(Port, ?avg5),
+ Result = port_receive_uint32(Port, Timeout),
+ Pid ! {self(), {data, Result}},
+ port_server_loop(Port, Timeout);
+
+ % Average load for the past 15 minutes
+ {Pid, ?avg15} ->
+ port_command(Port, ?avg15),
+ Result = port_receive_uint32(Port, Timeout),
+ Pid ! {self(), {data, Result}},
+ port_server_loop(Port, Timeout);
+
+ {Pid, ?util} ->
+ port_command(Port, ?util),
+ Result = port_receive_util(Port, Timeout),
+ Pid ! {self(), {data, Result}},
+ port_server_loop(Port, Timeout);
+
+ % Port ping
+ {Pid, ?ping} ->
+ port_command(Port, ?ping),
+ Result = port_receive_uint32(Port, Timeout),
+ Pid ! {self(), {data, Result}},
+ port_server_loop(Port, Timeout);
+
+ % Close port and this server
+ {Pid, ?quit} ->
+ port_command(Port, ?quit),
+ port_close(Port),
+ Pid ! {self(), {data, quit}},
+ ok;
+
+ % Ignore other commands
+ _ -> port_server_loop(Port, Timeout)
+ end.
+
+port_receive_uint32( Port, Timeout) -> port_receive_uint32(Port, Timeout, []).
+port_receive_uint32(_Port, _Timeout, [D3,D2,D1,D0]) -> ?INT32(D3,D2,D1,D0);
+port_receive_uint32(_Port, _Timeout, [_,_,_,_ | G]) -> exit({port_garbage, G});
+port_receive_uint32(Port, Timeout, D) ->
+ receive
+ {'EXIT', Port, Reason} -> exit({port_exit, Reason});
+ {Port, {data, ND}} -> port_receive_uint32(Port, Timeout, D ++ ND)
+ after Timeout -> exit(timeout_uint32) end.
+
+port_receive_util(Port, Timeout) ->
+ receive
+ {Port, {data, [ NP3,NP2,NP1,NP0, % Number of processors
+ NE3,NE2,NE1,NE0 % Number of entries per processor
+ | CpuData]}} ->
+ port_receive_cpu_util( ?INT32(NP3,NP2,NP1,NP0),
+ ?INT32(NE3,NE2,NE1,NE0),
+ CpuData, []);
+ {'EXIT', Port, Reason} -> exit({port_exit, Reason})
+ after Timeout -> exit(timeout_util) end.
+
+% per processor receive loop
+port_receive_cpu_util(0, _NE, [], CpuList) ->
+ % Return in ascending cpu_id order
+ lists:reverse(CpuList);
+port_receive_cpu_util(0, _NE, Garbage, _) ->
+ exit( {port_garbage, Garbage});
+port_receive_cpu_util(NP, NE, CpuData, CpuList) ->
+ {CpuUtil, Rest} = port_receive_cpu_util_entries(NE, #cpu_util{}, CpuData),
+ port_receive_cpu_util(NP - 1, NE, Rest, [ CpuUtil | CpuList]).
+
+% per entry receive loop
+port_receive_cpu_util_entries(0, CU, Rest) ->
+ {CU, Rest};
+port_receive_cpu_util_entries(NE, CU,
+ [ CID3, CID2, CID1, CID0,
+ Val3, Val2, Val1, Val0 |
+ CpuData]) ->
+
+ TagId = ?INT32(CID3,CID2,CID1,CID0),
+ Value = ?INT32(Val3,Val2,Val1,Val0),
+
+ % Conversions from integers to atoms
+ case TagId of
+ ?cu_cpu_id ->
+ NewCU = CU#cpu_util{cpu = Value},
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_user ->
+ NewCU = CU#cpu_util{
+ busy = [{user, Value} | CU#cpu_util.busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_nice_user ->
+ NewCU = CU#cpu_util{
+ busy = [{nice_user, Value} | CU#cpu_util.busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_kernel ->
+ NewCU = CU#cpu_util{
+ busy = [{kernel, Value} | CU#cpu_util.busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_io_wait ->
+ NewCU = CU#cpu_util{
+ non_busy = [{wait, Value} | CU#cpu_util.non_busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_idle ->
+ NewCU = CU#cpu_util{
+ non_busy = [{idle, Value} | CU#cpu_util.non_busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_hard_irq ->
+ NewCU = CU#cpu_util{
+ busy = [{hard_irq, Value} | CU#cpu_util.busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_soft_irq ->
+ NewCU = CU#cpu_util{
+ busy = [{soft_irq, Value} | CU#cpu_util.busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ ?cu_steal ->
+ NewCU = CU#cpu_util{
+ non_busy = [{steal, Value} | CU#cpu_util.non_busy] },
+ port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
+ Unhandled ->
+ exit({unexpected_type_id, Unhandled})
+ end;
+port_receive_cpu_util_entries(_, _, Data) ->
+ exit({data_mismatch, Data}).
+
+start_portprogram() ->
+ Command = filename:join([code:priv_dir(os_mon), "bin", "cpu_sup"]),
+ Port = open_port({spawn, Command}, [stream]),
+ port_command(Port, ?ping),
+ 4711 = port_receive_uint32(Port, 5000),
+ Port.
diff --git a/lib/os_mon/src/disksup.erl b/lib/os_mon/src/disksup.erl
new file mode 100644
index 0000000000..3340f7ee72
--- /dev/null
+++ b/lib/os_mon/src/disksup.erl
@@ -0,0 +1,369 @@
+%%
+%% %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(disksup).
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]).
+-export([get_disk_data/0,
+ get_check_interval/0, set_check_interval/1,
+ get_almost_full_threshold/0, set_almost_full_threshold/1]).
+-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]).
+
+-record(state, {threshold, timeout, os, diskdata = [],port}).
+
+%%----------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------
+
+start_link() ->
+ gen_server:start_link({local, disksup}, disksup, [], []).
+
+get_disk_data() ->
+ os_mon:call(disksup, get_disk_data).
+
+get_check_interval() ->
+ os_mon:call(disksup, get_check_interval).
+set_check_interval(Minutes) ->
+ case param_type(disk_space_check_interval, Minutes) of
+ true ->
+ os_mon:call(disksup, {set_check_interval, Minutes});
+ false ->
+ erlang:error(badarg)
+ end.
+
+get_almost_full_threshold() ->
+ os_mon:call(disksup, get_almost_full_threshold).
+set_almost_full_threshold(Float) ->
+ case param_type(disk_almost_full_threshold, Float) of
+ true ->
+ os_mon:call(disksup, {set_almost_full_threshold, Float});
+ false ->
+ erlang:error(badarg)
+ end.
+
+dummy_reply(get_disk_data) ->
+ [{"none", 0, 0}];
+dummy_reply(get_check_interval) ->
+ minutes_to_ms(os_mon:get_env(disksup, disk_space_check_interval));
+dummy_reply({set_check_interval, _}) ->
+ ok;
+dummy_reply(get_almost_full_threshold) ->
+ round(os_mon:get_env(disksup, disk_almost_full_threshold) * 100);
+dummy_reply({set_almost_full_threshold, _}) ->
+ ok.
+
+param_type(disk_space_check_interval, Val) when is_integer(Val),
+ Val>=1 -> true;
+param_type(disk_almost_full_threshold, Val) when is_number(Val),
+ 0=<Val,
+ Val=<1 -> true;
+param_type(_Param, _Val) -> false.
+
+param_default(disk_space_check_interval) -> 30;
+param_default(disk_almost_full_threshold) -> 0.80.
+
+%%----------------------------------------------------------------------
+%% gen_server callbacks
+%%----------------------------------------------------------------------
+
+init([]) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, low),
+
+ OS = get_os(),
+ Port = case OS of
+ {unix, Flavor} when Flavor==sunos4;
+ Flavor==solaris;
+ Flavor==freebsd;
+ Flavor==dragonfly;
+ Flavor==darwin;
+ Flavor==linux;
+ Flavor==openbsd;
+ Flavor==irix64;
+ Flavor==irix ->
+ start_portprogram();
+ {win32, _OSname} ->
+ not_used;
+ _ ->
+ exit({unsupported_os, OS})
+ end,
+
+ %% Read the values of some configuration parameters
+ Threshold = os_mon:get_env(disksup, disk_almost_full_threshold),
+ Timeout = os_mon:get_env(disksup, disk_space_check_interval),
+
+ %% Initiation first disk check
+ self() ! timeout,
+
+ {ok, #state{port=Port, os=OS,
+ threshold=round(Threshold*100),
+ timeout=minutes_to_ms(Timeout)}}.
+
+handle_call(get_disk_data, _From, State) ->
+ {reply, State#state.diskdata, State};
+
+handle_call(get_check_interval, _From, State) ->
+ {reply, State#state.timeout, State};
+handle_call({set_check_interval, Minutes}, _From, State) ->
+ Timeout = minutes_to_ms(Minutes),
+ {reply, ok, State#state{timeout=Timeout}};
+
+handle_call(get_almost_full_threshold, _From, State) ->
+ {reply, State#state.threshold, State};
+handle_call({set_almost_full_threshold, Float}, _From, State) ->
+ Threshold = round(Float * 100),
+ {reply, ok, State#state{threshold=Threshold}};
+
+handle_call({set_threshold, Threshold}, _From, State) -> % test only
+ {reply, ok, State#state{threshold=Threshold}}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(timeout, State) ->
+ NewDiskData = check_disk_space(State#state.os, State#state.port,
+ State#state.threshold),
+ timer:send_after(State#state.timeout, timeout),
+ {noreply, State#state{diskdata = NewDiskData}};
+handle_info({'EXIT', _Port, Reason}, State) ->
+ {stop, {port_died, Reason}, State#state{port=not_used}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ clear_alarms(),
+ case State#state.port of
+ not_used ->
+ ok;
+ Port ->
+ port_close(Port)
+ end,
+ ok.
+
+%% os_mon-2.0.1
+%% For live downgrade to/upgrade from os_mon-1.8[.1]
+code_change(Vsn, PrevState, "1.8") ->
+ case Vsn of
+
+ %% Downgrade from this version
+ {down, _Vsn} ->
+ State = case PrevState#state.port of
+ not_used -> PrevState#state{port=noport};
+ _ -> PrevState
+ end,
+ {ok, State};
+
+ %% Upgrade to this version
+ _Vsn ->
+ State = case PrevState#state.port of
+ noport -> PrevState#state{port=not_used};
+ _ -> PrevState
+ end,
+ {ok, State}
+ end;
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Other exports
+%%----------------------------------------------------------------------
+
+format_status(_Opt, [_PDict, #state{os = OS, threshold = Threshold,
+ timeout = Timeout,
+ diskdata = DiskData}]) ->
+ [{data, [{"OS", OS},
+ {"Timeout", Timeout},
+ {"Threshold", Threshold},
+ {"DiskData", DiskData}]}].
+
+%%----------------------------------------------------------------------
+%% Internal functions
+%%----------------------------------------------------------------------
+
+get_os() ->
+ case os:type() of
+ {unix, sunos} ->
+ case os:version() of
+ {5,_,_} -> {unix, solaris};
+ {4,_,_} -> {unix, sunos4};
+ V -> exit({unknown_os_version, V})
+ end;
+ {unix, irix64} -> {unix, irix};
+ OS ->
+ OS
+ end.
+
+%%--Port handling functions---------------------------------------------
+
+start_portprogram() ->
+ open_port({spawn, "sh -s disksup 2>&1"}, [stream]).
+
+my_cmd(Cmd0, Port) ->
+ %% Insert a new line after the command, in case the command
+ %% contains a comment character
+ Cmd = io_lib:format("(~s\n) </dev/null; echo \"\^M\"\n", [Cmd0]),
+ Port ! {self(), {command, [Cmd, 10]}},
+ get_reply(Port, []).
+
+get_reply(Port, O) ->
+ receive
+ {Port, {data, N}} ->
+ case newline(N, O) of
+ {ok, Str} -> Str;
+ {more, Acc} -> get_reply(Port, Acc)
+ end;
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason})
+ end.
+
+newline([13|_], B) -> {ok, lists:reverse(B)};
+newline([H|T], B) -> newline(T, [H|B]);
+newline([], B) -> {more, B}.
+
+%%--Check disk space----------------------------------------------------
+
+check_disk_space({win32,_}, not_used, Threshold) ->
+ Result = os_mon_sysinfo:get_disk_info(),
+ check_disks_win32(Result, Threshold);
+check_disk_space({unix, solaris}, Port, Threshold) ->
+ Result = my_cmd("/usr/bin/df -lk", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold);
+check_disk_space({unix, irix}, Port, Threshold) ->
+ Result = my_cmd("/usr/sbin/df -lk",Port),
+ check_disks_irix(skip_to_eol(Result), Threshold);
+check_disk_space({unix, linux}, Port, Threshold) ->
+ Result = my_cmd("/bin/df -lk", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold);
+check_disk_space({unix, dragonfly}, Port, Threshold) ->
+ Result = my_cmd("/bin/df -k -t ufs,hammer", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold);
+check_disk_space({unix, freebsd}, Port, Threshold) ->
+ Result = my_cmd("/bin/df -k -t ufs", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold);
+check_disk_space({unix, openbsd}, Port, Threshold) ->
+ Result = my_cmd("/bin/df -k -t ffs", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold);
+check_disk_space({unix, sunos4}, Port, Threshold) ->
+ Result = my_cmd("df", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold);
+check_disk_space({unix, darwin}, Port, Threshold) ->
+ Result = my_cmd("/bin/df -k -t ufs,hfs", Port),
+ check_disks_solaris(skip_to_eol(Result), Threshold).
+
+% This code works for Linux and FreeBSD as well
+check_disks_solaris("", _Threshold) ->
+ [];
+check_disks_solaris("\n", _Threshold) ->
+ [];
+check_disks_solaris(Str, Threshold) ->
+ case io_lib:fread("~s~d~d~d~d%~s", Str) of
+ {ok, [_FS, KB, _Used, _Avail, Cap, MntOn], RestStr} ->
+ if
+ Cap >= Threshold ->
+ set_alarm({disk_almost_full, MntOn}, []);
+ true ->
+ clear_alarm({disk_almost_full, MntOn})
+ end,
+ [{MntOn, KB, Cap} |
+ check_disks_solaris(RestStr, Threshold)];
+ _Other ->
+ check_disks_solaris(skip_to_eol(Str),Threshold)
+ end.
+
+%% Irix: like Linux with an extra FS type column and no '%'.
+check_disks_irix("", _Threshold) -> [];
+check_disks_irix("\n", _Threshold) -> [];
+check_disks_irix(Str, Threshold) ->
+ case io_lib:fread("~s~s~d~d~d~d~s", Str) of
+ {ok, [_FS, _FSType, KB, _Used, _Avail, Cap, MntOn], RestStr} ->
+ if Cap >= Threshold -> set_alarm({disk_almost_full, MntOn}, []);
+ true -> clear_alarm({disk_almost_full, MntOn}) end,
+ [{MntOn, KB, Cap} | check_disks_irix(RestStr, Threshold)];
+ _Other ->
+ check_disks_irix(skip_to_eol(Str),Threshold)
+ end.
+
+check_disks_win32([], _Threshold) ->
+ [];
+check_disks_win32([H|T], Threshold) ->
+ case io_lib:fread("~s~s~d~d~d", H) of
+ {ok, [Drive,"DRIVE_FIXED",BAvail,BTot,_TotFree], _RestStr} ->
+ Cap = trunc((BTot-BAvail) / BTot * 100),
+ if
+ Cap >= Threshold ->
+ set_alarm({disk_almost_full, Drive}, []);
+ true ->
+ clear_alarm({disk_almost_full, Drive})
+ end,
+ [{Drive, BTot div 1024, Cap} |
+ check_disks_win32(T, Threshold)]; % Return Total Capacity in Kbytes
+ {ok,_,_RestStr} ->
+ check_disks_win32(T,Threshold);
+ _Other ->
+ []
+ 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);
+ undefined ->
+ ok
+ end.
+
+clear_alarms() ->
+ lists:foreach(fun({{disk_almost_full, _MntOn} = AlarmId, set}) ->
+ alarm_handler:clear_alarm(AlarmId);
+ (_Other) ->
+ ignore
+ end,
+ get()).
+
+%%--Auxiliary-----------------------------------------------------------
+
+%% Type conversion
+minutes_to_ms(Minutes) ->
+ trunc(60000*Minutes).
+
+skip_to_eol([]) ->
+ [];
+skip_to_eol([$\n | T]) ->
+ T;
+skip_to_eol([_ | T]) ->
+ skip_to_eol(T).
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.
diff --git a/lib/os_mon/src/nteventlog.erl b/lib/os_mon/src/nteventlog.erl
new file mode 100644
index 0000000000..d624048c29
--- /dev/null
+++ b/lib/os_mon/src/nteventlog.erl
@@ -0,0 +1,162 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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(nteventlog).
+-behaviour(gen_server).
+
+%% API
+-export([start_link/2, start/2, stop/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {port, mfa}).
+
+%%----------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------
+
+start_link(Ident, MFA) ->
+ gen_server:start_link({local, nteventlog}, nteventlog,
+ [Ident, MFA], []).
+
+start(Ident, MFA) ->
+ gen_server:start({local, nteventlog}, nteventlog, [Ident, MFA], []).
+
+stop() ->
+ gen_server:call(nteventlog, stop).
+
+%%----------------------------------------------------------------------
+%% gen_server callbacks
+%%----------------------------------------------------------------------
+
+init([Identifier,MFA0]) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, low),
+
+ Port = case os:type() of
+ {win32, _OSname} -> start_portprogram(Identifier);
+ OS -> exit({unsupported_os, OS})
+ end,
+
+ %% If we're using os_sup:error_report/2,
+ %% the setting of os_sup_errortag should be used as argument
+ MFA = case MFA0 of
+ {os_sup, error_report, [_Tag]} ->
+ Tag = os_mon:get_env(os_sup, os_sup_errortag),
+ {os_sup, error_report, [Tag]};
+ _ ->
+ MFA0
+ end,
+
+ {ok, #state{port=Port, mfa=MFA}}.
+
+handle_call(stop, _From, State) ->
+ {stop, normal, stopped, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({_Port, {data, Data}}, #state{mfa={M,F,A}} = State) ->
+ T = parse_log(Data),
+ apply(M, F, [T | A]),
+ State#state.port ! {self(), {command, "A"}},
+ {noreply, State};
+handle_info({'EXIT', _Port, Reason}, State) ->
+ {stop, {port_died, Reason}, State#state{port=not_used}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ case State#state.port of
+ not_used -> ignore;
+ Port ->
+ port_close(Port)
+ end,
+ ok.
+
+%% os_mon-2.0
+%% For live downgrade to/upgrade from os_mon-1.8[.1]
+code_change(Vsn, PrevState, "1.8") ->
+ case Vsn of
+
+ %% Downgrade from this version
+ {down, _Vsn} ->
+ process_flag(trap_exit, false),
+
+ %% Downgrade to old State tuple
+ State = {PrevState#state.port, PrevState#state.mfa},
+ {ok, State};
+
+ %% Upgrade to this version
+ _Vsn ->
+ process_flag(trap_exit, true),
+
+ %% Upgrade to this state record
+ {Port, MFA} = PrevState,
+ State = #state{port=Port, mfa=MFA},
+ {ok, State}
+ end;
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Internal functions
+%%----------------------------------------------------------------------
+
+start_portprogram(Identifier) ->
+ Command =
+ filename:join([code:priv_dir(os_mon),"bin","nteventlog.exe"]) ++
+ " " ++ make_list(Identifier),
+ open_port({spawn,Command},[{packet,2}]).
+
+make_list(X) when is_atom(X) ->
+ atom_to_list(X);
+make_list(X) ->
+ X.
+
+holl_len([$H | Rest], Sum) ->
+ {Sum, Rest};
+holl_len([ N | Rest], Sum) ->
+ NN = N - $0,
+ holl_len(Rest, Sum * 10 + NN).
+holl_len(L) ->
+ holl_len(L,0).
+
+splitlist(L,N) ->
+ {lists:sublist(L,N),lists:nthtail(N,L)}.
+
+hollerith(Str) ->
+ {Len, Rest} = holl_len(Str),
+ splitlist(Rest,Len).
+
+holl_time(Str) ->
+ {Holl,Rest} = hollerith(Str),
+ Rev = lists:reverse(Holl),
+ B = list_to_integer(lists:reverse(lists:sublist(Rev,6))),
+ A = list_to_integer(lists:reverse(lists:nthtail(6,Rev))),
+ {{A,B,0},Rest}.
+
+parse_log(Str) ->
+ {Time, Rest1} = holl_time(Str),
+ {Category,Rest2} = hollerith(Rest1),
+ {Facility,Rest3} = hollerith(Rest2),
+ {Severity,Rest4} = hollerith(Rest3),
+ {Message,_} = hollerith(Rest4),
+ {Time,Category,Facility,Severity,Message}.
diff --git a/lib/os_mon/src/os_mon.app.src b/lib/os_mon/src/os_mon.app.src
new file mode 100644
index 0000000000..15bbd2663c
--- /dev/null
+++ b/lib/os_mon/src/os_mon.app.src
@@ -0,0 +1,32 @@
+%%
+%% %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%
+%%
+
+{application, os_mon,
+ [{description, "CPO CXC 138 46"},
+ {vsn, "%VSN%"},
+ {modules, [os_mon, os_mon_mib, os_sup,
+ disksup, memsup, cpu_sup, os_mon_sysinfo, nteventlog]},
+ {registered, [os_mon_sup, os_mon_sysinfo, disksup, memsup, cpu_sup,
+ os_sup_server]},
+ {applications, [kernel, stdlib, sasl]},
+ {env, [{start_cpu_sup, true},
+ {start_disksup, true},
+ {start_memsup, true},
+ {start_os_sup, false}]},
+ {mod, {os_mon, []}}]}.
diff --git a/lib/os_mon/src/os_mon.appup.src b/lib/os_mon/src/os_mon.appup.src
new file mode 100644
index 0000000000..f8e09a7d87
--- /dev/null
+++ b/lib/os_mon/src/os_mon.appup.src
@@ -0,0 +1,41 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-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%
+%%
+
+{"%VSN%",
+ [
+ {"2.1",
+ [{load_module, cpu_sup},
+ {load_module, disksup},
+ {load_module, memsup},
+ {load_module, os_mon},
+ {load_module, os_mon_mib}]},
+ {"2.1.1",
+ [{load_module, os_mon_mib}]}
+ ],
+ [
+ {"2.1",
+ [{load_module, cpu_sup},
+ {load_module, disksup},
+ {load_module, memsup},
+ {load_module, os_mon},
+ {load_module, os_mon_mib}]},
+ {"2.1.1",
+ [{load_module, os_mon_mib}]}
+ ]
+}.
diff --git a/lib/os_mon/src/os_mon.erl b/lib/os_mon/src/os_mon.erl
new file mode 100644
index 0000000000..ef368571db
--- /dev/null
+++ b/lib/os_mon/src/os_mon.erl
@@ -0,0 +1,179 @@
+%%
+%% %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(os_mon).
+
+-behaviour(application).
+-behaviour(supervisor).
+
+%% API
+-export([call/2, call/3, get_env/2]).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%%%-----------------------------------------------------------------
+%%% API
+%%%-----------------------------------------------------------------
+
+call(Service, Request) ->
+ call(Service, Request, 5000).
+
+call(Service, Request, Timeout) ->
+ try gen_server:call(server_name(Service), Request, Timeout)
+ catch
+ exit:{noproc, Call} ->
+ case lists:keysearch(os_mon, 1,
+ application:which_applications()) of
+ {value, _AppInfo} ->
+ case startp(Service) of
+ true ->
+ erlang:exit({noproc, Call});
+ false ->
+ String = "OS_MON (~p) called by ~p, "
+ "unavailable~n",
+ error_logger:warning_msg(String,
+ [Service, self()]),
+ Service:dummy_reply(Request)
+ end;
+ false ->
+ String = "OS_MON (~p) called by ~p, not started~n",
+ error_logger:warning_msg(String, [Service, self()]),
+ Service:dummy_reply(Request)
+ end
+ end.
+
+get_env(Service, Param) ->
+ case application:get_env(os_mon, Param) of
+ {ok, Value} ->
+ case Service:param_type(Param, Value) of
+ true ->
+ Value;
+ false ->
+ String = "OS_MON (~p), ignoring "
+ "bad configuration parameter (~p=~p)~n"
+ "Using default value instead~n",
+ error_logger:warning_msg(String,
+ [Service, Param, Value]),
+ Service:param_default(Param)
+ end;
+ undefined ->
+ Service:param_default(Param)
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Application callbacks
+%%%-----------------------------------------------------------------
+
+start(_, _) ->
+ supervisor:start_link({local, os_mon_sup}, os_mon, []).
+
+stop(_) ->
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Supervisor callbacks
+%%%-----------------------------------------------------------------
+
+init([]) ->
+ SupFlags = case os:type() of
+ {win32, _} ->
+ {one_for_one, 5, 3600};
+ _ ->
+ {one_for_one, 4, 3600}
+ end,
+ SysInf = childspec(sysinfo, startp(sysinfo)),
+ DskSup = childspec(disksup, startp(disksup)),
+ MemSup = childspec(memsup, startp(memsup)),
+ CpuSup = childspec(cpu_sup, startp(cpu_sup)),
+ OsSup = childspec(os_sup, startp(os_sup)),
+ {ok, {SupFlags, SysInf ++ DskSup ++ MemSup ++ CpuSup ++ OsSup}}.
+
+childspec(_Service, false) ->
+ [];
+childspec(cpu_sup, true) ->
+ [{cpu_sup, {cpu_sup, start_link, []},
+ permanent, 2000, worker, [cpu_sup]}];
+childspec(disksup, true) ->
+ [{disksup, {disksup, start_link, []},
+ permanent, 2000, worker, [disksup]}];
+childspec(memsup, true) ->
+ [{memsup, {memsup, start_link, []},
+ permanent, 2000, worker, [memsup]}];
+childspec(os_sup, true) ->
+ OS = os:type(),
+ Mod = case OS of
+ {win32, _} -> nteventlog; % windows
+ _ -> os_sup % solaris
+ end,
+ [{os_sup, {os_sup, start_link, [OS]},
+ permanent, 10000, worker, [Mod]}];
+childspec(sysinfo, true) ->
+ [{os_mon_sysinfo, {os_mon_sysinfo, start_link, []},
+ permanent, 2000, worker, [os_mon_sysinfo]}].
+
+%%%-----------------------------------------------------------------
+%%% Internal functions (OS_Mon configuration)
+%%%-----------------------------------------------------------------
+
+startp(Service) ->
+ %% Available for this platform?
+ case lists:member(Service, services(os:type())) of
+ true ->
+ %% Is there a start configuration parameter?
+ case start_param(Service) of
+ none ->
+ true;
+ Param ->
+ %% Is the start configuration parameter 'true'?
+ case application:get_env(os_mon, Param) of
+ {ok, true} ->
+ true;
+ _ ->
+ false
+ end
+ end;
+ false ->
+ false
+ end.
+
+services({unix, sunos}) ->
+ [cpu_sup, disksup, memsup, os_sup];
+services({unix, _}) -> % Other unix.
+ [cpu_sup, disksup, memsup];
+services({win32, _}) ->
+ [disksup, memsup, os_sup, sysinfo];
+services(vxworks) ->
+ [memsup];
+services(_) ->
+ [].
+
+server_name(cpu_sup) -> cpu_sup;
+server_name(disksup) -> disksup;
+server_name(memsup) -> memsup;
+server_name(os_sup) -> os_sup_server;
+server_name(sysinfo) -> os_mon_sysinfo.
+
+start_param(cpu_sup) -> start_cpu_sup;
+start_param(disksup) -> start_disksup;
+start_param(memsup) -> start_memsup;
+start_param(os_sup) -> start_os_sup;
+start_param(sysinfo) -> none.
diff --git a/lib/os_mon/src/os_mon_mib.erl b/lib/os_mon/src/os_mon_mib.erl
new file mode 100644
index 0000000000..a4ce274a16
--- /dev/null
+++ b/lib/os_mon/src/os_mon_mib.erl
@@ -0,0 +1,250 @@
+%%
+%% %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(os_mon_mib).
+%%%-----------------------------------------------------------------
+%%% Description: This module implements the OS-MON-MIB.
+%%% The tables are implemented as shadow tables with the module
+%%% snmp_shadow_table. Here the update functions are implemented.
+%%%-----------------------------------------------------------------
+
+-include("../../otp_mibs/include/OTP-MIB.hrl").
+
+%% API
+-export([load/1, unload/1]).
+
+%% Deprecated API
+-export([init/1, stop/1]).
+
+-deprecated([{init,1,eventually},
+ {stop,1,eventually}]).
+
+%% SNMP instrumentation
+-export([load_table/1, load_table/3, disk_table/1, disk_table/3,
+ mem_sys_mark/1, mem_proc_mark/1, disk_threshold/1]).
+
+%% SNMP shadow functions
+-export([update_load_table/0, update_disk_table/0]).
+
+%% Exported for internal use via rpc
+-export([get_load/1, get_disks/1]).
+
+%% Shadow tables
+-record(loadTable, {
+ loadErlNodeName,
+ loadSystemTotalMemory,
+ loadSystemUsedMemory,
+ loadLargestErlProcess,
+ loadLargestErlProcessUsedMemory,
+ loadCpuLoad,
+ loadCpuLoad5,
+ loadCpuLoad15,
+ loadOsWordsize,
+ loadSystemTotalMemory64,
+ loadSystemUsedMemory64,
+ loadLargestErlProcessUsedMemory64}).
+
+-record(diskTable,
+ {key, diskDescr, diskKBytes, diskCapacity}).
+
+%% Shadow argument macros
+-define(loadShadowArgs,
+ {loadTable, string, record_info(fields, loadTable), 5000,
+ {os_mon_mib, update_load_table}}).
+
+-define(diskShadowArgs,
+ {diskTable, {integer, integer}, record_info(fields, diskTable), 5000,
+ {os_mon_mib, update_disk_table}}).
+
+%% Misc
+-record(diskAlloc, {diskDescr, diskId}).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+%%-------------------------------------------------------------------------
+%% load(Agent) -> ok | {error, Reason}
+%% Agent - pid() | atom()
+%% Reason - term()
+%% Description: Loads the OTP-OS-MON-MIB
+%%-------------------------------------------------------------------------
+load(Agent) ->
+ MibDir = filename:join(code:priv_dir(os_mon), "mibs"),
+ snmpa:load_mibs(Agent, [filename:join(MibDir, "OTP-OS-MON-MIB")]).
+
+%%-------------------------------------------------------------------------
+%% unload(Agent) -> ok | {error, Reason}
+%% Agent - pid() | atom()
+%% Reason - term()
+%% Description: Unloads the OTP-OS-MON-MIB
+%%-------------------------------------------------------------------------
+unload(Agent) ->
+ snmpa:unload_mibs(Agent, ["OTP-OS-MON-MIB"]).
+
+%% To be backwards compatible
+init(Agent) ->
+ load(Agent).
+stop(Agent) ->
+ unload(Agent).
+
+%%%=========================================================================
+%%% SNMP instrumentation
+%%%=========================================================================
+load_table(Op) ->
+ snmp_shadow_table:table_func(Op, ?loadShadowArgs).
+load_table(Op, RowIndex, Cols) ->
+ snmp_shadow_table:table_func(Op, RowIndex, Cols, ?loadShadowArgs).
+
+disk_table(new) ->
+ Tab = diskAlloc,
+ Storage = ram_copies,
+ case lists:member(Tab, mnesia:system_info(tables)) of
+ true ->
+ case mnesia:table_info(Tab, storage_type) of
+ unknown ->
+ {atomic, ok}=mnesia:add_table_copy(Tab, node(), Storage);
+ Storage ->
+ catch delete_all(Tab)
+ end;
+ false ->
+ Nodes = [node()],
+ Props = [{type, set},
+ {attributes, record_info(fields, diskAlloc)},
+ {local_content, true},
+ {Storage, Nodes}],
+ {atomic, ok} = mnesia:create_table(Tab, Props)
+
+ end,
+ Rec = #diskAlloc{diskDescr = next_index, diskId = 1},
+ ok = mnesia:dirty_write(Rec),
+ snmp_shadow_table:table_func(new, ?diskShadowArgs).
+
+disk_table(Op, RowIndex, Cols) ->
+ snmp_shadow_table:table_func(Op, RowIndex, Cols, ?diskShadowArgs).
+
+mem_sys_mark(get) ->
+ {value, memsup:get_sysmem_high_watermark()};
+mem_sys_mark(_) ->
+ ok.
+
+mem_proc_mark(get) ->
+ {value, memsup:get_procmem_high_watermark()};
+mem_proc_mark(_) ->
+ ok.
+
+disk_threshold(get) ->
+ {value, disksup:get_almost_full_threshold()};
+disk_threshold(_) ->
+ ok.
+
+%%%=========================================================================
+%%% SNMP shadow functions
+%%%=========================================================================
+update_load_table() ->
+ delete_all(loadTable),
+ lists:foreach(
+ fun(Node) ->
+ case rpc:call(Node, os_mon_mib, get_load, [Node]) of
+ Load when is_record(Load,loadTable) ->
+ ok = mnesia:dirty_write(Load);
+ _Else ->
+ ok
+ end
+ end, [node() | nodes()]).
+
+
+update_disk_table() ->
+ delete_all(diskTable),
+ node_update_disk_table(
+ otp_mib:erl_node_table(get_next, [], [?erlNodeName,?erlNodeOutBytes])).
+
+%%%========================================================================
+%%% Exported for internal use via rpc
+%%%========================================================================
+get_load(Node) ->
+ {Total, Allocated, PidString, PidAllocated} = case memsup:get_memory_data() of
+ {MemTot, MemAlloc, undefined} -> {MemTot, MemAlloc, "undefined", 0};
+ {MemTot, MemAlloc, {Pid, PidMem}} -> {MemTot, MemAlloc, pid_to_str(Pid), PidMem}
+ end,
+ OsWordsize = case memsup:get_os_wordsize() of
+ WS when is_integer(WS) -> WS;
+ _ -> 0
+ end,
+ #loadTable{
+ loadErlNodeName = atom_to_list(Node),
+ loadSystemTotalMemory = mask_int32(Total),
+ loadSystemUsedMemory = mask_int32(Allocated),
+ loadLargestErlProcess = PidString,
+ loadLargestErlProcessUsedMemory = mask_int32(PidAllocated),
+ loadCpuLoad = get_cpu_load(avg1),
+ loadCpuLoad5 = get_cpu_load(avg5),
+ loadCpuLoad15 = get_cpu_load(avg15),
+ loadOsWordsize = OsWordsize,
+ loadSystemTotalMemory64 = Total,
+ loadSystemUsedMemory64 = Allocated,
+ loadLargestErlProcessUsedMemory64 = PidAllocated
+ }.
+
+mask_int32(Value) -> Value band ((1 bsl 32) - 1).
+
+get_disks(NodeId) ->
+ element(1,
+ lists:mapfoldl(
+ fun({Descr, KByte, Capacity}, DiskId) ->
+ {#diskTable{key = {NodeId, DiskId},
+ diskDescr = Descr,
+ diskKBytes = KByte,
+ diskCapacity = Capacity},
+ DiskId + 1}
+ end, 1, disksup:get_disk_data())).
+
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+node_update_disk_table([_, endOfTable]) ->
+ ok;
+
+node_update_disk_table([{[?erlNodeName | IndexList], NodeStr}, _]) ->
+ Disks = rpc:call(list_to_atom(NodeStr), os_mon_mib, get_disks,
+ IndexList),
+ lists:foreach(fun(Disk) ->
+ mnesia:dirty_write(Disk)
+ end, Disks),
+ node_update_disk_table(otp_mib:erl_node_table(get_next,
+ IndexList,
+ [?erlNodeName,
+ ?erlNodeOutBytes])).
+
+get_cpu_load(X) when X == avg1; X == avg5; X == avg15 ->
+ case erlang:round(apply(cpu_sup, X, [])/2.56) of
+ Large when Large > 100 ->
+ 100;
+ Load ->
+ Load
+ end.
+
+delete_all(Name) -> delete_all(mnesia:dirty_first(Name), Name).
+delete_all('$end_of_table', _Name) -> done;
+delete_all(Key, Name) ->
+ Next = mnesia:dirty_next(Name, Key),
+ ok = mnesia:dirty_delete({Name, Key}),
+ delete_all(Next, Name).
+
+pid_to_str(Pid) -> lists:flatten(io_lib:format("~w", [Pid])).
diff --git a/lib/os_mon/src/os_mon_sysinfo.erl b/lib/os_mon/src/os_mon_sysinfo.erl
new file mode 100644
index 0000000000..5d12bd95d1
--- /dev/null
+++ b/lib/os_mon/src/os_mon_sysinfo.erl
@@ -0,0 +1,147 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(os_mon_sysinfo).
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]).
+-export([get_disk_info/0, get_disk_info/1, get_mem_info/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(DISK_INFO, $d).
+-define(MEM_INFO, $m).
+-define(OK, $o).
+
+-record(state, {port}).
+
+%%----------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------
+
+start_link() ->
+ gen_server:start_link({local,os_mon_sysinfo}, os_mon_sysinfo, [],[]).
+
+get_disk_info() ->
+ gen_server:call(os_mon_sysinfo, get_disk_info).
+
+get_disk_info(DriveRoot) ->
+ gen_server:call(os_mon_sysinfo, {get_disk_info,DriveRoot}).
+
+get_mem_info() ->
+ gen_server:call(os_mon_sysinfo, get_mem_info).
+
+%%----------------------------------------------------------------------
+%% gen_server callbacks
+%%----------------------------------------------------------------------
+
+init([]) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, low),
+ Port = case os:type() of
+ {win32, _OSname} -> start_portprogram();
+ OS -> exit({unsupported_os, OS})
+ end,
+ {ok, #state{port=Port}}.
+
+handle_call(get_disk_info, _From, State) ->
+ {reply, get_disk_info1(State#state.port), State};
+handle_call({get_disk_info,RootList}, _From, State) ->
+ {reply, get_disk_info1(State#state.port,RootList), State};
+handle_call(get_mem_info, _From, State) ->
+ {reply, get_mem_info1(State#state.port), State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({'EXIT', _Port, Reason}, State) ->
+ {stop, {port_died, Reason}, State#state{port=not_used}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ case State#state.port of
+ not_used ->
+ ok;
+ Port ->
+ port_close(Port)
+ end,
+ ok.
+
+%% os_mon-2.0
+%% For live downgrade to/upgrade from os_mon-1.8[.1]
+code_change(Vsn, PrevState, "1.8") ->
+ case Vsn of
+
+ %% Downgrade from this version
+ {down, _Vsn} ->
+ process_flag(trap_exit, false);
+
+ %% Upgrade to this version
+ _Vsn ->
+ process_flag(trap_exit, true)
+ end,
+ {ok, PrevState};
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Internal functions
+%%----------------------------------------------------------------------
+
+start_portprogram() ->
+ Command =
+ filename:join([code:priv_dir(os_mon),"bin","win32sysinfo.exe"]),
+ Port = open_port({spawn,Command}, [{packet,1}]),
+ receive
+ {Port, {data, [?OK]}} ->
+ Port;
+ {Port, {data, Data}} ->
+ exit({port_error, Data});
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason})
+ after 5000 ->
+ exit({port_error, timeout})
+ end.
+
+get_disk_info1(Port) ->
+ Port ! {self(),{command,[?DISK_INFO]}},
+ get_data(Port,[]).
+
+get_disk_info1(Port,PathList) ->
+ Port ! {self(),{command,[?DISK_INFO|[P++[0]||P <- PathList]]}},
+ get_data(Port,[]).
+
+get_mem_info1(Port) ->
+ Port ! {self(),{command,[?MEM_INFO]}},
+ get_data(Port,[]).
+
+get_data(Port, Sofar) ->
+ receive
+ {Port, {data, [?OK]}} ->
+ lists:reverse(Sofar);
+ {Port, {data, Bytes}} ->
+ get_data(Port, [Bytes|Sofar]);
+ {'EXIT', Port, Reason} ->
+ exit({port_died, Reason})
+ after 5000 ->
+ lists:reverse(Sofar)
+ end.
diff --git a/lib/os_mon/src/os_sup.erl b/lib/os_mon/src/os_sup.erl
new file mode 100644
index 0000000000..f5c6c138ba
--- /dev/null
+++ b/lib/os_mon/src/os_sup.erl
@@ -0,0 +1,258 @@
+%%
+%% %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(os_sup).
+-behaviour(gen_server).
+
+%% API
+-export([start_link/1, start/0, stop/0]).
+-export([error_report/2]).
+-export([enable/0, enable/2, disable/0, disable/2]).
+-export([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]).
+
+-record(state, {port, mfa, config, path, conf}).
+
+%%----------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------
+
+start_link({win32, _OSname}) ->
+ Identifier = os_sup,
+ MFA = os_mon:get_env(os_sup, os_sup_mfa),
+ gen_server:start_link({local, os_sup_server}, nteventlog,
+ [Identifier, MFA], []);
+start_link(_OS) ->
+ gen_server:start_link({local, os_sup_server}, os_sup, [], []).
+
+start() -> % for testing
+ gen_server:start({local, os_sup_server}, os_sup, [], []).
+
+stop() ->
+ gen_server:call(os_sup_server, stop).
+
+error_report(LogData, Tag) ->
+ error_logger:error_report(Tag, LogData).
+
+enable() ->
+ command(enable).
+enable(Path, Conf) ->
+ command(enable, Path, Conf).
+
+disable() ->
+ command(disable).
+disable(Path, Conf) ->
+ command(disable, Path, Conf).
+
+param_type(os_sup_errortag, Val) when is_atom(Val) -> true;
+param_type(os_sup_own, Val) -> io_lib:printable_list(Val);
+param_type(os_sup_syslogconf, Val) -> io_lib:printable_list(Val);
+param_type(os_sup_enable, Val) when Val==true; Val==false -> true;
+param_type(os_sup_mfa, {Mod,Func,Args}) when is_atom(Mod),
+ is_atom(Func),
+ is_list(Args) -> true;
+param_type(_Param, _Val) -> false.
+
+param_default(os_sup_errortag) -> std_error;
+param_default(os_sup_own) -> "/etc";
+param_default(os_sup_syslogconf) -> "/etc/syslog.conf";
+param_default(os_sup_enable) -> true;
+param_default(os_sup_mfa) -> {os_sup, error_report, [std_error]}.
+
+%%----------------------------------------------------------------------
+%% gen_server callbacks
+%%----------------------------------------------------------------------
+
+init([]) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, low),
+
+ case os:type() of
+ {unix, sunos} ->
+ init2();
+ OS -> {stop, {unsupported_os, OS}}
+ end.
+
+init2() -> % Enable service if configured to do so
+ ConfigP = os_mon:get_env(os_sup, os_sup_enable),
+ case ConfigP of
+ true -> % ..yes -- do enable
+ Path = os_mon:get_env(os_sup, os_sup_own),
+ Conf = os_mon:get_env(os_sup, os_sup_syslogconf),
+ case enable(Path, Conf) of
+ ok ->
+ init3(#state{config=ConfigP, path=Path, conf=Conf});
+ {error, Error} ->
+ {stop, {mod_syslog, Error}}
+ end;
+ false -> % ..no -- skip directly to init3/1
+ init3(#state{config=ConfigP})
+ end.
+
+init3(State0) ->
+ Port = start_portprogram(),
+
+ %% Read the values of some configuration parameters
+ MFA = case os_mon:get_env(os_sup, os_sup_mfa) of
+ {os_sup, error_report, _} ->
+ Tag = os_mon:get_env(os_sup, os_sup_errortag),
+ {os_sup, error_report, [Tag]};
+ MFA0 ->
+ MFA0
+ end,
+
+ {ok, State0#state{port=Port, mfa=MFA}}.
+
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({_Port, {data, Data}}, #state{mfa={M,F,A}} = State) ->
+ apply(M, F, [Data | A]),
+ {noreply, State};
+handle_info({'EXIT', _Port, Reason}, State) ->
+ {stop, {port_died, Reason}, State#state{port=not_used}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{port=Port} = State) ->
+ case State#state.config of
+ true when is_port(Port) ->
+ Port ! {self(), {command, "only_stdin"}},
+ Res = disable(State#state.path, State#state.conf),
+ port_close(Port),
+ if
+ Res/="0" -> exit({mod_syslog, Res});
+ true -> ok
+ end;
+ true ->
+ Res = disable(State#state.path, State#state.conf),
+ if
+ Res/="0" -> exit({mod_syslog, Res});
+ true -> ok
+ end;
+ false when is_port(Port) ->
+ Port ! {self(), {command, "only_stdin"}},
+ port_close(Port);
+ false ->
+ ok
+ end.
+
+%% os_mon-2.0
+%% For live downgrade to/upgrade from os_mon-1.8[.1]
+code_change(Vsn, PrevState, "1.8") ->
+ case Vsn of
+
+ %% Downgrade from this version
+ {down, _Vsn} ->
+
+ %% Find out the error tag used
+ {DefM, DefF, _} = param_default(os_sup_mfa),
+ Tag = case PrevState#state.mfa of
+
+ %% Default callback function is used, then use
+ %% the corresponding tag
+ {DefM, DefF, [Tag0]} ->
+ Tag0;
+
+ %% Default callback function is *not* used
+ %% (before the downgrade, that is)
+ %% -- check the configuration parameter
+ _ ->
+ case application:get_env(os_mon,
+ os_sup_errortag) of
+ {ok, Tag1} ->
+ Tag1;
+
+ %% (actually, if it has no value,
+ %% the process should terminate
+ %% according to 1.8.1 version, but that
+ %% seems too harsh here)
+ _ ->
+ std_error
+ end
+ end,
+
+ %% Downgrade to old state record
+ State = {state, PrevState#state.port, Tag},
+ {ok, State};
+
+ %% Upgrade to this version
+ _Vsn ->
+
+ {state, Port, Tag} = PrevState,
+
+ {DefM, DefF, _} = param_default(os_sup_mfa),
+ MFA = {DefM, DefF, [Tag]},
+
+ %% We can safely assume the following configuration
+ %% parameters are defined, otherwise os_sup would never had
+ %% started in the first place.
+ %% (We can *not* safely assume they haven't been changed,
+ %% but that's a weakness inherited from the 1.8.1 version)
+ Path = application:get_env(os_mon, os_sup_own),
+ Conf = application:get_env(os_mon, os_sup_syslogconf),
+
+ %% Upgrade to this state record
+ State = #state{port=Port, mfa=MFA, config=true,
+ path=Path, conf=Conf},
+ {ok, State}
+ end;
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------
+%% Internal functions
+%%----------------------------------------------------------------------
+
+start_portprogram() ->
+ OwnPath = os_mon:get_env(os_sup, os_sup_own),
+ Command =
+ filename:join([code:priv_dir(os_mon), "bin", "ferrule"]) ++
+ " " ++ OwnPath,
+ open_port({spawn, Command}, [{packet, 2}]).
+
+%% os:cmd(cmd_str(enable)) should be done BEFORE starting os_sup
+%% os:cmd(cmd_str(disable)) should be done AFTER os_sup is terminated
+%% Both commands return "0" if successful
+command(Mode) ->
+ command(Mode, "/etc", "/etc/syslog.conf").
+command(Mode, Path, Conf) ->
+ case os:cmd(cmd_str(Mode, Path, Conf)) of
+ "0" ->
+ ok;
+ Error ->
+ {error, Error}
+ end.
+
+cmd_str(Mode, Path, Conf) ->
+ %% modpgm modesw ownpath syslogconf
+ PrivDir = code:priv_dir(os_mon),
+ ModeSw =
+ case Mode of
+ enable ->
+ " otp ";
+ disable ->
+ " nootp "
+ end,
+ PrivDir ++ "/bin/mod_syslog" ++ ModeSw ++ Path ++ " " ++ Conf.