1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(cpu_sup).
21
22%% API
23-export([start_link/0, start/0, stop/0]).
24-export([nprocs/0, avg1/0, avg5/0, avg15/0, util/0, util/1]).
25-export([dummy_reply/1]).
26
27%% For testing
28-export([ping/0]).
29
30%% gen_server callbacks
31-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
32	 terminate/2]).
33
34%% Internal protocol with the port program
35-define(nprocs,"n").
36-define(avg1,"1").
37-define(avg5,"5").
38-define(avg15,"f").
39-define(quit,"q").
40-define(ping,"p").
41-define(util,"u").
42
43-define(cu_cpu_id, 0).
44-define(cu_user, 1).
45-define(cu_nice_user, 2).
46-define(cu_kernel, 3).
47-define(cu_io_wait, 4).
48-define(cu_idle, 5).
49-define(cu_hard_irq, 6).
50-define(cu_soft_irq, 7).
51-define(cu_steal, 8).
52
53-define(INT32(D3,D2,D1,D0),
54	(((D3) bsl 24) bor ((D2) bsl 16) bor ((D1) bsl 8) bor (D0))).
55
56-define(MAX_UINT32, ((1 bsl 32) - 1)).
57
58-record(cpu_util, {cpu, busy = [], non_busy = []}).
59
60-record(state, {server, os_type}).
61%-record(state, {server, port = not_used, util = [], os_type}).
62
63-record(internal, {port = not_used, util = [], os_type}).
64
65%%----------------------------------------------------------------------
66%% Contract specifications
67%%----------------------------------------------------------------------
68
69-type util_cpus() :: 'all' | integer() | [integer()].
70-type util_state() :: 'user' | 'nice_user' | 'kernel' | 'wait' | 'idle'.
71-type util_value() :: [{util_state(), number()}] | number().
72-type util_desc() :: {util_cpus(), util_value(), util_value(), []}.
73
74%%----------------------------------------------------------------------
75%% Exported functions
76%%----------------------------------------------------------------------
77
78start() ->
79    gen_server:start({local, cpu_sup}, cpu_sup, [], []).
80
81start_link() ->
82    gen_server:start_link({local, cpu_sup}, cpu_sup, [], []).
83
84stop() ->
85    gen_server:call(cpu_sup, ?quit, infinity).
86
87-spec nprocs() -> integer() | {'error', any()}.
88
89nprocs() ->
90    os_mon:call(cpu_sup, ?nprocs, infinity).
91
92-spec avg1() -> integer() | {'error', any()}.
93
94avg1() ->
95    os_mon:call(cpu_sup, ?avg1, infinity).
96
97-spec avg5() -> integer() | {'error', any()}.
98
99avg5() ->
100    os_mon:call(cpu_sup, ?avg5, infinity).
101
102-spec avg15() -> integer() | {'error', any()}.
103
104avg15() ->
105    os_mon:call(cpu_sup, ?avg15, infinity).
106
107-spec util(['detailed' | 'per_cpu']) ->
108	util_desc() | [util_desc()] | {'error', any()}.
109
110util(Args) when is_list (Args) ->
111   % Get arguments
112   case lists:foldl(
113	    fun (detailed, {_ , PC}) -> {true, PC  };
114	        (per_cpu , {D , _ }) -> {D   , true};
115	        (_       , _       ) -> badarg
116	    end, {false, false}, Args) of
117	badarg ->
118	    erlang:error(badarg);
119	{Detailed, PerCpu} ->
120	    os_mon:call(cpu_sup, {?util, Detailed, PerCpu}, infinity)
121    end;
122util(_) ->
123    erlang:error(badarg).
124
125-spec util() -> number() | {'error', any()}.
126
127util() ->
128    case util([]) of
129	{all, Busy, _, _} -> Busy;
130	Error -> Error
131    end.
132
133dummy_reply(?nprocs) -> 0;
134dummy_reply(?avg1) ->   0;
135dummy_reply(?avg5) ->   0;
136dummy_reply(?avg15) ->  0;
137dummy_reply({?util,_,_}) -> {all, 0, 0, []}.
138
139%%----------------------------------------------------------------------
140%% For testing
141%%----------------------------------------------------------------------
142
143ping() ->
144    gen_server:call(cpu_sup,?ping).
145
146%%----------------------------------------------------------------------
147%% gen_server callbacks
148%%----------------------------------------------------------------------
149
150%% init
151init([]) ->
152    process_flag(trap_exit, true),
153    process_flag(priority, low),
154    {ok,
155	#state{	os_type = os:type(),
156		server = measurement_server_start()
157	}
158    }.
159handle_call(?quit, _From, State) ->
160    {stop, normal, ok, State};
161handle_call({?util, D, PC}, {Client, _Tag},
162	#state{os_type = {unix, Flavor}} = State)
163	when Flavor == sunos;
164	     Flavor == linux;
165	     Flavor == freebsd;
166	     Flavor == darwin ->
167    case measurement_server_call(State#state.server, {?util, D, PC, Client}) of
168	{error, Reason} ->
169	    {	reply,
170		{error, Reason},
171		State#state{server=measurement_server_restart(State#state.server)}
172	    };
173	Result -> {reply, Result, State}
174    end;
175handle_call({?util, Detailed, PerCpu}, _From, State) ->
176    String = "OS_MON (cpu_sup), util/1 unavailable for this OS~n",
177    error_logger:warning_msg(String),
178    {reply, dummy_reply({?util, Detailed, PerCpu}), State};
179handle_call(Request, _From, State) when Request==?nprocs;
180					Request==?avg1;
181					Request==?avg5;
182					Request==?avg15;
183					Request==?ping ->
184    case measurement_server_call(State#state.server, Request) of
185	{error, Reason} ->
186	    {	reply,
187		{error, Reason},
188		State#state{server=measurement_server_restart(State#state.server)}
189	    };
190	Result -> {reply, Result, State}
191    end.
192handle_cast(_Msg, State) ->
193    {noreply, State}.
194handle_info({'EXIT', _Port, Reason}, State) ->
195    {stop, {server_died, Reason}, State#state{server=not_used}};
196handle_info(_Info, State) ->
197    {noreply, State}.
198
199terminate(_Reason, State) ->
200    exit(State#state.server, normal).
201
202%%----------------------------------------------------------------------
203%% internal functions
204%%----------------------------------------------------------------------
205
206get_uint32_measurement(Request, #internal{port = P, os_type = {unix, linux}}) ->
207    case file:open("/proc/loadavg",[read,raw]) of
208        {ok,F} ->
209            {ok,D} = file:read_line(F),
210            ok = file:close(F),
211            {ok,[Load1,Load5,Load15,_PRun,PTotal],_} = io_lib:fread("~f ~f ~f ~d/~d", D),
212            case Request of
213                ?avg1  -> sunify(Load1);
214                ?avg5  -> sunify(Load5);
215                ?avg15 -> sunify(Load15);
216                ?ping -> 4711;
217                ?nprocs -> PTotal
218            end;
219        {error,_} ->
220            port_server_call(P, Request)
221    end;
222get_uint32_measurement(Request, #internal{port = P, os_type = {unix, Sys}}) when
223								Sys == sunos;
224								Sys == dragonfly;
225								Sys == openbsd;
226								Sys == freebsd;
227								Sys == darwin ->
228    port_server_call(P, Request);
229get_uint32_measurement(Request, #internal{os_type = {unix, Sys}}) when Sys == irix64;
230								 Sys == irix ->
231    %% Get the load average using uptime.
232    %% "8:01pm  up 2 days, 22:12,  4 users,  load average: 0.70, 0.58, 0.43"
233    D = os:cmd("uptime") -- "\n",
234    Avg = lists:reverse(hd(string:lexemes(lists:reverse(D), ":"))),
235    {ok, [L1, L5, L15], _} = io_lib:fread("~f, ~f, ~f", Avg),
236    case Request of
237	?avg1  -> sunify(L1);
238	?avg5  -> sunify(L5);
239	?avg15 -> sunify(L15);
240	?ping -> 4711;
241	?nprocs ->
242	    {ok, ProcList} = file:list_dir("/proc/pinfo"),
243	    length(ProcList)
244    end;
245get_uint32_measurement(_, _) ->
246    throw(not_implemented).
247
248
249get_util_measurement(?util, #internal{port = P }) ->
250    case port_server_call(P, ?util) of
251	{error, Error} -> {error, Error};
252        NewCpuUtil -> NewCpuUtil
253    end;
254get_util_measurement(_,_) ->
255    throw(not_implemented).
256
257%%----------------------------------------------------------------------
258%% BEGIN: tainted internal functions
259%%----------------------------------------------------------------------
260
261sunify(Val)  ->
262    round(Val*256). % Note that Solaris and Linux load averages are
263		% measured quite differently anyway
264
265
266keysearchdelete(_, _, []) ->
267    {false, []};
268keysearchdelete(K, N, [T|Ts]) when element(N, T) == K ->
269    {{value, T}, Ts};
270keysearchdelete(K, N, [T|Ts]) ->
271    {X, NTs} = keysearchdelete(K, N, Ts),
272    {X, [T|NTs]}.
273
274%% Internal cpu utilization functions
275
276%% cpu_util_diff(New, Old) takes a list of new cpu_util records as first
277%% argument and a list of old cpu_util records as second argument. The
278%% two lists have to be sorted on cpu index in ascending order.
279%%
280%% The returned value is a difference list in descending order.
281cpu_util_diff(New, Old) ->
282    cpu_util_diff(New, Old, []).
283
284cpu_util_diff([], [], Acc) ->
285    Acc;
286cpu_util_diff([#cpu_util{cpu      = Cpu,
287		     busy     = NewBusy,
288		     non_busy = NewNonBusy} | NewCpuUtils],
289	  [#cpu_util{cpu      = Cpu,
290		     busy     = OldBusy,
291		     non_busy = OldNonBusy} | OldCpuUtils],
292	  Acc) ->
293    {PreBusy, GotBusy} = state_list_diff(NewBusy, OldBusy),
294    {NonBusy, GotNonBusy} = state_list_diff(NewNonBusy, OldNonBusy),
295    Busy = case GotBusy orelse GotNonBusy of
296	   true ->
297	       PreBusy;
298	   false ->
299	       %% This can happen if cpu_sup:util/[0,1] is called
300	       %% again immediately after the previous call has
301	       %% returned. Because the user obviously is doing
302	       %% something we charge "user".
303	       lists:map(fun ({user, 0}) -> {user, 1};
304			     ({_, 0} = StateTup) -> StateTup
305			 end,
306			 PreBusy)
307       end,
308cpu_util_diff(NewCpuUtils, OldCpuUtils, [#cpu_util{cpu      = Cpu,
309						   busy     = Busy,
310						   non_busy = NonBusy}
311					 | Acc]);
312
313%% A new cpu appeared
314cpu_util_diff([#cpu_util{cpu = NC}|_] = New,
315	  [#cpu_util{cpu = OC}|_] = Old,
316	  Acc) when NC < OC ->
317cpu_util_diff(New, [#cpu_util{cpu = NC}|Old], Acc);
318cpu_util_diff([#cpu_util{cpu = NC}|_] = New, [], Acc) ->
319cpu_util_diff(New, [#cpu_util{cpu = NC}], Acc);
320
321%% An old cpu disappeared
322cpu_util_diff([#cpu_util{cpu = NC}|Ns],
323	  [#cpu_util{cpu = OC}|_] = Old,
324	  Acc) when NC > OC ->
325cpu_util_diff(Ns, Old, Acc);
326cpu_util_diff([], _Old, Acc) ->
327cpu_util_diff([], [], Acc).
328
329cpu_util_rel(NewCpuUtils, OldCpuUtils, Detailed, PerCpu) ->
330    cpu_util_rel(cpu_util_diff(NewCpuUtils, OldCpuUtils), Detailed, PerCpu).
331
332%%
333%% cpu_util_rel/3 takes a difference list of cpu_util records as first
334%% argument, a boolean determining if the result should be detailed as
335%% second argument, and a boolean determining if the result should be
336%% per cpu as third argument. The first argument (the difference list)
337%% has to be sorted on cpu index in descending order.
338%%
339cpu_util_rel(CUDiff, false, false) ->
340    {B, T} = lists:foldl(fun (#cpu_util{busy     = BusyList,
341					non_busy = NonBusyList},
342			      {BusyAcc, TotAcc}) ->
343				 Busy  = state_list_sum(BusyList),
344				 NonBusy = state_list_sum(NonBusyList),
345				 {BusyAcc+Busy, TotAcc+Busy+NonBusy}
346			 end,
347			 {0, 0},
348			 CUDiff),
349    BRel = B/T*100,
350    {all, BRel, 100-BRel, []};
351cpu_util_rel(CUDiff, true, false) ->
352    cpu_util_rel_det(CUDiff, #cpu_util{cpu = [], busy = [], non_busy = []});
353cpu_util_rel(CUDiff, false, true) ->
354    cpu_util_rel_pcpu(CUDiff, []);
355cpu_util_rel(CUDiff, true, true) ->
356    cpu_util_rel_det_pcpu(CUDiff, []).
357
358cpu_util_rel_pcpu([], Acc) ->
359    Acc;
360cpu_util_rel_pcpu([#cpu_util{cpu      = C,
361			     busy     = BusyList,
362			     non_busy = NonBusyList} | Rest], Acc) ->
363    Busy  = state_list_sum(BusyList),
364    NonBusy = state_list_sum(NonBusyList),
365    Tot = Busy + NonBusy,
366    cpu_util_rel_pcpu(Rest, [{C, Busy/Tot*100, NonBusy/Tot*100, []}|Acc]).
367
368cpu_util_rel_det([], #cpu_util{cpu      = CpuAcc,
369			       busy     = BusyAcc,
370			       non_busy = NonBusyAcc}) ->
371    Total = state_list_sum(BusyAcc) + state_list_sum(NonBusyAcc),
372    {CpuAcc, mk_rel_states(BusyAcc,Total), mk_rel_states(NonBusyAcc,Total), []};
373cpu_util_rel_det([#cpu_util{cpu      = Cpu,
374			    busy     = Busy,
375			    non_busy = NonBusy} | Rest],
376		 #cpu_util{cpu      = CpuAcc,
377			   busy     = BusyAcc,
378			   non_busy = NonBusyAcc}) ->
379    cpu_util_rel_det(Rest, #cpu_util{cpu      = [Cpu|CpuAcc],
380				     busy     = state_list_add(Busy,
381							       BusyAcc),
382				     non_busy = state_list_add(NonBusy,
383							       NonBusyAcc)}).
384
385cpu_util_rel_det_pcpu([], Acc) ->
386    Acc;
387cpu_util_rel_det_pcpu([#cpu_util{cpu      = Cpu,
388				 busy     = Busy,
389				 non_busy = NonBusy}| Rest], Acc) ->
390    Total = state_list_sum(Busy) + state_list_sum(NonBusy),
391    cpu_util_rel_det_pcpu(Rest,
392			  [{Cpu,
393			    mk_rel_states(Busy, Total),
394			    mk_rel_states(NonBusy, Total),
395			    []} | Acc]).
396
397mk_rel_states(States, Total) ->
398    lists:map(fun ({State, Value}) -> {State, 100*Value/Total} end, States).
399
400state_list_sum(StateList) ->
401    lists:foldl(fun ({_, X}, Acc) -> Acc+X end, 0, StateList).
402
403state_list_diff([],[]) ->
404    {[], false};
405state_list_diff([{State,ValueNew}|RestNew], []) ->
406    state_list_diff([{State, ValueNew} | RestNew], [{State, 0}]);
407state_list_diff([{State,ValueNew}|RestNew], [{State,ValueOld}|RestOld]) ->
408    ValDiff = val_diff(State, ValueNew, ValueOld),
409    {RestStateDiff, FoundDiff} = state_list_diff(RestNew, RestOld),
410    {[{State, ValDiff} | RestStateDiff], FoundDiff orelse ValDiff /= 0}.
411
412state_list_add([],[]) ->
413    [];
414state_list_add([{State, ValueA}|RestA], []) ->
415    [{State, ValueA} | state_list_add(RestA, [])];
416state_list_add([{State, ValueA} | RestA], [{State, ValueB} | RestB]) ->
417    [{State, ValueA + ValueB} | state_list_add(RestA, RestB)].
418
419one_step_backwards(State, New, Old) ->
420    case os:type() of
421	{unix, linux} ->
422	    %% This should never happen! But values sometimes takes a step
423	    %% backwards on linux. We'll ignore it as long as it's only
424	    %% one step...
425	    0;
426	_ ->
427	    val_diff2(State, New, Old)
428    end.
429
430val_diff(State, New, Old) when New == Old - 1 ->
431    one_step_backwards(State, New, Old);
432val_diff(State, ?MAX_UINT32, 0) ->
433    one_step_backwards(State, ?MAX_UINT32, 0);
434val_diff(State, New, Old) ->
435    val_diff2(State, New, Old).
436
437val_diff2(State, New, Old) when New > ?MAX_UINT32; Old > ?MAX_UINT32 ->
438    %% We obviously got uints > 32 bits
439    ensure_positive_diff(State, New - Old);
440val_diff2(State, New, Old) when New < Old ->
441    %% 32-bit integer wrapped
442    ensure_positive_diff(State, (?MAX_UINT32 + 1) + New - Old);
443val_diff2(_State, New, Old) ->
444    New - Old.
445
446ensure_positive_diff(_State, Diff) when Diff >= 0 ->
447    Diff;
448ensure_positive_diff(State, Diff) ->
449    throw({error, {negative_diff, State, Diff}}).
450%%----------------------------------------------------------------------
451%% END: tainted internal functions
452%%----------------------------------------------------------------------
453
454%%----------------------------------------------------------------------
455%% cpu_sup measurement server wrapper
456%%----------------------------------------------------------------------
457
458measurement_server_call(Pid, Request) ->
459    Timeout = 5000,
460    Pid ! {self(), Request},
461    receive
462	{data, Data} -> Data
463    after Timeout ->
464	{error, timeout}
465    end.
466
467measurement_server_restart(Pid) ->
468    exit(Pid, kill),
469    measurement_server_start().
470
471measurement_server_start() ->
472    spawn(fun() -> measurement_server_init() end).
473
474measurement_server_init() ->
475    process_flag(trap_exit, true),
476    OS = os:type(),
477    Server = case OS of
478	{unix, Flavor} when
479			    Flavor==sunos;
480			    Flavor==linux;
481			    Flavor==darwin;
482			    Flavor==freebsd;
483			    Flavor==dragonfly;
484			    Flavor==openbsd ->
485	    {ok, Pid} = port_server_start_link(),
486	    Pid;
487	{unix, Flavor} when
488			    Flavor==irix64;
489			    Flavor==irix ->
490	    not_used;
491	_ ->
492	    exit({unsupported_os, OS})
493    end,
494    measurement_server_loop(#internal{port=Server, os_type=OS}).
495
496measurement_server_loop(State) ->
497    receive
498	{_, quit} ->
499	    State#internal.port ! {self(), ?quit},
500	    ok;
501	{'DOWN',Monitor,process,_,_} ->
502	    measurement_server_loop(State#internal{ util = lists:keydelete(
503		Monitor,
504		2,
505		State#internal.util)});
506	{Pid, {?util, D, PC, Client}} ->
507	    {Monitor, OldCpuUtil, Utils2} = case keysearchdelete(Client, 1, State#internal.util) of
508		{{value, {Client, Mon, U}}, Us} -> {Mon, U, Us};
509		{false, Us} -> {erlang:monitor(process, Client), [], Us}
510	    end,
511	    try get_util_measurement(?util, State) of
512		NewCpuUtil ->
513		    Result = cpu_util_rel(NewCpuUtil, OldCpuUtil, D, PC),
514		    Pid ! {data, Result},
515	   	    measurement_server_loop(State#internal{util=[{Client,Monitor,NewCpuUtil}|Utils2]})
516	    catch
517		Error ->
518		    Pid ! {error, Error},
519		    measurement_server_loop(State)
520	    end;
521	{Pid, Request} ->
522            _ = try get_uint32_measurement(Request, State) of
523                    Result -> Pid ! {data, Result}
524                catch
525                    Error -> Pid ! {error, Error}
526                end,
527	    measurement_server_loop(State);
528        {'EXIT', OldPid, _n} when State#internal.port == OldPid ->
529	    {ok, NewPid} = port_server_start_link(),
530	    measurement_server_loop(State#internal{port = NewPid});
531	_Other ->
532	    measurement_server_loop(State)
533    end.
534
535%%----------------------------------------------------------------------
536%% cpu_sup port program server wrapper
537%%----------------------------------------------------------------------
538
539port_server_call(Pid, Command) ->
540    Pid ! {self(), Command},
541    receive
542	{Pid, {data, Result}} -> Result;
543	{Pid, {error, Reason}} -> {error, Reason}
544    end.
545
546port_server_start_link() ->
547    Timeout = 6000,
548    Pid = spawn_link(fun() -> port_server_init(Timeout) end),
549    Pid ! {self(), ?ping},
550    receive
551	{Pid, {data,4711}} -> {ok, Pid};
552	{error,Reason} -> {error, Reason}
553    after Timeout ->
554	{error, timeout}
555    end.
556
557port_server_init(Timeout) ->
558    Port = start_portprogram(),
559    port_server_loop(Port, Timeout).
560
561port_server_loop(Port, Timeout) ->
562    receive
563
564	% Adjust timeout
565	{Pid, {timeout, Timeout}} ->
566	    Pid ! {data, Timeout},
567	    port_server_loop(Port, Timeout);
568	% Number of processors
569        {Pid, ?nprocs} ->
570	    port_command(Port, ?nprocs),
571	    Result = port_receive_uint32(Port, Timeout),
572	    Pid ! {self(), {data, Result}},
573	    port_server_loop(Port, Timeout);
574
575	% Average load for the past minute
576        {Pid, ?avg1} ->
577	    port_command(Port, ?avg1),
578	    Result = port_receive_uint32(Port, Timeout),
579	    Pid ! {self(), {data, Result}},
580	    port_server_loop(Port, Timeout);
581
582	% Average load for the past five minutes
583        {Pid, ?avg5} ->
584	    port_command(Port, ?avg5),
585	    Result = port_receive_uint32(Port, Timeout),
586	    Pid ! {self(), {data, Result}},
587	    port_server_loop(Port, Timeout);
588
589	% Average load for the past 15 minutes
590        {Pid, ?avg15} ->
591	    port_command(Port, ?avg15),
592	    Result = port_receive_uint32(Port, Timeout),
593	    Pid ! {self(), {data, Result}},
594	    port_server_loop(Port, Timeout);
595
596	{Pid, ?util} ->
597	    port_command(Port, ?util),
598	    Result = port_receive_util(Port, Timeout),
599	    Pid ! {self(), {data, Result}},
600	    port_server_loop(Port, Timeout);
601
602	% Port ping
603	{Pid, ?ping} ->
604	    port_command(Port, ?ping),
605	    Result = port_receive_uint32(Port, Timeout),
606	    Pid ! {self(), {data, Result}},
607	    port_server_loop(Port, Timeout);
608
609	% Close port and this server
610	{Pid, ?quit} ->
611	    port_command(Port, ?quit),
612	    port_close(Port),
613	    Pid ! {self(), {data, quit}},
614	    ok;
615
616	% Ignore other commands
617	_ -> port_server_loop(Port, Timeout)
618    end.
619
620port_receive_uint32( Port,  Timeout) -> port_receive_uint32(Port, Timeout, []).
621port_receive_uint32(_Port, _Timeout, [D3,D2,D1,D0]) -> ?INT32(D3,D2,D1,D0);
622port_receive_uint32(_Port, _Timeout, [_,_,_,_ | G]) -> exit({port_garbage, G});
623port_receive_uint32(Port, Timeout, D) ->
624    receive
625	{'EXIT', Port, Reason} -> exit({port_exit, Reason});
626	{Port, {data, ND}} -> port_receive_uint32(Port, Timeout, D ++ ND)
627    after Timeout -> exit(timeout_uint32) end.
628
629port_receive_util(Port, Timeout) ->
630    receive
631	{Port, {data, [ NP3,NP2,NP1,NP0,  % Number of processors
632		        NE3,NE2,NE1,NE0   % Number of entries per processor
633		      | CpuData]}} ->
634	    port_receive_cpu_util( ?INT32(NP3,NP2,NP1,NP0),
635				   ?INT32(NE3,NE2,NE1,NE0),
636				   CpuData, []);
637	{'EXIT', Port, Reason} -> exit({port_exit, Reason})
638    after Timeout -> exit(timeout_util) end.
639
640% per processor receive loop
641port_receive_cpu_util(0, _NE, [], CpuList) ->
642    % Return in ascending cpu_id order
643    lists:reverse(CpuList);
644port_receive_cpu_util(0, _NE, Garbage, _) ->
645    exit( {port_garbage, Garbage});
646port_receive_cpu_util(NP, NE, CpuData, CpuList) ->
647    {CpuUtil, Rest} = port_receive_cpu_util_entries(NE, #cpu_util{}, CpuData),
648    port_receive_cpu_util(NP - 1, NE, Rest, [ CpuUtil | CpuList]).
649
650% per entry receive loop
651port_receive_cpu_util_entries(0, CU, Rest) ->
652    {CU, Rest};
653port_receive_cpu_util_entries(NE, CU,
654	[ CID3, CID2, CID1, CID0,
655	  Val3, Val2, Val1, Val0 |
656	  CpuData]) ->
657
658    TagId = ?INT32(CID3,CID2,CID1,CID0),
659    Value = ?INT32(Val3,Val2,Val1,Val0),
660
661    % Conversions from integers to atoms
662    case TagId of
663	?cu_cpu_id ->
664	    NewCU = CU#cpu_util{cpu = Value},
665    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
666	?cu_user ->
667	    NewCU = CU#cpu_util{
668		busy     = [{user, Value} | CU#cpu_util.busy] },
669    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
670	?cu_nice_user ->
671	    NewCU = CU#cpu_util{
672		busy     = [{nice_user, Value} | CU#cpu_util.busy] },
673    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
674	?cu_kernel ->
675	    NewCU = CU#cpu_util{
676		busy     = [{kernel, Value} | CU#cpu_util.busy] },
677    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
678	?cu_io_wait ->
679	    NewCU = CU#cpu_util{
680		non_busy = [{wait, Value} | CU#cpu_util.non_busy] },
681    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
682	?cu_idle ->
683	    NewCU = CU#cpu_util{
684		non_busy = [{idle, Value} | CU#cpu_util.non_busy] },
685    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
686	?cu_hard_irq ->
687	    NewCU = CU#cpu_util{
688		busy =     [{hard_irq, Value} | CU#cpu_util.busy] },
689    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
690	?cu_soft_irq ->
691	    NewCU = CU#cpu_util{
692		busy =     [{soft_irq, Value} | CU#cpu_util.busy] },
693    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
694	?cu_steal ->
695	    NewCU = CU#cpu_util{
696		non_busy = [{steal, Value} | CU#cpu_util.non_busy] },
697    	    port_receive_cpu_util_entries(NE - 1, NewCU, CpuData);
698    	Unhandled ->
699	    exit({unexpected_type_id, Unhandled})
700    end;
701port_receive_cpu_util_entries(_, _, Data) ->
702     exit({data_mismatch, Data}).
703
704start_portprogram() ->
705    Port = os_mon:open_port("cpu_sup", [stream]),
706    port_command(Port, ?ping),
707    4711 = port_receive_uint32(Port, 5000),
708    Port.
709