aboutsummaryrefslogtreecommitdiffstats
path: root/lib/os_mon/src/cpu_sup.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/os_mon/src/cpu_sup.erl')
-rw-r--r--lib/os_mon/src/cpu_sup.erl776
1 files changed, 776 insertions, 0 deletions
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.