From 96175f1ebfd109f1268898d68a735ec5a16e9933 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 20 Apr 2016 11:43:08 +0200 Subject: [ttb] Set trace patterns on messages Functions ttb:tpe/2 and ttb:ctpe/1 are added. --- lib/observer/doc/src/ttb.xml | 28 +++++++++++++++++++--------- lib/observer/src/ttb.erl | 12 +++++++++++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml index 2b637551db..76bbc777e8 100644 --- a/lib/observer/doc/src/ttb.xml +++ b/lib/observer/doc/src/ttb.xml @@ -257,17 +257,23 @@ ttb:p(all, call). - tp, tpl, ctp, ctpl, ctpg + tp, tpl, tpe, ctp, ctpl, ctpg, ctpe Set and clear trace patterns. -

These functions are to be used with - trace flag call for setting and clearing trace - patterns. When trace flag call is set on a process, +

These functions are to be used with trace + flag call, send, and 'receive' for + setting and clearing trace patterns.

+

When trace flag call is set on a process, function calls are traced on that process if a trace - pattern is set for the called function. Trace patterns - specify how to trace a function by using match - specifications. Match specifications are described in the - ERTS User's Guide. + pattern is set for the called function.

+

The send and 'receive' flags enable tracing + of all messages sent and received by the process. Trace + patterns set with tpe may limit traced messages based + on the message content, the sender, and/or the receiver.

+

Trace patterns specify how to trace a function or a message + by using match specifications. Match specifications are + described in the + ERTS User's Guide.

These functions are equivalent to the corresponding functions in module @@ -284,6 +290,8 @@ ttb:p(all, call).

Sets trace patterns on global function calls.

tpl

Sets trace patterns on local and global function calls.

+ tpe +

Sets trace patterns on messages.

ctp

Clears trace patterns on local and global function calls.

@@ -291,13 +299,15 @@ ttb:p(all, call).

Clears trace patterns on local function calls.

ctpg

Clears trace patterns on global function calls.

+ ctpe +

Clears trace patterns on messages.

With tp and tpl, one of the match specification shortcuts can be used (for example, ttb:tp(foo_module, caller)).

The shortcuts are as follows:

return - for [{'_',[],[{return_trace}]}] - (report the return value) + (report the return value from a traced function) caller - for [{'_',[],[{message,{caller}}]}] (report the calling function) {codestr, Str} - for dbg:fun2ms/1 arguments diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl index 4d6eb3ba8d..32c2fad775 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -25,7 +25,8 @@ -export([tracer/0,tracer/1,tracer/2,p/2,stop/0,stop/1,start_trace/4]). -export([get_et_handler/0]). -export([tp/2, tp/3, tp/4, ctp/0, ctp/1, ctp/2, ctp/3, tpl/2, tpl/3, tpl/4, - ctpl/0, ctpl/1, ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3]). + ctpl/0, ctpl/1, ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3, + tpe/2, ctpe/1]). -export([seq_trigger_ms/0,seq_trigger_ms/1]). -export([write_trace_info/2]). -export([write_config/2,write_config/3,run_config/1,run_config/2,list_config/1]). @@ -479,6 +480,11 @@ tpl(A,B,C,D) -> store(tpl,[A,B,C,ms(D)]), dbg:tpl(A,B,C,ms(D)). +tpe(A,B) -> + ensure_no_overloaded_nodes(), + store(tpe,[A,ms(B)]), + dbg:tpe(A,ms(B)). + ctp() -> store(ctp,[]), dbg:ctp(). @@ -518,6 +524,10 @@ ctpg(A,B,C) -> store(ctpg,[A,B,C]), dbg:ctpg(A,B,C). +ctpe(A) -> + store(ctpe,[A]), + dbg:ctpe(A). + ms(return) -> [{'_',[],[{return_trace}]}]; ms(caller) -> -- cgit v1.2.3 From b41327bb7f006c1379301336b9b9d6c1e077f34e Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 21 Apr 2016 17:28:20 +0200 Subject: [observer] Add functionality in GUI for trace pattern on messages --- lib/observer/doc/src/observer_ug.xml | 8 +++- lib/observer/src/observer_trace_wx.erl | 53 ++++++++++++++++++++++----- lib/observer/src/observer_traceoptions_wx.erl | 48 +++++++++++++++++------- 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml index ca354df864..be5d249f37 100644 --- a/lib/observer/doc/src/observer_ug.xml +++ b/lib/observer/doc/src/observer_ug.xml @@ -254,7 +254,13 @@

If function calls are traced, trace patterns must be added by clicking button Add Trace Pattern. Select a module, function(s), and a match specification. - If no functions are selected, all functions in the module are traced. + If no functions are selected, all functions in the module are traced.

+

+ Trace patterns can also be added for traced messages. This is + done in the same way as for traced function calls. Separate + trace patterns must be set for sent and received messages. +

+

