diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/os_mon/src/cpu_sup.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/os_mon/src/cpu_sup.erl')
-rw-r--r-- | lib/os_mon/src/cpu_sup.erl | 776 |
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. |