diff options
Diffstat (limited to 'lib/observer/src/observer_trace_wx.erl')
-rw-r--r-- | lib/observer/src/observer_trace_wx.erl | 668 |
1 files changed, 396 insertions, 272 deletions
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index b79358193e..23b9b1fe6b 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -18,7 +18,7 @@ -module(observer_trace_wx). --export([start/6]). +-export([start_link/2, add_processes/2]). -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). @@ -33,154 +33,214 @@ -define(CLEAR, 304). -define(SAVE_TRACEOPTS, 305). -define(LOAD_TRACEOPTS, 306). - - --record(state, { - parent, - frame, - text_ctrl, - trace_options, - toggle_button, - node, - traceoptions_open, - traced_procs, - traced_funcs = dict:new(), % Key =:= Module::atom, Value =:= [ #traced_func ] - match_specs = []}). % [ #match_spec{} ] - - -start(Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid) -> - wx_object:start(?MODULE, [Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid], []). - -init([Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid]) -> - State = - wx:batch(fun() -> - create_window(ParentFrame, TraceOpts) - end), - - Frame = State#state.frame, - TraceOpts2 = State#state.trace_options, - TracedFuncs = State#state.traced_funcs, - - wx_object:start(observer_traceoptions_wx, - [Frame, self(), Node, TraceOpts2, TracedFuncs, MatchSpecs], - []), - - {Frame, State#state{parent = ParentPid, - node = Node, - traced_procs = TracedProcs, - match_specs = MatchSpecs, - traceoptions_open = true}}. - - -create_window(ParentFrame, TraceOpts) -> +-define(TOGGLE_TRACE, 307). +-define(ADD_NEW, 308). +-define(ADD_TP, 309). +-define(PROCESSES, 350). +-define(MODULES, 351). +-define(FUNCTIONS, 352). + +-record(state, + {parent, + panel, + p_view, + m_view, + f_view, + nodes = [], + toggle_button, + tpids = [], %% #tpid + def_trace_opts = [], + tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec} + match_specs = []}). % [ #match_spec{} ] + +-record(tpid, {pid, 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}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([Notebook, ParentPid]) -> + wx:batch(fun() -> create_window(Notebook, ParentPid) end). + +create_window(Notebook, ParentPid) -> %% Create the window - Frame = wxFrame:new(ParentFrame, ?wxID_ANY, "Tracer", - [{style, ?wxDEFAULT_FRAME_STYLE}, - {size, {900, 900}}]), - wxFrame:connect(Frame, close_window,[{skip,true}]), - Panel = wxPanel:new(Frame, []), + Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), - - %% Menues - MenuBar = wxMenuBar:new(), - create_menues(MenuBar), - wxFrame:setMenuBar(Frame, MenuBar), - wxMenu:connect(Frame, command_menu_selected, []), - + Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}]), + 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), + wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]), %% Buttons - ToggleButton = wxToggleButton:new(Panel, ?wxID_ANY, "Start Trace", []), - wxSizer:add(Sizer, ToggleButton, [{flag, ?wxALL}, - {border, 5}]), - wxMenu:connect(ToggleButton, command_togglebutton_clicked, []), - - TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY, - [{style,?wxTE_READONLY bor - ?wxTE_MULTILINE}, - {size, {400, 300}}]), - - wxSizer:add(Sizer, TxtCtrl, [{proportion, 1}, - {flag, ?wxEXPAND}]), - - %% Display window + 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}]), wxWindow:setSizer(Panel, Sizer), - wxFrame:show(Frame), - #state{frame = Frame, - text_ctrl = TxtCtrl, - toggle_button = ToggleButton, - trace_options = TraceOpts#trace_options{main_window = false}}. - -create_menues(MenuBar) -> + {Panel, #state{parent=ParentPid, panel=Panel, + p_view=ProcessView, m_view=ModView, f_view=FuncView, + 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"}], + [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}]), + 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 = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120}, + {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], + lists:foldl(AddListEntry, 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}]), + + wxWindow:setFocus(Grid), + Grid. + +create_matchspec_view(Parent) -> + Panel = wxPanel:new(Parent), + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Splitter = wxSplitterWindow:new(Panel, []), + Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES}, {style, Style}]), + Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCTIONS}, {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, 150}]), + 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_selected), + 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"}, - separator, - #create_menu{id = ?SAVE_BUFFER, text = "Save buffer"}, - separator, - #create_menu{id = ?CLOSE, text = "Close"} - ]}, - {"View", [#create_menu{id = ?CLEAR, text = "Clear buffer"}]}, - {"Options", [#create_menu{id = ?OPTIONS, text = "Trace options"}]} - ], - observer_lib:create_menus(Menus, MenuBar, new_window). - + #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}] + }], + observer_wx:create_menus(Parent, Menus). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %Main window - -handle_event(#wx{id = ?CLOSE, event = #wxCommand{type = command_menu_selected}}, - #state{parent = Parent, - trace_options = TraceOpts, - match_specs = MatchSpecs} = State) -> - Parent ! {tracemenu_closed, TraceOpts, MatchSpecs}, - {stop, shutdown, State}; - -handle_event(#wx{id = ?OPTIONS, event = #wxCommand{type = command_menu_selected}}, - #state{frame = Frame, trace_options = TraceOpts, - traced_funcs = TracedFuncs, - node = Node, - match_specs = MatchSpecs, - traceoptions_open = false} = State) -> - - wx_object:start(observer_traceoptions_wx, - [Frame, self(), Node, TraceOpts, TracedFuncs, MatchSpecs], - []), - - {noreply, State#state{traceoptions_open = true}}; - -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), +%%Main window +handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> + case wx:getObjectType(Obj) =:= wxListCtrl of + true -> + wx:batch(fun() -> + Cols = wxListCtrl:getColumnCount(Obj), + Last = lists:foldl(fun(I, Last) -> + Last - wxListCtrl:getColumnWidth(Obj, I) + end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)), + Size = max(150, Last), + wxListCtrl:setColumnWidth(Obj, Cols-1, Size) + end); + false -> ok end, {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} + 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, 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{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}}, - #state{frame = Frame, - trace_options = TraceOpts, - match_specs = MatchSpecs} = State) -> - Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + #state{panel = Panel, + def_trace_opts = TraceOpts, + match_specs = MatchSpecs, + tpatterns = TracePatterns + } = 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(Frame, Path, TraceOpts, MatchSpecs); + write_file(Panel, Path, TraceOpts, MatchSpecs, dict:to_list(TracePatterns)); _ -> ok end, @@ -189,8 +249,8 @@ handle_event(#wx{id = ?SAVE_TRACEOPTS, handle_event(#wx{id = ?LOAD_TRACEOPTS, event = #wxCommand{type = command_menu_selected}}, - #state{frame = Frame} = State) -> - Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_FILE_MUST_EXIST}]), + #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), @@ -202,92 +262,180 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS, {noreply, State2}; -handle_event(#wx{event = #wxClose{type = close_window}}, - #state{parent = Parent, - trace_options = TraceOpts, - match_specs = MatchSpecs} = State) -> - Parent ! {tracemenu_closed, TraceOpts, MatchSpecs}, - {stop, shutdown, State}; - -handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, - #state{node = Node, - traced_procs = TracedProcs, - traced_funcs = TracedDict, - trace_options = TraceOpts, - text_ctrl = TextCtrl, - toggle_button = ToggleBtn} = State) -> - - start_trace(Node, TracedProcs, TracedDict, TraceOpts), - wxTextCtrl:appendText(TextCtrl, "Start Trace:\n"), - wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), - {noreply, State}; - -handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, %%Stop tracing - #state{text_ctrl = TxtCtrl, - toggle_button = ToggleBtn} = State) -> - dbg:stop_clear(), - wxTextCtrl:appendText(TxtCtrl, "Stop Trace.\n"), - wxToggleButton:setLabel(ToggleBtn, "Start Trace"), - {noreply, State}; - +%% handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, +%% #state{node = Node, +%% traced_procs = TracedProcs, +%% traced_funcs = TracedDict, +%% trace_options = TraceOpts, +%% text_ctrl = TextCtrl, +%% toggle_button = ToggleBtn} = State) -> + +%% start_trace(Node, TracedProcs, TracedDict, TraceOpts), +%% wxTextCtrl:appendText(TextCtrl, "Start Trace:\n"), +%% wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), +%% {noreply, State}; + +%% handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, %%Stop tracing +%% #state{text_ctrl = TxtCtrl, +%% toggle_button = ToggleBtn} = State) -> +%% dbg:stop_clear(), +%% wxTextCtrl:appendText(TxtCtrl, "Stop Trace.\n"), +%% wxToggleButton:setLabel(ToggleBtn, "Start Trace"), +%% {noreply, State}; + +handle_event(#wx{id=ID, event = What}, State) -> + io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, self(), ID, What]), + {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. -handle_event(#wx{event = What}, State) -> - io:format("~p~p: Unhandled event: ~p ~n", [?MODULE, self(), What]), +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +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 -> + {noreply, State} + end; +handle_cast(Msg, State) -> + io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_info({updated_traceopts, - TraceOpts, - MatchSpecs, - TracedFuncs}, State) -> - {noreply, State#state{trace_options = TraceOpts, - match_specs = MatchSpecs, - traced_funcs = TracedFuncs, - traceoptions_open = false}}; - -handle_info(traceopts_closed, State) -> - {noreply, State#state{traceoptions_open = false}}; - -handle_info(Tuple, #state{text_ctrl = TxtCtrl} = State) when is_tuple(Tuple) -> - Text = textformat(Tuple), - wxTextCtrl:appendText(TxtCtrl, lists:flatten(Text)), +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{node = Node, - frame = Frame}) -> - try - case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of - undefined -> fine; - Pid -> exit(Pid, kill) - end, - io:format("~p terminating tracemenu. Reason: ~p~n", [?MODULE, Reason]), - wxFrame:destroy(Frame), - ok - catch error:{badrpc, _} -> - observer_wx:return_to_localnode(Frame, Node), - wxFrame:destroy(Frame) - end. +terminate(Reason, #state{nodes=Nodes}) -> + %% case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of + %% undefined -> fine; + %% Pid -> exit(Pid, kill) + %% end, + ok. code_change(_, _, State) -> {stop, not_yet_implemented, State}. -handle_call(Msg, _From, State) -> - io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), - {reply, ok, State}. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_cast(Msg, State) -> - io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), - {noreply, State}. +do_add_processes(POpts, S0=#state{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)]), + 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) + 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) -> + _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, 1, observer_lib:to_str(Opts)), + 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_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{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})), + wxListCtrl:setItem(LCtrl, Row, 1, Ms), + Row+1 + 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, [], [], []). + +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}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start_trace(Node, TracedProcs, TracedDict, #trace_options{send = Send, treceive = Receive, functions = Functions, @@ -385,10 +533,9 @@ print(Num, X, Buff) -> trace_functions(TracedDict) -> Trace = fun(KeyAtom, RecordList, acc_in) -> - lists:foreach(fun(#traced_func{func_name = Function, arity = Arity, - match_spec = #match_spec{term_ms = MS}}) -> + match_spec = #match_spec{term = MS}}) -> dbg:tpl({KeyAtom, Function, Arity}, MS) end, RecordList), @@ -396,37 +543,23 @@ trace_functions(TracedDict) -> end, dict:fold(Trace, acc_in, TracedDict). - -write_file(Frame, Filename, #trace_options{send = Send, - treceive = Receive, - functions = Functions, - events = Events, - on_1st_spawn = On1stSpawn, - on_all_spawn = OnAllSpawn, - on_1st_link = On1stLink, - on_all_link = OnAllLink}, - MatchSpecs) -> - - FormattedMatchSpecs = lists:flatten(lists:foldl( - fun(#match_spec{alias = A, term_ms = T, fun2ms = F}, Acc) -> - [io_lib:format("{alias, ~p, term_ms, ~p, fun2ms, ~p}.\n", - [A, T, F]) | Acc] - end, [], MatchSpecs)), - - Binary = - list_to_binary("%%%\n%%% This file is generated by Observer\n" - "%%%\n%%% DO NOT EDIT!\n%%%\n" - "{send, " ++ atom_to_list(Send) ++ "}.\n" - "{treceive, " ++ atom_to_list(Receive) ++ "}.\n" - "{functions, " ++ atom_to_list(Functions) ++ "}.\n" - "{events, " ++ atom_to_list(Events) ++ "}.\n" - "{on_1st_spawn, " ++ atom_to_list(On1stSpawn) ++ "}.\n" - "{on_all_spawn, " ++ atom_to_list(OnAllSpawn) ++ "}.\n" - "{on_1st_link, " ++ atom_to_list(On1stLink) ++ "}.\n" - "{on_all_link, " ++ atom_to_list(OnAllLink) ++ "}.\n" - ++ FormattedMatchSpecs), - - case file:write_file(Filename, Binary) of +write_file(Frame, Filename, TraceOps, MatchSpecs, 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]) + end, + 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", + [FormatTP(TP) || TP <- TPs] + ], + case file:write_file(Filename, list_to_binary(Str)) of ok -> success; {error, Reason} -> @@ -434,47 +567,38 @@ write_file(Frame, Filename, #trace_options{send = Send, 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{frame = Frame} = State) -> +read_settings(Filename, #state{match_specs=Ms0, def_trace_opts=TO0} = State) -> case file:consult(Filename) of {ok, Terms} -> - {TraceOpts, MatchSpecs} = parse_settings(Terms, {#trace_options{}, []}), - State#state{trace_options = TraceOpts, match_specs = MatchSpecs}; + Ms = lists:usort(Ms0 ++ [parse_ms(MsList) || {ms, MsList} <- Terms]), + TOs = lists:usort(TO0 ++ proplists:get_value(traceopts, Terms, [])), + lists:foldl(fun parse_tp/2, + State#state{match_specs=Ms, def_trace_opts=TOs}, + Terms); {error, _} -> - observer_wx:create_txt_dialog(Frame, "Could not load settings", "Error", ?wxICON_ERROR), + observer_wx:create_txt_dialog(State#state.panel, "Could not load settings", + "Error", ?wxICON_ERROR), State end. - -parse_settings([], {TraceOpts, MatchSpecs}) -> - {TraceOpts, MatchSpecs}; -parse_settings([{send, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{send = Bool}, MS}); -parse_settings([{treceive, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{treceive = Bool}, MS}); -parse_settings([{functions, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{functions = Bool}, MS}); -parse_settings([{events, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{events = Bool}, MS}); -parse_settings([{on_1st_spawn, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{on_1st_spawn = Bool}, MS}); -parse_settings([{on_all_spawn, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{on_all_spawn = Bool}, MS}); -parse_settings([{on_1st_link, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{on_1st_link = Bool}, MS}); -parse_settings([{on_all_link, Bool} | T], {Opts, MS}) -> - parse_settings(T, {Opts#trace_options{on_all_link = Bool}, MS}); -parse_settings([{alias, A, term_ms, TermMS, fun2ms, F} | T], {Opts, MatchSpecs}) -> - Alias = case A of - undefined -> A; - _ -> lists:flatten(io_lib:format("~s", [A])) - end, - Fun2MS = case F of - undefined -> F; - _ -> lists:flatten(io_lib:format("~s", [F])) - end, - parse_settings(T, {Opts, [#match_spec{alias = Alias, - term_ms = TermMS, - str_ms = lists:flatten(io_lib:format("~p", [TermMS])), - fun2ms = Fun2MS} - | MatchSpecs]}). +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). + +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)} || + {F,A,List} <- FAs], + do_add_patterns({Mod, Patterns}, State); +parse_tp(_, State) -> + State. |