%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2011-2016. 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_trace_wx). -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]). -behaviour(wx_object). -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). -define(SAVE_TRACEOPTS, 305). -define(LOAD_TRACEOPTS, 306). -define(TOGGLE_TRACE, 307). -define(ADD_NEW_PROCS, 308). -define(ADD_NEW_PORTS, 309). -define(ADD_TP, 310). -define(TRACE_OUTPUT, 311). -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). -define(REMOVE_NODES, 332). -define(PROC_WIN, 340). -define(EDIT_PROCS, 341). -define(REMOVE_PROCS, 342). -define(PORT_WIN, 350). -define(EDIT_PORTS, 351). -define(REMOVE_PORTS, 352). -define(MODULES_WIN, 360). -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, proc_view, port_view, m_view, f_view, %% The listCtrl's logwin, %% The latest log window nodes = [], toggle_button, 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(titem, {id, opts}). 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_ports(Tracer, Ports) when is_list(Ports) -> wx_object:cast(Tracer, {add_ports, Ports}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Notebook, ParentPid]) -> wx:batch(fun() -> create_window(Notebook, ParentPid) end). create_window(Notebook, ParentPid) -> %% Create the window Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, {style, ?SASH_STYLE}]), {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, [{sashPosition,368}]), 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, [{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_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}]), wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN}, {border, 5}, {proportion,0}]), wxWindow:setSizer(Panel, Sizer), {Panel, #state{parent=ParentPid, panel=Panel, n_view=NodeView, proc_view=ProcessView, port_view=PortView, m_view=ModView, f_view=FuncView, toggle_button = ToggleButton, match_specs=default_matchspecs()}}. default_matchspecs() -> [{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_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}]), 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), AddProc = fun({Name, Align, DefSize}, Col) -> wxListItem:setText(Li, Name), wxListItem:setAlign(Li, Align), wxListCtrl:insertColumn(Procs, Col, Li), wxListCtrl:setColumnWidth(Procs, Col, DefSize), Col + 1 end, 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, 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, Ports}. create_matchspec_view(Parent) -> Panel = wxPanel:new(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}]), 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"), wxListCtrl:insertColumn(Funcs, 0, Li), wxListCtrl:setColumnWidth(Funcs, 0, 150), wxListItem:setText(Li, "Match Spec"), 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, 155}]), wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), 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_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"}]}, {"Options", [#create_menu{id = ?TRACE_OUTPUT, text = "Output"}, #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"}]} ], observer_wx:create_menus(Parent, Menus). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%Main window handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> case wx:getObjectType(Obj) =:= wxListCtrl of true -> observer_lib:set_listctrl_col_size(Obj, W); false -> ok end, {noreply, State}; 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 = #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; handle_event(#wx{id=?ADD_TP}, State = #state{panel=Parent, nodes=Nodes, match_specs=Ms}) -> Node = case Nodes of [N|_] -> N; [] -> node() end, case observer_traceoptions_wx:trace_pattern(self(), Parent, Node, Ms) of cancel -> {noreply, State}; Patterns -> {noreply, do_add_patterns(Patterns, State)} end; 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, tports = TPorts, tpatterns = TPs0, toggle_button = ToggleBtn, output = Opts } = State) -> try TPs = dict:to_list(TPs0), (TProcs == []) andalso (TPorts == []) andalso throw({error, "No processes or ports traced"}), (Nodes == []) andalso throw({error, "No nodes traced"}), 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, TPorts), 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(#wx{id=?LOG_CLEAR, userData=TCtrl}, State) -> wxTextCtrl:clear(TCtrl), {noreply, State}; 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 = ?SAVE_TRACEOPTS}, #state{panel = Panel, def_proc_flags = ProcFlags, def_port_flags = PortFlags, match_specs = MatchSpecs, 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, ProcFlags, PortFlags, MatchSpecs, Output, dict:to_list(TracePatterns) ); _ -> ok end, wxDialog:destroy(Dialog), {noreply, 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 -> Path = wxFileDialog:getPath(Dialog), read_settings(Path, State); _ -> State end, 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), {noreply, State}; handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Procs} = State) -> try [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Procs, Tpids), Opts = observer_traceoptions_wx:process_trace(Panel, DefOpts), 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, proc_view=LCtrl} = State) -> Selected = get_selected_items(LCtrl, Tpids), Pids = Tpids -- Selected, update_p_view(Pids, LCtrl), {noreply, State#state{tpids=Pids}}; 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_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; 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 _:_ -> cancel 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 } = State) -> try 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; 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, ?LINE, ID, What]), {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_call(Msg, From, _State) -> error({unhandled_call, Msg, From}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) -> try Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), 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} end; handle_cast(Msg, _State) -> error({unhandled_cast, Msg}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_info({active, _Node}, State=#state{parent=Parent}) -> create_menues(Parent), {noreply, State}; handle_info(not_active, State) -> {noreply, State}; handle_info({update_ms, NewMs}, State) -> {noreply, State#state{match_specs=NewMs}}; handle_info(Any, State) -> io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]), {noreply, State}. terminate(_Reason, #state{nodes=_Nodes}) -> ttb:stop(nofetch), ok. code_change(_, _, State) -> {ok, 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{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 -> %% if dynamicly updates add trace patterns for new nodes All = ordsets:union(NewNs, Ns0), update_nodes_view(All, Nview), All end, {Ps, Nodes} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% update_p_view(PidsOrPorts, LCtrl) -> %% pid- or port-view wxListCtrl:deleteAllItems(LCtrl), 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(Id)), wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)), Row+1 end, 0, PidsOrPorts). 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), wx:foldl(fun(Mod, Row) -> _Item = wxListCtrl:insertItem(LCtrl, Row, ""), ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Mod)), (Mod =:= Module) andalso wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), Row+1 end, 0, Mods). update_functions_view(Funcs, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), 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), 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). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 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, [], [], []). 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) when element(El, N) == element(El, O) -> merge(Ns, Os, El, New, [{O,N}|Ch], [N|All]); merge([N|Ns], Os=[O|_], El, New, Ch, All) when element(El, N) < element(El, O) -> merge(Ns, Os, El, [N|New], Ch, [N|All]); merge(Ns=[N|_], [O|Os], El, New, Ch, All) when element(El, N) > element(El, O) -> merge(Ns, Os, El, New, Ch, [O|All]); merge([], Os, _El, New, Ch, All) -> {lists:reverse(All, Os), New, Ch}; 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 ToFile 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, TPorts) -> _R1 = [setup_tps(FTP, []) || {_, FTP} <- TPs], _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 setup_tps([First=#tpattern{fa={_,'_'}}|Rest], Prev) -> setup_tp(First), [setup_tp(TP) || TP <- lists:reverse(Prev)], setup_tps(Rest, []); setup_tps([First=#tpattern{fa={F,_}}|Rest], Prev = [#tpattern{fa={F,_}}|_]) -> setup_tps(Rest, [First|Prev]); setup_tps([First|Rest], Prev) -> [setup_tp(TP) || TP <- lists:reverse(Prev)], setup_tps(Rest, [First]); 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). dbg_flags(Type,Flags) -> [dbg_flag(Type,Flag) || Flag <- Flags]. 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)); textformat(Trace) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 -> io_lib:format("*** Dropped ~p messages.~n", [element(2,Trace)]); textformat(Trace) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 -> io_lib:format("*** Seq trace not implmented.~n", []); textformat(_) -> "". format_trace(Trace, Size, TS0={_,_,MS}) -> {_,{H,M,S}} = calendar:now_to_local_time(TS0), TS = io_lib:format("~.2.0w:~.2.0w:~.2.0w:~.6.0w", [H,M,S,MS]), From = element(2, Trace), case element(3, Trace) of 'receive' -> case element(4, Trace) of {dbg,ok} -> ""; 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]); 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)]) 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]); return_to -> MFA = element(4, Trace), 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)]); Op -> io_lib:format("~s (~100p) ~100p ~s~n", [TS,From,Op,ftup(Trace,4,Size)]) end. %%% These f* functions returns non-flat strings %% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" %% {M,F,A} -> "M:F/A" ffunc({M,F,Argl}) when is_list(Argl) -> io_lib:format("~100p:~100p(~s)", [M, F, fargs(Argl)]); ffunc({M,F,Arity}) -> io_lib:format("~100p:~100p/~100p", [M,F,Arity]); ffunc(X) -> io_lib:format("~100p", [X]). %% Integer -> "Integer" %% [A1, A2, ..., AN] -> "A1, A2, ..., AN" fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); fargs([]) -> []; fargs([A]) -> io_lib:format("~100p", [A]); %% last arg fargs([A|Args]) -> [io_lib:format("~100p,", [A]) | fargs(Args)]; fargs(A) -> io_lib:format("~100p", [A]). % last or only arg %% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" ftup(Trace, Index, Index) -> io_lib:format("~100p", [element(Index, Trace)]); ftup(Trace, Index, Size) -> [io_lib:format("~100p ", [element(Index, Trace)]) | ftup(Trace, Index+1, Size)]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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", [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; {error, Reason} -> FailMsg = file:format_error(Reason), observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR) end. read_settings(Filename, #state{match_specs=Ms0, def_proc_flags=ProcFs0, def_port_flags=PortFs0} = State) -> case file:consult(Filename) of {ok, 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_proc_flags=ProcFs, def_port_flags=PortFs, output=Out}, Terms); {error, _} -> observer_wx:create_txt_dialog(State#state.panel, "Could not load settings", "Error", ?wxICON_ERROR), State end. 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=make_ms(List)} || {F,A,List} <- FAs], 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(_, _, _) -> [].