aboutsummaryrefslogblamecommitdiffstats
path: root/lib/observer/src/observer_pro_wx.erl
blob: 8e3e28caec50959e6bd511ef40f105869d9f55af (plain) (tree)
























                                                                          
                                         

                                  
                                
                              
                          





                     
                     










                                  
                            

          
 





                             

                        

                              



                            
 











                                               



                                                          

                                                                                
                                        
                  

                                                                     
                                       
 
                                                                   

                                  

                                         
                                              








                                                           








                                             




























                                                             

                                  
                                                                              
                           


                                                                           
                               

                                                                                      
                            


                                                                                      























                                                                                                        
                                 


                                                     


                                                                                        
                                                     


                                                                                     











                                                                             


                                                       



                                                                  
                                         



                                                                


                                                                        

             


                                         
                                                    







                                                      

                                                   








                                                              
                     
                                          
                  




                                     
                 

                                                  

        

                                                                                








                                                                 
                     


                                                                


                                                   

                                        
                                                       
                                        
                                                       
 
                           
                                                                       

                                    
                                                                  
 
                                                      
                                            
                                        
 
                                                         
                                 
                     




                                                                               
                                           



















                                                                   
                                                                                   











                                                                             


                                                                                  
                                   

                     

                                                                               
                     

                     

                                                        
                                                                 
                                        
 

                                                                          

                                        
                                           

 





                                                        
                                        
                                            
                                                       









                                                       





                                                                                                         
                                                    





                                               
                                                          

        





                                                                                      
                                            





                                       
                                                  

 





                                                                                      
                                            





                                       
                                                   

                                            
                                        









                                                                                         



                                                                  
 
                                                     








                                                             













                                                                                 
 

                                                                      

                                 
 




                                                                  
                                            

                                                       





                                                                  










































                                                                                                  

                                                                                

                                                                         




                                                      


                                                                 
                                                           

                                    
                                          



                                       
                                           









                                                                          
                                    

                                          



                                                 














                                                         
                             
                     

                                                                     







                                                      

                                                                                             




                                                               
                                                                  



                                                
                                                                               




                                                                               
                                             
                      
                                                  


                                                                 
                                                                

                                                                                   
                                                                                





                               
                                                            





                                                                                     
                              









                                                     

                                    

                                                                                


                                

                                                                                




                                       
                      



                                                     









                                                                    


                                       

                                              

                               



                                                         
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
-module(observer_pro_wx).

-behaviour(wx_object).

-export([start_link/2]).

%% wx_object callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
	 handle_event/2, handle_cast/2]).

-include_lib("wx/include/wx.hrl").
-include("../include/etop.hrl").
-include("observer_defs.hrl").
-include("etop_defs.hrl").

%% Defines
-define(COL_PID,  0).
-define(COL_NAME, 1).
-define(COL_TIME, 2).
-define(COL_REDS, 3).
-define(COL_MEM,  4).
-define(COL_MSG,  5).
-define(COL_FUN,  6).

-define(ID_KILL, 201).
-define(ID_PROC, 202).
-define(ID_REFRESH, 203).
-define(ID_REFRESH_INTERVAL, 204).
-define(ID_DUMP_TO_FILE, 205).
-define(ID_TRACEMENU, 206).
-define(ID_TRACE_ALL_MENU, 207).
-define(ID_TRACE_NEW_MENU, 208).
-define(ID_ACCUMULATE, 209).

%% Records

-record(sort,
	{
	  sort_key=?COL_REDS,
	  sort_incr=false
	}).

-record(holder, {parent,
		 info,
		 sort=#sort{},
		 accum=[],
		 attrs,
		 node,
		 backend_pid
		}).

-record(state, {parent,
		grid,
		panel,
		popup_menu,
		parent_notebook,
		trace_options=#trace_options{},
		match_specs=[],
		timer,
		tracemenu_opened,
		procinfo_menu_pids=[],
		sel={[], []},
		holder}).

