aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer')
-rw-r--r--lib/observer/Makefile2
-rw-r--r--lib/observer/doc/src/book.xml2
-rw-r--r--lib/observer/doc/src/crashdump.xml2
-rw-r--r--lib/observer/doc/src/etop.xml2
-rw-r--r--lib/observer/doc/src/notes.xml175
-rw-r--r--lib/observer/doc/src/notes_history.xml2
-rw-r--r--lib/observer/doc/src/observer_ug.xml111
-rw-r--r--lib/observer/doc/src/part.xml2
-rw-r--r--lib/observer/doc/src/part_notes.xml2
-rw-r--r--lib/observer/doc/src/part_notes_history.xml2
-rw-r--r--lib/observer/doc/src/ref_man.xml2
-rw-r--r--lib/observer/doc/src/ttb.xml74
-rw-r--r--lib/observer/include/etop.hrl2
-rwxr-xr-xlib/observer/priv/bin/cdv2
-rw-r--r--lib/observer/priv/bin/cdv.bat2
-rw-r--r--lib/observer/priv/crashdump_viewer.tool2
-rw-r--r--lib/observer/priv/crashdump_viewer/collapsd.gifbin141 -> 0 bytes
-rw-r--r--lib/observer/priv/crashdump_viewer/exploded.gifbin143 -> 0 bytes
-rw-r--r--lib/observer/src/Makefile13
-rw-r--r--lib/observer/src/cdv_atom_cb.erl2
-rw-r--r--lib/observer/src/cdv_bin_cb.erl4
-rw-r--r--lib/observer/src/cdv_detail_wx.erl4
-rw-r--r--lib/observer/src/cdv_dist_cb.erl2
-rw-r--r--lib/observer/src/cdv_ets_cb.erl9
-rw-r--r--lib/observer/src/cdv_fun_cb.erl2
-rw-r--r--lib/observer/src/cdv_gen_cb.erl2
-rw-r--r--lib/observer/src/cdv_html_wx.erl2
-rw-r--r--lib/observer/src/cdv_info_wx.erl2
-rw-r--r--lib/observer/src/cdv_int_tab_cb.erl2
-rw-r--r--lib/observer/src/cdv_mem_cb.erl6
-rw-r--r--lib/observer/src/cdv_mod_cb.erl2
-rw-r--r--lib/observer/src/cdv_multi_wx.erl2
-rw-r--r--lib/observer/src/cdv_port_cb.erl2
-rw-r--r--lib/observer/src/cdv_proc_cb.erl2
-rw-r--r--lib/observer/src/cdv_sched_cb.erl2
-rw-r--r--lib/observer/src/cdv_table_wx.erl2
-rw-r--r--lib/observer/src/cdv_term_cb.erl2
-rw-r--r--lib/observer/src/cdv_timer_cb.erl2
-rw-r--r--lib/observer/src/cdv_virtual_list_wx.erl2
-rw-r--r--lib/observer/src/cdv_wx.erl2
-rw-r--r--lib/observer/src/crashdump_viewer.erl99
-rw-r--r--lib/observer/src/crashdump_viewer.hrl2
-rw-r--r--lib/observer/src/etop.erl26
-rw-r--r--lib/observer/src/etop_defs.hrl2
-rw-r--r--lib/observer/src/etop_tr.erl2
-rw-r--r--lib/observer/src/etop_txt.erl26
-rw-r--r--lib/observer/src/multitrace.erl2
-rw-r--r--lib/observer/src/observer.app.src3
-rw-r--r--lib/observer/src/observer.appup.src2
-rw-r--r--lib/observer/src/observer.erl2
-rw-r--r--lib/observer/src/observer_alloc_wx.erl173
-rw-r--r--lib/observer/src/observer_app_wx.erl65
-rw-r--r--lib/observer/src/observer_defs.hrl13
-rw-r--r--lib/observer/src/observer_html_lib.erl2
-rw-r--r--lib/observer/src/observer_lib.erl150
-rw-r--r--lib/observer/src/observer_perf_wx.erl749
-rw-r--r--lib/observer/src/observer_port_wx.erl565
-rw-r--r--lib/observer/src/observer_pro_wx.erl71
-rw-r--r--lib/observer/src/observer_procinfo.erl20
-rw-r--r--lib/observer/src/observer_sys_wx.erl100
-rw-r--r--lib/observer/src/observer_trace_wx.erl674
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl106
-rw-r--r--lib/observer/src/observer_tv.hrl2
-rw-r--r--lib/observer/src/observer_tv_wx.erl65
-rw-r--r--lib/observer/src/observer_wx.erl112
-rw-r--r--lib/observer/src/ttb.erl60
-rw-r--r--lib/observer/src/ttb_et.erl2
-rw-r--r--lib/observer/test/Makefile6
-rw-r--r--lib/observer/test/crashdump_helper.erl4
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl23
-rw-r--r--lib/observer/test/etop_SUITE.erl4
-rw-r--r--lib/observer/test/observer_SUITE.erl49
-rw-r--r--lib/observer/test/ttb_SUITE.erl61
-rw-r--r--lib/observer/vsn.mk2
74 files changed, 2719 insertions, 981 deletions
diff --git a/lib/observer/Makefile b/lib/observer/Makefile
index 865dddaf51..8483922f76 100644
--- a/lib/observer/Makefile
+++ b/lib/observer/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2002-2009. All Rights Reserved.
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/lib/observer/doc/src/book.xml b/lib/observer/doc/src/book.xml
index 5ef1fd794b..7cc60718b1 100644
--- a/lib/observer/doc/src/book.xml
+++ b/lib/observer/doc/src/book.xml
@@ -4,7 +4,7 @@
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<header titlestyle="normal">
<copyright>
- <year>2002</year><year>2013</year>
+ <year>2002</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/crashdump.xml b/lib/observer/doc/src/crashdump.xml
index 27e88d07e5..48f944cbce 100644
--- a/lib/observer/doc/src/crashdump.xml
+++ b/lib/observer/doc/src/crashdump.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2003</year>
- <year>2013</year>
+ <year>2016</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/etop.xml b/lib/observer/doc/src/etop.xml
index 52f3b2a156..d70d9d1d23 100644
--- a/lib/observer/doc/src/etop.xml
+++ b/lib/observer/doc/src/etop.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2002</year>
- <year>2013</year>
+ <year>2016</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index dd99f45b19..79e2b2b9db 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2013</year>
+ <year>2004</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -32,6 +32,179 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ etop erroneously reported the average scheduler
+ utilization since the tool was first started instead of
+ the scheduler utilization since last update. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-14090 Aux Id: seq13232 </p>
+ </item>
+ <item>
+ <p>
+ crashdump_viewer crashed when the 'Slogan' had more than
+ one line. This is now corrected.</p>
+ <p>
+ Own Id: OTP-14093 Aux Id: ERL-318 </p>
+ </item>
+ <item>
+ <p>
+ When clicking an HTML-link to a port before the port tab
+ has been opened for the first time, observer would crash
+ since port info is not initiated. This is now corrected.</p>
+ <p>
+ Own Id: OTP-14151 Aux Id: PR-1296 </p>
+ </item>
+ <item>
+ <p>The dialyzer and observer applications will now use a
+ portable way to find the home directory. That means that
+ there is no longer any need to manually set the HOME
+ environment variable on Windows.</p>
+ <p>
+ Own Id: OTP-14249 Aux Id: ERL-161 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The shell script (priv/bin/cdv) and bat file
+ (priv/bin/cdv.bat) which can be used for starting
+ crashdump_viewer both started a distributed erlang node.
+ This would cause any attempt at starting a second
+ instance of the crashdump_viewer to fail. To solve this
+ problem, cdv and cdv.bat now use non-distributed nodes
+ when starting the crashdump_viewer.</p>
+ <p>
+ Own Id: OTP-14010</p>
+ </item>
+ <item>
+ <p>
+ A bug caused the number of buckets to be shown in the
+ 'Objects' column, and the number of objects to be shown
+ in the 'Memory' column for ets table in crashdump_viewer.
+ This is now corrected.</p>
+ <p>
+ Own Id: OTP-14064</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add option <c>queue_size</c> to ttb:tracer/2. This sets
+ the maximum queue size for the IP trace driver which is
+ used when tracing to shell and/or <c>{local,File}</c>.</p>
+ <p>
+ The default value for <c>queue_size</c> is specified by
+ <c>dbg</c>, and it is now changed from 50 to 200.</p>
+ <p>
+ Own Id: OTP-13829 Aux Id: seq13171 </p>
+ </item>
+ <item>
+ <p>
+ The port information page is updated to show more
+ information per port.</p>
+ <p>
+ Own Id: OTP-13948 Aux Id: ERL-272 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed error handling in observer when mnesia tables was
+ requested and not available.</p>
+ <p>
+ Own Id: OTP-13845 Aux Id: ERL-237 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a crash happening when observing another node, who
+ have a different number of schedulers than the current
+ one.</p>
+ <p>
+ Own Id: OTP-13702 Aux Id: ERL-171 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Update observer GUI to support tracing on ports, and to
+ set matchspecs for send/receive. This required some minor
+ bugfixes in runtime_tools/dbg.</p>
+ <p>
+ Own Id: OTP-13481</p>
+ </item>
+ <item>
+ <p>
+ Update dbg and ttb to work with a tracer module as tracer
+ and tracing on ports.</p>
+ <p>
+ Own Id: OTP-13500</p>
+ </item>
+ <item>
+ <p>
+ Added possibility to change update frequency and length
+ of the graph windows.</p>
+ <p>
+ Own Id: OTP-13555</p>
+ </item>
+ <item>
+ <p>
+ Improved background coloring to work with dark themes and
+ other visual improvements.</p>
+ <p>
+ Own Id: OTP-13556</p>
+ </item>
+ <item>
+ <p>
+ Crashdump viewer now allows port info "Port controls
+ forker process..."</p>
+ <p>
+ Own Id: OTP-13647</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.1.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/observer/doc/src/notes_history.xml b/lib/observer/doc/src/notes_history.xml
index ec155b852f..ef20a6dfa4 100644
--- a/lib/observer/doc/src/notes_history.xml
+++ b/lib/observer/doc/src/notes_history.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2006</year>
- <year>2013</year>
+ <year>2016</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml
index ca354df864..6eb72f3e58 100644
--- a/lib/observer/doc/src/observer_ug.xml
+++ b/lib/observer/doc/src/observer_ug.xml
@@ -168,7 +168,7 @@
<item><p>The length of the message queue for the process.</p></item>
</taglist>
- <p>Option <em>Process info</em> opens a detailed information window on the selected process,
+ <p>Option <em>Process info</em> opens a detailed information window on the process under the mouse pointer,
including the following:</p>
<taglist>
<tag>Process Information</tag>
@@ -195,12 +195,53 @@
</p>
</note>
- <p>Option <em>Trace Processes</em> adds the selected process identifiers to tab
+ <p>Option <em>Trace selected processes</em> adds the selected process identifiers to tab
<em>Trace Overview</em> plus the node that the processes reside on.
</p>
- <p>Option <em>Trace Named Processes</em> adds the registered name of the processes. This can be
+ <p>Option <em>Trace selected processes by name</em> adds the registered name of the processes. This can be
useful when tracing is done on many nodes, as processes with that name are then traced on
all traced nodes.</p>
+ <p>Option <em>Kill process</em> brutally kills the processes under
+ the mouse pointer by sending an exit signal with
+ reason <c>kill</c>.</p>
+
+ </section>
+
+ <section>
+ <title>Ports Tab</title>
+ <p>Tab <em>Ports</em> lists port information in columns.
+ For each port the following information is displayed:
+ </p>
+ <taglist>
+ <tag>Id</tag>
+ <item><p>The port identifier.</p></item>
+ <tag>Connected</tag>
+ <item><p>The process identifier for the process that owns the
+ port.</p></item>
+ <tag>Name</tag>
+ <item><p>The registered name of the port, if any.</p></item>
+ <tag>Controls</tag>
+ <item><p>The name of the command set by <seealso marker="erts:erlang#open_port-2"><c>erlang:open_port/2</c></seealso>.</p></item>
+ <tag>Slot</tag>
+ <item><p>The internal index of the port.</p></item>
+ </taglist>
+
+ <p>Option <em>Port info</em> opens a detailed information window
+ for the port under the mouse pointer. In addition to the
+ information above, it also shows links and monitors.</p>
+
+ <p>Option <em>Trace selected ports</em> adds the selected port
+ identifiers, and the nodes that the ports reside on,
+ to tab <em>Trace Overview</em>.</p>
+
+ <p>Option <em>Trace selected ports by name</em> adds the
+ registered name of the port to tab <em>Trace Overview</em>. This
+ can be useful when tracing is done on many nodes, as ports with
+ that name are then traced on all traced nodes.</p>
+
+ <p>Option <em>Close</em>
+ executes <seealso marker="erts:erlang#port_close-1"><c>erlang:port_close/1</c></seealso>
+ on the port under the mouse pointer.</p>
</section>
@@ -211,8 +252,11 @@
applications are not diplayed. Use menu <em>View</em> to view "system"
ETS tables, unreadable ETS tables, or Mnesia tables.
</p>
- <p>Double-click to view the table content. To view table information, select the table
- and activate menu <em>View &gt; Table information</em>.</p>
+ <p>Double-click to view the table content, or right-click and
+ select option <em>Show Table Content</em>. To view table
+ information, select the table and activate menu <em>View &gt;
+ Table information</em>, or right-click and select option <em>Table
+ info</em>.</p>
<p>You can use <seealso marker="stdlib:re">regular
expressions</seealso> and search for objects, and edit or delete them.
</p>
@@ -220,11 +264,12 @@
<section>
<title>Trace Overview Tab</title>
- <p>Tab <em>Trace Overview</em> handles tracing. Trace
- by selecting the 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 many others.
+ <p>Tab <em>Trace Overview</em> handles tracing. Trace by selecting
+ the processes or ports to be traced and how to trace them. For
+ processes, you can trace messages, function calls, scheduling,
+ garbage collections, and process-related events such
+ as <c>spawn</c>, <c>exit</c>, and many others. For ports, you can
+ trace messages, scheduling and port-related events.
</p>
<p>To trace function calls, you also need to set up
@@ -234,27 +279,51 @@
specifications can also be used to trigger more information
in the trace messages.
</p>
- <note><p>Trace patterns only apply to the traced processes.</p></note>
+
+ <p>You can also set match specifications on messages. By default,
+ if tracing messages, all messages sent and/or received by the
+ process or port are traced. Match specifications can be used to
+ reduce the number of traced messages and/or to trigger more
+ information in the trace messages.</p>
+
+ <note><p>Trace patterns only apply to the traced processes and
+ ports.</p></note>
<p>
- Processes are added from the <em>Applications</em> or <em>Processes</em> tabs.
- A special <em>new</em> identifier, meaning all processes spawned after trace
- start, can be added with button <em>Add 'new' Process</em>.
+ Processes are added from the <em>Applications</em>
+ or <em>Processes</em> tabs. Ports are added from
+ the <em>Ports</em> tab. A special <em>new</em> identifier,
+ meaning all processes, or ports, started after trace start, can
+ be added with buttons <em>Add 'new' Processes</em> and <em>Add
+ 'new' Ports</em>, respecively.
</p>
<p>
- When adding processes, a window with trace options is displayed. The chosen
- options are set for the selected processes.
- Process options can be changed by right-clicking a process.
+ When adding processes or ports, a window with trace options is
+ displayed. The chosen options are set for the selected
+ processes/ports. To change the options, right-click the process
+ or port and select <em>Edit process options</em>. To remove a
+ process or port from the list, right-click and select <em>Remove
+ process</em> or <em>Remove port</em>, respectively.
</p>
<p>
- Processes added by process identifiers add the nodes these
- processes reside on in the node list. More nodes can be added by clicking
- button <em>Add Nodes</em>.
+ Processes and ports added by process/port identifiers add the
+ nodes these processes/ports reside on in the node list. More
+ nodes can be added by clicking button <em>Add Nodes</em>, or by
+ right-clicking in the <em>Nodes</em> list and select <em>Add
+ Nodes</em>. To remove nodes, select them, then right-click and
+ choose <em>Remove nodes</em>.
</p>
<p>
If function calls are traced, trace patterns must be added by clicking button
<em>Add Trace Pattern</em>. Select a module, function(s), and a match specification.
- If no functions are selected, all functions in the module are traced.
+ If no functions are selected, all functions in the module are traced.</p>
+ <p>
+ Trace patterns can also be added for traced messages. Click
+ button <em>Add Trace Pattern</em> and select <em>Messages
+ sent</em> or <em>Messages received</em>, and a match
+ specification.
+ </p>
+ <p>
A few basic match specifications are provided in the tool, and
you can provide your own match specifications. The syntax of match
specifications is described in the <seealso
diff --git a/lib/observer/doc/src/part.xml b/lib/observer/doc/src/part.xml
index d8ec7664d9..165bd5864a 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>2013</year>
+ <year>2002</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/part_notes.xml b/lib/observer/doc/src/part_notes.xml
index c187702f64..ba15c39cda 100644
--- a/lib/observer/doc/src/part_notes.xml
+++ b/lib/observer/doc/src/part_notes.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2004</year><year>2013</year>
+ <year>2004</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/part_notes_history.xml b/lib/observer/doc/src/part_notes_history.xml
index c0c7b10a7e..e60210924c 100644
--- a/lib/observer/doc/src/part_notes_history.xml
+++ b/lib/observer/doc/src/part_notes_history.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2006</year>
- <year>2013</year>
+ <year>2016</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/ref_man.xml b/lib/observer/doc/src/ref_man.xml
index 37e20b2643..73e7e0053a 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>2013</year>
+ <year>2002</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml
index 2b637551db..7cd15e15d3 100644
--- a/lib/observer/doc/src/ttb.xml
+++ b/lib/observer/doc/src/ttb.xml
@@ -114,7 +114,8 @@ ttb:p(all, call).</input></pre>
<v>Opt = {file,Client} | {handler, FormatHandler} | {process_info,PI} |
shell | {shell, ShellSpec} | {timer, TimerSpec} |
{overload_check, {MSec, Module, Function}} |
- {flush, MSec} | resume | {resume, FetchTimeout}</v>
+ {flush, MSec} | resume | {resume, FetchTimeout} |
+ {queue_size, QueueSize}</v>
<v>TimerSpec = MSec | {MSec, StopOpts}</v>
<v>MSec = FetchTimeout = integer()</v>
<v>Module = Function = atom() </v>
@@ -126,6 +127,7 @@ ttb:p(all, call).</input></pre>
<v>FormatHandler = See format/2</v>
<v>PI = true | false </v>
<v>ShellSpec = true | false | only</v>
+ <v>QueueSize = non_neg_integer()</v>
</type>
<desc>
<p>Starts a file trace port on all specified nodes
@@ -147,6 +149,18 @@ ttb:p(all, call).</input></pre>
<c>Client</c> must be <c>{local, File}</c>. All
trace information is then sent to the trace control node where
it is written to file.</p></item>
+ <tag><c>queue_size</c></tag>
+ <item><p>When tracing to shell or <c>{local,File}</c>, an ip
+ trace driver is used internally. The ip trace driver has a
+ queue of maximum <c>QueueSize</c> messages waiting to be
+ delivered. If the driver cannot deliver messages as fast as
+ they are produced, the queue size might be exceeded and
+ messages are dropped. This parameter is optional, and is
+ only useful if many <c>{drop,N}</c> trace messages are
+ received by the trace handler. It has no meaning if shell
+ or <c>{local,File}</c> is not used. See
+ <seealso marker="runtime_tools:dbg#trace_port/2">dbg:trace_port/2</seealso>
+ for more information about the ip trace driver.</p></item>
<tag><c>process_info</c></tag>
<item><p>Indicates if process
information is to be collected. If <c>PI = true</c> (which is
@@ -229,27 +243,33 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>p(Procs,Flags) -> Return</name>
- <fsummary>Set the specified trace flags on the specified processes.</fsummary>
+ <name>p(Item,Flags) -> Return</name>
+ <fsummary>Set the specified trace flags on the specified processes or ports.</fsummary>
<type>
- <v>Return = {ok,[{Procs,MatchDesc}]}</v>
- <v>Procs = Process | [Process] | all | new | existing</v>
- <v>Process = pid() | atom() | {global,atom()}</v>
+ <v>Return = {ok,[{Item,MatchDesc}]}</v>
+ <v>Items = Item | [Item]</v>
+ <v>Item = pid() | port() | RegName | {global,GlobalRegName} |
+ all | processes | ports |
+ existing | existing_processes | existing_ports |
+ new | new_processes | new_ports</v>
+ <v>RegName = atom()</v>
+ <v>GlobalRegName = term()</v>
<v>Flags = Flag | [Flag]</v>
</type>
<desc>
- <p>Sets the specified trace flags on the specified
- processes. Flag <c>timestamp</c> is always turned on.
+ <p>Sets the specified trace flags on the specified processes
+ or ports. Flag <c>timestamp</c> is always turned on.
</p>
<p>See the Reference Manual for module
<seealso marker="runtime_tools:dbg"><c>dbg</c></seealso>
- and the possible trace flags. Parameter
+ for the possible trace flags. Parameter
<c>MatchDesc</c> is the same as returned from
<c>dbg:p/2</c>.</p>
<p>Processes can be specified as registered names, globally
- registered names, or process identifiers. If a registered name
- is specified, the flags are set on processes with this name on all
- active nodes.</p>
+ registered names, or process identifiers. Ports can be
+ specified as registered names or port identifiers. If a
+ registered name is specified, the flags are set on
+ processes/ports with this name on all active nodes.</p>
<p>Issuing this command starts the timer for this trace if option
<c>timer</c> is specified with <c>tracer/2</c>.
</p>
@@ -257,17 +277,23 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tp, tpl, ctp, ctpl, ctpg</name>
+ <name>tp, tpl, tpe, ctp, ctpl, ctpg, ctpe</name>
<fsummary>Set and clear trace patterns.</fsummary>
<desc>
- <p>These functions are to be used with
- trace flag <c>call</c> for setting and clearing trace
- patterns. When trace flag <c>call</c> is set on a process,
+ <p>These functions are to be used with trace
+ flag <c>call</c>, <c>send</c>, and <c>'receive'</c> for
+ setting and clearing trace patterns.</p>
+ <p>When trace flag <c>call</c> is set on a process,
function calls are traced on that process if a trace
- pattern is set for the called function. Trace patterns
- specify how to trace a function by using match
- specifications. Match specifications are described in the
- <seealso marker="erts:users_guide"><c>ERTS User's Guide</c></seealso>.
+ pattern is set for the called function.</p>
+ <p>The <c>send</c> and <c>'receive'</c> flags enable tracing
+ of all messages sent and received by the process/port. Trace
+ patterns set with <c>tpe</c> may limit traced messages based
+ on the message content, the sender, and/or the receiver.</p>
+ <p>Trace patterns specify how to trace a function or a message
+ by using match specifications. Match specifications are
+ described in the
+ <seealso marker="erts:match_spec"><c>ERTS User's Guide</c></seealso>.
</p>
<p>These functions are equivalent to the corresponding
functions in module
@@ -284,6 +310,8 @@ ttb:p(all, call).</input></pre>
<item><p>Sets trace patterns on global function calls.</p></item>
<tag><c>tpl</c></tag>
<item><p>Sets trace patterns on local and global function calls.</p></item>
+ <tag><c>tpe</c></tag>
+ <item><p>Sets trace patterns on messages.</p></item>
<tag><c>ctp</c></tag>
<item><p>Clears trace patterns on local and global function
calls.</p></item>
@@ -291,13 +319,15 @@ ttb:p(all, call).</input></pre>
<item><p>Clears trace patterns on local function calls.</p></item>
<tag><c>ctpg</c></tag>
<item><p>Clears trace patterns on global function calls.</p></item>
+ <tag><c>ctpe</c></tag>
+ <item><p>Clears trace patterns on messages.</p></item>
</taglist>
<p>With <c>tp</c> and <c>tpl</c>, one of the match specification shortcuts
can be used (for example, <c>ttb:tp(foo_module, caller)</c>).</p>
<p>The shortcuts are as follows:</p>
<list type="bulleted">
<item><c>return</c> - for <c>[{'_',[],[{return_trace}]}]</c>
- (report the return value)</item>
+ (report the return value from a traced function)</item>
<item><c>caller</c> - for <c>[{'_',[],[{message,{caller}}]}]</c>
(report the calling function)</item>
<item><c>{codestr, Str}</c> - for <c>dbg:fun2ms/1</c> arguments
@@ -469,7 +499,7 @@ ttb:p(all, call).</input></pre>
<p>For a description of the <c>match_spec()</c> syntax,
see section
<seealso marker="erts:match_spec"><c>Match Specifications in Erlang</c></seealso>
- in <c>ERTS</c>, which explains the general match specification "language".
+ in ERTS, which explains the general match specification "language".
</p>
<note>
<p>The <em>system tracer</em> for sequential tracing is
diff --git a/lib/observer/include/etop.hrl b/lib/observer/include/etop.hrl
index 0dac322a2b..002937e522 100644
--- a/lib/observer/include/etop.hrl
+++ b/lib/observer/include/etop.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/priv/bin/cdv b/lib/observer/priv/bin/cdv
index 1c44785ac2..d14fd47e41 100755
--- a/lib/observer/priv/bin/cdv
+++ b/lib/observer/priv/bin/cdv
@@ -1,4 +1,4 @@
#!/bin/sh
-erl -sname cdv -noinput -s crashdump_viewer script_start $@
+erl -noinput -s crashdump_viewer script_start $@
diff --git a/lib/observer/priv/bin/cdv.bat b/lib/observer/priv/bin/cdv.bat
index efa8bf8687..18136a30d6 100644
--- a/lib/observer/priv/bin/cdv.bat
+++ b/lib/observer/priv/bin/cdv.bat
@@ -1,2 +1,2 @@
@ECHO OFF
-CALL werl -sname cdv -s crashdump_viewer script_start %*
+CALL werl -s crashdump_viewer script_start %*
diff --git a/lib/observer/priv/crashdump_viewer.tool b/lib/observer/priv/crashdump_viewer.tool
deleted file mode 100644
index b6bd6bbdef..0000000000
--- a/lib/observer/priv/crashdump_viewer.tool
+++ /dev/null
@@ -1,2 +0,0 @@
-{version,"1.2"}.
-[{config_func,{crashdump_viewer,configData,[]}}].
diff --git a/lib/observer/priv/crashdump_viewer/collapsd.gif b/lib/observer/priv/crashdump_viewer/collapsd.gif
deleted file mode 100644
index 0b90c08a9a..0000000000
--- a/lib/observer/priv/crashdump_viewer/collapsd.gif
+++ /dev/null
Binary files differ
diff --git a/lib/observer/priv/crashdump_viewer/exploded.gif b/lib/observer/priv/crashdump_viewer/exploded.gif
deleted file mode 100644
index e3ab5ca2e9..0000000000
--- a/lib/observer/priv/crashdump_viewer/exploded.gif
+++ /dev/null
Binary files differ
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile
index 2d42510b47..ff2bcbdb99 100644
--- a/lib/observer/src/Makefile
+++ b/lib/observer/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2002-2013. All Rights Reserved.
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -67,6 +67,7 @@ MODULES= \
observer_html_lib \
observer_lib \
observer_perf_wx \
+ observer_port_wx \
observer_pro_wx \
observer_procinfo \
observer_sys_wx \
@@ -91,7 +92,7 @@ EXAMPLE_FILES= multitrace.erl
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
PRIVDIR= ../priv
-WEBTOOLFILES= $(PRIVDIR)/crashdump_viewer.tool $(PRIVDIR)/erlang_observer.png
+PNGFILES= $(PRIVDIR)/erlang_observer.png
BINDIR= $(PRIVDIR)/bin
ifeq ($(findstring win32,$(TARGET)),win32)
WIN32_EXECUTABLES= $(BINDIR)/etop.bat $(BINDIR)/cdv.bat
@@ -102,10 +103,6 @@ EXECUTABLES= \
$(BINDIR)/etop \
$(BINDIR)/cdv \
$(WIN32_EXECUTABLES)
-CDVDIR= $(PRIVDIR)/crashdump_viewer
-GIF_FILES= \
- $(CDVDIR)/collapsd.gif \
- $(CDVDIR)/exploded.gif
APP_FILE= observer.app
@@ -162,9 +159,7 @@ release_spec: opt
$(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)/ebin"
$(INSTALL_DIR) "$(RELSYSDIR)/priv/bin"
$(INSTALL_SCRIPT) $(EXECUTABLES) "$(RELSYSDIR)/priv/bin"
- $(INSTALL_DIR) "$(RELSYSDIR)/priv/crashdump_viewer"
- $(INSTALL_DATA) $(WEBTOOLFILES) "$(RELSYSDIR)/priv"
- $(INSTALL_DATA) $(GIF_FILES) "$(RELSYSDIR)/priv/crashdump_viewer"
+ $(INSTALL_DATA) $(PNGFILES) "$(RELSYSDIR)/priv"
release_docs_spec:
diff --git a/lib/observer/src/cdv_atom_cb.erl b/lib/observer/src/cdv_atom_cb.erl
index 0f0c397479..a123354c8f 100644
--- a/lib/observer/src/cdv_atom_cb.erl
+++ b/lib/observer/src/cdv_atom_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl
index 7e17ef135e..5472d36a6f 100644
--- a/lib/observer/src/cdv_bin_cb.erl
+++ b/lib/observer/src/cdv_bin_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ binary_to_term_fun(Bin) ->
try binary_to_term(Bin) of
Term -> plain_html(io_lib:format("~p",[Term]))
catch error:badarg ->
- Warning = "This binary can not be coverted to an Erlang term",
+ Warning = "This binary can not be converted to an Erlang term",
observer_html_lib:warning(Warning)
end
end.
diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl
index d53b721141..27057fd27f 100644
--- a/lib/observer/src/cdv_detail_wx.erl
+++ b/lib/observer/src/cdv_detail_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ init([Id, Data, ParentFrame, Callback, Parent]) ->
end,
{stop,normal};
{info,Info} ->
- observer_lib:display_info_dialog(Info),
+ observer_lib:display_info_dialog(ParentFrame,Info),
{stop,normal}
end.
diff --git a/lib/observer/src/cdv_dist_cb.erl b/lib/observer/src/cdv_dist_cb.erl
index 9c53ec86bc..2b4c9f56d1 100644
--- a/lib/observer/src/cdv_dist_cb.erl
+++ b/lib/observer/src/cdv_dist_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_ets_cb.erl b/lib/observer/src/cdv_ets_cb.erl
index bac8b56fc3..ddd2d42df6 100644
--- a/lib/observer/src/cdv_ets_cb.erl
+++ b/lib/observer/src/cdv_ets_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,10 +34,8 @@
-define(COL_NAME, ?COL_ID+1).
-define(COL_SLOT, ?COL_NAME+1).
-define(COL_OWNER, ?COL_SLOT+1).
--define(COL_BUCK, ?COL_OWNER+1).
--define(COL_OBJ, ?COL_BUCK+1).
+-define(COL_OBJ, ?COL_OWNER+1).
-define(COL_MEM, ?COL_OBJ+1).
--define(COL_TYPE, ?COL_MEM+1).
%% Callbacks for cdv_virtual_list_wx
col_to_elem(id) -> col_to_elem(?COL_ID);
@@ -45,8 +43,6 @@ col_to_elem(?COL_ID) -> #ets_table.id;
col_to_elem(?COL_NAME) -> #ets_table.name;
col_to_elem(?COL_SLOT) -> #ets_table.slot;
col_to_elem(?COL_OWNER) -> #ets_table.pid;
-col_to_elem(?COL_TYPE) -> #ets_table.data_type;
-col_to_elem(?COL_BUCK) -> #ets_table.buckets;
col_to_elem(?COL_OBJ) -> #ets_table.size;
col_to_elem(?COL_MEM) -> #ets_table.memory.
@@ -57,7 +53,6 @@ col_spec() ->
{"Owner", ?wxLIST_FORMAT_CENTRE, 120},
{"Objects", ?wxLIST_FORMAT_RIGHT, 80},
{"Memory", ?wxLIST_FORMAT_RIGHT, 80}
-% {"Type", ?wxLIST_FORMAT_LEFT, 50}
].
get_info(Owner) ->
diff --git a/lib/observer/src/cdv_fun_cb.erl b/lib/observer/src/cdv_fun_cb.erl
index 3a62eb3305..acebc94811 100644
--- a/lib/observer/src/cdv_fun_cb.erl
+++ b/lib/observer/src/cdv_fun_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_gen_cb.erl b/lib/observer/src/cdv_gen_cb.erl
index 69ecfbe80e..5fb43b5719 100644
--- a/lib/observer/src/cdv_gen_cb.erl
+++ b/lib/observer/src/cdv_gen_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl
index 70f0d02982..0ab0ba4315 100644
--- a/lib/observer/src/cdv_html_wx.erl
+++ b/lib/observer/src/cdv_html_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_info_wx.erl b/lib/observer/src/cdv_info_wx.erl
index 13c80942ac..01fe6b15f2 100644
--- a/lib/observer/src/cdv_info_wx.erl
+++ b/lib/observer/src/cdv_info_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_int_tab_cb.erl b/lib/observer/src/cdv_int_tab_cb.erl
index c48fbb6ee1..401ebf9664 100644
--- a/lib/observer/src/cdv_int_tab_cb.erl
+++ b/lib/observer/src/cdv_int_tab_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_mem_cb.erl b/lib/observer/src/cdv_mem_cb.erl
index bfe21bf309..abeddc7335 100644
--- a/lib/observer/src/cdv_mem_cb.erl
+++ b/lib/observer/src/cdv_mem_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -77,6 +77,10 @@ fix_alloc([{Title,Columns,Data}|Tables]) ->
fix_alloc(Tables)];
fix_alloc([{Title,[{_,V}|_]=Data}|Tables]) ->
fix_alloc([{Title,lists:duplicate(length(V),[]),Data}|Tables]);
+fix_alloc([{"",[]}|Tables]) -> % no name and no data, probably truncated dump
+ fix_alloc(Tables);
+fix_alloc([{Title,[]=Data}|Tables]) -> % no data, probably truncated dump
+ fix_alloc([{Title,[],Data}|Tables]);
fix_alloc([]) ->
[].
diff --git a/lib/observer/src/cdv_mod_cb.erl b/lib/observer/src/cdv_mod_cb.erl
index 2e41699c5a..2183e1aa3d 100644
--- a/lib/observer/src/cdv_mod_cb.erl
+++ b/lib/observer/src/cdv_mod_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_multi_wx.erl b/lib/observer/src/cdv_multi_wx.erl
index 13b980b5ea..b511503752 100644
--- a/lib/observer/src/cdv_multi_wx.erl
+++ b/lib/observer/src/cdv_multi_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_port_cb.erl b/lib/observer/src/cdv_port_cb.erl
index 7dbe6d7819..b5cbe8132d 100644
--- a/lib/observer/src/cdv_port_cb.erl
+++ b/lib/observer/src/cdv_port_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl
index 90f6715a06..592150146b 100644
--- a/lib/observer/src/cdv_proc_cb.erl
+++ b/lib/observer/src/cdv_proc_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_sched_cb.erl b/lib/observer/src/cdv_sched_cb.erl
index f236e6a159..192aaf31a7 100644
--- a/lib/observer/src/cdv_sched_cb.erl
+++ b/lib/observer/src/cdv_sched_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_table_wx.erl b/lib/observer/src/cdv_table_wx.erl
index 4e9c158ce3..df16230b70 100644
--- a/lib/observer/src/cdv_table_wx.erl
+++ b/lib/observer/src/cdv_table_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl
index 155cde2cd0..f0d90dde7c 100644
--- a/lib/observer/src/cdv_term_cb.erl
+++ b/lib/observer/src/cdv_term_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_timer_cb.erl b/lib/observer/src/cdv_timer_cb.erl
index 42285b0fc9..dcc794242c 100644
--- a/lib/observer/src/cdv_timer_cb.erl
+++ b/lib/observer/src/cdv_timer_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl
index 5b2775d61b..ebf58865e9 100644
--- a/lib/observer/src/cdv_virtual_list_wx.erl
+++ b/lib/observer/src/cdv_virtual_list_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl
index 82247cb93b..2587a6e64e 100644
--- a/lib/observer/src/cdv_wx.erl
+++ b/lib/observer/src/cdv_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index b66b4d59c9..13e73f027d 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -90,6 +90,7 @@
%% All possible tags - use macros in order to avoid misspelling in the code
+-define(abort,abort).
-define(allocated_areas,allocated_areas).
-define(allocator,allocator).
-define(atoms,atoms).
@@ -321,8 +322,16 @@ handle_call(general_info,_From,State=#state{file=File}) ->
NumAtoms = GenInfo#general_info.num_atoms,
WS = parse_vsn_str(GenInfo#general_info.system_vsn,4),
TW = case get(truncated) of
- true -> ["WARNING: The crash dump is truncated. "
- "Some information might be missing."];
+ true ->
+ case get(truncated_reason) of
+ undefined ->
+ ["WARNING: The crash dump is truncated. "
+ "Some information might be missing."];
+ Reason ->
+ ["WARNING: The crash dump is truncated "
+ "("++Reason++"). "
+ "Some information might be missing."]
+ end;
false -> []
end,
ets:insert(cdv_reg_proc_table,
@@ -515,8 +524,15 @@ truncated_warning([Tag|Tags]) ->
false -> truncated_warning(Tags)
end.
truncated_warning() ->
- ["WARNING: The crash dump is truncated here. "
- "Some information might be missing."].
+ case get(truncated_reason) of
+ undefined ->
+ ["WARNING: The crash dump is truncated here. "
+ "Some information might be missing."];
+ Reason ->
+ ["WARNING: The crash dump is truncated here "
+ "("++Reason++"). "
+ "Some information might be missing."]
+ end.
truncated_here(Tag) ->
case get(truncated) of
@@ -692,6 +708,7 @@ val(Fd, NoExist) ->
{eof,[]} -> NoExist;
[] -> NoExist;
{eof,Val} -> Val;
+ "=abort:"++_ -> NoExist;
Val -> Val
end.
@@ -787,7 +804,7 @@ do_read_file(File) ->
?erl_crash_dump ->
reset_index_table(),
insert_index(Tag,Id,N1+1),
- put(last_tag,{Tag,""}),
+ put_last_tag(Tag,""),
indexify(Fd,Rest,N1),
end_progress(),
check_if_truncated(),
@@ -831,7 +848,7 @@ indexify(Fd,Bin,N) ->
<<_:Pos/binary,TagAndRest/binary>> = Bin,
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos),
insert_index(Tag,Id,N1+1), % +1 to get past newline
- put(last_tag,{Tag,Id}),
+ put_last_tag(Tag,Id),
indexify(Fd,Rest,N1);
nomatch ->
case progress_read(Fd) of
@@ -911,7 +928,10 @@ general_info(File) ->
WholeLine -> WholeLine
end,
- GI = get_general_info(Fd,#general_info{created=Created}),
+ {Slogan,SysVsn} = get_slogan_and_sysvsn(Fd,[]),
+ GI = get_general_info(Fd,#general_info{created=Created,
+ slogan=Slogan,
+ system_vsn=SysVsn}),
{MemTot,MemMax} =
case lookup_index(?memory) of
@@ -965,12 +985,20 @@ general_info(File) ->
mem_max=MemMax,
instr_info=InstrInfo}.
+get_slogan_and_sysvsn(Fd,Acc) ->
+ case val(Fd,eof) of
+ "Slogan: " ++ SloganPart when Acc==[] ->
+ get_slogan_and_sysvsn(Fd,[SloganPart]);
+ "System version: " ++ SystemVsn ->
+ {lists:append(lists:reverse(Acc)),SystemVsn};
+ eof ->
+ {lists:append(lists:reverse(Acc)),"-1"};
+ SloganPart ->
+ get_slogan_and_sysvsn(Fd,[[$\n|SloganPart]|Acc])
+ end.
+
get_general_info(Fd,GenInfo) ->
case line_head(Fd) of
- "Slogan" ->
- get_general_info(Fd,GenInfo#general_info{slogan=val(Fd)});
- "System version" ->
- get_general_info(Fd,GenInfo#general_info{system_vsn=val(Fd)});
"Compiled" ->
get_general_info(Fd,GenInfo#general_info{compile_time=val(Fd)});
"Taints" ->
@@ -1174,7 +1202,11 @@ parse_link_list("{from,"++Str,Links,Monitors,MonitoredBy) ->
parse_link_list(", "++Rest,Links,Monitors,MonitoredBy) ->
parse_link_list(Rest,Links,Monitors,MonitoredBy);
parse_link_list([],Links,Monitors,MonitoredBy) ->
- {lists:reverse(Links),lists:reverse(Monitors),lists:reverse(MonitoredBy)}.
+ {lists:reverse(Links),lists:reverse(Monitors),lists:reverse(MonitoredBy)};
+parse_link_list(Unexpected,Links,Monitors,MonitoredBy) ->
+ io:format("WARNING: found unexpected data in link list:~n~s~n",[Unexpected]),
+ parse_link_list([],Links,Monitors,MonitoredBy).
+
parse_port(Str) ->
{Port,Rest} = parse_link(Str,[]),
@@ -1491,6 +1523,9 @@ get_portinfo(Fd,Port) ->
"Port controls linked-in driver" ->
Str = lists:flatten(["Linked in driver: " | val(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
+ "Port controls forker process" ->
+ Str = lists:flatten(["Forker process: " | val(Fd)]),
+ get_portinfo(Fd,Port#port{controls=Str});
"Port controls external process" ->
Str = lists:flatten(["External proc: " | val(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
@@ -1810,16 +1845,16 @@ main_modinfo(_Fd,LM,_LineHead) ->
all_modinfo(Fd,LM,LineHead) ->
case LineHead of
"Current attributes" ->
- Str = hex_to_str(val(Fd)),
+ Str = hex_to_str(val(Fd,"")),
LM#loaded_mod{current_attrib=Str};
"Current compilation info" ->
- Str = hex_to_str(val(Fd)),
+ Str = hex_to_str(val(Fd,"")),
LM#loaded_mod{current_comp_info=Str};
"Old attributes" ->
- Str = hex_to_str(val(Fd)),
+ Str = hex_to_str(val(Fd,"")),
LM#loaded_mod{old_attrib=Str};
"Old compilation info" ->
- Str = hex_to_str(val(Fd)),
+ Str = hex_to_str(val(Fd,"")),
LM#loaded_mod{old_comp_info=Str};
Other ->
unexpected(Fd,Other,"loaded modules info"),
@@ -1845,7 +1880,12 @@ hex_to_term([],Acc) ->
Bin};
Term ->
Term
- end.
+ end;
+hex_to_term(Rest,Acc) ->
+ {"WARNING: The term is probably truncated!",
+ "I can not convert hex to term.",
+ Rest,list_to_binary(lists:reverse(Acc))}.
+
hex_to_dec("F") -> 15;
hex_to_dec("E") -> 14;
@@ -2156,7 +2196,8 @@ sort_allocator_types([{Name,Data}|Allocators],Acc,DoTotal) ->
Type =
case string:tokens(Name,"[]") of
[T,_Id] -> T;
- [Name] -> Name
+ [Name] -> Name;
+ Other -> Other
end,
TypeData = proplists:get_value(Type,Acc,[]),
{NewTypeData,NewDoTotal} = sort_type_data(Type,Data,TypeData,DoTotal),
@@ -2683,6 +2724,7 @@ count_index(Tag) ->
%%-----------------------------------------------------------------
%% Convert tags read from crashdump to atoms used as first part of key
%% in cdv_dump_index_table
+tag_to_atom("abort") -> ?abort;
tag_to_atom("allocated_areas") -> ?allocated_areas;
tag_to_atom("allocator") -> ?allocator;
tag_to_atom("atoms") -> ?atoms;
@@ -2717,6 +2759,14 @@ tag_to_atom(UnknownTag) ->
list_to_atom(UnknownTag).
%%%-----------------------------------------------------------------
+%%% Store last tag for use when truncated, and reason if aborted
+put_last_tag(?abort,Reason) ->
+ %% Don't overwrite the real last tag
+ put(truncated_reason,Reason);
+put_last_tag(Tag,Id) ->
+ put(last_tag,{Tag,Id}).
+
+%%%-----------------------------------------------------------------
%%% Fetch next chunk from crashdump file
lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) ->
Indices = lookup_index(What),
@@ -2812,7 +2862,16 @@ collect(Pids,Acc) ->
update_progress(),
collect(Pids,Acc);
{'DOWN', _Ref, process, Pid, {pmap_done,Result}} ->
- collect(lists:delete(Pid,Pids),[Result|Acc])
+ collect(lists:delete(Pid,Pids),[Result|Acc]);
+ {'DOWN', _Ref, process, Pid, _Error} ->
+ Warning =
+ "WARNING: an error occured while parsing data.\n" ++
+ case get(truncated) of
+ true -> "This might be because the dump is truncated.\n";
+ false -> ""
+ end,
+ io:format(Warning),
+ collect(lists:delete(Pid,Pids),Acc)
end.
%%%-----------------------------------------------------------------
diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl
index bd08d3e1e1..a08659efd6 100644
--- a/lib/observer/src/crashdump_viewer.hrl
+++ b/lib/observer/src/crashdump_viewer.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/etop.erl b/lib/observer/src/etop.erl
index c97fcc481b..925f4456bb 100644
--- a/lib/observer/src/etop.erl
+++ b/lib/observer/src/etop.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
-export([start/0, start/1, config/2, stop/0, dump/1, help/0]).
%% Internal
-export([update/1]).
--export([loadinfo/1, meminfo/2, getopt/2]).
+-export([loadinfo/2, meminfo/2, getopt/2]).
-include("etop.hrl").
-include("etop_defs.hrl").
@@ -319,18 +319,18 @@ output(graphical) -> exit({deprecated, "Use observer instead"});
output(text) -> etop_txt.
-loadinfo(SysI) ->
+loadinfo(SysI,Prev) ->
#etop_info{n_procs = Procs,
run_queue = RQ,
now = Now,
wall_clock = WC,
runtime = RT} = SysI,
- Cpu = calculate_cpu_utilization(WC,RT),
+ Cpu = calculate_cpu_utilization(WC,RT,Prev#etop_info.runtime),
Clock = io_lib:format("~2.2.0w:~2.2.0w:~2.2.0w",
tuple_to_list(element(2,calendar:now_to_datetime(Now)))),
{Cpu,Procs,RQ,Clock}.
-calculate_cpu_utilization({_,WC},{_,RT}) ->
+calculate_cpu_utilization({_,WC},{_,RT},_) ->
%% Old version of observer_backend, using statistics(wall_clock)
%% and statistics(runtime)
case {WC,RT} of
@@ -341,15 +341,23 @@ calculate_cpu_utilization({_,WC},{_,RT}) ->
_ ->
round(100*RT/WC)
end;
-calculate_cpu_utilization(_,undefined) ->
+calculate_cpu_utilization(_,undefined,_) ->
%% First time collecting - no cpu utilization has been measured
%% since scheduler_wall_time flag is not yet on
0;
-calculate_cpu_utilization(_,RTInfo) ->
+calculate_cpu_utilization(WC,RTInfo,undefined) ->
+ %% Second time collecting - RTInfo shows scheduler_wall_time since
+ %% flag was set to true. Faking previous values by setting
+ %% everything to zero.
+ ZeroRT = [{Id,0,0} || {Id,_,_} <- RTInfo],
+ calculate_cpu_utilization(WC,RTInfo,ZeroRT);
+calculate_cpu_utilization(_,RTInfo,PrevRTInfo) ->
%% New version of observer_backend, using statistics(scheduler_wall_time)
- Sum = lists:foldl(fun({_,A,T},{AAcc,TAcc}) -> {A+AAcc,T+TAcc} end,
+ Sum = lists:foldl(fun({{_, A0, T0}, {_, A1, T1}},{AAcc,TAcc}) ->
+ {(A1 - A0)+AAcc,(T1 - T0)+TAcc}
+ end,
{0,0},
- RTInfo),
+ lists:zip(PrevRTInfo,RTInfo)),
case Sum of
{0,0} ->
0;
diff --git a/lib/observer/src/etop_defs.hrl b/lib/observer/src/etop_defs.hrl
index 39acda5758..7ad8417e2a 100644
--- a/lib/observer/src/etop_defs.hrl
+++ b/lib/observer/src/etop_defs.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/etop_tr.erl b/lib/observer/src/etop_tr.erl
index 38e048c307..8e43f8bb35 100644
--- a/lib/observer/src/etop_tr.erl
+++ b/lib/observer/src/etop_tr.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/etop_txt.erl b/lib/observer/src/etop_txt.erl
index b77fc3c55d..6b8f9df24f 100644
--- a/lib/observer/src/etop_txt.erl
+++ b/lib/observer/src/etop_txt.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,35 +22,35 @@
%%-compile(export_all).
-export([init/1,stop/1]).
--export([do_update/3]).
+-export([do_update/4]).
-include("etop.hrl").
-include("etop_defs.hrl").
--import(etop,[loadinfo/1,meminfo/2]).
+-import(etop,[loadinfo/2,meminfo/2]).
-define(PROCFORM,"~-15w~-20s~8w~8w~8w~8w ~-20s~n").
stop(Pid) -> Pid ! stop.
init(Config) ->
- loop(Config).
+ loop(#etop_info{},Config).
-loop(Config) ->
- Info = do_update(Config),
+loop(Prev,Config) ->
+ Info = do_update(Prev,Config),
receive
stop -> stopped;
- {dump,Fd} -> do_update(Fd,Info,Config), loop(Config);
- {config,_,Config1} -> loop(Config1)
- after Config#opts.intv -> loop(Config)
+ {dump,Fd} -> do_update(Fd,Info,Prev,Config), loop(Info,Config);
+ {config,_,Config1} -> loop(Info,Config1)
+ after Config#opts.intv -> loop(Info,Config)
end.
-do_update(Config) ->
+do_update(Prev,Config) ->
Info = etop:update(Config),
- do_update(standard_io,Info,Config).
+ do_update(standard_io,Info,Prev,Config).
-do_update(Fd,Info,Config) ->
- {Cpu,NProcs,RQ,Clock} = loadinfo(Info),
+do_update(Fd,Info,Prev,Config) ->
+ {Cpu,NProcs,RQ,Clock} = loadinfo(Info,Prev),
io:nl(Fd),
writedoubleline(Fd),
case Info#etop_info.memi of
diff --git a/lib/observer/src/multitrace.erl b/lib/observer/src/multitrace.erl
index dbb6858646..a01eeec6ae 100644
--- a/lib/observer/src/multitrace.erl
+++ b/lib/observer/src/multitrace.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index 61c21a832e..3a5bd172e7 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@
observer_html_lib,
observer_lib,
observer_perf_wx,
+ observer_port_wx,
observer_pro_wx,
observer_procinfo,
observer_sys_wx,
diff --git a/lib/observer/src/observer.appup.src b/lib/observer/src/observer.appup.src
index 9c7ae3a195..da3cd6a4aa 100644
--- a/lib/observer/src/observer.appup.src
+++ b/lib/observer/src/observer.appup.src
@@ -1,7 +1,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/observer.erl b/lib/observer/src/observer.erl
index bb1dedb7e6..79ba7fd614 100644
--- a/lib/observer/src/observer.erl
+++ b/lib/observer/src/observer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl
index 8d5c8a9037..ca54080e15 100644
--- a/lib/observer/src/observer_alloc_wx.erl
+++ b/lib/observer/src/observer_alloc_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -30,55 +30,75 @@
-record(state,
{
- offset = 0.0,
+ time = #ti{},
active = false,
parent,
- windows,
- data = {0, queue:new()},
+ wins,
+ mem,
+ samples,
panel,
paint,
appmon,
async
}).
--define(ALLOC_W, 1).
--define(UTIL_W, 2).
+-define(ID_REFRESH_INTERVAL, 102).
+
+-import(observer_perf_wx,
+ [make_win/4, setup_graph_drawing/1, refresh_panel/4, interval_dialog/2,
+ add_data/5, precalc/4]).
start_link(Notebook, Parent) ->
wx_object:start_link(?MODULE, [Notebook, Parent], []).
init([Notebook, Parent]) ->
try
- Panel = wxPanel:new(Notebook),
+ TopP = wxPanel:new(Notebook),
Main = wxBoxSizer:new(?wxVERTICAL),
- Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN,
- Carrier = wxPanel:new(Panel, [{winid, ?ALLOC_W}, {style,Style}]),
- Utilz = wxPanel:new(Panel, [{winid, ?UTIL_W}, {style,Style}]),
+ Panel = wxPanel:new(TopP),
+ GSzr = wxBoxSizer:new(?wxVERTICAL),
BorderFlags = ?wxLEFT bor ?wxRIGHT,
- wxSizer:add(Main, Carrier, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
- {proportion, 1}, {border, 5}]),
-
- wxSizer:add(Main, Utilz, [{flag, ?wxEXPAND bor BorderFlags},
- {proportion, 1}, {border, 5}]),
-
- MemWin = {MemPanel,_} = create_mem_info(Panel),
- wxSizer:add(Main, MemPanel, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},
- {proportion, 1}, {border, 5}]),
- wxWindow:setSizer(Panel, Main),
-
- PaintInfo = observer_perf_wx:setup_graph_drawing([Carrier, Utilz]),
- {Panel, #state{parent=Parent,
- panel =Panel,
- windows = {Carrier, Utilz, MemWin},
- paint=PaintInfo}
+ Carrier = make_win(alloc, Panel, GSzr, BorderFlags bor ?wxTOP),
+ Utilz = make_win(utilz, Panel, GSzr, BorderFlags),
+ wxWindow:setSizer(Panel, GSzr),
+ wxSizer:add(Main, Panel, [{flag, ?wxEXPAND},{proportion,2}]),
+
+ MemWin = create_mem_info(TopP),
+ wxSizer:add(Main, MemWin, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},
+ {proportion, 1}, {border, 5}]),
+ wxWindow:setSizer(TopP, Main),
+ Windows = [Carrier, Utilz],
+ PaintInfo = setup_graph_drawing(Windows),
+ {TopP, #state{parent= Parent,
+ panel = Panel,
+ wins = Windows,
+ mem = MemWin,
+ paint = PaintInfo,
+ time = setup_time()
+ }
}
catch _:Err ->
io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]),
{stop, Err}
end.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+setup_time() ->
+ Freq = 1,
+ #ti{fetch=Freq, disp=?DISP_FREQ/Freq}.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}},
+ #state{active=Active, panel=Panel, appmon=Old, wins=Wins0, time=#ti{fetch=F0} = Ti0} = State) ->
+ case interval_dialog(Panel, Ti0) of
+ Ti0 -> {noreply, State};
+ #ti{fetch=F0} = Ti -> %% Same fetch interval force refresh
+ Wins = [W#win{max=undefined} || W <- Wins0],
+ {noreply, precalc(State#state{time=Ti, wins=Wins})};
+ Ti when not Active ->
+ {noreply, State#state{time=Ti}};
+ Ti -> %% Changed fetch interval, drop all data
+ {noreply, restart_fetcher(Old, State#state{time=Ti})}
+ end;
handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
State = #state{}) ->
{noreply, State};
@@ -88,13 +108,11 @@ handle_event(Event, _State) ->
%%%%%%%%%%
handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
- #state{active=Active, offset=Offset, paint=Paint,
- windows=Windows, data=Data}) ->
+ #state{active=Active, time=Ti, paint=Paint,
+ wins = Windows}) ->
%% Sigh workaround bug on MacOSX (Id in paint event is always 0)
- Id = if Panel =:= element(?ALLOC_W, Windows) -> alloc;
- Panel =:= element(?UTIL_W, Windows) -> utilz
- end,
- observer_perf_wx:refresh_panel(Panel, Id, Offset, Data, Active, Paint),
+ Win = lists:keyfind(Panel, #win.panel, Windows),
+ refresh_panel(Active, Win, Ti, Paint),
ok.
%%%%%%%%%%
handle_call(Event, From, _State) ->
@@ -107,24 +125,36 @@ handle_cast(Event, _State) ->
handle_info({Key, {promise_reply, {badrpc, _}}}, #state{async=Key} = State) ->
{noreply, State#state{active=false, appmon=undefined}};
-handle_info({Key, {promise_reply, SysInfo}}, #state{async=Key, data=Data} = State) ->
+handle_info({Key, {promise_reply, SysInfo}},
+ #state{async=Key, panel=_Panel, samples=Data, active=Active, wins=Wins0,
+ time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) ->
+ Disp = trunc(Disp0),
+ Next = max(Tick - Disp, 0),
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
Info = alloc_info(SysInfo),
- update_alloc(State, Info),
- {noreply, State#state{offset=0.0, data = add_data(Info, Data), async=undefined}};
+ {Wins, Samples} = add_data(Info, Data, Wins0, Ti, Active),
+ S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, async=undefined},
+ if Active ->
+ update_alloc(S0, Info),
+ State = precalc(S1),
+ {noreply, State};
+ true ->
+ {noreply, S1}
+ end;
-handle_info({refresh, Seq, Freq, Node}, #state{panel=Panel, appmon=Node, async=Key} = State) ->
- wxWindow:refresh(Panel),
+handle_info({refresh, Seq},
+ State = #state{panel=Panel, appmon=Node, time=#ti{tick=Seq, disp=DispF}=Ti})
+ when (Seq+1) < (DispF*1.5) ->
Next = Seq+1,
- if
- Next > Freq, Key =:= undefined ->
- erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq, Node}),
+ State#state.active andalso (catch wxWindow:refresh(Panel)),
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
+ if Seq =:= (trunc(DispF)-1) ->
Req = rpc:async_call(Node, observer_backend, sys_info, []),
- {noreply, State#state{offset=Seq/Freq, async=Req}};
+ {noreply, State#state{time=Ti#ti{tick=Next}, async=Req}};
true ->
- erlang:send_after(trunc(1000 / Freq), self(), {refresh, Next, Freq, Node}),
- {noreply, State#state{offset=Seq/Freq}}
+ {noreply, State#state{time=Ti#ti{tick=Next}}}
end;
-handle_info({refresh, _Seq, _Freq, _Node}, State) ->
+handle_info({refresh, _S}, #state{}=State) ->
{noreply, State};
handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) ->
@@ -132,15 +162,9 @@ handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Ol
try
Node = Old,
wxWindow:refresh(Panel),
- {noreply, State#state{active=true}}
+ {noreply, precalc(State#state{active=true})}
catch _:_ ->
- SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []),
- Info = alloc_info(SysInfo),
- Freq = 6,
- erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq, Node}),
- wxWindow:refresh(Panel),
- {noreply, State#state{active=true, appmon=Node, offset=0.0,
- data = add_data(Info, {0, queue:new()})}}
+ {noreply, restart_fetcher(Node, State)}
end;
handle_info(not_active, State = #state{appmon=_Pid}) ->
@@ -160,12 +184,22 @@ code_change(_, _, State) ->
%%%%%%%%%%
-add_data(Stats, {N, Q}) when N > 60 ->
- {N, queue:drop(queue:in(Stats, Q))};
-add_data(Stats, {N, Q}) ->
- {N+1, queue:in(Stats, Q)}.
+restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) ->
+ SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []),
+ Info = alloc_info(SysInfo),
+ {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true),
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}),
+ wxWindow:refresh(Panel),
+ precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0},
+ wins=Wins, samples=Samples}).
+
+precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) ->
+ Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0],
+ State#state{wins=Wins}.
-update_alloc(#state{windows={_, _, {_, Grid}}}, Fields) ->
+
+update_alloc(#state{mem=Grid}, Fields) ->
+ wxWindow:freeze(Grid),
Max = wxListCtrl:getItemCount(Grid),
Update = fun({Name, BS, CS}, Row) ->
(Row >= Max) andalso wxListCtrl:insertItem(Grid, Row, ""),
@@ -174,7 +208,8 @@ update_alloc(#state{windows={_, _, {_, Grid}}}, Fields) ->
wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)),
Row + 1
end,
- lists:foldl(Update, 0, Fields),
+ wx:foldl(Update, 0, Fields),
+ wxWindow:thaw(Grid),
Fields.
alloc_info(SysInfo) ->
@@ -221,10 +256,9 @@ sum_alloc_one_instance([],BS,CS,TotalBS,TotalCS) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
create_mem_info(Parent) ->
- Panel = wxPanel:new(Parent),
- wxWindow:setBackgroundColour(Panel, {255,255,255}),
Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES,
- Grid = wxListCtrl:new(Panel, [{style, Style}]),
+ Grid = wxListCtrl:new(Parent, [{style, Style}]),
+
Li = wxListItem:new(),
AddListEntry = fun({Name, Align, DefSize}, Col) ->
wxListItem:setText(Li, Name),
@@ -239,19 +273,10 @@ create_mem_info(Parent) ->
lists:foldl(AddListEntry, 0, ListItems),
wxListItem:destroy(Li),
- Sizer = wxBoxSizer:new(?wxVERTICAL),
- wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT},
- {border, 5}, {proportion, 1}]),
- wxWindow:setSizerAndFit(Panel, Sizer),
- {Panel, Grid}.
-
+ Grid.
create_menus(Parent, _) ->
- MenuEntries =
- [{"File",
- [
- ]}
- ],
- observer_wx:create_menus(Parent, MenuEntries).
+ View = {"View", [#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Graph Settings"}]},
+ observer_wx:create_menus(Parent, [{"File", []}, View]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
index a2b7c21993..d41cb8ca42 100644
--- a/lib/observer/src/observer_app_wx.erl
+++ b/lib/observer/src/observer_app_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
-include("observer_defs.hrl").
%% Import drawing wrappers
--import(observer_perf_wx, [haveGC/0,
+-import(observer_perf_wx, [haveGC/0, make_gc/2, destroy_gc/1,
setPen/2, setFont/3, setBrush/2,
strokeLine/5, strokeLines/2, drawRoundedRectangle/6,
drawText/4, getTextExtent/2]).
@@ -191,8 +191,8 @@ handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}},
end;
handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
- State = #state{sel=undefined}) ->
- observer_lib:display_info_dialog("Select process first"),
+ State = #state{panel=Panel,sel=undefined}) ->
+ observer_lib:display_info_dialog(Panel,"Select process first"),
{noreply, State};
handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}},
@@ -205,7 +205,7 @@ handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}},
case observer_lib:user_term(Panel, "Enter message", "") of
cancel -> ok;
{ok, Term} -> Pid ! Term;
- {error, Error} -> observer_lib:display_info_dialog(Error)
+ {error, Error} -> observer_lib:display_info_dialog(Panel,Error)
end,
{noreply, State};
@@ -214,28 +214,28 @@ handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}}
case observer_lib:user_term(Panel, "Enter Exit Reason", "kill") of
cancel -> ok;
{ok, Term} -> exit(Pid, Term);
- {error, Error} -> observer_lib:display_info_dialog(Error)
+ {error, Error} -> observer_lib:display_info_dialog(Panel,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)]),
+ observer_trace_wx:add_processes([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)]),
+ observer_trace_wx:add_processes([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)),
+ observer_trace_wx:add_processes(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)),
+ observer_trace_wx:add_processes(tree_map(Sel, Get)),
{noreply, State};
handle_event(Event, _State) ->
@@ -244,28 +244,18 @@ handle_event(Event, _State) ->
%%%%%%%%%%
handle_sync_event(#wx{event = #wxPaint{}},_,
#state{app_w=DA, app=App, sel=Sel, paint=Paint, usegc=UseGC}) ->
- %% PaintDC must be created in a callback to work on windows.
- IsWindows = element(1, os:type()) =:= win32,
- %% Avoid Windows flickering hack
- DC = if IsWindows -> wx:typeCast(wxBufferedPaintDC:new(DA), wxPaintDC);
- true -> wxPaintDC:new(DA)
- end,
- IsWindows andalso wxDC:clear(DC),
- GC = case UseGC of
- true ->
- GC0 = ?wxGC:create(DC),
- %% Argh must handle scrolling when using ?wxGC
- {Sx,Sy} = wxScrolledWindow:calcScrolledPosition(DA, {0,0}),
- ?wxGC:translate(GC0, Sx,Sy),
- GC0;
- false ->
- wxScrolledWindow:doPrepareDC(DA,DC),
- DC
- end,
+ GC = {GC0, DC} = make_gc(DA, UseGC),
+ case UseGC of
+ false ->
+ wxScrolledWindow:doPrepareDC(DA,DC);
+ true ->
+ %% Argh must handle scrolling when using ?wxGC
+ {Sx,Sy} = wxScrolledWindow:calcScrolledPosition(DA, {0,0}),
+ ?wxGC:translate(GC0, Sx,Sy)
+ end,
%% Nothing is drawn until wxPaintDC is destroyed.
- draw({UseGC, GC}, App, Sel, Paint),
- UseGC andalso ?wxGC:destroy(GC),
- wxPaintDC:destroy(DC),
+ draw(GC, App, Sel, Paint),
+ destroy_gc(GC),
ok.
%%%%%%%%%%
handle_call(Event, From, _State) ->
@@ -312,15 +302,12 @@ handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}},
handle_info({delivery, Pid, app, Curr, AppData},
State = #state{panel=Panel, appmon=Pid, current=Curr, usegc=UseGC,
app_w=AppWin, paint=#paint{font=Font}}) ->
- GC = if UseGC -> ?wxGC:create(AppWin);
- true -> wxWindowDC:new(AppWin)
+ GC = if UseGC -> {?wxGC:create(AppWin), false};
+ true -> {false, wxWindowDC:new(AppWin)}
end,
- FontW = {UseGC, GC},
- setFont(FontW, Font, {0,0,0}),
- App = build_tree(AppData, FontW),
- if UseGC -> ?wxGC:destroy(GC);
- true -> wxWindowDC:destroy(GC)
- end,
+ setFont(GC, Font, {0,0,0}),
+ App = build_tree(AppData, GC),
+ destroy_gc(GC),
setup_scrollbar(AppWin, App),
wxWindow:refresh(Panel),
wxWindow:layout(Panel),
diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl
index 1c2fe520b7..504d0877d9 100644
--- a/lib/observer/src/observer_defs.hrl
+++ b/lib/observer/src/observer_defs.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -49,3 +49,14 @@
-define(LCTRL_WDECR, 4). %% Remove some pixels in column width to avoid creating unnecessary scrollbar
-define(SASH_STYLE, ?wxSP_LIVE_UPDATE bor ?wxSP_NOBORDER bor ?wxSP_3DSASH).
+
+-define(DISP_FREQ, 10). %% per second
+-define(FETCH_DATA, 2). %% per second
+-define(DISP_SECONDS, 60).
+
+-record(ti, {tick=0, disp=?DISP_FREQ/?FETCH_DATA, fetch=?FETCH_DATA, secs=?DISP_SECONDS}).
+
+-record(win, {name, panel, size, geom,
+ graphs=[], no_samples=0,
+ max, state,
+ info=[]}).
diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl
index f646f8ed3e..1f1306c370 100644
--- a/lib/observer/src/observer_html_lib.erl
+++ b/lib/observer/src/observer_html_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl
index 71a2b71a72..7ce4cf45c7 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,12 +20,12 @@
-module(observer_lib).
-export([get_wx_parent/1,
- display_info_dialog/1, display_yes_no_dialog/1,
+ display_info_dialog/2, display_yes_no_dialog/1,
display_progress_dialog/2, destroy_progress_dialog/0,
wait_for_progress/0, report_progress/1,
user_term/3, user_term_multiline/3,
interval_dialog/4, start_timer/1, stop_timer/1,
- display_info/2, fill_info/2, update_info/2, to_str/1,
+ display_info/2, display_info/3, fill_info/2, update_info/2, to_str/1,
create_menus/3, create_menu_item/3,
create_attrs/0,
set_listctrl_col_size/2,
@@ -105,10 +105,10 @@ setup_timer(Bool, {Timer, Old}) ->
timer:cancel(Timer),
setup_timer(Bool, {false, Old}).
-display_info_dialog(Str) ->
- display_info_dialog("",Str).
-display_info_dialog(Title,Str) ->
- Dlg = wxMessageDialog:new(wx:null(), Str, [{caption,Title}]),
+display_info_dialog(Parent,Str) ->
+ display_info_dialog(Parent,"",Str).
+display_info_dialog(Parent,Title,Str) ->
+ Dlg = wxMessageDialog:new(Parent, Str, [{caption,Title}]),
wxMessageDialog:showModal(Dlg),
wxMessageDialog:destroy(Dlg),
ok.
@@ -122,23 +122,26 @@ display_yes_no_dialog(Str) ->
%% display_info(Parent, [{Title, [{Label, Info}]}]) -> {Panel, Sizer, InfoFieldsToUpdate}
display_info(Frame, Info) ->
Panel = wxPanel:new(Frame),
- wxWindow:setBackgroundColour(Panel, {255,255,255}),
+ wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_SYSTEM),
Sizer = wxBoxSizer:new(?wxVERTICAL),
+ InfoFs = display_info(Panel, Sizer, Info),
+ wxWindow:setSizerAndFit(Panel, Sizer),
+ {Panel, Sizer, InfoFs}.
+
+display_info(Panel, Sizer, Info) ->
wxSizer:addSpacer(Sizer, 5),
Add = fun(BoxInfo) ->
case create_box(Panel, BoxInfo) of
{Box, InfoFs} ->
- wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL},
- {border, 5}]),
+ wxSizer:add(Sizer, Box,
+ [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
wxSizer:addSpacer(Sizer, 5),
InfoFs;
undefined ->
[]
end
end,
- InfoFs = [Add(I) || I <- Info],
- wxWindow:setSizerAndFit(Panel, Sizer),
- {Panel, Sizer, InfoFs}.
+ [Add(I) || I <- Info].
fill_info([{dynamic, Key}|Rest], Data)
when is_atom(Key); is_function(Key) ->
@@ -201,22 +204,21 @@ update_info2([Scroll = {_, _, _}|Fs], [{_, NewInfo}|Rest]) ->
update_scroll_boxes(Scroll, NewInfo),
update_info2(Fs, Rest);
update_info2([Field|Fs], [{_Str, {click, Value}}|Rest]) ->
- wxTextCtrl:setValue(Field, to_str(Value)),
+ wxStaticText:setLabel(Field, to_str(Value)),
update_info2(Fs, Rest);
update_info2([Field|Fs], [{_Str, Value}|Rest]) ->
- wxTextCtrl:setValue(Field, to_str(Value)),
+ wxStaticText:setLabel(Field, to_str(Value)),
update_info2(Fs, Rest);
update_info2([Field|Fs], [undefined|Rest]) ->
- wxTextCtrl:setValue(Field, ""),
+ wxStaticText:setLabel(Field, ""),
update_info2(Fs, Rest);
update_info2([], []) -> ok.
update_scroll_boxes({_, _, 0}, {_, []}) -> ok;
update_scroll_boxes({Win, Sizer, _}, {Type, List}) ->
[wxSizerItem:deleteWindows(Child) || Child <- wxSizer:getChildren(Sizer)],
- BC = wxWindow:getBackgroundColour(Win),
Cursor = wxCursor:new(?wxCURSOR_HAND),
- add_entries(Type, List, Win, Sizer, BC, Cursor),
+ add_entries(Type, List, Win, Sizer, Cursor),
wxCursor:destroy(Cursor),
wxSizer:recalcSizes(Sizer),
wxWindow:refresh(Win),
@@ -255,6 +257,11 @@ to_str({func, {F,A}}) when is_atom(F), is_integer(A) ->
lists:concat([F, "/", A]);
to_str({func, {F,'_'}}) when is_atom(F) ->
atom_to_list(F);
+to_str({inet, Addr}) ->
+ case inet:ntoa(Addr) of
+ {error,einval} -> to_str(Addr);
+ AddrStr -> AddrStr
+ end;
to_str({{format,Fun},Value}) when is_function(Fun) ->
Fun(Value);
to_str({A, B}) when is_atom(A), is_atom(B) ->
@@ -379,25 +386,22 @@ add_box(Panel, OuterBox, Cursor, Title, Proportion, {Format, List}) ->
wxScrolledWindow:setScrollbars(Scroll,1,1,0,0),
ScrollSizer = wxBoxSizer:new(?wxVERTICAL),
wxScrolledWindow:setSizer(Scroll, ScrollSizer),
- BC = wxWindow:getBackgroundColour(Panel),
- wxWindow:setBackgroundColour(Scroll,BC),
- add_entries(Format, List, Scroll, ScrollSizer, BC, Cursor),
+ wxWindow:setBackgroundStyle(Scroll, ?wxBG_STYLE_SYSTEM),
+ add_entries(Format, List, Scroll, ScrollSizer, Cursor),
wxSizer:add(Box,Scroll,[{proportion,1},{flag,?wxEXPAND}]),
wxSizer:add(OuterBox,Box,[{proportion,Proportion},{flag,?wxEXPAND}]),
{Scroll,ScrollSizer,length(List)}.
-add_entries(click, List, Scroll, ScrollSizer, BC, Cursor) ->
+add_entries(click, List, Scroll, ScrollSizer, Cursor) ->
Add = fun(Link) ->
TC = link_entry(Scroll, Link, Cursor),
- wxWindow:setBackgroundColour(TC,BC),
- wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}])
+ wxWindow:setBackgroundStyle(TC, ?wxBG_STYLE_SYSTEM),
+ wxSizer:add(ScrollSizer,TC, [{flag,?wxEXPAND}])
end,
[Add(Link) || Link <- List];
-add_entries(plain, List, Scroll, ScrollSizer, _, _) ->
+add_entries(plain, List, Scroll, ScrollSizer, _) ->
Add = fun(String) ->
- TC = wxTextCtrl:new(Scroll, ?wxID_ANY,
- [{style,?SINGLE_LINE_STYLE},
- {value,String}]),
+ TC = wxStaticText:new(Scroll, ?wxID_ANY, String),
wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}])
end,
[Add(String) || String <- List].
@@ -435,51 +439,51 @@ create_box(Panel, {scroll_boxes,Data}) ->
wxSizer:layout(OuterBox),
{OuterBox, Boxes};
-create_box(Panel, Data) ->
- {Title, Align, Info} = get_box_info(Data),
- Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, Title}]),
- LeftSize = get_max_size(Panel,Info),
- LeftProportion = [{proportion,0}],
- RightProportion = [{proportion,1}, {flag, Align bor ?wxEXPAND}],
+create_box(Parent, Data) ->
+ {Title, _Align, Info} = get_box_info(Data),
+ Top = wxStaticBoxSizer:new(?wxVERTICAL, Parent, [{label, Title}]),
+ Panel = wxPanel:new(Parent),
+ Box = wxBoxSizer:new(?wxVERTICAL),
+ LeftSize = 30 + get_max_width(Panel,Info),
+ RightProportion = [{flag, ?wxEXPAND}],
AddRow = fun({Desc0, Value0}) ->
Desc = Desc0++":",
Line = wxBoxSizer:new(?wxHORIZONTAL),
- wxSizer:add(Line,
- wxTextCtrl:new(Panel, ?wxID_ANY,
- [{style,?SINGLE_LINE_STYLE},
- {size,LeftSize},
- {value,Desc}]),
- LeftProportion),
+ Label = wxStaticText:new(Panel, ?wxID_ANY, Desc),
+ wxSizer:add(Line, 5, 0),
+ wxSizer:add(Line, Label),
+ wxSizer:setItemMinSize(Line, Label, LeftSize, -1),
Field =
case Value0 of
{click,"unknown"} ->
- wxTextCtrl:new(Panel, ?wxID_ANY,
- [{style,?SINGLE_LINE_STYLE},
- {value,"unknown"}]);
+ wxStaticText:new(Panel, ?wxID_ANY,"unknown");
{click,Value} ->
link_entry(Panel,Value);
_ ->
Value = to_str(Value0),
- TCtrl = wxTextCtrl:new(Panel, ?wxID_ANY,
- [{style,?SINGLE_LINE_STYLE},
- {value,Value}]),
- length(Value) > 50 andalso
- wxWindow:setToolTip(TCtrl,wxToolTip:new(Value)),
- TCtrl
+ case string:sub_word(lists:sublist(Value, 80),1,$\n) of
+ Value ->
+ %% Short string, no newlines - show all
+ wxStaticText:new(Panel, ?wxID_ANY, Value);
+ Shown ->
+ %% Long or with newlines,
+ %% use tooltip to show all
+ TCtrl = wxStaticText:new(Panel, ?wxID_ANY, [Shown,"..."]),
+ wxWindow:setToolTip(TCtrl,wxToolTip:new(Value)),
+ TCtrl
+ end
end,
wxSizer:add(Line, 10, 0), % space of size 10 horisontally
wxSizer:add(Line, Field, RightProportion),
-
- {_,H,_,_} = wxTextCtrl:getTextExtent(Field,"Wj"),
- wxTextCtrl:setMinSize(Field,{0,H}),
-
- wxSizer:add(Box, Line, [{proportion,0},{flag,?wxEXPAND}]),
+ wxSizer:add(Box, Line, [{proportion,1}]),
Field;
(undefined) ->
undefined
end,
InfoFields = [AddRow(Entry) || Entry <- Info],
- {Box, InfoFields}.
+ wxWindow:setSizer(Panel, Box),
+ wxSizer:add(Top, Panel, [{proportion,1},{flag,?wxEXPAND}]),
+ {Top, InfoFields}.
link_entry(Panel, Link) ->
Cursor = wxCursor:new(?wxCURSOR_HAND),
@@ -490,13 +494,12 @@ link_entry(Panel, Link, Cursor) ->
link_entry2(Panel, to_link(Link), Cursor).
link_entry2(Panel,{Target,Str},Cursor) ->
- TC = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?SINGLE_LINE_STYLE}]),
- wxTextCtrl:setForegroundColour(TC,?wxBLUE),
- wxTextCtrl:appendText(TC, Str),
+ TC = wxStaticText:new(Panel, ?wxID_ANY, Str),
+ wxWindow:setForegroundColour(TC,?wxBLUE),
wxWindow:setCursor(TC, Cursor),
- wxTextCtrl:connect(TC, left_down, [{userData,Target}]),
- wxTextCtrl:connect(TC, enter_window),
- wxTextCtrl:connect(TC, leave_window),
+ wxWindow:connect(TC, left_down, [{userData,Target}]),
+ wxWindow:connect(TC, enter_window),
+ wxWindow:connect(TC, leave_window),
ToolTip = wxToolTip:new("Click to see properties for " ++ Str),
wxWindow:setToolTip(TC, ToolTip),
TC.
@@ -521,23 +524,12 @@ html_window(Panel, Html) ->
wxHtmlWindow:setPage(Win, Html),
Win.
-get_max_size(Panel,Info) ->
- Txt = wxTextCtrl:new(Panel, ?wxID_ANY, []),
- Size = get_max_size(Txt,Info,0,0),
- wxTextCtrl:destroy(Txt),
- Size.
-
-get_max_size(Txt,[{Desc,_}|Info],MaxX,MaxY) ->
- {X,Y,_,_} = wxTextCtrl:getTextExtent(Txt,Desc++":"),
- if X>MaxX ->
- get_max_size(Txt,Info,X,Y);
- true ->
- get_max_size(Txt,Info,MaxX,MaxY)
- end;
-get_max_size(Txt,[undefined|Info],MaxX,MaxY) ->
- get_max_size(Txt,Info,MaxX,MaxY);
-get_max_size(_,[],X,_Y) ->
- {X+2,-1}.
+get_max_width(Parent,Info) ->
+ lists:foldl(fun({Desc,_}, Max) ->
+ {W, _, _, _} = wxWindow:getTextExtent(Parent, Desc),
+ max(W,Max);
+ (_, Max) -> Max
+ end, 0, Info).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_listctrl_col_size(LCtrl, Total) ->
@@ -732,7 +724,7 @@ progress_loop(Title,PD,Caller) ->
if is_list(Reason) -> Reason;
true -> file:format_error(Reason)
end,
- display_info_dialog("Crashdump Viewer Error",FailMsg),
+ display_info_dialog(PD,"Crashdump Viewer Error",FailMsg),
Caller ! error,
unregister(?progress_handler),
unlink(Caller);
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index ace0b62c1d..b0ead42e3f 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,8 +25,9 @@
handle_event/2, handle_sync_event/3, handle_cast/2]).
%% Drawing wrappers for DC and GC areas
--export([setup_graph_drawing/1, refresh_panel/6,
- haveGC/0,
+-export([make_win/4, setup_graph_drawing/1,
+ refresh_panel/4, precalc/4, add_data/5, interval_dialog/2,
+ haveGC/0, make_gc/2, destroy_gc/1,
setPen/2, setFont/3, setBrush/2,
strokeLine/5, strokeLines/2, drawRoundedRectangle/6,
drawText/4, getTextExtent/2]).
@@ -35,13 +36,18 @@
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
+-define(ID_REFRESH_INTERVAL, 102).
+
+-define(BW, 5).
+-define(BH, 5).
+
-record(state,
{
- offset = 0.0,
+ time = #ti{},
active = false,
parent,
- windows,
- data = {0, queue:new()},
+ samples, %% Orig data store
+ wins=[], %% per window content
panel,
paint,
appmon
@@ -51,50 +57,50 @@
-record(paint, {font, small, pen, pen2, pens, usegc = false}).
--define(RQ_W, 1).
--define(MEM_W, 2).
--define(IO_W, 3).
-
start_link(Notebook, Parent) ->
wx_object:start_link(?MODULE, [Notebook, Parent], []).
init([Notebook, Parent]) ->
- try
- Panel = wxPanel:new(Notebook),
- Main = wxBoxSizer:new(?wxVERTICAL),
+ try
+ Panel = wxPanel:new(Notebook),
+ Main = wxBoxSizer:new(?wxVERTICAL),
+ MemIO = wxBoxSizer:new(?wxHORIZONTAL),
+
+ CPU = make_win(runq, Panel, Main, ?wxALL),
+ MEM = make_win(memory, Panel, MemIO, ?wxLEFT),
+ IO = make_win(io, Panel, MemIO, ?wxLEFT bor ?wxRIGHT),
+
+ wxSizer:add(Main, MemIO, [{flag, ?wxEXPAND bor ?wxDOWN},
+ {proportion, 1}, {border, 5}]),
+ wxWindow:setSizer(Panel, Main),
+ Windows = [CPU, MEM, IO],
+ PaintInfo = setup_graph_drawing(Windows),
+
+ process_flag(trap_exit, true),
+ State0 = #state{parent=Parent,
+ panel =Panel,
+ wins = Windows,
+ paint=PaintInfo,
+ samples=reset_data()
+ },
+ {Panel, State0}
+ catch _:Err ->
+ io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]),
+ {stop, Err}
+ end.
+
+make_win(Name, Parent, Sizer, Border) ->
Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN,
- CPU = wxPanel:new(Panel, [{winid, ?RQ_W}, {style,Style}]),
- wxSizer:add(Main, CPU, [{flag, ?wxEXPAND bor ?wxALL},
- {proportion, 1}, {border, 5}]),
- MemIO = wxBoxSizer:new(?wxHORIZONTAL),
- MEM = wxPanel:new(Panel, [{winid, ?MEM_W}, {style,Style}]),
- IO = wxPanel:new(Panel, [{winid, ?IO_W}, {style,Style}]),
- wxSizer:add(MemIO, MEM, [{flag, ?wxEXPAND bor ?wxLEFT},
- {proportion, 1}, {border, 5}]),
- wxSizer:add(MemIO, IO, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT},
- {proportion, 1}, {border, 5}]),
- wxSizer:add(Main, MemIO, [{flag, ?wxEXPAND bor ?wxDOWN},
- {proportion, 1}, {border, 5}]),
- wxWindow:setSizer(Panel, Main),
-
- PaintInfo = setup_graph_drawing([CPU, MEM, IO]),
-
- process_flag(trap_exit, true),
- {Panel, #state{parent=Parent,
- panel =Panel,
- windows = {CPU, MEM, IO},
- paint=PaintInfo
- }}
- catch _:Err ->
- io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]),
- {stop, Err}
- end.
+ Panel = wxPanel:new(Parent, [{style,Style}]),
+ Opts = [{flag, ?wxEXPAND bor Border}, {proportion, 1}, {border, 5}],
+ wxSizer:add(Sizer, Panel, Opts),
+ #win{name=Name, panel=Panel}.
setup_graph_drawing(Panels) ->
IsWindows = element(1, os:type()) =:= win32,
IgnoreCB = {callback, fun(_,_) -> ok end},
- Do = fun(Panel) ->
- wxWindow:setBackgroundColour(Panel, ?wxWHITE),
+ Do = fun(#win{panel=Panel}) ->
+ wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_SYSTEM),
wxPanel:connect(Panel, paint, [callback]),
IsWindows andalso
wxPanel:connect(Panel, erase_background, [IgnoreCB])
@@ -117,8 +123,8 @@ setup_graph_drawing(Panels) ->
SF = wxFont:new(DefSize-2, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
{F, SF}
end,
- BlackPen = wxPen:new({0,0,0}, [{width, 2}]),
- Pens = [wxPen:new(Col, [{width, 3}]) || Col <- tuple_to_list(colors())],
+ BlackPen = wxPen:new({0,0,0}, [{width, 1}]),
+ Pens = [wxPen:new(Col, [{width, 1}]) || Col <- tuple_to_list(colors())],
#paint{usegc = UseGC,
font = Font,
small = SmallFont,
@@ -129,7 +135,18 @@ setup_graph_drawing(Panels) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}},
+ #state{panel=Panel, appmon=Old, wins=Wins0, time=#ti{fetch=F0} = Ti0} = State) ->
+ case interval_dialog(Panel, Ti0) of
+ Ti0 -> {noreply, State};
+ #ti{fetch=F0} = Ti -> %% Same fetch interval force refresh
+ Wins = [W#win{max=undefined} || W <- Wins0],
+ {noreply, precalc(State#state{time=Ti, wins=Wins})};
+ Ti when Old =:= undefined ->
+ {noreply, State#state{time=Ti}};
+ Ti -> %% Changed fetch interval, drop all data
+ {noreply, restart_fetcher(node(Old), State#state{time=Ti})}
+ end;
handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
State = #state{}) ->
{noreply, State};
@@ -139,41 +156,21 @@ handle_event(Event, _State) ->
%%%%%%%%%%
handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
- #state{active=Active, offset=Offset, paint=Paint,
- windows=Windows, data=Data}) ->
+ #state{active=Active, time=Ti, paint=Paint, wins=Windows}) ->
%% Sigh workaround bug on MacOSX (Id in paint event is always 0)
%% Panel = element(Id, Windows),
- Id = if Panel =:= element(?RQ_W, Windows) -> runq;
- Panel =:= element(?MEM_W, Windows) -> memory;
- Panel =:= element(?IO_W, Windows) -> io
- end,
-
- refresh_panel(Panel, Id, Offset, Data, Active, Paint),
+ Win = lists:keyfind(Panel, #win.panel, Windows),
+ refresh_panel(Active, Win, Ti, Paint),
ok.
-refresh_panel(Panel, Id, Offset, Data, Active, #paint{usegc=UseGC} = Paint) ->
+refresh_panel(Active, #win{name=_Id, panel=Panel}=Win, Ti, #paint{usegc=UseGC}=Paint) ->
%% PaintDC must be created in a callback to work on windows.
- IsWindows = element(1, os:type()) =:= win32,
- DC = if IsWindows ->
- %% Ugly hack to aviod flickering on windows, works on windows only
- %% But the other platforms are doublebuffered by default
- wx:typeCast(wxBufferedPaintDC:new(Panel), wxPaintDC);
- true ->
- wxPaintDC:new(Panel)
- end,
- IsWindows andalso wxDC:clear(DC),
- GC = if UseGC -> ?wxGC:create(DC);
- true -> DC
- end,
%% Nothing is drawn until wxPaintDC is destroyed.
- try
- draw(Offset, Id, {UseGC, GC}, Panel, Paint, Data, Active)
- catch _:Err ->
- io:format("Internal error ~p ~p~n",[Err, erlang:get_stacktrace()])
+ GC = make_gc(Panel, UseGC),
+ if Active -> draw_win(GC, Win, Ti, Paint);
+ true -> ignore
end,
- UseGC andalso ?wxGC:destroy(GC),
- wxPaintDC:destroy(DC).
-
+ destroy_gc(GC).
%%%%%%%%%%
handle_call(Event, From, _State) ->
@@ -182,41 +179,42 @@ handle_call(Event, From, _State) ->
handle_cast(Event, _State) ->
error({unhandled_cast, Event}).
%%%%%%%%%%
-handle_info(Stats = {stats, 1, _, _, _},
- State = #state{panel=Panel, data=Data, active=Active}) ->
+handle_info({stats, 1, _, _, _} = Stats,
+ #state{panel=Panel, samples=Data, active=Active, wins=Wins0,
+ time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) ->
if Active ->
+ Disp = trunc(Disp0),
+ Next = max(Tick - Disp, 0),
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
+ {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active),
+ State = precalc(State0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples}),
wxWindow:refresh(Panel),
- Freq = 6,
- erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq});
- true -> ignore
- end,
- {noreply, State#state{offset=0.0, data = add_data(Stats, Data)}};
-
-handle_info({refresh, Seq, Freq}, State = #state{panel=Panel, offset=Prev}) ->
- wxWindow:refresh(Panel),
- Next = Seq+1,
- if Seq > 1, Prev =:= 0.0 ->
- %% We didn't have time to handle the refresh
{noreply, State};
- Next < Freq ->
- erlang:send_after(trunc(1000 / Freq), self(), {refresh, Next, Freq}),
- {noreply, State#state{offset=Seq/Freq}};
true ->
- {noreply, State#state{offset=Seq/Freq}}
+ {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active),
+ Wins = [W#win{max=undefined} || W <- Wins1],
+ {noreply, State0#state{samples=Samples, wins=Wins, time=Ti#ti{tick=0}}}
end;
-handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) ->
+handle_info({refresh, Seq}, #state{panel=Panel, time=#ti{tick=Seq, disp=DispF}=Ti} = State0)
+ when (Seq+1) < (DispF*1.5) ->
+ Next = Seq+1,
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
+ State = precalc(State0#state{time=Ti#ti{tick=Next}}),
+ catch wxWindow:refresh(Panel),
+ {noreply, State};
+handle_info({refresh, _}, State) ->
+ {noreply, State};
+
+handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old, time=_Ti} = State) ->
create_menus(Parent, []),
try
Node = node(Old),
wxWindow:refresh(Panel),
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}),
{noreply, State#state{active=true}}
catch _:_ ->
- catch Old ! exit,
- Me = self(),
- Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, 1000]),
- wxWindow:refresh(Panel),
- {noreply, State#state{active=true, appmon=Pid, data={0, queue:new()}}}
+ {noreply,restart_fetcher(Node, State)}
end;
handle_info(not_active, State = #state{appmon=_Pid}) ->
@@ -237,173 +235,347 @@ terminate(_Event, #state{appmon=Pid}) ->
code_change(_, _, State) ->
State.
-add_data(Stats, {N, Q}) when N > 60 ->
- {N, queue:drop(queue:in(Stats, Q))};
-add_data(Stats, {N, Q}) ->
- {N+1, queue:in(Stats, Q)}.
+restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti, wins=Wins0}=State) ->
+ catch Old ! exit,
+ Me = self(),
+ Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, round(1000/Freq)]),
+ wxWindow:refresh(Panel),
+ Wins = [W#win{state=undefined} || W <- Wins0],
+ precalc(State#state{active=true, appmon=Pid, samples=reset_data(),
+ wins=Wins, time=Ti#ti{tick=0}}).
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+reset_data() ->
+ {0, queue:new()}.
-create_menus(Parent, _) ->
- MenuEntries =
- [{"File",
- [
- ]}
- ],
- observer_wx:create_menus(Parent, MenuEntries).
+add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active) when N > (Secs*Fetch+1) ->
+ {{value, Drop}, Q} = queue:out(Q0),
+ add_data_1(Wins, Stats, N, {Drop,Q}, Active);
+add_data(Stats, {N, Q}, Wins, _, Active) ->
+ add_data_1(Wins, Stats, N+1, {empty, Q}, Active).
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-collect_data(runq, {N, Q}) ->
- case queue:to_list(Q) of
- [] -> {0, 0, [], []};
- [_] -> {0, 0, [], []};
- [{stats, _Ver, Init0, _IO, _Mem}|Data0] ->
- Init = lists:sort(Init0),
- [_|Data=[First|_]] = lists:foldl(fun({stats, _, T0, _, _}, [Prev|Acc]) ->
- TN = lists:sort(T0),
- Delta = calc_delta(TN, Prev),
- [TN, list_to_tuple(Delta)|Acc]
- end, [Init], Data0),
- NoGraphs = tuple_size(First),
- {N, lmax(Data), lists:reverse([First|Data]), lists:seq(1, NoGraphs)}
- end;
-collect_data(memory, {N, Q}) ->
- MemT = mem_types(),
- Data = [list_to_tuple([Value || {Type,Value} <- MemInfo,
- lists:member(Type, MemT)])
- || {stats, _Ver, _RQ, _IO, MemInfo} <- queue:to_list(Q)],
- {N, lmax(Data), Data, MemT};
-collect_data(io, {N, Q}) ->
- case queue:to_list(Q) of
- [] -> {0, 0, [], []};
- [_] -> {0, 0, [], []};
- [{stats, _Ver, _RQ, {{_,In0}, {_,Out0}}, _Mem}|Data0] ->
- [_,_|Data=[First|_]] =
- lists:foldl(fun({stats, _, _, {{_,In}, {_,Out}}, _}, [PIn,Pout|Acc]) ->
- [In,Out,{In-PIn,Out-Pout}|Acc]
- end, [In0,Out0], Data0),
- {N, lmax(Data), lists:reverse([First|Data]), [input, output]}
+add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active)
+ when St /= undefined ->
+ try
+ {Wins, Stat} =
+ lists:mapfoldl(fun(Win0, Entry) ->
+ {Win1,Stat} = add_data_2(Win0, Last, Entry),
+ case Active of
+ true ->
+ Win = add_data_3(Win1, N, Drop, Stat, Q),
+ {Win, Stat};
+ false ->
+ {Win1, Stat}
+ end
+ end, #{}, Wins0),
+ {Wins, {N,queue:in(Stat#{}, Q)}}
+ catch no_scheduler_change ->
+ {[Win#win{state=init_data(Id, Last),
+ info = info(Id, Last)}
+ || #win{name=Id}=Win <- Wins0], {0,queue:new()}}
end;
-collect_data(alloc, {N, Q}) ->
- List = queue:to_list(Q),
- Data = [list_to_tuple([Carrier || {_Type,_Block,Carrier} <- MemInfo])
- || MemInfo <- List],
- Info = case List of %% Varies depending on erlang build config/platform
- [MInfo|_] -> [Type || {Type, _, _} <- MInfo];
- _ -> []
- end,
- {N, lmax(Data), Data, Info};
-
-collect_data(utilz, {N, Q}) ->
- List = queue:to_list(Q),
- Data = [list_to_tuple([round(100*Block/Carrier) || {_Type,Block,Carrier} <- MemInfo])
- || MemInfo <- List],
- Info = case List of %% Varies depending on erlang build config/platform
- [MInfo|_] -> [Type || {Type, _, _} <- MInfo];
- _ -> []
+
+add_data_1(Wins, Stats, 1, {_, Q}, _) ->
+ {[Win#win{state=init_data(Id, Stats),
+ info = info(Id, Stats)}
+ || #win{name=Id}=Win <- Wins], {0,Q}}.
+
+add_data_2(#win{name=Id, state=S0}=Win, Stats, Map) ->
+ {V1, S1} = collect_data(Id, Stats, S0),
+ {Win#win{state=S1}, Map#{Id=>V1}}.
+
+add_data_3(#win{name=Id, max={{OldMax, OldEntry},_,_,_},
+ geom=#{scale:={WS,HS}}, state={Max,_},
+ graphs=Graphs}=Win,
+ N, Drop0, Last, Q1)
+ when N > 3 ->
+ Drop = case Drop0 of
+ #{Id:=D} -> D;
+ _ -> Drop0
end,
- {N, lmax(Data), Data, Info}.
+ case {max_value(Max), Drop =:= OldEntry} of
+ {OldMax, false} ->
+ #{Id:=V4} = Last,
+ {{value, #{Id:=V3}},Q2} = queue:out_r(Q1),
+ {{value, #{Id:=V2}},Q3} = queue:out_r(Q2),
+ {{value, #{Id:=V1}},_} = queue:out_r(Q3),
+ Vals = [V1,V2,V3,V4],
+ Gs = tuple_size(V1),
+ Info = lists:zip(lists:seq(Gs, 1, -1), Graphs),
+ Lines = [add_lines(Vals, Drop, Prev, I, WS, HS) || {I, Prev} <- Info],
+ Win#win{graphs=Lines, no_samples=N};
+ _W -> %% Max changed Trigger complete recalc
+ Win#win{max=undefined}
+ end;
+add_data_3(Win, _, _, _,_) ->
+ %% Trigger complete recalc
+ Win#win{max=undefined}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+create_menus(Parent, _) ->
+ View = {"View", [#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Graph Settings"}]},
+ observer_wx:create_menus(Parent, [{"File", []}, View]).
+
+interval_dialog(Parent0, #ti{fetch=Fetch0, secs=Secs0}=Ti) ->
+ Parent = observer_lib:get_wx_parent(Parent0),
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, "Load Chart Settings",
+ [{style, ?wxDEFAULT_DIALOG_STYLE bor
+ ?wxRESIZE_BORDER}]),
+ {Sl1,FetchSl} = slider(Dialog, "Sample (ms)", trunc(1000 / Fetch0), 100, 10000),
+ {Sl2, SecsSl} = slider(Dialog, "Length (min)", Secs0 div 60, 1, 10),
+ TopSizer = wxBoxSizer:new(?wxVERTICAL),
+ Flags = [{flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT},
+ {border, 5}, {proportion, 1}],
+ wxSizer:add(TopSizer, Sl1, Flags),
+ wxSizer:add(TopSizer, Sl2, Flags),
+ wxSizer:add(TopSizer, wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), Flags),
+ wxWindow:setSizerAndFit(Dialog, TopSizer),
+ wxSizer:setSizeHints(TopSizer, Dialog),
+ Res = case wxDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Fetch = 1000 / wxSlider:getValue(FetchSl),
+ Secs = wxSlider:getValue(SecsSl) * 60,
+ Ti#ti{fetch=Fetch, secs=Secs, disp=?DISP_FREQ/Fetch};
+ ?wxID_CANCEL ->
+ Ti
+ end,
+ wxDialog:destroy(Dialog),
+ Res.
+
+slider(Parent, Str, Value, Min, Max) ->
+ Sz = wxBoxSizer:new(?wxHORIZONTAL),
+ Center = [{flag, ?wxALIGN_CENTER_VERTICAL}],
+ wxSizer:add(Sz, wxStaticText:new(Parent, ?wxID_ANY, Str), [{proportion, 1}|Center]),
+ Opt = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}],
+ Slider = wxSlider:new(Parent, ?wxID_ANY, Value, Min, Max, Opt),
+ wxSizer:add(Sz, Slider, [{proportion, 2}|Center]),
+ case Min > 1 of
+ false ->
+ {Sz, Slider};
+ true ->
+ CB = fun(#wx{event=Ev},_) -> step(Ev, Slider, Min) end,
+ wxSlider:connect(Slider, scroll_thumbtrack, [{callback, CB}]),
+ wxSlider:connect(Slider, scroll_changed, [{callback, CB}]),
+ {Sz, Slider}
+ end.
+step(_Ev = #wxScroll{commandInt=Value}, Slider, Min) ->
+ Val = Min * round(Value / Min),
+ wxSlider:setValue(Slider, Val),
+ ok.
-mem_types() ->
- [total, processes, atom, binary, code, ets].
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-lmax([]) -> 0;
-lmax(List) ->
- Max = [lists:max(tuple_to_list(T)) || T <- List,
- tuple_size(T) > 0],
- case Max of
- [] -> 0;
- _ -> lists:max(Max)
+mk_max() -> {0, undefined}.
+max_value({Max,_}) -> Max.
+%% max_data({_,Data}) -> Data. matched in function head
+
+lmax(MState, Tuple, Tuple) when is_tuple(Tuple) ->
+ lmax(MState, tuple_to_list(Tuple), Tuple);
+lmax(MState, Values, State) ->
+ Max = max_value(MState),
+ New = lists:max([Max|Values]),
+ case New >= Max of
+ false -> MState;
+ true -> {New, State}
end.
+init_data(runq, {stats, _, T0, _, _}) -> {mk_max(),lists:sort(T0)};
+init_data(io, {stats, _, _, {{_,In0}, {_,Out0}}, _}) -> {mk_max(), {In0,Out0}};
+init_data(memory, _) -> {mk_max(), info(memory, undefined)};
+init_data(alloc, _) -> {mk_max(), unused};
+init_data(utilz, _) -> {mk_max(), unused}.
+
+info(runq, {stats, _, T0, _, _}) -> lists:seq(1, length(T0));
+info(memory, _) -> [total, processes, atom, binary, code, ets];
+info(io, _) -> [input, output];
+info(alloc, First) -> [Type || {Type, _, _} <- First];
+info(utilz, First) -> [Type || {Type, _, _} <- First];
+info(_, []) -> [].
+
+collect_data(runq, {stats, _, T0, _, _}, {Max,S0}) ->
+ S1 = lists:sort(T0),
+ Delta = calc_delta(S1, S0),
+ Sample = list_to_tuple(Delta),
+ {Sample, {lmax(Max,Delta,Sample), S1}};
+collect_data(io, {stats, _, _, {{_,In0}, {_,Out0}}, _}, {Max,{PIn,POut}}) ->
+ In = In0-PIn,
+ Out = Out0-POut,
+ Sample = {In, Out},
+ {Sample, {lmax(Max, [In,Out], Sample), {In0, Out0}}};
+collect_data(memory, {stats, _, _, _, MemInfo}, {Max, MemTypes}) ->
+ Vs = [Value || {Type,Value} <- MemInfo, lists:member(Type, MemTypes)],
+ Sample = list_to_tuple(Vs),
+ {Sample, {lmax(Max, Vs, Sample),MemTypes}};
+collect_data(alloc, MemInfo, Max) ->
+ Vs = [Carrier || {_Type,_Block,Carrier} <- MemInfo],
+ Sample = list_to_tuple(Vs),
+ {Sample, {lmax(Max, Vs, Sample),unused}};
+collect_data(utilz, MemInfo, Max) ->
+ Vs = [round(100*Block/Carrier) || {_Type,Block,Carrier} <- MemInfo],
+ Sample = list_to_tuple(Vs),
+ {Sample, {lmax(Max,Vs,Sample),unused}}.
+
calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) ->
[100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)];
-calc_delta([], []) -> [].
+calc_delta([], []) -> [];
+calc_delta(_, _) -> throw(no_scheduler_change).
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active) ->
- %% This can be optimized a lot by collecting data once
- %% and draw to memory and then blit memory and only draw new entries in new memory
- %% area. Hmm now rewritten to use ?wxGC I don't now if it is feasable.
- {Len, Max0, Hs, Info} = collect_data(Id, Data),
- {Max,_,_} = MaxDisp = calc_max(Id, Max0),
+precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) ->
+ Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0],
+ State#state{wins=Wins}.
+
+precalc(Ti, {NoSamples,Q}, Paint, #win{name=Id, panel=Panel}=Win) ->
Size = wxWindow:getClientSize(Panel),
- {X0,Y0,WS,HS, DrawBs} = draw_borders(Id, Info, DC, Size, MaxDisp, Paint),
- Last = 60*WS+X0-1,
- Start = max(61-Len, 0)*WS+X0 - Offset*WS,
- Samples = length(Hs),
- NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end,
- case Active andalso Samples > 1 andalso NoGraphs > 0 of
- true ->
- Draw = fun(N) ->
- Lines = make_lines(Hs, Start, N, {X0,Max*HS,Last}, Y0, WS, HS),
- setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)),
- strokeLines(DC, Lines),
- N+1
- end,
- [Draw(I) || I <- lists:seq(NoGraphs, 1, -1)],
- DrawBs();
- false ->
- DrawBs(),
- Text = case Active andalso Samples =< 1 of
+ case Win of
+ #win{max=Max, no_samples=NoSamples, size=Size} when is_tuple(Max) ->
+ Win;
+ _SomeThingChanged ->
+ Hs = [Vals || #{Id:=Vals} <- queue:to_list(Q)],
+ Max = lists:foldl(fun(Vals,Max) -> lmax(Max, Vals, Vals) end,
+ mk_max(), Hs),
+ MaxDisp = calc_max(Id, Max),
+ #{scale:={WS,HS}} = Props = window_geom(Size, MaxDisp, Ti, Panel, Paint),
+ NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end,
+ Graphs = [make_lines(Hs, I, WS, HS) || I <- lists:seq(NoGraphs, 1, -1)],
+ State = case Win#win.state of
+ undefined -> {Max, undefined};
+ {_, St} -> {Max, St}
+ end,
+ Win#win{geom=Props, size=Size,
+ max=MaxDisp,
+ graphs=Graphs,
+ no_samples=NoSamples,
+ state=State}
+ end.
+
+window_geom({W,H}, {_, Max, _Unit, MaxUnit},
+ #ti{secs=Secs, fetch=FetchFreq},
+ Panel, #paint{font=Font}) ->
+ Str1 = observer_lib:to_str(MaxUnit),
+ Str2 = observer_lib:to_str(MaxUnit div 2),
+ Str3 = observer_lib:to_str(0),
+ {TW,TH,_,_} = wxWindow:getTextExtent(Panel, Str1, [{theFont, Font}]),
+ {SpaceW, _,_,_} = wxWindow:getTextExtent(Panel, "W", [{theFont, Font}]),
+ X0 = ?BW+TW+?BW,
+ X1 = W-?BW*4,
+ MaxTextY = TH+?BH,
+ BottomTextY = H-?BH-TH,
+ Y0 = MaxTextY + (TH / 2),
+ Y1 = BottomTextY - TH - ?BH,
+
+ ScaleW = (X1-X0-1)/(Secs*FetchFreq),
+ ScaleH = (Y1-Y0-1) / Max,
+ #{p0=>{X0,Y0}, p1=>{X1,Y1}, scale=>{ScaleW, ScaleH},
+ txsz=>{TW,TH,SpaceW}, txt=>{BottomTextY, MaxTextY}, strs=>{Str1,Str2,Str3}}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+draw_win(DC, #win{no_samples=Samples, geom=#{scale:={WS,HS}}, graphs=Graphs, max={_,Max,_,_}}=Win,
+ #ti{tick=Tick, fetch=FetchFreq, secs=Secs, disp=DispFreq}=Ti,
+ Paint=#paint{pens=Pens}) when Samples >= 2, Graphs =/= [] ->
+ %% Draw graphs
+ {X0,Y0,DrawBs} = draw_borders(DC, Ti, Win, Paint),
+ Offset = Tick / DispFreq,
+ Full = case Samples > (1+Secs*FetchFreq) of
+ true -> 1;
+ false -> 2
+ end,
+ Start = X0 + (max(Secs*FetchFreq+Full-Samples, 0) - Offset)*WS,
+ Last = Secs*FetchFreq*WS+X0,
+ Draw = fun(Lines0, N) ->
+ setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)),
+ Order = lists:reverse(Lines0),
+ [{_,Y}|Lines] = translate(Order, {Start, Y0}, 0, WS, {X0,Max*HS,Last}, []),
+ strokeLines(DC, [{Last,Y}|Lines]),
+ N-1
+ end,
+ lists:foldl(Draw, length(Graphs), Graphs),
+ DrawBs(),
+ ok;
+
+draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{small=Small}=Paint) ->
+ %% Draw Error Msg
+ try draw_borders(DC, Ti, Win, Paint) of
+ {X0,_Y0,DrawBs} ->
+ Text = case Samples =< 1 of
true -> "Waiting for data";
false -> "Information not available"
end,
setFont(DC, Small, {0,0,0}),
- drawText(DC, Text, X0 + 100, element(2,Size) div 2)
- end,
- ok.
+ {_,WW} = getSize(DC),
+ drawText(DC, Text, X0 + 100, WW div 2),
+ DrawBs(),
+ ok
+ catch _:_ -> %% Early redraws fail
+ ok
+ end.
-make_lines(Ds = [Data|_], PX, N, Clip, ZeroY, WS, HS) ->
+translate([{X0,Y}|Rest], {Sx,Sy}=Start, N, WS, {Cx,Cy,Cw}=Clip, Acc) ->
+ X = min((N-X0)*WS+Sx,Cw),
+ Next = if X0 > 0 -> N; true -> N+1 end,
+ case X =< Cx of
+ true ->
+ translate(Rest, Start, Next, WS, Clip, [{Cx,Sy-min(Cy,Y)}]);
+ false ->
+ translate(Rest, Start, Next, WS, Clip, [{X,Sy-min(Cy,Y)}|Acc])
+ end;
+translate([], _, _, _, _, Acc) ->
+ Acc.
+
+add_lines(Vals, Drop, OldLines, I, WS, HS) ->
+ Lines = strip(OldLines, Drop, 2),
+ New = make_lines(Vals, I, WS, HS),
+ New ++ Lines.
+
+strip([{X,_}|Rest], Drop, N) when X > 0.0001, N > 0 ->
+ strip(Rest, Drop, N);
+strip([_|Rest], Drop, N) when N > 0 ->
+ strip(Rest, Drop, N-1);
+strip(List, empty, _) -> List;
+strip(List, _, _) ->
+ lists:reverse(strip(lists:reverse(List), empty, 1)).
+
+make_lines(Ds = [Data|_], N, WS, HS) ->
Y = element(N,Data),
- make_lines(Ds, PX, N, Clip, ZeroY, WS, HS, Y, []).
+ make_lines(Ds, N, WS, HS, Y, []).
-make_lines([D1 | Ds = [D2|Rest]], PX, N, Clip={Cx,Cy, _}, ZeroY, WS, HS, Y0, Acc0) ->
+make_lines([D1 | Ds = [D2|Rest]], N, WS, HS, Y0, Acc0) ->
Y1 = element(N,D1),
Y2 = element(N,D2),
Y3 = case Rest of
[D3|_] -> element(N,D3);
[] -> Y2
end,
- This = {max(Cx, PX),ZeroY-min(Cy,Y1*HS)},
+ This = {0, Y1*HS},
Acc = if (abs(Y1-Y2) * HS) < 3.0 -> [This|Acc0];
WS < 3.0 -> [This|Acc0];
- PX < Cx ->
- make_splines(Y0,Y1,Y2,Y3,PX,Clip,ZeroY,WS,HS,Acc0);
- true ->
- make_splines(Y0,Y1,Y2,Y3,PX,Clip,ZeroY,WS,HS,[This|Acc0])
+ true -> make_splines(Y0,Y1,Y2,Y3,WS,HS,[This|Acc0])
end,
- make_lines(Ds, PX+WS, N, Clip, ZeroY, WS, HS, Y1, Acc);
-make_lines([D1], _PX, N, {_,Cy,Last}, ZeroY, _WS, HS, _Y0, Acc) ->
- Y1 = element(N,D1),
- [{Last,ZeroY-min(Cy, Y1*HS)}|Acc].
+ make_lines(Ds, N, WS, HS, Y1, Acc);
+make_lines([_D1], _N, _WS, _HS, _Y0, Acc) ->
+ Acc.
-make_splines(Y00,Y10,Y20,Y30,PX,Clip,ZeroY,WS,HS,Acc) ->
+make_splines(Y00,Y10,Y20,Y30,WS,HS,Acc) ->
Y1 = Y10*HS,
Y2 = Y20*HS,
- Steps = min(abs(Y1-Y2), WS),
+ Steps = min(abs(Y1-Y2), WS/2),
if Steps > 2 ->
Y0 = Y00*HS,
Y3 = Y30*HS,
Tan = spline_tan(Y0,Y1,Y2,Y3),
Delta = 1/Steps,
- splines(Steps-1, 0.0, Delta, Tan, Y1,Y2, PX, Clip,ZeroY, Delta*WS, Acc);
+ splines(Steps-1, 0.0, Delta, Tan, Y1,Y2, Acc);
true ->
Acc
end.
-splines(N, XD, XD0, Tan, Y1,Y2, PX0, Clip={Cx,Cy,_},ZeroY, WS, Acc) when N > 0 ->
- PX = PX0+WS,
+splines(N, XD, XD0, Tan, Y1,Y2, Acc) when N > 0 ->
Delta = XD+XD0,
- if PX < Cx ->
- splines(N-1, Delta, XD0, Tan, Y1, Y2, PX, Clip,ZeroY, WS, Acc);
- true ->
- Y = min(Cy, max(0,spline(Delta, Tan, Y1,Y2))),
- splines(N-1, Delta, XD0, Tan, Y1, Y2, PX, Clip,ZeroY, WS,
- [{PX, ZeroY-Y}|Acc])
- end;
-splines(_N, _XD, _XD0, _Tan, _Y1,_Y2, _PX, _Clip,_ZeroY, _WS, Acc) -> Acc.
+ Y = max(0, spline(Delta, Tan, Y1,Y2)),
+ splines(N-1, Delta, XD0, Tan, Y1, Y2, [{1.0-Delta, Y}|Acc]);
+splines(_N, _XD, _XD0, _Tan, _Y1,_Y2, Acc) ->
+ Acc.
spline(T, {M1, M2}, Y1, Y2) ->
%% Hermite Basis Funcs
@@ -423,34 +595,19 @@ spline_tan(Y0, Y1, Y2, Y3) ->
M2 = S*C*(Y3-Y1),
{M1,M2}.
--define(BW, 5).
--define(BH, 5).
-
-draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit},
+draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq},
+ #win{name=Type, geom=Geom, info=Info, max={_,_,Unit,_}},
#paint{pen=Pen, pen2=Pen2, font=Font, small=Small}) ->
- Str1 = observer_lib:to_str(MaxUnit),
- Str2 = observer_lib:to_str(MaxUnit div 2),
- Str3 = observer_lib:to_str(0),
-
- setFont(DC, Font, {0,0,0}),
- {TW,TH} = getTextExtent(DC, Str1),
- {SpaceW, _} = getTextExtent(DC, "W"),
+ #{p0:={GraphX0, GraphY0}, p1:={GraphX1,GraphY1}, scale:={ScaleW0,_},
+ txsz:={TW,TH,SpaceW}, txt:={BottomTextY, MaxTextY}, strs:={Str1,Str2,Str3}} = Geom,
- GraphX0 = ?BW+TW+?BW,
- GraphX1 = W-?BW*4,
+ ScaleW = ScaleW0*FetchFreq,
TopTextX = ?BW*3+TW,
- MaxTextY = TH+?BH,
- BottomTextY = H-?BH-TH,
SecondsY = BottomTextY - TH,
- GraphY0 = MaxTextY + (TH / 2),
- GraphY1 = SecondsY - ?BH,
- GraphW = GraphX1-GraphX0-1,
- GraphH = GraphY1-GraphY0-1,
+
GraphY25 = GraphY0 + (GraphY1 - GraphY0) / 4,
GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2,
GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4,
- ScaleW = GraphW / 60,
- ScaleH = GraphH / Max,
setFont(DC, Small, {0,0,0}),
Align = fun(Str, Y) ->
@@ -462,14 +619,21 @@ draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit},
Align(Str3, GraphY1 - (TH / 2) + 1),
setPen(DC, Pen),
- DrawSecs = fun(Secs, Pos) ->
- Str = [observer_lib:to_str(Secs)|" s"],
+ DrawSecs = fun(Sec, {Pos, Prev}) ->
+ Str = observer_lib:to_str(Sec) ++ "s",
X = GraphX0+Pos,
- drawText(DC, Str, X-SpaceW, SecondsY),
strokeLine(DC, X, GraphY0, X, GraphY1+5),
- Pos + 10*ScaleW
+ TxtX = X-SpaceW,
+ case TxtX > Prev of
+ true ->
+ drawText(DC, Str, TxtX, SecondsY),
+ TxtW = SpaceW*length(Str),
+ {Pos + 10*ScaleW, TxtX+TxtW};
+ false ->
+ {Pos + 10*ScaleW, Prev}
+ end
end,
- lists:foldl(DrawSecs, 0, lists:seq(60,0, -10)),
+ lists:foldl(DrawSecs, {0, 0}, lists:seq(Secs,0, -10)),
strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25),
strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50),
@@ -526,7 +690,7 @@ draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit},
{GraphX1, GraphY1+1}, {GraphX1, GraphY0-1},
{GraphX0, GraphY0-1}])
end,
- {GraphX0+1, GraphY1, ScaleW, ScaleH, DrawBorder}.
+ {GraphX0+1, GraphY1, DrawBorder}.
to_string(Atom) ->
Name = atom_to_list(Atom),
@@ -543,43 +707,44 @@ uppercase([C|Rest]) ->
calc_max(Type, Max) ->
bytes(Type, Max).
-calc_max1(Max) when Max < 10 ->
- 10;
-calc_max1(Max) ->
- case Max div 10 of
- X when X < 10 ->
- case Max rem 10 of
- 0 -> Max;
- _ ->
- (X+1)*10
- end;
- X ->
- 10*calc_max1(X)
- end.
-
-bytes(runq, Val) ->
- Upper = calc_max1(Val),
- {Upper, "", Upper};
-bytes(utilz, Val) ->
- Upper = calc_max1(Val),
- {Upper, "", Upper};
-bytes(_, B) ->
+bytes(runq, Max) ->
+ Upper = calc_max1(max_value(Max)),
+ {Max, Upper, "", Upper};
+bytes(utilz, Max) ->
+ Upper = calc_max1(max_value(Max)),
+ {Max, Upper, "", Upper};
+bytes(_, Max) ->
+ B = max_value(Max),
KB = B div 1024,
MB = KB div 1024,
GB = MB div 1024,
if
GB > 10 ->
Upper = calc_max1(GB),
- {Upper*1024*1024*1024, "(GB)", Upper};
+ {Max, Upper*1024*1024*1024, "(GB)", Upper};
MB > 10 ->
Upper = calc_max1(MB),
- {Upper*1024*1024, "(MB)", Upper};
+ {Max, Upper*1024*1024, "(MB)", Upper};
KB > 0 ->
Upper = calc_max1(KB),
- {Upper*1024, "(KB)", Upper};
+ {Max, Upper*1024, "(KB)", Upper};
true ->
Upper = calc_max1(B),
- {Upper, "(B)", Upper}
+ {Max, Upper, "(B)", Upper}
+ end.
+
+calc_max1(Max) when Max < 10 ->
+ 10;
+calc_max1(Max) ->
+ case Max div 10 of
+ X when X < 10 ->
+ case Max rem 10 of
+ 0 -> Max;
+ _ ->
+ (X+1)*10
+ end;
+ X ->
+ 10*calc_max1(X)
end.
colors() ->
@@ -592,6 +757,28 @@ colors() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% wxDC and ?wxGC wrappers
+make_gc(Panel,UseGC) ->
+ DC = case os:type() of
+ {win32, _} ->
+ %% Ugly hack to avoid flickering on windows, works on windows only
+ %% But the other platforms are doublebuffered by default
+ DC0 = wx:typeCast(wxBufferedPaintDC:new(Panel), wxPaintDC),
+ wxDC:clear(DC0),
+ DC0;
+ _ ->
+ wxPaintDC:new(Panel)
+ end,
+ if UseGC -> {?wxGC:create(DC), DC};
+ true -> {false, DC}
+ end.
+
+destroy_gc({GC, DC}) ->
+ (GC =/= false) andalso ?wxGC:destroy(GC),
+ case DC =/= false andalso wx:getObjectType(DC) of
+ false -> ok;
+ Type -> Type:destroy(DC)
+ end.
+
haveGC() ->
try
wxGraphicsRenderer:getDefaultRenderer(),
@@ -599,44 +786,48 @@ haveGC() ->
catch _:_ -> false
end.
+getSize({_, DC}) ->
+ wxDC:getSize(DC).
+
setPen({false, DC}, Pen) ->
wxDC:setPen(DC, Pen);
-setPen({true, GC}, Pen) ->
+setPen({GC, _}, Pen) ->
?wxGC:setPen(GC, Pen).
setFont({false, DC}, Font, Color) ->
wxDC:setTextForeground(DC, Color),
wxDC:setFont(DC, Font);
-setFont({true, GC}, Font, Color) ->
+setFont({GC, _}, Font, Color) ->
?wxGC:setFont(GC, Font, Color).
setBrush({false, DC}, Brush) ->
wxDC:setBrush(DC, Brush);
-setBrush({true, GC}, Brush) ->
+setBrush({GC, _}, Brush) ->
?wxGC:setBrush(GC, Brush).
strokeLine({false, DC}, X0, Y0, X1, Y1) ->
wxDC:drawLine(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)});
-strokeLine({true, GC}, X0, Y0, X1, Y1) ->
+strokeLine({GC, _}, X0, Y0, X1, Y1) ->
?wxGC:strokeLine(GC, X0, Y0, X1, Y1).
+strokeLines(_, [_]) -> ok;
strokeLines({false, DC}, Lines) ->
wxDC:drawLines(DC, [{round(X), round(Y)} || {X,Y} <- Lines]);
-strokeLines({true, GC}, Lines) ->
+strokeLines({GC, _}, Lines) ->
?wxGC:strokeLines(GC, Lines).
drawRoundedRectangle({false, DC}, X0, Y0, X1, Y1, R) ->
wxDC:drawRoundedRectangle(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}, round(R));
-drawRoundedRectangle({true, GC}, X0, Y0, X1, Y1, R) ->
+drawRoundedRectangle({GC, _}, X0, Y0, X1, Y1, R) ->
?wxGC:drawRoundedRectangle(GC, X0, Y0, X1, Y1, R).
drawText({false, DC}, Str, X, Y) ->
wxDC:drawText(DC, Str, {round(X),round(Y)});
-drawText({true, GC}, Str, X, Y) ->
+drawText({GC, _}, Str, X, Y) ->
?wxGC:drawText(GC, Str, X, Y).
getTextExtent({false, DC}, Str) ->
wxDC:getTextExtent(DC, Str);
-getTextExtent({true, GC}, Str) ->
+getTextExtent({GC, _}, Str) ->
{W,H,_,_} = ?wxGC:getTextExtent(GC, Str),
{W,H}.
diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl
new file mode 100644
index 0000000000..5b9e5c5671
--- /dev/null
+++ b/lib/observer/src/observer_port_wx.erl
@@ -0,0 +1,565 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+-module(observer_port_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").
+
+-define(GRID, 300).
+-define(ID_REFRESH, 301).
+-define(ID_REFRESH_INTERVAL, 302).
+-define(ID_PORT_INFO, 303).
+-define(ID_PORT_INFO_SELECTED, 304).
+-define(ID_TRACE_PORTS, 305).
+-define(ID_TRACE_NAMES, 306).
+-define(ID_TRACE_NEW, 307).
+-define(ID_TRACE_ALL, 308).
+-define(ID_CLOSE_PORT, 309).
+
+-define(TRACE_PORTS_STR, "Trace selected ports").
+-define(TRACE_NAMES_STR, "Trace selected ports, "
+ "if a process have a registered name "
+ "processes with same name will be traced on all nodes").
+
+-record(port,
+ {id,
+ connected,
+ name,
+ controls,
+ slot,
+ id_str,
+ links,
+ monitors,
+ monitored_by,
+ parallelism,
+ locking,
+ queue_size,
+ memory,
+ inet}).
+
+-record(opt, {sort_key=2,
+ sort_incr=true
+ }).
+
+-record(state,
+ {
+ parent,
+ grid,
+ panel,
+ node=node(),
+ opt=#opt{},
+ right_clicked_port,
+ ports,
+ timer,
+ open_wins=[]
+ }).
+
+start_link(Notebook, Parent) ->
+ wx_object:start_link(?MODULE, [Notebook, Parent], []).
+
+init([Notebook, Parent]) ->
+ Panel = wxPanel:new(Notebook),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ Style = ?wxLC_REPORT bor ?wxLC_HRULES,
+ Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]),
+ wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 1}, {border, 5}]),
+ wxWindow:setSizer(Panel, Sizer),
+ Li = wxListItem:new(),
+ AddListEntry = fun({Name, Align, DefSize}, Col) ->
+ wxListItem:setText(Li, Name),
+ wxListItem:setAlign(Li, Align),
+ wxListCtrl:insertColumn(Grid, Col, Li),
+ wxListCtrl:setColumnWidth(Grid, Col, DefSize),
+ Col + 1
+ end,
+ ListItems = [{"Id", ?wxLIST_FORMAT_LEFT, 150},
+ {"Connected", ?wxLIST_FORMAT_LEFT, 150},
+ {"Name", ?wxLIST_FORMAT_LEFT, 150},
+ {"Controls", ?wxLIST_FORMAT_LEFT, 200},
+ {"Slot", ?wxLIST_FORMAT_RIGHT, 50}],
+ lists:foldl(AddListEntry, 0, ListItems),
+ wxListItem:destroy(Li),
+
+ wxListCtrl:connect(Grid, command_list_item_right_click),
+ wxListCtrl:connect(Grid, command_list_item_activated),
+ wxListCtrl:connect(Grid, command_list_col_click),
+ wxListCtrl:connect(Grid, size, [{skip, true}]),
+
+ wxWindow:setFocus(Grid),
+ {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}.
+
+handle_event(#wx{id=?ID_REFRESH},
+ State = #state{node=Node, grid=Grid, opt=Opt}) ->
+ Ports0 = get_ports(Node),
+ Ports = update_grid(Grid, Opt, Ports0),
+ {noreply, State#state{ports=Ports}};
+
+handle_event(#wx{obj=Obj, event=#wxClose{}}, #state{open_wins=Opened} = State) ->
+ NewOpened =
+ case lists:keytake(Obj,2,Opened) of
+ false -> Opened;
+ {value,_,Rest} -> Rest
+ end,
+ {noreply, State#state{open_wins=NewOpened}};
+
+handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
+ State = #state{node=Node, grid=Grid,
+ opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) ->
+ Opt = case Col+2 of
+ Key -> Opt0#opt{sort_incr=not Bool};
+ NewKey -> Opt0#opt{sort_key=NewKey}
+ end,
+ Ports0 = get_ports(Node),
+ Ports = update_grid(Grid, Opt, Ports0),
+ wxWindow:setFocus(Grid),
+ {noreply, State#state{opt=Opt, ports=Ports}};
+
+handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) ->
+ observer_lib:set_listctrl_col_size(Grid, W),
+ {noreply, State};
+
+handle_event(#wx{event=#wxList{type=command_list_item_activated,
+ itemIndex=Index}},
+ State=#state{grid=Grid, ports=Ports, open_wins=Opened}) ->
+ Port = lists:nth(Index+1, Ports),
+ NewOpened = display_port_info(Grid, Port, Opened),
+ {noreply, State#state{open_wins=NewOpened}};
+
+handle_event(#wx{event=#wxList{type=command_list_item_right_click,
+ itemIndex=Index}},
+ State=#state{panel=Panel, ports=Ports}) ->
+ case Index of
+ -1 ->
+ {noreply, State};
+ _ ->
+ Port = lists:nth(Index+1, Ports),
+ Menu = wxMenu:new(),
+ wxMenu:append(Menu, ?ID_PORT_INFO,
+ "Port info for " ++ erlang:port_to_list(Port#port.id)),
+ wxMenu:append(Menu, ?ID_TRACE_PORTS,
+ "Trace selected ports",
+ [{help, ?TRACE_PORTS_STR}]),
+ wxMenu:append(Menu, ?ID_TRACE_NAMES,
+ "Trace selected ports by name (all nodes)",
+ [{help, ?TRACE_NAMES_STR}]),
+ wxMenu:append(Menu, ?ID_CLOSE_PORT,
+ "Close " ++ erlang:port_to_list(Port#port.id)),
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu),
+ {noreply, State#state{right_clicked_port=Port}}
+ end;
+
+handle_event(#wx{id=?ID_PORT_INFO},
+ State = #state{grid=Grid, right_clicked_port=Port,
+ open_wins=Opened}) ->
+ case Port of
+ undefined ->
+ {noreply, State};
+ _ ->
+ NewOpened = display_port_info(Grid, Port, Opened),
+ {noreply, State#state{right_clicked_port=undefined,
+ open_wins=NewOpened}}
+ end;
+
+handle_event(#wx{id=?ID_PORT_INFO_SELECTED},
+ State = #state{grid=Grid, ports=Ports, open_wins=Opened}) ->
+ case get_selected_items(Grid,Ports) of
+ [] ->
+ observer_wx:create_txt_dialog(State#state.panel, "No selected ports",
+ "Port Info", ?wxICON_EXCLAMATION),
+ {noreply, State};
+ Selected ->
+ NewOpened = lists:foldl(fun(P,O) -> display_port_info(Grid, P, O) end,
+ Opened, Selected),
+ {noreply, State#state{open_wins = NewOpened}}
+ end;
+
+handle_event(#wx{id=?ID_CLOSE_PORT}, State = #state{right_clicked_port=Port}) ->
+ case Port of
+ undefined ->
+ {noreply, State};
+ _ ->
+ erlang:port_close(Port#port.id),
+ {noreply, State#state{right_clicked_port=undefined}}
+ end;
+
+handle_event(#wx{id=?ID_TRACE_PORTS}, #state{grid=Grid, ports=Ports}=State) ->
+ case get_selected_items(Grid, Ports) of
+ [] ->
+ observer_wx:create_txt_dialog(State#state.panel, "No selected ports",
+ "Tracer", ?wxICON_EXCLAMATION);
+ Selected ->
+ SelectedIds = [Port#port.id || Port <- Selected],
+ observer_trace_wx:add_ports(SelectedIds)
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?ID_TRACE_NAMES}, #state{grid=Grid, ports=Ports}=State) ->
+ case get_selected_items(Grid, Ports) of
+ [] ->
+ observer_wx:create_txt_dialog(State#state.panel, "No selected ports",
+ "Tracer", ?wxICON_EXCLAMATION);
+ Selected ->
+ IdsOrRegs =
+ [case Port#port.name of
+ [] -> Port#port.id;
+ Name -> Name
+ end || Port <- Selected],
+ observer_trace_wx:add_ports(IdsOrRegs)
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) ->
+ observer_trace_wx:add_ports([new_ports]),
+ {noreply, State};
+
+handle_event(#wx{id=?ID_REFRESH_INTERVAL},
+ State = #state{grid=Grid, timer=Timer0}) ->
+ Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60),
+ {noreply, State#state{timer=Timer}};
+
+handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) ->
+ observer ! {open_link, TargetPid},
+ {noreply, State};
+
+handle_event(#wx{obj=Obj, event=#wxMouse{type=enter_window}}, State) ->
+ wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}),
+ {noreply, State};
+
+handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) ->
+ wxTextCtrl:setForegroundColour(Obj,?wxBLUE),
+ {noreply, State};
+
+handle_event(Event, _State) ->
+ error({unhandled_event, Event}).
+
+handle_sync_event(_Event, _Obj, _State) ->
+ ok.
+
+handle_call(Event, From, _State) ->
+ error({unhandled_call, Event, From}).
+
+handle_cast(Event, _State) ->
+ error({unhandled_cast, Event}).
+
+handle_info({portinfo_open, PortIdStr},
+ State = #state{node=Node, grid=Grid, opt=Opt, open_wins=Opened}) ->
+ Ports0 = get_ports(Node),
+ Ports = update_grid(Grid, Opt, Ports0),
+ Port = lists:keyfind(PortIdStr, #port.id_str, Ports),
+ NewOpened =
+ case Port of
+ false ->
+ self() ! {error,"No such port: " ++ PortIdStr},
+ Opened;
+ _ ->
+ display_port_info(Grid, Port, Opened)
+ end,
+ {noreply, State#state{ports=Ports, open_wins=NewOpened}};
+
+handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt,
+ ports=OldPorts}) ->
+ case get_ports(Node) of
+ OldPorts ->
+ %% no change
+ {noreply, State};
+ Ports0 ->
+ Ports = update_grid(Grid, Opt, Ports0),
+ {noreply, State#state{ports=Ports}}
+ end;
+
+handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt,
+ timer=Timer0}) ->
+ Ports0 = get_ports(Node),
+ Ports = update_grid(Grid, Opt, Ports0),
+ wxWindow:setFocus(Grid),
+ create_menus(Parent),
+ Timer = observer_lib:start_timer(Timer0),
+ {noreply, State#state{node=Node, ports=Ports, timer=Timer}};
+
+handle_info(not_active, State = #state{timer = Timer0}) ->
+ Timer = observer_lib:stop_timer(Timer0),
+ {noreply, State#state{timer=Timer}};
+
+handle_info({error, Error}, #state{panel=Panel} = State) ->
+ Str = io_lib:format("ERROR: ~s~n",[Error]),
+ observer_lib:display_info_dialog(Panel, Str),
+ {noreply, State};
+
+handle_info(_Event, State) ->
+ {noreply, State}.
+
+terminate(_Event, _State) ->
+ ok.
+
+code_change(_, _, State) ->
+ State.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+create_menus(Parent) ->
+ MenuEntries =
+ [{"View",
+ [#create_menu{id = ?ID_PORT_INFO_SELECTED,
+ text = "Port info for selected ports\tCtrl-I"},
+ separator,
+ #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"},
+ #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."}
+ ]},
+ {"Trace",
+ [#create_menu{id=?ID_TRACE_PORTS, text="Trace selected ports"},
+ #create_menu{id=?ID_TRACE_NAMES, text="Trace selected ports by name (all nodes)"},
+ #create_menu{id=?ID_TRACE_NEW, text="Trace new ports"}
+ ]}
+ ],
+ observer_wx:create_menus(Parent, MenuEntries).
+
+get_ports(Node) ->
+ case get_ports2(Node) of
+ Error = {error, _} ->
+ self() ! Error,
+ [];
+ Res ->
+ Res
+ end.
+get_ports2(Node) ->
+ case rpc:call(Node, observer_backend, get_port_list, []) of
+ {badrpc, Error} ->
+ {error, Error};
+ Error = {error, _} ->
+ Error;
+ Result ->
+ [list_to_portrec(Port) || Port <- Result]
+ end.
+
+list_to_portrec(PL) ->
+ %% PortInfo:
+ %% {registered_name, RegisteredName :: atom()} |
+ %% {id, Index :: integer() >= 0} |
+ %% {connected, Pid :: pid()} |
+ %% {links, Pids :: [pid()]} |
+ %% {name, String :: string()} |
+ %% {input, Bytes :: integer() >= 0} |
+ %% {output, Bytes :: integer() >= 0} |
+ %% {os_pid, OsPid :: integer() >= 0 | undefined},
+ PortId = proplists:get_value(port_id, PL),
+ #port{id = PortId,
+ id_str = erlang:port_to_list(PortId),
+ slot = proplists:get_value(id, PL),
+ connected = proplists:get_value(connected, PL),
+ links = proplists:get_value(links, PL, []),
+ name = proplists:get_value(registered_name, PL, []),
+ monitors = proplists:get_value(monitors, PL, []),
+ monitored_by = proplists:get_value(monitored_by, PL, []),
+ controls = proplists:get_value(name, PL),
+ parallelism = proplists:get_value(parallelism, PL),
+ locking = proplists:get_value(locking, PL),
+ queue_size = proplists:get_value(queue_size, PL, 0),
+ memory = proplists:get_value(memory, PL, 0),
+ inet = proplists:get_value(inet, PL, [])}.
+
+portrec_to_list(#port{id = Id,
+ slot = Slot,
+ connected = Connected,
+ links = Links,
+ name = Name,
+ monitors = Monitors,
+ monitored_by = MonitoredBy,
+ controls = Controls,
+ parallelism = Parallelism,
+ locking = Locking,
+ queue_size = QueueSize,
+ memory = Memory,
+ inet = Inet}) ->
+ [{id,Id},
+ {slot,Slot},
+ {connected,Connected},
+ {links,Links},
+ {name,Name},
+ {monitors,Monitors},
+ {monitored_by,MonitoredBy},
+ {controls,Controls},
+ {parallelism,Parallelism},
+ {locking,Locking},
+ {queue_size,QueueSize},
+ {memory,Memory} |
+ Inet].
+
+display_port_info(Parent, PortRec, Opened) ->
+ PortIdStr = PortRec#port.id_str,
+ case lists:keyfind(PortIdStr,1,Opened) of
+ false ->
+ Frame = do_display_port_info(Parent, PortRec),
+ [{PortIdStr,Frame}|Opened];
+ {_,Win} ->
+ wxFrame:raise(Win),
+ Opened
+ end.
+
+do_display_port_info(Parent0, PortRec) ->
+ Parent = observer_lib:get_wx_parent(Parent0),
+ Title = "Port Info: " ++ PortRec#port.id_str,
+ Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title,
+ [{style, ?wxSYSTEM_MENU bor ?wxCAPTION
+ bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER},
+ {size,{600,400}}]),
+ ScrolledWin = wxScrolledWindow:new(Frame,[{style,?wxHSCROLL bor ?wxVSCROLL}]),
+ wxScrolledWindow:enableScrolling(ScrolledWin,true,true),
+ wxScrolledWindow:setScrollbars(ScrolledWin,20,20,0,0),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxWindow:setSizer(ScrolledWin,Sizer),
+ Port = portrec_to_list(PortRec),
+ Fields0 = port_info_fields(Port),
+ _UpFields = observer_lib:display_info(ScrolledWin, Sizer, Fields0),
+ wxFrame:center(Frame),
+ wxFrame:connect(Frame, close_window, [{skip, true}]),
+ wxFrame:show(Frame),
+ Frame.
+
+
+
+port_info_fields(Port0) ->
+ {InetStruct,Port} = inet_extra_fields(Port0),
+ Struct =
+ [{"Overview",
+ [{"Registered Name", name},
+ {"Connected", {click,connected}},
+ {"Slot", slot},
+ {"Controls", controls},
+ {"Parallelism", parallelism},
+ {"Locking", locking},
+ {"Queue Size", {bytes,queue_size}},
+ {"Memory", {bytes,memory}}]},
+ {scroll_boxes,
+ [{"Links",1,{click,links}},
+ {"Monitors",1,{click,filter_monitor_info()}},
+ {"Monitored by",1,{click,monitored_by}}]} | InetStruct],
+ observer_lib:fill_info(Struct, Port).
+
+inet_extra_fields(Port) ->
+ Statistics = proplists:get_value(statistics,Port,[]),
+ Options = proplists:get_value(options,Port,[]),
+ Struct =
+ case proplists:get_value(controls,Port) of
+ Inet when Inet=="tcp_inet"; Inet=="udp_inet"; Inet=="sctp_inet" ->
+ [{"Inet",
+ [{"Local Address", {inet,local_address}},
+ {"Local Port Number", local_port},
+ {"Remote Address", {inet,remote_address}},
+ {"Remote Port Number", remote_port}]},
+ {"Statistics",
+ [stat_name_and_unit(Key) || {Key,_} <- Statistics]},
+ {"Options",
+ [{atom_to_list(Key),Key} || {Key,_} <- Options]}];
+ _ ->
+ []
+ end,
+ Port1 = lists:keydelete(statistics,1,Port),
+ Port2 = lists:keydelete(options,1,Port1),
+ {Struct,Port2 ++ Statistics ++ Options}.
+
+stat_name_and_unit(recv_avg) ->
+ {"Average package size received", {bytes,recv_avg}};
+stat_name_and_unit(recv_cnt) ->
+ {"Number of packets received", recv_cnt};
+stat_name_and_unit(recv_dvi) ->
+ {"Average packet size deviation received", {bytes,recv_dvi}};
+stat_name_and_unit(recv_max) ->
+ {"Largest packet received", {bytes,recv_max}};
+stat_name_and_unit(recv_oct) ->
+ {"Total received", {bytes,recv_oct}};
+stat_name_and_unit(send_avg) ->
+ {"Average packet size sent", {bytes, send_avg}};
+stat_name_and_unit(send_cnt) ->
+ {"Number of packets sent", send_cnt};
+stat_name_and_unit(send_max) ->
+ {"Largest packet sent", {bytes, send_max}};
+stat_name_and_unit(send_oct) ->
+ {"Total sent", {bytes, send_oct}};
+stat_name_and_unit(send_pend) ->
+ {"Data waiting to be sent from driver", {bytes,send_pend}};
+stat_name_and_unit(Key) ->
+ {atom_to_list(Key), Key}.
+
+filter_monitor_info() ->
+ fun(Data) ->
+ Ms = proplists:get_value(monitors, Data),
+ [Pid || {process, Pid} <- Ms]
+ end.
+
+update_grid(Grid, Opt, Ports) ->
+ wx:batch(fun() -> update_grid2(Grid, Opt, Ports) end).
+update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) ->
+ wxListCtrl:deleteAllItems(Grid),
+ Update =
+ fun(#port{id = Id,
+ slot = Slot,
+ connected = Connected,
+ name = Name,
+ controls = Ctrl},
+ Row) ->
+ _Item = wxListCtrl:insertItem(Grid, Row, ""),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN);
+ true -> ignore
+ end,
+
+ lists:foreach(fun({Col, Val}) ->
+ wxListCtrl:setItem(Grid, Row, Col,
+ observer_lib:to_str(Val))
+ end,
+ [{0,Id},{1,Connected},{2,Name},{3,Ctrl},{4,Slot}]),
+ Row + 1
+ end,
+ PortInfo = case Dir of
+ false -> lists:reverse(lists:keysort(Sort, Ports));
+ true -> lists:keysort(Sort, Ports)
+ end,
+ lists:foldl(Update, 0, PortInfo),
+ PortInfo.
+
+
+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_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl
index dd3441e482..f07b9e295a 100644
--- a/lib/observer/src/observer_pro_wx.erl
+++ b/lib/observer/src/observer_pro_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -83,6 +83,7 @@
timer,
procinfo_menu_pids=[],
sel={[], []},
+ right_clicked_pid,
holder}).
start_link(Notebook, Parent) ->
@@ -303,13 +304,14 @@ 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{sel={[_|Ids], [ToKill|Pids]}}=State) ->
- exit(ToKill, kill),
- {noreply, State#state{sel={Ids,Pids}}};
+handle_event(#wx{id=?ID_KILL}, #state{right_clicked_pid=Pid, sel=Sel0}=State) ->
+ exit(Pid, kill),
+ Sel = rm_selected(Pid,Sel0),
+ {noreply, State#state{sel=Sel}};
handle_event(#wx{id=?ID_PROC},
- #state{panel=Panel, sel={_, [Pid|_]},procinfo_menu_pids=Opened}=State) ->
+ #state{panel=Panel, right_clicked_pid=Pid, procinfo_menu_pids=Opened}=State) ->
Opened2 = start_procinfo(Pid, Panel, Opened),
{noreply, State#state{procinfo_menu_pids=Opened2}};
@@ -319,7 +321,7 @@ handle_event(#wx{id=?ID_TRACE_PIDS}, #state{sel={_, Pids}, panel=Panel}=State)
observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION),
{noreply, State};
Pids ->
- observer_trace_wx:add_processes(observer_wx:get_tracer(), Pids),
+ observer_trace_wx:add_processes(Pids),
{noreply, State}
end;
@@ -330,12 +332,12 @@ handle_event(#wx{id=?ID_TRACE_NAMES}, #state{sel={SelIds,_Pids}, holder=Holder,
{noreply, State};
_ ->
PidsOrReg = call(Holder, {get_name_or_pid, self(), SelIds}),
- observer_trace_wx:add_processes(observer_wx:get_tracer(), PidsOrReg),
+ observer_trace_wx:add_processes(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]),
+ observer_trace_wx:add_processes([new_processes]),
{noreply, State};
handle_event(#wx{event=#wxSize{size={W,_}}},
@@ -347,20 +349,26 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click,
itemIndex=Row}},
#state{panel=Panel, holder=Holder}=State) ->
- case call(Holder, {get_row, self(), Row, pid}) of
- {error, undefined} ->
- undefined;
- {ok, _} ->
- 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};
+ Pid =
+ case call(Holder, {get_row, self(), Row, pid}) of
+ {error, undefined} ->
+ undefined;
+ {ok, P} ->
+ Menu = wxMenu:new(),
+ wxMenu:append(Menu, ?ID_PROC,
+ "Process info for " ++ pid_to_list(P)),
+ wxMenu:append(Menu, ?ID_TRACE_PIDS,
+ "Trace selected processes",
+ [{help, ?TRACE_PIDS_STR}]),
+ wxMenu:append(Menu, ?ID_TRACE_NAMES,
+ "Trace selected processes by name (all nodes)",
+ [{help, ?TRACE_NAMES_STR}]),
+ wxMenu:append(Menu, ?ID_KILL, "Kill process " ++ pid_to_list(P)),
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu),
+ P
+ end,
+ {noreply, State#state{right_clicked_pid=Pid}};
handle_event(#wx{event=#wxList{type=command_list_item_focused,
itemIndex=Row}},
@@ -432,6 +440,17 @@ set_focus([Old|_], [New|_], Grid) ->
wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED),
wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED).
+rm_selected(Pid, {Ids, Pids}) ->
+ rm_selected(Pid, Ids, Pids, [], []).
+
+rm_selected(Pid, [_Id|Ids], [Pid|Pids], AccIds, AccPids) ->
+ {lists:reverse(AccIds)++Ids,lists:reverse(AccPids)++Pids};
+rm_selected(Pid, [Id|Ids], [OtherPid|Pids], AccIds, AccPids) ->
+ rm_selected(Pid, Ids, Pids, [Id|AccIds], [OtherPid|AccPids]);
+rm_selected(_, [], [], AccIds, AccPids) ->
+ {lists:reverse(AccIds), lists:reverse(AccPids)}.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_table_holder(Parent, Attrs) ->
@@ -492,7 +511,13 @@ table_holder(#holder{info=Info, attrs=Attrs,
table_holder(S0);
{dump, Fd} ->
EtopInfo = (S0#holder.etop)#etop_info{procinfo=array:to_list(Info)},
- etop_txt:do_update(Fd, EtopInfo, #opts{node=Node}),
+ %% The empty #etop_info{} below is a dummy previous info
+ %% value. It is used by etop to calculate the scheduler
+ %% utilization since last update. When dumping to file,
+ %% there is no previous measurement to use, so we just add
+ %% a dummy here, and the value shown will be since the
+ %% tool was started.
+ etop_txt:do_update(Fd, EtopInfo, #etop_info{}, #opts{node=Node}),
file:close(Fd),
table_holder(S0);
stop ->
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index 871ef603db..8d19d77488 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -92,7 +92,7 @@ init([Pid, ParentFrame, Parent]) ->
observer_wx:return_to_localnode(ParentFrame, node(Pid)),
{stop, badrpc};
process_undefined ->
- observer_lib:display_info_dialog("No such alive process"),
+ observer_lib:display_info_dialog(ParentFrame,"No such alive process"),
{stop, normal}
end.
@@ -125,11 +125,11 @@ handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) ->
{noreply, State};
handle_event(#wx{obj=Obj, event=#wxMouse{type=enter_window}}, State) ->
- wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}),
+ wxStaticText:setForegroundColour(Obj,{0,0,100,255}),
{noreply, State};
handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) ->
- wxTextCtrl:setForegroundColour(Obj,?wxBLUE),
+ wxStaticText:setForegroundColour(Obj,?wxBLUE),
{noreply, State};
handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}},
@@ -317,7 +317,7 @@ fetch_state_info2(Pid, M) ->
of
{status, _, {module, _},
[_PDict, _SysState, _Parent, _Dbg,
- [Header,{data, First},{data, Second}]]} ->
+ [Header,{data, First},{data, Second}|_]]} ->
[{"Behaviour", B}, Header] ++ First ++ Second;
{status, _, {module, _},
[_PDict, _SysState, _Parent, _Dbg,
@@ -370,7 +370,7 @@ process_info_fields(Pid) ->
{"Priority", priority},
{"Trap Exit", trap_exit},
{"Reductions", reductions},
- {"Binary", binary},
+ {"Binary", fun(Data) -> stringify_bins(Data) end},
{"Last Calls", last_calls},
{"Catch Level", catchlevel},
{"Trace", trace},
@@ -434,9 +434,14 @@ get_gc_info(Arg) ->
filter_monitor_info() ->
fun(Data) ->
Ms = proplists:get_value(monitors, Data),
- [Pid || {process, Pid} <- Ms]
+ [Id || {_Type, Id} <- Ms] % Type is process or port
end.
+stringify_bins(Data) ->
+ Bins = proplists:get_value(binary, Data),
+ [lists:flatten(io_lib:format("<< ~s, refc ~w>>", [observer_lib:to_str({bytes,Sz}),Refc]))
+ || {_Ptr, Sz, Refc} <- Bins].
+
local_pid_str(Pid) ->
%% observer can observe remote nodes
%% There is no function to get the local
@@ -449,7 +454,6 @@ local_pid_str(Pid) ->
global_pid_node_pref(Pid) ->
%% Global PID node prefix : X of <X.Y.Z>
string:strip(string:sub_word(pid_to_list(Pid),1,$.),left,$<).
-
io_get_data(Pid) ->
Pid ! {self(), get_data_and_close},
diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl
index dfd15380f2..fa824995f7 100644
--- a/lib/observer/src/observer_sys_wx.erl
+++ b/lib/observer/src/observer_sys_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -48,40 +48,51 @@ start_link(Notebook, Parent) ->
init([Notebook, Parent]) ->
SysInfo = observer_backend:sys_info(),
- {Info, Stat} = info_fields(),
+ {Sys, Mem, Cpu, Stats} = info_fields(),
Panel = wxPanel:new(Notebook),
Sizer = wxBoxSizer:new(?wxVERTICAL),
- TopSizer = wxBoxSizer:new(?wxHORIZONTAL),
- {FPanel0, _FSizer0, Fields0} =
- observer_lib:display_info(Panel, observer_lib:fill_info(Info, SysInfo)),
- {FPanel1, _FSizer1, Fields1} =
- observer_lib:display_info(Panel, observer_lib:fill_info(Stat, SysInfo)),
- wxSizer:add(TopSizer, FPanel0, [{flag, ?wxEXPAND}, {proportion, 1}]),
- wxSizer:add(TopSizer, FPanel1, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ HSizer0 = wxBoxSizer:new(?wxHORIZONTAL),
+ {FPanel0, _FSizer0, Fields0} = observer_lib:display_info(Panel, observer_lib:fill_info(Sys, SysInfo)),
+ {FPanel1, _FSizer1, Fields1} = observer_lib:display_info(Panel, observer_lib:fill_info(Mem, SysInfo)),
+ wxSizer:add(HSizer0, FPanel0, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(HSizer0, FPanel1, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ HSizer1 = wxBoxSizer:new(?wxHORIZONTAL),
+ {FPanel2, _FSizer2, Fields2} = observer_lib:display_info(Panel, observer_lib:fill_info(Cpu, SysInfo)),
+ {FPanel3, _FSizer3, Fields3} = observer_lib:display_info(Panel, observer_lib:fill_info(Stats, SysInfo)),
+ wxSizer:add(HSizer1, FPanel2, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(HSizer1, FPanel3, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
BorderFlags = ?wxLEFT bor ?wxRIGHT,
- wxSizer:add(Sizer, TopSizer, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
- {proportion, 0}, {border, 5}]),
+ wxSizer:add(Sizer, HSizer0, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
+ {proportion, 0}, {border, 5}]),
+ wxSizer:add(Sizer, HSizer1, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},
+ {proportion, 0}, {border, 5}]),
wxPanel:setSizer(Panel, Sizer),
Timer = observer_lib:start_timer(10),
{Panel, #sys_wx_state{parent=Parent,
parent_notebook=Notebook,
panel=Panel, sizer=Sizer,
- timer=Timer, fields=Fields0 ++ Fields1}}.
+ timer=Timer, fields=Fields0 ++ Fields1++Fields2++Fields3}}.
create_sys_menu(Parent) ->
View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"},
#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh interval"}]},
observer_wx:create_menus(Parent, [View]).
+update_syspage(#sys_wx_state{node = undefined}) -> ignore;
update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) ->
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)),
+ {Sys, Mem, Cpu, Stats} = info_fields(),
+ observer_lib:update_info(Fields,
+ observer_lib:fill_info(Sys, SysInfo) ++
+ observer_lib:fill_info(Mem, SysInfo) ++
+ observer_lib:fill_info(Cpu, SysInfo) ++
+ observer_lib:fill_info(Stats, SysInfo)),
wxSizer:layout(Sizer).
info_fields() ->
- Info = [{"System and Architecture",
+ Sys = [{"System and Architecture",
[{"System Version", otp_release},
{"ERTS Version", version},
{"Compiled for", system_architecture},
@@ -90,34 +101,35 @@ info_fields() ->
{"SMP Support", smp_support},
{"Thread Support", threads},
{"Async thread pool size", thread_pool_size}
- ]},
- {"CPU's and Threads",
- [{"Logical CPU's", logical_processors},
- {"Online Logical CPU's", logical_processors_online},
- {"Available Logical CPU's", logical_processors_available},
- {"Schedulers", schedulers},
- {"Online schedulers", schedulers_online},
- {"Available schedulers", schedulers_available}
- ]}
- ],
- Stat = [{"Memory Usage", right,
- [{"Total", {bytes, total}},
- {"Processes", {bytes, processes}},
- {"Atoms", {bytes, atom}},
- {"Binaries", {bytes, binary}},
- {"Code", {bytes, code}},
- {"ETS", {bytes, ets}}
- ]},
- {"Statistics", right,
- [{"Up time", {time_ms, uptime}},
- {"Max Processes", process_limit},
- {"Processes", process_count},
- {"Run Queue", run_queue},
- {"IO Input", {bytes, io_input}},
- {"IO Output", {bytes, io_output}}
- ]}
- ],
- {Info, Stat}.
+ ]}],
+
+ Cpu = [{"CPU's and Threads",
+ [{"Logical CPU's", logical_processors},
+ {"Online Logical CPU's", logical_processors_online},
+ {"Available Logical CPU's", logical_processors_available},
+ {"Schedulers", schedulers},
+ {"Online schedulers", schedulers_online},
+ {"Available schedulers", schedulers_available}
+ ]}
+ ],
+ Mem = [{"Memory Usage", right,
+ [{"Total", {bytes, total}},
+ {"Processes", {bytes, processes}},
+ {"Atoms", {bytes, atom}},
+ {"Binaries", {bytes, binary}},
+ {"Code", {bytes, code}},
+ {"ETS", {bytes, ets}}
+ ]}],
+ Stats = [{"Statistics", right,
+ [{"Up time", {time_ms, uptime}},
+ {"Max Processes", process_limit},
+ {"Processes", process_count},
+ {"Run Queue", run_queue},
+ {"IO Input", {bytes, io_input}},
+ {"IO Output", {bytes, io_output}}
+ ]}
+ ],
+ {Sys, Mem, Cpu, Stats}.
%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl
index 11c2acb561..af90e2100c 100644
--- a/lib/observer/src/observer_trace_wx.erl
+++ b/lib/observer/src/observer_trace_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
-module(observer_trace_wx).
--export([start_link/2, add_processes/2]).
+-export([start_link/2, add_processes/1, add_ports/1]).
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
handle_event/2, handle_cast/2]).
@@ -31,11 +31,15 @@
-define(SAVE_TRACEOPTS, 305).
-define(LOAD_TRACEOPTS, 306).
-define(TOGGLE_TRACE, 307).
--define(ADD_NEW, 308).
--define(ADD_TP, 309).
--define(TRACE_OUTPUT, 310).
--define(TRACE_DEFMS, 311).
--define(TRACE_DEFPS, 312).
+-define(ADD_NEW_PROCS, 308).
+-define(ADD_NEW_PORTS, 309).
+-define(ADD_TP, 310).
+-define(TRACE_OUTPUT, 311).
+-define(DEF_MS_FUNCS, 312).
+-define(DEF_MS_SEND, 313).
+-define(DEF_MS_RECV, 314).
+-define(DEF_PROC_OPTS, 315).
+-define(DEF_PORT_OPTS, 316).
-define(NODES_WIN, 330).
-define(ADD_NODES, 331).
@@ -45,36 +49,53 @@
-define(EDIT_PROCS, 341).
-define(REMOVE_PROCS, 342).
--define(MODULES_WIN, 350).
+-define(PORT_WIN, 350).
+-define(EDIT_PORTS, 351).
+-define(REMOVE_PORTS, 352).
--define(FUNCS_WIN, 360).
--define(EDIT_FUNCS_MS, 361).
--define(REMOVE_FUNCS_MS, 362).
+-define(MODULES_WIN, 360).
+-define(REMOVE_MOD_MS, 361).
--define(LOG_WIN, 370).
--define(LOG_SAVE, 321).
--define(LOG_CLEAR, 322).
+-define(FUNCS_WIN, 370).
+-define(EDIT_FUNCS_MS, 371).
+-define(REMOVE_FUNCS_MS, 372).
+
+-define(LOG_WIN, 380).
+-define(LOG_SAVE, 381).
+-define(LOG_CLEAR, 382).
+
+-define(NO_NODES_HELP,"Right click to add nodes").
+-define(NODES_HELP,"Select nodes to see traced processes and ports").
+-define(NO_P_HELP,"Add items from Processes/Ports tab").
+-define(P_HELP,"Select nodes to see traced processes and ports").
+-define(NO_TP_HELP,"Add trace pattern with button below").
+-define(TP_HELP,"Select module to see trace patterns").
-record(state,
{parent,
panel,
- n_view, p_view, m_view, f_view, %% The listCtrl's
+ n_view, proc_view, port_view, m_view, f_view, %% The listCtrl's
logwin, %% The latest log window
nodes = [],
toggle_button,
- tpids = [], %% #tpid
- def_trace_opts = [],
+ tpids = [], % #titem
+ tports = [], % #titem
+ def_proc_flags = [],
+ def_port_flags = [],
output = [],
tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec}
match_specs = []}). % [ #match_spec{} ]
--record(tpid, {pid, opts}).
+-record(titem, {id, opts}).
start_link(Notebook, ParentPid) ->
wx_object:start_link(?MODULE, [Notebook, ParentPid], []).
-add_processes(Tracer, Pids) when is_list(Pids) ->
- wx_object:cast(Tracer, {add_processes, Pids}).
+add_processes(Pids) when is_list(Pids) ->
+ wx_object:cast(observer_wx:get_tracer(), {add_processes, Pids}).
+
+add_ports(Ports) when is_list(Ports) ->
+ wx_object:cast(observer_wx:get_tracer(), {add_ports, Ports}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -87,11 +108,13 @@ create_window(Notebook, ParentPid) ->
Sizer = wxBoxSizer:new(?wxVERTICAL),
Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)},
{style, ?SASH_STYLE}]),
- {NodeProcView, NodeView, ProcessView} = create_process_view(Splitter),
+ {NodeProcView, NodeView, ProcessView, PortView} =
+ create_proc_port_view(Splitter),
{MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter),
wxSplitterWindow:setSashGravity(Splitter, 0.5),
wxSplitterWindow:setMinimumPaneSize(Splitter,50),
- wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView),
+ wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView,
+ [{sashPosition,368}]),
wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]),
%% Buttons
Buttons = wxBoxSizer:new(?wxHORIZONTAL),
@@ -99,7 +122,8 @@ create_window(Notebook, ParentPid) ->
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_NEW_PROCS, [{label, "Add 'new' Processes"}])),
+ wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PORTS, [{label, "Add 'new' Ports"}])),
wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}])),
wxMenu:connect(Panel, command_togglebutton_clicked, [{skip, true}]),
wxMenu:connect(Panel, command_button_clicked, [{skip, true}]),
@@ -107,24 +131,47 @@ create_window(Notebook, ParentPid) ->
{border, 5}, {proportion,0}]),
wxWindow:setSizer(Panel, Sizer),
{Panel, #state{parent=ParentPid, panel=Panel,
- n_view=NodeView, p_view=ProcessView, m_view=ModView, f_view=FuncView,
+ n_view=NodeView, proc_view=ProcessView, port_view=PortView,
+ m_view=ModView, f_view=FuncView,
toggle_button = ToggleButton,
match_specs=default_matchspecs()}}.
default_matchspecs() ->
- Ms = [{"Return Trace", [{'_', [], [{return_trace}]}], "fun(_) -> return_trace() end"},
- {"Exception Trace", [{'_', [], [{exception_trace}]}], "fun(_) -> exception_trace() end"},
- {"Message Caller", [{'_', [], [{message,{caller}}]}], "fun(_) -> message(caller()) end"},
- {"Message Dump", [{'_', [], [{message,{process_dump}}]}], "fun(_) -> message(process_dump()) end"}],
+ [{Key,default_matchspecs(Key)} || Key <- [funcs,send,'receive']].
+default_matchspecs(Key) ->
+ Ms = get_default_matchspecs(Key),
[make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms].
-create_process_view(Parent) ->
+get_default_matchspecs(funcs) ->
+ [{"Return Trace", [{'_', [], [{return_trace}]}],
+ "fun(_) -> return_trace() end"},
+ {"Exception Trace", [{'_', [], [{exception_trace}]}],
+ "fun(_) -> exception_trace() end"},
+ {"Message Caller", [{'_', [], [{message,{caller}}]}],
+ "fun(_) -> message(caller()) end"},
+ {"Message Dump", [{'_', [], [{message,{process_dump}}]}],
+ "fun(_) -> message(process_dump()) end"}];
+get_default_matchspecs(send) ->
+ [{"To local node", [{['$1','_'], [{'==',{node,'$1'},{node}}], []}],
+ "fun([Pid,_]) when node(Pid)==node() ->\n true\nend"},
+ {"To remote node", [{['$1','_'], [{'=/=',{node,'$1'},{node}}], []}],
+ "fun([Pid,_]) when node(Pid)=/=node() ->\n true\nend"}];
+get_default_matchspecs('receive') ->
+ [{"From local node", [{['$1','_','_'], [{'==','$1',{node}}], []}],
+ "fun([Node,_,_]) when Node==node() ->\n true\nend"},
+ {"From remote node", [{['$1','_','_'], [{'=/=','$1',{node}}], []}],
+ "fun([Node,_,_]) when Node=/=node() ->\n true\nend"}].
+
+
+create_proc_port_view(Parent) ->
Panel = wxPanel:new(Parent),
MainSz = wxBoxSizer:new(?wxHORIZONTAL),
Style = ?wxLC_REPORT bor ?wxLC_HRULES,
Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]),
Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]),
- Procs = wxListCtrl:new(Splitter, [{winid, ?PROC_WIN}, {style, Style}]),
+ ProcsPortsSplitter = wxSplitterWindow:new(Splitter, [{style, ?SASH_STYLE}]),
+ Procs = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PROC_WIN},{style,Style}]),
+ Ports = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PORT_WIN},{style,Style}]),
Li = wxListItem:new(),
wxListItem:setText(Li, "Nodes"),
wxListCtrl:insertColumn(Nodes, 0, Li),
@@ -136,31 +183,57 @@ create_process_view(Parent) ->
wxListCtrl:setColumnWidth(Procs, Col, DefSize),
Col + 1
end,
- ListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120},
- {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}],
- lists:foldl(AddProc, 0, ListItems),
+ ProcListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120},
+ {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}],
+ lists:foldl(AddProc, 0, ProcListItems),
+
+ AddPort = fun({Name, Align, DefSize}, Col) ->
+ wxListItem:setText(Li, Name),
+ wxListItem:setAlign(Li, Align),
+ wxListCtrl:insertColumn(Ports, Col, Li),
+ wxListCtrl:setColumnWidth(Ports, Col, DefSize),
+ Col + 1
+ end,
+ PortListItems = [{"Port Id", ?wxLIST_FORMAT_CENTER, 120},
+ {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}],
+ lists:foldl(AddPort, 0, PortListItems),
+
wxListItem:destroy(Li),
wxSplitterWindow:setSashGravity(Splitter, 0.0),
wxSplitterWindow:setMinimumPaneSize(Splitter,50),
- wxSplitterWindow:splitVertically(Splitter, Nodes, Procs, [{sashPosition, 155}]),
+ wxSplitterWindow:splitVertically(Splitter, Nodes, ProcsPortsSplitter,
+ [{sashPosition, 155}]),
wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSplitterWindow:setSashGravity(ProcsPortsSplitter, 0.5),
+ wxSplitterWindow:setMinimumPaneSize(ProcsPortsSplitter,50),
+ wxSplitterWindow:splitHorizontally(ProcsPortsSplitter, Procs, Ports,
+ [{sashPosition, 182}]),
+
wxListCtrl:connect(Procs, command_list_item_right_click),
+ wxListCtrl:connect(Ports, command_list_item_right_click),
wxListCtrl:connect(Nodes, command_list_item_right_click),
+ wxListCtrl:connect(Nodes, command_list_item_selected),
wxListCtrl:connect(Procs, size, [{skip, true}]),
+ wxListCtrl:connect(Ports, size, [{skip, true}]),
wxListCtrl:connect(Nodes, size, [{skip, true}]),
+ wxListCtrl:setToolTip(Nodes, ?NO_NODES_HELP),
+ wxListCtrl:setToolTip(Procs, ?NO_P_HELP),
+ wxListCtrl:setToolTip(Ports, ?NO_P_HELP),
+
wxPanel:setSizer(Panel, MainSz),
wxWindow:setFocus(Procs),
- {Panel, Nodes, Procs}.
+ {Panel, Nodes, Procs, Ports}.
create_matchspec_view(Parent) ->
Panel = wxPanel:new(Parent),
MainSz = wxBoxSizer:new(?wxHORIZONTAL),
Style = ?wxLC_REPORT bor ?wxLC_HRULES,
Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]),
- Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, {style, Style}]),
+ Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN},
+ {style, Style bor ?wxLC_SINGLE_SEL}]),
Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]),
Li = wxListItem:new(),
@@ -182,7 +255,9 @@ create_matchspec_view(Parent) ->
wxListCtrl:connect(Modules, size, [{skip, true}]),
wxListCtrl:connect(Funcs, size, [{skip, true}]),
wxListCtrl:connect(Modules, command_list_item_selected),
+ wxListCtrl:connect(Modules, command_list_item_right_click),
wxListCtrl:connect(Funcs, command_list_item_right_click),
+ wxListCtrl:setToolTip(Panel, ?NO_TP_HELP),
wxPanel:setSizer(Panel, MainSz),
{Panel, Modules, Funcs}.
@@ -192,8 +267,11 @@ create_menues(Parent) ->
#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"}]}
+ #create_menu{id = ?DEF_MS_FUNCS, text = "Default Match Specifications for Functions"},
+ #create_menu{id = ?DEF_MS_SEND, text = "Default Match Specifications for 'send'"},
+ #create_menu{id = ?DEF_MS_RECV, text = "Default Match Specifications for 'receive'"},
+ #create_menu{id = ?DEF_PROC_OPTS, text = "Default Process Options"},
+ #create_menu{id = ?DEF_PORT_OPTS, text = "Default Port Options"}]}
],
observer_wx:create_menus(Parent, Menus).
@@ -206,11 +284,19 @@ handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) ->
end,
{noreply, State};
-handle_event(#wx{id=?ADD_NEW}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) ->
+handle_event(#wx{id=?ADD_NEW_PROCS}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) ->
try
Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts),
- Process = #tpid{pid=new, opts=Opts},
- {noreply, do_add_processes([Process], State#state{def_trace_opts=Opts})}
+ Process = #titem{id=new_processes, opts=Opts},
+ {noreply, do_add_processes([Process], State#state{def_proc_flags=Opts})}
+ catch cancel -> {noreply, State}
+ end;
+
+handle_event(#wx{id=?ADD_NEW_PORTS}, State = #state{panel=Parent, def_port_flags=TraceOpts}) ->
+ try
+ Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts),
+ Port = #titem{id=new_ports, opts=Opts},
+ {noreply, do_add_ports([Port], State#state{def_port_flags=Opts})}
catch cancel -> {noreply, State}
end;
@@ -233,26 +319,36 @@ handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected,
update_functions_view(dict:fetch(Module, TPs), Fview),
{noreply, State};
+handle_event(#wx{id=?NODES_WIN,
+ event=#wxList{type=command_list_item_selected}},
+ State = #state{tpids=Tpids, tports=Tports, n_view=Nview,
+ proc_view=ProcView, port_view=PortView, nodes=Ns}) ->
+ Nodes = get_selected_items(Nview, Ns),
+ update_p_view(Tpids, ProcView, Nodes),
+ update_p_view(Tports, PortView, Nodes),
+ {noreply, State};
+
handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}},
#state{panel = Panel,
nodes = Nodes,
tpids = TProcs,
+ tports = TPorts,
tpatterns = TPs0,
toggle_button = ToggleBtn,
output = Opts
} = State) ->
try
TPs = dict:to_list(TPs0),
- (TProcs == []) andalso throw({error, "No processes traced"}),
+ (TProcs == []) andalso (TPorts == []) andalso throw({error, "No processes or ports traced"}),
(Nodes == []) andalso throw({error, "No nodes traced"}),
- HaveCallTrace = fun(#tpid{opts=Os}) -> lists:member(functions,Os) end,
+ HaveCallTrace = fun(#titem{opts=Os}) -> lists:member(functions,Os) end,
WStr = "Call trace actived but no trace patterns used",
(TPs == []) andalso lists:any(HaveCallTrace, TProcs) andalso
observer_wx:create_txt_dialog(Panel, WStr, "Warning", ?wxICON_WARNING),
{TTB, LogWin} = ttb_output_args(Panel, Opts),
{ok, _} = ttb:tracer(Nodes, TTB),
- setup_ttb(TPs, TProcs),
+ setup_ttb(TPs, TProcs, TPorts),
wxToggleButton:setLabel(ToggleBtn, "Stop Trace"),
{noreply, State#state{logwin=LogWin}}
catch {error, Msg} ->
@@ -302,7 +398,8 @@ handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) ->
handle_event(#wx{id = ?SAVE_TRACEOPTS},
#state{panel = Panel,
- def_trace_opts = TraceOpts,
+ def_proc_flags = ProcFlags,
+ def_port_flags = PortFlags,
match_specs = MatchSpecs,
tpatterns = TracePatterns,
output = Output
@@ -312,7 +409,7 @@ handle_event(#wx{id = ?SAVE_TRACEOPTS},
?wxID_OK ->
Path = wxFileDialog:getPath(Dialog),
write_file(Panel, Path,
- TraceOpts, MatchSpecs, Output,
+ ProcFlags, PortFlags, MatchSpecs, Output,
dict:to_list(TracePatterns)
);
_ ->
@@ -333,52 +430,159 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) ->
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),
+handle_event(#wx{id=?PROC_WIN, event=#wxList{type=command_list_item_right_click}},
+ State = #state{panel=Panel, proc_view=LCtrl, tpids=Tpids,
+ n_view=Nview, nodes=Nodes}) ->
+ case get_visible_ps(Tpids, Nodes, Nview) of
+ [] ->
+ ok;
+ Visible ->
+ case get_selected_items(LCtrl, Visible) of
+ [] ->
+ ok;
+ _ ->
+ create_right_click_menu(
+ Panel,
+ [{?EDIT_PROCS, "Edit process options"},
+ {?REMOVE_PROCS, "Remove processes"}])
+ end
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?PORT_WIN, event=#wxList{type=command_list_item_right_click}},
+ State = #state{panel=Panel, port_view=LCtrl, tports=Tports,
+ n_view=Nview, nodes=Nodes}) ->
+ case get_visible_ps(Tports, Nodes, Nview) of
+ [] ->
+ ok;
+ Visible ->
+ case get_selected_items(LCtrl, Visible) of
+ [] ->
+ ok;
+ _ ->
+ create_right_click_menu(
+ Panel,
+ [{?EDIT_PORTS, "Edit port options"},
+ {?REMOVE_PORTS, "Remove ports"}])
+ end
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?MODULES_WIN,event=#wxList{type=command_list_item_right_click}},
+ State = #state{panel=Panel, m_view=Mview, tpatterns=TPs}) ->
+ case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of
+ [] ->
+ ok;
+ _ ->
+ create_right_click_menu(
+ Panel,
+ [{?REMOVE_MOD_MS, "Remove trace patterns"}])
+ end,
+ {noreply,State};
+
+handle_event(#wx{id=?FUNCS_WIN,event=#wxList{type=command_list_item_right_click}},
+ State = #state{panel=Panel, m_view=Mview, f_view=Fview,
+ tpatterns=TPs}) ->
+ case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of
+ [] ->
+ ok;
+ [Module] ->
+ case get_selected_items(Fview, dict:fetch(Module, TPs)) of
+ [] ->
+ ok;
+ _ ->
+ create_right_click_menu(
+ Panel,
+ [{?EDIT_FUNCS_MS, "Edit matchspecs"},
+ {?REMOVE_FUNCS_MS, "Remove trace patterns"}])
+ end
+ end,
+ {noreply,State};
+
+handle_event(#wx{id=?NODES_WIN,event=#wxList{type=command_list_item_right_click}},
+ State = #state{panel=Panel, n_view=Nview, nodes=Nodes}) ->
+ Menu =
+ case get_selected_items(Nview, Nodes) of
+ [] ->
+ [{?ADD_NODES, "Add nodes"}];
+ _ ->
+ [{?ADD_NODES, "Add nodes"},
+ {?REMOVE_NODES, "Remove nodes"}]
+ end,
+ create_right_click_menu(Panel,Menu),
{noreply, State};
-handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, p_view=Ps} = State) ->
+handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Procs} = State) ->
try
- [#tpid{opts=DefOpts}|_] = Selected = get_selected_items(Ps, Tpids),
+ [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Procs, 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})}
+ Changed = [Tpid#titem{opts=Opts} || Tpid <- Selected],
+ {noreply, do_add_processes(Changed, State#state{def_proc_flags=Opts})}
catch _:_ ->
{noreply, State}
end;
-handle_event(#wx{id=?REMOVE_PROCS}, #state{tpids=Tpids, p_view=LCtrl} = State) ->
+handle_event(#wx{id=?REMOVE_PROCS},
+ #state{tpids=Tpids, proc_view=LCtrl,
+ n_view=Nview, nodes=Nodes} = State) ->
Selected = get_selected_items(LCtrl, Tpids),
Pids = Tpids -- Selected,
- update_process_view(Pids, LCtrl),
+ update_p_view(Pids, LCtrl, Nodes, Nview),
{noreply, State#state{tpids=Pids}};
-handle_event(#wx{id=?TRACE_DEFPS}, #state{panel=Panel, def_trace_opts=PO} = State) ->
+handle_event(#wx{id=?EDIT_PORTS}, #state{panel=Panel, tports=Tports, port_view=Ports} = State) ->
+ try
+ [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Ports, Tports),
+ Opts = observer_traceoptions_wx:port_trace(Panel, DefOpts),
+ Changed = [Tport#titem{opts=Opts} || Tport <- Selected],
+ {noreply, do_add_ports(Changed, State#state{def_port_flags=Opts})}
+ catch _:_ ->
+ {noreply, State}
+ end;
+
+handle_event(#wx{id=?REMOVE_PORTS},
+ #state{tports=Tports, port_view=LCtrl,
+ n_view=Nview, nodes=Nodes} = State) ->
+ Selected = get_selected_items(LCtrl, Tports),
+ Ports = Tports -- Selected,
+ update_p_view(Ports, LCtrl, Nodes, Nview),
+ {noreply, State#state{tports=Ports}};
+
+handle_event(#wx{id=?DEF_PROC_OPTS}, #state{panel=Panel, def_proc_flags=PO} = State) ->
try
Opts = observer_traceoptions_wx:process_trace(Panel, PO),
- {noreply, State#state{def_trace_opts=Opts}}
+ {noreply, State#state{def_proc_flags=Opts}}
catch _:_ ->
{noreply, State}
end;
-handle_event(#wx{id=?TRACE_DEFMS}, #state{panel=Panel, match_specs=Ms} = State) ->
+handle_event(#wx{id=?DEF_PORT_OPTS}, #state{panel=Panel, def_port_flags=PO} = State) ->
+ try
+ Opts = observer_traceoptions_wx:port_trace(Panel, PO),
+ {noreply, State#state{def_port_flags=Opts}}
+ catch _:_ ->
+ {noreply, State}
+ end;
+
+handle_event(#wx{id=?DEF_MS_FUNCS}, #state{panel=Panel, match_specs=Ms} = State) ->
try %% Return selected MS and sends new MS's to us
- observer_traceoptions_wx:select_matchspec(self(), Panel, Ms)
+ observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, funcs)
+ catch _:_ ->
+ cancel
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?DEF_MS_SEND}, #state{panel=Panel, match_specs=Ms} = State) ->
+ try %% Return selected MS and sends new MS's to us
+ observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, send)
+ catch _:_ ->
+ cancel
+ end,
+ {noreply, State};
+
+handle_event(#wx{id=?DEF_MS_RECV}, #state{panel=Panel, match_specs=Ms} = State) ->
+ try %% Return selected MS and sends new MS's to us
+ observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, 'receive')
catch _:_ ->
cancel
end,
@@ -389,12 +593,34 @@ handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs,
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 _:_ ->
+ case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of
+ [] ->
+ throw({error,"No module selected"});
+ [Module] ->
+ Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)),
+ Key = case Module of
+ 'Events' ->
+ SelectedEvents =
+ [Event || #tpattern{fa=Event} <- Selected],
+ E1 = hd(SelectedEvents),
+ case lists:all(fun(E) when E==E1 -> true;
+ (_) -> false
+ end,
+ SelectedEvents) of
+ true -> E1;
+ false -> throw({error,"Can not set match specs for multiple event types"})
+ end;
+ _ -> funcs
+ end,
+ Ms = observer_traceoptions_wx:select_matchspec(self(), Panel,
+ Mss, Key),
+ Changed = [TP#tpattern{ms=Ms} || TP <- Selected],
+ {noreply, do_add_patterns({Module, Changed}, State)}
+ end
+ catch {error, Msg} ->
+ observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR),
+ {noreply, State};
+ cancel ->
{noreply, State}
end;
@@ -417,6 +643,16 @@ handle_event(#wx{id=?REMOVE_FUNCS_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_vi
{noreply, State#state{tpatterns=TPs}}
end;
+handle_event(#wx{id=?REMOVE_MOD_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] ->
+ update_functions_view([], LCtrl),
+ TPs = dict:erase(Module, TPs0),
+ update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview),
+ {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),
@@ -458,11 +694,20 @@ handle_call(Msg, From, _State) ->
error({unhandled_call, Msg, From}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) ->
+handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) ->
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}),
+ POpts = [#titem{id=Pid, opts=Opts} || Pid <- Pids],
+ S = do_add_processes(POpts, State#state{def_proc_flags=Opts}),
+ {noreply, S}
+ catch cancel ->
+ {noreply, State}
+ end;
+handle_cast({add_ports, Ports}, State = #state{panel=Parent, def_port_flags=TraceOpts}) ->
+ try
+ Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts),
+ POpts = [#titem{id=Id, opts=Opts} || Id <- Ports],
+ S = do_add_ports(POpts, State#state{def_port_flags=Opts}),
{noreply, S}
catch cancel ->
{noreply, State}
@@ -510,45 +755,93 @@ do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_vi
State#state{tpatterns=TPs}
end.
-do_add_processes(POpts, S0=#state{n_view=Nview, p_view=LCtrl, tpids=OldPids, nodes=Ns0}) ->
- case merge_pids(POpts, OldPids) of
- {OldPids, [], []} ->
- S0;
- {Pids, New, _Changed} ->
- update_process_view(Pids, LCtrl),
- Ns1 = lists:usort([node(Pid) || #tpid{pid=Pid} <- New, is_pid(Pid)]),
+do_add_processes(POpts, S0=#state{n_view=Nview, proc_view=LCtrl, tpids=OldPids, nodes=OldNodes}) ->
+ CheckFun = fun(Pid) -> is_pid(Pid) end,
+ {Pids, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl,
+ OldPids, OldNodes, CheckFun),
+ S0#state{tpids=Pids, nodes=Nodes}.
+
+do_add_ports(POpts, S0=#state{n_view=Nview, port_view=LCtrl, tports=OldPorts, nodes=OldNodes}) ->
+ CheckFun = fun(Port) -> is_port(Port) end,
+ {Ports, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl,
+ OldPorts, OldNodes, CheckFun),
+ S0#state{tports=Ports, nodes=Nodes}.
+
+do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) ->
+ case merge_trace_items(POpts, OldPs) of
+ {OldPs, [], []} ->
+ {OldPs,Ns0};
+ {Ps, New, _Changed} ->
+ Ns1 = lists:usort([node(Id) || #titem{id=Id} <- New, Check(Id)]),
Nodes = case ordsets:subtract(Ns1, Ns0) of
+ [] when Ns0==[] -> [observer_wx:get_active_node()];
[] -> Ns0; %% No new Nodes
- NewNs ->
- %% if dynamicly updates add trace patterns for new nodes
- All = ordsets:union(NewNs, Ns0),
- update_nodes_view(All, Nview),
- All
+ NewNs -> ordsets:union(NewNs, Ns0)
end,
- S0#state{tpids=Pids, nodes=Nodes}
+ update_nodes_view(Nodes, Nview),
+ update_p_view(Ps, LCtrl, Nodes, Nview),
+ {Ps, Nodes}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-update_process_view(Pids, LCtrl) ->
+get_visible_ps(PidsOrPorts, [Node], _Nview) ->
+ %% If only one node, treat this as selected
+ get_visible_ps(PidsOrPorts, [Node]);
+get_visible_ps(PidsOrPorts, Nodes, Nview) ->
+ get_visible_ps(PidsOrPorts, get_selected_items(Nview, Nodes)).
+
+get_visible_ps(PidsOrPorts, Nodes) ->
+ %% Show pids/ports belonging to the selected nodes only (+ named pids/ports)
+ [P || P <- PidsOrPorts,
+ is_atom(P#titem.id) orelse
+ lists:member(node(P#titem.id),Nodes)].
+
+update_p_view(PidsOrPorts, LCtrl, Nodes, Nview) ->
+ update_p_view(get_visible_ps(PidsOrPorts, Nodes, Nview), LCtrl).
+update_p_view(PidsOrPorts, LCtrl, Nodes) ->
+ update_p_view(get_visible_ps(PidsOrPorts, Nodes), LCtrl).
+
+update_p_view(PidsOrPorts, LCtrl) ->
+ %% pid- or port-view
wxListCtrl:deleteAllItems(LCtrl),
- wx:foldl(fun(#tpid{pid=Pid, opts=Opts}, Row) ->
+ wx:foldl(fun(#titem{id=Id, opts=Opts}, Row) ->
_Item = wxListCtrl:insertItem(LCtrl, Row, ""),
?EVEN(Row) andalso
wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
- wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Pid)),
+ wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Id)),
wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)),
Row+1
- end, 0, Pids).
+ end, 0, PidsOrPorts),
+ case PidsOrPorts of
+ [] ->
+ wxListCtrl:setToolTip(LCtrl,?NO_P_HELP);
+ _ ->
+ wxListCtrl:setToolTip(LCtrl,?P_HELP)
+ end.
update_nodes_view(Nodes, LCtrl) ->
+ Selected =
+ case Nodes of
+ [_] -> Nodes;
+ _ -> get_selected_items(LCtrl, Nodes)
+ end,
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)),
+ lists:member(Node,Selected) andalso % keep selection
+ wxListCtrl:setItemState(LCtrl, Row, 16#FFFF,
+ ?wxLIST_STATE_SELECTED),
Row+1
- end, 0, Nodes).
+ end, 0, Nodes),
+ case Nodes of
+ [] ->
+ wxListCtrl:setToolTip(LCtrl,?NO_NODES_HELP);
+ _ ->
+ wxListCtrl:setToolTip(LCtrl,?NODES_HELP)
+ end.
update_modules_view(Mods, Module, LCtrl) ->
wxListCtrl:deleteAllItems(LCtrl),
@@ -560,33 +853,51 @@ update_modules_view(Mods, Module, LCtrl) ->
(Mod =:= Module) andalso
wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, ?wxLIST_STATE_SELECTED),
Row+1
- end, 0, Mods).
+ end, 0, Mods),
+ Parent = wxListCtrl:getParent(LCtrl),
+ case Mods of
+ [] ->
+ wxListCtrl:setToolTip(Parent,?NO_TP_HELP);
+ _ ->
+ wxListCtrl:setToolTip(Parent,?TP_HELP)
+ end.
update_functions_view(Funcs, LCtrl) ->
wxListCtrl:deleteAllItems(LCtrl),
- wx:foldl(fun(#tpattern{fa=FA, ms=#match_spec{str=Ms}}, Row) ->
+ wx:foldl(fun(#tpattern{m=M, fa=FA, ms=#match_spec{str=Ms}}, Row) ->
_Item = wxListCtrl:insertItem(LCtrl, Row, ""),
?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
- wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({func,FA})),
+ FuncStr =
+ case M of
+ 'Events' ->
+ observer_lib:to_str(FA);
+ _ ->
+ observer_lib:to_str({func,FA})
+ end,
+ wxListCtrl:setItem(LCtrl, Row, 0, FuncStr),
wxListCtrl:setItem(LCtrl, Row, 1, Ms),
Row+1
end, 0, Funcs).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-merge_pids([N1=#tpid{pid=new}|Ns], [N2=#tpid{pid=new}|Old]) ->
- {Pids, New, Changed} = merge_pids_1(Ns,Old),
- {[N1|Pids], New, [{N2,N2}|Changed]};
-merge_pids([N1=#tpid{pid=new}|Ns], Old) ->
- {Pids, New, Changed} = merge_pids_1(Ns,Old),
- {[N1|Pids], [N1|New], Changed};
-merge_pids(Ns, [N2=#tpid{pid=new}|Old]) ->
- {Pids, New, Changed} = merge_pids_1(Ns,Old),
- {[N2|Pids], New, Changed};
-merge_pids(New, Old) ->
- merge_pids_1(New, Old).
-
-merge_pids_1(New, Old) ->
- merge(lists:sort(New), Old, #tpid.pid, [], [], []).
+%% Trace items are processes and ports
+merge_trace_items([N1=#titem{id=NewP}|Ns], [N2=#titem{id=NewP}|Old])
+ when NewP==new_processes; NewP==new_ports ->
+ {Ids, New, Changed} = merge_trace_items_1(Ns,Old),
+ {[N1|Ids], New, [{N2,N2}|Changed]};
+merge_trace_items([N1=#titem{id=NewP}|Ns], Old)
+ when NewP==new_processes; NewP==new_ports ->
+ {Ids, New, Changed} = merge_trace_items_1(Ns,Old),
+ {[N1|Ids], [N1|New], Changed};
+merge_trace_items(Ns, [N2=#titem{id=NewP}|Old])
+ when NewP==new_processes; NewP==new_ports ->
+ {Ids, New, Changed} = merge_trace_items_1(Ns,Old),
+ {[N2|Ids], New, Changed};
+merge_trace_items(New, Old) ->
+ merge_trace_items_1(New, Old).
+
+merge_trace_items_1(New, Old) ->
+ merge(lists:sort(New), Old, #titem.id, [], [], []).
merge_patterns(New, Old) ->
merge(lists:sort(New), Old, #tpattern.fa, [], [], []).
@@ -676,10 +987,12 @@ create_logwindow(Parent, true) ->
wxFrame:show(LogWin),
{LogWin, Text}.
-setup_ttb(TPs, TPids) ->
+setup_ttb(TPs, TPids, TPorts) ->
_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,
+ _R2 = [ttb:p(Pid, dbg_flags(proc,Flags)) ||
+ #titem{id=Pid, opts=Flags} <- TPids],
+ _R3 = [ttb:p(Port, dbg_flags(port,Flags)) ||
+ #titem{id=Port, opts=Flags} <- TPorts],
ok.
%% Sigh order is important
@@ -695,20 +1008,24 @@ setup_tps([First|Rest], Prev) ->
setup_tps([], Prev) ->
[setup_tp(TP) || TP <- lists:reverse(Prev)].
+setup_tp(#tpattern{m='Events',fa=Event, ms=#match_spec{term=Ms}}) ->
+ ttb:tpe(Event,Ms);
setup_tp(#tpattern{m=M,fa={F,A}, ms=#match_spec{term=Ms}}) ->
ttb:tpl(M,F,A,Ms).
-dbg_flags(Flags) ->
- [dbg_flag(Flag) || Flag <- Flags].
+dbg_flags(Type,Flags) ->
+ [dbg_flag(Type,Flag) || Flag <- Flags].
-dbg_flag(send) -> s;
-dbg_flag('receive') -> r;
-dbg_flag(functions) -> c;
-dbg_flag(on_spawn) -> sos;
-dbg_flag(on_link) -> sol;
-dbg_flag(on_first_spawn) -> sofs;
-dbg_flag(on_first_link) -> sofl;
-dbg_flag(events) -> p.
+dbg_flag(_,send) -> s;
+dbg_flag(_,'receive') -> r;
+dbg_flag(proc,functions) -> c;
+dbg_flag(proc,on_spawn) -> sos;
+dbg_flag(proc,on_link) -> sol;
+dbg_flag(proc,on_first_spawn) -> sofs;
+dbg_flag(proc,on_first_link) -> sofl;
+dbg_flag(proc,events) -> p;
+dbg_flag(port,events) -> ports;
+dbg_flag(_,Flag) -> Flag.
textformat(Trace) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 ->
format_trace(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace));
@@ -784,23 +1101,28 @@ ftup(Trace, Index, Size) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-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])
- end,
- FormatTP = fun({Module, FTPs}) ->
- List = format_ftp(FTPs, FormatMS),
- io_lib:format("{tp, ~w, [~s]}.~n",[Module, List])
+write_file(Frame, Filename, ProcFlags, PortFlags, MatchSpecs, Output, TPs) ->
+ MSToList = fun(#match_spec{name=Id, term=T, func=F}) ->
+ [{name,Id},{term,T},{func,F}]
end,
+ MSTermList = [{ms,Key,[MSToList(MS) || MS <- MSs]} ||
+ {Key,MSs} <- MatchSpecs],
+ TPToTuple = fun(#tpattern{fa={F,A}, ms=Ms}) ->
+ {F,A,MSToList(Ms)}
+ end,
+ ModuleTermList = [{tp, Module, [TPToTuple(FTP) || FTP <- FTPs]} ||
+ {Module,FTPs} <- TPs],
+
Str =
["%%%\n%%% This file is generated by Observer\n",
"%%%\n%%% DO NOT EDIT!\n%%%\n",
- [["{ms, ", FormatMS(Ms), "}.\n"] || Ms <- MatchSpecs],
- "{traceopts, ", io_lib:format("~w",[TraceOps]) ,"}.\n",
- "{output, ", io_lib:format("~w",[Output]) ,"}.\n",
- [FormatTP(TP) || TP <- TPs]
+ [io_lib:format("~p.~n",[MSTerm]) || MSTerm <- MSTermList],
+ io_lib:format("~p.~n",[{procflags,ProcFlags}]),
+ io_lib:format("~p.~n",[{portflags,PortFlags}]),
+ io_lib:format("~p.~n",[{output,Output}]),
+ [io_lib:format("~p.~n",[ModuleTerm]) || ModuleTerm <- ModuleTermList]
],
+
case file:write_file(Filename, list_to_binary(Str)) of
ok ->
success;
@@ -809,38 +1131,66 @@ write_file(Frame, Filename, TraceOps, MatchSpecs, Output, TPs) ->
observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR)
end.
-format_ftp([#tpattern{fa={F,A}, ms=Ms}], FormatMS) ->
- io_lib:format("{~w, ~w, ~s}", [F,A,FormatMS(Ms)]);
-format_ftp([#tpattern{fa={F,A}, ms=Ms}|Rest], FormatMS) ->
- [io_lib:format("{~w, ~w, ~s},~n ", [F,A,FormatMS(Ms)]),
- format_ftp(Rest, FormatMS)].
-
-read_settings(Filename, #state{match_specs=Ms0, def_trace_opts=TO0} = State) ->
+read_settings(Filename, #state{match_specs=Ms0, def_proc_flags=ProcFs0, def_port_flags=PortFs0} = State) ->
case file:consult(Filename) of
{ok, Terms} ->
- Ms = lists:usort(Ms0 ++ [parse_ms(MsList) || {ms, MsList} <- Terms]),
- TOs = lists:usort(TO0 ++ proplists:get_value(traceopts, Terms, [])),
+ Ms = parse_ms(Terms, Ms0),
+ ProcFs1 = proplists:get_value(procflags, Terms, []) ++
+ proplists:get_value(traceopts, Terms, []), % for backwards comp.
+ ProcFs = lists:usort(ProcFs0 ++ ProcFs1),
+ PortFs = lists:usort(PortFs0 ++
+ proplists:get_value(portflags, Terms, [])),
Out = proplists:get_value(output, Terms, []),
lists:foldl(fun parse_tp/2,
- State#state{match_specs=Ms, def_trace_opts=TOs, output=Out},
+ State#state{match_specs=Ms, def_proc_flags=ProcFs,
+ def_port_flags=PortFs, output=Out},
Terms);
{error, _} ->
- observer_wx:create_txt_dialog(State#state.panel, "Could not load settings",
+ observer_wx:create_txt_dialog(State#state.panel,
+ "Could not load settings",
"Error", ?wxICON_ERROR),
State
end.
-parse_ms(Opts) ->
- Name = proplists:get_value(name, Opts, "TracePattern"),
- Term = proplists:get_value(term, Opts, [{'_',[],[ok]}]),
- FunStr = proplists:get_value(term, Opts, "fun(_) -> ok end"),
- make_ms(Name, Term, FunStr).
+parse_ms(Terms, OldMSs) ->
+ MSs =
+ case [{Key,[make_ms(MS) || MS <- MSs]} || {ms,Key,MSs} <- Terms] of
+ [] ->
+ case [make_ms(MS) || {ms,MS} <- Terms] of
+ [] ->
+ [];
+ FuncMSs -> % for backwards compatibility
+ [{funcs,FuncMSs}]
+ end;
+ KeyMSs ->
+ KeyMSs
+ end,
+ parse_ms_1(MSs, dict:from_list(OldMSs)).
+
+parse_ms_1([{Key,MSs} | T], Dict) ->
+ parse_ms_1(T, dict:append_list(Key,MSs,Dict));
+parse_ms_1([],Dict) ->
+ [{Key,rm_dups(MSs,[])} || {Key,MSs} <- dict:to_list(Dict)].
+
+rm_dups([H|T],Acc) ->
+ case lists:member(H,Acc) of
+ true ->
+ rm_dups(T,Acc);
+ false ->
+ rm_dups(T,[H|Acc])
+ end;
+rm_dups([],Acc) ->
+ lists:reverse(Acc).
+
+make_ms(MS) ->
+ [{func,FunStr},{name,Name},{term,Term}] = lists:keysort(1,MS),
+ make_ms(Name,Term,FunStr).
make_ms(Name, Term, FunStr) ->
#match_spec{name=Name, term=Term, str=io_lib:format("~w", Term), func = FunStr}.
parse_tp({tp, Mod, FAs}, State) ->
- Patterns = [#tpattern{m=Mod,fa={F,A}, ms=parse_ms(List)} ||
+ Patterns = [#tpattern{m=Mod,fa={F,A}, ms=make_ms(List)} ||
{F,A,List} <- FAs],
do_add_patterns({Mod, Patterns}, State);
parse_tp(_, State) ->
@@ -866,3 +1216,9 @@ get_indecies(Rest = [_|_], I, [_|T]) ->
get_indecies(Rest, I+1, T);
get_indecies(_, _, _) ->
[].
+
+create_right_click_menu(Panel,Menus) ->
+ Menu = wxMenu:new(),
+ [wxMenu:append(Menu,Id,Str) || {Id,Str} <- Menus],
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu).
diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl
index 9ba9b72b6f..285c298c4b 100644
--- a/lib/observer/src/observer_traceoptions_wx.erl
+++ b/lib/observer/src/observer_traceoptions_wx.erl
@@ -22,8 +22,8 @@
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
--export([process_trace/2, trace_pattern/4, select_nodes/2,
- output/2, select_matchspec/3]).
+-export([process_trace/2, port_trace/2, trace_pattern/4, select_nodes/2,
+ output/2, select_matchspec/4]).
process_trace(Parent, Default) ->
Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options",
@@ -36,12 +36,20 @@ process_trace(Parent, Default) ->
FuncBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace function call", []),
check_box(FuncBox, lists:member(functions, Default)),
+ ArityBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace arity instead of arguments", []),
+ check_box(ArityBox, lists:member(functions, Default)),
SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []),
check_box(SendBox, lists:member(send, Default)),
RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []),
check_box(RecBox, lists:member('receive', Default)),
EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace process events", []),
check_box(EventBox, lists:member(events, Default)),
+ SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of processes", []),
+ check_box(SchedBox, lists:member(running_procs, Default)),
+ ExitBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of exiting processes", []),
+ check_box(ExitBox, lists:member(exiting, Default)),
+ GCBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace garbage collections", []),
+ check_box(GCBox, lists:member(garbage_collection, Default)),
{SpawnBox, SpwnAllRadio, SpwnFirstRadio} =
optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"),
@@ -57,7 +65,7 @@ process_trace(Parent, 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, CheckBox, []) || CheckBox <- [FuncBox,ArityBox,SendBox,RecBox,EventBox,SchedBox,ExitBox,GCBox]],
wxSizer:add(LeftSz, 150, -1),
wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}, {proportion,1}]),
@@ -80,7 +88,9 @@ process_trace(Parent, Default) ->
case wxDialog:showModal(Dialog) of
?wxID_OK ->
All = [{SendBox, send}, {RecBox, 'receive'},
- {FuncBox, functions}, {EventBox, events},
+ {FuncBox, functions}, {ArityBox, arity},
+ {EventBox, events}, {SchedBox, running_procs},
+ {ExitBox, exiting}, {GCBox, garbage_collection},
{{SpawnBox, SpwnAllRadio}, on_spawn},
{{SpawnBox,SpwnFirstRadio}, on_first_spawn},
{{LinkBox, LinkAllRadio}, on_link},
@@ -98,12 +108,57 @@ process_trace(Parent, Default) ->
throw(cancel)
end.
+port_trace(Parent, Default) ->
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, "Port Options",
+ [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]),
+ Panel = wxPanel:new(Dialog),
+ MainSz = wxBoxSizer:new(?wxVERTICAL),
+ OptsSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Tracing options"}]),
+
+ SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []),
+ check_box(SendBox, lists:member(send, Default)),
+ RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []),
+ check_box(RecBox, lists:member('receive', Default)),
+ EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace port events", []),
+ check_box(EventBox, lists:member(events, Default)),
+ SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of ports", []),
+ check_box(SchedBox, lists:member(running_ports, Default)),
+
+ [wxSizer:add(OptsSz, CheckBox, []) || CheckBox <- [SendBox,RecBox,EventBox,SchedBox]],
+ wxSizer:add(OptsSz, 150, -1),
+
+ wxPanel:setSizer(Panel, OptsSz),
+ wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]),
+ 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 ->
+ All = [{SendBox, send}, {RecBox, 'receive'},
+ {EventBox, events}, {SchedBox, running_ports}],
+ Opts = [Id || {Tick, Id} <- All, wxCheckBox:getValue(Tick)],
+ wxDialog:destroy(Dialog),
+ lists:reverse(Opts);
+ ?wxID_CANCEL ->
+ wxDialog:destroy(Dialog),
+ throw(cancel)
+ end.
+
trace_pattern(ParentPid, Parent, Node, MatchSpecs) ->
try
- Module = module_selector(Parent, Node),
- MFAs = function_selector(Parent, Node, Module),
- MatchSpec = select_matchspec(ParentPid, Parent, MatchSpecs),
- {Module, [#tpattern{m=M,fa={F,A},ms=MatchSpec} || {M,F,A} <- MFAs]}
+ {Module,MFAs,MatchSpec} =
+ case module_selector(Parent, Node) of
+ {'$trace_event',Event} ->
+ MS = select_matchspec(ParentPid, Parent, MatchSpecs, Event),
+ {'Events',[{'Events',Event}],MS};
+ Mod ->
+ MFAs0 = function_selector(Parent, Node, Mod),
+ MS = select_matchspec(ParentPid, Parent, MatchSpecs, funcs),
+ {Mod,MFAs0,MS}
+ end,
+ {Module, [#tpattern{m=M,fa=FA,ms=MatchSpec} || {M,FA} <- MFAs]}
catch cancel -> cancel
end.
@@ -112,7 +167,7 @@ select_nodes(Parent, Nodes) ->
check_selector(Parent, Choices).
module_selector(Parent, Node) ->
- Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module",
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module or Event",
[{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
{size, {400, 400}}]),
Panel = wxPanel:new(Dialog),
@@ -136,7 +191,9 @@ module_selector(Parent, Node) ->
wxWindow:setFocus(TxtCtrl),
%% init data
Modules = get_modules(Node),
- AllModules = [{atom_to_list(X), X} || X <- Modules],
+ Events = [{"Messages sent",{'$trace_event',send}},
+ {"Messages received",{'$trace_event','receive'}}],
+ AllModules = Events ++ [{atom_to_list(X), X} || X <- Modules],
filter_listbox_data("", AllModules, ListBox),
wxTextCtrl:connect(TxtCtrl, command_text_updated,
[{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) ->
@@ -174,9 +231,9 @@ function_selector(Parent, Node, Module) ->
not(erl_internal:guard_bif(Name, Arity))]),
ParsedChoices = parse_function_names(Choices),
case check_selector(Parent, ParsedChoices) of
- [] -> [{Module, '_', '_'}];
+ [] -> [{Module, {'_', '_'}}];
FAs ->
- [{Module, F, A} || {F,A} <- FAs]
+ [{Module, {F, A}} || {F,A} <- FAs]
end.
check_selector(Parent, ParsedChoices) ->
@@ -268,7 +325,12 @@ get_checked(ListBox, Acc) ->
lists:reverse(Acc)
end.
-select_matchspec(Pid, Parent, MatchSpecs) ->
+select_matchspec(Pid, Parent, AllMatchSpecs, Key) ->
+ {MatchSpecs,RestMS} =
+ case lists:keytake(Key,1,AllMatchSpecs) of
+ {value,{Key,MSs0},Rest} -> {MSs0,Rest};
+ false -> {[],AllMatchSpecs}
+ end,
Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Match Specifications",
[{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
{size, {400, 400}}]),
@@ -313,8 +375,12 @@ select_matchspec(Pid, Parent, MatchSpecs) ->
filter_listbox_data("", Choices, ListBox),
Add = fun(_,_) ->
- case edit_ms(TextCtrl, new, Parent) of
- Ms = #match_spec{} -> add_and_select(-1, Ms, ListBox);
+ case edit_ms(TextCtrl, new, Dialog) of
+ Ms = #match_spec{} ->
+ add_and_select(-1, Ms, ListBox),
+ wxWindow:enable(OkButt),
+ wxWindow:enable(EditMsBtn),
+ wxWindow:enable(DelMsBtn);
Else -> Else
end
end,
@@ -323,8 +389,12 @@ select_matchspec(Pid, Parent, MatchSpecs) ->
case SelId >= 0 of
true ->
#match_spec{name=Name} = wxListBox:getClientData(ListBox,SelId),
- case edit_ms(TextCtrl, Name, Parent) of
- Ms = #match_spec{} -> add_and_select(SelId, Ms, ListBox);
+ case edit_ms(TextCtrl, Name, Dialog) of
+ Ms = #match_spec{} ->
+ add_and_select(SelId, Ms, ListBox),
+ wxWindow:enable(OkButt),
+ wxWindow:enable(EditMsBtn),
+ wxWindow:enable(DelMsBtn);
Else -> Else
end;
false ->
@@ -367,7 +437,7 @@ select_matchspec(Pid, Parent, MatchSpecs) ->
Count = wxListBox:getCount(ListBox),
MSs = [wxListBox:getClientData(ListBox, Id) ||
Id <- lists:seq(0, Count-1)],
- Pid ! {update_ms, MSs},
+ Pid ! {update_ms, [{Key,MSs}|RestMS]},
MS = lists:nth(SelId+1, MSs),
wxDialog:destroy(Dialog),
MS;
diff --git a/lib/observer/src/observer_tv.hrl b/lib/observer/src/observer_tv.hrl
index 8d57a6fa2b..318a989750 100644
--- a/lib/observer/src/observer_tv.hrl
+++ b/lib/observer/src/observer_tv.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl
index de498ff442..bf2afee2a5 100644
--- a/lib/observer/src/observer_tv_wx.erl
+++ b/lib/observer/src/observer_tv_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -37,7 +37,8 @@
-define(ID_UNREADABLE, 405).
-define(ID_SYSTEM_TABLES, 406).
-define(ID_TABLE_INFO, 407).
-
+-define(ID_SHOW_TABLE, 408).
+
-record(opt, {type=ets,
sys_hidden=true,
unread_hidden=true,
@@ -49,6 +50,7 @@
{
parent,
grid,
+ panel,
node=node(),
opt=#opt{},
selected,
@@ -86,12 +88,13 @@ init([Notebook, Parent]) ->
wxListItem:destroy(Li),
wxListCtrl:connect(Grid, command_list_item_activated),
+ wxListCtrl:connect(Grid, command_list_item_right_click),
wxListCtrl:connect(Grid, command_list_item_selected),
wxListCtrl:connect(Grid, command_list_col_click),
wxListCtrl:connect(Grid, size, [{skip, true}]),
wxWindow:setFocus(Grid),
- {Panel, #state{grid=Grid, parent=Parent, timer={false, 10}}}.
+ {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}.
handle_event(#wx{id=?ID_REFRESH},
State = #state{node=Node, grid=Grid, opt=Opt}) ->
@@ -121,6 +124,8 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0})
end,
case get_tables2(Node, Opt) of
Error = {error, _} ->
+ Id =:= ?ID_MNESIA andalso
+ wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true),
self() ! Error,
{noreply, State};
Tables ->
@@ -145,6 +150,16 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated,
end,
{noreply, State};
+handle_event(#wx{event=#wxList{type=command_list_item_right_click}},
+ State=#state{panel=Panel}) ->
+
+ Menu = wxMenu:new(),
+ wxMenu:append(Menu, ?ID_TABLE_INFO, "Table info"),
+ wxMenu:append(Menu, ?ID_SHOW_TABLE, "Show Table Content"),
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu),
+ {noreply, State};
+
handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}},
State) ->
{noreply, State#state{selected=Index}};
@@ -160,6 +175,22 @@ handle_event(#wx{id=?ID_TABLE_INFO},
{noreply, State}
end;
+handle_event(#wx{id=?ID_SHOW_TABLE},
+ State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) ->
+ case Sel of
+ undefined ->
+ {noreply, State};
+ R when is_integer(R) ->
+ Table = lists:nth(Sel+1, Tabs),
+ case Table#tab.protection of
+ private ->
+ self() ! {error, "Table has 'private' protection and can not be read"};
+ _ ->
+ observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}])
+ end,
+ {noreply, State}
+ end;
+
handle_event(#wx{id=?ID_REFRESH_INTERVAL},
State = #state{grid=Grid, timer=Timer0}) ->
Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60),
@@ -188,22 +219,33 @@ handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt,
{noreply, State#state{tabs=Tabs}}
end;
-handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt,
+handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt0,
timer=Timer0}) ->
- Tables = get_tables(Node, Opt),
+ {Tables, Opt} = case Opt0#opt.type =:= mnesia andalso get_tables2(Node, Opt0) of
+ Ts when is_list(Ts) ->
+ {Ts, Opt0};
+ _ -> % false or error getting mnesia tables
+ Opt1 = Opt0#opt{type=ets},
+ {get_tables(Node, Opt1), Opt1}
+ end,
Tabs = update_grid(Grid, Opt, Tables),
wxWindow:setFocus(Grid),
create_menus(Parent, Opt),
Timer = observer_lib:start_timer(Timer0),
- {noreply, State#state{node=Node, tabs=Tabs, timer=Timer}};
+ {noreply, State#state{node=Node, tabs=Tabs, timer=Timer, opt=Opt}};
handle_info(not_active, State = #state{timer = Timer0}) ->
Timer = observer_lib:stop_timer(Timer0),
{noreply, State#state{timer=Timer}};
-handle_info({error, Error}, State) ->
- handle_error(Error),
- {noreply, State};
+handle_info({error, Error}, #state{panel=Panel,opt=Opt}=State) ->
+ Str = io_lib:format("ERROR: ~s~n",[Error]),
+ observer_lib:display_info_dialog(Panel,Str),
+ case Opt#opt.type of
+ mnesia -> wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true);
+ _ -> ok
+ end,
+ {noreply, State#state{opt=Opt#opt{type=ets}}};
handle_info(_Event, State) ->
{noreply, State}.
@@ -315,6 +357,7 @@ display_table_info(Parent0, Node, Source, Table) ->
{_, Sizer, _} = observer_lib:display_info(Frame, [IdInfo,Settings,Memory]),
wxSizer:setSizeHints(Sizer, Frame),
+ wxWindow:setMinSize(Frame, {300, -1}),
wxFrame:center(Frame),
wxFrame:show(Frame).
@@ -323,10 +366,6 @@ list_to_strings([A]) -> integer_to_list(A);
list_to_strings([A|B]) ->
integer_to_list(A) ++ " ," ++ list_to_strings(B).
-handle_error(Foo) ->
- Str = io_lib:format("ERROR: ~s~n",[Foo]),
- observer_lib:display_info_dialog(Str).
-
update_grid(Grid, Opt, Tables) ->
wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end).
update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) ->
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index 68e814e01a..738a00180f 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,8 +21,8 @@
-behaviour(wx_object).
-export([start/0, stop/0]).
--export([create_menus/2, get_attrib/1, get_tracer/0, set_status/1,
- create_txt_dialog/4, try_rpc/4, return_to_localnode/2]).
+-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/0,
+ set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]).
-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
handle_call/3, handle_info/2, check_page_title/1]).
@@ -55,6 +55,7 @@
notebook,
main_panel,
pro_panel,
+ port_panel,
tv_panel,
sys_panel,
trace_panel,
@@ -65,7 +66,8 @@
node,
nodes,
prev_node="",
- log = false
+ log = false,
+ reply_to=false
}).
start() ->
@@ -89,6 +91,12 @@ set_status(What) ->
get_tracer() ->
wx_object:call(observer, get_tracer).
+get_active_node() ->
+ wx_object:call(observer, get_active_node).
+
+get_menubar() ->
+ wx_object:call(observer, get_menubar).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init(_Args) ->
@@ -164,6 +172,10 @@ setup(#state{frame = Frame} = State) ->
ProPanel = observer_pro_wx:start_link(Notebook, self()),
wxNotebook:addPage(Notebook, ProPanel, "Processes", []),
+ %% Port Panel
+ PortPanel = observer_port_wx:start_link(Notebook, self()),
+ wxNotebook:addPage(Notebook, PortPanel, "Ports", []),
+
%% Table Viewer Panel
TVPanel = observer_tv_wx:start_link(Notebook, self()),
wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []),
@@ -187,6 +199,7 @@ setup(#state{frame = Frame} = State) ->
status_bar = StatusBar,
sys_panel = SysPanel,
pro_panel = ProPanel,
+ port_panel = PortPanel,
tv_panel = TVPanel,
trace_panel = TracePanel,
app_panel = AppPanel,
@@ -229,14 +242,13 @@ handle_event(#wx{id = ?ID_CDV, event = #wxCommand{type = command_menu_selected}}
spawn(crashdump_viewer, start, []),
{noreply, State};
-handle_event(#wx{event = #wxClose{}}, #state{log=LogOn} = State) ->
- LogOn andalso rpc:block_call(State#state.node, rb, stop, []),
- {stop, normal, State};
+handle_event(#wx{event = #wxClose{}}, State) ->
+ stop_servers(State),
+ {noreply, State};
-handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}},
- #state{log=LogOn} = State) ->
- LogOn andalso rpc:block_call(State#state.node, rb, stop, []),
- {stop, normal, State};
+handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) ->
+ stop_servers(State),
+ {noreply, State};
handle_event(#wx{id = ?wxID_HELP, event = #wxCommand{type = command_menu_selected}}, State) ->
External = "http://www.erlang.org/doc/apps/observer/index.html",
@@ -379,9 +391,15 @@ handle_call({get_attrib, Attrib}, _From, State) ->
handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) ->
{reply, TraceP, State};
-handle_call(stop, _, State = #state{frame = Frame}) ->
- wxFrame:destroy(Frame),
- {stop, normal, ok, State};
+handle_call(get_active_node, _From, State=#state{node=Node}) ->
+ {reply, Node, State};
+
+handle_call(get_menubar, _From, State=#state{menubar=MenuBar}) ->
+ {reply, MenuBar, State};
+
+handle_call(stop, From, State) ->
+ stop_servers(State),
+ {noreply, State#state{reply_to=From}};
handle_call(log_status, _From, State) ->
{reply, State#state.log, State};
@@ -406,16 +424,21 @@ handle_info({nodedown, Node},
create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION),
{noreply, State3};
-handle_info({open_link, Pid0}, State = #state{pro_panel=ProcViewer, frame=Frame}) ->
- Pid = case Pid0 of
- [_|_] -> try list_to_pid(Pid0) catch _:_ -> Pid0 end;
- _ -> Pid0
+handle_info({open_link, Id0}, State = #state{pro_panel=ProcViewer,
+ port_panel=PortViewer,
+ frame=Frame}) ->
+ Id = case Id0 of
+ [_|_] -> try list_to_pid(Id0) catch _:_ -> Id0 end;
+ _ -> Id0
end,
%% Forward to process tab
- case is_pid(Pid) of
- true -> wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid};
- false ->
- Msg = io_lib:format("Information about ~p is not available or implemented",[Pid]),
+ case Id of
+ Pid when is_pid(Pid) ->
+ wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid};
+ "#Port" ++ _ = Port ->
+ wx_object:get_pid(PortViewer) ! {portinfo_open, Port};
+ _ ->
+ Msg = io_lib:format("Information about ~p is not available or implemented",[Id]),
Info = wxMessageDialog:new(Frame, Msg),
wxMessageDialog:showModal(Info),
wxMessageDialog:destroy(Info)
@@ -426,17 +449,45 @@ handle_info({get_debug_info, From}, State = #state{notebook=Notebook, active_tab
From ! {observer_debug, wx:get_env(), Notebook, Pid},
{noreply, State};
-handle_info({'EXIT', Pid, _Reason}, State) ->
- io:format("Child (~s) crashed exiting: ~p ~p~n",
- [pid2panel(Pid, State), Pid,_Reason]),
+handle_info({'EXIT', Pid, Reason}, State) ->
+ case Reason of
+ normal ->
+ {noreply, State};
+ _ ->
+ io:format("Observer: Child (~s) crashed exiting: ~p ~p~n",
+ [pid2panel(Pid, State), Pid, Reason]),
+ {stop, normal, State}
+ end;
+
+handle_info({stop, Me}, State) when Me =:= self() ->
{stop, normal, State};
handle_info(_Info, State) ->
{noreply, State}.
-terminate(_Reason, #state{frame = Frame}) ->
+stop_servers(#state{node=Node, log=LogOn, sys_panel=Sys, pro_panel=Procs, tv_panel=TVs,
+ trace_panel=Trace, app_panel=Apps, perf_panel=Perfs,
+ allc_panel=Alloc, port_panel=Ports} = _State) ->
+ LogOn andalso rpc:block_call(Node, rb, stop, []),
+ Me = self(),
+ Tabs = [Sys, Procs, Ports, TVs, Trace, Apps, Perfs, Alloc],
+ Stop = fun() ->
+ try
+ _ = [wx_object:stop(Panel) || Panel <- Tabs],
+ ok
+ catch _:_ -> ok
+ end,
+ Me ! {stop, Me}
+ end,
+ spawn(Stop).
+
+terminate(_Reason, #state{frame = Frame, reply_to=From}) ->
wxFrame:destroy(Frame),
wx:destroy(),
+ case From of
+ false -> ignore;
+ _ -> gen_server:reply(From, ok)
+ end,
ok.
code_change(_, _, State) ->
@@ -513,10 +564,11 @@ check_page_title(Notebook) ->
get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys,
tv_panel=Tv, trace_panel=Trace, app_panel=App,
- perf_panel=Perf, allc_panel=Alloc
+ perf_panel=Perf, allc_panel=Alloc, port_panel=Port
}) ->
Panel = case check_page_title(Notebook) of
"Processes" -> Pro;
+ "Ports" -> Port;
"System" -> Sys;
"Table Viewer" -> Tv;
?TRACE_STR -> Trace;
@@ -528,9 +580,10 @@ get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys,
pid2panel(Pid, #state{pro_panel=Pro, sys_panel=Sys,
tv_panel=Tv, trace_panel=Trace, app_panel=App,
- perf_panel=Perf, allc_panel=Alloc}) ->
+ perf_panel=Perf, allc_panel=Alloc, port_panel=Port}) ->
case Pid of
Pro -> "Processes";
+ Port -> "Ports";
Sys -> "System";
Tv -> "Table Viewer" ;
Trace -> ?TRACE_STR;
@@ -583,7 +636,8 @@ create_connect_dialog(connect, #state{frame = Frame}) ->
wxWindow:setSizerAndFit(Dialog, VSizer),
wxSizer:setSizeHints(VSizer, Dialog),
- CookiePath = filename:join(os:getenv("HOME"), ".erlang.cookie"),
+ {ok,[[HomeDir]]} = init:get_argument(home),
+ CookiePath = filename:join(HomeDir, ".erlang.cookie"),
DefaultCookie = case filelib:is_file(CookiePath) of
true ->
{ok, Bin} = file:read_file(CookiePath),
diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl
index 3cba3b97b0..87a50e046b 100644
--- a/lib/observer/src/ttb.erl
+++ b/lib/observer/src/ttb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,7 +25,8 @@
-export([tracer/0,tracer/1,tracer/2,p/2,stop/0,stop/1,start_trace/4]).
-export([get_et_handler/0]).
-export([tp/2, tp/3, tp/4, ctp/0, ctp/1, ctp/2, ctp/3, tpl/2, tpl/3, tpl/4,
- ctpl/0, ctpl/1, ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3]).
+ ctpl/0, ctpl/1, ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3,
+ tpe/2, ctpe/1]).
-export([seq_trigger_ms/0,seq_trigger_ms/1]).
-export([write_trace_info/2]).
-export([write_config/2,write_config/3,run_config/1,run_config/2,list_config/1]).
@@ -77,6 +78,11 @@ do_tracer(Nodes0,PI,Client,Traci) ->
do_tracer(Clients,PI,Traci) ->
Shell = proplists:get_value(shell, Traci, false),
+ IpPortSpec =
+ case proplists:get_value(queue_size, Traci) of
+ undefined -> 0;
+ QS -> {0,QS}
+ end,
DefShell = fun(Trace) -> dbg:dhandler(Trace, standard_io) end,
{ClientSucc,Succ} =
lists:foldl(
@@ -97,7 +103,7 @@ do_tracer(Clients,PI,Traci) ->
[_,H] = string:tokens(atom_to_list(N),"@"),
H
end,
- case catch dbg:tracer(N,port,dbg:trace_port(ip,0)) of
+ case catch dbg:tracer(N,port,dbg:trace_port(ip,IpPortSpec)) of
{ok,N} ->
{ok,Port} = dbg:trace_port_control(N,get_listen_port),
{ok,T} = dbg:get_tracer(N),
@@ -159,6 +165,8 @@ opt([{resume,MSec}|O],{PI,Client,Traci}) ->
opt(O,{PI,Client,[{resume, {true, MSec}}|Traci]});
opt([{flush,MSec}|O],{PI,Client,Traci}) ->
opt(O,{PI,Client,[{flush, MSec}|Traci]});
+opt([{queue_size,QueueSize}|O],{PI,Client,Traci}) ->
+ opt(O,{PI,Client,[{queue_size,QueueSize}|Traci]});
opt([],Opt) ->
ensure_opt(Opt).
@@ -397,16 +405,16 @@ arg_list([A1|A],Acc) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Set trace flags on processes
-p(Procs0,Flags0) ->
+p(ProcsPorts0,Flags0) ->
ensure_no_overloaded_nodes(),
- store(p,[Procs0,Flags0]),
- no_store_p(Procs0,Flags0).
-no_store_p(Procs0,Flags0) ->
+ store(p,[ProcsPorts0,Flags0]),
+ no_store_p(ProcsPorts0,Flags0).
+no_store_p(ProcsPorts0,Flags0) ->
case transform_flags(to_list(Flags0)) of
{error,Reason} ->
{error,Reason};
Flags ->
- Procs = procs(Procs0),
+ ProcsPorts = procs_ports(ProcsPorts0),
case lists:foldl(fun(P,{PMatched,Ps}) -> case dbg:p(P,Flags) of
{ok,Matched} ->
{[{P,Matched}|PMatched],[P|Ps]};
@@ -414,7 +422,7 @@ no_store_p(Procs0,Flags0) ->
display_warning(P,Reason),
{PMatched,Ps}
end
- end,{[],[]},Procs) of
+ end,{[],[]},ProcsPorts) of
{[],[]} -> {error, no_match};
{SuccMatched,Succ} ->
no_store_write_trace_info(flags,{Succ,Flags}),
@@ -429,18 +437,22 @@ transform_flags(Flags) ->
dbg:transform_flags([timestamp | Flags]).
-procs(Procs) when is_list(Procs) ->
- lists:foldl(fun(P,Acc) -> proc(P)++Acc end,[],Procs);
-procs(Proc) ->
- proc(Proc).
+procs_ports(Procs) when is_list(Procs) ->
+ lists:foldl(fun(P,Acc) -> proc_port(P)++Acc end,[],Procs);
+procs_ports(Proc) ->
+ proc_port(Proc).
-proc(Procs) when Procs=:=all; Procs=:=existing; Procs=:=new ->
- [Procs];
-proc(Name) when is_atom(Name) ->
+proc_port(P) when P=:=all; P=:=ports; P=:=processes;
+ P=:=existing; P=:=existing_ports; P=:=existing_processes;
+ P=:=new; P=:=new_ports; P=:=new_processes ->
+ [P];
+proc_port(Name) when is_atom(Name) ->
[Name]; % can be registered on this node or other node
-proc(Pid) when is_pid(Pid) ->
+proc_port(Pid) when is_pid(Pid) ->
[Pid];
-proc({global,Name}) ->
+proc_port(Port) when is_port(Port) ->
+ [Port];
+proc_port({global,Name}) ->
case global:whereis_name(Name) of
Pid when is_pid(Pid) ->
[Pid];
@@ -477,6 +489,11 @@ tpl(A,B,C,D) ->
store(tpl,[A,B,C,ms(D)]),
dbg:tpl(A,B,C,ms(D)).
+tpe(A,B) ->
+ ensure_no_overloaded_nodes(),
+ store(tpe,[A,ms(B)]),
+ dbg:tpe(A,ms(B)).
+
ctp() ->
store(ctp,[]),
dbg:ctp().
@@ -516,6 +533,10 @@ ctpg(A,B,C) ->
store(ctpg,[A,B,C]),
dbg:ctpg(A,B,C).
+ctpe(A) ->
+ store(ctpe,[A]),
+ dbg:ctpe(A).
+
ms(return) ->
[{'_',[],[{return_trace}]}];
ms(caller) ->
@@ -1296,6 +1317,9 @@ ip_to_file(Trace, {shell_only, Fun} = State) ->
ip_to_file(Trace,{{file,File}, ShellOutput}) ->
Fun = dbg:trace_port(file,File), %File can be a filename or a wrap spec
Port = Fun(),
+ %% Just in case this is on the traced node,
+ %% make sure the port is not traced.
+ p(Port,clear),
%% Store the port so it can be properly closed
?MODULE ! {ip_to_file_trace_port, Port, self()},
receive {?MODULE,ok} -> ok end,
diff --git a/lib/observer/src/ttb_et.erl b/lib/observer/src/ttb_et.erl
index 683dd6c9a2..95e8e9aa07 100644
--- a/lib/observer/src/ttb_et.erl
+++ b/lib/observer/src/ttb_et.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/observer/test/Makefile b/lib/observer/test/Makefile
index e8bb7d0a52..6100af5e17 100644
--- a/lib/observer/test/Makefile
+++ b/lib/observer/test/Makefile
@@ -45,8 +45,8 @@ RELSYSDIR = $(RELEASE_PATH)/observer_test
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-ERL_MAKE_FLAGS += -pa $(ERL_TOP)/lib/test_server/ebin
-ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include
+ERL_MAKE_FLAGS +=
+ERL_COMPILE_FLAGS +=
EBIN = .
@@ -60,8 +60,6 @@ make_emakefile:
$(MODULES) > $(EMAKEFILE)
tests debug opt: make_emakefile
- cd $(ERL_TOP)/lib/test_server/src && \
- $(MAKE) ../ebin/test_server_line.beam
erl $(ERL_MAKE_FLAGS) -make
clean:
diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl
index eea82d8c3c..4239a3d0d1 100644
--- a/lib/observer/test/crashdump_helper.erl
+++ b/lib/observer/test/crashdump_helper.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
-module(crashdump_helper).
-export([n1_proc/2,remote_proc/2]).
-compile(r13).
--include("test_server.hrl").
+-include_lib("common_test/include/ct.hrl").
n1_proc(N2,Creator) ->
spawn(fun() -> n1_proc(Creator,N2,x,y,[]) end).
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index eae4ee01b9..8df69c6624 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
-export([init_per_testcase/2, end_per_testcase/2]).
-include_lib("common_test/include/ct.hrl").
--include("test_server_line.hrl").
-include_lib("kernel/include/file.hrl").
-define(failed_file,"failed-cases.txt").
@@ -102,7 +101,7 @@ end_per_group(_GroupName, Config) ->
init_per_suite(Config) when is_list(Config) ->
delete_saved(Config),
DataDir = ?config(data_dir,Config),
- Rels = [R || R <- [r16b,'17'], ?t:is_release_available(R)] ++ [current],
+ Rels = [R || R <- ['17','18'], ?t:is_release_available(R)] ++ [current],
io:format("Creating crash dumps for the following releases: ~p", [Rels]),
AllDumps = create_dumps(DataDir,Rels),
[{dumps,AllDumps}|Config].
@@ -421,6 +420,10 @@ special(File,Procs) ->
%% ".trunc" ->
%% %% ????
%% ok;
+ ".trunc.bytes" ->
+ {ok,_,[TW]} = crashdump_viewer:general_info(),
+ {match,_} = re:run(TW,"CRASH DUMP SIZE LIMIT REACHED"),
+ ok;
_ ->
ok
end,
@@ -482,7 +485,11 @@ do_create_dumps(DataDir,Rel) ->
current ->
CD3 = dump_with_args(DataDir,Rel,"instr","+Mim true"),
CD4 = dump_with_strange_module_name(DataDir,Rel,"strangemodname"),
- {[CD1,CD2,CD3,CD4], DosDump};
+ Bytes = rand:uniform(300000) + 100,
+ CD5 = dump_with_args(DataDir,Rel,"trunc.bytes",
+ "-env ERL_CRASH_DUMP_BYTES " ++
+ integer_to_list(Bytes)),
+ {[CD1,CD2,CD3,CD4,CD5], DosDump};
_ ->
{[CD1,CD2], DosDump}
end.
@@ -607,21 +614,21 @@ dos_dump(DataDir,Rel,Dump) ->
rel_opt(Rel) ->
case Rel of
- r16b -> [{erl,[{release,"r16b_latest"}]}];
'17' -> [{erl,[{release,"17_latest"}]}];
+ '18' -> [{erl,[{release,"18_latest"}]}];
current -> []
end.
dump_prefix(Rel) ->
case Rel of
- r16b -> "r16b_dump.";
'17' -> "r17_dump.";
- current -> "r18_dump."
+ '18' -> "r18_dump.";
+ current -> "r19_dump."
end.
compat_rel(Rel) ->
case Rel of
- r16b -> "+R16 ";
'17' -> "+R17 ";
+ '18' -> "+R18 ";
current -> ""
end.
diff --git a/lib/observer/test/etop_SUITE.erl b/lib/observer/test/etop_SUITE.erl
index d4857c5e2f..7614989f1f 100644
--- a/lib/observer/test/etop_SUITE.erl
+++ b/lib/observer/test/etop_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
-export([text/1,text/2,text_tracing_off/1,text_tracing_off/2]).
-export([init_per_testcase/2, end_per_testcase/2]).
--include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct.hrl").
-define(default_timeout, ?t:minutes(1)).
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index 7f96d72e59..41726b1521 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
%%
-module(observer_SUITE).
--include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct.hrl").
-include_lib("wx/include/wx.hrl").
-include_lib("observer/src/observer_tv.hrl").
@@ -34,11 +34,12 @@
%% Test cases
-export([app_file/1, appup_file/1,
- basic/1, process_win/1, table_win/1
+ basic/1, process_win/1, table_win/1,
+ port_win_when_tab_not_initiated/1
]).
%% Default timetrap timeout (set in init_per_testcase)
--define(default_timeout, ?t:minutes(1)).
+-define(default_timeout, ?t:minutes(2)).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -49,7 +50,8 @@ groups() ->
[{gui, [],
[basic,
process_win,
- table_win
+ table_win,
+ port_win_when_tab_not_initiated
]
}].
@@ -171,6 +173,7 @@ test_page("Applications" ++ _, _Window) ->
test_page("Processes" ++ _, _Window) ->
timer:sleep(500), %% Give it time to refresh
Active = get_active(),
+ Active ! refresh_interval,
ChangeSort = fun(N) ->
FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
Active ! FakeEv,
@@ -184,7 +187,23 @@ test_page("Processes" ++ _, _Window) ->
timer:sleep(1000), %% Give it time to refresh
ok;
-test_page(_Title = "Table" ++ _, _Window) ->
+test_page("Ports" ++ _, _Window) ->
+ timer:sleep(500), %% Give it time to refresh
+ Active = get_active(),
+ Active ! refresh_interval,
+ ChangeSort = fun(N) ->
+ FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
+ Active ! FakeEv,
+ timer:sleep(200)
+ end,
+ [ChangeSort(N) || N <- lists:seq(1,4) ++ [0]],
+ Activate = #wx{event=#wxList{type=command_list_item_activated,
+ itemIndex=2}},
+ Active ! Activate,
+ timer:sleep(1000), %% Give it time to refresh
+ ok;
+
+test_page("Table" ++ _, _Window) ->
Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
Table = lists:nth(3, Tables),
ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
@@ -208,6 +227,13 @@ test_page(_Title = "Table" ++ _, _Window) ->
timer:sleep(1000),
ok;
+test_page("Trace Overview" ++ _, _Window) ->
+ timer:sleep(500), %% Give it time to refresh
+ Active = get_active(),
+ Active ! refresh_interval,
+ timer:sleep(1000), %% Give it time to refresh
+ ok;
+
test_page(Title, Window) ->
io:format("Page ~p: ~p~n", [Title, Window]),
%% Just let it display some info and hopefully it doesn't crash
@@ -275,6 +301,17 @@ table_win(Config) when is_list(Config) ->
observer:stop(),
ok.
+%% Test PR-1296/OTP-14151
+%% Clicking a link to a port before the port tab has been activated the
+%% first time crashes observer.
+port_win_when_tab_not_initiated(Config) ->
+ {ok,Port} = gen_tcp:listen(0,[]),
+ ok = observer:start(),
+ Notebook = setup_whitebox_testing(),
+ observer ! {open_link,erlang:port_to_list(Port)},
+ timer:sleep(1000),
+ observer:stop(),
+ ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl
index bdf10f507d..c06ec21f36 100644
--- a/lib/observer/test/ttb_SUITE.erl
+++ b/lib/observer/test/ttb_SUITE.erl
@@ -2,7 +2,7 @@
%% %CopyrightBegin%
%%
%%
-%% Copyright Ericsson AB 2002-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@
-export([init_per_testcase/2, end_per_testcase/2]).
-export([foo/0]).
--include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct.hrl").
-define(default_timeout, ?t:minutes(1)).
-define(OUTPUT, "handler_output").
@@ -819,6 +819,7 @@ myhandler(_Fd,Trace,_,Relay) ->
simple_call_handler() ->
{fun(A, {trace_ts, _, call, _, _} ,_,_) -> io:format(A, "ok.~n", []);
+ (A, {drop, N}, _, _) -> io:format(A, "{drop, ~p}.", [N]);
(_, end_of_trace, _, _) -> ok end, []}.
marking_call_handler() ->
@@ -954,17 +955,24 @@ begin_trace_local(ServerNode, ClientNode, Dest) ->
?line ttb:tpl(client, get, []).
check_size(N, Dest, Output, ServerNode, ClientNode) ->
- ?line begin_trace(ServerNode, ClientNode, Dest),
- ?line case Dest of
+ begin_trace(ServerNode, ClientNode, Dest),
+ case Dest of
{local, _} ->
- ?line ttb_helper:msgs_ip(N);
+ ttb_helper:msgs_ip(N);
_ ->
- ?line ttb_helper:msgs(N)
+ ttb_helper:msgs(N)
end,
- ?line {_, D} = ttb:stop([fetch, return_fetch_dir]),
- ?line ttb:format(D, [{out, Output}, {handler, simple_call_handler()}]),
- ?line {ok, Ret} = file:consult(Output),
- ?line true = (N + 1 == length(Ret)).
+ {_, D} = ttb:stop([fetch, return_fetch_dir]),
+ ttb:format(D, [{out, Output}, {handler, simple_call_handler()}]),
+ {ok, Ret} = file:consult(Output),
+ check_output(N+1, Ret).
+
+check_output(Expected, Ret)
+ when length(Ret) =:= Expected -> ok;
+check_output(Expected, Ret) ->
+ io:format("~p~n",[Ret]),
+ io:format("Expected ~p got ~p ~n",[Expected, length(Ret)]),
+ Expected = length(Ret).
fetch_when_no_option_given(suite) ->
[];
@@ -1166,8 +1174,8 @@ changing_cwd_on_control_node(Config) when is_list(Config) ->
?line {_, D} = ttb:stop([fetch, return_fetch_dir]),
?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]),
?line {ok, Ret} = file:consult(?OUTPUT),
- ?line true = (2*(NumMsgs + 1) == length(Ret)),
- ?line ok = file:set_cwd(OldDir).
+ check_output(2*(NumMsgs + 1),Ret),
+ ok = file:set_cwd(OldDir).
changing_cwd_on_control_node(cleanup,_Config) ->
?line stop_client_and_server().
@@ -1176,18 +1184,19 @@ changing_cwd_on_control_node_with_local_trace(suite) ->
changing_cwd_on_control_node_with_local_trace(doc) ->
["Changing cwd on control node during local tracing is safe"];
changing_cwd_on_control_node_with_local_trace(Config) when is_list(Config) ->
- ?line {ok, OldDir} = file:get_cwd(),
- ?line {ServerNode, ClientNode} = start_client_and_server(),
- ?line begin_trace(ServerNode, ClientNode, {local, ?FNAME}),
- ?line NumMsgs = 3,
- ?line ttb_helper:msgs_ip(NumMsgs),
- ?line ok = file:set_cwd(".."),
- ?line ttb_helper:msgs_ip(NumMsgs),
- ?line {_, D} = ttb:stop([fetch, return_fetch_dir]),
- ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]),
- ?line {ok, Ret} = file:consult(?OUTPUT),
- ?line true = (2*(NumMsgs + 1) == length(Ret)),
- ?line ok = file:set_cwd(OldDir).
+ {ok, OldDir} = file:get_cwd(),
+ {ServerNode, ClientNode} = start_client_and_server(),
+ begin_trace(ServerNode, ClientNode, {local, ?FNAME}),
+ NumMsgs = 3,
+ ttb_helper:msgs_ip(NumMsgs),
+ ok = file:set_cwd(".."),
+ ttb_helper:msgs_ip(NumMsgs),
+ {_, D} = ttb:stop([fetch, return_fetch_dir]),
+ ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]),
+ {ok, Ret} = file:consult(?OUTPUT),
+ Expected = 2*(NumMsgs + 1),
+ check_output(Expected, Ret),
+ ok = file:set_cwd(OldDir).
changing_cwd_on_control_node_with_local_trace(cleanup,_Config) ->
?line stop_client_and_server().
@@ -1205,7 +1214,7 @@ changing_cwd_on_remote_node(Config) when is_list(Config) ->
?line {_, D} = ttb:stop([fetch, return_fetch_dir]),
?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]),
?line {ok, Ret} = file:consult(?OUTPUT),
- ?line true = (2*(NumMsgs + 1) == length(Ret)).
+ check_output(2*(NumMsgs + 1),Ret).
changing_cwd_on_remote_node(cleanup,_Config) ->
?line stop_client_and_server().
@@ -1497,7 +1506,7 @@ logic(N, M, TracingType) ->
ct:log("formatted ~p",[{D,?OUTPUT}]),
?line {ok, Ret} = file:consult(?OUTPUT),
ct:log("consulted: ~p",[Ret]),
- ?line M = length(Ret).
+ check_output(M,Ret).
begin_trace_with_resume(ServerNode, ClientNode, Dest) ->
?line {ok, _} = ttb:tracer([ServerNode,ClientNode], [{file, Dest}, resume]),
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index aede0858d6..ca9ad72473 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.1.2
+OBSERVER_VSN = 2.3.1