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