aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src/observer_trace_wx.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer/src/observer_trace_wx.erl')
-rw-r--r--lib/observer/src/observer_trace_wx.erl668
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.