aboutsummaryrefslogblamecommitdiffstats
path: root/lib/os_mon/src/cpu_sup.erl
blob: e758b63d19aa5755f879fed3f9a4f298259a6127 (plain) (tree)
1
2
3
4
5
6
7
8
9

                   
  
                                                        
  


                                                                   
  






                                                                           
  


















































                                                                        

                                                                        
                                                          
                                                                   













                                                                        
                                               



                                            
                                             



                                          
                                             



                                          
                                              



                                           

                                                       















                                                                     
                                           





































                                                                        
                             

                                























































                                                                                  

                                                                      
                               








                                                                                 






                                                                                 
























































































































































































































































                                                                                     



                                           

                                              



                                                 

































                                                                                                      




                                                             
                                           


                                                                   














                                                                        
                           



                                                             
                                        
























































































































































                                                                               
                                                 


                                           
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions 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() -> integer() | {'error', any()}.

nprocs() ->
    os_mon:call(cpu_sup, ?nprocs, infinity).

-spec avg1() -> integer() | {'error', any()}.

avg1() ->
    os_mon:call(cpu_sup, ?avg1, infinity).

-spec avg5() -> integer() | {'error', any()}.

avg5() ->
    os_mon:call(cpu_sup, ?avg5, infinity).

-spec avg15() -> integer() | {'error', any()}.

avg15() ->
    os_mon:call(cpu_sup, ?avg15, infinity).

-spec util(['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() -> float() | {'error', any()}.

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;
	     Flavor == freebsd;
	     Flavor == darwin ->
    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{os_type = {unix, linux}}) ->
    {ok,F} = file:open("/proc/loadavg",[read,raw]),
    {ok,D} = file:read_line(F),
    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{port = P, os_type = {unix, Sys}}) when
								Sys == sunos;
								Sys == dragonfly;
								Sys == openbsd;
								Sys == freebsd;
								Sys == darwin ->
    port_server_call(P, Request);
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;
			    Flavor==darwin;
			    Flavor==freebsd;
			    Flavor==dragonfly;
			    Flavor==openbsd ->
	    {ok, Pid} = port_server_start_link(),
	    Pid;
	{unix, Flavor} when
			    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', OldPid, _n} when State#internal.port == OldPid ->
	    {ok, NewPid} = port_server_start_link(),
	    measurement_server_loop(State#internal{port = NewPid});
	_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_link() ->
    Timeout = 6000,
    Pid = spawn_link(fun() -> port_server_init(Timeout) end),
    Pid ! {self(), ?ping},
    receive
	{Pid, {data,4711}} -> {ok, 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() ->
    Port = os_mon:open_port("cpu_sup", [stream]),
    port_command(Port, ?ping),
    4711 = port_receive_uint32(Port, 5000),
    Port.