A few basic match specifications are provided in the tool, and you can provide your own match specifications. The syntax of match specifications is described in the match_specs=default_matchspecs()}}. default_matchspecs() -> - Ms = [{"Return Trace", [{'_', [], [{return_trace}]}], "fun(_) -> return_trace() end"}, - {"Exception Trace", [{'_', [], [{exception_trace}]}], "fun(_) -> exception_trace() end"}, - {"Message Caller", [{'_', [], [{message,{caller}}]}], "fun(_) -> message(caller()) end"}, - {"Message Dump", [{'_', [], [{message,{process_dump}}]}], "fun(_) -> message(process_dump()) end"}], + [{Key,default_matchspecs(Key)} || Key <- [funcs,send,'receive']]. +default_matchspecs(Key) -> + Ms = get_default_matchspecs(Key), [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms]. +get_default_matchspecs(funcs) -> + [{"Skeleton", [{'$1', [], [true]}], "fun(Args) -> true end"}, + {"Return Trace", [{'_', [], [{return_trace}]}], + "fun(_) -> return_trace() end"}, + {"Exception Trace", [{'_', [], [{exception_trace}]}], "fun(_) -> exception_trace() end"}, + {"Message Caller", [{'_', [], [{message,{caller}}]}], "fun(_) -> message(caller()) end"}, + {"Message Dump", [{'_', [], [{message,{process_dump}}]}], "fun(_) -> message(process_dump()) end"}]; +get_default_matchspecs(send) -> + [{"Skeleton", [{['$1','$2'], [], [true]}], "fun([Pid,Msg]) -> true end"}]; +get_default_matchspecs('receive') -> + [{"Skeleton", [{['$1','$2','$3'], [], [true]}], "fun([Node,Pid,Msg]) -> true end"}]. + + create_process_view(Parent) -> Panel = wxPanel:new(Parent), MainSz = wxBoxSizer:new(?wxHORIZONTAL), @@ -192,7 +204,7 @@ create_menues(Parent) -> #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}]}, {"Options", [#create_menu{id = ?TRACE_OUTPUT, text = "Output"}, - #create_menu{id = ?TRACE_DEFMS, text = "Match Specifications"}, + #create_menu{id = ?TRACE_DEFMS, text = "Default Match Specifications for Functions"}, #create_menu{id = ?TRACE_DEFPS, text = "Default Process Options"}]} ], observer_wx:create_menus(Parent, Menus). @@ -378,7 +390,7 @@ handle_event(#wx{id=?TRACE_DEFPS}, #state{panel=Panel, def_trace_opts=PO} = Stat handle_event(#wx{id=?TRACE_DEFMS}, #state{panel=Panel, match_specs=Ms} = State) -> try %% Return selected MS and sends new MS's to us - observer_traceoptions_wx:select_matchspec(self(), Panel, Ms) + observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, funcs) catch _:_ -> cancel end, @@ -391,10 +403,22 @@ handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs, try [Module] = get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))), Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)), - Ms = observer_traceoptions_wx:select_matchspec(self(), Panel, Mss), + Key = case Module of + 'Events' -> + SelectedEvents = [Event || #tpattern{fa=Event} <- Selected], + E1 = hd(SelectedEvents), + case lists:all(fun(E) when E==E1 -> true; (_) -> false end, + SelectedEvents) of + true -> E1; + false -> throw({error,"Can not set match specs for multiple event types"}) + end; + _ -> funcs + end, + Ms = observer_traceoptions_wx:select_matchspec(self(), Panel, Mss, Key), Changed = [TP#tpattern{ms=Ms} || TP <- Selected], {noreply, do_add_patterns({Module, Changed}, State)} - catch _:_ -> + catch {error, Msg} -> + observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR), {noreply, State} end; @@ -564,10 +588,17 @@ update_modules_view(Mods, Module, LCtrl) -> update_functions_view(Funcs, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), - wx:foldl(fun(#tpattern{fa=FA, ms=#match_spec{str=Ms}}, Row) -> + wx:foldl(fun(#tpattern{m=M, fa=FA, ms=#match_spec{str=Ms}}, Row) -> _Item = wxListCtrl:insertItem(LCtrl, Row, ""), ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), - wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({func,FA})), + FuncStr = + case M of + 'Events' -> + observer_lib:to_str(FA); + _ -> + observer_lib:to_str({func,FA}) + end, + wxListCtrl:setItem(LCtrl, Row, 0, FuncStr), wxListCtrl:setItem(LCtrl, Row, 1, Ms), Row+1 end, 0, Funcs). @@ -695,6 +726,8 @@ setup_tps([First|Rest], Prev) -> setup_tps([], Prev) -> [setup_tp(TP) || TP <- lists:reverse(Prev)]. +setup_tp(#tpattern{m='Events',fa=Event, ms=#match_spec{term=Ms}}) -> + ttb:tpe(Event,Ms); setup_tp(#tpattern{m=M,fa={F,A}, ms=#match_spec{term=Ms}}) -> ttb:tpl(M,F,A,Ms). diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index 9ba9b72b6f..f9b4929ec4 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -23,7 +23,7 @@ -include("observer_defs.hrl"). -export([process_trace/2, trace_pattern/4, select_nodes/2, - output/2, select_matchspec/3]). + output/2, select_matchspec/4]). process_trace(Parent, Default) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", @@ -100,10 +100,17 @@ process_trace(Parent, Default) -> trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> try - Module = module_selector(Parent, Node), - MFAs = function_selector(Parent, Node, Module), - MatchSpec = select_matchspec(ParentPid, Parent, MatchSpecs), - {Module, [#tpattern{m=M,fa={F,A},ms=MatchSpec} || {M,F,A} <- MFAs]} + {Module,MFAs,MatchSpec} = + case module_selector(Parent, Node) of + {'$trace_event',Event} -> + MS = select_matchspec(ParentPid, Parent, MatchSpecs, Event), + {'Events',[{'Events',Event}],MS}; + Mod -> + MFAs0 = function_selector(Parent, Node, Mod), + MS = select_matchspec(ParentPid, Parent, MatchSpecs, funcs), + {Mod,MFAs0,MS} + end, + {Module, [#tpattern{m=M,fa=FA,ms=MatchSpec} || {M,FA} <- MFAs]} catch cancel -> cancel end. @@ -112,7 +119,7 @@ select_nodes(Parent, Nodes) -> check_selector(Parent, Choices). module_selector(Parent, Node) -> - Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module", + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module or Event", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, {size, {400, 400}}]), Panel = wxPanel:new(Dialog), @@ -136,7 +143,9 @@ module_selector(Parent, Node) -> wxWindow:setFocus(TxtCtrl), %% init data Modules = get_modules(Node), - AllModules = [{atom_to_list(X), X} || X <- Modules], + Events = [{"Messages sent",{'$trace_event',send}}, + {"Messages received",{'$trace_event','receive'}}], + AllModules = Events ++ [{atom_to_list(X), X} || X <- Modules], filter_listbox_data("", AllModules, ListBox), wxTextCtrl:connect(TxtCtrl, command_text_updated, [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> @@ -174,9 +183,9 @@ function_selector(Parent, Node, Module) -> not(erl_internal:guard_bif(Name, Arity))]), ParsedChoices = parse_function_names(Choices), case check_selector(Parent, ParsedChoices) of - [] -> [{Module, '_', '_'}]; + [] -> [{Module, {'_', '_'}}]; FAs -> - [{Module, F, A} || {F,A} <- FAs] + [{Module, {F, A}} || {F,A} <- FAs] end. check_selector(Parent, ParsedChoices) -> @@ -268,7 +277,12 @@ get_checked(ListBox, Acc) -> lists:reverse(Acc) end. -select_matchspec(Pid, Parent, MatchSpecs) -> +select_matchspec(Pid, Parent, AllMatchSpecs, Key) -> + {MatchSpecs,RestMS} = + case lists:keytake(Key,1,AllMatchSpecs) of + {value,{Key,MSs0},Rest} -> {MSs0,Rest}; + false -> {[],AllMatchSpecs} + end, Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Match Specifications", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, {size, {400, 400}}]), @@ -314,7 +328,11 @@ select_matchspec(Pid, Parent, MatchSpecs) -> Add = fun(_,_) -> case edit_ms(TextCtrl, new, Parent) of - Ms = #match_spec{} -> add_and_select(-1, Ms, ListBox); + Ms = #match_spec{} -> + add_and_select(-1, Ms, ListBox), + wxWindow:enable(OkButt), + wxWindow:enable(EditMsBtn), + wxWindow:enable(DelMsBtn); Else -> Else end end, @@ -324,7 +342,11 @@ select_matchspec(Pid, Parent, MatchSpecs) -> true -> #match_spec{name=Name} = wxListBox:getClientData(ListBox,SelId), case edit_ms(TextCtrl, Name, Parent) of - Ms = #match_spec{} -> add_and_select(SelId, Ms, ListBox); + Ms = #match_spec{} -> + add_and_select(SelId, Ms, ListBox), + wxWindow:enable(OkButt), + wxWindow:enable(EditMsBtn), + wxWindow:enable(DelMsBtn); Else -> Else end; false -> @@ -367,7 +389,7 @@ select_matchspec(Pid, Parent, MatchSpecs) -> Count = wxListBox:getCount(ListBox), MSs = [wxListBox:getClientData(ListBox, Id) || Id <- lists:seq(0, Count-1)], - Pid ! {update_ms, MSs}, + Pid ! {update_ms, [{Key,MSs}|RestMS]}, MS = lists:nth(SelId+1, MSs), wxDialog:destroy(Dialog), MS; -- cgit v1.2.3 From eeb094055101971ff94449cff08986126fb6add1 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 27 Apr 2016 12:21:26 +0200 Subject: [observer] Add Ports tab in GUI --- lib/observer/src/Makefile | 1 + lib/observer/src/observer.app.src | 1 + lib/observer/src/observer_port_wx.erl | 396 +++++++++++++++++++++++++++++ lib/observer/src/observer_tv_wx.erl | 1 + lib/observer/src/observer_wx.erl | 30 ++- lib/runtime_tools/src/observer_backend.erl | 12 +- 6 files changed, 431 insertions(+), 10 deletions(-) create mode 100644 lib/observer/src/observer_port_wx.erl diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 85dc5933c1..dd7831fa2b 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -67,6 +67,7 @@ MODULES= \ observer_html_lib \ observer_lib \ observer_perf_wx \ + observer_port_wx \ observer_pro_wx \ observer_procinfo \ observer_sys_wx \ diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src index 5ddf65fa59..3a5bd172e7 100644 --- a/lib/observer/src/observer.app.src +++ b/lib/observer/src/observer.app.src @@ -51,6 +51,7 @@ observer_html_lib, observer_lib, observer_perf_wx, + observer_port_wx, observer_pro_wx, observer_procinfo, observer_sys_wx, diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl new file mode 100644 index 0000000000..64502f0d16 --- /dev/null +++ b/lib/observer/src/observer_port_wx.erl @@ -0,0 +1,396 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +-module(observer_port_wx). + +-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_sync_event/3, handle_cast/2]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-define(GRID, 300). +-define(ID_REFRESH, 301). +-define(ID_REFRESH_INTERVAL, 302). +-define(ID_PORT_INFO, 303). +-define(ID_TRACE_PORTS, 304). +-define(ID_TRACE_NAMES, 305). +-define(ID_TRACE_NEW, 306). +-define(ID_TRACE_ALL, 307). +-define(ID_CLOSE_PORT, 308). + +-define(TRACE_PORTS_STR, "Trace selected ports"). +-define(TRACE_NAMES_STR, "Trace selected ports, " + "if a process have a registered name " + "processes with same name will be traced on all nodes"). + +-record(port, + {id, + connected, + name, + controls, + slot, + id_str, + links, + monitors}). + +-record(opt, {sort_key=2, + sort_incr=true + }). + +-record(state, + { + parent, + grid, + panel, + node=node(), + opt=#opt{}, + selected, + ports, + timer, + open_wins=[] + }). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + Panel = wxPanel:new(Notebook), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Sizer), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, DefSize), + Col + 1 + end, + ListItems = [{"Id", ?wxLIST_FORMAT_LEFT, 150}, + {"Connected", ?wxLIST_FORMAT_LEFT, 150}, + {"Name", ?wxLIST_FORMAT_LEFT, 150}, + {"Controls", ?wxLIST_FORMAT_LEFT, 200}, + {"Slot", ?wxLIST_FORMAT_RIGHT, 50}], + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + wxListCtrl:connect(Grid, command_list_item_right_click), + wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, command_list_col_click), + wxListCtrl:connect(Grid, size, [{skip, true}]), + + wxWindow:setFocus(Grid), + {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}. + +handle_event(#wx{id=?ID_REFRESH}, + State = #state{node=Node, grid=Grid, opt=Opt}) -> + Ports0 = get_ports(Node), + Ports = update_grid(Grid, Opt, Ports0), + {noreply, State#state{ports=Ports}}; + +handle_event(#wx{obj=Obj, event=#wxClose{}}, #state{open_wins=Opened} = State) -> + NewOpened = + case lists:keytake(Obj,2,Opened) of + false -> Opened; + {value,_,Rest} -> Rest + end, + {noreply, State#state{open_wins=NewOpened}}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + State = #state{node=Node, grid=Grid, + opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> + Opt = case Col+2 of + Key -> Opt0#opt{sort_incr=not Bool}; + NewKey -> Opt0#opt{sort_key=NewKey} + end, + Ports0 = get_ports(Node), + Ports = update_grid(Grid, Opt, Ports0), + wxWindow:setFocus(Grid), + {noreply, State#state{opt=Opt, ports=Ports}}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> + observer_lib:set_listctrl_col_size(Grid, W), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_activated, + itemIndex=Index}}, + State=#state{grid=Grid, ports=Ports, open_wins=Opened}) -> + Port = lists:nth(Index+1, Ports), + NewOpened = display_port_info(Grid, Port, Opened), + {noreply, State#state{open_wins=NewOpened}}; + +handle_event(#wx{event=#wxList{type=command_list_item_right_click}}, + State=#state{panel=Panel}) -> + + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_PORT_INFO, "Port info"), + wxMenu:append(Menu, ?ID_TRACE_PORTS, "Trace ports", + [{help, ?TRACE_PORTS_STR}]), + wxMenu:append(Menu, ?ID_TRACE_NAMES, "Trace named ports (all nodes)", + [{help, ?TRACE_NAMES_STR}]), + wxMenu:append(Menu, ?ID_CLOSE_PORT, "Close Port"), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, + State) -> + {noreply, State#state{selected=Index}}; + +handle_event(#wx{id=?ID_PORT_INFO}, + State = #state{grid=Grid, ports=Ports, + selected=Sel, open_wins=Opened}) -> + case Sel of + undefined -> + {noreply, State}; + R when is_integer(R) -> + Port = lists:nth(Sel+1, Ports), + NewOpened = display_port_info(Grid, Port, Opened), + {noreply, State#state{open_wins = NewOpened}} + end; + +handle_event(#wx{id=?ID_CLOSE_PORT}, State = #state{selected=Sel, ports=Ports}) -> + case Sel of + undefined -> + {noreply, State}; + R when is_integer(R) -> + Port = lists:nth(Sel+1, Ports), + erlang:port_close(Port#port.id), + {noreply, State#state{selected=undefined}} + end; + +handle_event(#wx{id=?ID_REFRESH_INTERVAL}, + State = #state{grid=Grid, timer=Timer0}) -> + Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), + {noreply, State#state{timer=Timer}}; + +handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) -> + observer ! {open_link, TargetPid}, + {noreply, State}; + +handle_event(#wx{obj=Obj, event=#wxMouse{type=enter_window}}, State) -> + wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), + {noreply, State}; + +handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> + wxTextCtrl:setForegroundColour(Obj,?wxBLUE), + {noreply, State}; + +handle_event(Event, _State) -> + error({unhandled_event, Event}). + +handle_sync_event(_Event, _Obj, _State) -> + ok. + +handle_call(Event, From, _State) -> + error({unhandled_call, Event, From}). + +handle_cast(Event, _State) -> + error({unhandled_cast, Event}). + +handle_info({portinfo_open, PortIdStr}, + State = #state{grid=Grid, ports=Ports, open_wins=Opened}) -> + Port = lists:keyfind(PortIdStr,#port.id_str,Ports), + NewOpened = display_port_info(Grid, Port, Opened), + {noreply, State#state{open_wins = NewOpened}}; + +handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, + ports=OldPorts}) -> + case get_ports(Node) of + OldPorts -> + %% no change + {noreply, State}; + Ports0 -> + Ports = update_grid(Grid, Opt, Ports0), + {noreply, State#state{ports=Ports}} + end; + +handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, + timer=Timer0}) -> + Ports0 = get_ports(Node), + Ports = update_grid(Grid, Opt, Ports0), + wxWindow:setFocus(Grid), + create_menus(Parent), + Timer = observer_lib:start_timer(Timer0), + {noreply, State#state{node=Node, ports=Ports, timer=Timer}}; + +handle_info(not_active, State = #state{timer = Timer0}) -> + Timer = observer_lib:stop_timer(Timer0), + {noreply, State#state{timer=Timer}}; + +handle_info({error, Error}, State) -> + handle_error(Error), + {noreply, State}; + +handle_info(_Event, State) -> + {noreply, State}. + +terminate(_Event, _State) -> + ok. + +code_change(_, _, State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent) -> + MenuEntries = + [{"View", + [#create_menu{id = ?ID_PORT_INFO, text = "Port information\tCtrl-I"}, + separator, + #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, + #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} + ]}], + observer_wx:create_menus(Parent, MenuEntries). + +get_ports(Node) -> + case get_ports2(Node) of + Error = {error, _} -> + self() ! Error, + []; + Res -> + Res + end. +get_ports2(Node) -> + case rpc:call(Node, observer_backend, get_port_list, []) of + {badrpc, Error} -> + {error, Error}; + Error = {error, _} -> + Error; + Result -> + [list_to_portrec(Port) || Port <- Result] + end. + +list_to_portrec(PL) -> + %% PortInfo: + %% {registered_name, RegisteredName :: atom()} | + %% {id, Index :: integer() >= 0} | + %% {connected, Pid :: pid()} | + %% {links, Pids :: [pid()]} | + %% {name, String :: string()} | + %% {input, Bytes :: integer() >= 0} | + %% {output, Bytes :: integer() >= 0} | + %% {os_pid, OsPid :: integer() >= 0 | undefined}, + PortId = proplists:get_value(port_id, PL), + #port{id = PortId, + id_str = erlang:port_to_list(PortId), + slot = proplists:get_value(id, PL), + connected = proplists:get_value(connected, PL), + links = proplists:get_value(links, PL, ignore), + name = proplists:get_value(registered_name, PL, ignore), + monitors = proplists:get_value(monitors, PL, ignore), + controls = proplists:get_value(name, PL)}. + +portrec_to_list(#port{id = Id, + slot = Slot, + connected = Connected, + links = Links, + name = Name, + monitors = Monitors, + controls = Controls}) -> + [{id,Id}, + {slot,Slot}, + {connected,Connected}, + {links,Links}, + {name,Name}, + {monitors,Monitors}, + {controls,Controls}]. + +display_port_info(Parent, PortRec, Opened) -> + PortIdStr = PortRec#port.id_str, + case lists:keyfind(PortIdStr,1,Opened) of + false -> + Frame = do_display_port_info(Parent, PortRec), + [{PortIdStr,Frame}|Opened]; + {_,Win} -> + wxFrame:raise(Win), + Opened + end. + +do_display_port_info(Parent0, PortRec) -> + Parent = observer_lib:get_wx_parent(Parent0), + Title = "Port Info: " ++ PortRec#port.id_str, + Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, + [{style, ?wxSYSTEM_MENU bor ?wxCAPTION + bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), + + Port = portrec_to_list(PortRec), + Fields0 = port_info_fields(Port), + {_FPanel, _Sizer, _UpFields} = observer_lib:display_info(Frame, Fields0), + wxFrame:center(Frame), + wxFrame:connect(Frame, close_window, [{skip, true}]), + wxFrame:show(Frame), + Frame. + + +port_info_fields(Port) -> + Struct = + [{"Overview", + [{"Name", name}, + {"Connected", {click,connected}}, + {"Slot", slot}, + {"Controls", controls}]}, + {scroll_boxes, + [{"Links",1,{click,links}}, + {"Monitors",1,{click,monitors}}]}], + observer_lib:fill_info(Struct, Port). + +handle_error(Foo) -> + Str = io_lib:format("ERROR: ~s~n",[Foo]), + observer_lib:display_info_dialog(Str). + +update_grid(Grid, Opt, Ports) -> + wx:batch(fun() -> update_grid2(Grid, Opt, Ports) end). +update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> + wxListCtrl:deleteAllItems(Grid), + Update = + fun(#port{id = Id, + slot = Slot, + connected = Connected, + name = Name, + controls = Ctrl}, + Row) -> + _Item = wxListCtrl:insertItem(Grid, Row, ""), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN); + true -> ignore + end, + + lists:foreach(fun({_, ignore}) -> ignore; + ({Col, Val}) -> + wxListCtrl:setItem(Grid, Row, Col, + observer_lib:to_str(Val)) + end, + [{0,Id},{1,Connected},{2,Name},{3,Ctrl},{4,Slot}]), + Row + 1 + end, + PortInfo = case Dir of + false -> lists:reverse(lists:keysort(Sort, Ports)); + true -> lists:keysort(Sort, Ports) + end, + lists:foldl(Update, 0, PortInfo), + PortInfo. diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 1860f2f469..6acaf4fc73 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -315,6 +315,7 @@ display_table_info(Parent0, Node, Source, Table) -> {_, Sizer, _} = observer_lib:display_info(Frame, [IdInfo,Settings,Memory]), wxSizer:setSizeHints(Sizer, Frame), + wxWindow:setMinSize(Frame, {300, -1}), wxFrame:center(Frame), wxFrame:show(Frame). diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index eba603eab5..d896da1291 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -55,6 +55,7 @@ notebook, main_panel, pro_panel, + port_panel, tv_panel, sys_panel, trace_panel, @@ -164,6 +165,10 @@ setup(#state{frame = Frame} = State) -> ProPanel = observer_pro_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, ProPanel, "Processes", []), + %% Port Panel + PortPanel = observer_port_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, PortPanel, "Ports", []), + %% Table Viewer Panel TVPanel = observer_tv_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []), @@ -187,6 +192,7 @@ setup(#state{frame = Frame} = State) -> status_bar = StatusBar, sys_panel = SysPanel, pro_panel = ProPanel, + port_panel = PortPanel, tv_panel = TVPanel, trace_panel = TracePanel, app_panel = AppPanel, @@ -406,16 +412,21 @@ handle_info({nodedown, Node}, create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), {noreply, State3}; -handle_info({open_link, Pid0}, State = #state{pro_panel=ProcViewer, frame=Frame}) -> - Pid = case Pid0 of - [_|_] -> try list_to_pid(Pid0) catch _:_ -> Pid0 end; - _ -> Pid0 +handle_info({open_link, Id0}, State = #state{pro_panel=ProcViewer, + port_panel=PortViewer, + frame=Frame}) -> + Id = case Id0 of + [_|_] -> try list_to_pid(Id0) catch _:_ -> Id0 end; + _ -> Id0 end, %% Forward to process tab - case is_pid(Pid) of - true -> wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid}; - false -> - Msg = io_lib:format("Information about ~p is not available or implemented",[Pid]), + case Id of + Pid when is_pid(Pid) -> + wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid}; + "#Port" ++ _ = Port -> + wx_object:get_pid(PortViewer) ! {portinfo_open, Port}; + _ -> + Msg = io_lib:format("Information about ~p is not available or implemented",[Id]), Info = wxMessageDialog:new(Frame, Msg), wxMessageDialog:showModal(Info), wxMessageDialog:destroy(Info) @@ -513,10 +524,11 @@ check_page_title(Notebook) -> get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace, app_panel=App, - perf_panel=Perf, allc_panel=Alloc + perf_panel=Perf, allc_panel=Alloc, port_panel=Port }) -> Panel = case check_page_title(Notebook) of "Processes" -> Pro; + "Ports" -> Port; "System" -> Sys; "Table Viewer" -> Tv; ?TRACE_STR -> Trace; diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl index 66653c5b7f..cedb677178 100644 --- a/lib/runtime_tools/src/observer_backend.erl +++ b/lib/runtime_tools/src/observer_backend.erl @@ -23,7 +23,8 @@ -export([vsn/0]). %% observer stuff --export([sys_info/0, get_table/3, get_table_list/2, fetch_stats/2]). +-export([sys_info/0, get_port_list/0, + get_table/3, get_table_list/2, fetch_stats/2]). %% etop stuff -export([etop_collect/1]). @@ -139,6 +140,15 @@ get_mnesia_loop(Parent, {Match, Cont}) -> Parent ! {self(), Match}, get_mnesia_loop(Parent, mnesia:select(Cont)). +get_port_list() -> + [begin + [{port_id,P}|erlang:port_info(P)] ++ + case erlang:port_info(P,monitors) of + undefined -> []; + Monitors -> [Monitors] + end + end || P <- erlang:ports()]. + get_table_list(ets, Opts) -> HideUnread = proplists:get_value(unread_hidden, Opts, true), HideSys = proplists:get_value(sys_hidden, Opts, true), -- cgit v1.2.3 From 7c605482cb179dc81b219d239d15c585049b6433 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 27 Apr 2016 14:15:57 +0200 Subject: [observer] Set correct parent in Label dialog The dialog for setting label on match specs in observer had faulty parent. This is now corrected. --- lib/observer/src/observer_traceoptions_wx.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index f9b4929ec4..8a6d1403a8 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -327,7 +327,7 @@ select_matchspec(Pid, Parent, AllMatchSpecs, Key) -> filter_listbox_data("", Choices, ListBox), Add = fun(_,_) -> - case edit_ms(TextCtrl, new, Parent) of + case edit_ms(TextCtrl, new, Dialog) of Ms = #match_spec{} -> add_and_select(-1, Ms, ListBox), wxWindow:enable(OkButt), @@ -341,7 +341,7 @@ select_matchspec(Pid, Parent, AllMatchSpecs, Key) -> case SelId >= 0 of true -> #match_spec{name=Name} = wxListBox:getClientData(ListBox,SelId), - case edit_ms(TextCtrl, Name, Parent) of + case edit_ms(TextCtrl, Name, Dialog) of Ms = #match_spec{} -> add_and_select(SelId, Ms, ListBox), wxWindow:enable(OkButt), -- cgit v1.2.3 From 90989e58d9e7f7c6fc3c4b52e4191d66d8ff2a96 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 28 Apr 2016 09:52:48 +0200 Subject: [ttb] Allow setting trace flags on ports --- lib/observer/doc/src/ttb.xml | 30 ++++++++++++++++++------------ lib/observer/src/ttb.erl | 36 +++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml index 76bbc777e8..94ecef24b4 100644 --- a/lib/observer/doc/src/ttb.xml +++ b/lib/observer/doc/src/ttb.xml @@ -229,27 +229,33 @@ ttb:p(all, call). - p(Procs,Flags) -> Return - Set the specified trace flags on the specified processes. + p(Item,Flags) -> Return + Set the specified trace flags on the specified processes or ports. - Return = {ok,[{Procs,MatchDesc}]} - Procs = Process | [Process] | all | new | existing - Process = pid() | atom() | {global,atom()} + Return = {ok,[{Item,MatchDesc}]} + Items = Item | [Item] + Item = pid() | port() | RegName | {global,GlobalRegName} | + all | processes | ports | + existing | existing_processes | existing_ports | + new | new_processes | new_ports + RegName = atom() + GlobalRegName = term() Flags = Flag | [Flag] -

