1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-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-module(observer_pro_wx).
20
21-behaviour(wx_object).
22
23-export([start_link/3]).
24
25%% wx_object callbacks
26-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
27	 handle_event/2, handle_cast/2]).
28
29-include_lib("wx/include/wx.hrl").
30-include("etop.hrl").
31-include("observer_defs.hrl").
32-include("etop_defs.hrl").
33
34%% Defines
35-define(COL_PID,  0).
36-define(COL_NAME, ?COL_PID+1).
37%%-define(COL_TIME, 2).
38-define(COL_REDS, ?COL_NAME+1).
39-define(COL_MEM,  ?COL_REDS+1).
40-define(COL_MSG,  ?COL_MEM+1).
41-define(COL_FUN,  ?COL_MSG+1).
42
43-define(ID_KILL, 201).
44-define(ID_PROC, 202).
45-define(ID_REFRESH, 203).
46-define(ID_REFRESH_INTERVAL, 204).
47-define(ID_DUMP_TO_FILE, 205).
48-define(ID_TRACE_PIDS, 206).
49-define(ID_TRACE_NAMES, 207).
50-define(ID_TRACE_NEW, 208).
51-define(ID_TRACE_ALL, 209).
52-define(ID_ACCUMULATE, 210).
53-define(ID_GARBAGE_COLLECT, 211).
54
55-define(TRACE_PIDS_STR, "Trace selected process identifiers").
56-define(TRACE_NAMES_STR, "Trace selected processes, "
57	"if a process have a registered name "
58	"processes with same name will be traced on all nodes").
59
60
61%% Records
62
63-record(sort,
64	{
65	  sort_key=?COL_REDS,
66	  sort_incr=false
67	}).
68
69-record(holder, {parent,
70		 info,
71                 next=[],
72		 sort=#sort{},
73		 accum=[],
74                 next_accum=[],
75		 attrs,
76		 node,
77		 backend_pid,
78                 old_backend=false
79		}).
80
81-record(state, {parent,
82		grid,
83		panel,
84		popup_menu,
85		parent_notebook,
86		timer,
87		procinfo_menu_pids=[],
88		sel={[], []},
89		right_clicked_pid,
90		holder}).
91
92start_link(Notebook, Parent, Config) ->
93    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []).
94
95%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96init([Notebook, Parent, Config]) ->
97    Attrs = observer_lib:create_attrs(Notebook),
98    Self = self(),
99    Acc = maps:get(acc, Config, false),
100    Holder = spawn_link(fun() -> init_table_holder(Self, Acc, Attrs) end),
101    {ProPanel, State} = setup(Notebook, Parent, Holder, Config),
102    {ProPanel, State#state{holder=Holder}}.
103
104setup(Notebook, Parent, Holder, Config) ->
105    ProPanel = wxPanel:new(Notebook, []),
106
107    Grid  = create_list_box(ProPanel, Holder),
108    Sizer = wxBoxSizer:new(?wxVERTICAL),
109    wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL},
110			      {proportion, 1},
111			      {border,4}]),
112
113    wxWindow:setSizer(ProPanel, Sizer),
114
115    State = #state{parent=Parent,
116		   grid=Grid,
117		   panel=ProPanel,
118		   parent_notebook=Notebook,
119		   holder=Holder,
120		   timer=Config
121		   },
122    {ProPanel, State}.
123
124
125%% UI-creation
126
127create_pro_menu(Parent, Holder) ->
128    MenuEntries = [{"File",
129		    [#create_menu{id=?ID_DUMP_TO_FILE, text="Dump to file"}]},
130		   {"View",
131		    [#create_menu{id=?ID_ACCUMULATE, text="Accumulate",
132				  type=check,
133				  check=call(Holder, {get_accum, self()})},
134		     separator,
135		     #create_menu{id=?ID_REFRESH, text="Refresh\tCtrl-R"},
136		     #create_menu{id=?ID_REFRESH_INTERVAL, text="Refresh Interval"}]},
137		   {"Trace",
138		    [#create_menu{id=?ID_TRACE_PIDS, text="Trace processes"},
139		     #create_menu{id=?ID_TRACE_NAMES, text="Trace named processes (all nodes)"},
140		     #create_menu{id=?ID_TRACE_NEW, text="Trace new processes"}
141		     %% , #create_menu{id=?ID_TRACE_ALL_MENU, text="Trace all processes"}
142		    ]}
143		  ],
144    observer_wx:create_menus(Parent, MenuEntries).
145
146create_list_box(Panel, Holder) ->
147    Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_HRULES,
148    ListCtrl = wxListCtrl:new(Panel, [{style, Style},
149				      {onGetItemText,
150				       fun(_, Row, Col) ->
151					       safe_call(Holder, {get_row, self(), Row, Col})
152				       end},
153				      {onGetItemAttr,
154				       fun(_, Item) ->
155					       safe_call(Holder, {get_attr, self(), Item})
156				       end}
157				     ]),
158    Li = wxListItem:new(),
159    AddListEntry = fun({Name, Align, DefSize}, Col) ->
160			   wxListItem:setText(Li, Name),
161			   wxListItem:setAlign(Li, Align),
162			   wxListCtrl:insertColumn(ListCtrl, Col, Li),
163			   wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize),
164			   Col + 1
165		   end,
166    Scale = observer_wx:get_scale(),
167    ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE,  Scale*120},
168		 {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, Scale*200},
169%%		 {"Time", ?wxLIST_FORMAT_CENTRE, Scale*50},
170		 {"Reds", ?wxLIST_FORMAT_RIGHT, Scale*100},
171		 {"Memory", ?wxLIST_FORMAT_RIGHT, Scale*100},
172		 {"MsgQ",  ?wxLIST_FORMAT_RIGHT, Scale*50},
173		 {"Current Function", ?wxLIST_FORMAT_LEFT,  Scale*200}],
174    lists:foldl(AddListEntry, 0, ListItems),
175    wxListItem:destroy(Li),
176
177    wxListCtrl:setItemCount(ListCtrl, 1),
178    wxListCtrl:connect(ListCtrl, size, [{skip, true}]),
179    wxListCtrl:connect(ListCtrl, command_list_item_activated),
180    wxListCtrl:connect(ListCtrl, command_list_item_right_click),
181    wxListCtrl:connect(ListCtrl, command_list_col_click),
182    %% Use focused instead of selected, selected doesn't generate events
183    %% for all multiple selections on Linux
184    wxListCtrl:connect(ListCtrl, command_list_item_focused),
185    ListCtrl.
186
187dump_to_file(Parent, FileName, Holder) ->
188    case file:open(FileName, [write]) of
189	{ok, Fd} ->
190	    %% Holder closes the file when it's done
191	    Holder ! {dump, Fd};
192	{error, Reason} ->
193	    FailMsg = file:format_error(Reason),
194	    MD = wxMessageDialog:new(Parent, FailMsg),
195	    wxDialog:showModal(MD),
196	    wxDialog:destroy(MD)
197    end.
198
199start_procinfo(undefined, _Frame, Opened) ->
200    Opened;
201start_procinfo(Pid, Frame, Opened) ->
202    case lists:keyfind(Pid, 1, Opened) of
203	false ->
204	    case observer_procinfo:start(Pid, Frame, self()) of
205		{error, _} -> Opened;
206		PI -> [{Pid, PI} | Opened]
207	    end;
208	{_, PI} ->
209	    wxFrame:raise(PI),
210	    Opened
211    end.
212
213
214safe_call(Holder, What) ->
215    case call(Holder, What, 2000) of
216        Res when is_atom(Res) -> "";
217        Res -> Res
218    end.
219
220call(Holder, What) ->
221    call(Holder, What, infinity).
222
223call(Holder, What, TMO) ->
224    Ref = erlang:monitor(process, Holder),
225    Holder ! What,
226    receive
227	{'DOWN', Ref, _, _, _} -> holder_dead;
228	{Holder, Res} ->
229	    erlang:demonitor(Ref),
230	    Res
231    after TMO ->
232	    timeout
233    end.
234
235%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
236
237handle_info({holder_updated, Count}, State0=#state{grid=Grid}) ->
238    State = update_selection(State0),
239
240    wxListCtrl:setItemCount(Grid, Count),
241    Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1),
242    observer_wx:set_status(io_lib:format("Number of Processes: ~w", [Count])),
243    {noreply, State};
244
245handle_info(refresh_interval, #state{holder=Holder}=State) ->
246    Holder ! refresh,
247    {noreply, State};
248
249handle_info({procinfo_menu_closed, Pid},
250	    #state{procinfo_menu_pids=Opened}=State) ->
251    NewPids = lists:keydelete(Pid, 1, Opened),
252    {noreply, State#state{procinfo_menu_pids=NewPids}};
253
254handle_info({procinfo_open, Pid},
255	    #state{panel=Panel, procinfo_menu_pids=Opened}=State) ->
256    Opened2 = start_procinfo(Pid, Panel, Opened),
257    {noreply, State#state{procinfo_menu_pids=Opened2}};
258
259handle_info({active, Node},
260	    #state{holder=Holder, timer=Timer, parent=Parent}=State) ->
261    create_pro_menu(Parent, Holder),
262    Holder ! {change_node, Node},
263    {noreply, State#state{timer=observer_lib:start_timer(Timer, 10)}};
264
265handle_info(not_active, #state{timer=Timer0}=State) ->
266    Timer = observer_lib:stop_timer(Timer0),
267    {noreply, State#state{timer=Timer}};
268
269handle_info(Info, State) ->
270    io:format("~p:~p, Unexpected info: ~tp~n", [?MODULE, ?LINE, Info]),
271    {noreply, State}.
272
273terminate(_Reason, #state{holder=Holder}) ->
274    Holder ! stop,
275    etop:stop(),
276    ok.
277
278code_change(_, _, State) ->
279    {ok, State}.
280
281handle_call(get_config, _, #state{holder=Holder, timer=Timer}=State) ->
282    Conf = observer_lib:timer_config(Timer),
283    Accum = case safe_call(Holder, {get_accum, self()}) of
284                Bool when is_boolean(Bool) -> Bool;
285                _ -> false
286            end,
287    {reply, Conf#{acc=>Accum}, State};
288
289handle_call(Msg, _From, State) ->
290    io:format("~p:~p: Unhandled call ~tp~n",[?MODULE, ?LINE, Msg]),
291    {reply, ok, State}.
292
293handle_cast(Msg, State) ->
294    io:format("~p:~p: Unhandled cast ~tp~n", [?MODULE, ?LINE, Msg]),
295    {noreply, State}.
296
297%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
298
299handle_event(#wx{id=?ID_DUMP_TO_FILE}, #state{panel=Panel, holder=Holder}=State) ->
300    FD  =  wxFileDialog:new(Panel,
301			    [{style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
302    case wxFileDialog:showModal(FD) of
303	?wxID_OK ->
304	    Path = wxFileDialog:getPath(FD),
305	    wxDialog:destroy(FD),
306	    dump_to_file(Panel, Path, Holder);
307	_ ->
308	    wxDialog:destroy(FD)
309    end,
310    {noreply, State};
311
312handle_event(#wx{id=?ID_ACCUMULATE,
313		 event=#wxCommand{type=command_menu_selected, commandInt=CmdInt}},
314	     #state{holder=Holder}=State) ->
315    Holder ! {accum, CmdInt =:= 1},
316    {noreply, State};
317
318handle_event(#wx{id=?ID_REFRESH, event=#wxCommand{type=command_menu_selected}},
319	     #state{holder=Holder}=State) ->
320    Holder ! refresh,
321    {noreply, State};
322
323handle_event(#wx{id=?ID_REFRESH_INTERVAL},
324	     #state{panel=Panel, timer=Timer0}=State) ->
325    Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60),
326    {noreply, State#state{timer=Timer}};
327
328handle_event(#wx{id=?ID_KILL}, #state{right_clicked_pid=Pid, sel=Sel0}=State) ->
329    exit(Pid, kill),
330    Sel = rm_selected(Pid,Sel0),
331    {noreply, State#state{sel=Sel}};
332
333handle_event(#wx{id=?ID_GARBAGE_COLLECT}, #state{sel={_, Pids}}=State) ->
334    _ = [rpc:call(node(Pid), erlang, garbage_collect, [Pid]) || Pid <- Pids],
335    {noreply, State};
336
337handle_event(#wx{id=?ID_PROC},
338	     #state{panel=Panel, right_clicked_pid=Pid, procinfo_menu_pids=Opened}=State) ->
339    Opened2 = start_procinfo(Pid, Panel, Opened),
340    {noreply, State#state{procinfo_menu_pids=Opened2}};
341
342handle_event(#wx{id=?ID_TRACE_PIDS}, #state{sel={_, Pids}, panel=Panel}=State)  ->
343    case Pids of
344	[] ->
345	    observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION),
346	    {noreply, State};
347	Pids ->
348	    observer_trace_wx:add_processes(Pids),
349	    {noreply,  State}
350    end;
351
352handle_event(#wx{id=?ID_TRACE_NAMES}, #state{sel={SelIds,_Pids}, holder=Holder, panel=Panel}=State)  ->
353    case SelIds of
354	[] ->
355	    observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION),
356	    {noreply, State};
357	_ ->
358	    PidsOrReg = call(Holder, {get_name_or_pid, self(), SelIds}),
359	    observer_trace_wx:add_processes(PidsOrReg),
360	    {noreply,  State}
361    end;
362
363handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) ->
364    observer_trace_wx:add_processes([new_processes]),
365    {noreply,  State};
366
367handle_event(#wx{event=#wxSize{size={W,_}}},
368	     #state{grid=Grid}=State) ->
369    observer_lib:set_listctrl_col_size(Grid, W),
370    {noreply, State};
371
372handle_event(#wx{event=#wxList{type=command_list_item_right_click,
373			       itemIndex=Row}},
374	     #state{panel=Panel, holder=Holder}=State) ->
375
376    Pid =
377	case call(Holder, {get_row, self(), Row, pid}) of
378	    {error, undefined} ->
379		undefined;
380	    {ok, P} ->
381		Menu = wxMenu:new(),
382		wxMenu:append(Menu, ?ID_PROC,
383			      "Process info for " ++ pid_to_list(P)),
384		wxMenu:append(Menu, ?ID_TRACE_PIDS,
385			      "Trace selected processes",
386			      [{help, ?TRACE_PIDS_STR}]),
387		wxMenu:append(Menu, ?ID_TRACE_NAMES,
388			      "Trace selected processes by name (all nodes)",
389			      [{help, ?TRACE_NAMES_STR}]),
390		wxMenu:append(Menu, ?ID_GARBAGE_COLLECT, "Garbage collect processes"),
391		wxMenu:append(Menu, ?ID_KILL, "Kill process " ++ pid_to_list(P)),
392		wxWindow:popupMenu(Panel, Menu),
393		wxMenu:destroy(Menu),
394		P
395	end,
396    {noreply, State#state{right_clicked_pid=Pid}};
397
398handle_event(#wx{event=#wxList{type=command_list_item_focused,
399			       itemIndex=Row}},
400	     #state{grid=Grid,holder=Holder} = State) ->
401    case Row >= 0 of
402	true ->
403	    SelIds = [Row|lists:delete(Row, get_selected_items(Grid))],
404	    Pids = call(Holder, {get_pids, self(), SelIds}),
405	    {noreply, State#state{sel={SelIds, Pids}}};
406	false ->
407	    {noreply, State}
408    end;
409
410handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
411	     #state{holder=Holder}=State) ->
412    Holder !  {change_sort, Col},
413    {noreply, State};
414
415handle_event(#wx{event=#wxList{type=command_list_item_activated}},
416	     #state{panel=Panel, procinfo_menu_pids=Opened,
417		    sel={_, [Pid|_]}}=State) ->
418    Opened2 = start_procinfo(Pid, Panel, Opened),
419    {noreply, State#state{procinfo_menu_pids=Opened2}};
420
421handle_event(Event, State) ->
422    io:format("~p:~p: handle event ~tp\n", [?MODULE, ?LINE, Event]),
423    {noreply, State}.
424
425
426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
427
428update_selection(State=#state{holder=Holder, grid=Grid,
429			      sel={SelIds0, SelPids0}}) ->
430    Sel = {SelIds,_SelPids} = call(Holder, {get_rows_from_pids, self(), SelPids0}),
431    set_focus(SelIds0, SelIds, Grid),
432    case SelIds =:= SelIds0 of
433	true -> ok;
434	false ->
435	    wx:batch(fun() ->
436			     [wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_SELECTED) ||
437				 I <- SelIds0],
438			     [wxListCtrl:setItemState(Grid, I, 16#FFFF, ?wxLIST_STATE_SELECTED) ||
439				 I <- SelIds]
440		     end)
441    end,
442    %%io:format("Update ~p -> ~p~n",[{SelIds0, SelPids0}, Sel]),
443    State#state{sel=Sel}.
444
445get_selected_items(Grid) ->
446    get_selected_items(Grid, -1, []).
447
448get_selected_items(Grid, Index, ItemAcc) ->
449    Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL},
450						{state, ?wxLIST_STATE_SELECTED}]),
451    case Item of
452	-1 ->
453	    lists:reverse(ItemAcc);
454	_ ->
455	    get_selected_items(Grid, Item, [Item | ItemAcc])
456    end.
457
458set_focus([], [], _Grid) -> ok;
459set_focus([Same|_], [Same|_], _Grid) -> ok;
460set_focus([], [New|_], Grid) ->
461    wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED);
462set_focus([Old|_], [], Grid) ->
463    wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED);
464set_focus([Old|_], [New|_], Grid) ->
465    wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED),
466    wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED).
467
468rm_selected(Pid, {Ids, Pids}) ->
469    rm_selected(Pid, Ids, Pids, [], []).
470
471rm_selected(Pid, [_Id|Ids], [Pid|Pids], AccIds, AccPids) ->
472    {lists:reverse(AccIds)++Ids,lists:reverse(AccPids)++Pids};
473rm_selected(Pid, [Id|Ids], [OtherPid|Pids], AccIds, AccPids) ->
474    rm_selected(Pid, Ids, Pids, [Id|AccIds], [OtherPid|AccPids]);
475rm_selected(_, [], [], AccIds, AccPids) ->
476    {lists:reverse(AccIds), lists:reverse(AccPids)}.
477
478
479%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
480
481init_table_holder(Parent, Accum0, Attrs) ->
482    process_flag(trap_exit, true),
483    Backend = spawn_link(node(), observer_backend, procs_info, [self()]),
484    Accum = case Accum0 of
485                true -> true;
486                _ -> []
487            end,
488    table_holder(#holder{parent=Parent,
489			 info=array:new(),
490			 node=node(),
491			 backend_pid=Backend,
492			 attrs=Attrs,
493                         accum=Accum
494			}).
495
496table_holder(#holder{info=Info, attrs=Attrs,
497		     node=Node, backend_pid=Backend, old_backend=Old}=S0) ->
498    receive
499	{get_row, From, Row, Col} ->
500	    get_row(From, Row, Col, Info),
501	    table_holder(S0);
502	{get_attr, From, Row} ->
503	    get_attr(From, Row, Attrs),
504	    table_holder(S0);
505        {procs_info, Backend, Procs} ->
506	    State = handle_update(Procs, S0),
507	    table_holder(State);
508        {'EXIT', Backend, normal} when Old =:= false ->
509            S1 = update_complete(S0),
510            table_holder(S1#holder{backend_pid=undefined});
511	{Backend, EtopInfo=#etop_info{}} ->
512	    State = handle_update_old(EtopInfo, S0),
513	    table_holder(State#holder{backend_pid=undefined});
514	refresh when is_pid(Backend)->
515	    table_holder(S0); %% Already updating
516	refresh ->
517            Pid = case Old of
518                      true ->
519                          spawn_link(Node, observer_backend, etop_collect, [self()]);
520                      false ->
521                          spawn_link(Node, observer_backend, procs_info, [self()])
522                  end,
523            table_holder(S0#holder{backend_pid=Pid});
524	{change_sort, Col} ->
525	    State = change_sort(Col, S0),
526	    table_holder(State);
527	{get_pids, From, Indices} ->
528	    get_pids(From, Indices, Info),
529	    table_holder(S0);
530	{get_rows_from_pids, From, Pids} ->
531	    get_rows_from_pids(From, Pids, Info),
532	    table_holder(S0);
533	{get_name_or_pid, From, Indices} ->
534	    get_name_or_pid(From, Indices, Info),
535	    table_holder(S0);
536	{get_node, From} ->
537	    From ! {self(), Node},
538	    table_holder(S0);
539	{change_node, NewNode} ->
540	    case Node == NewNode of
541		true ->
542		    table_holder(S0);
543		false ->
544                    _ = rpc:call(NewNode, code, ensure_loaded, [observer_backend]),
545                    case rpc:call(NewNode, erlang, function_exported,
546                                  [observer_backend,procs_info, 1]) of
547                        true ->
548                            self() ! refresh,
549                            table_holder(S0#holder{node=NewNode, old_backend=false});
550                        false ->
551                            self() ! refresh,
552                            table_holder(S0#holder{node=NewNode, old_backend=true});
553                        _ ->
554                            table_holder(S0)
555                    end
556            end;
557	{accum, Bool} ->
558	    table_holder(change_accum(Bool,S0));
559	{get_accum, From} ->
560	    From ! {self(), S0#holder.accum == true},
561	    table_holder(S0);
562	{dump, Fd} ->
563            Collector = spawn_link(Node, observer_backend, etop_collect,[self()]),
564            receive
565                {Collector, EtopInfo=#etop_info{}} ->
566                    etop_txt:do_update(Fd, EtopInfo, #etop_info{}, #opts{node=Node}),
567                    file:close(Fd),
568                    table_holder(S0);
569                {'EXIT', Collector, _} ->
570                    table_holder(S0)
571            end;
572	stop ->
573	    ok;
574        {'EXIT', Backend, normal} ->
575            table_holder(S0);
576        {'EXIT', Backend, _Reason} ->
577            %% Node crashed will be noticed soon..
578            table_holder(S0#holder{backend_pid=undefined});
579	_What ->
580            %% io:format("~p: Table holder got ~tp~n",[?MODULE, _What]),
581	    table_holder(S0)
582    end.
583
584change_sort(Col, S0=#holder{parent=Parent, info=Data, sort=Sort0}) ->
585    {Sort, ProcInfo}=sort(Col, Sort0, Data),
586    Parent ! {holder_updated, array:size(Data)},
587    S0#holder{info=array:from_list(ProcInfo), sort=Sort}.
588
589change_accum(true, S0) ->
590    S0#holder{accum=true};
591change_accum(false, S0=#holder{info=Info}) ->
592    self() ! refresh,
593    Accum = [{Pid, Reds} || #etop_proc_info{pid=Pid, reds=Reds} <- array:to_list(Info)],
594    S0#holder{accum=lists:sort(Accum)}.
595
596handle_update_old(#etop_info{procinfo=ProcInfo0},
597                  S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) ->
598    {ProcInfo1, Accum} = accum(ProcInfo0, S0),
599    {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1),
600    Info = array:from_list(ProcInfo),
601    Parent ! {holder_updated, array:size(Info)},
602    S0#holder{info=Info, accum=Accum}.
603
604handle_update(ProcInfo0, S0=#holder{next=Next, sort=#sort{sort_key=KeyField}}) ->
605    {ProcInfo1, Accum} = accum(ProcInfo0, S0),
606    Sort = sort_fun(KeyField, true),
607    Merge = merge_fun(KeyField),
608    Merged = Merge(Sort(ProcInfo1), Next),
609    case Accum of
610        true ->  S0#holder{next=Merged};
611        _List -> S0#holder{next=Merged, next_accum=Accum}
612    end.
613
614update_complete(#holder{parent=Parent, sort=#sort{sort_incr=Incr},
615                        next=ProcInfo, accum=Accum, next_accum=NextAccum}=S0) ->
616    Info = case Incr of
617               true -> array:from_list(ProcInfo);
618               false -> array:from_list(lists:reverse(ProcInfo))
619           end,
620    Parent ! {holder_updated, array:size(Info)},
621    S0#holder{info=Info, accum= Accum =:= true orelse NextAccum,
622              next=[], next_accum=[]}.
623
624accum(ProcInfo, #holder{accum=true}) ->
625    {ProcInfo, true};
626accum(ProcInfo0, #holder{accum=Previous, next_accum=Next}) ->
627    Accum = [{Pid, Reds} || #etop_proc_info{pid=Pid, reds=Reds} <- ProcInfo0],
628    ProcInfo = lists:sort(ProcInfo0),
629    {accum2(ProcInfo,Previous,[]), lists:merge(lists:sort(Accum), Next)}.
630
631accum2([PI=#etop_proc_info{pid=Pid, reds=Reds}|PIs],
632       [{Pid, OldReds}|Old], Acc) ->
633    accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds}|Acc]);
634accum2(PIs=[#etop_proc_info{pid=Pid}|_], [{OldPid,_}|Old], Acc)
635  when Pid > OldPid ->
636    accum2(PIs, Old, Acc);
637accum2([PI|PIs], Old, Acc) ->
638    accum2(PIs, Old, [PI|Acc]);
639accum2([], _, Acc) -> Acc.
640
641sort(Col, Opt, Table)
642  when not is_list(Table) ->
643    sort(Col,Opt,array:to_list(Table));
644sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) ->
645    {Opt#sort{sort_incr=not Bool},lists:reverse(Table)};
646sort(Col, S=#sort{sort_incr=Incr}, Table) ->
647    Sort = sort_fun(Col, Incr),
648    {S#sort{sort_key=Col}, Sort(Table)}.
649
650sort_fun(?COL_NAME, true) ->
651    fun(Table) -> lists:sort(fun sort_name/2, Table) end;
652sort_fun(?COL_NAME, false) ->
653    fun(Table) -> lists:sort(fun sort_name_rev/2, Table) end;
654sort_fun(Col, true) ->
655    N = col_to_element(Col),
656    fun(Table) -> lists:keysort(N, Table) end;
657sort_fun(Col, false) ->
658    N = col_to_element(Col),
659    fun(Table) -> lists:reverse(lists:keysort(N, Table)) end.
660
661merge_fun(?COL_NAME) ->
662    fun(A,B) -> lists:merge(fun sort_name/2, A, B) end;
663merge_fun(Col) ->
664    KeyField = col_to_element(Col),
665    fun(A,B) -> lists:keymerge(KeyField, A, B) end.
666
667
668sort_name(#etop_proc_info{name={_,_,_}=A}, #etop_proc_info{name={_,_,_}=B}) ->
669    A =< B;
670sort_name(#etop_proc_info{name=A}, #etop_proc_info{name=B})
671  when is_atom(A), is_atom(B) ->
672    A =< B;
673sort_name(#etop_proc_info{name=Reg}, #etop_proc_info{name={M,_F,_A}})
674  when is_atom(Reg) ->
675    Reg < M;
676sort_name(#etop_proc_info{name={M,_,_}}, #etop_proc_info{name=Reg})
677  when is_atom(Reg) ->
678    M < Reg.
679
680sort_name_rev(#etop_proc_info{name={_,_,_}=A}, #etop_proc_info{name={_,_,_}=B}) ->
681    A >= B;
682sort_name_rev(#etop_proc_info{name=A}, #etop_proc_info{name=B})
683  when is_atom(A), is_atom(B) ->
684    A >= B;
685sort_name_rev(#etop_proc_info{name=Reg}, #etop_proc_info{name={M,_F,_A}})
686  when is_atom(Reg) ->
687    Reg >= M;
688sort_name_rev(#etop_proc_info{name={M,_,_}}, #etop_proc_info{name=Reg})
689  when is_atom(Reg) ->
690    M >= Reg.
691
692%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
693
694get_procinfo_data(Col, Info) ->
695    element(col_to_element(Col), Info).
696col_to_element(?COL_PID)  -> #etop_proc_info.pid;
697col_to_element(?COL_NAME) -> #etop_proc_info.name;
698col_to_element(?COL_MEM)  -> #etop_proc_info.mem;
699%%col_to_element(?COL_TIME) -> #etop_proc_info.runtime;
700col_to_element(?COL_REDS) -> #etop_proc_info.reds;
701col_to_element(?COL_FUN)  -> #etop_proc_info.cf;
702col_to_element(?COL_MSG)  -> #etop_proc_info.mq.
703
704get_pids(From, Indices, ProcInfo) ->
705    Processes = [(array:get(I, ProcInfo))#etop_proc_info.pid || I <- Indices],
706    From ! {self(), Processes}.
707
708get_name_or_pid(From, Indices, ProcInfo) ->
709    Get = fun(#etop_proc_info{name=Name}) when is_atom(Name) -> Name;
710	     (#etop_proc_info{pid=Pid}) -> Pid
711	  end,
712    Processes = [Get(array:get(I, ProcInfo)) || I <- Indices],
713    From ! {self(), Processes}.
714
715get_row(From, Row, pid, Info) ->
716    Pid = case Row =:= -1 of
717	      true ->  {error, undefined};
718	      false -> {ok, get_procinfo_data(?COL_PID, array:get(Row, Info))}
719	  end,
720    From ! {self(), Pid};
721get_row(From, Row, Col, Info) ->
722    Data = case Row >= array:size(Info) of
723	       true ->
724		   "";
725	       false ->
726		   ProcInfo = array:get(Row, Info),
727		   get_procinfo_data(Col, ProcInfo)
728	   end,
729    From ! {self(), observer_lib:to_str(Data)}.
730
731get_rows_from_pids(From, Pids0, Info) ->
732    Search = fun(Idx, #etop_proc_info{pid=Pid}, Acc0={Pick0, {Idxs, Pids}}) ->
733		     case ordsets:is_element(Pid, Pick0) of
734			 true ->
735			     Acc = {[Idx|Idxs],[Pid|Pids]},
736			     Pick = ordsets:del_element(Pid, Pick0),
737			     case Pick =:= [] of
738				 true -> throw(Acc);
739				 false -> {Pick, Acc}
740			     end;
741			 false -> Acc0
742		     end
743	     end,
744    Res = try
745	      {_, R} = array:foldl(Search, {ordsets:from_list(Pids0), {[],[]}}, Info),
746	      R
747	  catch R0 -> R0
748	  end,
749    From ! {self(), Res}.
750
751get_attr(From, Row, Attrs) ->
752    Attribute = case Row rem 2 =:= 0 of
753		    true ->  Attrs#attrs.even;
754		    false -> Attrs#attrs.odd
755		end,
756    From ! {self(), Attribute}.
757