start_link(Notebook, Parent) ->
    wx_object:start_link(?MODULE, [Notebook, Parent], []).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([Notebook, Parent]) ->
    Attrs = observer_lib:create_attrs(),
    Self = self(),
    Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end),
    {ProPanel, State} = setup(Notebook, Parent, Holder),
    MatchSpecs = generate_matchspecs(),

    {ProPanel, State#state{holder=Holder, match_specs=MatchSpecs}}.

setup(Notebook, Parent, Holder) ->
    ProPanel = wxPanel:new(Notebook, []),

    Grid  = create_list_box(ProPanel, Holder),
    Sizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL},
			      {proportion, 1},
			      {border,4}]),

    wxWindow:setSizer(ProPanel, Sizer),

    Popup = create_popup_menu(ProPanel),

    State =  #state{parent=Parent,
		    grid=Grid,
		    panel=ProPanel,
		    popup_menu=Popup,
		    parent_notebook=Notebook,
		    tracemenu_opened=false,
		    holder=Holder,
		    timer={false, 10}
		   },
    {ProPanel, State}.

generate_matchspecs() ->
    try
	StrMs1 = "[{'_', [], [{return_trace}]}].",
	StrMs2 = "[{'_', [], [{exception_trace}]}].",
	StrMs3 = "[{'_', [], [{message, {caller}}]}].",
	StrMs4 = "[{'_', [], [{message, {process_dump}}]}].",

	{ok, Tokens1, _} = erl_scan:string(StrMs1),
	{ok, Tokens2, _} = erl_scan:string(StrMs2),
	{ok, Tokens3, _} = erl_scan:string(StrMs3),
	{ok, Tokens4, _} = erl_scan:string(StrMs4),
	{ok, Term1} = erl_parse:parse_term(Tokens1),
	{ok, Term2} = erl_parse:parse_term(Tokens2),
	{ok, Term3} = erl_parse:parse_term(Tokens3),
	{ok, Term4} = erl_parse:parse_term(Tokens4),

	[#match_spec{term_ms = Term1, str_ms = StrMs1},
	 #match_spec{term_ms = Term2, str_ms = StrMs2},
	 #match_spec{term_ms = Term3, str_ms = StrMs3},
	 #match_spec{term_ms = Term4, str_ms = StrMs4}]
    catch
	_:_ ->
	    []
    end.

%% UI-creation

create_pro_menu(Parent, Holder) ->
    MenuEntries = [{"File",
		    [#create_menu{id=?ID_DUMP_TO_FILE, text="Dump to file"}]},
		   {"View",
		    [#create_menu{id=?ID_ACCUMULATE, text="Accumulate",
				  type=check,
				  check=call(Holder, {get_accum, self()})},
		     separator,
		     #create_menu{id=?ID_REFRESH, text="Refresh\tCtrl-R"},
		     #create_menu{id=?ID_REFRESH_INTERVAL, text="Refresh Interval"}]},
		   {"Trace",
		    [#create_menu{id=?ID_TRACEMENU, text="Trace selected processes"},
		     #create_menu{id=?ID_TRACE_NEW_MENU, text="Trace new processes"},
		     #create_menu{id=?ID_TRACE_ALL_MENU, text="Trace all processes"}]}
		  ],
    observer_wx:create_menus(Parent, MenuEntries).

create_popup_menu(ParentFrame) ->
    MiniFrame = wxMiniFrame:new(ParentFrame, ?wxID_ANY, "Options", [{style, ?wxFRAME_FLOAT_ON_PARENT}]),
    Panel = wxPanel:new(MiniFrame),
    Sizer = wxBoxSizer:new(?wxVERTICAL),
    TraceBtn = wxButton:new(Panel, ?ID_TRACEMENU, [{label, "Trace selected"},
						   {style, ?wxNO_BORDER}]),
    ProcBtn = wxButton:new(Panel, ?ID_PROC, [{label, "Process info"},
					     {style, ?wxNO_BORDER}]),
    KillBtn = wxButton:new(Panel, ?ID_KILL, [{label, "Kill process"},
					     {style, ?wxNO_BORDER}]),

    wxButton:connect(TraceBtn, command_button_clicked),
    wxButton:connect(ProcBtn, command_button_clicked),
    wxButton:connect(KillBtn, command_button_clicked),
    wxSizer:add(Sizer, TraceBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
    wxSizer:add(Sizer, ProcBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
    wxSizer:add(Sizer, KillBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
    wxPanel:setSizer(Panel, Sizer),
    wxSizer:setSizeHints(Sizer, MiniFrame),
    MiniFrame.

create_list_box(Panel, Holder) ->
    Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL,
    ListCtrl = wxListCtrl:new(Panel, [{style, Style},
				      {onGetItemText,
				       fun(_, Row, Col) ->
					       call(Holder, {get_row, self(), Row, Col})
				       end},
				      {onGetItemAttr,
				       fun(_, Item) ->
					       call(Holder, {get_attr, self(), Item})
				       end}
				     ]),
    Li = wxListItem:new(),
    AddListEntry = fun({Name, Align, DefSize}, Col) ->
			   wxListItem:setText(Li, Name),
			   wxListItem:setAlign(Li, Align),
			   wxListCtrl:insertColumn(ListCtrl, Col, Li),
			   wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize),
			   Col + 1
		   end,
    ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE,  120},
		 {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 200},
		 {"Time", ?wxLIST_FORMAT_CENTRE, 50},
		 {"Reds", ?wxLIST_FORMAT_RIGHT, 100},
		 {"Memory", ?wxLIST_FORMAT_RIGHT, 100},
		 {"MsgQ",  ?wxLIST_FORMAT_RIGHT, 50},
		 {"Current Function", ?wxLIST_FORMAT_LEFT,  200}],
    lists:foldl(AddListEntry, 0, ListItems),
    wxListItem:destroy(Li),

    wxListCtrl:setItemCount(ListCtrl, 1),
    wxListCtrl:connect(ListCtrl, size, [{skip, true}]),
    wxListCtrl:connect(ListCtrl, command_list_item_activated),
    wxListCtrl:connect(ListCtrl, command_list_item_right_click),
    wxListCtrl:connect(ListCtrl, command_list_col_click),
    %% Use focused instead of selected, selected doesn't generate events
    %% for all multiple selections on Linux
    wxListCtrl:connect(ListCtrl, command_list_item_focused),
    ListCtrl.

dump_to_file(Parent, FileName, Holder) ->
    case file:open(FileName, [write]) of
	{ok, Fd} ->
	    %% Holder closes the file when it's done
	    Holder ! {dump, Fd};
	{error, Reason} ->
	    FailMsg = file:format_error(Reason),
	    MD = wxMessageDialog:new(Parent, FailMsg),
	    wxDialog:showModal(MD),
	    wxDialog:destroy(MD)
    end.

start_procinfo(_Node, undefined, _Frame, Opened) ->
    Opened;
start_procinfo(Node, Pid, Frame, Opened) ->
    case lists:member(Pid, Opened) of
	true ->
	    Opened;
	false ->
	    observer_procinfo:start(Node, Pid, Frame, self()),
	    [Pid | Opened]
    end.

call(Holder, What) ->
    Ref = erlang:monitor(process, Holder),
    Holder ! What,
    receive
	{'DOWN', Ref, _, _, _} -> "";
	{Holder, Res} ->
	    erlang:demonitor(Ref),
	    Res
    after 2000 ->
	    io:format("Hanging call ~p~n",[What]),
	    ""
    end.

%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

handle_info({holder_updated, Count}, State0=#state{grid=Grid}) ->
    State = update_selection(State0),

    wxListCtrl:setItemCount(Grid, Count),
    wxListCtrl:refreshItems(Grid, 0, Count-1),

    {noreply, State};

handle_info(refresh_interval, #state{holder=Holder}=State) ->
    Holder ! refresh,
    {noreply, State};

handle_info({tracemenu_closed, TraceOpts, MatchSpecs}, State) ->
    {noreply, State#state{tracemenu_opened=false,
			  trace_options=TraceOpts,
			  match_specs=MatchSpecs}};

handle_info({procinfo_menu_closed, Pid},
	    #state{procinfo_menu_pids=Opened}=State) ->
    NewPids = lists:delete(Pid, Opened),
    {noreply, State#state{procinfo_menu_pids=NewPids}};

handle_info({active, Node},
	    #state{holder=Holder, timer=Timer, parent=Parent}=State) ->
    create_pro_menu(Parent, Holder),
    Holder ! {change_node, Node},
    {noreply, State#state{timer=observer_lib:start_timer(Timer)}};

handle_info(not_active, #state{timer=Timer0}=State) ->
    Timer = observer_lib:stop_timer(Timer0),
    {noreply, State#state{timer=Timer}};

handle_info({node, Node}, #state{holder=Holder}=State) ->
    Holder ! {change_node, Node},
    {noreply, State};

handle_info(Info, State) ->
    io:format("~p, ~p, Handled unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
    {noreply, State}.

terminate(Reason, #state{holder=Holder}) ->
    io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]),
    Holder ! stop,
    etop:stop(),
    ok.

code_change(_, _, State) ->
    {stop, not_yet_implemented, State}.


handle_call(Msg, _From, State) ->
    io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
    {reply, ok, State}.


handle_cast(Msg, State) ->
    io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
    {noreply, State}.

%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

handle_event(#wx{id=?ID_DUMP_TO_FILE}, #state{panel=Panel, holder=Holder}=State) ->
    FD  =  wxFileDialog:new(Panel,
			    [{style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
    case wxFileDialog:showModal(FD) of
	?wxID_OK ->
	    Path = wxFileDialog:getPath(FD),
	    wxDialog:destroy(FD),
	    dump_to_file(Panel, Path, Holder);
	_ ->
	    wxDialog:destroy(FD)
    end,
    {noreply, State};

handle_event(#wx{id=?ID_ACCUMULATE,
		 event=#wxCommand{type=command_menu_selected, commandInt=CmdInt}},
	     #state{holder=Holder}=State) ->
    Holder ! {accum, CmdInt =:= 1},
    {noreply, State};

handle_event(#wx{id=?ID_REFRESH, event=#wxCommand{type=command_menu_selected}},
	     #state{holder=Holder}=State) ->
    Holder ! refresh,
    {noreply, State};

handle_event(#wx{id=?ID_REFRESH_INTERVAL},
	     #state{panel=Panel, timer=Timer0}=State) ->
    Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60),
    {noreply, State#state{timer=Timer}};

handle_event(#wx{id=?ID_KILL},
	     #state{popup_menu=Pop,sel={[_|Ids], [ToKill|Pids]}}=State) ->
    wxWindow:show(Pop, [{show, false}]),
    exit(ToKill, kill),
    {noreply, State#state{sel={Ids,Pids}}};


handle_event(#wx{id=?ID_PROC},
	     #state{panel=Panel,
		    popup_menu=Pop,
		    holder=Holder,
		    sel={_, [Pid|_]},
		    procinfo_menu_pids=Opened}=State) ->
    wxWindow:show(Pop, [{show, false}]),
    Node = call(Holder, {get_node, self()}),
    Opened2 = start_procinfo(Node, Pid, Panel, Opened),
    {noreply, State#state{procinfo_menu_pids=Opened2}};

handle_event(#wx{id=?ID_TRACEMENU},
	     #state{holder=Holder,
		    popup_menu=Pop,
		    trace_options=Options,
		    match_specs=MatchSpecs,
		    sel=Pids,
		    tracemenu_opened=false,
		    panel=Panel}=State)  ->
    wxWindow:show(Pop, [{show, false}]),
    case Pids of
	[] ->
	    observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION),
	    {noreply, State};
	Pids ->
	    Node = call(Holder, {get_node, self()}),
	    observer_trace_wx:start(Node,
				    Pids,
				    Options,
				    MatchSpecs,
				    Panel,
				    self()),
	    {noreply,  State#state{tracemenu_opened=true}}
    end;

handle_event(#wx{id=?ID_TRACE_ALL_MENU, event=#wxCommand{type=command_menu_selected}},
	     #state{holder=Holder,
		    trace_options=Options,
		    match_specs=MatchSpecs,
		    tracemenu_opened=false,
		    panel=Panel}=State) ->
    Node = call(Holder, {get_node, self()}),
    observer_trace_wx:start(Node,
			    all,
			    Options,
			    MatchSpecs,
			    Panel,
			    self()),
    {noreply, State#state{tracemenu_opened=true}};


handle_event(#wx{id=?ID_TRACE_NEW_MENU, event=#wxCommand{type=command_menu_selected}},
	     #state{holder=Holder,
		    trace_options=Options,
		    match_specs=MatchSpecs,
		    tracemenu_opened=false,
		    panel=Panel}=State) ->
    Node = call(Holder, {get_node, self()}),
    observer_trace_wx:start(Node,
			    new,
			    Options,
			    MatchSpecs,
			    Panel,
			    self()),
    {noreply,  State#state{tracemenu_opened=true}};

handle_event(#wx{event=#wxSize{size={W,_}}},
	     #state{grid=Grid}=State) ->
    wx:batch(fun() ->
		     Cols = wxListCtrl:getColumnCount(Grid),
		     Last = lists:foldl(fun(I, Last) ->
						Last - wxListCtrl:getColumnWidth(Grid, I)
					end, W-2, lists:seq(0, Cols - 2)),
		     Size = max(200, Last),
		     wxListCtrl:setColumnWidth(Grid, Cols-1, Size)
	     end),
    {noreply, State};

handle_event(#wx{event=#wxList{type=command_list_item_right_click,
			       itemIndex=Row}},
	     #state{popup_menu=Popup,
		    holder=Holder}=State) ->

    case call(Holder, {get_row, self(), Row, pid}) of
	{error, undefined} ->
	    wxWindow:show(Popup, [{show, false}]),
	    undefined;
	{ok, _} ->
	    wxWindow:move(Popup, wx_misc:getMousePosition()),
	    wxWindow:show(Popup)
    end,
    {noreply, State};

handle_event(#wx{event=#wxList{type=command_list_item_focused,
			       itemIndex=Row}},
	     #state{grid=Grid,popup_menu=Pop,holder=Holder} = State) ->
    case Row >= 0 of
	true ->
	    wxWindow:show(Pop, [{show, false}]),
	    SelIds = [Row|lists:delete(Row, get_selected_items(Grid))],
	    Pids = call(Holder, {get_pids, self(), SelIds}),
	    %% io:format("Focused ~p -> ~p~n",[State#state.sel, {SelIds, Pids}]),
	    {noreply, State#state{sel={SelIds, Pids}}};
	false ->
	    %% io:format("Focused -1~n",[]),
	    {noreply, State}
    end;

handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
	     #state{holder=Holder}=State) ->
    Holder !  {change_sort, Col},
    {noreply, State};

handle_event(#wx{event=#wxList{type=command_list_item_activated}},
	     #state{panel=Panel, procinfo_menu_pids=Opened,
		    holder=Holder,
		    sel={_, [Pid|_]}}=State)
  when Pid =/= undefined ->
    Node = call(Holder, {get_node, self()}),
    Opened2=start_procinfo(Node, Pid, Panel, Opened),
    {noreply, State#state{procinfo_menu_pids=Opened2}};

handle_event(Event, State) ->
    io:format("~p~p, handle event ~p\n", [?MODULE, ?LINE, Event]),
    {noreply, State}.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

update_selection(State=#state{holder=Holder, grid=Grid,
			      sel={SelIds0, SelPids0}}) ->
    Sel = {SelIds,_SelPids} = call(Holder, {get_rows_from_pids, self(), SelPids0}),
    set_focus(SelIds0, SelIds, Grid),
    case SelIds =:= SelIds0 of
	true -> ok;
	false ->
	    wx:batch(fun() ->
			     [wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_SELECTED) ||
				 I <- SelIds0],
			     [wxListCtrl:setItemState(Grid, I, 16#FFFF, ?wxLIST_STATE_SELECTED) ||
				 I <- SelIds]
		     end)
    end,
    %%io:format("Update ~p -> ~p~n",[{SelIds0, SelPids0}, Sel]),
    State#state{sel=Sel}.

get_selected_items(Grid) ->
    get_selected_items(Grid, -1, []).

get_selected_items(Grid, Index, ItemAcc) ->
    Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL},
						{state, ?wxLIST_STATE_SELECTED}]),
    case Item of
	-1 ->
	    lists:reverse(ItemAcc);
	_ ->
	    get_selected_items(Grid, Item, [Item | ItemAcc])
    end.

set_focus([], [], _Grid) -> ok;
set_focus([Same|_], [Same|_], _Grid) -> ok;
set_focus([], [New|_], Grid) ->
    wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED);
set_focus([Old|_], [], Grid) ->
    wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED);
set_focus([Old|_], [New|_], Grid) ->
    wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED),
    wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED).


%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

init_table_holder(Parent, Attrs) ->
    Backend = spawn_link(node(), observer_backend,etop_collect,[self()]),
    table_holder(#holder{parent=Parent,
			 info=#etop_info{procinfo=[]},
			 node=node(),
			 backend_pid=Backend,
			 attrs=Attrs
			}).

table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs,
		     node=Node, backend_pid=Backend}=S0) ->
    receive
	{get_row, From, Row, Col} ->
	    get_row(From, Row, Col, Info),
	    table_holder(S0);
	{get_attr, From, Row} ->
	    get_attr(From, Row, Attrs),
	    table_holder(S0);
	{Backend, EtopInfo=#etop_info{}} ->
	    State = handle_update(EtopInfo, S0),
	    table_holder(State#holder{backend_pid=undefined});
	refresh when is_pid(Backend)->
	    table_holder(S0); %% Already updating
	refresh ->
	    Pid = spawn_link(Node,observer_backend,etop_collect,[self()]),
	    table_holder(S0#holder{backend_pid=Pid});
	{change_sort, Col} ->
	    State = change_sort(Col, S0),
	    table_holder(State);
	{get_pids, From, Indices} ->
	    get_pids(From, Indices, Info),
	    table_holder(S0);
	{get_rows_from_pids, From, Pids} ->
	    get_rows_from_pids(From, Pids, Info),
	    table_holder(S0);

	{get_node, From} ->
	    From ! {self(), Node},
	    table_holder(S0);
	{change_node, NewNode} ->
	    case Node == NewNode of
		true ->
		    table_holder(S0);
		false ->
		    self() ! refresh,
		    table_holder(S0#holder{node=NewNode})
	    end;
	{accum, Bool} ->
	    table_holder(change_accum(Bool,S0));
	{get_accum, From} ->
	    From ! {self(), S0#holder.accum == true},
	    table_holder(S0);
	{dump, Fd} ->
	    etop_txt:do_update(Fd, S0#holder.info, #opts{node=Node}),
	    file:close(Fd),
	    table_holder(S0);
	stop ->
	    ok;
	What ->
	    io:format("Table holder got ~p~n",[What]),
	    table_holder(S0)
    end.

change_sort(Col, S0=#holder{parent=Parent, info=EI=#etop_info{procinfo=Data}, sort=Sort0}) ->
    {Sort, ProcInfo}=sort(Col, Sort0, Data),
    Parent ! {holder_updated, length(Data)},
    S0#holder{info=EI#etop_info{procinfo=ProcInfo}, sort=Sort}.

change_accum(true, S0) ->
    S0#holder{accum=true};
change_accum(false, S0=#holder{info=#etop_info{procinfo=Info}}) ->
    self() ! refresh,
    S0#holder{accum=lists:sort(Info)}.

handle_update(EI=#etop_info{procinfo=ProcInfo0},
	      S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) ->
    {ProcInfo1, S1} = accum(ProcInfo0, S0),
    {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1),
    Parent ! {holder_updated, length(ProcInfo)},
    S1#holder{info=EI#etop_info{procinfo=ProcInfo}}.

accum(ProcInfo, State=#holder{accum=true}) ->
    {ProcInfo, State};
accum(ProcInfo0, State=#holder{accum=Previous}) ->
    ProcInfo = lists:sort(ProcInfo0),
    {accum2(ProcInfo,Previous,[]), State#holder{accum=ProcInfo}}.

accum2([PI=#etop_proc_info{pid=Pid, reds=Reds, runtime=RT}|PIs],
       [#etop_proc_info{pid=Pid, reds=OldReds, runtime=OldRT}|Old], Acc) ->
    accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds, runtime=RT-OldRT}|Acc]);
accum2(PIs=[#etop_proc_info{pid=Pid}|_], [#etop_proc_info{pid=OldPid}|Old], Acc)
  when Pid > OldPid ->
    accum2(PIs, Old, Acc);
accum2([PI|PIs], Old, Acc) ->
    accum2(PIs, Old, [PI|Acc]);
accum2([], _, Acc) -> Acc.

sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) ->
    {Opt#sort{sort_incr=not Bool}, lists:reverse(Table)};
sort(Col, S=#sort{sort_incr=true}, Table) ->
    {S#sort{sort_key=Col}, lists:keysort(col_to_element(Col), Table)};
sort(Col, S=#sort{sort_incr=false}, Table) ->
    {S#sort{sort_key=Col}, lists:reverse(lists:keysort(col_to_element(Col), Table))}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

get_procinfo_data(Col, Info) ->
    element(col_to_element(Col), Info).
col_to_element(?COL_PID)  -> #etop_proc_info.pid;
col_to_element(?COL_NAME) -> #etop_proc_info.name;
col_to_element(?COL_MEM)  -> #etop_proc_info.mem;
col_to_element(?COL_TIME) -> #etop_proc_info.runtime;
col_to_element(?COL_REDS) -> #etop_proc_info.reds;
col_to_element(?COL_FUN)  -> #etop_proc_info.cf;
col_to_element(?COL_MSG)  -> #etop_proc_info.mq.

get_pids(From, Indices, ProcInfo) ->
    Processes = [(lists:nth(I+1, ProcInfo))#etop_proc_info.pid || I <- Indices],
    From ! {self(), Processes}.

get_row(From, Row, pid, Info) ->
    Pid = case Row =:= -1 of
	      true ->  {error, undefined};
	      false -> {ok, get_procinfo_data(?COL_PID, lists:nth(Row+1, Info))}
	  end,
    From ! {self(), Pid};
get_row(From, Row, Col, Info) ->
    Data = case Row+1 > length(Info) of
	       true ->
		   "";
	       false ->
		   ProcInfo = lists:nth(Row+1, Info),
		   get_procinfo_data(Col, ProcInfo)
	   end,
    From ! {self(), observer_lib:to_str(Data)}.

get_rows_from_pids(From, Pids0, Info) ->
    Res = lists:foldl(fun(Pid, Data = {Ids, Pids}) ->
			      case index(Pid, Info, 0) of
				  false -> Data;
				  Index -> {[Index|Ids], [Pid|Pids]}
			      end
		      end, {[],[]}, Pids0),
    From ! {self(), Res}.

get_attr(From, Row, Attrs) ->
    Attribute = case Row rem 2 =:= 0 of
		    true ->  Attrs#attrs.even;
		    false -> Attrs#attrs.odd
		end,
    From ! {self(), Attribute}.

index(Pid, [#etop_proc_info{pid=Pid}|_], Index) -> Index;
index(Pid, [_|PI], Index) -> index(Pid, PI, Index+1);
index(_, _, _) -> false.