aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Gudmundsson <[email protected]>2011-11-24 12:54:13 +0100
committerDan Gudmundsson <[email protected]>2011-11-24 12:54:13 +0100
commitd7eaea0d44fac1f0c67d67c188d6a635b0a4da4c (patch)
tree29acee8c984b45dc674bacc5191b5d6724bf4f55
parentfcda85751547c4dbcea92f1142186ec9d571ba82 (diff)
downloadotp-d7eaea0d44fac1f0c67d67c188d6a635b0a4da4c.tar.gz
otp-d7eaea0d44fac1f0c67d67c188d6a635b0a4da4c.tar.bz2
otp-d7eaea0d44fac1f0c67d67c188d6a635b0a4da4c.zip
[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
-rw-r--r--lib/observer/src/Makefile4
-rw-r--r--lib/observer/src/observer_defs.hrl17
-rw-r--r--lib/observer/src/observer_lib.erl14
-rw-r--r--lib/observer/src/observer_pro_wx.erl52
-rw-r--r--lib/observer/src/observer_sys_wx.erl24
-rw-r--r--lib/observer/src/observer_trace_wx.erl549
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl193
-rw-r--r--lib/observer/src/observer_wx.erl4
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}.