From d7eaea0d44fac1f0c67d67c188d6a635b0a4da4c Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Thu, 24 Nov 2011 12:54:13 +0100 Subject: [observer] Add more trace functionality Add logging output options, to file, shell or log-window. Add remove/change process options, trace patterns via right-click. Show traced nodes. Add possibility to add named processes, which will be traced on all nodes. Add menu to log window --- lib/observer/src/Makefile | 4 + lib/observer/src/observer_defs.hrl | 17 +- lib/observer/src/observer_lib.erl | 14 +- lib/observer/src/observer_pro_wx.erl | 52 ++- lib/observer/src/observer_sys_wx.erl | 24 +- lib/observer/src/observer_trace_wx.erl | 549 +++++++++++++++++++------- lib/observer/src/observer_traceoptions_wx.erl | 193 ++++++--- lib/observer/src/observer_wx.erl | 4 + 8 files changed, 614 insertions(+), 243 deletions(-) diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 95954d8587..f2f3f68115 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -57,6 +57,8 @@ MODULES= \ HRL_FILES= \ ../include/etop.hrl INTERNAL_HRL_FILES= \ + observer_tv.hrl \ + observer_defs.hrl \ crashdump_viewer.hrl \ etop_defs.hrl ERL_FILES= $(MODULES:%=%.erl) @@ -104,6 +106,8 @@ ERL_COMPILE_FLAGS += \ # Targets # ---------------------------------------------------- +$(TARGET_FILES): $(INTERNAL_HRL_FILES) + debug opt: $(TARGET_FILES) clean: diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index d83a1e2fa5..586e7bbff9 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -16,16 +16,6 @@ %% %% %CopyrightEnd% --record(trace_options, {send = false, - treceive = false, - functions = false, - events = false, - on_1st_spawn = false, - on_all_spawn = false, - on_1st_link = false, - on_all_link = false, - main_window = true}). - -record(match_spec, {name = "", term = [], str = [], @@ -37,15 +27,10 @@ arity, %integer match_spec = #match_spec{}}). --record(on_spawn, {checkbox, all_spawn, first_spawn}). - --record(on_link, {checkbox, all_link, first_link}). - --record(pid, {window, traced}). - -record(create_menu, {id, text, + help = [], type = append, check = false }). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 90c270e977..3096741c6f 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -239,15 +239,21 @@ create_menu(Name, MenuItems, Index, MenuBar, _Type) -> create_menu_item(#create_menu{id = ?wxID_HELP=Id}, Menu, Index) -> wxMenu:insert(Menu, Index, Id), Index+1; -create_menu_item(#create_menu{id = Id, text = Text, type = Type, check = Check}, Menu, Index) -> +create_menu_item(#create_menu{id=Id, text=Text, help=Help, type=Type, check=Check}, + Menu, Index) -> + Opts = case Help of + [] -> []; + _ -> [{help, Help}] + end, case Type of append -> - wxMenu:insert(Menu, Index, Id, [{text, Text}]); + wxMenu:insert(Menu, Index, Id, + [{text, Text}|Opts]); check -> - wxMenu:insertCheckItem(Menu, Index, Id, Text), + wxMenu:insertCheckItem(Menu, Index, Id, Text, Opts), wxMenu:check(Menu, Id, Check); radio -> - wxMenu:insertRadioItem(Menu, Index, Id, Text), + wxMenu:insertRadioItem(Menu, Index, Id, Text, Opts), wxMenu:check(Menu, Id, Check); separator -> wxMenu:insertSeparator(Menu, Index) diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 3738bdba65..77f454ab21 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -44,10 +44,17 @@ -define(ID_REFRESH, 203). -define(ID_REFRESH_INTERVAL, 204). -define(ID_DUMP_TO_FILE, 205). --define(ID_TRACEMENU, 206). --define(ID_TRACE_ALL_MENU, 207). --define(ID_TRACE_NEW_MENU, 208). --define(ID_ACCUMULATE, 209). +-define(ID_TRACE_PIDS, 206). +-define(ID_TRACE_NAMES, 207). +-define(ID_TRACE_NEW, 208). +-define(ID_TRACE_ALL, 209). +-define(ID_ACCUMULATE, 210). + +-define(TRACE_PIDS_STR, "Trace selected process identifiers"). +-define(TRACE_NAMES_STR, "Trace selected processes, " + "if a process have a registered name " + "processes with same name will be traced on all nodes"). + %% Records @@ -121,8 +128,9 @@ create_pro_menu(Parent, Holder) -> #create_menu{id=?ID_REFRESH, text="Refresh\tCtrl-R"}, #create_menu{id=?ID_REFRESH_INTERVAL, text="Refresh Interval"}]}, {"Trace", - [#create_menu{id=?ID_TRACEMENU, text="Trace selected processes"}, - #create_menu{id=?ID_TRACE_NEW_MENU, text="Trace new processes"} + [#create_menu{id=?ID_TRACE_PIDS, text="Trace processes"}, + #create_menu{id=?ID_TRACE_NAMES, text="Trace named processes (all nodes)"}, + #create_menu{id=?ID_TRACE_NEW, text="Trace new processes"} %% , #create_menu{id=?ID_TRACE_ALL_MENU, text="Trace all processes"} ]} ], @@ -300,7 +308,7 @@ handle_event(#wx{id=?ID_PROC}, Opened2 = start_procinfo(Pid, Panel, Opened), {noreply, State#state{procinfo_menu_pids=Opened2}}; -handle_event(#wx{id=?ID_TRACEMENU}, #state{sel={_, Pids}, panel=Panel}=State) -> +handle_event(#wx{id=?ID_TRACE_PIDS}, #state{sel={_, Pids}, panel=Panel}=State) -> case Pids of [] -> observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), @@ -310,7 +318,18 @@ handle_event(#wx{id=?ID_TRACEMENU}, #state{sel={_, Pids}, panel=Panel}=State) - {noreply, State} end; -handle_event(#wx{id=?ID_TRACE_NEW_MENU, event=#wxCommand{type=command_menu_selected}}, State) -> +handle_event(#wx{id=?ID_TRACE_NAMES}, #state{sel={SelIds,_Pids}, holder=Holder, panel=Panel}=State) -> + case SelIds of + [] -> + observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), + {noreply, State}; + _ -> + PidsOrReg = call(Holder, {get_name_or_pid, self(), SelIds}), + observer_trace_wx:add_processes(observer_wx:get_tracer(), 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]), {noreply, State}; @@ -336,8 +355,10 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click, {ok, _} -> Menu = wxMenu:new(), wxMenu:append(Menu, ?ID_PROC, "Process info"), - wxMenu:append(Menu, ?ID_TRACEMENU, "Trace selected"), - wxMenu:append(Menu, ?ID_TRACEMENU, "Kill Process"), + 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, @@ -452,6 +473,9 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, {get_rows_from_pids, From, Pids} -> get_rows_from_pids(From, Pids, Info), table_holder(S0); + {get_name_or_pid, From, Indices} -> + get_name_or_pid(From, Indices, Info), + table_holder(S0); {get_node, From} -> From ! {self(), Node}, @@ -537,6 +561,14 @@ get_pids(From, Indices, ProcInfo) -> Processes = [(lists:nth(I+1, ProcInfo))#etop_proc_info.pid || I <- Indices], From ! {self(), Processes}. +get_name_or_pid(From, Indices, ProcInfo) -> + Get = fun(#etop_proc_info{name=Name}) when is_atom(Name) -> Name; + (#etop_proc_info{pid=Pid}) -> Pid + end, + Processes = [Get(lists:nth(I+1, ProcInfo)) || I <- Indices], + From ! {self(), Processes}. + + get_row(From, Row, pid, Info) -> Pid = case Row =:= -1 of true -> {error, undefined}; diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index ddedcf3829..789728bbeb 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -97,12 +97,12 @@ info_fields() -> ]} ], Stat = [{"Memory Usage", right, - [{"Total", total}, - {"Processes", processes}, - {"Atoms", atom}, - {"Binaries", binary}, - {"Code", code}, - {"Ets", ets} + [{"Total", {bytes, total}}, + {"Processes", {bytes, processes}}, + {"Atoms", {bytes, atom}}, + {"Binaries", {bytes, binary}}, + {"Code", {bytes, code}}, + {"Ets", {bytes, ets}} ]}, {"Statistics", right, [{"Up time", uptime}, @@ -202,15 +202,6 @@ sys_info() -> {logical_processors_available, erlang:system_info(logical_processors_available)}, {logical_processors_online, erlang:system_info(logical_processors_online)}, - {total, {bytes, erlang:memory(total)}}, - %%{processes_used, erlang:memory(processes_used)}, - {processes, {bytes, erlang:memory(processes)}}, - %%{atom_used, erlang:memory(atom_used)}, - {atom, {bytes, erlang:memory(atom)}}, - {binary, {bytes, erlang:memory(binary)}}, - {code, {bytes, erlang:memory(code)}}, - {ets, {bytes, erlang:memory(ets)}}, - {otp_release, erlang:system_info(otp_release)}, {version, erlang:system_info(version)}, {system_architecture, erlang:system_info(system_architecture)}, @@ -219,5 +210,6 @@ sys_info() -> {threads, erlang:system_info(threads)}, {thread_pool_size, erlang:system_info(thread_pool_size)}, {wordsize_internal, erlang:system_info({wordsize, internal})}, - {wordsize_external, erlang:system_info({wordsize, external})} + {wordsize_external, erlang:system_info({wordsize, external})} | + erlang:memory() ]. diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 0ab7db121b..31c57bd9d3 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -27,30 +27,43 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). --define(OPTIONS, 301). --define(SAVE_BUFFER, 302). --define(CLOSE, 303). --define(CLEAR, 304). -define(SAVE_TRACEOPTS, 305). -define(LOAD_TRACEOPTS, 306). -define(TOGGLE_TRACE, 307). -define(ADD_NEW, 308). -define(ADD_TP, 309). --define(PROCESSES, 350). --define(MODULES, 351). --define(FUNCTIONS, 352). --define(TRACERWIN, 353). +-define(TRACE_OUTPUT, 310). +-define(TRACE_DEFMS, 311). +-define(TRACE_DEFPS, 312). + +-define(NODES_WIN, 330). +-define(ADD_NODES, 331). +-define(REMOVE_NODES, 332). + +-define(PROC_WIN, 340). +-define(EDIT_PROCS, 341). +-define(REMOVE_PROCS, 342). + +-define(MODULES_WIN, 350). + +-define(FUNCS_WIN, 360). +-define(EDIT_FUNCS_MS, 361). +-define(REMOVE_FUNCS_MS, 362). + +-define(LOG_WIN, 370). +-define(LOG_SAVE, 321). +-define(LOG_CLEAR, 322). -record(state, {parent, panel, - p_view, - m_view, - f_view, + n_view, p_view, m_view, f_view, %% The listCtrl's + logwin, %% The latest log window nodes = [], toggle_button, tpids = [], %% #tpid def_trace_opts = [], + output = [], tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec} match_specs = []}). % [ #match_spec{} ] @@ -72,26 +85,27 @@ create_window(Notebook, ParentPid) -> Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}]), - ProcessView = create_process_view(Splitter), + {NodeProcView, NodeView, ProcessView} = create_process_view(Splitter), {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter), wxSplitterWindow:setSashGravity(Splitter, 0.5), wxSplitterWindow:setMinimumPaneSize(Splitter,50), - wxSplitterWindow:splitHorizontally(Splitter, ProcessView, MatchSpecView), + wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView), wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]), %% Buttons Buttons = wxBoxSizer:new(?wxHORIZONTAL), ToggleButton = wxToggleButton:new(Panel, ?TOGGLE_TRACE, "Start Trace", []), wxSizer:add(Buttons, ToggleButton), - New = wxButton:new(Panel, ?ADD_NEW, [{label, "Trace New Processes"}]), - wxSizer:add(Buttons, New), - ATP = wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}]), - wxSizer:add(Buttons, ATP), - wxMenu:connect(Panel, command_togglebutton_clicked, []), - wxMenu:connect(Panel, command_button_clicked, []), - wxSizer:add(Sizer, Buttons, [{flag, ?wxALL},{border, 2}, {proportion,0}]), + 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_TP, [{label, "Add Trace Pattern"}])), + wxMenu:connect(Panel, command_togglebutton_clicked, [{skip, true}]), + wxMenu:connect(Panel, command_button_clicked, [{skip, true}]), + wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN}, + {border, 5}, {proportion,0}]), wxWindow:setSizer(Panel, Sizer), {Panel, #state{parent=ParentPid, panel=Panel, - p_view=ProcessView, m_view=ModView, f_view=FuncView, + n_view=NodeView, p_view=ProcessView, m_view=ModView, f_view=FuncView, toggle_button = ToggleButton, match_specs=default_matchspecs()}}. @@ -103,36 +117,51 @@ default_matchspecs() -> [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms]. create_process_view(Parent) -> - Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, - Grid = wxListCtrl:new(Parent, [{winid, ?PROCESSES}, {style, Style}]), + Panel = wxPanel:new(Parent), + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + Style = ?wxLC_REPORT bor ?wxLC_HRULES, + Splitter = wxSplitterWindow:new(Panel, []), + Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]), + Procs = wxListCtrl:new(Splitter, [{winid, ?PROC_WIN}, {style, Style}]), Li = wxListItem:new(), - AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, "Nodes"), + wxListCtrl:insertColumn(Nodes, 0, Li), + + AddProc = fun({Name, Align, DefSize}, Col) -> wxListItem:setText(Li, Name), wxListItem:setAlign(Li, Align), - wxListCtrl:insertColumn(Grid, Col, Li), - wxListCtrl:setColumnWidth(Grid, Col, DefSize), + wxListCtrl:insertColumn(Procs, Col, Li), + wxListCtrl:setColumnWidth(Procs, Col, DefSize), Col + 1 end, ListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120}, {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], - lists:foldl(AddListEntry, 0, ListItems), + lists:foldl(AddProc, 0, ListItems), wxListItem:destroy(Li), - %% wxListCtrl:connect(Grid, command_list_item_activated), - %% wxListCtrl:connect(Grid, command_list_item_selected), - wxListCtrl:connect(Grid, size, [{skip, true}]), + wxSplitterWindow:setSashGravity(Splitter, 0.0), + wxSplitterWindow:setMinimumPaneSize(Splitter,50), + wxSplitterWindow:splitVertically(Splitter, Nodes, Procs, [{sashPosition, 150}]), + wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), - wxWindow:setFocus(Grid), - Grid. + wxListCtrl:connect(Procs, command_list_item_right_click), + wxListCtrl:connect(Nodes, command_list_item_right_click), + wxListCtrl:connect(Procs, size, [{skip, true}]), + wxListCtrl:connect(Nodes, size, [{skip, true}]), + + wxPanel:setSizer(Panel, MainSz), + wxWindow:setFocus(Procs), + {Panel, Nodes, Procs}. create_matchspec_view(Parent) -> Panel = wxPanel:new(Parent), MainSz = wxBoxSizer:new(?wxHORIZONTAL), - Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Style = ?wxLC_REPORT bor ?wxLC_HRULES, Splitter = wxSplitterWindow:new(Panel, []), - Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES}, {style, Style}]), - Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCTIONS}, {style, Style}]), + Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, {style, Style}]), + Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]), Li = wxListItem:new(), + wxListItem:setText(Li, "Modules"), wxListCtrl:insertColumn(Modules, 0, Li), wxListItem:setText(Li, "Functions"), @@ -142,6 +171,7 @@ create_matchspec_view(Parent) -> wxListCtrl:insertColumn(Funcs, 1, Li), wxListCtrl:setColumnWidth(Funcs, 1, 300), wxListItem:destroy(Li), + wxSplitterWindow:setSashGravity(Splitter, 0.0), wxSplitterWindow:setMinimumPaneSize(Splitter,50), wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 150}]), @@ -150,14 +180,19 @@ 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(Funcs, command_list_item_selected), + wxListCtrl:connect(Funcs, command_list_item_right_click), wxPanel:setSizer(Panel, MainSz), {Panel, Modules, Funcs}. create_menues(Parent) -> - Menus = [{"File", [#create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"}, - #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}] - }], + Menus = [{"File", + [#create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"}, + #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_DEFPS, text = "Default Process Options"}]} + ], observer_wx:create_menus(Parent, Menus). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -179,12 +214,11 @@ handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> {noreply, State}; handle_event(#wx{id=?ADD_NEW}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> - case observer_traceoptions_wx:process_trace(Parent, TraceOpts) of - {ok, Opts} -> - Process = #tpid{pid=new, opts=Opts}, - {noreply, do_add_processes([Process], State#state{def_trace_opts=Opts})}; - cancel -> - {noreply, State} + 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})} + catch cancel -> {noreply, State} end; handle_event(#wx{id=?ADD_TP}, @@ -200,98 +234,101 @@ handle_event(#wx{id=?ADD_TP}, {noreply, do_add_patterns(Patterns, State)} end; -handle_event(#wx{id=?MODULES, event=#wxList{type=command_list_item_selected, itemIndex=Row}}, +handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected, itemIndex=Row}}, State = #state{tpatterns=TPs, m_view=Mview, f_view=Fview}) -> Module = list_to_atom(wxListCtrl:getItemText(Mview, Row)), update_functions_view(dict:fetch(Module, TPs), Fview), {noreply, State}; - handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, #state{panel = Panel, nodes = Nodes, tpids = TProcs, - tpatterns = TPs, - toggle_button = ToggleBtn} = State) -> - LogWin = wxFrame:new(Panel, ?TRACERWIN, "Trace Log", [{size, {750, 800}}]), - Text = wxTextCtrl:new(LogWin, ?wxID_ANY, - [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor - ?wxTE_DONTWRAP bor ?wxTE_READONLY}]), - Font = observer_wx:get_attrib({font, fixed}), - Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), - true = wxTextCtrl:setDefaultStyle(Text, Attr), - Env = wx:get_env(), - Write = fun(Trace) -> - wx:set_env(Env), - wxTextCtrl:appendText(Text, textformat(Trace)) - end, - {ok, _} = ttb:tracer(Nodes, [{file, {local,"/tmp/foo"}}, {shell, {only, Write}}]), - setup_ttb(dict:to_list(TPs), TProcs), - wxFrame:connect(LogWin, close_window, [{skip, true}]), - wxFrame:show(LogWin), - wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), - {noreply, State}; + tpatterns = TPs0, + toggle_button = ToggleBtn, + output = Opts + } = State) -> + try + TPs = dict:to_list(TPs0), + (TProcs == []) andalso throw({error, "No processes traced"}), + (Nodes == []) andalso throw({error, "No nodes traced"}), + HaveCallTrace = fun(#tpid{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), + wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), + {noreply, State#state{logwin=LogWin}} + catch {error, Msg} -> + observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR), + wxToggleButton:setValue(ToggleBtn, false), + {noreply, State} + end; handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, #state{toggle_button = ToggleBtn} = State) -> %%Stop tracing ttb:stop(nofetch), wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + wxToggleButton:setValue(ToggleBtn, false), + {noreply, State#state{logwin=false}}; + +handle_event(#wx{id=Id, obj=LogWin, event=Ev}, + #state{toggle_button = ToggleBtn, logwin=Latest} = State) + when Id =:= ?LOG_WIN; is_record(Ev, wxClose) -> + case LogWin of + Latest -> + %%Stop tracing + ttb:stop(nofetch), + wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + wxToggleButton:setValue(ToggleBtn, false), + {noreply, State#state{logwin=false}}; + _ -> + {noreply, State} + end; + +handle_event(Ev = #wx{id=?LOG_CLEAR, userData=TCtrl}, State) -> + wxTextCtrl:clear(TCtrl), {noreply, State}; -handle_event(#wx{id=?TRACERWIN, event=#wxClose{}}, - #state{toggle_button = ToggleBtn} = State) -> - %%Stop tracing - ttb:stop(nofetch), - wxToggleButton:setLabel(ToggleBtn, "Start Trace"), +handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) -> + Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Path = wxFileDialog:getPath(Dialog), + wxDialog:destroy(Dialog), + wxTextCtrl:saveFile(TCtrl, [{file, Path}]); + _ -> + wxDialog:destroy(Dialog), + ok + end, {noreply, State}; -%% handle_event(#wx{id = ?CLEAR, event = #wxCommand{type = command_menu_selected}}, -%% #state{text_ctrl = TxtCtrl} = State) -> -%% wxTextCtrl:clear(TxtCtrl), -%% {noreply, State}; - -%% handle_event(#wx{id = ?SAVE_BUFFER, event = #wxCommand{type = command_menu_selected}}, -%% #state{frame = Frame, text_ctrl = TxtCtrl} = State) -> -%% Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), -%% case wxFileDialog:showModal(Dialog) of -%% ?wxID_OK -> -%% Path = wxFileDialog:getPath(Dialog), -%% wxDialog:destroy(Dialog), -%% case filelib:is_file(Path) of -%% true -> -%% observer_wx:create_txt_dialog(Frame, "File already exists: " ++ Path ++ "\n", -%% "Error", ?wxICON_ERROR); -%% false -> -%% wxTextCtrl:saveFile(TxtCtrl, [{file, Path}]) -%% end; -%% _ -> -%% wxDialog:destroy(Dialog), -%% ok -%% end, -%% {noreply, State}; - -handle_event(#wx{id = ?SAVE_TRACEOPTS, - event = #wxCommand{type = command_menu_selected}}, +handle_event(#wx{id = ?SAVE_TRACEOPTS}, #state{panel = Panel, def_trace_opts = TraceOpts, match_specs = MatchSpecs, - tpatterns = TracePatterns + tpatterns = TracePatterns, + output = Output } = State) -> Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), case wxFileDialog:showModal(Dialog) of ?wxID_OK -> Path = wxFileDialog:getPath(Dialog), - write_file(Panel, Path, TraceOpts, MatchSpecs, dict:to_list(TracePatterns)); + write_file(Panel, Path, + TraceOpts, MatchSpecs, Output, + dict:to_list(TracePatterns) + ); _ -> ok end, wxDialog:destroy(Dialog), {noreply, State}; -handle_event(#wx{id = ?LOAD_TRACEOPTS, - event = #wxCommand{type = command_menu_selected}}, - #state{panel = Panel} = State) -> +handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) -> Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]), State2 = case wxFileDialog:showModal(Dialog) of ?wxID_OK -> @@ -303,27 +340,142 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS, 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"}]; + ?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), + {noreply, State}; + +handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, p_view=Ps} = State) -> + try + [#tpid{opts=DefOpts}|_] = Selected = get_selected_items(Ps, 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})} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_PROCS}, #state{tpids=Tpids, p_view=LCtrl} = State) -> + Selected = get_selected_items(LCtrl, Tpids), + Pids = Tpids -- Selected, + update_process_view(Pids, LCtrl), + {noreply, State#state{tpids=Pids}}; + +handle_event(#wx{id=?TRACE_DEFPS}, #state{panel=Panel, def_trace_opts=PO} = State) -> + try + Opts = observer_traceoptions_wx:process_trace(Panel, PO), + {noreply, State#state{def_trace_opts=Opts}} + catch _:_ -> + {noreply, State} + end; + +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) + 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 + } = State) -> + 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), + Changed = [TP#tpattern{ms=Ms} || TP <- Selected], + {noreply, do_add_patterns({Module, Changed}, State)} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_FUNCS_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] -> + FMs0 = dict:fetch(Module, TPs0), + Selected = get_selected_items(LCtrl, FMs0), + FMs = FMs0 -- Selected, + update_functions_view(FMs, LCtrl), + TPs = case FMs of + [] -> + New = dict:erase(Module, TPs0), + update_modules_view(lists:sort(dict:fetch_keys(New)), Module, Mview), + New; + _ -> + dict:store(Module, FMs, TPs0) + end, + {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), + {noreply, State#state{output=Out}} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?ADD_NODES}, #state{panel=Panel, n_view=Nview, nodes=Ns0} = State) -> + try + Possible = [node()|nodes()] -- Ns0, + case Possible of + [] -> + Msg = "Already selected all connected nodes\n" + "Use the Nodes menu to connect to new nodes first.", + observer_wx:create_txt_dialog(Panel, Msg, "No available nodes", ?wxICON_INFORMATION), + throw(cancel); + _ -> + Ns = lists:usort(Ns0 ++ observer_traceoptions_wx:select_nodes(Panel, Possible)), + update_nodes_view(Ns, Nview), + {noreply, State#state{nodes=Ns}} + end + catch cancel -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_NODES}, #state{n_view=Nview, nodes=Ns0} = State) -> + Sel = get_selected_items(Nview, Ns0), + Ns = Ns0 -- Sel, + update_nodes_view(Ns, Nview), + {noreply, State#state{nodes = Ns}}; + handle_event(#wx{id=ID, event = What}, State) -> - io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, self(), ID, What]), + io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, ?LINE, ID, What]), {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_call(Msg, _From, State) -> - io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), - {reply, ok, State}. +handle_call(Msg, From, _State) -> + error({unhandled_call, Msg, From}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> - case observer_traceoptions_wx:process_trace(Parent, TraceOpts) of - {ok, Opts} -> - POpts = [#tpid{pid=Pid, opts=Opts} || Pid <- Pids], - {noreply, do_add_processes(POpts, State#state{def_trace_opts=Opts})}; - cancel -> + 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}), + {noreply, S} + catch cancel -> {noreply, State} end; -handle_cast(Msg, State) -> - io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), - {noreply, State}. +handle_cast(Msg, _State) -> + error({unhandled_cast, Msg}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -342,18 +494,30 @@ handle_info(Any, State) -> {noreply, State}. terminate(_Reason, #state{nodes=_Nodes}) -> - %% case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of - %% undefined -> fine; - %% Pid -> exit(Pid, kill) - %% end, + ttb:stop(nofetch), ok. code_change(_, _, State) -> {stop, not_yet_implemented, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> + Old = case dict:find(Module, TPs0) of + {ok, Prev} -> Prev; + error -> [] + end, + case merge_patterns(NewPs, Old) of + {Old, [], []} -> + State; + {MPatterns, _New, _Changed} -> + %% if dynamicly updates update New and Changed + TPs = dict:store(Module, MPatterns, TPs0), + update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), + update_functions_view(dict:fetch(Module, TPs), Fview), + State#state{tpatterns=TPs} + end. -do_add_processes(POpts, S0=#state{p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> +do_add_processes(POpts, S0=#state{n_view=Nview, p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> case merge_pids(POpts, OldPids) of {OldPids, [], []} -> S0; @@ -363,13 +527,15 @@ do_add_processes(POpts, S0=#state{p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> Nodes = case ordsets:subtract(Ns1, Ns0) of [] -> Ns0; %% No new Nodes NewNs -> - %% Handle new nodes - %% BUGBUG add trace patterns for new nodes - ordsets:union(NewNs, Ns0) + %% if dynamicly updates add trace patterns for new nodes + All = ordsets:union(NewNs, Ns0), + update_nodes_view(All, Nview), + All end, S0#state{tpids=Pids, nodes=Nodes} end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% update_process_view(Pids, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), wx:foldl(fun(#tpid{pid=Pid, opts=Opts}, Row) -> @@ -381,20 +547,15 @@ update_process_view(Pids, LCtrl) -> Row+1 end, 0, Pids). -do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> - Old = case dict:find(Module, TPs0) of - {ok, Prev} -> Prev; - error -> [] - end, - case merge_patterns(NewPs, Old) of - {Old, [], []} -> - State; - {MPatterns, _New, _Changed} -> - TPs = dict:store(Module, MPatterns, TPs0), - update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), - update_functions_view(dict:fetch(Module, TPs), Fview), - State#state{tpatterns=TPs} - end. +update_nodes_view(Nodes, LCtrl) -> + 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)), + Row+1 + end, 0, Nodes). update_modules_view(Mods, Module, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), @@ -419,7 +580,6 @@ 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]}; @@ -438,7 +598,6 @@ merge_pids_1(New, Old) -> merge_patterns(New, Old) -> merge(lists:sort(New), Old, #tpattern.fa, [], [], []). - merge([N|Ns], [N|Os], El, New, Ch, All) -> merge(Ns, Os, El, New, Ch, [N|All]); merge([N|Ns], [O|Os], El, New, Ch, All) @@ -456,13 +615,78 @@ merge(Ns, [], _El, New, Ch, All) -> {lists:reverse(All, Ns), Ns++New, Ch}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +ttb_output_args(Parent, Opts) -> + ToWindow = proplists:get_value(window, Opts, true), + ToShell = proplists:get_value(shell, Opts, false), + ToFile = proplists:get_value(file, Opts, false), + ToWindow orelse ToShell orelse ToShell orelse + throw({error, "No output of trace"}), + {LogWin,Text} = create_logwindow(Parent, ToWindow), + Write = output_fun(Text, ToShell), + Shell = output_shell(ToFile, Write), + FileOpts = output_file(ToFile, proplists:get_value(wrap, Opts, false), Opts), + {[{file, {local,FileOpts}}|Shell], LogWin}. + +output_shell(true, false) -> + []; %% File only +output_shell(true, Write) when is_function(Write) -> + [{shell, Write}]; +output_shell(false, Write) when is_function(Write) -> + [{shell, {only, Write}}]. + +output_fun(false, false) -> false; +output_fun(false, true) -> fun(Trace) -> io:put_chars(textformat(Trace)) end; +output_fun(Text, false) -> + Env = wx:get_env(), + fun(Trace) -> + wx:set_env(Env), + wxTextCtrl:appendText(Text, textformat(Trace)) + end; +output_fun(Text, true) -> + Env = wx:get_env(), + fun(Trace) -> + wx:set_env(Env), + IoList = textformat(Trace), + wxTextCtrl:appendText(Text, IoList), + io:put_chars(textformat(Trace)) + end. + +output_file(false, _, _Opts) -> + "ttb"; %% Will be ignored +output_file(true, false, Opts) -> + proplists:get_value(filename, Opts, "ttb"); +output_file(true, true, Opts) -> + Name = proplists:get_value(filename, Opts, "ttb"), + Size = proplists:get_value(wrap_sz, Opts, 128), + Count = proplists:get_value(wrap_c, Opts, 8), + {wrap, Name, Size*1024, Count}. + + +create_logwindow(_Parent, false) -> {false, false}; +create_logwindow(Parent, true) -> + LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750, 800}}]), + MB = wxMenuBar:new(), + File = wxMenu:new(), + wxMenu:append(File, ?LOG_CLEAR, "Clear Log\tCtrl-C"), + wxMenu:append(File, ?LOG_SAVE, "Save Log\tCtrl-S"), + wxMenu:append(File, ?wxID_CLOSE, "Close"), + wxMenuBar:append(MB, File, "File"), + wxFrame:setMenuBar(LogWin, MB), + Text = wxTextCtrl:new(LogWin, ?wxID_ANY, + [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor + ?wxTE_DONTWRAP bor ?wxTE_READONLY}]), + Font = observer_wx:get_attrib({font, fixed}), + Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), + true = wxTextCtrl:setDefaultStyle(Text, Attr), + wxFrame:connect(LogWin, close_window, [{skip, true}]), + wxFrame:connect(LogWin, command_menu_selected, [{userData, Text}]), + wxFrame:show(LogWin), + {LogWin, Text}. setup_ttb(TPs, TPids) -> _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, - %% io:format("ttb:p(pid(\"~w\", ~w).", [Pid, Flags]), - %% io:format("TTB ~w ~w~n",[R2, R1]), ok. %% Sigh order is important @@ -511,33 +735,33 @@ format_trace(Trace, Size, TS0={_,_,MS}) -> case element(4, Trace) of {dbg,ok} -> ""; Message -> - io_lib:format("~s (~100p) << ~100p ~n", [TS,From,Message]) + io_lib:format("~s (~100p) << ~100p~n", [TS,From,Message]) end; 'send' -> Message = element(4, Trace), To = element(5, Trace), - io_lib:format("~s (~100p) ~100p ! ~100p ~n", [TS,From,To,Message]); + io_lib:format("~s (~100p) ~100p ! ~100p~n", [TS,From,To,Message]); call -> case element(4, Trace) of MFA when Size == 5 -> Message = element(5, Trace), io_lib:format("~s (~100p) call ~s (~100p) ~n", [TS,From,ffunc(MFA),Message]); MFA -> - io_lib:format("~s (~100p) call ~s ~n", [TS,From,ffunc(MFA)]) + io_lib:format("~s (~100p) call ~s~n", [TS,From,ffunc(MFA)]) end; return_from -> MFA = element(4, Trace), Ret = element(5, Trace), - io_lib:format("~s (~100p) returned from ~s -> ~100p ~n", [TS,From,ffunc(MFA),Ret]); + io_lib:format("~s (~100p) returned from ~s -> ~100p~n", [TS,From,ffunc(MFA),Ret]); return_to -> MFA = element(4, Trace), - io_lib:format("~s (~100p) returning to ~s ~n", [TS,From,ffunc(MFA)]); + io_lib:format("~s (~100p) returning to ~s~n", [TS,From,ffunc(MFA)]); spawn when Size == 5 -> Pid = element(4, Trace), MFA = element(5, Trace), - io_lib:format("~s (~100p) spawn ~100p as ~s ~n", [TS,From,Pid,ffunc(MFA)]); + io_lib:format("~s (~100p) spawn ~100p as ~s~n", [TS,From,Pid,ffunc(MFA)]); Op -> - io_lib:format("~s (~100p) ~100p ~s ~n", [TS,From,Op,ftup(Trace,4,Size)]) + io_lib:format("~s (~100p) ~100p ~s~n", [TS,From,Op,ftup(Trace,4,Size)]) end. %%% These f* functions returns non-flat strings @@ -567,7 +791,7 @@ ftup(Trace, Index, Size) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -write_file(Frame, Filename, TraceOps, MatchSpecs, TPs) -> +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]) @@ -581,6 +805,7 @@ write_file(Frame, Filename, TraceOps, MatchSpecs, TPs) -> "%%%\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] ], case file:write_file(Filename, list_to_binary(Str)) of @@ -602,8 +827,9 @@ read_settings(Filename, #state{match_specs=Ms0, def_trace_opts=TO0} = State) -> {ok, Terms} -> Ms = lists:usort(Ms0 ++ [parse_ms(MsList) || {ms, MsList} <- Terms]), TOs = lists:usort(TO0 ++ proplists:get_value(traceopts, Terms, [])), + Out = proplists:get_value(output, Terms, []), lists:foldl(fun parse_tp/2, - State#state{match_specs=Ms, def_trace_opts=TOs}, + State#state{match_specs=Ms, def_trace_opts=TOs, output=Out}, Terms); {error, _} -> observer_wx:create_txt_dialog(State#state.panel, "Could not load settings", @@ -626,3 +852,24 @@ parse_tp({tp, Mod, FAs}, State) -> do_add_patterns({Mod, Patterns}, State); parse_tp(_, State) -> State. + +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_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index bad05ec016..6a634e06f0 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -21,9 +21,8 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). --export([process_trace/2, trace_pattern/4]). - --compile(export_all). +-export([process_trace/2, trace_pattern/4, select_nodes/2, + output/2, select_matchspec/3]). process_trace(Parent, Default) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", @@ -53,19 +52,15 @@ process_trace(Parent, Default) -> check_box(LinkBox, LinkBool), enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]), enable(LinkBox, [LinkAllRadio, LinkFirstRadio]), - wxRadioButton:setValue(SpwnAllRadio, lists:member(on_spawn, Default)), - wxRadioButton:setValue(SpwnFirstRadio, lists:member(on_first_spawn, Default)), - wxRadioButton:setValue(LinkAllRadio, lists:member(on_link, Default)), - wxRadioButton:setValue(LinkFirstRadio, lists:member(on_first_link, Default)), - - wxSizer:add(LeftSz, FuncBox, []), - wxSizer:add(LeftSz, SendBox, []), - wxSizer:add(LeftSz, RecBox, []), - wxSizer:add(LeftSz, EventBox, []), + [wxRadioButton:setValue(Radio, lists:member(Opt, 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, 150, -1), - wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}]), - wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}]), + wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}, {proportion,1}]), + wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}, {proportion,1}]), wxPanel:setSizer(Panel, PanelSz), wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]), Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), @@ -81,26 +76,26 @@ process_trace(Parent, Default) -> enable(LinkBox, [LinkAllRadio, LinkFirstRadio]) end}]), - Res = case wxDialog:showModal(Dialog) of - ?wxID_OK -> - All = [{SendBox, send}, {RecBox, 'receive'}, - {FuncBox, functions}, {EventBox, events}, - {{SpawnBox, SpwnAllRadio}, on_spawn}, - {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, - {{LinkBox, LinkAllRadio}, on_link}, - {{LinkBox, LinkFirstRadio}, on_first_link}], - Check = fun({Box, Radio}) -> - wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio); - (Box) -> - wxCheckBox:getValue(Box) - end, - Opts = [Id || {Tick, Id} <- All, Check(Tick)], - {ok, lists:reverse(Opts)}; - ?wxID_CANCEL -> - cancel - end, - wxDialog:destroy(Dialog), - Res. + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + All = [{SendBox, send}, {RecBox, 'receive'}, + {FuncBox, functions}, {EventBox, events}, + {{SpawnBox, SpwnAllRadio}, on_spawn}, + {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, + {{LinkBox, LinkAllRadio}, on_link}, + {{LinkBox, LinkFirstRadio}, on_first_link}], + Check = fun({Box, Radio}) -> + wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio); + (Box) -> + wxCheckBox:getValue(Box) + end, + Opts = [Id || {Tick, Id} <- All, Check(Tick)], + wxDialog:destroy(Dialog), + lists:reverse(Opts); + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> try @@ -111,6 +106,10 @@ trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> catch cancel -> cancel end. +select_nodes(Parent, Nodes) -> + Choices = [{atom_to_list(X), X} || X <- Nodes], + check_selector(Parent, Choices). + module_selector(Parent, Node) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, @@ -167,6 +166,17 @@ module_selector(Parent, Node) -> end. function_selector(Parent, Node, Module) -> + Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]), + Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, + not(erl_internal:guard_bif(Name, Arity))]), + ParsedChoices = parse_function_names(Choices), + case check_selector(Parent, ParsedChoices) of + [] -> [{Module, '_', '_'}]; + FAs -> + [{Module, F, A} || {F,A} <- FAs] + end. + +check_selector(Parent, ParsedChoices) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Functions", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, {size, {400, 400}}]), @@ -195,10 +205,6 @@ function_selector(Parent, Node, Module) -> wxWindow:setSizer(Dialog, MainSz), wxWindow:setFocus(TxtCtrl), %% Init - Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]), - Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, - not(erl_internal:guard_bif(Name, Arity))]), - ParsedChoices = parse_function_names(Choices), filter_listbox_data("", ParsedChoices, ListBox, false), %% Setup Event handling wxTextCtrl:connect(TxtCtrl, command_text_updated, @@ -242,22 +248,19 @@ function_selector(Parent, Node, Module) -> case wxDialog:showModal(Dialog) of ?wxID_OK -> wxDialog:destroy(Dialog), - case get_checked_funcs(ListBox, []) of - [] -> [{Module, '_', '_'}]; - FAs -> - [{Module, F, A} || {F,A} <- FAs] - end; + get_checked(ListBox, []); ?wxID_CANCEL -> wxDialog:destroy(Dialog), + get_checked(ListBox, []), throw(cancel) end. -get_checked_funcs(ListBox, Acc) -> +get_checked(ListBox, Acc) -> receive {ListBox, true, FA} -> - get_checked_funcs(ListBox, [FA|lists:delete(FA,Acc)]); + get_checked(ListBox, [FA|lists:delete(FA,Acc)]); {ListBox, false, FA} -> - get_checked_funcs(ListBox, lists:delete(FA,Acc)) + get_checked(ListBox, lists:delete(FA,Acc)) after 0 -> lists:reverse(Acc) end. @@ -370,6 +373,38 @@ select_matchspec(Pid, Parent, MatchSpecs) -> throw(cancel) end. +output(Parent, Default) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + MainSz = wxBoxSizer:new(?wxVERTICAL), + PanelSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Output"}]), + + %% Output select + WinB = wxCheckBox:new(Panel, ?wxID_ANY, "Window", []), + check_box(WinB, proplists:get_value(window, Default, true)), + ShellB = wxCheckBox:new(Panel, ?wxID_ANY, "Shell", []), + check_box(ShellB, proplists:get_value(shell, Default, false)), + [wxSizer:add(PanelSz, CheckBox, []) || CheckBox <- [WinB, ShellB]], + GetFileOpts = ttb_file_options(Panel, PanelSz, Default), + %% Set sizer and show dialog + wxPanel:setSizer(Panel, PanelSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {proportion,1}, {border, 3}]), + 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 -> + Res = [{window, wxCheckBox:getValue(WinB)}, + {shell, wxCheckBox:getValue(ShellB)} | GetFileOpts()], + wxDialog:destroy(Dialog), + Res; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. + edit_ms(TextCtrl, Label0, Parent) -> Str = ensure_last_is_dot(wxStyledTextCtrl:getText(TextCtrl)), try @@ -570,3 +605,69 @@ ensure_last_is_dot(String) -> false -> String ++ "." end. + +ttb_file_options(Panel, Sizer, Default) -> + Top = wxBoxSizer:new(?wxVERTICAL), + NameS = wxBoxSizer:new(?wxHORIZONTAL), + FileBox = wxCheckBox:new(Panel, ?wxID_ANY, "File (Using ttb file tracer)", []), + check_box(FileBox, proplists:get_value(file, Default, false)), + wxSizer:add(Sizer, FileBox), + Desc = wxStaticText:new(Panel, ?wxID_ANY, "File"), + FileName = proplists:get_value(filename, Default, "ttb"), + FileT = wxTextCtrl:new(Panel, ?wxID_ANY, [{size, {150,-1}}, {value, FileName}]), + FileB = wxButton:new(Panel, ?wxID_ANY, [{label, "Browse"}]), + wxSizer:add(NameS, Desc, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(NameS, FileT, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(NameS, FileB, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]), + + WrapB = wxCheckBox:new(Panel, ?wxID_ANY, "Wrap logs"), + WrapSz = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_sz, Default, 128), + 64, 10*1024, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]), + WrapC = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_c, Default, 8), + 2, 100, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]), + + wxSizer:add(Top, NameS, [{flag, ?wxEXPAND}]), + wxSizer:add(Top, WrapB, []), + wxSizer:add(Top, WrapSz,[{flag, ?wxEXPAND}]), + wxSizer:add(Top, WrapC, [{flag, ?wxEXPAND}]), + wxSizer:add(Sizer, Top, [{flag, ?wxEXPAND bor ?wxLEFT},{border, 20}]), + + Enable = fun(UseFile, UseWrap0) -> + UseWrap = UseFile andalso UseWrap0, + [wxWindow:enable(W, [{enable, UseFile}]) || W <- [Desc,FileT,FileB,WrapB]], + [wxWindow:enable(W, [{enable, UseWrap}]) || W <- [WrapSz, WrapC]], + check_box(WrapB, UseWrap0) + end, + Enable(proplists:get_value(file, Default, false), proplists:get_value(wrap, Default, false)), + wxPanel:connect(FileBox, command_checkbox_clicked, + [{callback, fun(_,_) -> + Enable(wxCheckBox:getValue(FileBox), + wxCheckBox:getValue(WrapB)) + end}]), + wxPanel:connect(WrapB, command_checkbox_clicked, + [{callback, fun(_,_) -> + Enable(true, wxCheckBox:getValue(WrapB)) + end}]), + wxPanel:connect(FileB, command_button_clicked, + [{callback, fun(_,_) -> get_file(FileT) end}]), + fun() -> + [{file, wxCheckBox:getValue(FileBox)}, + {filename, wxTextCtrl:getValue(FileT)}, + {wrap, wxCheckBox:getValue(WrapB)}, + {wrap_sz, wxSlider:getValue(WrapSz)}, + {wrap_c, wxSlider:getValue(WrapC)}] + end. + +get_file(Text) -> + Str = wxTextCtrl:getValue(Text), + Dialog = wxFileDialog:new(Text, + [{message, "Select a file"}, + {default_file, Str}]), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Dir = wxFileDialog:getDirectory(Dialog), + File = wxFileDialog:getFilename(Dialog), + wxTextCtrl:setValue(Text, filename:join(Dir, File)); + _ -> ok + end, + wxFileDialog:destroy(Dialog). diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index f9dec1ab1b..65380a49b0 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -302,6 +302,10 @@ handle_info({nodedown, Node}, create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), {noreply, State3}; +handle_info({'EXIT', _Pid, _Reason}, State) -> + io:format("Child crashed exiting: ~p ~p~n", [_Pid,_Reason]), + {stop, normal, State}; + handle_info(_Info, State) -> {noreply, State}. -- cgit v1.2.3