Sets the specified trace flags on the specified - processes. Flag timestamp is always turned on. +

Sets the specified trace flags on the specified processes + or ports. Flag timestamp is always turned on.

See the Reference Manual for module dbg - and the possible trace flags. Parameter + for the possible trace flags. Parameter MatchDesc is the same as returned from dbg:p/2.

Processes can be specified as registered names, globally - registered names, or process identifiers. If a registered name - is specified, the flags are set on processes with this name on all - active nodes.

+ registered names, or process identifiers. Ports can be + specified as registered names or port identifiers. If a + registered name is specified, the flags are set on + processes/ports with this name on all active nodes.

Issuing this command starts the timer for this trace if option timer is specified with tracer/2.

@@ -267,7 +273,7 @@ ttb:p(all, call). function calls are traced on that process if a trace pattern is set for the called function.

The send and 'receive' flags enable tracing - of all messages sent and received by the process. Trace + of all messages sent and received by the process/port. Trace patterns set with tpe may limit traced messages based on the message content, the sender, and/or the receiver.

Trace patterns specify how to trace a function or a message diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl index 32c2fad775..e41f2c36fc 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -398,16 +398,16 @@ arg_list([A1|A],Acc) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Set trace flags on processes -p(Procs0,Flags0) -> +p(ProcsPorts0,Flags0) -> ensure_no_overloaded_nodes(), - store(p,[Procs0,Flags0]), - no_store_p(Procs0,Flags0). -no_store_p(Procs0,Flags0) -> + store(p,[ProcsPorts0,Flags0]), + no_store_p(ProcsPorts0,Flags0). +no_store_p(ProcsPorts0,Flags0) -> case transform_flags(to_list(Flags0)) of {error,Reason} -> {error,Reason}; Flags -> - Procs = procs(Procs0), + ProcsPorts = procs_ports(ProcsPorts0), case lists:foldl(fun(P,{PMatched,Ps}) -> case dbg:p(P,Flags) of {ok,Matched} -> {[{P,Matched}|PMatched],[P|Ps]}; @@ -415,7 +415,7 @@ no_store_p(Procs0,Flags0) -> display_warning(P,Reason), {PMatched,Ps} end - end,{[],[]},Procs) of + end,{[],[]},ProcsPorts) of {[],[]} -> {error, no_match}; {SuccMatched,Succ} -> no_store_write_trace_info(flags,{Succ,Flags}), @@ -430,20 +430,22 @@ transform_flags(Flags) -> dbg:transform_flags([timestamp | Flags]). -procs(Procs) when is_list(Procs) -> - lists:foldl(fun(P,Acc) -> proc(P)++Acc end,[],Procs); -procs(Proc) -> - proc(Proc). +procs_ports(Procs) when is_list(Procs) -> + lists:foldl(fun(P,Acc) -> proc_port(P)++Acc end,[],Procs); +procs_ports(Proc) -> + proc_port(Proc). -proc(Procs) when Procs=:=all; Procs=:=ports; Procs=:=processes; - Procs=:=existing; Procs=:=existing_ports; Procs=:=existing_processes; - Procs=:=new; Procs=:=new_ports; Procs=:=new_processes -> - [Procs]; -proc(Name) when is_atom(Name) -> +proc_port(P) when P=:=all; P=:=ports; P=:=processes; + P=:=existing; P=:=existing_ports; P=:=existing_processes; + P=:=new; P=:=new_ports; P=:=new_processes -> + [P]; +proc_port(Name) when is_atom(Name) -> [Name]; % can be registered on this node or other node -proc(Pid) when is_pid(Pid) -> +proc_port(Pid) when is_pid(Pid) -> [Pid]; -proc({global,Name}) -> +proc_port(Port) when is_port(Port) -> + [Port]; +proc_port({global,Name}) -> case global:whereis_name(Name) of Pid when is_pid(Pid) -> [Pid]; -- cgit v1.2.3 From 7621e4aded2b5e8ad1a04102718c9bce21d4dc4c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 28 Apr 2016 14:13:33 +0200 Subject: [observer] Add tracing of ports --- lib/observer/src/observer_port_wx.erl | 32 ++ lib/observer/src/observer_pro_wx.erl | 2 +- lib/observer/src/observer_trace_wx.erl | 411 +++++++++++++++++--------- lib/observer/src/observer_traceoptions_wx.erl | 38 ++- 4 files changed, 349 insertions(+), 134 deletions(-) diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index 64502f0d16..a09637161f 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -183,6 +183,38 @@ handle_event(#wx{id=?ID_CLOSE_PORT}, State = #state{selected=Sel, ports=Ports}) {noreply, State#state{selected=undefined}} end; +handle_event(#wx{id=?ID_TRACE_PORTS}, #state{selected=Sel, ports=Ports}=State) -> + case Sel of + undefined -> + observer_wx:create_txt_dialog(State#state.panel, "No selected port", + "Tracer", ?wxICON_EXCLAMATION), + {noreply, State}; + R when is_integer(R) -> + Port = lists:nth(Sel+1, Ports), + observer_trace_wx:add_ports(observer_wx:get_tracer(), [Port#port.id]), + {noreply, State} + end; + +handle_event(#wx{id=?ID_TRACE_NAMES}, #state{selected=Sel, ports=Ports}=State) -> + case Sel of + undefined -> + observer_wx:create_txt_dialog(State#state.panel, "No selected port", + "Tracer", ?wxICON_EXCLAMATION), + {noreply, State}; + R when is_integer(R) -> + Port = lists:nth(Sel+1, Ports), + IdOrReg = case Port#port.name of + ignore -> Port#port.id; + Name -> Name + end, + observer_trace_wx:add_ports(observer_wx:get_tracer(), [IdOrReg]), + {noreply, State} + end; + +handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> + observer_trace_wx:add_ports(observer_wx:get_tracer(), [new_ports]), + {noreply, State}; + handle_event(#wx{id=?ID_REFRESH_INTERVAL}, State = #state{grid=Grid, timer=Timer0}) -> Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index bd914cdf65..ca0ddab657 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -335,7 +335,7 @@ handle_event(#wx{id=?ID_TRACE_NAMES}, #state{sel={SelIds,_Pids}, holder=Holder, end; handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> - observer_trace_wx:add_processes(observer_wx:get_tracer(), [new]), + observer_trace_wx:add_processes(observer_wx:get_tracer(), [new_processes]), {noreply, State}; handle_event(#wx{event=#wxSize{size={W,_}}}, diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 08710ca9a6..9f4a54c304 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -19,7 +19,7 @@ -module(observer_trace_wx). --export([start_link/2, add_processes/2]). +-export([start_link/2, add_processes/2, add_ports/2]). -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). @@ -31,11 +31,13 @@ -define(SAVE_TRACEOPTS, 305). -define(LOAD_TRACEOPTS, 306). -define(TOGGLE_TRACE, 307). --define(ADD_NEW, 308). --define(ADD_TP, 309). --define(TRACE_OUTPUT, 310). --define(TRACE_DEFMS, 311). --define(TRACE_DEFPS, 312). +-define(ADD_NEW_PROCS, 308). +-define(ADD_NEW_PORTS, 309). +-define(ADD_TP, 310). +-define(TRACE_OUTPUT, 311). +-define(TRACE_DEFMS, 312). +-define(DEF_PROC_OPTS, 313). +-define(DEF_PORT_OPTS, 314). -define(NODES_WIN, 330). -define(ADD_NODES, 331). @@ -45,30 +47,36 @@ -define(EDIT_PROCS, 341). -define(REMOVE_PROCS, 342). --define(MODULES_WIN, 350). +-define(PORT_WIN, 350). +-define(EDIT_PORTS, 351). +-define(REMOVE_PORTS, 352). --define(FUNCS_WIN, 360). --define(EDIT_FUNCS_MS, 361). --define(REMOVE_FUNCS_MS, 362). +-define(MODULES_WIN, 360). --define(LOG_WIN, 370). --define(LOG_SAVE, 321). --define(LOG_CLEAR, 322). +-define(FUNCS_WIN, 370). +-define(EDIT_FUNCS_MS, 371). +-define(REMOVE_FUNCS_MS, 372). + +-define(LOG_WIN, 380). +-define(LOG_SAVE, 381). +-define(LOG_CLEAR, 382). -record(state, {parent, panel, - n_view, p_view, m_view, f_view, %% The listCtrl's + n_view, proc_view, port_view, m_view, f_view, %% The listCtrl's logwin, %% The latest log window nodes = [], toggle_button, - tpids = [], %% #tpid - def_trace_opts = [], + tpids = [], % #titem + tports = [], % #titem + def_proc_flags = [], + def_port_flags = [], output = [], tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec} match_specs = []}). % [ #match_spec{} ] --record(tpid, {pid, opts}). +-record(titem, {id, opts}). start_link(Notebook, ParentPid) -> wx_object:start_link(?MODULE, [Notebook, ParentPid], []). @@ -76,6 +84,9 @@ start_link(Notebook, ParentPid) -> add_processes(Tracer, Pids) when is_list(Pids) -> wx_object:cast(Tracer, {add_processes, Pids}). +add_ports(Tracer, Ports) when is_list(Ports) -> + wx_object:cast(Tracer, {add_ports, Ports}). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Notebook, ParentPid]) -> @@ -87,11 +98,13 @@ create_window(Notebook, ParentPid) -> Sizer = wxBoxSizer:new(?wxVERTICAL), Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, {style, ?SASH_STYLE}]), - {NodeProcView, NodeView, ProcessView} = create_process_view(Splitter), + {NodeProcView, NodeView, ProcessView, PortView} = + create_proc_port_view(Splitter), {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter), wxSplitterWindow:setSashGravity(Splitter, 0.5), wxSplitterWindow:setMinimumPaneSize(Splitter,50), - wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView), + wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView, + [{sashPosition,368}]), wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]), %% Buttons Buttons = wxBoxSizer:new(?wxHORIZONTAL), @@ -99,7 +112,8 @@ create_window(Notebook, ParentPid) -> wxSizer:add(Buttons, ToggleButton, [{flag, ?wxALIGN_CENTER_VERTICAL}]), wxSizer:addSpacer(Buttons, 15), wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NODES, [{label, "Add Nodes"}])), - wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW, [{label, "Add 'new' Process"}])), + wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PROCS, [{label, "Add 'new' Processes"}])), + wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PORTS, [{label, "Add 'new' Ports"}])), wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}])), wxMenu:connect(Panel, command_togglebutton_clicked, [{skip, true}]), wxMenu:connect(Panel, command_button_clicked, [{skip, true}]), @@ -107,7 +121,8 @@ create_window(Notebook, ParentPid) -> {border, 5}, {proportion,0}]), wxWindow:setSizer(Panel, Sizer), {Panel, #state{parent=ParentPid, panel=Panel, - n_view=NodeView, p_view=ProcessView, m_view=ModView, f_view=FuncView, + n_view=NodeView, proc_view=ProcessView, port_view=PortView, + m_view=ModView, f_view=FuncView, toggle_button = ToggleButton, match_specs=default_matchspecs()}}. @@ -130,13 +145,15 @@ get_default_matchspecs('receive') -> [{"Skeleton", [{['$1','$2','$3'], [], [true]}], "fun([Node,Pid,Msg]) -> true end"}]. -create_process_view(Parent) -> +create_proc_port_view(Parent) -> Panel = wxPanel:new(Parent), MainSz = wxBoxSizer:new(?wxHORIZONTAL), Style = ?wxLC_REPORT bor ?wxLC_HRULES, Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]), Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]), - Procs = wxListCtrl:new(Splitter, [{winid, ?PROC_WIN}, {style, Style}]), + ProcsPortsSplitter = wxSplitterWindow:new(Splitter, [{style, ?SASH_STYLE}]), + Procs = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PROC_WIN},{style,Style}]), + Ports = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PORT_WIN},{style,Style}]), Li = wxListItem:new(), wxListItem:setText(Li, "Nodes"), wxListCtrl:insertColumn(Nodes, 0, Li), @@ -148,24 +165,44 @@ create_process_view(Parent) -> wxListCtrl:setColumnWidth(Procs, Col, DefSize), Col + 1 end, - ListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120}, - {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], - lists:foldl(AddProc, 0, ListItems), + ProcListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120}, + {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], + lists:foldl(AddProc, 0, ProcListItems), + + AddPort = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(Ports, Col, Li), + wxListCtrl:setColumnWidth(Ports, Col, DefSize), + Col + 1 + end, + PortListItems = [{"Port Id", ?wxLIST_FORMAT_CENTER, 120}, + {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], + lists:foldl(AddPort, 0, PortListItems), + wxListItem:destroy(Li), wxSplitterWindow:setSashGravity(Splitter, 0.0), wxSplitterWindow:setMinimumPaneSize(Splitter,50), - wxSplitterWindow:splitVertically(Splitter, Nodes, Procs, [{sashPosition, 155}]), + wxSplitterWindow:splitVertically(Splitter, Nodes, ProcsPortsSplitter, + [{sashPosition, 155}]), wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSplitterWindow:setSashGravity(ProcsPortsSplitter, 0.5), + wxSplitterWindow:setMinimumPaneSize(ProcsPortsSplitter,50), + wxSplitterWindow:splitHorizontally(ProcsPortsSplitter, Procs, Ports, + [{sashPosition, 182}]), + wxListCtrl:connect(Procs, command_list_item_right_click), + wxListCtrl:connect(Ports, command_list_item_right_click), wxListCtrl:connect(Nodes, command_list_item_right_click), wxListCtrl:connect(Procs, size, [{skip, true}]), + wxListCtrl:connect(Ports, size, [{skip, true}]), wxListCtrl:connect(Nodes, size, [{skip, true}]), wxPanel:setSizer(Panel, MainSz), wxWindow:setFocus(Procs), - {Panel, Nodes, Procs}. + {Panel, Nodes, Procs, Ports}. create_matchspec_view(Parent) -> Panel = wxPanel:new(Parent), @@ -205,7 +242,8 @@ create_menues(Parent) -> {"Options", [#create_menu{id = ?TRACE_OUTPUT, text = "Output"}, #create_menu{id = ?TRACE_DEFMS, text = "Default Match Specifications for Functions"}, - #create_menu{id = ?TRACE_DEFPS, text = "Default Process Options"}]} + #create_menu{id = ?DEF_PROC_OPTS, text = "Default Process Options"}, + #create_menu{id = ?DEF_PORT_OPTS, text = "Default Port Options"}]} ], observer_wx:create_menus(Parent, Menus). @@ -218,11 +256,19 @@ handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> end, {noreply, State}; -handle_event(#wx{id=?ADD_NEW}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> +handle_event(#wx{id=?ADD_NEW_PROCS}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) -> try Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), - Process = #tpid{pid=new, opts=Opts}, - {noreply, do_add_processes([Process], State#state{def_trace_opts=Opts})} + Process = #titem{id=new_processes, opts=Opts}, + {noreply, do_add_processes([Process], State#state{def_proc_flags=Opts})} + catch cancel -> {noreply, State} + end; + +handle_event(#wx{id=?ADD_NEW_PORTS}, State = #state{panel=Parent, def_port_flags=TraceOpts}) -> + try + Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts), + Port = #titem{id=new_ports, opts=Opts}, + {noreply, do_add_ports([Port], State#state{def_port_flags=Opts})} catch cancel -> {noreply, State} end; @@ -249,22 +295,23 @@ handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, command #state{panel = Panel, nodes = Nodes, tpids = TProcs, + tports = TPorts, tpatterns = TPs0, toggle_button = ToggleBtn, output = Opts } = State) -> try TPs = dict:to_list(TPs0), - (TProcs == []) andalso throw({error, "No processes traced"}), + (TProcs == []) andalso (TPorts == []) andalso throw({error, "No processes or ports traced"}), (Nodes == []) andalso throw({error, "No nodes traced"}), - HaveCallTrace = fun(#tpid{opts=Os}) -> lists:member(functions,Os) end, + HaveCallTrace = fun(#titem{opts=Os}) -> lists:member(functions,Os) end, WStr = "Call trace actived but no trace patterns used", (TPs == []) andalso lists:any(HaveCallTrace, TProcs) andalso observer_wx:create_txt_dialog(Panel, WStr, "Warning", ?wxICON_WARNING), {TTB, LogWin} = ttb_output_args(Panel, Opts), {ok, _} = ttb:tracer(Nodes, TTB), - setup_ttb(TPs, TProcs), + setup_ttb(TPs, TProcs, TPorts), wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), {noreply, State#state{logwin=LogWin}} catch {error, Msg} -> @@ -314,7 +361,8 @@ handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) -> handle_event(#wx{id = ?SAVE_TRACEOPTS}, #state{panel = Panel, - def_trace_opts = TraceOpts, + def_proc_flags = ProcFlags, + def_port_flags = PortFlags, match_specs = MatchSpecs, tpatterns = TracePatterns, output = Output @@ -324,7 +372,7 @@ handle_event(#wx{id = ?SAVE_TRACEOPTS}, ?wxID_OK -> Path = wxFileDialog:getPath(Dialog), write_file(Panel, Path, - TraceOpts, MatchSpecs, Output, + ProcFlags, PortFlags, MatchSpecs, Output, dict:to_list(TracePatterns) ); _ -> @@ -351,6 +399,9 @@ handle_event(#wx{id=Type, event=#wxList{type=command_list_item_right_click}}, ?PROC_WIN -> [{?EDIT_PROCS, "Edit process options"}, {?REMOVE_PROCS, "Remove processes"}]; + ?PORT_WIN -> + [{?EDIT_PORTS, "Edit port options"}, + {?REMOVE_PORTS, "Remove ports"}]; ?FUNCS_WIN -> [{?EDIT_FUNCS_MS, "Edit matchspecs"}, {?REMOVE_FUNCS_MS, "Remove trace patterns"}]; @@ -364,26 +415,50 @@ handle_event(#wx{id=Type, event=#wxList{type=command_list_item_right_click}}, wxMenu:destroy(Menu), {noreply, State}; -handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, p_view=Ps} = State) -> +handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Procs} = State) -> try - [#tpid{opts=DefOpts}|_] = Selected = get_selected_items(Ps, Tpids), + [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Procs, Tpids), Opts = observer_traceoptions_wx:process_trace(Panel, DefOpts), - Changed = [Tpid#tpid{opts=Opts} || Tpid <- Selected], - {noreply, do_add_processes(Changed, State#state{def_trace_opts=Opts})} + Changed = [Tpid#titem{opts=Opts} || Tpid <- Selected], + {noreply, do_add_processes(Changed, State#state{def_proc_flags=Opts})} catch _:_ -> {noreply, State} end; -handle_event(#wx{id=?REMOVE_PROCS}, #state{tpids=Tpids, p_view=LCtrl} = State) -> +handle_event(#wx{id=?REMOVE_PROCS}, #state{tpids=Tpids, proc_view=LCtrl} = State) -> Selected = get_selected_items(LCtrl, Tpids), Pids = Tpids -- Selected, - update_process_view(Pids, LCtrl), + update_p_view(Pids, LCtrl), {noreply, State#state{tpids=Pids}}; -handle_event(#wx{id=?TRACE_DEFPS}, #state{panel=Panel, def_trace_opts=PO} = State) -> +handle_event(#wx{id=?EDIT_PORTS}, #state{panel=Panel, tports=Tports, port_view=Ports} = State) -> + try + [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Ports, Tports), + Opts = observer_traceoptions_wx:port_trace(Panel, DefOpts), + Changed = [Tport#titem{opts=Opts} || Tport <- Selected], + {noreply, do_add_ports(Changed, State#state{def_port_flags=Opts})} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_PORTS}, #state{tports=Tports, port_view=LCtrl} = State) -> + Selected = get_selected_items(LCtrl, Tports), + Ports = Tports -- Selected, + update_p_view(Ports, LCtrl), + {noreply, State#state{tports=Ports}}; + +handle_event(#wx{id=?DEF_PROC_OPTS}, #state{panel=Panel, def_proc_flags=PO} = State) -> try Opts = observer_traceoptions_wx:process_trace(Panel, PO), - {noreply, State#state{def_trace_opts=Opts}} + {noreply, State#state{def_proc_flags=Opts}} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?DEF_PORT_OPTS}, #state{panel=Panel, def_port_flags=PO} = State) -> + try + Opts = observer_traceoptions_wx:port_trace(Panel, PO), + {noreply, State#state{def_port_flags=Opts}} catch _:_ -> {noreply, State} end; @@ -401,24 +476,34 @@ handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs, match_specs=Mss } = State) -> try - [Module] = get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))), - Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)), - Key = case Module of - 'Events' -> - SelectedEvents = [Event || #tpattern{fa=Event} <- Selected], - E1 = hd(SelectedEvents), - case lists:all(fun(E) when E==E1 -> true; (_) -> false end, - SelectedEvents) of - true -> E1; - false -> throw({error,"Can not set match specs for multiple event types"}) - end; - _ -> funcs - end, - Ms = observer_traceoptions_wx:select_matchspec(self(), Panel, Mss, Key), - Changed = [TP#tpattern{ms=Ms} || TP <- Selected], - {noreply, do_add_patterns({Module, Changed}, State)} + case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of + [] -> + throw({error,"No module selected"}); + [Module] -> + Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)), + Key = case Module of + 'Events' -> + SelectedEvents = + [Event || #tpattern{fa=Event} <- Selected], + E1 = hd(SelectedEvents), + case lists:all(fun(E) when E==E1 -> true; + (_) -> false + end, + SelectedEvents) of + true -> E1; + false -> throw({error,"Can not set match specs for multiple event types"}) + end; + _ -> funcs + end, + Ms = observer_traceoptions_wx:select_matchspec(self(), Panel, + Mss, Key), + Changed = [TP#tpattern{ms=Ms} || TP <- Selected], + {noreply, do_add_patterns({Module, Changed}, State)} + end catch {error, Msg} -> observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR), + {noreply, State}; + cancel -> {noreply, State} end; @@ -482,11 +567,20 @@ handle_call(Msg, From, _State) -> error({unhandled_call, Msg, From}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> +handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) -> try Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), - POpts = [#tpid{pid=Pid, opts=Opts} || Pid <- Pids], - S = do_add_processes(POpts, State#state{def_trace_opts=Opts}), + POpts = [#titem{id=Pid, opts=Opts} || Pid <- Pids], + S = do_add_processes(POpts, State#state{def_proc_flags=Opts}), + {noreply, S} + catch cancel -> + {noreply, State} + end; +handle_cast({add_ports, Ports}, State = #state{panel=Parent, def_port_flags=TraceOpts}) -> + try + Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts), + POpts = [#titem{id=Id, opts=Opts} || Id <- Ports], + S = do_add_ports(POpts, State#state{def_port_flags=Opts}), {noreply, S} catch cancel -> {noreply, State} @@ -534,13 +628,25 @@ do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_vi State#state{tpatterns=TPs} end. -do_add_processes(POpts, S0=#state{n_view=Nview, p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> - case merge_pids(POpts, OldPids) of - {OldPids, [], []} -> - S0; - {Pids, New, _Changed} -> - update_process_view(Pids, LCtrl), - Ns1 = lists:usort([node(Pid) || #tpid{pid=Pid} <- New, is_pid(Pid)]), +do_add_processes(POpts, S0=#state{n_view=Nview, proc_view=LCtrl, tpids=OldPids, nodes=OldNodes}) -> + CheckFun = fun(Pid) -> is_pid(Pid) end, + {Pids, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl, + OldPids, OldNodes, CheckFun), + S0#state{tpids=Pids, nodes=Nodes}. + +do_add_ports(POpts, S0=#state{n_view=Nview, port_view=LCtrl, tports=OldPorts, nodes=OldNodes}) -> + CheckFun = fun(Port) -> is_port(Port) end, + {Ports, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl, + OldPorts, OldNodes, CheckFun), + S0#state{tports=Ports, nodes=Nodes}. + +do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) -> + case merge_trace_items(POpts, OldPs) of + {OldPs, [], []} -> + {OldPs,Ns0}; + {Ps, New, _Changed} -> + update_p_view(Ps, LCtrl), + Ns1 = lists:usort([node(Id) || #titem{id=Id} <- New, Check(Id)]), Nodes = case ordsets:subtract(Ns1, Ns0) of [] -> Ns0; %% No new Nodes NewNs -> @@ -549,20 +655,21 @@ do_add_processes(POpts, S0=#state{n_view=Nview, p_view=LCtrl, tpids=OldPids, nod update_nodes_view(All, Nview), All end, - S0#state{tpids=Pids, nodes=Nodes} + {Ps, Nodes} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -update_process_view(Pids, LCtrl) -> +update_p_view(PidsOrPorts, LCtrl) -> + %% pid- or port-view wxListCtrl:deleteAllItems(LCtrl), - wx:foldl(fun(#tpid{pid=Pid, opts=Opts}, Row) -> + wx:foldl(fun(#titem{id=Id, opts=Opts}, Row) -> _Item = wxListCtrl:insertItem(LCtrl, Row, ""), ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), - wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Pid)), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Id)), wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)), Row+1 - end, 0, Pids). + end, 0, PidsOrPorts). update_nodes_view(Nodes, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), @@ -604,20 +711,24 @@ update_functions_view(Funcs, LCtrl) -> end, 0, Funcs). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -merge_pids([N1=#tpid{pid=new}|Ns], [N2=#tpid{pid=new}|Old]) -> - {Pids, New, Changed} = merge_pids_1(Ns,Old), - {[N1|Pids], New, [{N2,N2}|Changed]}; -merge_pids([N1=#tpid{pid=new}|Ns], Old) -> - {Pids, New, Changed} = merge_pids_1(Ns,Old), - {[N1|Pids], [N1|New], Changed}; -merge_pids(Ns, [N2=#tpid{pid=new}|Old]) -> - {Pids, New, Changed} = merge_pids_1(Ns,Old), - {[N2|Pids], New, Changed}; -merge_pids(New, Old) -> - merge_pids_1(New, Old). - -merge_pids_1(New, Old) -> - merge(lists:sort(New), Old, #tpid.pid, [], [], []). +%% Trace items are processes and ports +merge_trace_items([N1=#titem{id=NewP}|Ns], [N2=#titem{id=NewP}|Old]) + when NewP==new_processes; NewP==new_ports -> + {Ids, New, Changed} = merge_trace_items_1(Ns,Old), + {[N1|Ids], New, [{N2,N2}|Changed]}; +merge_trace_items([N1=#titem{id=NewP}|Ns], Old) + when NewP==new_processes; NewP==new_ports -> + {Ids, New, Changed} = merge_trace_items_1(Ns,Old), + {[N1|Ids], [N1|New], Changed}; +merge_trace_items(Ns, [N2=#titem{id=NewP}|Old]) + when NewP==new_processes; NewP==new_ports -> + {Ids, New, Changed} = merge_trace_items_1(Ns,Old), + {[N2|Ids], New, Changed}; +merge_trace_items(New, Old) -> + merge_trace_items_1(New, Old). + +merge_trace_items_1(New, Old) -> + merge(lists:sort(New), Old, #titem.id, [], [], []). merge_patterns(New, Old) -> merge(lists:sort(New), Old, #tpattern.fa, [], [], []). @@ -707,10 +818,12 @@ create_logwindow(Parent, true) -> wxFrame:show(LogWin), {LogWin, Text}. -setup_ttb(TPs, TPids) -> +setup_ttb(TPs, TPids, TPorts) -> _R1 = [setup_tps(FTP, []) || {_, FTP} <- TPs], - _R2 = [ttb:p(Pid, dbg_flags(Flags)) || #tpid{pid=Pid, opts=Flags} <- TPids], - [#tpid{pid=_Pid, opts=_Flags}|_] = TPids, + _R2 = [ttb:p(Pid, dbg_flags(proc,Flags)) || + #titem{id=Pid, opts=Flags} <- TPids], + _R3 = [ttb:p(Port, dbg_flags(port,Flags)) || + #titem{id=Port, opts=Flags} <- TPorts], ok. %% Sigh order is important @@ -731,17 +844,18 @@ setup_tp(#tpattern{m='Events',fa=Event, ms=#match_spec{term=Ms}}) -> setup_tp(#tpattern{m=M,fa={F,A}, ms=#match_spec{term=Ms}}) -> ttb:tpl(M,F,A,Ms). -dbg_flags(Flags) -> - [dbg_flag(Flag) || Flag <- Flags]. +dbg_flags(Type,Flags) -> + [dbg_flag(Type,Flag) || Flag <- Flags]. -dbg_flag(send) -> s; -dbg_flag('receive') -> r; -dbg_flag(functions) -> c; -dbg_flag(on_spawn) -> sos; -dbg_flag(on_link) -> sol; -dbg_flag(on_first_spawn) -> sofs; -dbg_flag(on_first_link) -> sofl; -dbg_flag(events) -> p. +dbg_flag(_,send) -> s; +dbg_flag(_,'receive') -> r; +dbg_flag(proc,functions) -> c; +dbg_flag(proc,on_spawn) -> sos; +dbg_flag(proc,on_link) -> sol; +dbg_flag(proc,on_first_spawn) -> sofs; +dbg_flag(proc,on_first_link) -> sofl; +dbg_flag(proc,events) -> p; +dbg_flag(port,events) -> ports. textformat(Trace) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 -> format_trace(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace)); @@ -817,23 +931,28 @@ ftup(Trace, Index, Size) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -write_file(Frame, Filename, TraceOps, MatchSpecs, Output, TPs) -> - FormatMS = fun(#match_spec{name=Id, term=T, func=F}) -> - io_lib:format("[{name,\"~s\"}, {term, ~w}, {func, \"~s\"}]", - [Id, T, F]) - end, - FormatTP = fun({Module, FTPs}) -> - List = format_ftp(FTPs, FormatMS), - io_lib:format("{tp, ~w, [~s]}.~n",[Module, List]) +write_file(Frame, Filename, ProcFlags, PortFlags, MatchSpecs, Output, TPs) -> + MSToList = fun(#match_spec{name=Id, term=T, func=F}) -> + [{name,Id},{term,T},{func,F}] end, + MSTermList = [{ms,Key,[MSToList(MS) || MS <- MSs]} || + {Key,MSs} <- MatchSpecs], + TPToTuple = fun(#tpattern{fa={F,A}, ms=Ms}) -> + {F,A,MSToList(Ms)} + end, + ModuleTermList = [{tp, Module, [TPToTuple(FTP) || FTP <- FTPs]} || + {Module,FTPs} <- TPs], + Str = ["%%%\n%%% This file is generated by Observer\n", "%%%\n%%% DO NOT EDIT!\n%%%\n", - [["{ms, ", FormatMS(Ms), "}.\n"] || Ms <- MatchSpecs], - "{traceopts, ", io_lib:format("~w",[TraceOps]) ,"}.\n", - "{output, ", io_lib:format("~w",[Output]) ,"}.\n", - [FormatTP(TP) || TP <- TPs] + [io_lib:format("~p.~n",[MSTerm]) || MSTerm <- MSTermList], + io_lib:format("~p.~n",[{procflags,ProcFlags}]), + io_lib:format("~p.~n",[{portflags,PortFlags}]), + io_lib:format("~p.~n",[{output,Output}]), + [io_lib:format("~p.~n",[ModuleTerm]) || ModuleTerm <- ModuleTermList] ], + case file:write_file(Filename, list_to_binary(Str)) of ok -> success; @@ -842,38 +961,66 @@ write_file(Frame, Filename, TraceOps, MatchSpecs, Output, TPs) -> observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR) end. -format_ftp([#tpattern{fa={F,A}, ms=Ms}], FormatMS) -> - io_lib:format("{~w, ~w, ~s}", [F,A,FormatMS(Ms)]); -format_ftp([#tpattern{fa={F,A}, ms=Ms}|Rest], FormatMS) -> - [io_lib:format("{~w, ~w, ~s},~n ", [F,A,FormatMS(Ms)]), - format_ftp(Rest, FormatMS)]. - -read_settings(Filename, #state{match_specs=Ms0, def_trace_opts=TO0} = State) -> +read_settings(Filename, #state{match_specs=Ms0, def_proc_flags=ProcFs0, def_port_flags=PortFs0} = State) -> case file:consult(Filename) of {ok, Terms} -> - Ms = lists:usort(Ms0 ++ [parse_ms(MsList) || {ms, MsList} <- Terms]), - TOs = lists:usort(TO0 ++ proplists:get_value(traceopts, Terms, [])), + Ms = parse_ms(Terms, Ms0), + ProcFs1 = proplists:get_value(procflags, Terms, []) ++ + proplists:get_value(traceopts, Terms, []), % for backwards comp. + ProcFs = lists:usort(ProcFs0 ++ ProcFs1), + PortFs = lists:usort(PortFs0 ++ + proplists:get_value(portflags, Terms, [])), Out = proplists:get_value(output, Terms, []), lists:foldl(fun parse_tp/2, - State#state{match_specs=Ms, def_trace_opts=TOs, output=Out}, + State#state{match_specs=Ms, def_proc_flags=ProcFs, + def_port_flags=PortFs, output=Out}, Terms); {error, _} -> - observer_wx:create_txt_dialog(State#state.panel, "Could not load settings", + observer_wx:create_txt_dialog(State#state.panel, + "Could not load settings", "Error", ?wxICON_ERROR), State end. -parse_ms(Opts) -> - Name = proplists:get_value(name, Opts, "TracePattern"), - Term = proplists:get_value(term, Opts, [{'_',[],[ok]}]), - FunStr = proplists:get_value(term, Opts, "fun(_) -> ok end"), - make_ms(Name, Term, FunStr). +parse_ms(Terms, OldMSs) -> + MSs = + case [{Key,[make_ms(MS) || MS <- MSs]} || {ms,Key,MSs} <- Terms] of + [] -> + case [make_ms(MS) || {ms,MS} <- Terms] of + [] -> + []; + FuncMSs -> % for backwards compatibility + [{funcs,FuncMSs}] + end; + KeyMSs -> + KeyMSs + end, + parse_ms_1(MSs, dict:from_list(OldMSs)). + +parse_ms_1([{Key,MSs} | T], Dict) -> + parse_ms_1(T, dict:append_list(Key,MSs,Dict)); +parse_ms_1([],Dict) -> + [{Key,rm_dups(MSs,[])} || {Key,MSs} <- dict:to_list(Dict)]. + +rm_dups([H|T],Acc) -> + case lists:member(H,Acc) of + true -> + rm_dups(T,Acc); + false -> + rm_dups(T,[H|Acc]) + end; +rm_dups([],Acc) -> + lists:reverse(Acc). + +make_ms(MS) -> + [{func,FunStr},{name,Name},{term,Term}] = lists:keysort(1,MS), + make_ms(Name,Term,FunStr). make_ms(Name, Term, FunStr) -> #match_spec{name=Name, term=Term, str=io_lib:format("~w", Term), func = FunStr}. parse_tp({tp, Mod, FAs}, State) -> - Patterns = [#tpattern{m=Mod,fa={F,A}, ms=parse_ms(List)} || + Patterns = [#tpattern{m=Mod,fa={F,A}, ms=make_ms(List)} || {F,A,List} <- FAs], do_add_patterns({Mod, Patterns}, State); parse_tp(_, State) -> diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index 8a6d1403a8..e623f90df3 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -22,7 +22,7 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). --export([process_trace/2, trace_pattern/4, select_nodes/2, +-export([process_trace/2, port_trace/2, trace_pattern/4, select_nodes/2, output/2, select_matchspec/4]). process_trace(Parent, Default) -> @@ -98,6 +98,42 @@ process_trace(Parent, Default) -> throw(cancel) end. +port_trace(Parent, Default) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Port Options", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + MainSz = wxBoxSizer:new(?wxVERTICAL), + OptsSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Tracing options"}]), + + SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []), + check_box(SendBox, lists:member(send, Default)), + RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []), + check_box(RecBox, lists:member('receive', Default)), + EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace port events", []), + check_box(EventBox, lists:member(events, Default)), + + [wxSizer:add(OptsSz, CheckBox, []) || CheckBox <- [SendBox,RecBox,EventBox]], + wxSizer:add(OptsSz, 150, -1), + + wxPanel:setSizer(Panel, OptsSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + wxWindow:setSizerAndFit(Dialog, MainSz), + wxSizer:setSizeHints(MainSz, Dialog), + + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + All = [{SendBox, send}, {RecBox, 'receive'}, + {EventBox, events}], + Opts = [Id || {Tick, Id} <- All, wxCheckBox:getValue(Tick)], + wxDialog:destroy(Dialog), + lists:reverse(Opts); + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. + trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> try {Module,MFAs,MatchSpec} = -- cgit v1.2.3 From 6dfc55407493278e9875814df054de4df51e7527 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 28 Apr 2016 14:32:25 +0200 Subject: [observer] Add menu option to set default MS for send/receive --- lib/observer/src/observer_trace_wx.erl | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 9f4a54c304..f5211c8a31 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -35,9 +35,11 @@ -define(ADD_NEW_PORTS, 309). -define(ADD_TP, 310). -define(TRACE_OUTPUT, 311). --define(TRACE_DEFMS, 312). --define(DEF_PROC_OPTS, 313). --define(DEF_PORT_OPTS, 314). +-define(DEF_MS_FUNCS, 312). +-define(DEF_MS_SEND, 313). +-define(DEF_MS_RECV, 314). +-define(DEF_PROC_OPTS, 315). +-define(DEF_PORT_OPTS, 316). -define(NODES_WIN, 330). -define(ADD_NODES, 331). @@ -241,7 +243,9 @@ create_menues(Parent) -> #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}]}, {"Options", [#create_menu{id = ?TRACE_OUTPUT, text = "Output"}, - #create_menu{id = ?TRACE_DEFMS, text = "Default Match Specifications for Functions"}, + #create_menu{id = ?DEF_MS_FUNCS, text = "Default Match Specifications for Functions"}, + #create_menu{id = ?DEF_MS_SEND, text = "Default Match Specifications for 'send'"}, + #create_menu{id = ?DEF_MS_RECV, text = "Default Match Specifications for 'receive'"}, #create_menu{id = ?DEF_PROC_OPTS, text = "Default Process Options"}, #create_menu{id = ?DEF_PORT_OPTS, text = "Default Port Options"}]} ], @@ -463,7 +467,7 @@ handle_event(#wx{id=?DEF_PORT_OPTS}, #state{panel=Panel, def_port_flags=PO} = St {noreply, State} end; -handle_event(#wx{id=?TRACE_DEFMS}, #state{panel=Panel, match_specs=Ms} = State) -> +handle_event(#wx{id=?DEF_MS_FUNCS}, #state{panel=Panel, match_specs=Ms} = State) -> try %% Return selected MS and sends new MS's to us observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, funcs) catch _:_ -> @@ -471,6 +475,22 @@ handle_event(#wx{id=?TRACE_DEFMS}, #state{panel=Panel, match_specs=Ms} = State) end, {noreply, State}; +handle_event(#wx{id=?DEF_MS_SEND}, #state{panel=Panel, match_specs=Ms} = State) -> + try %% Return selected MS and sends new MS's to us + observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, send) + catch _:_ -> + cancel + end, + {noreply, State}; + +handle_event(#wx{id=?DEF_MS_RECV}, #state{panel=Panel, match_specs=Ms} = State) -> + try %% Return selected MS and sends new MS's to us + observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, 'receive') + catch _:_ -> + cancel + end, + {noreply, State}; + handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs, f_view=LCtrl, m_view=Mview, match_specs=Mss -- cgit v1.2.3 From 5ddc361d6652fb5aa7a7ac6ad06be95f16f0030d Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 28 Apr 2016 14:46:02 +0200 Subject: [observer] Add right click menu in Table tab --- lib/observer/src/observer_tv_wx.erl | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 6acaf4fc73..59f6443551 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -37,7 +37,8 @@ -define(ID_UNREADABLE, 405). -define(ID_SYSTEM_TABLES, 406). -define(ID_TABLE_INFO, 407). - +-define(ID_SHOW_TABLE, 408). + -record(opt, {type=ets, sys_hidden=true, unread_hidden=true, @@ -49,6 +50,7 @@ { parent, grid, + panel, node=node(), opt=#opt{}, selected, @@ -86,12 +88,13 @@ init([Notebook, Parent]) -> wxListItem:destroy(Li), wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_right_click), wxListCtrl:connect(Grid, command_list_item_selected), wxListCtrl:connect(Grid, command_list_col_click), wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - {Panel, #state{grid=Grid, parent=Parent, timer={false, 10}}}. + {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> @@ -145,6 +148,16 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated, end, {noreply, State}; +handle_event(#wx{event=#wxList{type=command_list_item_right_click}}, + State=#state{panel=Panel}) -> + + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_TABLE_INFO, "Table info"), + wxMenu:append(Menu, ?ID_SHOW_TABLE, "Show Table Content"), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu), + {noreply, State}; + handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, State) -> {noreply, State#state{selected=Index}}; @@ -160,6 +173,22 @@ handle_event(#wx{id=?ID_TABLE_INFO}, {noreply, State} end; +handle_event(#wx{id=?ID_SHOW_TABLE}, + State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) -> + case Sel of + undefined -> + {noreply, State}; + R when is_integer(R) -> + Table = lists:nth(Sel+1, Tabs), + case Table#tab.protection of + private -> + self() ! {error, "Table has 'private' protection and can not be read"}; + _ -> + observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]) + end, + {noreply, State} + end; + handle_event(#wx{id=?ID_REFRESH_INTERVAL}, State = #state{grid=Grid, timer=Timer0}) -> Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), -- cgit v1.2.3 From cd14e151f84926cbdd1243b62ef5478681f56369 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 29 Apr 2016 08:58:27 +0200 Subject: [observer] In Trace tab, show procs/ports for selected node only Earlier, all traced processes and ports would be shown, independent of selected nodes in node view. This is now corrected. --- lib/observer/src/observer_trace_wx.erl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index f5211c8a31..ac8c6cdc4f 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -198,6 +198,7 @@ create_proc_port_view(Parent) -> wxListCtrl:connect(Procs, command_list_item_right_click), wxListCtrl:connect(Ports, command_list_item_right_click), wxListCtrl:connect(Nodes, command_list_item_right_click), + wxListCtrl:connect(Nodes, command_list_item_selected), wxListCtrl:connect(Procs, size, [{skip, true}]), wxListCtrl:connect(Ports, size, [{skip, true}]), wxListCtrl:connect(Nodes, size, [{skip, true}]), @@ -295,6 +296,15 @@ handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected, update_functions_view(dict:fetch(Module, TPs), Fview), {noreply, State}; +handle_event(#wx{id=?NODES_WIN, + event=#wxList{type=command_list_item_selected, itemIndex=Row}}, + State = #state{tpids=Tpids, tports=Tports, n_view=Nview, + proc_view=ProcView, port_view=PortView}) -> + Node = list_to_atom(wxListCtrl:getItemText(Nview, Row)), + update_p_view(Tpids, ProcView, Node), + update_p_view(Tports, PortView, Node), + {noreply, State}; + handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, #state{panel = Panel, nodes = Nodes, @@ -679,6 +689,13 @@ do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) -> end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +update_p_view(PidsOrPorts0, LCtrl, Node) -> + %% Show processes/ports belonging to the given node, + %% and processes/ports traced by name + PidsOrPorts = [P || P <- PidsOrPorts0, + is_atom(P#titem.id) orelse node(P#titem.id)==Node], + update_p_view(PidsOrPorts,LCtrl). + update_p_view(PidsOrPorts, LCtrl) -> %% pid- or port-view wxListCtrl:deleteAllItems(LCtrl), -- cgit v1.2.3 From 7b478eced25af1d3143d232e503723aaf86dfe8a Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 29 Apr 2016 10:02:37 +0200 Subject: [observer] Make right click menu act on the "expected pid" If multiple processes were selcted, "Process Info" and "Kill Process" menu choices would pick the first pid in the selection list, i.e. the last selected process. This is now changed so the process under the mouse pointer is used instead. --- lib/observer/src/observer_pro_wx.erl | 55 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index ca0ddab657..6296f89e7f 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -83,6 +83,7 @@ timer, procinfo_menu_pids=[], sel={[], []}, + right_clicked_pid, holder}). start_link(Notebook, Parent) -> @@ -303,13 +304,14 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL}, Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60), {noreply, State#state{timer=Timer}}; -handle_event(#wx{id=?ID_KILL}, #state{sel={[_|Ids], [ToKill|Pids]}}=State) -> - exit(ToKill, kill), - {noreply, State#state{sel={Ids,Pids}}}; +handle_event(#wx{id=?ID_KILL}, #state{right_clicked_pid=Pid, sel=Sel0}=State) -> + exit(Pid, kill), + Sel = rm_selected(Pid,Sel0), + {noreply, State#state{sel=Sel}}; handle_event(#wx{id=?ID_PROC}, - #state{panel=Panel, sel={_, [Pid|_]},procinfo_menu_pids=Opened}=State) -> + #state{panel=Panel, right_clicked_pid=Pid, procinfo_menu_pids=Opened}=State) -> Opened2 = start_procinfo(Pid, Panel, Opened), {noreply, State#state{procinfo_menu_pids=Opened2}}; @@ -347,20 +349,26 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click, itemIndex=Row}}, #state{panel=Panel, holder=Holder}=State) -> - case call(Holder, {get_row, self(), Row, pid}) of - {error, undefined} -> - undefined; - {ok, _} -> - Menu = wxMenu:new(), - wxMenu:append(Menu, ?ID_PROC, "Process info"), - wxMenu:append(Menu, ?ID_TRACE_PIDS, "Trace processes", [{help, ?TRACE_PIDS_STR}]), - wxMenu:append(Menu, ?ID_TRACE_NAMES, "Trace named processes (all nodes)", - [{help, ?TRACE_NAMES_STR}]), - wxMenu:append(Menu, ?ID_KILL, "Kill Process"), - wxWindow:popupMenu(Panel, Menu), - wxMenu:destroy(Menu) - end, - {noreply, State}; + Pid = + case call(Holder, {get_row, self(), Row, pid}) of + {error, undefined} -> + undefined; + {ok, P} -> + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_PROC, + "Process info for " ++ pid_to_list(P)), + wxMenu:append(Menu, ?ID_TRACE_PIDS, + "Trace selected processes", + [{help, ?TRACE_PIDS_STR}]), + wxMenu:append(Menu, ?ID_TRACE_NAMES, + "Trace selected processes by name (all nodes)", + [{help, ?TRACE_NAMES_STR}]), + wxMenu:append(Menu, ?ID_KILL, "Kill process " ++ pid_to_list(P)), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu), + P + end, + {noreply, State#state{right_clicked_pid=Pid}}; handle_event(#wx{event=#wxList{type=command_list_item_focused, itemIndex=Row}}, @@ -432,6 +440,17 @@ set_focus([Old|_], [New|_], Grid) -> wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED), wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED). +rm_selected(Pid, {Ids, Pids}) -> + rm_selected(Pid, Ids, Pids, [], []). + +rm_selected(Pid, [_Id|Ids], [Pid|Pids], AccIds, AccPids) -> + {lists:reverse(AccIds)++Ids,lists:reverse(AccPids)++Pids}; +rm_selected(Pid, [Id|Ids], [OtherPid|Pids], AccIds, AccPids) -> + rm_selected(Pid, Ids, Pids, [Id|AccIds], [OtherPid|AccPids]); +rm_selected(_, [], [], AccIds, AccPids) -> + {lists:reverse(AccIds), lists:reverse(AccPids)}. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_table_holder(Parent, Attrs) -> -- cgit v1.2.3 From e4c25c20f548c068057547ad2095a4c65afc0d86 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 29 Apr 2016 12:00:23 +0200 Subject: [observer] Allow multiple select in Ports tab This is helpful when selecting ports to be traced. --- lib/observer/src/observer_app_wx.erl | 8 +- lib/observer/src/observer_port_wx.erl | 193 +++++++++++++++++++++------------ lib/observer/src/observer_pro_wx.erl | 6 +- lib/observer/src/observer_trace_wx.erl | 10 +- 4 files changed, 134 insertions(+), 83 deletions(-) diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index cef83037d0..936b2783e2 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -221,21 +221,21 @@ handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}} %%% Trace api handle_event(#wx{id=?ID_TRACE_PID, event=#wxCommand{type=command_menu_selected}}, State = #state{sel={Box,_}}) -> - observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_pid(Box)]), + observer_trace_wx:add_processes([box_to_pid(Box)]), {noreply, State}; handle_event(#wx{id=?ID_TRACE_NAME, event=#wxCommand{type=command_menu_selected}}, State = #state{sel={Box,_}}) -> - observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_reg(Box)]), + observer_trace_wx:add_processes([box_to_reg(Box)]), {noreply, State}; handle_event(#wx{id=?ID_TRACE_TREE_PIDS, event=#wxCommand{type=command_menu_selected}}, State = #state{sel=Sel}) -> Get = fun(Box) -> box_to_pid(Box) end, - observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)), + observer_trace_wx:add_processes(tree_map(Sel, Get)), {noreply, State}; handle_event(#wx{id=?ID_TRACE_TREE_NAMES, event=#wxCommand{type=command_menu_selected}}, State = #state{sel=Sel}) -> Get = fun(Box) -> box_to_reg(Box) end, - observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)), + observer_trace_wx:add_processes(tree_map(Sel, Get)), {noreply, State}; handle_event(Event, _State) -> diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index a09637161f..c8548e176e 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -32,11 +32,12 @@ -define(ID_REFRESH, 301). -define(ID_REFRESH_INTERVAL, 302). -define(ID_PORT_INFO, 303). --define(ID_TRACE_PORTS, 304). --define(ID_TRACE_NAMES, 305). --define(ID_TRACE_NEW, 306). --define(ID_TRACE_ALL, 307). --define(ID_CLOSE_PORT, 308). +-define(ID_PORT_INFO_SELECTED, 304). +-define(ID_TRACE_PORTS, 305). +-define(ID_TRACE_NAMES, 306). +-define(ID_TRACE_NEW, 307). +-define(ID_TRACE_ALL, 308). +-define(ID_CLOSE_PORT, 309). -define(TRACE_PORTS_STR, "Trace selected ports"). -define(TRACE_NAMES_STR, "Trace selected ports, " @@ -64,7 +65,7 @@ panel, node=node(), opt=#opt{}, - selected, + right_clicked_port, ports, timer, open_wins=[] @@ -76,7 +77,7 @@ start_link(Notebook, Parent) -> init([Notebook, Parent]) -> Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxVERTICAL), - Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Style = ?wxLC_REPORT bor ?wxLC_HRULES, Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), @@ -99,7 +100,6 @@ init([Notebook, Parent]) -> wxListCtrl:connect(Grid, command_list_item_right_click), wxListCtrl:connect(Grid, command_list_item_activated), - wxListCtrl:connect(Grid, command_list_item_selected), wxListCtrl:connect(Grid, command_list_col_click), wxListCtrl:connect(Grid, size, [{skip, true}]), @@ -143,76 +143,92 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated, NewOpened = display_port_info(Grid, Port, Opened), {noreply, State#state{open_wins=NewOpened}}; -handle_event(#wx{event=#wxList{type=command_list_item_right_click}}, - State=#state{panel=Panel}) -> - - Menu = wxMenu:new(), - wxMenu:append(Menu, ?ID_PORT_INFO, "Port info"), - wxMenu:append(Menu, ?ID_TRACE_PORTS, "Trace ports", - [{help, ?TRACE_PORTS_STR}]), - wxMenu:append(Menu, ?ID_TRACE_NAMES, "Trace named ports (all nodes)", - [{help, ?TRACE_NAMES_STR}]), - wxMenu:append(Menu, ?ID_CLOSE_PORT, "Close Port"), - wxWindow:popupMenu(Panel, Menu), - wxMenu:destroy(Menu), - {noreply, State}; - -handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, - State) -> - {noreply, State#state{selected=Index}}; +handle_event(#wx{event=#wxList{type=command_list_item_right_click, + itemIndex=Index}}, + State=#state{panel=Panel, ports=Ports}) -> + case Index of + -1 -> + {noreply, State}; + _ -> + Port = lists:nth(Index+1, Ports), + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_PORT_INFO, + "Port info for " ++ erlang:port_to_list(Port#port.id)), + wxMenu:append(Menu, ?ID_TRACE_PORTS, + "Trace selected ports", + [{help, ?TRACE_PORTS_STR}]), + wxMenu:append(Menu, ?ID_TRACE_NAMES, + "Trace selected ports by name (all nodes)", + [{help, ?TRACE_NAMES_STR}]), + wxMenu:append(Menu, ?ID_CLOSE_PORT, + "Close " ++ erlang:port_to_list(Port#port.id)), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu), + {noreply, State#state{right_clicked_port=Port}} + end; handle_event(#wx{id=?ID_PORT_INFO}, - State = #state{grid=Grid, ports=Ports, - selected=Sel, open_wins=Opened}) -> - case Sel of + State = #state{grid=Grid, right_clicked_port=Port, + open_wins=Opened}) -> + case Port of undefined -> {noreply, State}; - R when is_integer(R) -> - Port = lists:nth(Sel+1, Ports), + _ -> NewOpened = display_port_info(Grid, Port, Opened), - {noreply, State#state{open_wins = NewOpened}} + {noreply, State#state{right_clicked_port=undefined, + open_wins=NewOpened}} end; -handle_event(#wx{id=?ID_CLOSE_PORT}, State = #state{selected=Sel, ports=Ports}) -> - case Sel of - undefined -> +handle_event(#wx{id=?ID_PORT_INFO_SELECTED}, + State = #state{grid=Grid, ports=Ports, open_wins=Opened}) -> + case get_selected_items(Grid,Ports) of + [] -> + observer_wx:create_txt_dialog(State#state.panel, "No selected ports", + "Port Info", ?wxICON_EXCLAMATION), {noreply, State}; - R when is_integer(R) -> - Port = lists:nth(Sel+1, Ports), - erlang:port_close(Port#port.id), - {noreply, State#state{selected=undefined}} + Selected -> + NewOpened = lists:foldl(fun(P,O) -> display_port_info(Grid, P, O) end, + Opened, Selected), + {noreply, State#state{open_wins = NewOpened}} end; -handle_event(#wx{id=?ID_TRACE_PORTS}, #state{selected=Sel, ports=Ports}=State) -> - case Sel of +handle_event(#wx{id=?ID_CLOSE_PORT}, State = #state{right_clicked_port=Port}) -> + case Port of undefined -> - observer_wx:create_txt_dialog(State#state.panel, "No selected port", - "Tracer", ?wxICON_EXCLAMATION), {noreply, State}; - R when is_integer(R) -> - Port = lists:nth(Sel+1, Ports), - observer_trace_wx:add_ports(observer_wx:get_tracer(), [Port#port.id]), - {noreply, State} - end; + _ -> + erlang:port_close(Port#port.id), + {noreply, State#state{right_clicked_port=undefined}} + end; + +handle_event(#wx{id=?ID_TRACE_PORTS}, #state{grid=Grid, ports=Ports}=State) -> + case get_selected_items(Grid, Ports) of + [] -> + observer_wx:create_txt_dialog(State#state.panel, "No selected ports", + "Tracer", ?wxICON_EXCLAMATION); + Selected -> + SelectedIds = [Port#port.id || Port <- Selected], + observer_trace_wx:add_ports(SelectedIds) + end, + {noreply, State}; -handle_event(#wx{id=?ID_TRACE_NAMES}, #state{selected=Sel, ports=Ports}=State) -> - case Sel of - undefined -> - observer_wx:create_txt_dialog(State#state.panel, "No selected port", - "Tracer", ?wxICON_EXCLAMATION), - {noreply, State}; - R when is_integer(R) -> - Port = lists:nth(Sel+1, Ports), - IdOrReg = case Port#port.name of - ignore -> Port#port.id; - Name -> Name - end, - observer_trace_wx:add_ports(observer_wx:get_tracer(), [IdOrReg]), - {noreply, State} - end; +handle_event(#wx{id=?ID_TRACE_NAMES}, #state{grid=Grid, ports=Ports}=State) -> + case get_selected_items(Grid, Ports) of + [] -> + observer_wx:create_txt_dialog(State#state.panel, "No selected ports", + "Tracer", ?wxICON_EXCLAMATION); + Selected -> + IdsOrRegs = + [case Port#port.name of + undefined -> Port#port.id; + Name -> Name + end || Port <- Selected], + observer_trace_wx:add_ports(IdsOrRegs) + end, + {noreply, State}; handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> - observer_trace_wx:add_ports(observer_wx:get_tracer(), [new_ports]), + observer_trace_wx:add_ports([new_ports]), {noreply, State}; handle_event(#wx{id=?ID_REFRESH_INTERVAL}, @@ -292,11 +308,18 @@ code_change(_, _, State) -> create_menus(Parent) -> MenuEntries = [{"View", - [#create_menu{id = ?ID_PORT_INFO, text = "Port information\tCtrl-I"}, + [#create_menu{id = ?ID_PORT_INFO_SELECTED, + text = "Port info for selected ports\tCtrl-I"}, separator, #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} - ]}], + ]}, + {"Trace", + [#create_menu{id=?ID_TRACE_PORTS, text="Trace selected ports"}, + #create_menu{id=?ID_TRACE_NAMES, text="Trace selected ports by name (all nodes)"}, + #create_menu{id=?ID_TRACE_NEW, text="Trace new ports"} + ]} + ], observer_wx:create_menus(Parent, MenuEntries). get_ports(Node) -> @@ -332,9 +355,9 @@ list_to_portrec(PL) -> id_str = erlang:port_to_list(PortId), slot = proplists:get_value(id, PL), connected = proplists:get_value(connected, PL), - links = proplists:get_value(links, PL, ignore), - name = proplists:get_value(registered_name, PL, ignore), - monitors = proplists:get_value(monitors, PL, ignore), + links = proplists:get_value(links, PL, []), + name = proplists:get_value(registered_name, PL, []), + monitors = proplists:get_value(monitors, PL, []), controls = proplists:get_value(name, PL)}. portrec_to_list(#port{id = Id, @@ -388,9 +411,16 @@ port_info_fields(Port) -> {"Controls", controls}]}, {scroll_boxes, [{"Links",1,{click,links}}, - {"Monitors",1,{click,monitors}}]}], + {"Monitors",1,{click,filter_monitor_info()}}]}], observer_lib:fill_info(Struct, Port). +filter_monitor_info() -> + fun(Data) -> + Ms = proplists:get_value(monitors, Data), + [Pid || {process, Pid} <- Ms] + end. + + handle_error(Foo) -> Str = io_lib:format("ERROR: ~s~n",[Foo]), observer_lib:display_info_dialog(Str). @@ -412,8 +442,7 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> true -> ignore end, - lists:foreach(fun({_, ignore}) -> ignore; - ({Col, Val}) -> + lists:foreach(fun({Col, Val}) -> wxListCtrl:setItem(Grid, Row, Col, observer_lib:to_str(Val)) end, @@ -426,3 +455,25 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> end, lists:foldl(Update, 0, PortInfo), PortInfo. + + +get_selected_items(Grid, Data) -> + get_indecies(get_selected_items(Grid, -1, []), Data). +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. + +get_indecies(Items, Data) -> + get_indecies(Items, 0, Data). +get_indecies([I|Rest], I, [H|T]) -> + [H|get_indecies(Rest, I+1, T)]; +get_indecies(Rest = [_|_], I, [_|T]) -> + get_indecies(Rest, I+1, T); +get_indecies(_, _, _) -> + []. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 6296f89e7f..ee6829b847 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -321,7 +321,7 @@ handle_event(#wx{id=?ID_TRACE_PIDS}, #state{sel={_, Pids}, panel=Panel}=State) observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), {noreply, State}; Pids -> - observer_trace_wx:add_processes(observer_wx:get_tracer(), Pids), + observer_trace_wx:add_processes(Pids), {noreply, State} end; @@ -332,12 +332,12 @@ handle_event(#wx{id=?ID_TRACE_NAMES}, #state{sel={SelIds,_Pids}, holder=Holder, {noreply, State}; _ -> PidsOrReg = call(Holder, {get_name_or_pid, self(), SelIds}), - observer_trace_wx:add_processes(observer_wx:get_tracer(), PidsOrReg), + observer_trace_wx:add_processes(PidsOrReg), {noreply, State} end; handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> - observer_trace_wx:add_processes(observer_wx:get_tracer(), [new_processes]), + observer_trace_wx:add_processes([new_processes]), {noreply, State}; handle_event(#wx{event=#wxSize{size={W,_}}}, diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index ac8c6cdc4f..dfb4857c0b 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -19,7 +19,7 @@ -module(observer_trace_wx). --export([start_link/2, add_processes/2, add_ports/2]). +-export([start_link/2, add_processes/1, add_ports/1]). -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). @@ -83,11 +83,11 @@ start_link(Notebook, ParentPid) -> wx_object:start_link(?MODULE, [Notebook, ParentPid], []). -add_processes(Tracer, Pids) when is_list(Pids) -> - wx_object:cast(Tracer, {add_processes, Pids}). +add_processes(Pids) when is_list(Pids) -> + wx_object:cast(observer_wx:get_tracer(), {add_processes, Pids}). -add_ports(Tracer, Ports) when is_list(Ports) -> - wx_object:cast(Tracer, {add_ports, Ports}). +add_ports(Ports) when is_list(Ports) -> + wx_object:cast(observer_wx:get_tracer(), {add_ports, Ports}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From ddbe6a2d2531abfc7c72b07442a585f2649faedd Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 4 May 2016 14:29:23 +0200 Subject: [observer] Improve appearance in Trace tab * Only allow single selection of modules * Add right click menu to remove module * Allow multiple selection of nodes, and show procs/ports for ALL selected nodes * On right click, only show menu items that make sense for the current content and selection * Add tooltips with help text --- lib/observer/src/observer_port_wx.erl | 2 +- lib/observer/src/observer_trace_wx.erl | 219 ++++++++++++++++++++++++++------- 2 files changed, 175 insertions(+), 46 deletions(-) diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index c8548e176e..3b788642cc 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -220,7 +220,7 @@ handle_event(#wx{id=?ID_TRACE_NAMES}, #state{grid=Grid, ports=Ports}=State) -> Selected -> IdsOrRegs = [case Port#port.name of - undefined -> Port#port.id; + [] -> Port#port.id; Name -> Name end || Port <- Selected], observer_trace_wx:add_ports(IdsOrRegs) diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index dfb4857c0b..5c34961fc1 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -54,6 +54,7 @@ -define(REMOVE_PORTS, 352). -define(MODULES_WIN, 360). +-define(REMOVE_MOD_MS, 361). -define(FUNCS_WIN, 370). -define(EDIT_FUNCS_MS, 371). @@ -63,6 +64,13 @@ -define(LOG_SAVE, 381). -define(LOG_CLEAR, 382). +-define(NO_NODES_HELP,"Right click to add nodes"). +-define(NODES_HELP,"Select nodes to see traced processes and ports"). +-define(NO_P_HELP,"Add items from Processes/Ports tab"). +-define(P_HELP,"Select nodes to see traced processes and ports"). +-define(NO_TP_HELP,"Add trace pattern with button below"). +-define(TP_HELP,"Select module to see trace patterns"). + -record(state, {parent, panel, @@ -203,6 +211,10 @@ create_proc_port_view(Parent) -> wxListCtrl:connect(Ports, size, [{skip, true}]), wxListCtrl:connect(Nodes, size, [{skip, true}]), + wxListCtrl:setToolTip(Nodes, ?NO_NODES_HELP), + wxListCtrl:setToolTip(Procs, ?NO_P_HELP), + wxListCtrl:setToolTip(Ports, ?NO_P_HELP), + wxPanel:setSizer(Panel, MainSz), wxWindow:setFocus(Procs), {Panel, Nodes, Procs, Ports}. @@ -212,7 +224,8 @@ create_matchspec_view(Parent) -> MainSz = wxBoxSizer:new(?wxHORIZONTAL), Style = ?wxLC_REPORT bor ?wxLC_HRULES, Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]), - Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, {style, Style}]), + Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, + {style, Style bor ?wxLC_SINGLE_SEL}]), Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]), Li = wxListItem:new(), @@ -234,7 +247,9 @@ create_matchspec_view(Parent) -> wxListCtrl:connect(Modules, size, [{skip, true}]), wxListCtrl:connect(Funcs, size, [{skip, true}]), wxListCtrl:connect(Modules, command_list_item_selected), + wxListCtrl:connect(Modules, command_list_item_right_click), wxListCtrl:connect(Funcs, command_list_item_right_click), + wxListCtrl:setToolTip(Panel, ?NO_TP_HELP), wxPanel:setSizer(Panel, MainSz), {Panel, Modules, Funcs}. @@ -297,12 +312,12 @@ handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected, {noreply, State}; handle_event(#wx{id=?NODES_WIN, - event=#wxList{type=command_list_item_selected, itemIndex=Row}}, + event=#wxList{type=command_list_item_selected}}, State = #state{tpids=Tpids, tports=Tports, n_view=Nview, - proc_view=ProcView, port_view=PortView}) -> - Node = list_to_atom(wxListCtrl:getItemText(Nview, Row)), - update_p_view(Tpids, ProcView, Node), - update_p_view(Tports, PortView, Node), + proc_view=ProcView, port_view=PortView, nodes=Ns}) -> + Nodes = get_selected_items(Nview, Ns), + update_p_view(Tpids, ProcView, Nodes), + update_p_view(Tports, PortView, Nodes), {noreply, State}; handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, @@ -407,26 +422,86 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) -> wxDialog:destroy(Dialog), {noreply, State2}; -handle_event(#wx{id=Type, event=#wxList{type=command_list_item_right_click}}, - State = #state{panel=Panel}) -> - Menus = case Type of - ?PROC_WIN -> - [{?EDIT_PROCS, "Edit process options"}, - {?REMOVE_PROCS, "Remove processes"}]; - ?PORT_WIN -> - [{?EDIT_PORTS, "Edit port options"}, - {?REMOVE_PORTS, "Remove ports"}]; - ?FUNCS_WIN -> - [{?EDIT_FUNCS_MS, "Edit matchspecs"}, - {?REMOVE_FUNCS_MS, "Remove trace patterns"}]; - ?NODES_WIN -> - [{?ADD_NODES, "Trace other nodes"}, - {?REMOVE_NODES, "Remove nodes"}] - end, - Menu = wxMenu:new(), - [wxMenu:append(Menu,Id,Str) || {Id,Str} <- Menus], - wxWindow:popupMenu(Panel, Menu), - wxMenu:destroy(Menu), +handle_event(#wx{id=?PROC_WIN, event=#wxList{type=command_list_item_right_click}}, + State = #state{panel=Panel, proc_view=LCtrl, tpids=Tpids, + n_view=Nview, nodes=Nodes}) -> + case get_visible_ps(Tpids, Nodes, Nview) of + [] -> + ok; + Visible -> + case get_selected_items(LCtrl, Visible) of + [] -> + ok; + _ -> + create_right_click_menu( + Panel, + [{?EDIT_PROCS, "Edit process options"}, + {?REMOVE_PROCS, "Remove processes"}]) + end + end, + {noreply, State}; + +handle_event(#wx{id=?PORT_WIN, event=#wxList{type=command_list_item_right_click}}, + State = #state{panel=Panel, port_view=LCtrl, tports=Tports, + n_view=Nview, nodes=Nodes}) -> + case get_visible_ps(Tports, Nodes, Nview) of + [] -> + ok; + Visible -> + case get_selected_items(LCtrl, Visible) of + [] -> + ok; + _ -> + create_right_click_menu( + Panel, + [{?EDIT_PORTS, "Edit port options"}, + {?REMOVE_PORTS, "Remove ports"}]) + end + end, + {noreply, State}; + +handle_event(#wx{id=?MODULES_WIN,event=#wxList{type=command_list_item_right_click}}, + State = #state{panel=Panel, m_view=Mview, tpatterns=TPs}) -> + case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of + [] -> + ok; + _ -> + create_right_click_menu( + Panel, + [{?REMOVE_MOD_MS, "Remove trace patterns"}]) + end, + {noreply,State}; + +handle_event(#wx{id=?FUNCS_WIN,event=#wxList{type=command_list_item_right_click}}, + State = #state{panel=Panel, m_view=Mview, f_view=Fview, + tpatterns=TPs}) -> + case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of + [] -> + ok; + [Module] -> + case get_selected_items(Fview, dict:fetch(Module, TPs)) of + [] -> + ok; + _ -> + create_right_click_menu( + Panel, + [{?EDIT_FUNCS_MS, "Edit matchspecs"}, + {?REMOVE_FUNCS_MS, "Remove trace patterns"}]) + end + end, + {noreply,State}; + +handle_event(#wx{id=?NODES_WIN,event=#wxList{type=command_list_item_right_click}}, + State = #state{panel=Panel, n_view=Nview, nodes=Nodes}) -> + Menu = + case get_selected_items(Nview, Nodes) of + [] -> + [{?ADD_NODES, "Add nodes"}]; + _ -> + [{?ADD_NODES, "Add nodes"}, + {?REMOVE_NODES, "Remove nodes"}] + end, + create_right_click_menu(Panel,Menu), {noreply, State}; handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Procs} = State) -> @@ -439,10 +514,12 @@ handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Pro {noreply, State} end; -handle_event(#wx{id=?REMOVE_PROCS}, #state{tpids=Tpids, proc_view=LCtrl} = State) -> +handle_event(#wx{id=?REMOVE_PROCS}, + #state{tpids=Tpids, proc_view=LCtrl, + n_view=Nview, nodes=Nodes} = State) -> Selected = get_selected_items(LCtrl, Tpids), Pids = Tpids -- Selected, - update_p_view(Pids, LCtrl), + update_p_view(Pids, LCtrl, Nodes, Nview), {noreply, State#state{tpids=Pids}}; handle_event(#wx{id=?EDIT_PORTS}, #state{panel=Panel, tports=Tports, port_view=Ports} = State) -> @@ -455,10 +532,12 @@ handle_event(#wx{id=?EDIT_PORTS}, #state{panel=Panel, tports=Tports, port_view=P {noreply, State} end; -handle_event(#wx{id=?REMOVE_PORTS}, #state{tports=Tports, port_view=LCtrl} = State) -> +handle_event(#wx{id=?REMOVE_PORTS}, + #state{tports=Tports, port_view=LCtrl, + n_view=Nview, nodes=Nodes} = State) -> Selected = get_selected_items(LCtrl, Tports), Ports = Tports -- Selected, - update_p_view(Ports, LCtrl), + update_p_view(Ports, LCtrl, Nodes, Nview), {noreply, State#state{tports=Ports}}; handle_event(#wx{id=?DEF_PROC_OPTS}, #state{panel=Panel, def_proc_flags=PO} = State) -> @@ -556,6 +635,16 @@ handle_event(#wx{id=?REMOVE_FUNCS_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_vi {noreply, State#state{tpatterns=TPs}} end; +handle_event(#wx{id=?REMOVE_MOD_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_view=Mview} = State) -> + case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs0))) of + [] -> {noreply, State}; + [Module] -> + update_functions_view([], LCtrl), + TPs = dict:erase(Module, TPs0), + update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), + {noreply, State#state{tpatterns=TPs}} + end; + handle_event(#wx{id=?TRACE_OUTPUT}, #state{panel=Panel, output=Out0} = State) -> try Out = observer_traceoptions_wx:output(Panel, Out0), @@ -675,26 +764,33 @@ do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) -> {OldPs, [], []} -> {OldPs,Ns0}; {Ps, New, _Changed} -> - update_p_view(Ps, LCtrl), Ns1 = lists:usort([node(Id) || #titem{id=Id} <- New, Check(Id)]), Nodes = case ordsets:subtract(Ns1, Ns0) of [] -> Ns0; %% No new Nodes - NewNs -> - %% if dynamicly updates add trace patterns for new nodes - All = ordsets:union(NewNs, Ns0), - update_nodes_view(All, Nview), - All + NewNs -> ordsets:union(NewNs, Ns0) end, + update_nodes_view(Nodes, Nview), + update_p_view(Ps, LCtrl, Nodes, Nview), {Ps, Nodes} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -update_p_view(PidsOrPorts0, LCtrl, Node) -> - %% Show processes/ports belonging to the given node, - %% and processes/ports traced by name - PidsOrPorts = [P || P <- PidsOrPorts0, - is_atom(P#titem.id) orelse node(P#titem.id)==Node], - update_p_view(PidsOrPorts,LCtrl). +get_visible_ps(PidsOrPorts, [Node], _Nview) -> + %% If only one node, treat this as selected + get_visible_ps(PidsOrPorts, [Node]); +get_visible_ps(PidsOrPorts, Nodes, Nview) -> + get_visible_ps(PidsOrPorts, get_selected_items(Nview, Nodes)). + +get_visible_ps(PidsOrPorts, Nodes) -> + %% Show pids/ports belonging to the selected nodes only (+ named pids/ports) + [P || P <- PidsOrPorts, + is_atom(P#titem.id) orelse + lists:member(node(P#titem.id),Nodes)]. + +update_p_view(PidsOrPorts, LCtrl, Nodes, Nview) -> + update_p_view(get_visible_ps(PidsOrPorts, Nodes, Nview), LCtrl). +update_p_view(PidsOrPorts, LCtrl, Nodes) -> + update_p_view(get_visible_ps(PidsOrPorts, Nodes), LCtrl). update_p_view(PidsOrPorts, LCtrl) -> %% pid- or port-view @@ -706,17 +802,37 @@ update_p_view(PidsOrPorts, LCtrl) -> wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Id)), wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)), Row+1 - end, 0, PidsOrPorts). + end, 0, PidsOrPorts), + case PidsOrPorts of + [] -> + wxListCtrl:setToolTip(LCtrl,?NO_P_HELP); + _ -> + wxListCtrl:setToolTip(LCtrl,?P_HELP) + end. update_nodes_view(Nodes, LCtrl) -> + Selected = + case Nodes of + [_] -> Nodes; + _ -> get_selected_items(LCtrl, Nodes) + end, wxListCtrl:deleteAllItems(LCtrl), wx:foldl(fun(Node, Row) -> _Item = wxListCtrl:insertItem(LCtrl, Row, ""), ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Node)), + lists:member(Node,Selected) andalso % keep selection + wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, + ?wxLIST_STATE_SELECTED), Row+1 - end, 0, Nodes). + end, 0, Nodes), + case Nodes of + [] -> + wxListCtrl:setToolTip(LCtrl,?NO_NODES_HELP); + _ -> + wxListCtrl:setToolTip(LCtrl,?NODES_HELP) + end. update_modules_view(Mods, Module, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), @@ -728,7 +844,14 @@ update_modules_view(Mods, Module, LCtrl) -> (Mod =:= Module) andalso wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), Row+1 - end, 0, Mods). + end, 0, Mods), + Parent = wxListCtrl:getParent(LCtrl), + case Mods of + [] -> + wxListCtrl:setToolTip(Parent,?NO_TP_HELP); + _ -> + wxListCtrl:setToolTip(Parent,?TP_HELP) + end. update_functions_view(Funcs, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), @@ -1083,3 +1206,9 @@ get_indecies(Rest = [_|_], I, [_|T]) -> get_indecies(Rest, I+1, T); get_indecies(_, _, _) -> []. + +create_right_click_menu(Panel,Menus) -> + Menu = wxMenu:new(), + [wxMenu:append(Menu,Id,Str) || {Id,Str} <- Menus], + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu). -- cgit v1.2.3 From c17d02df4184e2e1425827b2e469936c686049ed Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 11 May 2016 16:20:03 +0200 Subject: [observer] Add test of new Ports tab --- lib/observer/test/observer_SUITE.erl | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl index 3dc3e4772b..212fd08532 100644 --- a/lib/observer/test/observer_SUITE.erl +++ b/lib/observer/test/observer_SUITE.erl @@ -171,6 +171,7 @@ test_page("Applications" ++ _, _Window) -> test_page("Processes" ++ _, _Window) -> timer:sleep(500), %% Give it time to refresh Active = get_active(), + Active ! refresh_interval, ChangeSort = fun(N) -> FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}}, Active ! FakeEv, @@ -184,7 +185,23 @@ test_page("Processes" ++ _, _Window) -> timer:sleep(1000), %% Give it time to refresh ok; -test_page(_Title = "Table" ++ _, _Window) -> +test_page("Ports" ++ _, _Window) -> + timer:sleep(500), %% Give it time to refresh + Active = get_active(), + Active ! refresh_interval, + ChangeSort = fun(N) -> + FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}}, + Active ! FakeEv, + timer:sleep(200) + end, + [ChangeSort(N) || N <- lists:seq(1,4) ++ [0]], + Activate = #wx{event=#wxList{type=command_list_item_activated, + itemIndex=2}}, + Active ! Activate, + timer:sleep(1000), %% Give it time to refresh + ok; + +test_page("Table" ++ _, _Window) -> Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)], Table = lists:nth(3, Tables), ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]), @@ -208,6 +225,13 @@ test_page(_Title = "Table" ++ _, _Window) -> timer:sleep(1000), ok; +test_page("Trace Overview" ++ _, _Window) -> + timer:sleep(500), %% Give it time to refresh + Active = get_active(), + Active ! refresh_interval, + timer:sleep(1000), %% Give it time to refresh + ok; + test_page(Title, Window) -> io:format("Page ~p: ~p~n", [Title, Window]), %% Just let it display some info and hopefully it doesn't crash -- cgit v1.2.3 From d70f97d61f632b92b3e29e133487a082d2de04fc Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 12 May 2016 09:49:25 +0200 Subject: [observer] Add more default match specs for messages --- lib/observer/src/observer_trace_wx.erl | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 5c34961fc1..f104f5bb19 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -143,16 +143,24 @@ default_matchspecs(Key) -> [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms]. get_default_matchspecs(funcs) -> - [{"Skeleton", [{'$1', [], [true]}], "fun(Args) -> true end"}, - {"Return Trace", [{'_', [], [{return_trace}]}], + [{"Return Trace", [{'_', [], [{return_trace}]}], "fun(_) -> return_trace() end"}, - {"Exception Trace", [{'_', [], [{exception_trace}]}], "fun(_) -> exception_trace() end"}, - {"Message Caller", [{'_', [], [{message,{caller}}]}], "fun(_) -> message(caller()) end"}, - {"Message Dump", [{'_', [], [{message,{process_dump}}]}], "fun(_) -> message(process_dump()) end"}]; + {"Exception Trace", [{'_', [], [{exception_trace}]}], + "fun(_) -> exception_trace() end"}, + {"Message Caller", [{'_', [], [{message,{caller}}]}], + "fun(_) -> message(caller()) end"}, + {"Message Dump", [{'_', [], [{message,{process_dump}}]}], + "fun(_) -> message(process_dump()) end"}]; get_default_matchspecs(send) -> - [{"Skeleton", [{['$1','$2'], [], [true]}], "fun([Pid,Msg]) -> true end"}]; + [{"To local node", [{['$1','_'], [{'==',{node,'$1'},{node}}], []}], + "fun([Pid,_]) when node(Pid)==node() ->\n true\nend"}, + {"To remote node", [{['$1','_'], [{'=/=',{node,'$1'},{node}}], []}], + "fun([Pid,_]) when node(Pid)=/=node() ->\n true\nend"}]; get_default_matchspecs('receive') -> - [{"Skeleton", [{['$1','$2','$3'], [], [true]}], "fun([Node,Pid,Msg]) -> true end"}]. + [{"From local node", [{['$1','_','_'], [{'==','$1',{node}}], []}], + "fun([Node,_,_]) when Node==node() ->\n true\nend"}, + {"From remote node", [{['$1','_','_'], [{'=/=','$1',{node}}], []}], + "fun([Node,_,_]) when Node=/=node() ->\n true\nend"}]. create_proc_port_view(Parent) -> -- cgit v1.2.3 From 4aceb606b06df67fc7a98d8dc2b841cf444d670c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 12 May 2016 11:47:22 +0200 Subject: [runtime_tools] Allow setting trace flag 'exiting' with dbg --- lib/runtime_tools/src/dbg.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl index 8cdb5a43e3..bca600cc07 100644 --- a/lib/runtime_tools/src/dbg.erl +++ b/lib/runtime_tools/src/dbg.erl @@ -1155,7 +1155,7 @@ all() -> [send,'receive',call,procs,ports,garbage_collection,running, set_on_spawn,set_on_first_spawn,set_on_link,set_on_first_link, timestamp,monotonic_timestamp,strict_monotonic_timestamp, - arity,return_to,silent,running_procs,running_ports]. + arity,return_to,silent,running_procs,running_ports,exiting]. display_info([Node|Nodes]) -> io:format("~nNode ~w:~n",[Node]), -- cgit v1.2.3 From 705011b8a223630d9800de9bd4d5cfd0b5986879 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 12 May 2016 15:37:37 +0200 Subject: [runtime_tools] Don't trace the trace client port When using an IP trace client, turn of all trace flags on the client port to avoid massive overload. Note that this is only a protection on client start - it is currently not possible to protect the port against trace flags set at a later point in time. --- lib/runtime_tools/src/dbg.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl index bca600cc07..c0d4665bda 100644 --- a/lib/runtime_tools/src/dbg.erl +++ b/lib/runtime_tools/src/dbg.erl @@ -1313,6 +1313,9 @@ tc_loop(Other, _Handler, _HData) -> gen_reader(ip, {Host, Portno}) -> case gen_tcp:connect(Host, Portno, [{active, false}, binary]) of {ok, Sock} -> + %% Just in case this is on the traced node, + %% make sure the port is not traced. + p(Sock,clear), mk_reader(fun ip_read/2, Sock); Error -> exit(Error) -- cgit v1.2.3 From ebd440bfc9eb685606250a82cae352f1ee37dac6 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 12 May 2016 15:40:32 +0200 Subject: [observer] Allow more trace flags on procs/ports from GUI --- lib/observer/src/observer_trace_wx.erl | 3 ++- lib/observer/src/observer_traceoptions_wx.erl | 20 ++++++++++++++++---- lib/observer/src/ttb.erl | 3 +++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index f104f5bb19..7df599c602 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -1023,7 +1023,8 @@ dbg_flag(proc,on_link) -> sol; dbg_flag(proc,on_first_spawn) -> sofs; dbg_flag(proc,on_first_link) -> sofl; dbg_flag(proc,events) -> p; -dbg_flag(port,events) -> ports. +dbg_flag(port,events) -> ports; +dbg_flag(_,Flag) -> Flag. textformat(Trace) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 -> format_trace(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace)); diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index e623f90df3..285c298c4b 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -36,12 +36,20 @@ process_trace(Parent, Default) -> FuncBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace function call", []), check_box(FuncBox, lists:member(functions, Default)), + ArityBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace arity instead of arguments", []), + check_box(ArityBox, lists:member(functions, Default)), SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []), check_box(SendBox, lists:member(send, Default)), RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []), check_box(RecBox, lists:member('receive', Default)), EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace process events", []), check_box(EventBox, lists:member(events, Default)), + SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of processes", []), + check_box(SchedBox, lists:member(running_procs, Default)), + ExitBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of exiting processes", []), + check_box(ExitBox, lists:member(exiting, Default)), + GCBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace garbage collections", []), + check_box(GCBox, lists:member(garbage_collection, Default)), {SpawnBox, SpwnAllRadio, SpwnFirstRadio} = optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"), @@ -57,7 +65,7 @@ process_trace(Parent, Default) -> {Radio, Opt} <- [{SpwnAllRadio, on_spawn}, {SpwnFirstRadio, on_first_spawn}, {LinkAllRadio, on_link}, {LinkFirstRadio, on_first_link}]], - [wxSizer:add(LeftSz, CheckBox, []) || CheckBox <- [FuncBox,SendBox,RecBox,EventBox]], + [wxSizer:add(LeftSz, CheckBox, []) || CheckBox <- [FuncBox,ArityBox,SendBox,RecBox,EventBox,SchedBox,ExitBox,GCBox]], wxSizer:add(LeftSz, 150, -1), wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}, {proportion,1}]), @@ -80,7 +88,9 @@ process_trace(Parent, Default) -> case wxDialog:showModal(Dialog) of ?wxID_OK -> All = [{SendBox, send}, {RecBox, 'receive'}, - {FuncBox, functions}, {EventBox, events}, + {FuncBox, functions}, {ArityBox, arity}, + {EventBox, events}, {SchedBox, running_procs}, + {ExitBox, exiting}, {GCBox, garbage_collection}, {{SpawnBox, SpwnAllRadio}, on_spawn}, {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, {{LinkBox, LinkAllRadio}, on_link}, @@ -111,8 +121,10 @@ port_trace(Parent, Default) -> check_box(RecBox, lists:member('receive', Default)), EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace port events", []), check_box(EventBox, lists:member(events, Default)), + SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of ports", []), + check_box(SchedBox, lists:member(running_ports, Default)), - [wxSizer:add(OptsSz, CheckBox, []) || CheckBox <- [SendBox,RecBox,EventBox]], + [wxSizer:add(OptsSz, CheckBox, []) || CheckBox <- [SendBox,RecBox,EventBox,SchedBox]], wxSizer:add(OptsSz, 150, -1), wxPanel:setSizer(Panel, OptsSz), @@ -125,7 +137,7 @@ port_trace(Parent, Default) -> case wxDialog:showModal(Dialog) of ?wxID_OK -> All = [{SendBox, send}, {RecBox, 'receive'}, - {EventBox, events}], + {EventBox, events}, {SchedBox, running_ports}], Opts = [Id || {Tick, Id} <- All, wxCheckBox:getValue(Tick)], wxDialog:destroy(Dialog), lists:reverse(Opts); diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl index e41f2c36fc..ac6c4572eb 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -1310,6 +1310,9 @@ ip_to_file(Trace, {shell_only, Fun} = State) -> ip_to_file(Trace,{{file,File}, ShellOutput}) -> Fun = dbg:trace_port(file,File), %File can be a filename or a wrap spec Port = Fun(), + %% Just in case this is on the traced node, + %% make sure the port is not traced. + p(Port,clear), %% Store the port so it can be properly closed ?MODULE ! {ip_to_file_trace_port, Port, self()}, receive {?MODULE,ok} -> ok end, -- cgit v1.2.3 From 7db703f88a8665bf6fc3266619044da3c882b1e3 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 12 May 2016 15:50:19 +0200 Subject: [observer] Automatically add active node When setting trace flags on 'new_processes' or 'new_ports', the currently active node is now automatically added if no other node is traced. --- lib/observer/src/observer_trace_wx.erl | 1 + lib/observer/src/observer_wx.erl | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 7df599c602..af90e2100c 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -774,6 +774,7 @@ do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) -> {Ps, New, _Changed} -> Ns1 = lists:usort([node(Id) || #titem{id=Id} <- New, Check(Id)]), Nodes = case ordsets:subtract(Ns1, Ns0) of + [] when Ns0==[] -> [observer_wx:get_active_node()]; [] -> Ns0; %% No new Nodes NewNs -> ordsets:union(NewNs, Ns0) end, diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index d896da1291..29778f6d13 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -21,8 +21,8 @@ -behaviour(wx_object). -export([start/0, stop/0]). --export([create_menus/2, get_attrib/1, get_tracer/0, set_status/1, - create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). +-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, + set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, handle_call/3, handle_info/2, check_page_title/1]). @@ -90,6 +90,9 @@ set_status(What) -> get_tracer() -> wx_object:call(observer, get_tracer). +get_active_node() -> + wx_object:call(observer, get_active_node). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init(_Args) -> @@ -385,6 +388,9 @@ handle_call({get_attrib, Attrib}, _From, State) -> handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) -> {reply, TraceP, State}; +handle_call(get_active_node, _From, State=#state{node=Node}) -> + {reply, Node, State}; + handle_call(stop, _, State = #state{frame = Frame}) -> wxFrame:destroy(Frame), {stop, normal, ok, State}; -- cgit v1.2.3 From ab22fe599d0e6918cea46d620acaf7f8e16d36b6 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 19 May 2016 15:15:51 +0200 Subject: [observer] Update user guide Document the new Ports tab and tracing of ports. --- lib/observer/doc/src/observer_ug.xml | 109 +++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml index be5d249f37..6eb72f3e58 100644 --- a/lib/observer/doc/src/observer_ug.xml +++ b/lib/observer/doc/src/observer_ug.xml @@ -168,7 +168,7 @@

The length of the message queue for the process.

-

Option Process info opens a detailed information window on the selected process, +

Option Process info opens a detailed information window on the process under the mouse pointer, including the following:

Process Information @@ -195,12 +195,53 @@

-

Option Trace Processes adds the selected process identifiers to tab +

Option Trace selected processes adds the selected process identifiers to tab Trace Overview plus the node that the processes reside on.

-

Option Trace Named Processes adds the registered name of the processes. This can be +

Option Trace selected processes by name adds the registered name of the processes. This can be useful when tracing is done on many nodes, as processes with that name are then traced on all traced nodes.

+

Option Kill process brutally kills the processes under + the mouse pointer by sending an exit signal with + reason kill.

+ + + +
+ Ports Tab +

Tab Ports lists port information in columns. + For each port the following information is displayed: +

+ + Id +

The port identifier.

+ Connected +

The process identifier for the process that owns the + port.

+ Name +

The registered name of the port, if any.

+ Controls +

The name of the command set by erlang:open_port/2.

+ Slot +

The internal index of the port.

+
+ +

Option Port info opens a detailed information window + for the port under the mouse pointer. In addition to the + information above, it also shows links and monitors.

+ +

Option Trace selected ports adds the selected port + identifiers, and the nodes that the ports reside on, + to tab Trace Overview.

+ +

Option Trace selected ports by name adds the + registered name of the port to tab Trace Overview. This + can be useful when tracing is done on many nodes, as ports with + that name are then traced on all traced nodes.

+ +

Option Close + executes erlang:port_close/1 + on the port under the mouse pointer.

@@ -211,8 +252,11 @@ applications are not diplayed. Use menu View to view "system" ETS tables, unreadable ETS tables, or Mnesia tables.

-

Double-click to view the table content. To view table information, select the table - and activate menu View > Table information.

+

Double-click to view the table content, or right-click and + select option Show Table Content. To view table + information, select the table and activate menu View > + Table information, or right-click and select option Table + info.

You can use regular expressions and search for objects, and edit or delete them.

@@ -220,11 +264,12 @@
Trace Overview Tab -

Tab Trace Overview handles tracing. Trace - by selecting the processes to be traced and how to trace - them. You can trace messages, function calls, and events, where - events are process-related events such as spawn, - exit, and many others. +

Tab Trace Overview handles tracing. Trace by selecting + the processes or ports to be traced and how to trace them. For + processes, you can trace messages, function calls, scheduling, + garbage collections, and process-related events such + as spawn, exit, and many others. For ports, you can + trace messages, scheduling and port-related events.

To trace function calls, you also need to set up @@ -234,31 +279,49 @@ specifications can also be used to trigger more information in the trace messages.

-

Trace patterns only apply to the traced processes.

+ +

You can also set match specifications on messages. By default, + if tracing messages, all messages sent and/or received by the + process or port are traced. Match specifications can be used to + reduce the number of traced messages and/or to trigger more + information in the trace messages.

+ +

Trace patterns only apply to the traced processes and + ports.

- Processes are added from the Applications or Processes tabs. - A special new identifier, meaning all processes spawned after trace - start, can be added with button Add 'new' Process. + Processes are added from the Applications + or Processes tabs. Ports are added from + the Ports tab. A special new identifier, + meaning all processes, or ports, started after trace start, can + be added with buttons Add 'new' Processes and Add + 'new' Ports, respecively.

- When adding processes, a window with trace options is displayed. The chosen - options are set for the selected processes. - Process options can be changed by right-clicking a process. + When adding processes or ports, a window with trace options is + displayed. The chosen options are set for the selected + processes/ports. To change the options, right-click the process + or port and select Edit process options. To remove a + process or port from the list, right-click and select Remove + process or Remove port, respectively.

- Processes added by process identifiers add the nodes these - processes reside on in the node list. More nodes can be added by clicking - button Add Nodes. + Processes and ports added by process/port identifiers add the + nodes these processes/ports reside on in the node list. More + nodes can be added by clicking button Add Nodes, or by + right-clicking in the Nodes list and select Add + Nodes. To remove nodes, select them, then right-click and + choose Remove nodes.

If function calls are traced, trace patterns must be added by clicking button Add Trace Pattern. Select a module, function(s), and a match specification. If no functions are selected, all functions in the module are traced.

- Trace patterns can also be added for traced messages. This is - done in the same way as for traced function calls. Separate - trace patterns must be set for sent and received messages. + Trace patterns can also be added for traced messages. Click + button Add Trace Pattern and select Messages + sent or Messages received, and a match + specification.

A few basic match specifications are provided in the tool, and -- cgit v1.2.3