aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDan Gudmundsson <[email protected]>2011-12-09 13:28:55 +0100
committerDan Gudmundsson <[email protected]>2011-12-09 13:28:55 +0100
commit4743d3991ee56e5989563dde61bce91d625a05f4 (patch)
treedab48b92ab721f6a54ed666ddc375a3bfd2e73f3 /lib
parente3bcbd4451cdb1dd0a826810a898c2d0e9fa390d (diff)
parent9a4f23a450541fe5ed9dc1cd35b32b8621736949 (diff)
downloadotp-4743d3991ee56e5989563dde61bce91d625a05f4.tar.gz
otp-4743d3991ee56e5989563dde61bce91d625a05f4.tar.bz2
otp-4743d3991ee56e5989563dde61bce91d625a05f4.zip
Merge branch 'dgud/observer/gui/OTP-4779'
* dgud/observer/gui/OTP-4779: [observer] Add basic documentation [observer] Do not start polling tables until user view them [observer] Move rpc calls to runtime tools [observer] Added an application viewer [observer] Remove unused time from process view [observer] Change process_info stack to be listctrl [observer] Fix listctrl colum size calculation [observer] Add more trace functionality [observer] Use standard popup menu
Diffstat (limited to 'lib')
-rw-r--r--lib/observer/doc/src/Makefile2
-rw-r--r--lib/observer/doc/src/observer.xml58
-rw-r--r--lib/observer/doc/src/observer_ug.xml190
-rw-r--r--lib/observer/doc/src/part.xml3
-rw-r--r--lib/observer/doc/src/ref_man.xml3
-rw-r--r--lib/observer/src/Makefile8
-rw-r--r--lib/observer/src/observer_app_wx.erl524
-rw-r--r--lib/observer/src/observer_defs.hrl17
-rw-r--r--lib/observer/src/observer_lib.erl70
-rw-r--r--lib/observer/src/observer_pro_wx.erl134
-rw-r--r--lib/observer/src/observer_procinfo.erl76
-rw-r--r--lib/observer/src/observer_sys_wx.erl68
-rw-r--r--lib/observer/src/observer_trace_wx.erl566
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl193
-rw-r--r--lib/observer/src/observer_tv_table.erl87
-rw-r--r--lib/observer/src/observer_tv_wx.erl218
-rw-r--r--lib/observer/src/observer_wx.erl59
-rw-r--r--lib/runtime_tools/src/observer_backend.erl215
18 files changed, 1822 insertions, 669 deletions
diff --git a/lib/observer/doc/src/Makefile b/lib/observer/doc/src/Makefile
index f82a49abbe..cd9f9466ca 100644
--- a/lib/observer/doc/src/Makefile
+++ b/lib/observer/doc/src/Makefile
@@ -36,6 +36,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
XML_APPLICATION_FILES = ref_man.xml
XML_REF3_FILES = \
crashdump.xml \
+ observer.xml \
etop.xml \
ttb.xml
XML_REF6_FILES = observer_app.xml
@@ -48,6 +49,7 @@ XML_PART_FILES = \
XML_CHAPTER_FILES = \
crashdump_ug.xml \
etop_ug.xml \
+ observer_ug.xml \
ttb_ug.xml \
notes.xml \
notes_history.xml
diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml
new file mode 100644
index 0000000000..03830f2b1c
--- /dev/null
+++ b/lib/observer/doc/src/observer.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2011</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Observer</title>
+ <prepared>Dan Gudmundsson</prepared>
+ <responsible></responsible>
+ <docno>1</docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2011-12-08</date>
+ <rev>PA1</rev>
+ <file>observer.xml</file>
+ </header>
+ <module>observer</module>
+ <modulesummary>A GUI tool for observing an erlang system.</modulesummary>
+ <description>
+ <p>The observer is gui frontend containing various tools to
+ inspect a system. It displays system information, application
+ structures, process information, ets or mnesia tables and a frontend
+ for tracing with <seealso marker="ttb">ttb</seealso>.
+ </p>
+
+ <p>See the <seealso marker="observer_ug">user's guide</seealso>
+ for more information about how to get started.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>start() -> ok</name>
+ <fsummary>Start the observer gui</fsummary>
+ <desc>
+ <p>This function starts the <c>observer</c> gui.
+ Close the window to stop the application.
+ </p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml
new file mode 100644
index 0000000000..569d72e71e
--- /dev/null
+++ b/lib/observer/doc/src/observer_ug.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2011</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Observer</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>observer_ug.xml</file>
+ </header>
+
+ <section>
+ <title>Introduction</title>
+ <p>Observer, is a graphical tool for observing the characteristics of
+ erlang systems. Observer displays system information, application
+ supervisor trees, process information, ets or mnesia tables and contains
+ a frontend for erlang tracing.
+ </p>
+ </section>
+
+ <section>
+ <title>General</title>
+ <p>Normally observer should be run from a standalone node to minimize
+ the impact of the system being observed. Example:
+ </p>
+ <code>
+ > erl -sname observer -hidden -setcookie MyCookie -run observer
+ </code>
+ <p>
+ Choose which node to observe via <c>Nodes</c> menu. The <c>View/Refresh
+ Interval</c> controls how frequent the view should be updated.
+ The refresh interval is set per viewer so you can
+ have different settings for each viewer. To minimize the system
+ impact only the active viewer is updated and the other
+ views will be updated when activated.
+ </p>
+ <note>
+ <p>Only R15B nodes can be observed.</p>
+ </note>
+
+ <p> In general the mouse buttons behaves as expected, use left click
+ to select objects, right click to pop up a menu with most used
+ choices and double click to bring up information about the
+ selected object. In most viewers with several columns you can change
+ sort order by left clicking on column header.
+ </p>
+ </section>
+
+ <section>
+ <title>Applications</title>
+ <p>The <c>Applications</c> view lists application information.
+ Select an application in the left list to display its supervisor
+ tree.
+ </p>
+ <p><c>Trace process</c> will add the selected process identifier
+ to <c>Trace Overview</c> view and the node the process resides on
+ will be added as well.
+ </p>
+ <p><c>Trace named process</c> will add the
+ registered name of the process. This can be useful when tracing on
+ several nodes, then processes with that name will be traced on all traced
+ nodes.
+ </p>
+ <p><c>Trace process tree</c> and <c>Trace named process
+ tree</c> will add the selected process and all processes below,
+ right of, it to the <c>Trace Overview</c> view.
+ </p>
+ </section>
+
+ <section>
+ <title>Processes</title>
+ <p>The <c>Processes</c> view lists process information.
+ For each process the following information is presented:
+ </p>
+ <taglist>
+ <tag>Pid</tag>
+ <item>The process identifier.</item>
+ <tag>Reds</tag>
+ <item>This is the number of reductions that has been executed
+ on the process</item>
+ <tag>Memory</tag>
+ <item>This is the size of the process in bytes, obtained by a
+ call to <c>process_info(Pid,memory)</c>.</item>
+ <tag>MsgQ</tag>
+ <item>This is the length of the message queue for the process.</item>
+ </taglist>
+ <note>
+ <p><em>Reds</em> can be presented as accumulated values or as values since last update.</p>
+ </note>
+ <p><c>Trace Processes</c> will add the selected process identifiers to the <c>Trace Overview</c> view and the
+ node the processes reside on will be added as well.
+ <c>Trace Named Processes</c> will add the registered name of processes. This can be useful
+ when tracing is done on several nodes, then processes with that name will be traced on all traced nodes.
+ </p>
+ </section>
+
+ <section>
+ <title>Table Viewer</title>
+ <p>The <c>Table Viewer</c> view lists tables. By default ets tables
+ are visible and unreadable, private ets, tables and tables created by the OTP
+ applications are not visible. Use <c>View</c> menu to view "system"
+ ets tables, unreadable ets tables or mnesia tables.
+ </p>
+ <p>Double click to view the content of the table. Select table and activate <c>View/Table Information</c>
+ menu to view table information.
+ </p>
+ <p>In the table viewer you can regexp search for objects, edit and delete objects.
+ </p>
+ </section>
+
+ <section>
+ <title>Trace Overview</title>
+ <p>The <c>Trace Overview</c> view handles tracing. Tracing is done
+ by selecting which processes to be traced and how to trace
+ them. You can trace messages, function calls and events, where
+ events are process related events such as <c>spawn</c>,
+ <c>exit</c> and several others.
+ </p>
+
+ <p>When you want to trace function calls, you also need to setup
+ <c>trace patterns</c>. Trace patterns selects the function calls
+ that will be traced. The number of traced function calls can be
+ further reduced with <c>match specifications</c>. Match
+ specifications can also be used to trigger additional information
+ in the trace messages.
+ </p>
+ <note><p>Trace patterns only applies to the traced processes.</p></note>
+
+ <p>
+ Processes are added from the <c>Applications</c> or <c>Processes</c> views.
+ A special <c>new</c> identifier, meaning all processes spawned after trace start,
+ can be added with the <c>Add 'new' Process</c> button.
+ </p>
+ <p>
+ When adding processes, a window with trace options will pop up. The chosen options will
+ be set for the selected processes.
+ Process options can be changed by right clicking on a process.
+ </p>
+ <p>
+ Processes added by process identifiers will add the nodes these
+ processes resides on in the node list. Additional nodes can be added by the <c>Add
+ Nodes</c> button.
+ </p>
+ <p>
+ If function calls are traced, trace patterns must be added by <c>Add Trace Pattern</c> button.
+ Select a module, function(s) and a match specification.
+ If no functions are selected, all functions in the module will be traced.
+ A few basic match specifications are provided in the tool, and
+ you can provide your own match specifications. The syntax of match
+ specifications are described in the <seealso
+ marker="erts:match_spec">ERTS User's Guide</seealso>. To simplify
+ the writing of a match specification they can also be written as
+ <c>fun/1</c> see <seealso marker="stdlib:ms_transform">ms_transform manual page</seealso> for
+ further information.
+ </p>
+
+ <p>Use the <c>Start trace</c> button to start the trace.
+ By default trace output is written to a new window, tracing is stopped when the
+ window is closed, or with <c>Stop Trace</c> button.
+ Trace output can be changed via <c>Options/Output</c> menu.
+ The trace settings, including match specifications, can be saved to, or loaded from, a file.
+ </p>
+ <p>More information about tracing can be found in <seealso
+ marker="runtime_tools:dbg">dbg</seealso> and in the chapter "Match
+ specifications in Erlang" in <seealso marker="erts:match_spec">ERTS User's
+ Guide</seealso> and the
+ <seealso marker="stdlib:ms_transform">ms_transform manual page</seealso>.
+ </p>
+ </section>
+</chapter>
diff --git a/lib/observer/doc/src/part.xml b/lib/observer/doc/src/part.xml
index bd6c2b6c77..0d6aad09f2 100644
--- a/lib/observer/doc/src/part.xml
+++ b/lib/observer/doc/src/part.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2002</year><year>2009</year>
+ <year>2002</year><year>2011</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -31,6 +31,7 @@
<p>The <em>Observer</em> application contains tools for tracing
and investigation of distributed systems.</p>
</description>
+ <xi:include href="observer_ug.xml"/>
<xi:include href="ttb_ug.xml"/>
<xi:include href="etop_ug.xml"/>
<xi:include href="crashdump_ug.xml"/>
diff --git a/lib/observer/doc/src/ref_man.xml b/lib/observer/doc/src/ref_man.xml
index 3d37570d2d..c33ce74141 100644
--- a/lib/observer/doc/src/ref_man.xml
+++ b/lib/observer/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2002</year><year>2009</year>
+ <year>2002</year><year>2011</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -34,6 +34,7 @@
<br></br>
</description>
<xi:include href="observer_app.xml"/>
+ <xi:include href="observer.xml"/>
<xi:include href="ttb.xml"/>
<xi:include href="etop.xml"/>
<xi:include href="crashdump.xml"/>
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile
index 95954d8587..ca26afc11d 100644
--- a/lib/observer/src/Makefile
+++ b/lib/observer/src/Makefile
@@ -42,6 +42,7 @@ MODULES= \
etop_tr \
etop_txt \
observer \
+ observer_app_wx \
observer_lib \
observer_wx \
observer_pro_wx \
@@ -57,6 +58,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)
@@ -103,8 +106,7 @@ ERL_COMPILE_FLAGS += \
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-
-debug opt: $(TARGET_FILES)
+opt debug: $(TARGET_FILES)
clean:
rm -f $(TARGET_FILES)
@@ -116,6 +118,8 @@ $(APP_TARGET): $(APP_SRC) ../vsn.mk
$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
sed -e 's;%VSN%;$(VSN);' $< > $@
+$(TARGET_FILES): $(INTERNAL_HRL_FILES)
+
docs:
# ----------------------------------------------------
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
new file mode 100644
index 0000000000..62046577ad
--- /dev/null
+++ b/lib/observer/src/observer_app_wx.erl
@@ -0,0 +1,524 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(observer_app_wx).
+
+-export([start_link/2]).
+
+%% wx_object callbacks
+-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
+ handle_event/2, handle_sync_event/3, handle_cast/2]).
+
+-behaviour(wx_object).
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+-record(state,
+ {
+ parent,
+ panel,
+ apps_w,
+ app_w,
+ paint,
+ current,
+ app,
+ sel,
+ appmon
+ }).
+
+-record(paint, {font, pen, brush, sel, links}).
+
+-record(app, {ptree, n2p, links, dim}).
+-record(box, {x,y, w,h, s1}).
+-record(str, {x,y,text,pid}).
+
+-define(BX_E, 10). %% Empty width between text and box
+-define(BX_HE, (?BX_E div 2)).
+-define(BY_E, 10). %% Empty height between text and box
+-define(BY_HE, (?BY_E div 2)).
+
+-define(BB_X, 16). %% Empty width between boxes
+-define(BB_Y, 12). %% Empty height between boxes
+
+-define(DRAWAREA, 5).
+-define(ID_PROC_INFO, 101).
+-define(ID_PROC_MSG, 102).
+-define(ID_PROC_KILL, 103).
+-define(ID_TRACE_PID, 104).
+-define(ID_TRACE_NAME, 105).
+-define(ID_TRACE_TREE_PIDS, 106).
+-define(ID_TRACE_TREE_NAMES, 107).
+
+start_link(Notebook, Parent) ->
+ wx_object:start_link(?MODULE, [Notebook, Parent], []).
+
+init([Notebook, Parent]) ->
+ Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)},
+ {winid, 1}
+ ]),
+ Main = wxBoxSizer:new(?wxHORIZONTAL),
+ Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)},
+ {style, ?wxSP_LIVE_UPDATE},
+ {id, 2}
+ ]),
+ Apps = wxListBox:new(Splitter, 3, []),
+ %% Need extra panel and sizer to get correct size updates
+ %% in draw area for some reason
+ P2 = wxPanel:new(Splitter, [{winid, 4}]),
+ Extra = wxBoxSizer:new(?wxVERTICAL),
+ DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA},
+ {style,?wxFULL_REPAINT_ON_RESIZE}]),
+ wxWindow:setBackgroundColour(DrawingArea, ?wxWHITE),
+ wxWindow:setVirtualSize(DrawingArea, 800, 800),
+ wxSplitterWindow:setMinimumPaneSize(Splitter,50),
+ wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]),
+ wxWindow:setSizer(P2, Extra),
+ wxSplitterWindow:splitVertically(Splitter, Apps, P2, [{sashPosition, 150}]),
+ wxWindow:setSizer(Panel, Main),
+
+ wxSizer:add(Main, Splitter, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 1}, {border, 5}]),
+ wxWindow:setSizer(Panel, Main),
+ wxListBox:connect(Apps, command_listbox_selected),
+ wxPanel:connect(DrawingArea, paint, [callback]),
+ wxPanel:connect(DrawingArea, size, [{skip, true}]),
+ wxPanel:connect(DrawingArea, left_up),
+ wxPanel:connect(DrawingArea, left_dclick),
+ wxPanel:connect(DrawingArea, right_down),
+
+ DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
+ SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT),
+ SelBrush = wxBrush:new(SelCol),
+ LinkPen = wxPen:new(SelCol, [{width, 2}]),
+ %% GC = wxGraphicsContext:create(DrawingArea),
+ %% _Font = wxGraphicsContext:createFont(GC, DefFont),
+ {Panel, #state{parent=Parent,
+ panel =Panel,
+ apps_w=Apps,
+ app_w =DrawingArea,
+ paint=#paint{font= DefFont,
+ pen= ?wxBLACK_PEN,
+ brush=?wxLIGHT_GREY_BRUSH,
+ sel= SelBrush,
+ links=LinkPen
+ }
+ }}.
+
+setup_scrollbar(AppWin, App) ->
+ setup_scrollbar(wxWindow:getClientSize(AppWin), AppWin, App).
+
+setup_scrollbar({CW, CH}, AppWin, #app{dim={W0,H0}}) ->
+ W = max(W0,CW),
+ H = max(H0,CH),
+ PPC = 20,
+ if W0 =< CW, H0 =< CH ->
+ wxScrolledWindow:setScrollbars(AppWin, W, H, 1, 1);
+ H0 =< CH ->
+ wxScrolledWindow:setScrollbars(AppWin, PPC, H, W div PPC+1, 1);
+ W0 =< CW ->
+ wxScrolledWindow:setScrollbars(AppWin, W, PPC, 1, H div PPC+1);
+ true ->
+ wxScrolledWindow:setScrollbars(AppWin, PPC, PPC, W div PPC+1, H div PPC+1)
+ end;
+setup_scrollbar(_, _, undefined) -> ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(#wx{event=#wxCommand{type=command_listbox_selected, cmdString=AppStr}},
+ State = #state{appmon=AppMon, current=Prev}) ->
+ case AppStr of
+ [] ->
+ {noreply, State};
+ _ ->
+ App = list_to_atom(AppStr),
+ (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []),
+ appmon_info:app(AppMon, App, true, []),
+ {noreply, State#state{current=App}}
+ end;
+
+handle_event(#wx{id=Id, event=_Sz=#wxSize{size=Size}},
+ State=#state{app=App, app_w=AppWin}) ->
+ Id =:= ?DRAWAREA andalso setup_scrollbar(Size,AppWin,App),
+ {noreply, State};
+
+handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}},
+ S0=#state{app=#app{ptree=Tree}, app_w=AppWin}) ->
+ {X,Y} = wxScrolledWindow:calcUnscrolledPosition(AppWin, X0, Y0),
+ Hit = locate_node(X,Y, [Tree]),
+ State = handle_mouse_click(Hit, Type, S0),
+ {noreply, State};
+
+handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
+ State = #state{sel=undefined}) ->
+ observer_lib:display_info_dialog("Select process first"),
+ {noreply, State};
+
+handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}},
+ State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) ->
+ observer_procinfo:start(Pid, Panel, self()),
+ {noreply, State};
+
+handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}},
+ State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) ->
+ case observer_lib:user_term(Panel, "Enter message", "") of
+ cancel -> ok;
+ {ok, Term} -> Pid ! Term;
+ {error, Error} -> observer_lib:display_info_dialog(Error)
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}},
+ State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) ->
+ case observer_lib:user_term(Panel, "Enter Exit Reason", "") of
+ cancel -> ok;
+ {ok, Term} -> exit(Pid, Term);
+ {error, Error} -> observer_lib:display_info_dialog(Error)
+ end,
+ {noreply, State};
+
+%%% Trace api
+handle_event(#wx{id=?ID_TRACE_PID, event=#wxCommand{type=command_menu_selected}},
+ State = #state{sel={Box,_}}) ->
+ observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_pid(Box)]),
+ {noreply, State};
+handle_event(#wx{id=?ID_TRACE_NAME, event=#wxCommand{type=command_menu_selected}},
+ State = #state{sel={Box,_}}) ->
+ observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_reg(Box)]),
+ {noreply, State};
+handle_event(#wx{id=?ID_TRACE_TREE_PIDS, event=#wxCommand{type=command_menu_selected}},
+ State = #state{sel=Sel}) ->
+ Get = fun(Box) -> box_to_pid(Box) end,
+ observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)),
+ {noreply, State};
+handle_event(#wx{id=?ID_TRACE_TREE_NAMES, event=#wxCommand{type=command_menu_selected}},
+ State = #state{sel=Sel}) ->
+ Get = fun(Box) -> box_to_reg(Box) end,
+ observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)),
+ {noreply, State};
+
+handle_event(Event, _State) ->
+ error({unhandled_event, Event}).
+
+%%%%%%%%%%
+handle_sync_event(#wx{event = #wxPaint{}},_,
+ #state{app_w=DA, app=App, sel=Sel, paint=Paint}) ->
+ %% PaintDC must be created in a callback to work on windows.
+ DC = wxPaintDC:new(DA),
+ wxScrolledWindow:doPrepareDC(DA,DC),
+ %% Nothing is drawn until wxPaintDC is destroyed.
+ draw(DC, App, Sel, Paint),
+ wxPaintDC:destroy(DC),
+ ok.
+%%%%%%%%%%
+handle_call(Event, From, _State) ->
+ error({unhandled_call, Event, From}).
+
+handle_cast(Event, _State) ->
+ error({unhandled_cast, Event}).
+%%%%%%%%%%
+handle_info({active, Node}, State = #state{parent=Parent, current=Curr, appmon=Appmon}) ->
+ create_menus(Parent, []),
+ {ok, Pid} = appmon_info:start_link(Node, self(), []),
+ case Appmon of
+ undefined -> ok;
+ Pid -> ok;
+ _ -> %% Deregister me as client (and stop appmon if last)
+ exit(Appmon, normal)
+ end,
+ appmon_info:app_ctrl(Pid, Node, true, []),
+ (Curr =/= undefined) andalso appmon_info:app(Pid, Curr, true, []),
+ {noreply, State#state{appmon=Pid}};
+
+handle_info(not_active, State = #state{appmon=AppMon, current=Prev}) ->
+ appmon_info:app_ctrl(AppMon, node(AppMon), false, []),
+ (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []),
+ {noreply, State};
+
+handle_info({delivery, Pid, app_ctrl, _, Apps0},
+ State = #state{appmon=Pid, apps_w=LBox}) ->
+ Apps = [atom_to_list(App) || {_, App, {_, _, _}} <- Apps0],
+ wxListBox:clear(LBox),
+ wxListBox:appendStrings(LBox, [App || App <- lists:sort(Apps)]),
+ {noreply, State};
+
+handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}},
+ State = #state{panel=Panel}) ->
+ wxWindow:refresh(Panel),
+ {noreply, State#state{app=undefined, sel=undefined}};
+
+handle_info({delivery, Pid, app, Curr, AppData},
+ State = #state{panel=Panel, appmon=Pid, current=Curr,
+ app_w=AppWin, paint=#paint{font=Font}}) ->
+ App = build_tree(AppData, {AppWin,Font}),
+ setup_scrollbar(AppWin, App),
+ wxWindow:refresh(Panel),
+ wxWindow:layout(Panel),
+ {noreply, State#state{app=App, sel=undefined}};
+
+handle_info(_Event, State) ->
+ %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]),
+ {noreply, State}.
+
+%%%%%%%%%%
+terminate(_Event, _State) ->
+ ok.
+code_change(_, _, State) ->
+ State.
+
+handle_mouse_click(Node = {#box{s1=#str{pid=Pid}},_}, Type,
+ State=#state{app_w=AppWin,panel=Panel}) ->
+ case Type of
+ left_dclick -> observer_procinfo:start(Pid, Panel, self());
+ right_down -> popup_menu(Panel);
+ _ -> ok
+ end,
+ wxWindow:refresh(AppWin),
+ State#state{sel=Node};
+handle_mouse_click(_, _, State = #state{sel=undefined}) ->
+ State;
+handle_mouse_click(_, right_down, State=#state{panel=Panel}) ->
+ popup_menu(Panel),
+ State;
+handle_mouse_click(_, _, State=#state{app_w=AppWin}) ->
+ wxWindow:refresh(AppWin),
+ State#state{sel=undefined}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+create_menus(Parent, _) ->
+ MenuEntries =
+ [{"File",
+ [#create_menu{id=?ID_PROC_INFO, text="Process info"},
+ #create_menu{id=?ID_PROC_MSG, text="Send Msg"},
+ #create_menu{id=?ID_PROC_KILL, text="Kill process"}
+ ]},
+ {"Trace",
+ [#create_menu{id=?ID_TRACE_PID, text="Trace process"},
+ #create_menu{id=?ID_TRACE_NAME, text="Trace named process"},
+ #create_menu{id=?ID_TRACE_TREE_PIDS, text="Trace process tree"},
+ #create_menu{id=?ID_TRACE_TREE_NAMES, text="Trace named process tree"}
+ ]}],
+ observer_wx:create_menus(Parent, MenuEntries).
+
+popup_menu(Panel) ->
+ Menu = wxMenu:new(),
+ wxMenu:append(Menu, ?ID_PROC_INFO, "Process info"),
+ wxMenu:append(Menu, ?ID_TRACE_PID, "Trace process"),
+ wxMenu:append(Menu, ?ID_TRACE_NAME, "Trace named process"),
+ wxMenu:append(Menu, ?ID_TRACE_TREE_PIDS, "Trace process tree"),
+ wxMenu:append(Menu, ?ID_TRACE_TREE_NAMES, "Trace named process tree"),
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+locate_node(X, _Y, [{Box=#box{x=BX}, _Chs}|_Rest])
+ when X < BX ->
+ {left, Box};
+locate_node(X,Y, [Node={Box=#box{x=BX,y=BY,w=BW,h=BH}, _Chs}|Rest])
+ when X =< (BX+BW)->
+ if
+ Y < BY -> {above, Box}; %% Above
+ Y =< (BY+BH) -> Node;
+ true -> locate_node(X,Y,Rest)
+ end;
+locate_node(X,Y, [{_, Chs}|Rest]) ->
+ case locate_node(X,Y,Chs) of
+ Node = {#box{},_} -> Node;
+ _Miss ->
+ locate_node(X,Y,Rest)
+ end;
+locate_node(_, _, []) -> false.
+
+locate_box(From, [{Box=#box{s1=#str{pid=From}},_}|_]) -> Box;
+locate_box(From, [{_,Chs}|Rest]) ->
+ case locate_box(From, Chs) of
+ Box = #box{} -> Box;
+ _ -> locate_box(From, Rest)
+ end;
+locate_box(From, []) -> {false, From}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+build_tree({Root, P2Name, Links, XLinks0}, Font) ->
+ Fam = sofs:relation_to_family(sofs:relation(Links)),
+ Name2P = gb_trees:from_orddict(lists:sort([{Name,Pid} || {Pid,Name} <- P2Name])),
+ Lookup = gb_trees:from_orddict(sofs:to_external(Fam)),
+ {_, Tree0} = build_tree2(Root, Lookup, Name2P, Font),
+ {Tree, Dim} = calc_tree_size(Tree0),
+ Fetch = fun({From, To}, Acc) ->
+ try {value, ToPid} = gb_trees:lookup(To, Name2P),
+ FromPid = gb_trees:get(From, Name2P),
+ [{locate_box(FromPid, [Tree]),locate_box(ToPid, [Tree])}|Acc]
+ catch _:_ ->
+ Acc
+ end
+ end,
+ XLinks = lists:foldl(Fetch, [], XLinks0),
+ #app{ptree=Tree, dim=Dim, links=XLinks}.
+
+build_tree2(Root, Tree0, N2P, Font) ->
+ case gb_trees:lookup(Root, Tree0) of
+ none -> {Tree0, {box(Root, N2P, Font), []}};
+ {value, Children} ->
+ Tree1 = gb_trees:delete(Root, Tree0),
+ {Tree, CHs} = lists:foldr(fun("port " ++_, Acc) ->
+ Acc; %% Skip ports
+ (Child,{T0, Acc}) ->
+ {T, C} = build_tree2(Child, T0, N2P, Font),
+ {T, [C|Acc]}
+ end, {Tree1, []}, Children),
+ {Tree, {box(Root, N2P, Font), CHs}}
+ end.
+
+calc_tree_size(Tree) ->
+ Cols = calc_col_start(Tree, [0]),
+ {Boxes,{W,Hs}} = calc_tree_size(Tree, Cols, ?BB_X, [?BB_Y]),
+ {Boxes, {W,lists:max(Hs)}}.
+
+calc_col_start({#box{w=W}, Chs}, [Max|Acc0]) ->
+ Acc = if Acc0 == [] -> [0]; true -> Acc0 end,
+ Depth = lists:foldl(fun(Child, MDepth) -> calc_col_start(Child, MDepth) end,
+ Acc, Chs),
+ [max(W,Max)|Depth].
+
+calc_tree_size({Box=#box{w=W,h=H}, []}, _, X, [Y|Ys]) ->
+ {{Box#box{x=X,y=Y}, []}, {X+W+?BB_X,[Y+H+?BB_Y|Ys]}};
+calc_tree_size({Box, Children}, [Col|Cols], X, [H0|Hs0]) ->
+ Hs1 = calc_row_start(Children, H0, Hs0),
+ StartX = X+Col+?BB_X,
+ {Boxes, {W,Hs}} = calc_tree_sizes(Children, Cols, StartX, StartX, Hs1, []),
+ Y = middle(Boxes, H0),
+ H = Y+Box#box.h+?BB_Y,
+ {{Box#box{x=X,y=Y}, Boxes}, {W,[H|Hs]}}.
+
+calc_tree_sizes([Child|Chs], Cols, X0, W0, Hs0, Acc) ->
+ {Tree, {W,Hs}} = calc_tree_size(Child, Cols, X0, Hs0),
+ calc_tree_sizes(Chs, Cols, X0, max(W,W0), Hs, [Tree|Acc]);
+calc_tree_sizes([], _, _, W,Hs, Acc) ->
+ {lists:reverse(Acc), {W,Hs}}.
+
+calc_row_start(Chs = [{#box{h=H},_}|_], Start, Hs0) ->
+ NChs = length(Chs),
+ Wanted = (H*NChs + ?BB_Y*(NChs-1)) div 2 - H div 2,
+ case Hs0 of
+ [] -> [max(?BB_Y, Start - Wanted)];
+ [Next|Hs] ->
+ [max(Next, Start - Wanted)|Hs]
+ end.
+
+middle([], Y) -> Y;
+middle([{#box{y=Y}, _}], _) -> Y;
+middle([{#box{y=Y0},_}|List], _) ->
+ {#box{y=Y1},_} = lists:last(List),
+ (Y0+Y1) div 2.
+
+box(Str0, N2P, {Win,Font}) ->
+ Pid = gb_trees:get(Str0, N2P),
+ Str = if hd(Str0) =:= $< -> lists:append(io_lib:format("~w", [Pid]));
+ true -> Str0
+ end,
+ {TW,TH, _, _} = wxWindow:getTextExtent(Win, Str, [{theFont, Font}]),
+ Data = #str{text=Str, x=?BX_HE, y=?BY_HE, pid=Pid},
+ %% Add pid
+ #box{w=TW+?BX_E, h=TH+?BY_E, s1=Data}.
+
+box_to_pid(#box{s1=#str{pid=Pid}}) -> Pid.
+box_to_reg(#box{s1=#str{text=[$<|_], pid=Pid}}) -> Pid;
+box_to_reg(#box{s1=#str{text=Name}}) -> list_to_atom(Name).
+
+tree_map({Box, Chs}, Fun) ->
+ tree_map(Chs, Fun, [Fun(Box)]).
+tree_map([{Box, Chs}|Rest], Fun, Acc0) ->
+ Acc = tree_map(Chs, Fun, [Fun(Box)|Acc0]),
+ tree_map(Rest, Fun, Acc);
+tree_map([], _ , Acc) -> Acc.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+draw(_DC, undefined, _, _) ->
+ ok;
+draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel,
+ #paint{font=Font, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) ->
+ %% Canvas = wxGraphicsContext:create(DC),
+ %% Pen = wxGraphicsContext:createPen(Canvas, ?wxBLACK_PEN),
+ %% wxGraphicsContext:setPen(Canvas, Pen),
+ %% Brush = wxGraphicsContext:createBrush(Canvas, ?wxLIGHT_GREY_BRUSH),
+ %% wxGraphicsContext:setBrush(Canvas, Brush),
+ %% Font = wxGraphicsContext:createFont(Canvas, wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT)),
+ %% wxGraphicsContext:setFont(Canvas, Font),
+ %% draw_tree(Tree, Canvas).
+ wxDC:setPen(DC, LPen),
+ [draw_xlink(Link, DC) || Link <- Links],
+ wxDC:setPen(DC, Pen),
+ %% wxDC:drawRectangle(DC, {2,2}, {W-2,H-2}), %% DEBUG
+ wxDC:setBrush(DC, Brush),
+ wxDC:setFont(DC, Font),
+ draw_tree(Tree, root, DC),
+ case Sel of
+ undefined -> ok;
+ {#box{x=X,y=Y,w=W,h=H,s1=Str1}, _} ->
+ wxDC:setBrush(DC, SelBrush),
+ wxDC:drawRoundedRectangle(DC, {X-1,Y-1}, {W+2,H+2}, 8.0),
+ draw_str(DC, Str1, X, Y)
+ end.
+
+draw_tree({Box=#box{x=X,y=Y,w=W,h=H,s1=Str1}, Chs}, Parent, DC) ->
+ %%wxGraphicsContext:drawRoundedRectangle(DC, float(X), float(Y), float(W), float(H), 8.0),
+ wxDC:drawRoundedRectangle(DC, {X,Y}, {W,H}, 8.0),
+ draw_str(DC, Str1, X, Y),
+ Dot = case Chs of
+ [] -> ok;
+ [{#box{x=CX0},_}|_] ->
+ CY = Y+(H div 2),
+ CX = CX0-(?BB_X div 2),
+ wxDC:drawLine(DC, {X+W, CY}, {CX, CY}),
+ {CX, CY}
+ end,
+ draw_link(Parent, Box, DC),
+ [draw_tree(Child, Dot, DC) || Child <- Chs].
+
+draw_link({CX,CY}, #box{x=X,y=Y0,h=H}, DC) ->
+ Y = Y0+(H div 2),
+ case Y =:= CY of
+ true ->
+ wxDC:drawLine(DC, {CX, CY}, {X, CY});
+ false ->
+ wxDC:drawLines(DC, [{CX, CY}, {CX, Y}, {X,Y}])
+ end;
+draw_link(_, _, _) -> ok.
+
+draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, y=Y1}}, DC)
+ when X0 =:= X1 ->
+ draw_xlink(X0,Y0,X1,Y1,BH,DC);
+draw_xlink({#box{x=X0, y=Y0, h=BH, w=BW}, #box{x=X1, y=Y1}}, DC)
+ when X0 < X1 ->
+ draw_xlink(X0+BW,Y0,X1,Y1,BH,DC);
+draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, w=BW, y=Y1}}, DC)
+ when X0 > X1 ->
+ draw_xlink(X1+BW,Y1,X0,Y0,BH,DC);
+draw_xlink({_From, _To}, _DC) ->
+ ignore.
+draw_xlink(X0, Y00, X1, Y11, BH, DC) ->
+ {Y0,Y1} = if Y00 < Y11 -> {Y00+BH-6, Y11+6};
+ true -> {Y00+6, Y11+BH-6}
+ end,
+ wxDC:drawLines(DC, [{X0,Y0}, {X0+5,Y0}, {X1-5,Y1}, {X1,Y1}]).
+
+draw_str(DC, #str{x=Sx,y=Sy, text=Text}, X, Y) ->
+ %%wxGraphicsContext:drawText(DC, Text, float(Sx+X), float(Sy+Y));
+ wxDC:drawText(DC, Text, {X+Sx,Y+Sy});
+draw_str(_, _, _, _) -> ok.
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..967baa5c7a 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -19,11 +19,12 @@
-module(observer_lib).
-export([get_wx_parent/1,
- display_info_dialog/1,
+ display_info_dialog/1, user_term/3,
interval_dialog/4, start_timer/1, stop_timer/1,
display_info/2, fill_info/2, update_info/2, to_str/1,
create_menus/3, create_menu_item/3,
- create_attrs/0
+ create_attrs/0,
+ set_listctrl_col_size/2
]).
-include_lib("wx/include/wx.hrl").
@@ -195,6 +196,7 @@ to_str(No) when is_integer(No) ->
to_str(Term) ->
io_lib:format("~w", [Term]).
+create_menus([], _MenuBar, _Type) -> ok;
create_menus(Menus, MenuBar, Type) ->
Add = fun({Tag, Ms}, Index) ->
create_menu(Tag, Ms, Index, MenuBar, Type)
@@ -239,15 +241,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)
@@ -295,3 +303,53 @@ create_box(Panel, Data) ->
wxSizer:add(Box, Right),
wxSizer:addSpacer(Box, 30),
{Box, InfoFields}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+set_listctrl_col_size(LCtrl, Total) ->
+ wx:batch(fun() -> calc_last(LCtrl, Total) end).
+
+calc_last(LCtrl, _Total) ->
+ Cols = wxListCtrl:getColumnCount(LCtrl),
+ {Total, _} = wxWindow:getClientSize(LCtrl),
+ SBSize = scroll_size(LCtrl),
+ Last = lists:foldl(fun(I, Last) ->
+ Last - wxListCtrl:getColumnWidth(LCtrl, I)
+ end, Total-SBSize, lists:seq(0, Cols - 2)),
+ Size = max(150, Last),
+ wxListCtrl:setColumnWidth(LCtrl, Cols-1, Size).
+
+scroll_size(LCtrl) ->
+ case os:type() of
+ {win32, nt} -> 0;
+ {unix, darwin} ->
+ %% I can't figure out is there is a visible scrollbar
+ %% Always make room for it
+ wxSystemSettings:getMetric(?wxSYS_VSCROLL_X);
+ _ ->
+ case wxWindow:hasScrollbar(LCtrl, ?wxVERTICAL) of
+ true -> wxSystemSettings:getMetric(?wxSYS_VSCROLL_X);
+ false -> 0
+ end
+ end.
+
+
+user_term(Parent, Title, Default) ->
+ Dialog = wxTextEntryDialog:new(Parent, Title, [{value, Default}]),
+ case wxTextEntryDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Str = wxTextEntryDialog:getValue(Dialog),
+ wxTextEntryDialog:destroy(Dialog),
+ parse_string(Str);
+ ?wxID_CANCEL ->
+ wxTextEntryDialog:destroy(Dialog)
+ end.
+
+parse_string(Str) ->
+ try
+ {ok, Tokens, _} = erl_scan:string(Str),
+ erl_parse:parse_term(Tokens)
+ catch _:{badmatch, {error, {_, _, Err}}} ->
+ {error, ["Parse error: ", Err]};
+ _Err ->
+ {error, ["Syntax error in: ", Str]}
+ end.
diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl
index cfc1c0665f..7578215ff9 100644
--- a/lib/observer/src/observer_pro_wx.erl
+++ b/lib/observer/src/observer_pro_wx.erl
@@ -32,22 +32,29 @@
%% Defines
-define(COL_PID, 0).
--define(COL_NAME, 1).
--define(COL_TIME, 2).
--define(COL_REDS, 3).
--define(COL_MEM, 4).
--define(COL_MSG, 5).
--define(COL_FUN, 6).
+-define(COL_NAME, ?COL_PID+1).
+%%-define(COL_TIME, 2).
+-define(COL_REDS, ?COL_NAME+1).
+-define(COL_MEM, ?COL_REDS+1).
+-define(COL_MSG, ?COL_MEM+1).
+-define(COL_FUN, ?COL_MSG+1).
-define(ID_KILL, 201).
-define(ID_PROC, 202).
-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
@@ -98,12 +105,9 @@ setup(Notebook, Parent, Holder) ->
wxWindow:setSizer(ProPanel, Sizer),
- Popup = create_popup_menu(ProPanel),
-
State = #state{parent=Parent,
grid=Grid,
panel=ProPanel,
- popup_menu=Popup,
parent_notebook=Notebook,
holder=Holder,
timer={false, 10}
@@ -124,34 +128,14 @@ 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"}
]}
],
observer_wx:create_menus(Parent, MenuEntries).
-create_popup_menu(ParentFrame) ->
- MiniFrame = wxMiniFrame:new(ParentFrame, ?wxID_ANY, "Options", [{style, ?wxFRAME_FLOAT_ON_PARENT}]),
- Panel = wxPanel:new(MiniFrame),
- Sizer = wxBoxSizer:new(?wxVERTICAL),
- TraceBtn = wxButton:new(Panel, ?ID_TRACEMENU, [{label, "Trace selected"},
- {style, ?wxNO_BORDER}]),
- ProcBtn = wxButton:new(Panel, ?ID_PROC, [{label, "Process info"},
- {style, ?wxNO_BORDER}]),
- KillBtn = wxButton:new(Panel, ?ID_KILL, [{label, "Kill process"},
- {style, ?wxNO_BORDER}]),
-
- wxButton:connect(TraceBtn, command_button_clicked),
- wxButton:connect(ProcBtn, command_button_clicked),
- wxButton:connect(KillBtn, command_button_clicked),
- wxSizer:add(Sizer, TraceBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
- wxSizer:add(Sizer, ProcBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
- wxSizer:add(Sizer, KillBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
- wxPanel:setSizer(Panel, Sizer),
- wxSizer:setSizeHints(Sizer, MiniFrame),
- MiniFrame.
-
create_list_box(Panel, Holder) ->
Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_HRULES,
ListCtrl = wxListCtrl:new(Panel, [{style, Style},
@@ -174,7 +158,7 @@ create_list_box(Panel, Holder) ->
end,
ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE, 120},
{"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 200},
- {"Time", ?wxLIST_FORMAT_CENTRE, 50},
+%% {"Time", ?wxLIST_FORMAT_CENTRE, 50},
{"Reds", ?wxLIST_FORMAT_RIGHT, 100},
{"Memory", ?wxLIST_FORMAT_RIGHT, 100},
{"MsgQ", ?wxLIST_FORMAT_RIGHT, 50},
@@ -257,10 +241,6 @@ handle_info(not_active, #state{timer=Timer0}=State) ->
Timer = observer_lib:stop_timer(Timer0),
{noreply, State#state{timer=Timer}};
-handle_info({node, Node}, #state{holder=Holder}=State) ->
- Holder ! {change_node, Node},
- {noreply, State};
-
handle_info(Info, State) ->
io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
@@ -314,25 +294,17 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL},
Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60),
{noreply, State#state{timer=Timer}};
-handle_event(#wx{id=?ID_KILL},
- #state{popup_menu=Pop,sel={[_|Ids], [ToKill|Pids]}}=State) ->
- wxWindow:show(Pop, [{show, false}]),
+handle_event(#wx{id=?ID_KILL}, #state{sel={[_|Ids], [ToKill|Pids]}}=State) ->
exit(ToKill, kill),
{noreply, State#state{sel={Ids,Pids}}};
-handle_event(#wx{id = ?ID_PROC},
- #state{panel=Panel,
- popup_menu=Pop,
- sel={_, [Pid|_]},
- procinfo_menu_pids=Opened}=State) ->
- wxWindow:show(Pop, [{show, false}]),
+handle_event(#wx{id=?ID_PROC},
+ #state{panel=Panel, sel={_, [Pid|_]},procinfo_menu_pids=Opened}=State) ->
Opened2 = start_procinfo(Pid, Panel, Opened),
{noreply, State#state{procinfo_menu_pids=Opened2}};
-handle_event(#wx{id = ?ID_TRACEMENU},
- #state{popup_menu=Pop, sel={_, Pids}, panel=Panel}=State) ->
- wxWindow:show(Pop, [{show, false}]),
+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),
@@ -342,50 +314,54 @@ handle_event(#wx{id = ?ID_TRACEMENU},
{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};
handle_event(#wx{event=#wxSize{size={W,_}}},
#state{grid=Grid}=State) ->
- wx:batch(fun() ->
- Cols = wxListCtrl:getColumnCount(Grid),
- Last = lists:foldl(fun(I, Last) ->
- Last - wxListCtrl:getColumnWidth(Grid, I)
- end, W-Cols*3-?LCTRL_WDECR, lists:seq(0, Cols - 2)),
- Size = max(200, Last),
- %% io:format("Width ~p ~p => ~p~n",[W, Last, Size]),
- wxListCtrl:setColumnWidth(Grid, Cols-1, Size)
- end),
+ observer_lib:set_listctrl_col_size(Grid, W),
{noreply, State};
handle_event(#wx{event=#wxList{type=command_list_item_right_click,
itemIndex=Row}},
- #state{popup_menu=Popup,
- holder=Holder}=State) ->
+ #state{panel=Panel, holder=Holder}=State) ->
case call(Holder, {get_row, self(), Row, pid}) of
{error, undefined} ->
- wxWindow:show(Popup, [{show, false}]),
undefined;
{ok, _} ->
- wxWindow:move(Popup, wx_misc:getMousePosition()),
- wxWindow:show(Popup)
+ Menu = wxMenu:new(),
+ wxMenu:append(Menu, ?ID_PROC, "Process info"),
+ 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,
{noreply, State};
handle_event(#wx{event=#wxList{type=command_list_item_focused,
itemIndex=Row}},
- #state{grid=Grid,popup_menu=Pop,holder=Holder} = State) ->
+ #state{grid=Grid,holder=Holder} = State) ->
case Row >= 0 of
true ->
- wxWindow:show(Pop, [{show, false}]),
SelIds = [Row|lists:delete(Row, get_selected_items(Grid))],
Pids = call(Holder, {get_pids, self(), SelIds}),
- %% io:format("Focused ~p -> ~p~n",[State#state.sel, {SelIds, Pids}]),
{noreply, State#state{sel={SelIds, Pids}}};
false ->
- %% io:format("Focused -1~n",[]),
{noreply, State}
end;
@@ -448,7 +424,6 @@ set_focus([Old|_], [New|_], Grid) ->
wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED),
wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_table_holder(Parent, Attrs) ->
@@ -486,6 +461,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},
@@ -562,7 +540,7 @@ get_procinfo_data(Col, Info) ->
col_to_element(?COL_PID) -> #etop_proc_info.pid;
col_to_element(?COL_NAME) -> #etop_proc_info.name;
col_to_element(?COL_MEM) -> #etop_proc_info.mem;
-col_to_element(?COL_TIME) -> #etop_proc_info.runtime;
+%%col_to_element(?COL_TIME) -> #etop_proc_info.runtime;
col_to_element(?COL_REDS) -> #etop_proc_info.reds;
col_to_element(?COL_FUN) -> #etop_proc_info.cf;
col_to_element(?COL_MSG) -> #etop_proc_info.mq.
@@ -571,6 +549,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_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index 127599a39e..a4c5914c49 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -41,7 +41,7 @@
-record(worker, {panel, callback}).
start(Process, ParentFrame, Parent) ->
- wx_object:start(?MODULE, [Process, ParentFrame, Parent], []).
+ wx_object:start_link(?MODULE, [Process, ParentFrame, Parent], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -52,7 +52,7 @@ init([Pid, ParentFrame, Parent]) ->
{registered_name, Registered} -> atom_to_list(Registered)
end,
Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title],
- [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {800,700}}]),
+ [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
MenuBar = wxMenuBar:new(),
create_menus(MenuBar),
wxFrame:setMenuBar(Frame, MenuBar),
@@ -75,10 +75,7 @@ init([Pid, ParentFrame, Parent]) ->
}}
catch error:{badrpc, _} ->
observer_wx:return_to_localnode(ParentFrame, node(Pid)),
- {stop, badrpc, #state{parent=Parent, pid=Pid}};
- Error:Reason ->
- io:format("~p:~p: ~p ~p~n ~p~n",
- [?MODULE, ?LINE, Error, Reason, erlang:get_stacktrace()])
+ {stop, badrpc, #state{parent=Parent, pid=Pid}}
end.
init_panel(Notebook, Str, Pid, Fun) ->
@@ -92,30 +89,27 @@ init_panel(Notebook, Str, Pid, Fun) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_event(#wx{event=#wxClose{type=close_window}}, State) ->
- {stop, shutdown, State};
+ {stop, normal, State};
handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) ->
- {stop, shutdown, State};
+ {stop, normal, State};
handle_event(#wx{id=?REFRESH}, #state{pages=Pages}=State) ->
[(W#worker.callback)() || W <- Pages],
{noreply, State};
-handle_event(Event, State) ->
- io:format("~p: ~p, Handle event: ~p~n", [?MODULE, ?LINE, Event]),
- {noreply, State}.
+handle_event(Event, _State) ->
+ error({unhandled_event, Event}).
-handle_info(Info, State) ->
- io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+handle_info(_Info, State) ->
+ %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
-handle_call(Call, _From, State) ->
- io:format("~p ~p: Got call ~p~n",[?MODULE, ?LINE, Call]),
- {reply, ok, State}.
+handle_call(Call, From, _State) ->
+ error({unhandled_call, Call, From}).
-handle_cast(Cast, State) ->
- io:format("~p ~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]),
- {noreply, State}.
+handle_cast(Cast, _State) ->
+ error({unhandled_cast, Cast}).
terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) ->
Parent ! {procinfo_menu_closed, Pid},
@@ -179,26 +173,42 @@ init_dict_page(Parent, Pid) ->
{Text, Update}.
init_stack_page(Parent, Pid) ->
- Text = init_text_page(Parent),
- Format = fun({Mod, Fun, Arg, Info}) ->
- Str = io_lib:format("~w:~w/~w", [Mod,Fun,Arg]),
- case Info of
- [{file,File},{line,Line}] ->
- io_lib:format("~-45.s ~s:~w~n", [Str,File,Line]);
- _ ->
- [Str,$\n]
- end
- end,
+ LCtrl = wxListCtrl:new(Parent, [{style, ?wxLC_REPORT bor ?wxLC_HRULES}]),
+ Li = wxListItem:new(),
+ wxListItem:setText(Li, "Module:Function/Arg"),
+ wxListCtrl:insertColumn(LCtrl, 0, Li),
+ wxListCtrl:setColumnWidth(LCtrl, 0, 300),
+ wxListItem:setText(Li, "File:LineNumber"),
+ wxListCtrl:insertColumn(LCtrl, 1, Li),
+ wxListCtrl:setColumnWidth(LCtrl, 1, 300),
+ wxListItem:destroy(Li),
Update = fun() ->
{current_stacktrace,RawBt} =
observer_wx:try_rpc(node(Pid), erlang, process_info,
[Pid, current_stacktrace]),
- Last = wxTextCtrl:getLastPosition(Text),
- wxTextCtrl:remove(Text, 0, Last),
- [wxTextCtrl:writeText(Text, Format(Entry)) || Entry <- RawBt]
+ wxListCtrl:deleteAllItems(LCtrl),
+ wx:foldl(fun({M, F, A, Info}, Row) ->
+ _Item = wxListCtrl:insertItem(LCtrl, Row, ""),
+ ?EVEN(Row) andalso
+ wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
+ wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})),
+ FileLine = case Info of
+ [{file,File},{line,Line}] ->
+ io_lib:format("~s:~w", [File,Line]);
+ _ ->
+ []
+ end,
+ wxListCtrl:setItem(LCtrl, Row, 1, FileLine),
+ Row+1
+ end, 0, RawBt)
end,
+ Resize = fun(#wx{event=#wxSize{size={W,_}}},Ev) ->
+ wxEvent:skip(Ev),
+ observer_lib:set_listctrl_col_size(LCtrl, W)
+ end,
+ wxListCtrl:connect(LCtrl, size, [{callback, Resize}]),
Update(),
- {Text, Update}.
+ {LCtrl, Update}.
create_menus(MenuBar) ->
Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]},
diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl
index ddedcf3829..09602bbd9e 100644
--- a/lib/observer/src/observer_sys_wx.erl
+++ b/lib/observer/src/observer_sys_wx.erl
@@ -24,8 +24,6 @@
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
handle_event/2, handle_cast/2]).
--export([sys_info/0]).
-
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
@@ -48,7 +46,7 @@ start_link(Notebook, Parent) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([Notebook, Parent]) ->
- SysInfo = sys_info(),
+ SysInfo = observer_backend:sys_info(),
{Info, Stat} = info_fields(),
Panel = wxPanel:new(Notebook),
Sizer = wxBoxSizer:new(?wxHORIZONTAL),
@@ -73,7 +71,7 @@ create_sys_menu(Parent) ->
observer_wx:create_menus(Parent, [View]).
update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) ->
- SysInfo = observer_wx:try_rpc(Node, ?MODULE, sys_info, []),
+ SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []),
{Info, Stat} = info_fields(),
observer_lib:update_info(Fields, observer_lib:fill_info(Info, SysInfo) ++
observer_lib:fill_info(Stat, SysInfo)),
@@ -97,20 +95,20 @@ 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},
+ [{"Up time", {time_ms, uptime}},
{"Max Processes", process_limit},
{"Processes", process_count},
{"Run Queue", run_queue},
- {"IO Input", io_input},
- {"IO Output", io_output}
+ {"IO Input", {bytes, io_input}},
+ {"IO Output", {bytes, io_output}}
]}
],
{Info, Stat}.
@@ -126,16 +124,6 @@ handle_info(refresh_interval, #sys_wx_state{panel = Panel,
end,
{noreply, State};
-handle_info({node, Node}, #sys_wx_state{panel = Panel} = State) ->
- UpdState = State#sys_wx_state{node = Node},
- try
- update_syspage(UpdState),
- {noreply, UpdState}
- catch error:{badrpc, _} ->
- observer_wx:return_to_localnode(Panel, Node),
- {noreply, State}
- end;
-
handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel,
timer = Timer} = State) ->
UpdState = State#sys_wx_state{node = Node},
@@ -148,7 +136,6 @@ handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel,
{noreply, State}
end;
-
handle_info(not_active, #sys_wx_state{timer = Timer} = State) ->
{noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}};
@@ -188,36 +175,3 @@ handle_event(#wx{id = ?ID_REFRESH_INTERVAL,
handle_event(Event, State) ->
io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
{noreply, State}.
-
-
-sys_info() ->
- {{_,Input},{_,Output}} = erlang:statistics(io),
- [{process_count, erlang:system_info(process_count)},
- {process_limit, erlang:system_info(process_limit)},
- {uptime, {time_ms, element(1, erlang:statistics(wall_clock))}},
- {run_queue, erlang:statistics(run_queue)},
- {io_input, {bytes, Input}},
- {io_output, {bytes, Output}},
- {logical_processors, erlang:system_info(logical_processors)},
- {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)},
- {kernel_poll, erlang:system_info(kernel_poll)},
- {smp_support, erlang:system_info(smp_support)},
- {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})}
- ].
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl
index 0ab7db121b..d0b6a1e063 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:add(Buttons, ToggleButton, [{flag, ?wxALIGN_CENTER_VERTICAL}]),
+ wxSizer:addSpacer(Buttons, 15),
+ wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NODES, [{label, "Add Nodes"}])),
+ wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW, [{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, 155}]),
+ wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ 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}]),
- wxWindow:setFocus(Grid),
- Grid.
+ 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,49 +171,45 @@ 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}]),
+ wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 155}]),
wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxListCtrl:connect(Modules, size, [{skip, true}]),
wxListCtrl:connect(Funcs, size, [{skip, true}]),
wxListCtrl:connect(Modules, command_list_item_selected),
- %% wxListCtrl:connect(Funcs, command_list_item_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).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%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
+ true -> observer_lib:set_listctrl_col_size(Obj, W);
+ 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}
+ 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 +225,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(#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 +331,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 +485,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 +518,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 +538,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 +571,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 +589,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 +606,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 ToFile orelse
+ throw({error, "No output of trace"}),
+ {LogWin,Text} = create_logwindow(Parent, ToWindow),
+ Write = output_fun(Text, ToShell),
+ Shell = output_shell(ToFile, Write),
+ FileOpts = output_file(ToFile, proplists:get_value(wrap, Opts, false), Opts),
+ {[{file, {local,FileOpts}}|Shell], LogWin}.
+
+output_shell(true, false) ->
+ []; %% File only
+output_shell(true, Write) when is_function(Write) ->
+ [{shell, Write}];
+output_shell(false, Write) when is_function(Write) ->
+ [{shell, {only, Write}}].
+
+output_fun(false, false) -> false;
+output_fun(false, true) -> fun(Trace) -> io:put_chars(textformat(Trace)) end;
+output_fun(Text, false) ->
+ Env = wx:get_env(),
+ fun(Trace) ->
+ wx:set_env(Env),
+ wxTextCtrl:appendText(Text, textformat(Trace))
+ end;
+output_fun(Text, true) ->
+ Env = wx:get_env(),
+ fun(Trace) ->
+ wx:set_env(Env),
+ IoList = textformat(Trace),
+ wxTextCtrl:appendText(Text, IoList),
+ io:put_chars(textformat(Trace))
+ end.
+
+output_file(false, _, _Opts) ->
+ "ttb"; %% Will be ignored
+output_file(true, false, Opts) ->
+ proplists:get_value(filename, Opts, "ttb");
+output_file(true, true, Opts) ->
+ Name = proplists:get_value(filename, Opts, "ttb"),
+ Size = proplists:get_value(wrap_sz, Opts, 128),
+ Count = proplists:get_value(wrap_c, Opts, 8),
+ {wrap, Name, Size*1024, Count}.
+
+
+create_logwindow(_Parent, false) -> {false, false};
+create_logwindow(Parent, true) ->
+ LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750, 800}}]),
+ MB = wxMenuBar:new(),
+ File = wxMenu:new(),
+ wxMenu:append(File, ?LOG_CLEAR, "Clear Log\tCtrl-C"),
+ wxMenu:append(File, ?LOG_SAVE, "Save Log\tCtrl-S"),
+ wxMenu:append(File, ?wxID_CLOSE, "Close"),
+ wxMenuBar:append(MB, File, "File"),
+ wxFrame:setMenuBar(LogWin, MB),
+ Text = wxTextCtrl:new(LogWin, ?wxID_ANY,
+ [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor
+ ?wxTE_DONTWRAP bor ?wxTE_READONLY}]),
+ Font = observer_wx:get_attrib({font, fixed}),
+ Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]),
+ true = wxTextCtrl:setDefaultStyle(Text, Attr),
+ wxFrame:connect(LogWin, close_window, [{skip, true}]),
+ wxFrame:connect(LogWin, command_menu_selected, [{userData, Text}]),
+ wxFrame:show(LogWin),
+ {LogWin, Text}.
setup_ttb(TPs, TPids) ->
_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 +726,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 +782,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 +796,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 +818,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 +843,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_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index 7b5cdb44b9..31d5f3d632 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -24,8 +24,6 @@
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
handle_event/2, handle_sync_event/3, handle_cast/2]).
--export([get_table/3]).
-
-include("observer_defs.hrl").
-import(observer_lib, [to_str/1]).
@@ -221,33 +219,10 @@ search_area(Parent) ->
edit(Index, #state{pid=Pid, frame=Frame}) ->
Str = get_row(Pid, Index, all),
- Dialog = wxTextEntryDialog:new(Frame, "Edit object:", [{value, Str}]),
- case wxTextEntryDialog:showModal(Dialog) of
- ?wxID_OK ->
- New = wxTextEntryDialog:getValue(Dialog),
- wxTextEntryDialog:destroy(Dialog),
- case Str =:= New of
- true -> ok;
- false ->
- complete_edit(Index, New, Pid)
- end;
- ?wxID_CANCEL ->
- wxTextEntryDialog:destroy(Dialog)
- end.
-
-complete_edit(Row, New0, Pid) ->
- New = case lists:reverse(New0) of
- [$.|_] -> New0;
- _ -> New0 ++ "."
- end,
- try
- {ok, Tokens, _} = erl_scan:string(New),
- {ok, Term} = erl_parse:parse_term(Tokens),
- Pid ! {edit, Row, Term}
- catch _:{badmatch, {error, {_, _, Err}}} ->
- self() ! {error, ["Parse error: ", Err]};
- _Err ->
- self() ! {error, ["Syntax error in: ", New]}
+ case observer_lib:user_term(Frame, "Edit object:", Str) of
+ cancel -> ok;
+ {ok, Term} -> Pid ! {edit, Index, Term};
+ Err = {error, _} -> self() ! Err
end.
handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) ->
@@ -260,14 +235,7 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
{noreply, State};
handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) ->
- wx:batch(fun() ->
- Cols = wxListCtrl:getColumnCount(Grid),
- Last = lists:foldl(fun(I, Last) ->
- Last - wxListCtrl:getColumnWidth(Grid, I)
- end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)),
- Size = max(?DEFAULT_COL_WIDTH, Last),
- wxListCtrl:setColumnWidth(Grid, Cols-1, Size)
- end),
+ observer_lib:set_listctrl_col_size(Grid, W),
{noreply, State};
handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}},
@@ -537,7 +505,7 @@ table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) ->
%% io:format("ignoring refresh", []),
table_holder(S0);
refresh ->
- GetTab = rpc:call(S0#holder.node, ?MODULE, get_table,
+ GetTab = rpc:call(S0#holder.node, observer_backend, get_table,
[self(), S0#holder.tabid, S0#holder.source]),
table_holder(S0#holder{pid=GetTab});
{delete, Row} ->
@@ -738,49 +706,6 @@ insert(Object, #holder{tabid=Id, source=Source, node=Node}) ->
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-get_table(Parent, Table, Module) ->
- spawn(fun() ->
- link(Parent),
- get_table2(Parent, Table, Module)
- end).
-
-get_table2(Parent, Table, Type) ->
- Size = case Type of
- ets -> ets:info(Table, size);
- mnesia -> mnesia:table_info(Table, size)
- end,
- case Size > 0 of
- false ->
- Parent ! {self(), '$end_of_table'},
- normal;
- true when Type =:= ets ->
- Mem = ets:info(Table, memory),
- Average = Mem div Size,
- NoElements = max(10, 20000 div Average),
- get_ets_loop(Parent, ets:match(Table, '$1', NoElements));
- true ->
- Mem = mnesia:table_info(Table, memory),
- Average = Mem div Size,
- NoElements = max(10, 20000 div Average),
- Ms = [{'$1', [], ['$1']}],
- Get = fun() ->
- get_mnesia_loop(Parent, mnesia:select(Table, Ms, NoElements, read))
- end,
- %% Not a transaction, we don't want to grab locks when inspecting the table
- mnesia:async_dirty(Get)
- end.
-
-get_ets_loop(Parent, '$end_of_table') ->
- Parent ! {self(), '$end_of_table'};
-get_ets_loop(Parent, {Match, Cont}) ->
- Parent ! {self(), Match},
- get_ets_loop(Parent, ets:match(Cont)).
-
-get_mnesia_loop(Parent, '$end_of_table') ->
- Parent ! {self(), '$end_of_table'};
-get_mnesia_loop(Parent, {Match, Cont}) ->
- Parent ! {self(), Match},
- get_ets_loop(Parent, mnesia:select(Cont)).
column_names(Node, Type, Table) ->
case Type of
diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl
index ded03fadb1..ad3e8c14ab 100644
--- a/lib/observer/src/observer_tv_wx.erl
+++ b/lib/observer/src/observer_tv_wx.erl
@@ -23,8 +23,6 @@
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
handle_event/2, handle_sync_event/3, handle_cast/2]).
--export([get_table_list/1]). %% RPC called move to runtime tools?
-
-behaviour(wx_object).
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
@@ -92,8 +90,7 @@ init([Notebook, Parent]) ->
wxListCtrl:connect(Grid, size, [{skip, true}]),
wxWindow:setFocus(Grid),
- Timer = observer_lib:start_timer(10),
- {Panel, #state{grid=Grid, parent=Parent, timer=Timer}}.
+ {Panel, #state{grid=Grid, parent=Parent, timer={false, 10}}}.
handle_event(#wx{id=?ID_REFRESH},
State = #state{node=Node, grid=Grid, opt=Opt}) ->
@@ -121,20 +118,18 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0})
?ID_UNREADABLE -> Opt0#opt{unread_hidden= not Opt0#opt.unread_hidden};
?ID_SYSTEM_TABLES -> Opt0#opt{sys_hidden= not Opt0#opt.sys_hidden}
end,
- Tables = get_tables(Node, Opt),
- Tabs = update_grid(Grid, Opt, Tables),
- wxWindow:setFocus(Grid),
- {noreply, State#state{opt=Opt, tabs=Tabs}};
+ case get_tables2(Node, Opt) of
+ Error = {error, _} ->
+ self() ! Error,
+ {noreply, State};
+ Tables ->
+ Tabs = update_grid(Grid, Opt, Tables),
+ wxWindow:setFocus(Grid),
+ {noreply, State#state{opt=Opt, tabs=Tabs}}
+ end;
handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) ->
- wx:batch(fun() ->
- Cols = wxListCtrl:getColumnCount(Grid),
- Last = lists:foldl(fun(I, Last) ->
- Last - wxListCtrl:getColumnWidth(Grid, I)
- end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)),
- Size = max(200, Last),
- wxListCtrl:setColumnWidth(Grid, Cols-1, Size)
- end),
+ observer_lib:set_listctrl_col_size(Grid, W),
{noreply, State};
handle_event(#wx{obj=Grid, event=#wxList{type=command_list_item_activated,
@@ -169,18 +164,17 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL},
Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60),
{noreply, State#state{timer=Timer}};
-handle_event(Event, State) ->
- io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]),
- {noreply, State}.
+handle_event(Event, _State) ->
+ error({unhandled_event, Event}).
handle_sync_event(_Event, _Obj, _State) ->
ok.
-handle_call(_Event, _From, State) ->
- {noreply, State}.
+handle_call(Event, From, _State) ->
+ error({unhandled_call, Event, From}).
-handle_cast(_Event, State) ->
- {noreply, State}.
+handle_cast(Event, _State) ->
+ error({unhandled_cast, Event}).
handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) ->
Tables = get_tables(Node, Opt),
@@ -200,12 +194,6 @@ handle_info(not_active, State = #state{timer = Timer0}) ->
Timer = observer_lib:stop_timer(Timer0),
{noreply, State#state{timer=Timer}};
-handle_info({node, Node}, State = #state{grid=Grid, opt=Opt}) ->
- Tables = get_tables(Node, Opt),
- Tabs = update_grid(Grid, Opt, Tables),
- wxWindow:setFocus(Grid),
- {noreply, State#state{node=Node, tabs=Tabs}};
-
handle_info({error, Error}, State) ->
handle_error(Error),
{noreply, State};
@@ -240,95 +228,41 @@ create_menus(Parent, #opt{sys_hidden=Sys, unread_hidden=UnR, type=Type}) ->
]}],
observer_wx:create_menus(Parent, MenuEntries).
-get_tables(Node, Opt) ->
- case rpc:call(Node, ?MODULE, get_table_list, [Opt]) of
- {badrpc, Error} ->
- self() ! {error, Error},
- [];
- {error, Error} ->
- self() ! {error, Error},
+get_tables(Node, Opts) ->
+ case get_tables2(Node, Opts) of
+ Error = {error, _} ->
+ self() ! Error,
[];
+ Res ->
+ Res
+ end.
+get_tables2(Node, #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread}) ->
+ Args = [Type, [{sys_hidden,Sys}, {unread_hidden,Unread}]],
+ case rpc:call(Node, observer_backend, get_table_list, Args) of
+ {badrpc, Error} ->
+ {error, Error};
+ Error = {error, _} ->
+ Error;
Result ->
- Result
+ [list_to_tabrec(Tab) || Tab <- Result]
end.
-get_table_list(#opt{type=ets, unread_hidden=HideUnread, sys_hidden=HideSys}) ->
- Info = fun(Id, Acc) ->
- try
- TabId = case ets:info(Id, named_table) of
- true -> ignore;
- false -> Id
- end,
- Name = ets:info(Id, name),
- Protection = ets:info(Id, protection),
- ignore(HideUnread andalso Protection == private, unreadable),
- Owner = ets:info(Id, owner),
- RegName = case catch process_info(Owner, registered_name) of
- [] -> ignore;
- {registered_name, ProcName} -> ProcName
- end,
- ignore(HideSys andalso ordsets:is_element(RegName, sys_processes()), system_tab),
- ignore(HideSys andalso ordsets:is_element(Name, sys_tables()), system_tab),
- ignore((RegName == mnesia_monitor)
- andalso Name /= schema
- andalso is_atom((catch mnesia:table_info(Name, where_to_read))), mnesia_tab),
- Memory = ets:info(Id, memory) * erlang:system_info(wordsize),
- Tab = #tab{name = Name,
- id = TabId,
- protection = Protection,
- owner = Owner,
- size = ets:info(Id, size),
- reg_name = RegName,
- type = ets:info(Id, type),
- keypos = ets:info(Id, keypos),
- heir = ets:info(Id, heir),
- memory = Memory,
- compressed = ets:info(Id, compressed),
- fixed = ets:info(Id, fixed)
- },
- [Tab|Acc]
- catch _:_What ->
- %% io:format("Skipped ~p: ~p ~n",[Id, _What]),
- Acc
- end
- end,
- lists:foldl(Info, [], ets:all());
-get_table_list(#opt{type=mnesia, sys_hidden=HideSys}) ->
- Owner = ets:info(schema, owner),
- Owner /= undefined orelse
- throw({error, "Mnesia is not running on: " ++ atom_to_list(node())}),
- {registered_name, RegName} = process_info(Owner, registered_name),
- Info = fun(Id, Acc) ->
- try
- Name = Id,
- ignore(HideSys andalso ordsets:is_element(Name, mnesia_tables()), system_tab),
- ignore(Name =:= schema, mnesia_tab),
- Storage = mnesia:table_info(Id, storage_type),
- Tab0 = #tab{name = Name,
- owner = Owner,
- size = mnesia:table_info(Id, size),
- reg_name = RegName,
- type = mnesia:table_info(Id, type),
- keypos = 2,
- memory = mnesia:table_info(Id, memory) * erlang:system_info(wordsize),
- storage = Storage,
- index = mnesia:table_info(Id, index)
- },
- Tab = if Storage == disc_only_copies ->
- Tab0#tab{fixed = element(2, dets:info(Id, safe_fixed)) /= []};
- (Storage == ram_copies) orelse
- (Storage == disc_copies) ->
- Tab0#tab{fixed = ets:info(Id, fixed),
- compressed = ets:info(Id, compressed)};
- true -> Tab0
- end,
- [Tab|Acc]
- catch _:_What ->
- %% io:format("Skipped ~p: ~p ~n",[Id, _What]),
- Acc
- end
- end,
- lists:foldl(Info, [], mnesia:system_info(tables)).
+list_to_tabrec(PL) ->
+ #tab{name = proplists:get_value(name, PL),
+ id = proplists:get_value(id, PL, ignore),
+ size = proplists:get_value(size, PL, 0),
+ memory= proplists:get_value(memory, PL, 0), %% In bytes
+ owner=proplists:get_value(owner, PL),
+ reg_name=proplists:get_value(reg_name, PL),
+ protection = proplists:get_value(protection, PL, public),
+ type=proplists:get_value(type, PL, set),
+ keypos=proplists:get_value(keypos, PL, 1),
+ heir=proplists:get_value(heir, PL, none),
+ compressed=proplists:get_value(compressed, PL, false),
+ fixed=proplists:get_value(fixed, PL, false),
+ %% Mnesia Info
+ storage =proplists:get_value(storage, PL),
+ index = proplists:get_value(index, PL)}.
display_table_info(Parent0, Node, Source, Table) ->
Parent = observer_lib:get_wx_parent(Parent0),
@@ -382,59 +316,6 @@ list_to_strings([A]) -> integer_to_list(A);
list_to_strings([A,B]) ->
integer_to_list(A) ++ " ," ++ list_to_strings(B).
-sys_tables() ->
- [ac_tab, asn1,
- cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table,
- cell_id, cell_pos, clist,
- cover_internal_data_table, cover_collected_remote_data_table, cover_binary_code_table,
- code, code_names, cookies,
- corba_policy, corba_policy_associations,
- dets, dets_owners, dets_registry,
- disk_log_names, disk_log_pids,
- eprof, erl_atom_cache, erl_epmd_nodes,
- etop_accum_tab, etop_tr,
- ets_coverage_data,
- file_io_servers,
- gs_mapping, gs_names, gstk_db,
- gstk_grid_cellid, gstk_grid_cellpos, gstk_grid_id,
- httpd,
- id,
- ign_req_index, ign_requests,
- index,
- inet_cache, inet_db, inet_hosts,
- 'InitialReferences',
- int_db,
- interpreter_includedirs_macros,
- ir_WstringDef,
- lmcounter, locks,
-% mnesia_decision,
- mnesia_gvar, mnesia_stats,
-% mnesia_transient_decision,
- pg2_table,
- queue,
- schema,
- shell_records,
- snmp_agent_table, snmp_local_db2, snmp_mib_data, snmp_note_store, snmp_symbolic_ets,
- tkFun, tkLink, tkPriv,
- ttb, ttb_history_table,
- udp_fds, udp_pids
- ].
-
-sys_processes() ->
- [auth, code_server, global_name_server, inet_db,
- mnesia_recover, net_kernel, timer_server, wxe_master].
-
-mnesia_tables() ->
- [ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef,
- ir_Contained, ir_Container, ir_EnumDef, ir_ExceptionDef,
- ir_IDLType, ir_IRObject, ir_InterfaceDef, ir_ModuleDef,
- ir_ORB, ir_OperationDef, ir_PrimitiveDef, ir_Repository,
- ir_SequenceDef, ir_StringDef, ir_StructDef, ir_TypedefDef,
- ir_UnionDef, logTable, logTransferTable, mesh_meas,
- mesh_type, mnesia_clist, orber_CosNaming,
- orber_objkeys, user
- ].
-
handle_error(Foo) ->
Str = io_lib:format("ERROR: ~s~n",[Foo]),
observer_lib:display_info_dialog(Str).
@@ -470,6 +351,3 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) ->
end,
lists:foldl(Update, 0, ProcInfo),
ProcInfo.
-
-ignore(true, Reason) -> throw(Reason);
-ignore(_,_ ) -> ok.
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index f9dec1ab1b..5a593abf11 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -54,13 +54,17 @@
tv_panel,
sys_panel,
trace_panel,
+ app_panel,
active_tab,
node,
nodes
}).
start() ->
- wx_object:start(?MODULE, [], []).
+ case wx_object:start(?MODULE, [], []) of
+ Err = {error, _} -> Err;
+ _Obj -> ok
+ end.
create_menus(Object, Menus) when is_list(Menus) ->
wx_object:call(Object, {create_menus, Menus}).
@@ -78,7 +82,7 @@ init(_Args) ->
wx:new(),
catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer",
- [{size, {1000, 500}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
+ [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
wxFrame:setIcon(Frame, Icon),
@@ -125,6 +129,10 @@ setup(#state{frame = Frame} = State) ->
%% I postpone the creation of the other tabs so they can query/use
%% the window size
+ %% App Viewer Panel
+ AppPanel = observer_app_wx:start_link(Notebook, self()),
+ wxNotebook:addPage(Notebook, AppPanel, "Applications", []),
+
%% Process Panel
ProPanel = observer_pro_wx:start_link(Notebook, self()),
wxNotebook:addPage(Notebook, ProPanel, "Processes", []),
@@ -137,6 +145,7 @@ setup(#state{frame = Frame} = State) ->
TracePanel = observer_trace_wx:start_link(Notebook, self()),
wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []),
+
%% Force redraw (window needs it)
wxWindow:refresh(Panel),
@@ -150,16 +159,24 @@ setup(#state{frame = Frame} = State) ->
pro_panel = ProPanel,
tv_panel = TVPanel,
trace_panel = TracePanel,
+ app_panel = AppPanel,
active_tab = SysPid,
node = node(),
nodes = Nodes
},
%% Create resources which we don't want to duplicate
- SysFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
- SysFontSize = wxFont:getPointSize(SysFont),
- Modern = wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
- put({font, modern}, Modern),
- put({font, fixed}, Modern),
+ SysFont = wxSystemSettings:getFont(?wxSYS_SYSTEM_FIXED_FONT),
+ %% OemFont = wxSystemSettings:getFont(?wxSYS_OEM_FIXED_FONT),
+ %% io:format("Sz sys ~p(~p) oem ~p(~p)~n",
+ %% [wxFont:getPointSize(SysFont), wxFont:isFixedWidth(SysFont),
+ %% wxFont:getPointSize(OemFont), wxFont:isFixedWidth(OemFont)]),
+ Fixed = case wxFont:isFixedWidth(SysFont) of
+ true -> SysFont;
+ false -> %% Sigh
+ SysFontSize = wxFont:getPointSize(SysFont),
+ wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL)
+ end,
+ put({font, fixed}, Fixed),
UpdState.
@@ -270,10 +287,13 @@ handle_cast(_Cast, State) ->
handle_call({create_menus, TabMenus}, _From,
State = #state{menubar=MenuBar, menus=PrevTabMenus}) ->
- wx:batch(fun() ->
- clean_menus(PrevTabMenus, MenuBar),
- observer_lib:create_menus(TabMenus, MenuBar, plugin)
- end),
+ if TabMenus == PrevTabMenus -> ignore;
+ true ->
+ wx:batch(fun() ->
+ clean_menus(PrevTabMenus, MenuBar),
+ observer_lib:create_menus(TabMenus, MenuBar, plugin)
+ end)
+ end,
{reply, ok, State#state{menus=TabMenus}};
handle_call({get_attrib, Attrib}, _From, State) ->
@@ -302,6 +322,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}.
@@ -368,9 +392,8 @@ connect2(NodeName, Opts, Cookie) ->
{error, net_kernel, Reason}
end.
-change_node_view(Node, State = #state{pro_panel=Pro, sys_panel=Sys, tv_panel=Tv}) ->
- lists:foreach(fun(Pid) -> wx_object:get_pid(Pid) ! {node, Node} end,
- [Pro, Sys, Tv]),
+change_node_view(Node, State) ->
+ get_active_pid(State) ! {active, Node},
StatusText = ["Observer - " | atom_to_list(Node)],
wxFrame:setTitle(State#state.frame, StatusText),
wxStatusBar:setStatusText(State#state.status_bar, StatusText),
@@ -380,12 +403,14 @@ check_page_title(Notebook) ->
Selection = wxNotebook:getSelection(Notebook),
wxNotebook:getPageText(Notebook, Selection).
-get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace}) ->
+get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys,
+ tv_panel=Tv, trace_panel=Trace, app_panel=App}) ->
Panel = case check_page_title(Notebook) of
"Processes" -> Pro;
"System" -> Sys;
"Table Viewer" -> Tv;
- ?TRACE_STR -> Trace
+ ?TRACE_STR -> Trace;
+ "Applications" -> App
end,
wx_object:get_pid(Panel).
@@ -468,7 +493,7 @@ default_menus(NodesMenuItems) ->
[#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]}
end,
case os:type() =:= {unix, darwin} of
- false ->
+ false ->
FileMenu = {"File", [Quit]},
HelpMenu = {"Help", [About,Help]},
[FileMenu, NodeMenu, HelpMenu];
diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl
index 9c1f9da5b1..2f8ffbcdb6 100644
--- a/lib/runtime_tools/src/observer_backend.erl
+++ b/lib/runtime_tools/src/observer_backend.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -21,6 +21,9 @@
%% General
-export([vsn/0]).
+%% observer stuff
+-export([sys_info/0, get_table/3, get_table_list/2]).
+
%% etop stuff
-export([etop_collect/1]).
-include("observer_backend.hrl").
@@ -42,7 +45,158 @@ vsn() ->
Error -> Error
end.
+%%
+%% observer backend
+%%
+sys_info() ->
+ {{_,Input},{_,Output}} = erlang:statistics(io),
+ [{process_count, erlang:system_info(process_count)},
+ {process_limit, erlang:system_info(process_limit)},
+ {uptime, element(1, erlang:statistics(wall_clock))},
+ {run_queue, erlang:statistics(run_queue)},
+ {io_input, Input},
+ {io_output, Output},
+ {logical_processors, erlang:system_info(logical_processors)},
+ {logical_processors_available, erlang:system_info(logical_processors_available)},
+ {logical_processors_online, erlang:system_info(logical_processors_online)},
+
+ {otp_release, erlang:system_info(otp_release)},
+ {version, erlang:system_info(version)},
+ {system_architecture, erlang:system_info(system_architecture)},
+ {kernel_poll, erlang:system_info(kernel_poll)},
+ {smp_support, erlang:system_info(smp_support)},
+ {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})} |
+ erlang:memory()
+ ].
+
+get_table(Parent, Table, Module) ->
+ spawn(fun() ->
+ link(Parent),
+ get_table2(Parent, Table, Module)
+ end).
+
+get_table2(Parent, Table, Type) ->
+ Size = case Type of
+ ets -> ets:info(Table, size);
+ mnesia -> mnesia:table_info(Table, size)
+ end,
+ case Size > 0 of
+ false ->
+ Parent ! {self(), '$end_of_table'},
+ normal;
+ true when Type =:= ets ->
+ Mem = ets:info(Table, memory),
+ Average = Mem div Size,
+ NoElements = max(10, 20000 div Average),
+ get_ets_loop(Parent, ets:match(Table, '$1', NoElements));
+ true ->
+ Mem = mnesia:table_info(Table, memory),
+ Average = Mem div Size,
+ NoElements = max(10, 20000 div Average),
+ Ms = [{'$1', [], ['$1']}],
+ Get = fun() ->
+ get_mnesia_loop(Parent, mnesia:select(Table, Ms, NoElements, read))
+ end,
+ %% Not a transaction, we don't want to grab locks when inspecting the table
+ mnesia:async_dirty(Get)
+ end.
+get_ets_loop(Parent, '$end_of_table') ->
+ Parent ! {self(), '$end_of_table'};
+get_ets_loop(Parent, {Match, Cont}) ->
+ Parent ! {self(), Match},
+ get_ets_loop(Parent, ets:match(Cont)).
+
+get_mnesia_loop(Parent, '$end_of_table') ->
+ Parent ! {self(), '$end_of_table'};
+get_mnesia_loop(Parent, {Match, Cont}) ->
+ Parent ! {self(), Match},
+ get_mnesia_loop(Parent, mnesia:select(Cont)).
+
+get_table_list(ets, Opts) ->
+ HideUnread = proplists:get_value(unread_hidden, Opts, true),
+ HideSys = proplists:get_value(sys_hidden, Opts, true),
+ Info = fun(Id, Acc) ->
+ try
+ TabId = case ets:info(Id, named_table) of
+ true -> ignore;
+ false -> Id
+ end,
+ Name = ets:info(Id, name),
+ Protection = ets:info(Id, protection),
+ ignore(HideUnread andalso Protection == private, unreadable),
+ Owner = ets:info(Id, owner),
+ RegName = case catch process_info(Owner, registered_name) of
+ [] -> ignore;
+ {registered_name, ProcName} -> ProcName
+ end,
+ ignore(HideSys andalso ordsets:is_element(RegName, sys_processes()), system_tab),
+ ignore(HideSys andalso ordsets:is_element(Name, sys_tables()), system_tab),
+ ignore((RegName == mnesia_monitor)
+ andalso Name /= schema
+ andalso is_atom((catch mnesia:table_info(Name, where_to_read))), mnesia_tab),
+ Memory = ets:info(Id, memory) * erlang:system_info(wordsize),
+ Tab = [{name,Name},
+ {id,TabId},
+ {protection,Protection},
+ {owner,Owner},
+ {size,ets:info(Id, size)},
+ {reg_name,RegName},
+ {type,ets:info(Id, type)},
+ {keypos,ets:info(Id, keypos)},
+ {heir,ets:info(Id, heir)},
+ {memory,Memory},
+ {compressed,ets:info(Id, compressed)},
+ {fixed,ets:info(Id, fixed)}
+ ],
+ [Tab|Acc]
+ catch _:_What ->
+ %% io:format("Skipped ~p: ~p ~n",[Id, _What]),
+ Acc
+ end
+ end,
+ lists:foldl(Info, [], ets:all());
+
+get_table_list(mnesia, Opts) ->
+ HideSys = proplists:get_value(sys_hidden, Opts, true),
+ Owner = ets:info(schema, owner),
+ Owner /= undefined orelse
+ throw({error, "Mnesia is not running on: " ++ atom_to_list(node())}),
+ {registered_name, RegName} = process_info(Owner, registered_name),
+ Info = fun(Id, Acc) ->
+ try
+ Name = Id,
+ ignore(HideSys andalso ordsets:is_element(Name, mnesia_tables()), system_tab),
+ ignore(Name =:= schema, mnesia_tab),
+ Storage = mnesia:table_info(Id, storage_type),
+ Tab0 = [{name,Name},
+ {owner,Owner},
+ {size,mnesia:table_info(Id, size)},
+ {reg_name,RegName},
+ {type,mnesia:table_info(Id, type)},
+ {keypos,2},
+ {memory,mnesia:table_info(Id, memory) * erlang:system_info(wordsize)},
+ {storage,Storage},
+ {index,mnesia:table_info(Id, index)}
+ ],
+ Tab = if Storage == disc_only_copies ->
+ [{fixed, dets:info(Id, safe_fixed)}|Tab0];
+ (Storage == ram_copies) orelse
+ (Storage == disc_copies) ->
+ [{fixed, ets:info(Id, fixed)},
+ {compressed, ets:info(Id, compressed)}|Tab0];
+ true -> Tab0
+ end,
+ [Tab|Acc]
+ catch _:_What ->
+ %% io:format("Skipped ~p: ~p ~p ~n",[Id, _What, erlang:get_stacktrace()]),
+ Acc
+ end
+ end,
+ lists:foldl(Info, [], mnesia:system_info(tables)).
%%
%% etop backend
@@ -395,3 +549,62 @@ match_filenames(Dir,MetaFile,[H|T],Files) ->
end;
match_filenames(_Dir,_MetaFile,[],Files) ->
Files.
+
+
+%%%%%%%%%%%%%%%%%
+
+sys_tables() ->
+ [ac_tab, asn1,
+ cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table,
+ cell_id, cell_pos, clist,
+ cover_internal_data_table, cover_collected_remote_data_table, cover_binary_code_table,
+ code, code_names, cookies,
+ corba_policy, corba_policy_associations,
+ dets, dets_owners, dets_registry,
+ disk_log_names, disk_log_pids,
+ eprof, erl_atom_cache, erl_epmd_nodes,
+ etop_accum_tab, etop_tr,
+ ets_coverage_data,
+ file_io_servers,
+ gs_mapping, gs_names, gstk_db,
+ gstk_grid_cellid, gstk_grid_cellpos, gstk_grid_id,
+ httpd,
+ id,
+ ign_req_index, ign_requests,
+ index,
+ inet_cache, inet_db, inet_hosts,
+ 'InitialReferences',
+ int_db,
+ interpreter_includedirs_macros,
+ ir_WstringDef,
+ lmcounter, locks,
+ % mnesia_decision,
+ mnesia_gvar, mnesia_stats,
+ % mnesia_transient_decision,
+ pg2_table,
+ queue,
+ schema,
+ shell_records,
+ snmp_agent_table, snmp_local_db2, snmp_mib_data, snmp_note_store, snmp_symbolic_ets,
+ tkFun, tkLink, tkPriv,
+ ttb, ttb_history_table,
+ udp_fds, udp_pids
+ ].
+
+sys_processes() ->
+ [auth, code_server, global_name_server, inet_db,
+ mnesia_recover, net_kernel, timer_server, wxe_master].
+
+mnesia_tables() ->
+ [ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef,
+ ir_Contained, ir_Container, ir_EnumDef, ir_ExceptionDef,
+ ir_IDLType, ir_IRObject, ir_InterfaceDef, ir_ModuleDef,
+ ir_ORB, ir_OperationDef, ir_PrimitiveDef, ir_Repository,
+ ir_SequenceDef, ir_StringDef, ir_StructDef, ir_TypedefDef,
+ ir_UnionDef, logTable, logTransferTable, mesh_meas,
+ mesh_type, mnesia_clist, orber_CosNaming,
+ orber_objkeys, user
+ ].
+
+ignore(true, Reason) -> throw(Reason);
+ignore(_,_ ) -> ok.