%%
%% %CopyrightBegin%
%%
  Copyright Ericsson AB 1997 - 2021 . 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
%%
%%     -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]).

  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(), number()}] | number().
-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() -> number() | {'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).

%%----------------------------------------------------------------------
%% internal functions 
%%----------------------------------------------------------------------

get_uint32_measurement(Request, #internal{port = P, os_type = {unix, linux}}) ->
    case file:open("/proc/loadavg",[read,raw]) of
        {ok,F} ->
            {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;
        {error,_} ->
            port_server_call(P, Request)
    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:lexemes(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)  ->
  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
	{'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});
        {'EXIT', _, normal} ->
            case State#internal.port of
                not_used ->
                    ok;
                Srv ->
                    Srv ! {self(), ?quit},
                    ok
            end;
	_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.


 %CopyrightBegin%


 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     -2.0

 Unless required by applicable law or agreed to in writing, software
 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%

 API
 For testing
 gen_server callbacks
-record(state, {server, port = not_used, util = [], os_type}).
----------------------------------------------------------------------
 Contract specifications 
----------------------------------------------------------------------
----------------------------------------------------------------------
 Exported functions
----------------------------------------------------------------------
 Get arguments
----------------------------------------------------------------------
 For testing
----------------------------------------------------------------------
----------------------------------------------------------------------
 gen_server callbacks
----------------------------------------------------------------------
 init
----------------------------------------------------------------------
 internal functions 
----------------------------------------------------------------------
 Get the load average using uptime.
----------------------------------------------------------------------
 BEGIN: tainted internal functions 
----------------------------------------------------------------------
 measured quite differently anyway

 The returned value is a difference list in descending order.
 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".
 A new cpu appeared
 An old cpu disappeared
 
 argument, a boolean determining if the result should be detailed as
 has to be sorted on cpu index in descending order.
 
 This should never happen! But values sometimes takes a step
 backwards on linux. We'll ignore it as long as it's only
----------------------------------------------------------------------
 END: tainted internal functions 
----------------------------------------------------------------------
----------------------------------------------------------------------
 cpu_sup measurement server wrapper
----------------------------------------------------------------------
----------------------------------------------------------------------
 cpu_sup port program server wrapper
----------------------------------------------------------------------
 Adjust timeout
 Number of processors
 Port ping
 Close port and this server
 Ignore other commands
 Number of processors
 Number of entries per processor
 per processor receive loop
 Return in ascending cpu_id order
 per entry receive loop
 Conversions from integers to atoms
  Copyright Ericsson AB 1997 - 2021 . All Rights Reserved .
  Licensed under the Apache License , Version 2.0 ( the " License " ) ;
  distributed under the License is distributed on an " AS IS " BASIS ,
-module(cpu_sup).

-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]).

-export([ping/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2]).

  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(internal, {port = not_used, util = [], os_type}).


-type util_cpus() :: 'all' | integer() | [integer()].
-type util_state() :: 'user' | 'nice_user' | 'kernel' | 'wait' | 'idle'.
-type util_value() :: [{util_state(), number()}] | number().
-type util_desc() :: {util_cpus(), util_value(), util_value(), []}.


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) ->
   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() -> number() | {'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, []}.


ping() ->
    gen_server:call(cpu_sup,?ping).


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).


get_uint32_measurement(Request, #internal{port = P, os_type = {unix, linux}}) ->
    case file:open("/proc/loadavg",[read,raw]) of
        {ok,F} ->
            {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;
        {error,_} ->
            port_server_call(P, Request)
    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 ->
  " 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:lexemes(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).


sunify(Val)  ->
  Note that Solaris and Linux load averages are


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 .
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 ->
	       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]);

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);

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
  second argument , and a boolean determining if the result should be
  per cpu as third argument . The first argument ( the difference list )
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} ->
  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}}).


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
	{'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});
        {'EXIT', _, normal} ->
            case State#internal.port of
                not_used ->
                    ok;
                Srv ->
                    Srv ! {self(), ?quit},
                    ok
            end;
	_Other ->
	    measurement_server_loop(State)
    end.


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

	{Pid, {timeout, Timeout}} ->
	    Pid ! {data, Timeout},
	    port_server_loop(Port, Timeout);
        {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);

	{Pid, ?ping} ->
	    port_command(Port, ?ping),
	    Result = port_receive_uint32(Port, Timeout),
	    Pid ! {self(), {data, Result}},
	    port_server_loop(Port, Timeout);

	{Pid, ?quit} ->
	    port_command(Port, ?quit),
	    port_close(Port),
	    Pid ! {self(), {data, quit}},
	    ok;

	_ -> 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
		      | 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.

port_receive_cpu_util(0, _NE, [], CpuList) ->
    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]).

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),

    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.
