aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/observer/doc/src/Makefile10
-rw-r--r--lib/observer/doc/src/cdv.xml60
-rw-r--r--lib/observer/doc/src/crashdump.xml23
-rw-r--r--lib/observer/doc/src/crashdump_help.html307
-rw-r--r--lib/observer/doc/src/crashdump_ug.xml427
-rw-r--r--lib/observer/doc/src/ref_man.xml1
-rw-r--r--lib/observer/src/Makefile18
-rw-r--r--lib/observer/src/cdv_atom_wx.erl48
-rw-r--r--lib/observer/src/cdv_bin_wx.erl36
-rw-r--r--lib/observer/src/cdv_detail_win.erl193
-rw-r--r--lib/observer/src/cdv_dist_wx.erl86
-rw-r--r--lib/observer/src/cdv_ets_wx.erl66
-rw-r--r--lib/observer/src/cdv_fun_wx.erl58
-rw-r--r--lib/observer/src/cdv_gen_wx.erl45
-rw-r--r--lib/observer/src/cdv_info_page.erl119
-rw-r--r--lib/observer/src/cdv_int_tab_wx.erl79
-rw-r--r--lib/observer/src/cdv_mem_wx.erl78
-rw-r--r--lib/observer/src/cdv_mod_wx.erl103
-rw-r--r--lib/observer/src/cdv_multi_panel.erl190
-rw-r--r--lib/observer/src/cdv_port_wx.erl102
-rw-r--r--lib/observer/src/cdv_proc_wx.erl152
-rw-r--r--lib/observer/src/cdv_table_page.erl109
-rw-r--r--lib/observer/src/cdv_timer_wx.erl51
-rw-r--r--lib/observer/src/cdv_virtual_list.erl425
-rw-r--r--lib/observer/src/crashdump_viewer.erl1677
-rw-r--r--lib/observer/src/crashdump_viewer.hrl168
-rw-r--r--lib/observer/src/crashdump_viewer_html.erl61
-rw-r--r--lib/observer/src/crashdump_viewer_wx.erl481
-rw-r--r--lib/observer/src/observer_lib.erl225
-rw-r--r--lib/observer/src/observer_wx.erl18
30 files changed, 3834 insertions, 1582 deletions
diff --git a/lib/observer/doc/src/Makefile b/lib/observer/doc/src/Makefile
index 0f564d3299..c2f5aff5a0 100644
--- a/lib/observer/doc/src/Makefile
+++ b/lib/observer/doc/src/Makefile
@@ -34,6 +34,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
+XML_REF1_FILES = \
+ cdv.xml
XML_REF3_FILES = \
crashdump.xml \
observer.xml \
@@ -59,7 +61,7 @@ BOOK_FILES = book.xml
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
- $(XML_PART_FILES) $(XML_REF3_FILES) \
+ $(XML_PART_FILES) $(XML_REF1_FILES) $(XML_REF3_FILES) \
$(XML_APPLICATION_FILES) $(XML_REF6_FILES)
ONLY_HTML_FILE = \
@@ -81,6 +83,7 @@ HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
INFO_FILE = ../../info
+MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1)
MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
@@ -110,6 +113,7 @@ html: gifs $(HTML_REF_MAN_FILE) $(ONLY_HTML_FILE:%=$(HTMLDIR)/%)
clean clean_docs:
rm -rf $(HTMLDIR)/*
+ rm -f $(MAN1DIR)/*
rm -f $(MAN3DIR)/*
rm -f $(MAN6DIR)/*
rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
@@ -119,7 +123,7 @@ clean clean_docs:
$(HTMLDIR)/$(ONLY_HTML_FILE):
$(INSTALL_DATA) $(ONLY_HTML_FILE) $@
-man: $(MAN3_FILES) $(MAN6_FILES)
+man: $(MAN1_FILES) $(MAN3_FILES) $(MAN6_FILES)
gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
@@ -139,6 +143,8 @@ release_docs_spec: docs
$(INSTALL_DATA) $(HTMLDIR)/* \
"$(RELSYSDIR)/doc/html"
$(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
+ $(INSTALL_DATA) $(MAN1DIR)/* "$(RELEASE_PATH)/man/man1"
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
$(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
diff --git a/lib/observer/doc/src/cdv.xml b/lib/observer/doc/src/cdv.xml
new file mode 100644
index 0000000000..fc8f16bc4e
--- /dev/null
+++ b/lib/observer/doc/src/cdv.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE comref SYSTEM "comref.dtd">
+
+<comref>
+ <header>
+ <copyright>
+ <year>2013</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>The cdv program</title>
+ <prepared>Siri Hansen</prepared>
+ <responsible>Siri Hansen</responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2013-10-015</date>
+ <rev>PA1</rev>
+ <file>cdv.xml</file>
+ </header>
+ <com>cdv</com>
+ <comsummary>Script used for starting the Crashdump Viewer from the
+ OS command line.
+ </comsummary>
+
+ <description>
+ <p>The <c>cdv</c> shell script can be found under the <c>priv</c>
+ directory of the <c>observer</c> application. The script is used
+ for starting the Crashdump Viewer tool from the OS command
+ line.</p>
+ <p>For Windows users, <c>cdv.bat</c> can be found in the same
+ location.</p>
+ </description>
+
+ <funcs>
+ <func>
+ <name>cdv [file]</name>
+ <fsummary>Start the Crashdump Viewer and load the given file.</fsummary>
+ <desc>
+ <p>The <c>file</c> arguments is optional. If not given, a file
+ dialog will pop up allowing the user to select a crashdump
+ from the file system.</p>
+ </desc>
+ </func>
+ </funcs>
+
+</comref>
diff --git a/lib/observer/doc/src/crashdump.xml b/lib/observer/doc/src/crashdump.xml
index 5b58a52739..27e42e83b7 100644
--- a/lib/observer/doc/src/crashdump.xml
+++ b/lib/observer/doc/src/crashdump.xml
@@ -31,13 +31,14 @@
<checked></checked>
<date>2003-03-10</date>
<rev>PA1</rev>
- <file>crashdump.sgml</file>
+ <file>crashdump.xml</file>
</header>
<module>crashdump_viewer</module>
- <modulesummary>A HTML based tool for browsing Erlang crashdumps.</modulesummary>
+ <modulesummary>A WxWidgets based tool for browsing Erlang
+ crashdumps.</modulesummary>
<description>
- <p>The Crashdump Viewer is an HTML based tool for browsing Erlang
- crashdumps. Crashdump Viewer runs under the WebTool application.</p>
+ <p>The Crashdump Viewer is a WxWidgets based tool for browsing Erlang
+ crashdumps.</p>
<p>See the <seealso marker="crashdump_ug">user's guide</seealso>
for more information about how to get started with the Crashdump
@@ -46,16 +47,26 @@
<funcs>
<func>
<name>start() -> ok</name>
+ <name>start(File) -> ok</name>
<fsummary>Start the crashdump_viewer</fsummary>
+ <type>
+ <v>File = string()</v>
+ <d>The file name of the crashdump.</d>
+ </type>
<desc>
- <p>This function starts the <c>crashdump_viewer</c>.</p>
+ <p>This function starts the <c>crashdump_viewer</c> GUI and
+ loads the given crashdump.</p>
+
+ <p>If <c>File</c> is not given, a file dialog will be opened
+ where the crashdump can be selected.</p>
</desc>
</func>
<func>
<name>stop() -> ok</name>
<fsummary>Stop the crashdump_viewer</fsummary>
<desc>
- <p>This function stops the <c>crashdump_viewer</c>.</p>
+ <p>This function stops the <c>crashdump_viewer</c> and closes
+ all GUI windows.</p>
</desc>
</func>
</funcs>
diff --git a/lib/observer/doc/src/crashdump_help.html b/lib/observer/doc/src/crashdump_help.html
deleted file mode 100644
index 268b9495d6..0000000000
--- a/lib/observer/doc/src/crashdump_help.html
+++ /dev/null
@@ -1,307 +0,0 @@
-</<!doctype chapter PUBLIC "-//Stork//DTD chapter//EN">
-<!--
- ``The contents of this file are subject to the Erlang Public License,
- Version 1.1, (the "License"); you may not use this file except in
- compliance with the License. You should have received a copy of the
- Erlang Public License along with this software. If not, it can be
- retrieved via the world wide web at http://www.erlang.org/.
-
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- the License for the specific language governing rights and limitations
- under the License.
-
- The Initial Developer of the Original Code is Ericsson Utvecklings AB.
- Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
- AB. All Rights Reserved.''
-
- $Id$
--->
-<html>
-<head>
-<title>Crashdump Viewer help</title>
-</head>
-<body BGCOLOR="#FFFFFF">
-<center>
-<a HREF="http://www.erlang.se"><img BORDER=0 ALT="[Erlang Systems]"
-SRC="min_head.gif"></a>
-</center>
-<blockquote>
- <h2>Information pages</h2>
-
- <p>Each menu item points to an information page. If no information
- is found for an item, the page will simply say "No information
- found". The reason for not finding any information about an item
- can be that the dump is truncated, that it is a dump from an old
- OTP release in which this item was not written or that the item
- simply wasn't present in the system at the point of failure.
-
- <p>If the dump was truncated, a warning is displayed.
-
- <p>Even if some information about an item exists, there might be
- empty fields if the dump originates from an old OTP release.
-
- <p>The value "-1" in any field means "unknown", and in most cases
- it means that the dump was truncated somewhere around this field.
-
- <p>Only some of the fields in the different information pages are
- described here. These are fields that to not exist in the raw
- crashdump, or in some way differs from the fields in the raw
- crashdump. Details about other field can be found in the user's
- guide for the Erlang runtime system, in the chapter "How to
- interpret the Erlang crash dumps". A link to this chapter can be
- found in the Crashdump Viewer's menu under documentation, and
- there are also direct links from the specific sections below to
- related information in "How to interpret the Erlang crash dumps".
-
- <a NAME="general_info">
- <h3>General information</h3>
-
- <p>This is the first page shown when a new dump is loaded into
- the system. It shows a very short overview of the dump.
-
- <p>'Node name' will only exist in dumps originating from OTP R9C
- and later.
-
- <p>The following fields are not described in the Erlang runtime
- system user's guide:
-
- <dl>
- <dt><strong>Crashdump created on</strong></dt>
- <dd>Time of failure.</dd>
-
- <dt><strong>Memory allocated</strong></dt>
- <dd>The total number of bytes allocated, equivalent to
- <code>c:memory(total)</code>. This will only be present in
- dumps from OTP R9C and later.</dd>
-
- <dt><strong>Memory maximum</strong></dt>
- <dd>The maximum number of bytes that has been allocated
- during the lifetime of the originating node. This will not be
- present in dumps older than OTP R9C, and even in newer
- releases it is only shown if the Erlang runtime system was run
- instrumented.</dd>
-
- <dt><strong>Atoms</strong></dt>
- <dd>If at all available in the dump, this is the total
- number of atoms in the atom table. If the size of the atom
- table is not available, the number of atoms visible in the
- dump is presented.</dd>
-
- <dt><strong>Processes, ETS tables and Funs</strong></dt>
- <dd>The number of processes, ETS tables and funs visible in
- the dump.</dd>
- </dl>
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#general_info>
- More...</a>
- </center>
-
-
- <a NAME="processes">
- <h3>Processes</h3>
-
- <p>The Process Information page shows a list of all processes
- found in the crashdump, including some short information about
- each process. By default the processes are sorted by their
- pids. To sort by other topic, click any heading in the process
- table.
-
- <p>Detailed information about a specific process is shown when
- the pid is clicked.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#processes>
- More...</a>
- </center>
-
-
-
- <a NAME="ports">
- <h3>Ports</h3>
-
- <p>The port information page shows all port information found in
- the dump.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#ports>
- More...</a>
- </center>
-
-
-
- <a NAME="ets_tables"><a NAME="internal_ets_tables">
- <h3>ETS tables</h3>
-
- <p>The ETS table information page shows all ETS table
- information found in the dump. The 'Id' is the same as the
- 'Table' field found in the raw crashdump, and 'Memory' is the
- 'Words' field from the raw crashdump translated into
- bytes. 'Type' is the type of table, and it can be either "hash"
- or "tree". For tree tables there will be no value in the
- 'Bucket' field.
-
- <p>Clicking a pid in the 'Owner' column takes you to the
- detailed information about the process owning the ETS table.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#ets_tables>
- More...</a>
- </center>
-
-
- <a NAME="timers">
- <h3>Timers</h3>
-
- <p>The timer information page shows all timer information found
- in the dump.
-
- <p>Clicking a pid in the 'Owner' column takes you to the
- detailed information about the process owning the timer.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#timers>
- More...</a>
- </center>
-
-
-
- <a NAME="funs">
- <h3>Fun table</h3>
-
- <p>The Fun table information page shows all Fun information
- found in the dump. Fun information will only exist in dumps from
- OTP R8B or later.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#funs>
- More...</a>
- </center>
-
-
-
- <a NAME="atoms">
- <h3>Atoms</h3>
-
- <p>The atoms information page lists all atoms found in the
- dump. The last created atom is listed first.
-
- <p>Note that if the dump is from OTP R8B or earlier, the raw
- dump lists the atoms in the opposite order and the Crashdump
- Viewer reverses them. This means that there is no problem if the
- dump is not truncated. However, if the dump is truncated, the
- last atoms might not be shown at all!!
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#atoms>
- More...</a>
- </center>
-
-
- <a NAME="distribution_info">
- <h3>Distribution information</h3>
-
- <p>The distribution information page shows all distribution
- information found in the dump.
-
- <p>If the page shows "Not alive", it means that the node was not
- distributed.
-
- <p>It the node was distributed, all connected nodes are
- shown. Visible nodes are alive nodes with a living connection to
- the originating node. Hidden nodes are the same as visible
- nodes, except they are started with the "-hidden" flag. Not
- connected nodes are nodes that are not connected to the
- originating node anymore, but references (i.e. process or port
- identitifiers) exist.
-
- <p>'Links/Monitors' may contain information about links or
- monitors between processes on the originating node and the
- connected node.
-
- <p>'Extra Info' may contain debug information (i.e. special
- information written if the emulator is debug compiled) or error
- information.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#distribution_info>
- More...</a>
- </center>
-
-
- <a NAME="loaded_modules">
- <h3>Loaded modules</h3>
-
- <p>The loaded modules information page shows all modules that
- were loaded on the originating node, and the current size of the
- code. If old code exsits, the old size is also shown.
-
- <p>Detailed information about a specific module is shown when
- the module name is clicked.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#loaded_modules>
- More...</a>
- </center>
-
-
- <a NAME="internal_tables">
- <h3>Internal tables</h3>
-
- <p>Internal tables are shown in two information pages: hash
- tables and index tables.
-
- <center>
- <a href=/crashdump_erts_doc/crash_dump.html#internal_tables>
- More...</a>
- </center>
-
-
- <a NAME="memory">
- <h3>Memory</h3>
-
- <p>Memory information is divided into three pages.
-
- <p>The first page, <strong>Memory</strong>, shows information
- similar to what you can obtain on a living node with
- <code>c:memory()</code>. This will only be present in dumps from
- OTP R9C and later.
- <a href=/crashdump_erts_doc/crash_dump.html#memory>More...</a>
-
- <p>The <strong>Allocated areas</strong> page shows information
- similar to what you can obtain on a living node with
- <code>erlang:system_info(allocated_areas)</code>.
- <a href=/crashdump_erts_doc/crash_dump.html#allocated_areas>More...</a>
-
- <p>The <strong>Allocator information</strong> page shows
- information about allocators. The contents of the page will vary
- with the version.
- <a href=/crashdump_erts_doc/crash_dump.html#allocator>More...</a>
-
- <center>
-
- </center>
-
-
- <h3>Documentation</h3>
-
- <p>'Crashdump Viewer help' is this document.
-
- <p>'How to interpret Erlang crashdumps' is a document from the
- Erlang runtime system describing details in the raw
- crashdumps. Here you will also find information about each
- single field in the different information pages.
-
-</blockquote>
-<center>
-<hr>
-<font SIZE=-1>
-
-Copyright &copy; 1991-2003
-<a HREF="http://www.erlang.se">Ericsson Utvecklings AB</a><br>
-<!--#include virtual="/ssi/otp_footer.html"-->
-</font>
-</center>
-</body>
-</html>
diff --git a/lib/observer/doc/src/crashdump_ug.xml b/lib/observer/doc/src/crashdump_ug.xml
index fd4405df09..d22fb4cc40 100644
--- a/lib/observer/doc/src/crashdump_ug.xml
+++ b/lib/observer/doc/src/crashdump_ug.xml
@@ -13,12 +13,11 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
</legalnotice>
<title>Crashdump Viewer</title>
@@ -31,9 +30,9 @@
<section>
<title>Introduction</title>
- <p>The Crashdump Viewer is an HTML based tool for browsing Erlang
- crashdumps. Crashdump Viewer runs under the WebTool application.
- </p>
+ <p>The Crashdump Viewer is a WxWidgets based tool for browsing
+ Erlang crashdumps.
+ </p>
</section>
<section>
@@ -42,93 +41,357 @@
<p>The easiest way to start Crashdump Viewer is to use the
provided shell script named <c>cdv</c> with the full path to the
erlang crashdump as an argument. The script can be found in the
- priv directory of the <c>observer</c> application. This starts
- WebTool, Crashdump Viewer and a web browser, and loads the given
- file. The browser should then display a page named General
- Information which shows a short summary of the information in
- the crashdump.</p>
-
- <p>The default browser is Internet Explorer on Windows, open on Mac OS X,
- or else Firefox. To use another browser, give the browser's start command
- as the second argument to <c>cdv</c>. If the given browser name is
- not known to Crashdump Viewer, the browser argument is executed as
- a command with the start URL as the only argument.</p>
+ priv directory of the <c>observer</c> application. This starts the
+ Crashdump Viewer GUI and loads the given file. If no file name is
+ given, a file dialog will be opened where the file can be
+ selected.</p>
<p>Under Windows the batch file <c>cdv.bat</c> can be used.</p>
<p>It is also possible to start the Crashdump Viewer from within
an erlang node by calling <seealso
- marker="crashdump_viewer#start/0">crashdump_viewer:start/0</seealso>. This
- will automatically start WebTool and display the web address where
- WebTool can be found. See the documentation for the WebTool
- application for further information about how to use WebTool.</p>
-
- <p>Point your web browser to the address displayed, and you should
- now see the start page of WebTool. At the top of the page, you
- will see a link to "CrashDumpViewer". Click this link to get to
- the start page for Crashdump Viewer. (Note that if webtool is on
- localhost, you must configure your web browser to have direct
- connection to the internet, or you must set no proxy for
- localhost.)
- </p>
- <p>From the start page of Crashdump Viewer, push the "Load
- Crashdump" button to load a crashdump into the tool. Then enter
- the filename of the crashdump in the entry field and push the
- "Ok" button. This will bring you to the General Information
- page, i.e. the same page as the <c>cdv</c> script will open in
- the browser.
- </p>
- <p>Crashdumps generated by OTP R9C and later are loaded directly
- into the Crashdump Viewer, while dumps from earlier releases first
- are translated by the Crashdump Translater. The Crashdump
- Translater creates a new file with the same name as the original
- crashdump, but with the extension <c>.translated</c>. If there is
- no write access to the directory of the original file, you will be
- asked to enter a new path and filename for the translated file.
- </p>
+ marker="crashdump_viewer#start/0">crashdump_viewer:start/0</seealso>
+ or <seealso
+ marker="crashdump_viewer#start/1">crashdump_viewer:start/1</seealso>.</p>
</section>
<section>
- <title>Navigating</title>
- <p>The lefthand frame contains a menu. Menu folders can be
- expanded and collapsed by clicking the folder picture. When a menu
- item is clicked, the item information is shown in the big
- information frame.
- </p>
- <p>The filename frame above the information frame shows the full
- name of the currently viewed Erlang crashdump.
- </p>
- <p>To load a new crashdump, click the "Load New Crashdump" button
- in the menu frame.
- </p>
- <p>The various information shown in the information frame will
- contain links to process identifiers (PIDs) and port
- identifiers. Clicking one of these links will take you to the
- detailed information page for the process or port in question. Use
- the "Back" button in your browser to get back to the
- startingpoint. If the process or port resided on a remote node,
- there will be no information available. Clicking the link will
- then take you to the information about the remote node.
- </p>
+ <title>The graphical interface</title>
+
+ <p>The main window is opened when Crashdump Viewer has loaded a
+ crashdump. It contains a title bar, a menu bar, a number of
+ information panels and a status bar.</p>
+
+ <p>The title bar shows the name of the currently loaded
+ crashdump.</p>
+
+ <p>The menu bar contains a <em>File</em> menu and a <em>Help</em>
+ menu. From the File menu a new crashdump can be loaded or the tool
+ can be terminated. From the Help menu this user's guide and the
+ chapter "How to interpret the Erlang crash dumps" from the user's
+ guide for Erlang runtime system can be opened. "How to interpret
+ the Erlang crash dumps" describes the raw crashdumps in
+ detail. Here you will also find information about each single
+ field in the different information pages. This document can also
+ be found directly in the OTP online documentation, via the Erlang
+ runtime system user's guide.</p>
+
+ <p>The status bar at the bottom of the window will show a warning
+ if the currently loaded dump is truncated.</p>
+
+ <p>The centre area of the main window contains the information
+ panels. Each panel displays information about a specific item or a
+ list of items. A panel is selected by clicking the title of the
+ tab.</p>
+
+ <p>From panels that display lists of items, for example the
+ Processes- or the Ports panel, a new window with further
+ information can be opened by double clicking a row or by right
+ clicking the row and selecting an item from the drop down
+ menu. The new window is called a detail window. Detail windows can
+ be opened for processes, ports, nodes and modules.</p>
+
+ <p>The various information shown in a detail window might contain
+ links to processes or ports. Clicking one of these links will open
+ the detail window for the process or port in question. If the
+ process or port resided on a remote node, there will be no
+ information available. Clicking the link will then pop up a dialog
+ where you can choose whether to open the detail window for the
+ remote node or not.
+ </p>
+
+ <p>Some of the panels contain a left hand menu where sub items of
+ the panel's information area can be selected. Click on one of the
+ rows, and the information will be displayed in the right hand
+ information area.</p>
</section>
<section>
- <title>Help</title>
- <p>Further help on how to use the Crashdump Viewer tool can be
- found in the tool's menu under 'Documentation':
- </p>
- <p>'Crashdump Viewer help' is a short document describing each
- information page and any additional information that might occur,
- compared to the raw dump described in 'How to interpret Erlang
- crashdumps'.
- </p>
- <p>'How to interpret Erlang crashdumps' is a document from the
- Erlang runtime system describing details in the raw
- crashdumps. Here you will also find information about each single
- field in the different information pages. This document can also
- be found directly in the OTP online documentation, via the Erlang
- runtime system user's guide.
- </p>
+ <title>Data content</title>
+
+ <p>Each panel in the main window contains an information
+ page. If no information is found for an item, the page will be
+ empty. The reason for not finding any information about an item
+ can be that the dump is truncated, that it is a dump from an old
+ OTP release in which this item was not written or that the item
+ simply wasn't present in the system at the point of failure.</p>
+
+ <p>If the dump was truncated, a warning is displayed in the
+ status bar of the main window.</p>
+
+ <p>Even if some information about an item exists, there might be
+ empty fields if the dump originates from an old OTP release.</p>
+
+ <p>The value "-1" in any field means "unknown", and in most
+ cases it means that the dump was truncated somewhere around this
+ field.</p>
+
+ <p>The sections below describe some of the fields in the
+ different information panels. These are fields that do not exist
+ in the raw crashdump, or in some way differ from the fields in
+ the raw crashdump. Details about other fields can be found in
+ the user's guide for the Erlang runtime system, in the chapter
+ "How to interpret the Erlang crash dumps". That chapter can also
+ be opened from the Help menu in the Crashdump Viewer's main
+ window, and there are also direct links from the specific
+ sections below to related information in "How to interpret the
+ Erlang crash dumps".</p>
</section>
-</chapter>
+ <section>
+ <marker id="general_info"/>
+ <title>General information</title>
+
+ <p>The <em>General information</em> panel shows a short overview
+ of the dump.</p>
+
+ <p>The following fields are not described in the Erlang runtime
+ system user's guide:</p>
+
+ <taglist>
+ <tag><em>Crashdump created on</em></tag>
+ <item>Time of failure.</item>
+
+ <tag><em>Memory allocated</em></tag>
+ <item>The total number of bytes allocated, equivalent to
+ <c>c:memory(total)</c>.</item>
+
+ <tag><em>Memory maximum</em></tag>
+ <item>The maximum number of bytes that has been allocated during
+ the lifetime of the originating node. This will only be shown if
+ the Erlang runtime system was run instrumented.</item>
+
+ <tag><em>Atoms</em></tag>
+ <item>If available in the dump, this is the total number of
+ atoms in the atom table. If the size of the atom table is not
+ available, the number of atoms visible in the dump is
+ presented.</item>
+
+ <tag><em>Processes, ETS tables and Funs</em></tag>
+ <item>The number of processes, ETS tables and funs visible in
+ the dump.</item>
+ </taglist>
+
+ <p>
+ <seealso marker="erts:crash_dump#general_info">More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="processes"/>
+ <title>Processes</title>
+
+ <p>The <em>Processes</em> panel shows a list of all processes
+ found in the crashdump, including some short information about
+ each process. By default the processes are sorted by their
+ pids. To sort by other topic, click the desired column
+ heading.</p>
+
+ <p>The <em>Memory</em> column shows the 'Memory' field which was
+ added to crashdumps in R16B01. This is the total amount of memory
+ used by the process. For crashdumps from earlier releases, this
+ column shows the 'Stack+heap' field. The value shown is always in
+ bytes.</p>
+
+ <p>To view detailed information about a specific process, double
+ click the row in the list or right click the row and select
+ "Properties for &lt;pid&gt;".</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#processes">More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="ports"/>
+ <title>Ports</title>
+
+ <p>The <em>Ports</em> panel is similar to the <em>Processes</em>
+ panel, except it lists all ports found in the crashdump.</p>
+
+ <p>To see more details about a specific port, dobule click the row
+ or right click it and select "Properties for &lt;port&gt;". From
+ the right click menu you can also select "Properties for
+ &lt;pid&gt;", where &lt;pid&gt; is the process connected to the
+ port.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#ports">
+ More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="ets_tables"/><marker id="internal_ets_tables"/>
+ <title>ETS tables</title>
+
+ <p>The <em>ETS Tables</em> panel shows all ETS table information
+ found in the dump. The 'Id' is the same as the 'Table' field found
+ in the raw crashdump, and 'Memory' is the 'Words' field from the
+ raw crashdump translated into bytes. 'Type' is the type of table,
+ and it can be either "hash" or "tree". For tree tables there will
+ be no value in the 'Bucket' field.</p>
+
+ <p>To open the detailed information page about the owner process
+ of an ETS table, right click the row and select "Properties for
+ &lt;pid&gt;".</p>
+
+ <p>Double clicking a row in the ETS Tables panel has no
+ effect.</p>
+
+ <p>From the left hand menu you can also select to see internal ETS
+ tables.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#ets_tables">
+ More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="timers"/>
+ <title>Timers</title>
+
+ <p>The <em>Timers</em> panel shows all timer information found in
+ the dump.</p>
+
+ <p>To open the detailed information page about the owner process
+ of a timer, right click the row and select "Properties for
+ &lt;pid&gt;".</p>
+
+ <p>Double clicking a row in the Timers panel has no effect.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#timers">More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="funs"/>
+ <title>Funs</title>
+
+ <p>The <em>Funs</em> panel shows all Fun information found in the
+ dump.</p>
+
+ <p>To open the detailed information page about the module to which
+ the fun belongs, right click the row and select "Properties for
+ &lt;mod&gt;".</p>
+
+ <p>Double clicking a row in the Funs panel has no effect.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#funs">More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="atoms"/>
+ <title>Atoms</title>
+
+ <p>The <em>Atoms</em> panel lists all atoms found in the dump. By
+ default the atoms are sorted in creation order from first to
+ last. This is opposite of the raw crashdump where atoms are listed
+ from last to first, meaning that if the dump was truncated in the
+ middle of the atom list only the last created atoms will be seen
+ in the <em>Atoms</em> panel.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#atoms">More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="distribution_info"/>
+ <title>Nodes</title>
+
+ <p>The <em>Nodes</em> panel shows a list of all external erlang
+ nodes which are referenced from the crashdump.</p>
+
+ <p>If the page is empty it either means that the crashed node was
+ not distributed, that it was distributed but had no references to
+ other nodes or that the dump was truncated.</p>
+
+ <p>If the node was distributed, all referenced nodes are
+ shown. The column named <em>Connection type</em> shows if the node
+ is visible, hidden or not connected. Visible nodes are alive nodes
+ with a living connection to the originating node. Hidden nodes are
+ the same as visible nodes, except they are started with the
+ <c>-hidden</c> flag. Not connected nodes are nodes that are not
+ connected to the originating node anymore, but references
+ (i.e. process or port identifiers) exist.</p>
+
+ <p>To see more detailed information about a node, double click the
+ row or right click the row and select "Properties for node
+ &lt;node&gt;". From the right click menu you can also select
+ "Properties for &lt;port&gt;", to open the detailed information
+ window for the controlling port.</p>
+
+ <p>In the detailed information window for a node, any exsisting
+ links and monitors between processes on the originating node and
+ the connected node are shown. <em>Extra Info</em> may contain
+ debug information (i.e. special information written if the
+ emulator is debug compiled) or error information.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#distribution_info">
+ More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="loaded_modules"/>
+ <title>Loaded modules</title>
+
+ <p>The <em>Modules</em> panel lists all modules that were loaded
+ on the originating node, and the current size of the code. If old
+ code exsits, the old size is also shown.</p>
+
+ <p>To see detailed information about a specific module, double
+ click the row or right click it and select "Properties for
+ &lt;mod&gt;".</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#loaded_modules">
+ More...</seealso>
+ </p>
+ </section>
+
+ <section>
+ <marker id="memory"/>
+ <title>Memory</title>
+
+ <p>The <em>Memory</em> panel shows memory and allocator
+ information. From the left hand menu you can select:</p>
+
+ <p>
+ <list>
+
+ <item><em>Memory</em> <seealso
+ marker="erts:crash_dump#memory">More...</seealso></item>
+
+ <item><em>Allocator Summary</em> - this page presents a
+ summary of values from all allocators below.</item>
+
+ <item><em>&lt;Allocator&gt;</em> - one entry per allocator
+ <seealso
+ marker="erts:crash_dump#allocator">More...</seealso></item>
+
+ <item><em>Allocated Areas</em> <seealso
+ marker="erts:crash_dump#allocated_areas">More...</seealso></item>
+
+ </list>
+ </p>
+ </section>
+
+ <section>
+ <marker id="internal_tables"/>
+ <title>Internal tables</title>
+
+ <p>On the <em>Internal Tables</em> panel you can choose from the
+ left hand menu to see hash tables or index tables.</p>
+
+ <p>
+ <seealso marker="erts:crash_dump#internal_tables">More...</seealso>
+ </p>
+ </section>
+</chapter>
diff --git a/lib/observer/doc/src/ref_man.xml b/lib/observer/doc/src/ref_man.xml
index 3230a1c9d3..c3805b2d86 100644
--- a/lib/observer/doc/src/ref_man.xml
+++ b/lib/observer/doc/src/ref_man.xml
@@ -38,5 +38,6 @@
<xi:include href="ttb.xml"/>
<xi:include href="etop.xml"/>
<xi:include href="crashdump.xml"/>
+ <xi:include href="cdv.xml"/>
</application>
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile
index 42f5c19935..dfebe71282 100644
--- a/lib/observer/src/Makefile
+++ b/lib/observer/src/Makefile
@@ -37,6 +37,24 @@ RELSYSDIR = $(RELEASE_PATH)/lib/observer-$(VSN)
MODULES= \
crashdump_viewer \
crashdump_viewer_html \
+ crashdump_viewer_wx \
+ cdv_info_page \
+ cdv_virtual_list \
+ cdv_detail_win \
+ cdv_table_page \
+ cdv_multi_panel \
+ cdv_gen_wx \
+ cdv_proc_wx \
+ cdv_port_wx \
+ cdv_ets_multi_wx \
+ cdv_ets_wx \
+ cdv_timer_wx \
+ cdv_fun_wx \
+ cdv_atom_wx \
+ cdv_dist_wx \
+ cdv_mod_wx \
+ cdv_mem_wx \
+ cdv_int_tab_wx \
etop \
etop_gui \
etop_tr \
diff --git a/lib/observer/src/cdv_atom_wx.erl b/lib/observer/src/cdv_atom_wx.erl
new file mode 100644
index 0000000000..372badc944
--- /dev/null
+++ b/lib/observer/src/cdv_atom_wx.erl
@@ -0,0 +1,48 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_atom_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ format/1]).
+
+-include_lib("wx/include/wx.hrl").
+
+%% Defines
+-define(COL_ID, 0).
+-define(COL_ATOM, ?COL_ID+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_ID);
+col_to_elem(Id) -> Id+1.
+
+col_spec() ->
+ [{"Creation order", ?wxLIST_FORMAT_CENTER, 100},
+ {"Atom", ?wxLIST_FORMAT_LEFT, 100}].
+
+get_info(_) ->
+ {ok,Info,TW} = crashdump_viewer:atoms(),
+ {Info,TW}.
+
+format({Bin,q}) when is_binary(Bin) ->
+ [$'|binary_to_list(Bin)];
+format({Bin,nq}) when is_binary(Bin) ->
+ lists:flatten(io_lib:format("~ts",[Bin]));
+format(D) ->
+ D.
diff --git a/lib/observer/src/cdv_bin_wx.erl b/lib/observer/src/cdv_bin_wx.erl
new file mode 100644
index 0000000000..d79f0dbc22
--- /dev/null
+++ b/lib/observer/src/cdv_bin_wx.erl
@@ -0,0 +1,36 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_bin_wx).
+
+-export([get_details/1,
+ detail_pages/0]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Callbacks for cdv_detail_win
+get_details(Id) ->
+ {ok,Bin} = crashdump_viewer:expand_binary(Id),
+ {ok,{"Expanded Binary", io_lib:format("~tp",[Bin]), []}}.
+
+detail_pages() ->
+ [{simple, "Binary", fun init_bin_page/3}].
+
+init_bin_page(Parent, _, Bin) ->
+ Html = crashdump_viewer_html:plain_page(Bin),
+ observer_lib:html_window(Parent,Html).
diff --git a/lib/observer/src/cdv_detail_win.erl b/lib/observer/src/cdv_detail_win.erl
new file mode 100644
index 0000000000..014ed41e70
--- /dev/null
+++ b/lib/observer/src/cdv_detail_win.erl
@@ -0,0 +1,193 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_detail_win).
+
+-behaviour(wx_object).
+
+-export([start/3]).
+
+-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
+ handle_call/3, handle_info/2]).
+
+-export([init_detail_page/3]).
+
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+-include("observer_defs.hrl").
+
+-record(state, {parent,
+ frame,
+ id,
+ pages=[]
+ }).
+
+
+%% Defines
+-define(ID_NOTEBOOK, 604).
+
+%% Detail view
+start(Id, ParentFrame, Callback) ->
+ wx_object:start_link(?MODULE, [Id, ParentFrame, Callback, self()], []).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([Id, ParentFrame, Callback, Parent]) ->
+ case Callback:get_details(Id) of
+ {ok,Details} ->
+ init(Id,ParentFrame,Callback,Parent,Details);
+ {yes_no, Info, Fun} ->
+ case observer_lib:display_yes_no_dialog(Info) of
+ ?wxID_YES -> Fun();
+ ?wxID_NO -> ok
+ end,
+ {stop,normal};
+ {info,Info} ->
+ observer_lib:display_info_dialog(Info),
+ {stop,normal}
+ end.
+
+init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
+ Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [Title],
+ [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
+ MenuBar = wxMenuBar:new(),
+ create_menus(MenuBar),
+ wxFrame:setMenuBar(Frame, MenuBar),
+
+ Panel = wxPanel:new(Frame, []),
+ Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
+
+ case TW of
+ [] ->
+ undefined;
+ _ ->
+ StatusBar = observer_lib:create_status_bar(Panel),
+ wxSizer:add(Sizer, StatusBar, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 0},
+ {border,4}]),
+ wxTextCtrl:writeText(StatusBar, TW),
+ StatusBar
+ end,
+
+ wxPanel:setSizer(Panel, Sizer),
+
+ Pages = [init_panel(Type, Notebook, PageTitle, Fun, [Id,Info])
+ || {Type,PageTitle,Fun} <- Callback:detail_pages()],
+
+ wxFrame:connect(Frame, close_window),
+ wxMenu:connect(Frame, command_menu_selected),
+ wxFrame:show(Frame),
+ {Frame, #state{parent=Parent,
+ id=Id,
+ frame=Frame,
+ pages=Pages
+ }}.
+
+init_panel(simple, Notebook, Str, Fun, FunArgs) ->
+ Panel = wxScrolledWindow:new(Notebook),
+ wxScrolledWindow:enableScrolling(Panel,true,true),
+ wxScrolledWindow:setScrollbars(Panel,1,1,0,0),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ Window = apply(Fun, [Panel | FunArgs]),
+ wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 1},
+ {border, 5}]),
+ wxPanel:setSizer(Panel, Sizer),
+ true = wxNotebook:addPage(Notebook, Panel, Str),
+ Panel;
+init_panel(list, Notebook, Str, Fun, FunArgs) ->
+ Panel = apply(Fun, [Notebook | FunArgs]),
+ true = wxNotebook:addPage(Notebook, Panel, Str),
+ Panel.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+handle_event(#wx{event=#wxClose{type=close_window}}, State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}},
+ State) ->
+ {stop, normal, State};
+
+handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,
+ linkInfo=#wxHtmlLinkInfo{href=Target}}},
+ State) ->
+ case Target of
+ "#Binary<" ++ Id0 ->
+ Id = string:strip(Id0,right,$>),
+ start(Id, State#state.frame, cdv_bin_wx);
+ _ ->
+ cdv_virtual_list:start_detail_win(Target)
+ end,
+ {noreply, State};
+
+handle_event(#wx{event=#wxMouse{type=left_down},
+ userData=Target},
+ State) ->
+ cdv_virtual_list:start_detail_win(Target),
+ {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_info(_Info, State) ->
+ %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+handle_call(Call, From, _State) ->
+ error({unhandled_call, Call, From}).
+
+handle_cast(Cast, _State) ->
+ error({unhandled_cast, Cast}).
+
+terminate(_Reason, #state{parent=Parent,id=Id,frame=Frame}) ->
+ Parent ! {detail_win_closed, Id},
+ case Frame of
+ undefined -> ok;
+ _ -> wxFrame:destroy(Frame)
+ end,
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+init_detail_page(Panel, Fields, Info) ->
+ Filled = observer_lib:fill_info(Fields, Info),
+ {FPanel, _, _UpFields} = observer_lib:display_info(Panel, Filled),
+ FPanel.
+
+create_menus(MenuBar) ->
+ Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}],
+ observer_lib:create_menus(Menus, MenuBar, new_window).
diff --git a/lib/observer/src/cdv_dist_wx.erl b/lib/observer/src/cdv_dist_wx.erl
new file mode 100644
index 0000000000..6a4a6da422
--- /dev/null
+++ b/lib/observer/src/cdv_dist_wx.erl
@@ -0,0 +1,86 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_dist_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1,
+ get_details/1,
+ detail_pages/0]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Columns
+-define(COL_NAME, 0).
+-define(COL_TYPE, ?COL_NAME+1).
+-define(COL_CTRL, ?COL_TYPE+1).
+-define(COL_CH, ?COL_CTRL+1).
+-define(COL_CRE, ?COL_CH+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_CH);
+col_to_elem(?COL_NAME) -> #nod.name;
+col_to_elem(?COL_CH) -> #nod.channel;
+col_to_elem(?COL_CTRL) -> #nod.controller;
+col_to_elem(?COL_CRE) -> #nod.creation;
+col_to_elem(?COL_TYPE) -> #nod.conn_type.
+
+col_spec() ->
+ [{"Name", ?wxLIST_FORMAT_LEFT, 300},
+ {"Connection type", ?wxLIST_FORMAT_LEFT, 150},
+ {"Controller", ?wxLIST_FORMAT_LEFT, 150},
+ {"Channel", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Creation", ?wxLIST_FORMAT_RIGHT, 80}].
+
+get_info(_) ->
+ {ok,Info,TW} = crashdump_viewer:dist_info(),
+ {Info,TW}.
+
+get_detail_cols(_) ->
+ {[?COL_CH,?COL_CTRL],true}.
+
+%% Callbacks for cdv_detail_win
+get_details(Id) ->
+ {ok,Info,TW} = crashdump_viewer:node_info(Id),
+ Proplist = crashdump_viewer:to_proplist(record_info(fields,nod),Info),
+ Title = io_lib:format("~s (~s)",[Info#nod.name,Id]),
+ {ok,{Title,Proplist,TW}}.
+
+detail_pages() ->
+ [{simple, "General Information", fun init_gen_page/3}].
+
+init_gen_page(Parent, _Id, Info) ->
+ Fields = info_fields(),
+ cdv_detail_win:init_detail_page(Parent, Fields, Info).
+
+%%%-----------------------------------------------------------------
+%%% Internal
+info_fields() ->
+ [{"Overview",
+ [{"Name", name},
+ {"Type", conn_type},
+ {"Channel", channel},
+ {"Controller", {click,controller}},
+ {"Creation", creation},
+ {"Extra Info", error}]},
+ {scroll_boxes,
+ [{"Remote Links",1,{click,remote_links}},
+ {"Remote Monitors",1,{click,remote_mon}},
+ {"Remote Monitored By",1,{click,remote_mon_by}}]}].
diff --git a/lib/observer/src/cdv_ets_wx.erl b/lib/observer/src/cdv_ets_wx.erl
new file mode 100644
index 0000000000..7a2e80a989
--- /dev/null
+++ b/lib/observer/src/cdv_ets_wx.erl
@@ -0,0 +1,66 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_ets_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Defines
+-define(COL_ID, 0).
+-define(COL_NAME, ?COL_ID+1).
+-define(COL_SLOT, ?COL_NAME+1).
+-define(COL_OWNER, ?COL_SLOT+1).
+-define(COL_TYPE, ?COL_OWNER+1).
+-define(COL_BUCK, ?COL_TYPE+1).
+-define(COL_OBJ, ?COL_BUCK+1).
+-define(COL_MEM, ?COL_OBJ+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_ID);
+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.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.
+
+col_spec() ->
+ [{"Id", ?wxLIST_FORMAT_LEFT, 200},
+ {"Name", ?wxLIST_FORMAT_LEFT, 200},
+ {"Slot", ?wxLIST_FORMAT_RIGHT, 60},
+ {"Owner", ?wxLIST_FORMAT_CENTRE, 90},
+ {"Type", ?wxLIST_FORMAT_LEFT, 60},
+ {"Buckets", ?wxLIST_FORMAT_RIGHT, 60},
+ {"Objects", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Memory", ?wxLIST_FORMAT_RIGHT, 80}].
+
+get_info(Owner) ->
+ {ok,Info,TW} = crashdump_viewer:ets_tables(Owner),
+ {Info,TW}.
+
+get_detail_cols(all) ->
+ {[?COL_OWNER],false};
+get_detail_cols(_) ->
+ {[],false}.
diff --git a/lib/observer/src/cdv_fun_wx.erl b/lib/observer/src/cdv_fun_wx.erl
new file mode 100644
index 0000000000..c4a78594fa
--- /dev/null
+++ b/lib/observer/src/cdv_fun_wx.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_fun_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Defines
+-define(COL_MOD, 0).
+-define(COL_UNIQ, ?COL_MOD+1).
+-define(COL_INDEX, ?COL_UNIQ+1).
+-define(COL_ADDR, ?COL_INDEX+1).
+-define(COL_NADDR, ?COL_ADDR+1).
+-define(COL_REFC, ?COL_NADDR+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_MOD);
+col_to_elem(?COL_MOD) -> #fu.module;
+col_to_elem(?COL_UNIQ) -> #fu.uniq;
+col_to_elem(?COL_INDEX) -> #fu.index;
+col_to_elem(?COL_ADDR) -> #fu.address;
+col_to_elem(?COL_NADDR) -> #fu.native_address;
+col_to_elem(?COL_REFC) -> #fu.refc.
+
+col_spec() ->
+ [{"Module", ?wxLIST_FORMAT_LEFT, 200},
+ {"Uniq", ?wxLIST_FORMAT_RIGHT, 150},
+ {"Index", ?wxLIST_FORMAT_RIGHT, 60},
+ {"Address", ?wxLIST_FORMAT_LEFT, 120},
+ {"Native Address", ?wxLIST_FORMAT_LEFT, 120},
+ {"Refc", ?wxLIST_FORMAT_RIGHT, 60}].
+
+get_info(_) ->
+ {ok,Info,TW} = crashdump_viewer:funs(),
+ {Info,TW}.
+
+get_detail_cols(_) ->
+ {[?COL_MOD],false}.
diff --git a/lib/observer/src/cdv_gen_wx.erl b/lib/observer/src/cdv_gen_wx.erl
new file mode 100644
index 0000000000..92eee203a7
--- /dev/null
+++ b/lib/observer/src/cdv_gen_wx.erl
@@ -0,0 +1,45 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_gen_wx).
+
+-export([get_info/0]).
+
+-include("crashdump_viewer.hrl").
+
+get_info() ->
+ {ok,Info} = crashdump_viewer:init_general_info(),
+ Fields = info_fields(),
+ Proplist =
+ crashdump_viewer:to_proplist(record_info(fields,general_info),Info),
+ {Fields,Proplist,[]}.
+
+info_fields() ->
+ [{"General Information",
+ [{"Slogan",slogan},
+ {"Node name",node_name},
+ {"Crashdump created on",created},
+ {"System version",system_vsn},
+ {"Compiled",compile_time},
+ {"Taints",taints},
+ {"Memory allocated",{bytes,mem_tot}},
+ {"Memory maximum",{bytes,mem_max}},
+ {"Atoms",num_atoms},
+ {"Processes",num_procs},
+ {"ETS tables",num_ets},
+ {"Timers",num_timers},
+ {"Funs",num_fun}]}].
diff --git a/lib/observer/src/cdv_info_page.erl b/lib/observer/src/cdv_info_page.erl
new file mode 100644
index 0000000000..b2f94fbc7f
--- /dev/null
+++ b/lib/observer/src/cdv_info_page.erl
@@ -0,0 +1,119 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_info_page).
+
+-behaviour(wx_object).
+
+-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_cast/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+%% Records
+-record(state,
+ {panel,
+ sizer,
+ fpanel,
+ callback,
+ trunc_warn=[]
+ }).
+
+start_link(ParentWin, Info) ->
+ wx_object:start_link(?MODULE, [ParentWin, Info], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([ParentWin, Callback]) when is_atom(Callback) ->
+ {InfoFields,Info,TW} = Callback:get_info(),
+ {Panel,Sizer,FPanel} = create_box(ParentWin,InfoFields,Info),
+ {Panel,#state{panel=Panel,
+ sizer=Sizer,
+ fpanel=FPanel,
+ callback=Callback,
+ trunc_warn=TW}};
+
+init([ParentWin, {InfoFields,Info,TW}]) ->
+ {Panel,Sizer,FPanel} = create_box(ParentWin,InfoFields,Info),
+ {Panel, #state{panel=Panel,
+ sizer=Sizer,
+ fpanel=FPanel,
+ trunc_warn=TW}}.
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info(active, State) ->
+ crashdump_viewer_wx:set_status(State#state.trunc_warn),
+ {noreply, State};
+
+handle_info(not_active, #state{} = State) ->
+ {noreply, State};
+
+handle_info(new_dump, #state{callback=Callback,panel=Panel,
+ sizer=Sizer,fpanel=FPanel} = State) ->
+ {InfoFields,Info,TW} = Callback:get_info(),
+ NewFPanel =
+ wx:batch(
+ fun() ->
+ wxWindow:destroy(FPanel),
+ FP = create_field_panel(Panel,Sizer,InfoFields,Info),
+ wxSizer:layout(Sizer),
+ FP
+ end),
+ {noreply, State#state{fpanel=NewFPanel,trunc_warn=TW}};
+
+handle_info(Info, State) ->
+ io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast(Msg, State) ->
+ io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+handle_event(Event, State) ->
+ io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ {noreply, State}.
+
+%%%-----------------------------------------------------------------
+%%% Internal
+create_box(ParentWin,InfoFields,Info) ->
+ Panel = wxPanel:new(ParentWin),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ FPanel = create_field_panel(Panel,Sizer,InfoFields,Info),
+ wxPanel:setSizer(Panel, Sizer),
+ {Panel,Sizer,FPanel}.
+
+create_field_panel(Panel,Sizer,InfoFields,Info0) ->
+ Info = observer_lib:fill_info(InfoFields, Info0),
+ {FPanel, _FSizer, _Fields} = observer_lib:display_info(Panel,Info),
+ BorderFlags = ?wxLEFT bor ?wxRIGHT,
+ wxSizer:add(Sizer, FPanel, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
+ {proportion, 0}, {border, 5}]),
+ FPanel.
diff --git a/lib/observer/src/cdv_int_tab_wx.erl b/lib/observer/src/cdv_int_tab_wx.erl
new file mode 100644
index 0000000000..f039bb3502
--- /dev/null
+++ b/lib/observer/src/cdv_int_tab_wx.erl
@@ -0,0 +1,79 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_int_tab_wx).
+
+-export([get_info/0]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+get_info() ->
+ [{"Hash Tables",cdv_table_page,get_hash_info()},
+ {"Index Tables",cdv_table_page,get_index_info()},
+ {"Internal ETS Tables",cdv_table_page,get_internal_ets_info()}].
+
+%%%-----------------------------------------------------------------
+%%% Hash tables
+get_hash_info() ->
+ {ok,Info0,TW} = crashdump_viewer:hash_tables(),
+ Columns = hash_columns(),
+ Info = [crashdump_viewer:to_value_list(R) || R <- Info0],
+ {Columns,Info,TW}.
+
+hash_columns() ->
+ [{"Name", ?wxLIST_FORMAT_LEFT, 150},
+ {"Size", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Used", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Objects",?wxLIST_FORMAT_RIGHT, 100},
+ {"Depth", ?wxLIST_FORMAT_RIGHT, 100}].
+
+%%%-----------------------------------------------------------------
+%%% Index tables
+get_index_info() ->
+ {ok,Info0,TW} = crashdump_viewer:index_tables(),
+ Columns = index_columns(),
+ Info = [crashdump_viewer:to_value_list(R) || R <- Info0],
+ {Columns,Info,TW}.
+
+index_columns() ->
+ [{"Name", ?wxLIST_FORMAT_LEFT, 150},
+ {"Size", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Limit", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Used", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Rate", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Entries",?wxLIST_FORMAT_RIGHT, 100}].
+
+%%%-----------------------------------------------------------------
+%%% Internal ets tables
+get_internal_ets_info() ->
+ {ok,Info0,TW} = crashdump_viewer:internal_ets_tables(),
+ Columns = int_ets_columns(),
+ Info = [begin
+ [_,_|Data] = crashdump_viewer:to_value_list(R), %skip pid and slot
+ [Desc|Data]
+ end || {Desc,R} <- Info0],
+ {Columns,Info,TW}.
+
+int_ets_columns() ->
+ [{"Description", ?wxLIST_FORMAT_LEFT, 170},
+ {"Id", ?wxLIST_FORMAT_LEFT, 80},
+ {"Name", ?wxLIST_FORMAT_LEFT, 80},
+ {"Type", ?wxLIST_FORMAT_LEFT, 80},
+ {"Buckets", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Objects", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Memory", ?wxLIST_FORMAT_RIGHT, 80}].
diff --git a/lib/observer/src/cdv_mem_wx.erl b/lib/observer/src/cdv_mem_wx.erl
new file mode 100644
index 0000000000..413c7de761
--- /dev/null
+++ b/lib/observer/src/cdv_mem_wx.erl
@@ -0,0 +1,78 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_mem_wx).
+
+-export([get_info/0]).
+
+-include("crashdump_viewer.hrl").
+-include_lib("wx/include/wx.hrl").
+
+get_info() ->
+ {AllocInfo,AllocTW} = get_alloc_info(),
+ [{"Memory",cdv_info_page,get_mem_info()}
+ | [{Title,cdv_table_page,{Cols,Data,AllocTW}} ||
+ {Title,Cols,Data} <- AllocInfo]] ++
+ [{"Allocated Areas",cdv_table_page,get_area_info()}].
+
+
+%%%-----------------------------------------------------------------
+%%% Memory page
+get_mem_info() ->
+ {ok,Info,TW} = crashdump_viewer:memory(),
+ {[{"Memory Information",gen_mem_info_fields(Info)}],Info,TW}.
+
+gen_mem_info_fields([{Key,_}|T]) ->
+ [{upper(atom_to_list(Key)),{bytes,Key}}|gen_mem_info_fields(T)];
+gen_mem_info_fields([]) ->
+ [].
+
+upper(Key) ->
+ string:join([string:to_upper([H]) ++ T ||
+ [H|T] <- string:tokens(Key,"_")]," ").
+
+
+%%%-----------------------------------------------------------------
+%%% Allocated areas page
+get_area_info() ->
+ {ok,Info0,TW} = crashdump_viewer:allocated_areas(),
+ Info = [tuple_to_list(R) || R <- Info0],
+ {area_columns(),Info,TW}.
+
+area_columns() ->
+ [{"", ?wxLIST_FORMAT_LEFT, 150},
+ {"Allocated (bytes)",?wxLIST_FORMAT_RIGHT, 150},
+ {"Used (bytes)", ?wxLIST_FORMAT_RIGHT, 150}].
+
+%%%-----------------------------------------------------------------
+%%% Allocator page
+get_alloc_info() ->
+ {ok,Info,TW} = crashdump_viewer:allocator_info(),
+ {fix_alloc(Info),TW}.
+
+fix_alloc([{Title,Columns,Data}|Tables]) ->
+ [{Title,alloc_columns(Columns),
+ [[Key|Values] || {Key,Values} <- Data]} |
+ fix_alloc(Tables)];
+fix_alloc([{Title,[{_,V}|_]=Data}|Tables]) ->
+ fix_alloc([{Title,lists:duplicate(length(V),[]),Data}|Tables]);
+fix_alloc([]) ->
+ [].
+
+alloc_columns(Columns) ->
+ [{"", ?wxLIST_FORMAT_LEFT, 200} |
+ [{Column, ?wxLIST_FORMAT_RIGHT, 150} || Column <- Columns]].
diff --git a/lib/observer/src/cdv_mod_wx.erl b/lib/observer/src/cdv_mod_wx.erl
new file mode 100644
index 0000000000..601da1f4e8
--- /dev/null
+++ b/lib/observer/src/cdv_mod_wx.erl
@@ -0,0 +1,103 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_mod_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1,
+ get_details/1,
+ detail_pages/0,
+ format/1]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Defines
+-define(COL_ID, 0).
+-define(COL_CUR, ?COL_ID+1).
+-define(COL_OLD, ?COL_CUR+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_ID);
+col_to_elem(?COL_ID) -> #loaded_mod.mod;
+col_to_elem(?COL_CUR) -> #loaded_mod.current_size;
+col_to_elem(?COL_OLD) -> #loaded_mod.old_size.
+
+col_spec() ->
+ [{"Module", ?wxLIST_FORMAT_LEFT, 300},
+ {"Current size", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Old size", ?wxLIST_FORMAT_RIGHT, 80}].
+
+get_info(_) ->
+ {ok,Info,TW} = crashdump_viewer:loaded_modules(),
+ {Info,TW}.
+
+get_detail_cols(_) ->
+ {[?COL_ID],true}.
+
+%% Callbacks for cdv_detail_win
+get_details(Id) ->
+ {ok,Info,TW} = crashdump_viewer:loaded_mod_details(Id),
+ Proplist = crashdump_viewer:to_proplist(record_info(fields,loaded_mod),Info),
+ Title = io_lib:format("~s",[Info#loaded_mod.mod]),
+ {ok,{Title,Proplist,TW}}.
+
+detail_pages() ->
+ [{simple, "General Information", fun init_gen_page/3},
+ {simple, "Current Attributes", fun init_curr_attr_page/3},
+ {simple, "Current Compilation Info", fun init_curr_comp_page/3},
+ {simple, "Old Attributes", fun init_old_attr_page/3},
+ {simple, "Old Compilation Info", fun init_old_comp_page/3}].
+
+init_gen_page(Parent, _Id, Info) ->
+ Fields = info_fields(),
+ cdv_detail_win:init_detail_page(Parent, Fields, Info).
+
+init_curr_attr_page(Parent, _Id, Info) ->
+ init_info_page(Parent, proplists:get_value(current_attrib,Info)).
+
+init_curr_comp_page(Parent, _Id, Info) ->
+ init_info_page(Parent, proplists:get_value(current_comp_info,Info)).
+
+init_old_attr_page(Parent, _Id, Info) ->
+ init_info_page(Parent, proplists:get_value(old_attrib,Info)).
+
+init_old_comp_page(Parent, _Id, Info) ->
+ init_info_page(Parent, proplists:get_value(old_comp_info,Info)).
+
+init_info_page(Parent, undefined) ->
+ init_info_page(Parent, "");
+init_info_page(Parent, String) ->
+ Html = crashdump_viewer_html:plain_page(String),
+ observer_lib:html_window(Parent,Html).
+
+format({Bin,q}) when is_binary(Bin) ->
+ [$'|binary_to_list(Bin)];
+format({Bin,nq}) when is_binary(Bin) ->
+ lists:flatten(io_lib:format("~ts",[Bin]));
+format(D) ->
+ D.
+
+%%%-----------------------------------------------------------------
+%%% Internal
+info_fields() ->
+ [{"Overview",
+ [{"Name", mod},
+ {"Current Size", current_size},
+ {"Old Size", old_size}]}].
diff --git a/lib/observer/src/cdv_multi_panel.erl b/lib/observer/src/cdv_multi_panel.erl
new file mode 100644
index 0000000000..3744863710
--- /dev/null
+++ b/lib/observer/src/cdv_multi_panel.erl
@@ -0,0 +1,190 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_multi_panel).
+
+-behaviour(wx_object).
+
+-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_cast/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+%% Records
+-record(state,
+ {main_panel,
+ main_sizer,
+ menu,
+ menu_sizer,
+ callback,
+ pages,
+ dyn_panel,
+ dyn_sizer,
+ dyn_page
+ }).
+
+start_link(Notebook, Callback) ->
+ wx_object:start_link(?MODULE, [Notebook, Callback], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([Notebook, Callback]) ->
+ Pages = Callback:get_info(),
+ MainPanel = wxPanel:new(Notebook),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ LeftMenuSizer = wxStaticBoxSizer:new(?wxVERTICAL,MainPanel,
+ [{label,"Please select"}]),
+ LeftMenu = wxListBox:new(MainPanel,?wxID_ANY,
+ [{style,?wxLB_SINGLE},
+ {choices,[T || {T,_,_} <- Pages]}]),
+ wxListBox:setSelection(LeftMenu,0),
+ wxListBox:connect(LeftMenu, command_listbox_selected),
+ wxSizer:add(LeftMenuSizer,LeftMenu,[{flag,?wxEXPAND},{proportion,2}]),
+
+ DynPanel = wxScrolledWindow:new(MainPanel),
+ wxScrolledWindow:enableScrolling(DynPanel,true,true),
+ wxScrolledWindow:setScrollbars(DynPanel,1,1,0,0),
+
+ BorderFlags = ?wxLEFT bor ?wxRIGHT,
+ wxSizer:add(Sizer, LeftMenuSizer,
+ [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
+ {proportion, 0}, {border, 5}]),
+ wxSizer:add(Sizer, DynPanel, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
+ {proportion, 1}, {border, 5}]),
+ wxPanel:setSizer(MainPanel, Sizer),
+
+ {MainPanel, #state{main_panel=MainPanel,
+ main_sizer=Sizer,
+ menu=LeftMenu,
+ menu_sizer=LeftMenuSizer,
+ callback=Callback,
+ pages=Pages,
+ dyn_panel=DynPanel
+ }}.
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info(active, State) ->
+ NewState =
+ wx:batch(
+ fun() ->
+ update_dyn_page(State)
+ end),
+ {noreply, NewState};
+
+handle_info(not_active, State) ->
+ {noreply, State};
+
+handle_info(new_dump, State) ->
+ NewState =
+ wx:batch(
+ fun() ->
+ update_left_menu(State)
+ end),
+ {noreply, NewState};
+
+handle_info(Info, State) ->
+ io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast(Msg, State) ->
+ io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+handle_event(#wx{event=#wxCommand{type=command_listbox_selected,
+ cmdString=[]}},
+ State) ->
+ %% For some reason, the listbox sometimes gets an "unselect"
+ %% command like this during termination. Ignore!
+ {noreply, State};
+
+handle_event(#wx{event=#wxCommand{type=command_listbox_selected,
+ cmdString=_DynName}},
+ State) ->
+ NewState =
+ wx:batch(fun() ->
+ update_dyn_page(State)
+ end),
+ {noreply,NewState};
+
+handle_event(Event, State) ->
+ io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ {noreply, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+update_left_menu(#state{main_panel=Panel,
+ callback=Callback,
+ menu=OldMenu,
+ menu_sizer=MenuSizer} = State) ->
+ Pages = Callback:get_info(),
+ wxListBox:disconnect(OldMenu),
+ wxWindow:destroy(OldMenu),
+ NewMenu = wxListBox:new(Panel,?wxID_ANY,
+ [{style,?wxLB_SINGLE},
+ {choices,[T || {T,_,_} <- Pages]}]),
+ wxListBox:setSelection(NewMenu,0),
+ wxListBox:connect(NewMenu, command_listbox_selected),
+ wxSizer:add(MenuSizer,NewMenu,[{flag,?wxEXPAND},{proportion,2}]),
+ wxSizer:layout(MenuSizer),
+ State#state{pages=Pages,menu=NewMenu}.
+
+update_dyn_page(#state{dyn_page=undefined} = State) ->
+ load_dyn_page(State);
+update_dyn_page(#state{dyn_page=OldDynPage,
+ dyn_sizer=OldDynSizer} = State) ->
+ wxSizer:detach(OldDynSizer,OldDynPage),
+ wxWindow:destroy(OldDynPage),
+ load_dyn_page(State).
+
+load_dyn_page(#state{main_sizer=MainSizer,
+ dyn_panel=DynPanel,
+ menu=Menu,
+ pages=Pages} = State) ->
+ {Page,Sizer} =
+ wx:batch(fun() ->
+ wxWindow:freeze(DynPanel),
+ Name = wxListBox:getStringSelection(Menu),
+ Res = load_dyn_page(DynPanel,Name,Pages),
+ wxSizer:layout(MainSizer),
+ wxWindow:thaw(DynPanel),
+ Res
+ end),
+ wx_object:get_pid(Page) ! active,
+ State#state{dyn_page=Page,dyn_sizer=Sizer}.
+
+load_dyn_page(Panel,Name,Pages) ->
+ Sizer = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label,Name}]),
+
+ {_,Callback,Info} = lists:keyfind(Name,1,Pages),
+ DynPage = Callback:start_link(Panel,Info),
+
+ wxSizer:add(Sizer,DynPage,[{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxPanel:setSizerAndFit(Panel,Sizer,[{deleteOld,true}]),
+ {DynPage,Sizer}.
diff --git a/lib/observer/src/cdv_port_wx.erl b/lib/observer/src/cdv_port_wx.erl
new file mode 100644
index 0000000000..ca591aeaaa
--- /dev/null
+++ b/lib/observer/src/cdv_port_wx.erl
@@ -0,0 +1,102 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_port_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1,
+ get_details/1,
+ detail_pages/0,
+ format/1]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Columns
+-define(COL_ID, 0).
+-define(COL_CONN, ?COL_ID+1).
+-define(COL_NAME, ?COL_CONN+1).
+-define(COL_CTRL, ?COL_NAME+1).
+-define(COL_SLOT, ?COL_CTRL+1).
+
+
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_ID);
+col_to_elem(?COL_ID) -> #port.id;
+col_to_elem(?COL_CONN) -> #port.connected;
+col_to_elem(?COL_NAME) -> #port.name;
+col_to_elem(?COL_CTRL) -> #port.controls;
+col_to_elem(?COL_SLOT) -> #port.slot.
+
+col_spec() ->
+ [{"Id", ?wxLIST_FORMAT_LEFT, 120},
+ {"Connected", ?wxLIST_FORMAT_LEFT, 120},
+ {"Name", ?wxLIST_FORMAT_LEFT, 200},
+ {"Controls", ?wxLIST_FORMAT_LEFT, 250},
+ {"Slot", ?wxLIST_FORMAT_RIGHT, 50}].
+
+get_info(_) ->
+ {ok,Info,TW} = crashdump_viewer:ports(),
+ {Info,TW}.
+
+get_detail_cols(_) ->
+ {[?COL_ID,?COL_CONN],true}.
+
+%% Callbacks for cdv_detail_win
+get_details(Id) ->
+ case crashdump_viewer:port(Id) of
+ {ok,Info,TW} ->
+ Proplist =
+ crashdump_viewer:to_proplist(record_info(fields,port),Info),
+ {ok,{Id,Proplist,TW}};
+ {error,{other_node,NodeId}} ->
+ Info = "The port you are searching for was residing on "
+ "a remote node. No port information is available. "
+ "Show information about the remote node?",
+ {yes_no, Info, fun()->cdv_virtual_list:start_detail_win(NodeId) end};
+ {error,not_found} ->
+ Info = "The port you are searching for could not be found.",
+ {info,Info}
+ end.
+
+detail_pages() ->
+ [{simple, "General Information", fun init_gen_page/3}].
+
+init_gen_page(Parent, _Id, Info) ->
+ Fields = info_fields(),
+ cdv_detail_win:init_detail_page(Parent, Fields, Info).
+
+format({I1,I2}) ->
+ "#Port<"++integer_to_list(I1) ++ "." ++ integer_to_list(I2) ++ ">";
+format(D) ->
+ D.
+
+
+%%%-----------------------------------------------------------------
+%%% Internal
+info_fields() ->
+ [{"Overview",
+ [{"Name", name},
+ {"Connected", connected},
+ {"Slot", slot},
+ {"Controls", controls}]},
+ {scroll_boxes,
+ [{"Links",1,{click,links}},
+ {"Monitors",1,{click,monitors}}]}].
diff --git a/lib/observer/src/cdv_proc_wx.erl b/lib/observer/src/cdv_proc_wx.erl
new file mode 100644
index 0000000000..a0f42b5e6b
--- /dev/null
+++ b/lib/observer/src/cdv_proc_wx.erl
@@ -0,0 +1,152 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_proc_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1,
+ get_details/1,
+ detail_pages/0]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Columns
+-define(COL_ID, 0).
+-define(COL_NAME, ?COL_ID+1).
+-define(COL_STATE,?COL_NAME+1).
+-define(COL_REDS, ?COL_STATE+1).
+-define(COL_MEM, ?COL_REDS+1).
+-define(COL_MSG, ?COL_MEM+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_ID);
+col_to_elem(?COL_ID) -> #proc.pid;
+col_to_elem(?COL_NAME) -> #proc.name;
+col_to_elem(?COL_STATE) -> #proc.state;
+col_to_elem(?COL_MEM) -> #proc.memory;
+col_to_elem(?COL_REDS) -> #proc.reds;
+col_to_elem(?COL_MSG) -> #proc.msg_q_len.
+
+col_spec() ->
+ [{"Pid", ?wxLIST_FORMAT_CENTRE, 120},
+ {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 250},
+ {"State", ?wxLIST_FORMAT_LEFT, 100},
+ {"Reds", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Memory", ?wxLIST_FORMAT_RIGHT, 100},
+ {"MsgQ", ?wxLIST_FORMAT_RIGHT, 100}].
+
+get_info(_) ->
+ {ok,Info,TW} = crashdump_viewer:processes(),
+ {Info,TW}.
+
+get_detail_cols(_) ->
+ {[?COL_ID],true}.
+
+%% Callbacks for cdv_detail_win
+get_details(Id) ->
+ case crashdump_viewer:proc_details(Id) of
+ {ok,Info,TW} ->
+ Proplist =
+ crashdump_viewer:to_proplist(record_info(fields,proc),Info),
+ Title = io_lib:format("~s (~p)",[Info#proc.name, Id]),
+ {ok,{Title,Proplist,TW}};
+ {error,{other_node,NodeId}} ->
+ Info = "The process you are searching for was residing on "
+ "a remote node. No process information is available. "
+ "Show information about the remote node?",
+ {yes_no, Info, fun()->cdv_virtual_list:start_detail_win(NodeId) end};
+ {error,not_found} ->
+ Info = "The process you are searching for could not be found.",
+ {info,Info}
+ end.
+
+detail_pages() ->
+ [{simple, "General Information", fun init_gen_page/3},
+ {simple, "Messages", fun init_message_page/3},
+ {simple, "Dictionary", fun init_dict_page/3},
+ {simple, "Stack Dump", fun init_stack_page/3},
+ {list, "ETS tables", fun init_ets_page/3},
+ {list, "Timers", fun init_timer_page/3}].
+
+init_gen_page(Parent, _Pid, Info) ->
+ Fields = info_fields(),
+ cdv_detail_win:init_detail_page(Parent, Fields, Info).
+
+init_message_page(Parent, Pid, _Info) ->
+ init_memory_page(Parent, Pid, "MsgQueue").
+
+init_dict_page(Parent, Pid, _Info) ->
+ init_memory_page(Parent, Pid, "Dictionary").
+
+init_stack_page(Parent, Pid, _Info) ->
+ init_memory_page(Parent, Pid, "StackDump").
+
+init_memory_page(Parent, Pid, What) ->
+ Html =
+ case crashdump_viewer:expand_memory(Pid,What) of
+ {ok,Memory} ->
+ crashdump_viewer_html:expanded_memory(What,Memory);
+ {error,Reason} ->
+ crashdump_viewer_html:warning(Reason)
+ end,
+ observer_lib:html_window(Parent,Html).
+
+init_ets_page(Parent, Pid, _Info) ->
+ cdv_virtual_list:start_link(Parent, cdv_ets_wx, Pid).
+
+init_timer_page(Parent, Pid, _Info) ->
+ cdv_virtual_list:start_link(Parent, cdv_timer_wx, Pid).
+
+%%%-----------------------------------------------------------------
+%%% Internal
+info_fields() ->
+ [{"Overview",
+ [{"Initial Call", init_func},
+ {dynamic, current_func},
+ {"Registered Name", name},
+ {"Status", state},
+ {"Started", start_time},
+ {"Parent", {click,parent}},
+ {"Message Queue Len",msg_q_len},
+ {"Reductions", reds},
+ {"Program counter", prog_count},
+ {"Continuation pointer",cp},
+ {"Arity",arity}]},
+ {scroll_boxes,
+ [{"Last Calls",1,{plain,last_calls}}]},
+ {scroll_boxes,
+ [{"Links",1,{click,links}},
+ {"Monitors",2,{click,monitors}},
+ {"Monitored By",2,{click,mon_by}}]},
+ {"Memory and Garbage Collection",
+ [{"Memory", memory},
+ {"Stack and Heap", stack_heap},
+ {"Old Heap", old_heap},
+ {"Heap Unused", heap_unused},
+ {"Old Heap Unused", old_heap_unused},
+ {"Number of Heap Fragements", num_heap_frag},
+ {"Heap Fragment Data",heap_frag_data},
+ {"New Heap Start", new_heap_start},
+ {"New Heap Top", new_heap_top},
+ {"Stack Top", stack_top},
+ {"Stack End", stack_end},
+ {"Old Heap Start", old_heap_start},
+ {"Old Heap Top", old_heap_top},
+ {"Old Heap End", old_heap_end}]}].
diff --git a/lib/observer/src/cdv_table_page.erl b/lib/observer/src/cdv_table_page.erl
new file mode 100644
index 0000000000..fecc20b95c
--- /dev/null
+++ b/lib/observer/src/cdv_table_page.erl
@@ -0,0 +1,109 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_table_page).
+
+-behaviour(wx_object).
+
+-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_cast/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+%% Records
+-record(state,
+ {trunc_warn=[]}).
+
+start_link(ParentWin, Info) ->
+ wx_object:start_link(?MODULE, [ParentWin, Info], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([ParentWin, Callback]) when is_atom(Callback) ->
+ {ok,TableInfo} = Callback:get_info(),
+ init([ParentWin, TableInfo]);
+
+init([ParentWin, {ColumnSpec,Info,TW}]) ->
+ Style0 = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES,
+ Style =
+ case lists:all(fun({"",_,_}) -> true; (_) -> false end, ColumnSpec) of
+ true -> Style0 bor ?wxLC_NO_HEADER;
+ false -> Style0
+ end,
+ Grid = wxListCtrl:new(ParentWin, [{style, Style}]),
+ Li = wxListItem:new(),
+ AddListEntry = fun({Name, Align, DefSize}, Col) ->
+ wxListItem:setText(Li, Name),
+ wxListItem:setAlign(Li, Align),
+ wxListCtrl:insertColumn(Grid, Col, Li),
+ wxListCtrl:setColumnWidth(Grid, Col, DefSize),
+ Col + 1
+ end,
+ lists:foldl(AddListEntry, 0, ColumnSpec),
+ wxListItem:destroy(Li),
+ Insert = fun(RowData, Row) ->
+ wxListCtrl:insertItem(Grid, Row, ""),
+ set_items(Grid,Row,RowData,0),
+ Row + 1
+ end,
+ lists:foldl(Insert, 0, Info),
+ {Grid, #state{trunc_warn=TW}}.
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info(active, State) ->
+ crashdump_viewer_wx:set_status(State#state.trunc_warn),
+ {noreply, State};
+
+handle_info(not_active, #state{} = State) ->
+ {noreply, State};
+
+handle_info(Info, State) ->
+ io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast(Msg, State) ->
+ io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+handle_event(Event, State) ->
+ io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ {noreply, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+set_items(Grid,Row,[Col|Cols],ColN) ->
+ Str = case Col of
+ undefined -> "";
+ _ -> observer_lib:to_str(Col)
+ end,
+ wxListCtrl:setItem(Grid, Row, ColN, Str),
+ set_items(Grid,Row,Cols,ColN+1);
+set_items(_,_,[],_) ->
+ ok.
diff --git a/lib/observer/src/cdv_timer_wx.erl b/lib/observer/src/cdv_timer_wx.erl
new file mode 100644
index 0000000000..0bfd958a15
--- /dev/null
+++ b/lib/observer/src/cdv_timer_wx.erl
@@ -0,0 +1,51 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_timer_wx).
+
+-export([col_to_elem/1,
+ col_spec/0,
+ get_info/1,
+ get_detail_cols/1]).
+
+-include_lib("wx/include/wx.hrl").
+-include("crashdump_viewer.hrl").
+
+%% Defines
+-define(COL_OWNER, 0).
+-define(COL_MSG, ?COL_OWNER+1).
+-define(COL_TIME, ?COL_MSG+1).
+
+%% Callbacks for cdv_virtual_list
+col_to_elem(id) -> col_to_elem(?COL_OWNER);
+col_to_elem(?COL_OWNER) -> #timer.pid;
+col_to_elem(?COL_MSG) -> #timer.msg;
+col_to_elem(?COL_TIME) -> #timer.time.
+
+col_spec() ->
+ [{"Owner", ?wxLIST_FORMAT_LEFT, 150},
+ {"Message", ?wxLIST_FORMAT_LEFT, 500},
+ {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 150}].
+
+get_info(Owner) ->
+ {ok,Info,TW} = crashdump_viewer:timers(Owner),
+ {Info,TW}.
+
+get_detail_cols(all) ->
+ {[?COL_OWNER],false};
+get_detail_cols(_) ->
+ {[],false}.
diff --git a/lib/observer/src/cdv_virtual_list.erl b/lib/observer/src/cdv_virtual_list.erl
new file mode 100644
index 0000000000..03feadad45
--- /dev/null
+++ b/lib/observer/src/cdv_virtual_list.erl
@@ -0,0 +1,425 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_virtual_list).
+
+-behaviour(wx_object).
+
+-export([start_link/2, start_link/3, start_detail_win/1]).
+
+%% wx_object callbacks
+-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
+ handle_event/2, handle_cast/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+%% Defines
+-define(COL_ID, 0).
+-define(ID_DETAILS, 202).
+
+%% Records
+
+-record(sort,
+ {
+ sort_key,
+ sort_incr=true
+ }).
+
+-record(holder, {parent,
+ info,
+ last_row,
+ sort,
+ attrs,
+ callback
+ }).
+
+-record(state, {grid,
+ panel,
+ detail_wins=[],
+ holder,
+ callback,
+ trunc_warn=[],
+ menu_cols=[], % columns to show in right click menu
+ menu_items=[]}). % right click menu items for the selected row
+
+start_link(ParentWin, Callback) ->
+ wx_object:start_link({local,Callback},?MODULE,
+ [ParentWin, Callback, all], []).
+
+start_link(ParentWin, Callback, Owner) ->
+ wx_object:start_link(?MODULE, [ParentWin, Callback, Owner], []).
+
+start_detail_win(Id) ->
+ Callback =
+ case Id of
+ "<"++_ ->
+ cdv_proc_wx;
+ "#Port"++_ ->
+ cdv_port_wx;
+ _ ->
+ case catch list_to_integer(Id) of
+ NodeId when is_integer(NodeId) ->
+ cdv_dist_wx;
+ _ ->
+ cdv_mod_wx
+ end
+ end,
+ start_detail_win(Callback,Id).
+start_detail_win(Callback,Id) ->
+ wx_object:cast(Callback,{start_detail_win,Id}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+init([ParentWin, Callback, Owner]) ->
+ {Holder,TW} = spawn_table_holder(Callback, Owner),
+ Panel = wxPanel:new(ParentWin),
+ {Grid,MenuCols} = create_list_box(Panel, Holder, Callback, Owner),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 1},
+ {border,4}]),
+
+ wxWindow:setSizer(Panel, Sizer),
+
+ State = #state{grid=Grid,
+ panel=Panel,
+ holder=Holder,
+ callback=Callback,
+ trunc_warn=TW,
+ menu_cols=MenuCols
+ },
+ {Panel, State}.
+
+%% UI-creation
+
+create_list_box(Panel, Holder, Callback, Owner) ->
+ Style =
+ ?wxLC_SINGLE_SEL bor ?wxLC_REPORT bor ?wxLC_VIRTUAL bor
+ ?wxLC_HRULES bor ?wxHSCROLL bor ?wxVSCROLL,
+ ListCtrl = wxListCtrl:new(Panel, [{style, Style},
+ {onGetItemText,
+ fun(_, Row, Col) ->
+ call(Holder, {get_row, self(), Row, Col})
+ end},
+ {onGetItemAttr,
+ fun(_, Item) ->
+ call(Holder, {get_attr, self(), Item})
+ end}
+ ]),
+ Li = wxListItem:new(),
+ AddListEntry = fun({Name, Align, DefSize}, Col) ->
+ wxListItem:setText(Li, Name),
+ wxListItem:setAlign(Li, Align),
+ wxListCtrl:insertColumn(ListCtrl, Col, Li),
+ wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize),
+ Col + 1
+ end,
+ ListItems = Callback:col_spec(),
+ lists:foldl(AddListEntry, 0, ListItems),
+ wxListItem:destroy(Li),
+
+ wxListCtrl:setItemCount(ListCtrl, 0),
+ wxListCtrl:connect(ListCtrl, size, [{skip, true}]),
+ wxListCtrl:connect(ListCtrl, command_list_col_click),
+
+
+ %% If detail pages can be opened from this list - catch double
+ %% click and right click
+ DetailCols =
+ case catch Callback:get_detail_cols(Owner) of
+ {DC,DoubleClick} when is_list(DC), DC=/=[] ->
+ wxListCtrl:connect(ListCtrl, command_list_item_right_click),
+ if DoubleClick ->
+ wxListCtrl:connect(ListCtrl, command_list_item_activated);
+ true ->
+ ok
+ end,
+ DC;
+ _ ->
+ []
+ end,
+
+ {ListCtrl,DetailCols}.
+
+do_start_detail_win(undefined, State) ->
+ State;
+do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened,
+ callback=Callback}=State) ->
+ NewOpened =
+ case lists:keyfind(Id, 1, Opened) of
+ false ->
+ case cdv_detail_win:start(Id, Panel, Callback) of
+ {error, _} ->
+ Opened;
+ IW ->
+ [{Id, IW} | Opened]
+ end;
+ {_, IW} ->
+ wxFrame:raise(IW),
+ Opened
+ end,
+ State#state{detail_wins=NewOpened}.
+
+call(Holder, What) when is_atom(Holder) ->
+ call(whereis(Holder), What);
+call(Holder, What) when is_pid(Holder) ->
+ Ref = erlang:monitor(process, Holder),
+ Holder ! What,
+ receive
+ {'DOWN', Ref, _, _, _} -> "";
+ {Holder, Res} ->
+ erlang:demonitor(Ref),
+ Res
+ after 5000 ->
+ io:format("Hanging call ~p~n",[What]),
+ ""
+ end;
+call(_,_) ->
+ "".
+
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info({holder_updated, Count}, State=#state{grid=Grid}) ->
+ wxListCtrl:setItemCount(Grid, Count),
+ Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1),
+
+ {noreply, State};
+
+handle_info({detail_win_closed, Id},#state{detail_wins=Opened}=State) ->
+ Opened2 = lists:keydelete(Id, 1, Opened),
+ {noreply, State#state{detail_wins=Opened2}};
+
+handle_info(active, State) ->
+ crashdump_viewer_wx:set_status(State#state.trunc_warn),
+ {noreply, State};
+
+handle_info(not_active, State) ->
+ {noreply, State};
+
+handle_info(new_dump,
+ #state{grid=Grid,detail_wins=Opened,
+ holder=Holder,callback=Callback}=State) ->
+ lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened),
+ wxListCtrl:deleteAllItems(Grid),
+ Ref = erlang:monitor(process,Holder),
+ Holder ! stop,
+ receive {'DOWN',Ref,_,_,_} -> ok end,
+ {NewHolder,TW} = spawn_table_holder(Callback, all),
+ {noreply, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}};
+
+handle_info(Info, State) ->
+ io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(_Reason, #state{holder=Holder}) ->
+ Holder ! stop,
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast({start_detail_win,Id}, State) ->
+ State2 = do_start_detail_win(Id, State),
+ {noreply, State2};
+
+handle_cast(Msg, State) ->
+ io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(#wx{id=MenuId,
+ event=#wxCommand{type = command_menu_selected}},
+ #state{menu_items=MenuItems} = State) ->
+ case lists:keyfind(MenuId,1,MenuItems) of
+ {MenuId,Id} ->
+ start_detail_win(Id);
+ false ->
+ ok
+ end,
+ {noreply, State};
+
+handle_event(#wx{event=#wxSize{size={W,_}}},
+ #state{grid=Grid}=State) ->
+ observer_lib:set_listctrl_col_size(Grid, W),
+ {noreply, State};
+
+handle_event(#wx{event=#wxList{type=command_list_item_right_click,
+ itemIndex=Row}},
+ #state{panel=Panel, holder=Holder, menu_cols=MenuCols} = State) ->
+ Menu = wxMenu:new(),
+ MenuItems =
+ lists:flatmap(
+ fun(Col) ->
+ MenuId = ?ID_DETAILS + Col,
+ ColText = call(Holder, {get_row, self(), Row, Col}),
+ case ColText of
+ "[]" -> [];
+ _ ->
+ What =
+ case catch list_to_integer(ColText) of
+ NodeId when is_integer(NodeId) ->
+ "node " ++ ColText;
+ _ ->
+ ColText
+ end,
+ Text = "Properties for " ++ What,
+ wxMenu:append(Menu, MenuId, Text),
+ [{MenuId,ColText}]
+ end
+ end,
+ MenuCols),
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu),
+ {noreply,State#state{menu_items=MenuItems}};
+
+handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
+ #state{holder=Holder}=State) ->
+ Holder ! {change_sort, Col},
+ {noreply, State};
+
+handle_event(#wx{event=#wxList{type=command_list_item_activated,
+ itemIndex=Row}},
+ #state{holder=Holder} = State) ->
+ Id = call(Holder, {get_row, self(), Row, id}),
+ start_detail_win(Id),
+ {noreply, State};
+
+handle_event(Event, State) ->
+ io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]),
+ {noreply, State}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+spawn_table_holder(Callback, Owner) ->
+ {Info,TW} = Callback:get_info(Owner),
+ Attrs = observer_lib:create_attrs(),
+ Parent = self(),
+ Holder =
+ case Owner of
+ all ->
+ Name = list_to_atom(atom_to_list(Callback) ++ "__holder"),
+ spawn_link(
+ fun() ->
+ register(Name,self()),
+ init_table_holder(Parent, Attrs, Callback, Info)
+ end),
+ Name;
+ _ ->
+ spawn_link(
+ fun() ->
+ init_table_holder(Parent, Attrs, Callback, Info)
+ end)
+ end,
+ {Holder,TW}.
+
+init_table_holder(Parent, Attrs, Callback, Info0) ->
+ Info = array:from_list(Info0),
+ S = handle_update(Info, #holder{parent=Parent,
+ info=Info,
+ sort=#sort{sort_key=
+ Callback:col_to_elem(id)},
+ attrs=Attrs,
+ callback=Callback
+ }),
+ table_holder(S).
+
+table_holder(#holder{callback=Callback, attrs=Attrs}=S0) ->
+ receive
+ _M={get_row, From, Row, Col} ->
+ %% erlang:display(_M),
+ State = get_row(From, Row, Col, S0),
+ table_holder(State);
+ _M={get_attr, From, Row} ->
+ %% erlang:display(_M),
+ get_attr(From, Row, Attrs),
+ table_holder(S0);
+ _M={change_sort, Col} ->
+ %% erlang:display(_M),
+ State = change_sort(Callback:col_to_elem(Col), S0),
+ table_holder(State);
+ stop ->
+ ok;
+ What ->
+ io:format("Table holder got ~p~n",[What]),
+ table_holder(S0)
+ end.
+
+change_sort(Col, S0=#holder{parent=Parent, info=Info0, sort=Sort0}) ->
+ NRows = array:size(Info0),
+ InfoList0 = array:to_list(Info0),
+ {Sort, InfoList}=sort(Col, Sort0, InfoList0),
+ Info = array:from_list(InfoList),
+ Parent ! {holder_updated, NRows},
+ S0#holder{info=Info, last_row=undefined, sort=Sort}.
+
+handle_update(Info0, S0=#holder{parent=Parent, sort=Sort}) ->
+ NRows = array:size(Info0),
+ InfoList0 = array:to_list(Info0),
+ {_Sort, InfoList} = do_sort(Sort, InfoList0),
+ Info = array:from_list(InfoList),
+ Parent ! {holder_updated, NRows},
+ S0#holder{info=Info, last_row=undefined}.
+
+sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) ->
+ do_sort(Opt#sort{sort_incr=not Bool}, Table);
+sort(Col, Sort,Table) ->
+ do_sort(Sort#sort{sort_key=Col, sort_incr=true}, Table).
+
+do_sort(Sort=#sort{sort_key=Col, sort_incr=true}, Table) ->
+ {Sort, lists:keysort(Col, Table)};
+do_sort(Sort=#sort{sort_key=Col, sort_incr=false}, Table) ->
+ {Sort, lists:reverse(lists:keysort(Col, Table))}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_cell_data(Callback, ColNo, RowInfo) ->
+ case element(Callback:col_to_elem(ColNo), RowInfo) of
+ undefined -> "";
+ Cell -> try Callback:format(Cell) catch error:undef -> Cell end
+ end.
+
+get_row(From, Row, Col,
+ #holder{callback=Callback, last_row={Row,RowInfo}}=State) ->
+ Data = get_cell_data(Callback, Col, RowInfo),
+ From ! {self(), observer_lib:to_str(Data)},
+ State;
+get_row(From, Row, Col, #holder{callback=Callback, info=Info}=S0) ->
+ {Data,State} =
+ case Row >= array:size(Info) of
+ true ->
+ {"",S0};
+ false ->
+ RowInfo = array:get(Row, Info),
+ CellData = get_cell_data(Callback, Col, RowInfo),
+ {CellData,S0#holder{last_row={Row,RowInfo}}}
+ end,
+ From ! {self(), observer_lib:to_str(Data)},
+ State.
+
+get_attr(From, Row, Attrs) ->
+ Attribute = case Row rem 2 =:= 0 of
+ true -> Attrs#attrs.even;
+ false -> Attrs#attrs.odd
+ end,
+ From ! {self(), Attribute}.
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index e7d71c581e..bd3d4cc72b 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -20,80 +20,55 @@
%%
%% This module is the main module in the crashdump viewer. It implements
-%% the server started by webtool and the API for the crashdump viewer tool.
-%%
-%% All functions in the API except configData/0 and start_link/0 are
-%% called from HTML pages via erl_scheme (mod_esi).
+%% the server backend for the crashdump viewer tool.
%%
%% Tables
%% ------
-%% cdv_menu_table: This table holds the menu which is presented in the left
-%% frame of the crashdump viewer page. Each element in the table represents
-%% one meny item, and the state of the item indicates if it is presently
-%% visible or not.
-%%
-%% cdv_dump_index_table: This table holds all tags read from the crashdump.
-%% Each tag indicates where the information about a specific item starts.
-%% The table entry for a tag includes the start position for this
-%% item-information. All tags start with a "=" at the beginning of
-%% a line.
+%% cdv_dump_index_table: This table holds all tags read from the
+%% crashdump. Each tag indicates where the information about a
+%% specific item starts. The table entry for a tag includes the start
+%% position for this item-information. In a crash dump file, all tags
+%% start with a "=" at the beginning of a line.
%%
%% Process state
%% -------------
%% file: The name of the crashdump currently viewed.
%% dump_vsn: The version number of the crashdump
-%% procs_summary: Process summary represented by a list of
-%% #proc records. This is used for efficiency reasons when sorting the
-%% process summary table instead of reading all processes from the
-%% dump again. Note that if the dump contains more than
-%% ?max_sort_process_num processes, the sort functionality is not
-%% available, and the procs_summary field in the state will have the
-%% value 'too_many'.
-%% sorted: string(), indicated what item was last sorted in process summary.
-%% This is needed so reverse sorting can be done.
-%% shared_heap: 'true' if crashdump comes from a system running shared heap,
-%% else 'false'.
%% wordsize: 4 | 8, the number of bytes in a word.
%% binaries: a gb_tree containing binaries or links to binaries in the dump
%%
%% User API
--export([start/0,stop/0,script_start/0,script_start/1]).
-
-%% Webtool API
--export([configData/0,
- start_link/0]).
--export([start_page/2,
- read_file_frame/2,
- read_file/2,
- redirect/2,
- filename_frame/2,
- menu_frame/2,
- initial_info_frame/2,
- toggle/2,
- general_info/2,
- processes/3,
- proc_details/2,
- port/2,
- ports/3,
- ets_tables/3,
- internal_ets_tables/2,
- timers/3,
- fun_table/3,
- atoms/3,
- dist_info/2,
- loaded_modules/3,
- loaded_mod_details/2,
- memory/2,
- allocated_areas/2,
- allocator_info/2,
- hash_tables/2,
- index_tables/2,
- sort_procs/3,
- expand/2,
- expand_binary/2,
+-export([start/0,start/1,stop/0,script_start/0,script_start/1]).
+
+%% GUI API
+-export([start_link/0,
+ get_progress/0]).
+-export([read_file/1,
+ init_general_info/0,
+ processes/0,
+ proc_details/1,
+ port/1,
+ ports/0,
+ ets_tables/1,
+ internal_ets_tables/0,
+ timers/1,
+ funs/0,
+ atoms/0,
+ dist_info/0,
+ node_info/1,
+ loaded_modules/0,
+ loaded_mod_details/1,
+ memory/0,
+ allocated_areas/0,
+ allocator_info/0,
+ hash_tables/0,
+ index_tables/0,
+ expand_binary/1,
expand_memory/2]).
+%% Library function
+-export([to_proplist/2, to_value_list/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -106,28 +81,13 @@
-include_lib("kernel/include/file.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
--define(START_PAGE,"/cdv_erl/crashdump_viewer/start_page").
--define(READ_FILE_PAGE,"/cdv_erl/crashdump_viewer/read_file?path=").
-define(SERVER, crashdump_viewer_server).
-define(call_timeout,3600000).
-define(chunk_size,1000). % number of bytes read from crashdump at a time
-define(max_line_size,100). % max number of bytes (i.e. characters) the
% line_head/1 function can return
--define(max_display_size,500). % max number of bytes that will be directly
- % displayed. If e.g. msg_q is longer than
- % this, it must be explicitly expanded.
-define(max_display_binary_size,50). % max size of a binary that will be
% directly displayed.
--define(max_sort_process_num,10000). % Max number of processes that allows
- % sorting. If more than this number of
- % processes exist, they will be displayed
- % in the order they are found in the log.
--define(items_chunk_size,?max_sort_process_num). % Number of items per chunk
- % when page of many items
- % is displayed, e.g. processes,
- % timers, funs...
- % Must be equal to
- % ?max_sort_process_num!
-define(not_available,"N/A").
@@ -136,7 +96,6 @@
-define(allocator,allocator).
-define(atoms,atoms).
-define(binary,binary).
--define(debug_proc_dictionary,debug_proc_dictionary).
-define(ende,ende).
-define(erl_crash_dump,erl_crash_dump).
-define(ets,ets).
@@ -164,8 +123,7 @@
-define(visible_node,visible_node).
--record(state,{file,dump_vsn,procs_summary,sorted,shared_heap=false,
- wordsize=4,num_atoms="unknown",binaries,bg_status}).
+-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown",binaries}).
%%%-----------------------------------------------------------------
%%% Debugging
@@ -198,133 +156,72 @@ stop_debug() ->
%%%-----------------------------------------------------------------
%%% User API
start() ->
- webtool:start(),
- receive after 1000 -> ok end,
- webtool:start_tools([],"app=crashdump_viewer"),
- receive after 1000 -> ok end,
- ok.
+ start(undefined).
+start(File) ->
+ crashdump_viewer_wx:start(File).
stop() ->
- webtool:stop_tools([],"app=crashdump_viewer"),
- webtool:stop().
+ case whereis(?SERVER) of
+ undefined ->
+ ok;
+ Pid ->
+ Ref = erlang:monitor(process,Pid),
+ cast(stop),
+ receive {'DOWN', Ref, process, Pid, _} -> ok end
+ end.
%%%-----------------------------------------------------------------
%%% Start crashdump_viewer via the cdv script located in
%%% $OBSERVER_PRIV_DIR/bin
script_start() ->
- usage().
-script_start([File]) ->
- DefaultBrowser =
- case os:type() of
- {win32,_} -> iexplore;
- {unix,darwin} -> open;
- _ -> firefox
- end,
- script_start([File,DefaultBrowser]);
-script_start([FileAtom,Browser]) ->
+ do_script_start(fun() -> start() end),
+ erlang:halt().
+script_start([FileAtom]) ->
File = atom_to_list(FileAtom),
case filelib:is_regular(File) of
true ->
- io:format("Starting crashdump_viewer...\n"),
- start(),
- io:format("Reading crashdump..."),
- read_file(File),
- redirect([],[]),
- io:format("done\n"),
- start_browser(Browser);
+ do_script_start(fun() -> start(File) end);
false ->
io:format("cdv error: the given file does not exist\n"),
usage()
- end.
-
-start_browser(Browser) ->
- PortStr = integer_to_list(gen_server:call(web_tool,get_port)),
- Url = "http://localhost:" ++ PortStr ++ ?START_PAGE,
- {OSType,_} = os:type(),
- case Browser of
- none ->
- ok;
- iexplore when OSType == win32->
- io:format("Starting internet explorer...\n"),
- {ok,R} = win32reg:open(""),
- Key="\\local_machine\\SOFTWARE\\Microsoft\\IE Setup\\Setup",
- win32reg:change_key(R,Key),
- {ok,Val} = win32reg:value(R,"Path"),
- IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"),
- os:cmd("\"" ++ IExplore ++ "\" " ++ Url);
- _ when OSType == win32 ->
- io:format("Starting ~w...\n",[Browser]),
- os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url);
- B when B==firefox; B==mozilla ->
- io:format("Sending URL to ~w...",[Browser]),
- BStr = atom_to_list(Browser),
- SendCmd = BStr ++ " -raise -remote \'openUrl(" ++ Url ++ ")\'",
- Port = open_port({spawn,SendCmd},[exit_status]),
- receive
- {Port,{exit_status,0}} ->
- io:format("done\n");
- {Port,{exit_status,_Error}} ->
- io:format(" not running, starting ~w...\n",[Browser]),
- os:cmd(BStr ++ " " ++ Url)
- after 5000 ->
- io:format(" failed, starting ~w...\n",[Browser]),
- erlang:port_close(Port),
- os:cmd(BStr ++ " " ++ Url)
- end;
- _ ->
- io:format("Starting ~w...\n",[Browser]),
- os:cmd(atom_to_list(Browser) ++ " " ++ Url)
end,
- ok.
+ erlang:halt();
+script_start(_) ->
+ usage(),
+ erlang:halt().
+
+do_script_start(StartFun) ->
+ process_flag(trap_exit,true),
+ case StartFun() of
+ ok ->
+ case whereis(cdv_wx) of
+ Pid when is_pid(Pid) ->
+ link(Pid),
+ receive
+ {'EXIT', Pid, normal} ->
+ ok;
+ {'EXIT', Pid, Reason} ->
+ io:format("\ncdv crash: ~p\n",[Reason])
+ end;
+ _ ->
+ io:format("\ncdv crash: ~p\n",[unknown_reason])
+ end;
+ Error ->
+ io:format("\ncdv start failed: ~p\n",[Error])
+ end.
usage() ->
io:format(
- "\nusage: cdv file [ browser ]\n"
+ "usage: cdv [file]\n"
"\tThe \'file\' must be an existing erlang crash dump.\n"
- "\tDefault browser is \'iexplore\' (Internet Explorer) on Windows,\n"
- "\t\'open\' on Mac OS X, or else \'firefox\'.\n",
+ "\tIf omitted a file dialog will be opened.\n",
[]).
-
-
-
-%%%-----------------------------------------------------------------
-%%% Return config data used by webtool
-configData() ->
- Dir = filename:join(code:priv_dir(observer),"crashdump_viewer"),
- {crashdump_viewer,
- [{web_data,{"CrashDumpViewer",?START_PAGE}},
- {alias,{"/crashdump_viewer",Dir}},
- {alias,{"/crashdump_erts_doc",erts_docdir()}},
- {alias,{"/crashdump_doc",cdv_docdir()}},
- {alias,{erl_alias,"/cdv_erl",[?MODULE]}},
- {start,{child,{{local,?SERVER},
- {?MODULE,start_link,[]},
- permanent,100,worker,[?MODULE]}}}
- ]}.
-
-erts_docdir() ->
- ErtsVsn = erlang:system_info(version),
- RootDir = code:root_dir(),
- VsnErtsDir = filename:join(RootDir,"erts-"++ErtsVsn),
- DocDir = filename:join(["doc","html"]),
- case filelib:is_dir(VsnErtsDir) of
- true ->
- filename:join(VsnErtsDir,DocDir);
- false ->
- %% So this can be run in clearcase
- filename:join([RootDir,"erts",DocDir])
- end.
-
-cdv_docdir() ->
- ObserverDir = code:lib_dir(observer),
- filename:join([ObserverDir,"doc","html"]).
-
%%====================================================================
%% External functions
%%====================================================================
%%%--------------------------------------------------------------------
-%%% Start the server
+%%% Start the server - called by crashdump_viewer_wx
start_link() ->
case whereis(?SERVER) of
undefined ->
@@ -334,119 +231,78 @@ start_link() ->
end.
%%%-----------------------------------------------------------------
-%%% If crashdump_viewer is just started, show welcome frame. Else
-%%% show menu and general_info
-start_page(_Env,_Input) ->
- call(start_page).
-
-%%%-----------------------------------------------------------------
-%%% Display the form for entering the file name for the crashdump
-%%% to view.
-read_file_frame(_Env,_Input) ->
- crashdump_viewer_html:read_file_frame().
-
-%%%-----------------------------------------------------------------
-%%% Called when the 'ok' button is clicked after entering the dump
-%%% file name.
-read_file(_Env,Input) ->
- call({read_file,Input}).
-
-%%%-----------------------------------------------------------------
-%%% The topmost frame of the main page. Called when a crashdump is
-%%% loaded.
-filename_frame(_Env,_Input) ->
- call(filename_frame).
-
-%%%-----------------------------------------------------------------
-%%% The initial information frame. Called when a crashdump is loaded.
-initial_info_frame(_Env,_Input) ->
- call(initial_info_frame).
-
-%%%-----------------------------------------------------------------
-%%% The left frame of the main page. Called when a crashdump is
-%%% loaded.
-menu_frame(_Env,_Input) ->
- crashdump_viewer_html:menu_frame().
-
-%%%-----------------------------------------------------------------
-%%% Called when the collapsed or exploded picture in the menu is
-%%% clicked.
-toggle(_Env,Input) ->
- call({toggle,Input}).
+%%% Called by crashdump_viewer_wx
+read_file(File) ->
+ cast({read_file,File,self()}).
%%%-----------------------------------------------------------------
-%%% The following functions are called when menu items are clicked.
-general_info(_Env,_Input) ->
- call(general_info).
-processes(SessionId,_Env,_Input) ->
- call({procs_summary,SessionId}).
-ports(SessionId,_Env,_Input) ->
- call({ports,SessionId}).
-ets_tables(SessionId,_Env,Input) ->
- call({ets_tables,SessionId,Input}).
-internal_ets_tables(_Env,_Input) ->
+%%% The following functions are called when the different tabs are
+%%% created
+init_general_info() ->
+ call(init_general_info).
+processes() ->
+ call(procs_summary).
+ports() ->
+ call(ports).
+ets_tables(Owner) ->
+ call({ets_tables,Owner}).
+internal_ets_tables() ->
call(internal_ets_tables).
-timers(SessionId,_Env,Input) ->
- call({timers,SessionId,Input}).
-fun_table(SessionId,_Env,_Input) ->
- call({funs,SessionId}).
-atoms(SessionId,_Env,_Input) ->
- call({atoms,SessionId}).
-dist_info(_Env,_Input) ->
+timers(Owner) ->
+ call({timers,Owner}).
+funs() ->
+ call(funs).
+atoms() ->
+ call(atoms).
+dist_info() ->
call(dist_info).
-loaded_modules(SessionId,_Env,_Input) ->
- call({loaded_mods,SessionId}).
-loaded_mod_details(_Env,Input) ->
- call({loaded_mod_details,Input}).
-memory(_Env,_Input) ->
+node_info(Channel) ->
+ call({node_info,Channel}).
+loaded_modules() ->
+ call(loaded_mods).
+loaded_mod_details(Mod) ->
+ call({loaded_mod_details,Mod}).
+memory() ->
call(memory).
-allocated_areas(_Env,_Input) ->
+allocated_areas() ->
call(allocated_areas).
-allocator_info(_Env,_Input) ->
+allocator_info() ->
call(allocator_info).
-hash_tables(_Env,_Input) ->
+hash_tables() ->
call(hash_tables).
-index_tables(_Env,_Input) ->
+index_tables() ->
call(index_tables).
%%%-----------------------------------------------------------------
%%% Called when a link to a process (Pid) is clicked.
-proc_details(_Env,Input) ->
- call({proc_details,Input}).
-
-%%%-----------------------------------------------------------------
-%%% Called when one of the headings in the process summary table are
-%%% clicked. It sorts the processes by the clicked heading.
-sort_procs(SessionId,_Env,Input) ->
- call({sort_procs,SessionId,Input}).
+proc_details(Pid) ->
+ call({proc_details,Pid}).
%%%-----------------------------------------------------------------
%%% Called when a link to a port is clicked.
-port(_Env,Input) ->
- call({port,Input}).
+port(Id) ->
+ call({port,Id}).
%%%-----------------------------------------------------------------
-%%% Called when the "Expand" link in a call stack (Last Calls) is
-%%% clicked.
-expand(_Env,Input) ->
- call({expand,Input}).
+%%% Stack dump, message queue and dictionary tabs in process detail
+%%% window
+expand_memory(Pid,What) ->
+ call({expand_memory,Pid,What}).
%%%-----------------------------------------------------------------
-%%% Called when the "Expand" link in a stack dump, message queue or
-%%% dictionary is clicked.
-expand_memory(_Env,Input) ->
- call({expand_memory,Input}).
+%%% Called when "<< xxx bytes>>" link is clicket to open a new window
+%%% displaying the whole binary.
+expand_binary(Pos) ->
+ call({expand_binary,Pos}).
%%%-----------------------------------------------------------------
-%%% Called when "<< xxx bytes>>" link in a stack dump, message queue or
-%%% dictionary is clicked.
-expand_binary(_Env,Input) ->
- call({expand_binary,Input}).
-
-%%%-----------------------------------------------------------------
-%%% Called on regular intervals while waiting for a dump to be read
-redirect(_Env,_Input) ->
- call(redirect).
+%%% Wait for a progress report when reading/processing crash dump.
+%%% Called from crashdump_viewer_wx.
+get_progress() ->
+ receive
+ {progress,Status} ->
+ Status
+ end.
%%====================================================================
%% Server functions
@@ -461,7 +317,6 @@ redirect(_Env,_Input) ->
%% {stop, Reason}
%%--------------------------------------------------------------------
init([]) ->
- ets:new(cdv_menu_table,[set,named_table,{keypos,#menu_item.index},public]),
ets:new(cdv_dump_index_table,[ordered_set,named_table,public]),
{ok, #state{}}.
@@ -475,223 +330,135 @@ init([]) ->
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
-handle_call(start_page,_From,State=#state{file=undefined,bg_status=undefined})->
- Reply = crashdump_viewer_html:welcome(),
- {reply,Reply,State};
-handle_call(start_page, _From, State=#state{file=undefined,bg_status={done,Page}}) ->
- {reply,Page,State};
-handle_call(start_page, _From, State=#state{file=undefined,bg_status=Status}) ->
- Reply = crashdump_viewer_html:redirect(Status),
- {reply,Reply,State};
-handle_call(start_page, _From, State) ->
- Reply = crashdump_viewer_html:start_page(),
- {reply,Reply,State};
-handle_call({read_file,Input}, _From, _State) ->
- {ok,File} = get_value("path",httpd:parse_query(Input)),
- spawn_link(fun() -> read_file(File) end),
- Status = background_status(reading,File),
- Reply = crashdump_viewer_html:redirect(Status),
- {reply, Reply, #state{bg_status=Status}};
-handle_call(redirect,_From, State=#state{bg_status={done,Page}}) ->
- {reply, Page, State#state{bg_status=undefined}};
-handle_call(redirect,_From, State=#state{bg_status=Status}) ->
- Reply = crashdump_viewer_html:redirect(Status),
- {reply, Reply, State};
-handle_call(filename_frame,_From,State=#state{file=File}) ->
- Reply = crashdump_viewer_html:filename_frame(File),
- {reply,Reply,State};
-handle_call(initial_info_frame,_From,State=#state{file=File}) ->
+handle_call(init_general_info,_From,State=#state{file=File}) ->
GenInfo = general_info(File),
[{DumpVsn,_}] = lookup_index(?erl_crash_dump),
NumAtoms = GenInfo#general_info.num_atoms,
- {WS,SH} = parse_vsn_str(GenInfo#general_info.system_vsn,4,false),
- NumProcs = list_to_integer(GenInfo#general_info.num_procs),
- ProcsSummary =
- if NumProcs > ?max_sort_process_num -> too_many;
- true -> State#state.procs_summary
- end,
+ WS = parse_vsn_str(GenInfo#general_info.system_vsn,4),
NewState = State#state{dump_vsn=[list_to_integer(L) ||
L<-string:tokens(DumpVsn,".")],
- shared_heap=SH,
wordsize=WS,
- num_atoms=NumAtoms,
- procs_summary=ProcsSummary},
- Reply = crashdump_viewer_html:general_info(GenInfo),
- {reply,Reply,NewState};
-handle_call({toggle,Input},_From,State) ->
- {ok,Index} = get_value("index",httpd:parse_query(Input)),
- do_toggle(list_to_integer(Index)),
- Reply = crashdump_viewer_html:menu_frame(),
- {reply,Reply,State};
-handle_call({expand,Input},_From,State=#state{file=File}) ->
- [{"pos",Pos},{"size",Size},{"what",What},{"truncated",Truncated}] =
- httpd:parse_query(Input),
- Expanded = get_expanded(File,list_to_integer(Pos),list_to_integer(Size)),
- TruncText = if Truncated=="true" -> "WARNING: This term is truncated!\n\n";
- true -> ""
- end,
- Reply =
- case {Truncated,What} of
- {_,"LastCalls"} ->
- LastCalls = replace_all($ ,$\n,Expanded,[]),
- crashdump_viewer_html:info_page(What,[TruncText,LastCalls]);
- {_,"StackDump"} ->
- crashdump_viewer_html:info_page(What,[TruncText,Expanded]);
- {"false",_} ->
- crashdump_viewer_html:pretty_info_page(What,Expanded);
- {"true",_} ->
- crashdump_viewer_html:info_page(What,[TruncText,Expanded])
- end,
- {reply,Reply,State};
-handle_call({expand_memory,Input},_From,State=#state{file=File,binaries=B}) ->
- [{"pid",Pid},{"what",What}] = httpd:parse_query(Input),
+ num_atoms=NumAtoms},
+ {reply,{ok,GenInfo},NewState};
+handle_call({expand_memory,Pid,What},_From,State=#state{file=File,binaries=B}) ->
Reply =
case truncated_warning([{?proc,Pid}]) of
[] ->
- Expanded = expand_memory(File,What,Pid,B),
- crashdump_viewer_html:expanded_memory(What,Expanded);
+ {ok,expand_memory(File,What,Pid,B)};
_TW ->
Info =
"The crashdump is truncated in the middle of this "
"process' memory information, so this information "
"can not be extracted.",
- crashdump_viewer_html:info_page(What,Info)
+ {error,Info}
end,
{reply,Reply,State};
-handle_call({expand_binary,Input},_From,State=#state{file=File}) ->
- [{"pos",Pos0}] = httpd:parse_query(Input),
+handle_call({expand_binary,Pos0},_From,State=#state{file=File}) ->
Pos = list_to_integer(Pos0),
Fd = open(File),
pos_bof(Fd,Pos),
{Bin,_Line} = get_binary(val(Fd)),
close(Fd),
- Reply=crashdump_viewer_html:expanded_binary(io_lib:format("~p",[Bin])),
- {reply,Reply,State};
-handle_call(general_info,_From,State=#state{file=File}) ->
- GenInfo=general_info(File),
- Reply = crashdump_viewer_html:general_info(GenInfo),
- {reply,Reply,State};
-handle_call({procs_summary,SessionId},_From,State) ->
+ {reply,{ok,Bin},State};
+handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
TW = truncated_warning([?proc]),
- NewState = procs_summary(SessionId,TW,"pid",State#state{sorted=undefined}),
- {reply,ok,NewState};
-handle_call({sort_procs,SessionId,Input}, _From, State) ->
- {ok,Sort} = get_value("sort",httpd:parse_query(Input)),
- TW = truncated_warning([?proc]),
- NewState = procs_summary(SessionId,TW,Sort,State),
- {reply,ok,NewState};
-handle_call({proc_details,Input},_From,State=#state{file=File,shared_heap=SH}) ->
- {ok,Pid} = get_value("pid",httpd:parse_query(Input)),
+ Procs = procs_summary(File,WS),
+ {reply,{ok,Procs,TW},State};
+handle_call({proc_details,Pid},_From,State=#state{file=File,wordsize=WS})->
Reply =
- case get_proc_details(File,Pid,State#state.dump_vsn) of
+ case get_proc_details(File,Pid,State#state.dump_vsn,WS) of
{ok,Proc} ->
TW = truncated_warning([{?proc,Pid}]),
- crashdump_viewer_html:proc_details(Pid,Proc,TW,SH);
+ {ok,Proc,TW};
{other_node,Node} ->
- TW = truncated_warning([?visible_node,
- ?hidden_node,
- ?not_connected]),
- crashdump_viewer_html:nods(Node,TW);
+ {error,{other_node,Node}};
not_found ->
- crashdump_viewer_html:info_page(["Could not find process: ",
- Pid],?space)
+ {error,not_found}
end,
{reply, Reply, State};
-handle_call({port,Input},_From,State=#state{file=File}) ->
- {ok,P} = get_value("port",httpd:parse_query(Input)),
- Id = [$#|P],
+handle_call({port,Id},_From,State=#state{file=File}) ->
Reply =
case get_port(File,Id) of
{ok,PortInfo} ->
TW = truncated_warning([{?port,Id}]),
- crashdump_viewer_html:port(Id,PortInfo,TW);
+ {ok,PortInfo,TW};
{other_node,Node} ->
- TW = truncated_warning([?visible_node,
- ?hidden_node,
- ?not_connected]),
- crashdump_viewer_html:nods(Node,TW);
+ {error,{other_node,Node}};
not_found ->
- crashdump_viewer_html:info_page(
- ["Could not find port: ",Id],?space)
+ {error,not_found}
end,
{reply,Reply,State};
-handle_call({ports,SessionId},_From,State=#state{file=File}) ->
+handle_call(ports,_From,State=#state{file=File}) ->
TW = truncated_warning([?port]),
- get_ports(SessionId,File,TW),
- {reply,ok,State};
-handle_call({ets_tables,SessionId,Input},_From,State=#state{file=File,wordsize=WS}) ->
- {Pid,Heading} =
- case get_value("pid",httpd:parse_query(Input)) of
- {ok,P} ->
- {P,["ETS Tables for Process ",P]};
- error ->
- {'$2',"ETS Table Information"}
+ Ports = get_ports(File),
+ {reply,{ok,Ports,TW},State};
+handle_call({ets_tables,Pid0},_From,State=#state{file=File,wordsize=WS}) ->
+ Pid =
+ case Pid0 of
+ all -> '$2';
+ _ -> Pid0
end,
TW = truncated_warning([?ets]),
- get_ets_tables(SessionId,File,Heading,TW,Pid,WS),
- {reply,ok,State};
+ Ets = get_ets_tables(File,Pid,WS),
+ {reply,{ok,Ets,TW},State};
handle_call(internal_ets_tables,_From,State=#state{file=File,wordsize=WS}) ->
InternalEts = get_internal_ets_tables(File,WS),
TW = truncated_warning([?internal_ets]),
- Reply = crashdump_viewer_html:internal_ets_tables(InternalEts,TW),
- {reply,Reply,State};
-handle_call({timers,SessionId,Input},_From,State=#state{file=File}) ->
- {Pid,Heading} =
- case get_value("pid",httpd:parse_query(Input)) of
- {ok,P} -> {P,["Timers for Process ",P]};
- error -> {'$2',"Timer Information"}
+ {reply,{ok,InternalEts,TW},State};
+handle_call({timers,Pid0},_From,State=#state{file=File}) ->
+ Pid =
+ case Pid0 of
+ all -> '$2';
+ _ -> Pid0
end,
TW = truncated_warning([?timer]),
- get_timers(SessionId,File,Heading,TW,Pid),
- {reply,ok,State};
+ Timers = get_timers(File,Pid),
+ {reply,{ok,Timers,TW},State};
handle_call(dist_info,_From,State=#state{file=File}) ->
+ TW = truncated_warning([?visible_node,?hidden_node,?not_connected]),
Nods=nods(File),
+ {reply,{ok,Nods,TW},State};
+handle_call({node_info,Channel},_From,State=#state{file=File}) ->
TW = truncated_warning([?visible_node,?hidden_node,?not_connected]),
- Reply = crashdump_viewer_html:nods(Nods,TW),
- {reply,Reply,State};
-handle_call({loaded_mods,SessionId},_From,State=#state{file=File}) ->
+ Nod=get_node(File,Channel),
+ {reply,{ok,Nod,TW},State};
+handle_call(loaded_mods,_From,State=#state{file=File}) ->
TW = truncated_warning([?mod]),
- loaded_mods(SessionId,File,TW),
- {reply,ok,State};
-handle_call({loaded_mod_details,Input},_From,State=#state{file=File}) ->
- {ok,Mod} = get_value("mod",httpd:parse_query(Input)),
- ModInfo = get_loaded_mod_details(File,Mod),
+ {_CC,_OC,Mods} = loaded_mods(File),
+ {reply,{ok,Mods,TW},State};
+handle_call({loaded_mod_details,Mod},_From,State=#state{file=File}) ->
TW = truncated_warning([{?mod,Mod}]),
- Reply = crashdump_viewer_html:loaded_mod_details(ModInfo,TW),
- {reply,Reply,State};
-handle_call({funs,SessionId},_From,State=#state{file=File}) ->
+ ModInfo = get_loaded_mod_details(File,Mod),
+ {reply,{ok,ModInfo,TW},State};
+handle_call(funs,_From,State=#state{file=File}) ->
TW = truncated_warning([?fu]),
- funs(SessionId,File,TW),
- {reply,ok,State};
-handle_call({atoms,SessionId},_From,State=#state{file=File,num_atoms=Num}) ->
+ Funs = funs(File),
+ {reply,{ok,Funs,TW},State};
+handle_call(atoms,_From,State=#state{file=File,num_atoms=NumAtoms0}) ->
TW = truncated_warning([?atoms,?num_atoms]),
- atoms(SessionId,File,TW,Num),
- {reply,ok,State};
+ NumAtoms = try list_to_integer(NumAtoms0) catch error:badarg -> -1 end,
+ Atoms = atoms(File,NumAtoms),
+ {reply,{ok,Atoms,TW},State};
handle_call(memory,_From,State=#state{file=File}) ->
Memory=memory(File),
TW = truncated_warning([?memory]),
- Reply = crashdump_viewer_html:memory(Memory,TW),
- {reply,Reply,State};
+ {reply,{ok,Memory,TW},State};
handle_call(allocated_areas,_From,State=#state{file=File}) ->
AllocatedAreas=allocated_areas(File),
TW = truncated_warning([?allocated_areas]),
- Reply = crashdump_viewer_html:allocated_areas(AllocatedAreas,TW),
- {reply,Reply,State};
+ {reply,{ok,AllocatedAreas,TW},State};
handle_call(allocator_info,_From,State=#state{file=File}) ->
SlAlloc=allocator_info(File),
TW = truncated_warning([?allocator]),
- Reply = crashdump_viewer_html:allocator_info(SlAlloc,TW),
- {reply,Reply,State};
+ {reply,{ok,SlAlloc,TW},State};
handle_call(hash_tables,_From,State=#state{file=File}) ->
HashTables=hash_tables(File),
TW = truncated_warning([?hash_table,?index_table]),
- Reply = crashdump_viewer_html:hash_tables(HashTables,TW),
- {reply,Reply,State};
+ {reply,{ok,HashTables,TW},State};
handle_call(index_tables,_From,State=#state{file=File}) ->
IndexTables=index_tables(File),
TW = truncated_warning([?hash_table,?index_table]),
- Reply = crashdump_viewer_html:index_tables(IndexTables,TW),
- {reply,Reply,State}.
+ {reply,{ok,IndexTables,TW},State}.
@@ -702,11 +469,18 @@ handle_call(index_tables,_From,State=#state{file=File}) ->
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
-handle_cast({background_done,{Page,File,Binaries},Dict}, State) ->
- lists:foreach(fun({Key,Val}) -> put(Key,Val) end, Dict),
- {noreply, State#state{file=File,binaries=Binaries,bg_status={done,Page}}};
-handle_cast({background_status,Status}, State) ->
- {noreply, State#state{bg_status=Status}}.
+handle_cast({read_file,File,ProgressReceiver}, _State) ->
+ case do_read_file(File,ProgressReceiver) of
+ {ok,Binaries} ->
+ report_progress(ProgressReceiver,{ok,done}),
+ {noreply, #state{file=File,binaries=Binaries}};
+ Error ->
+ report_progress(ProgressReceiver,Error),
+ {noreply, #state{}}
+ end;
+handle_cast(stop,State) ->
+ {stop,normal,State}.
+
%%--------------------------------------------------------------------
%% Function: handle_info/2
@@ -791,23 +565,9 @@ compare_pid("<"++Id,"<"++OtherId) ->
compare_pid(_,_) ->
false.
-background_status(Action,File) ->
- SizeInfo = filesizeinfo(File),
- background_status(Action,File,SizeInfo).
-
-background_status(processing,File,SizeInfo) ->
- "Processing " ++ File ++ SizeInfo;
-background_status(reading,File,SizeInfo) ->
- "Reading file " ++ File ++ SizeInfo.
-
-filesizeinfo(File) ->
- case file:read_file_info(File) of
- {ok,#file_info{size=Size}} ->
- " (" ++ integer_to_list(Size) ++ " bytes)";
- _X ->
- ""
- end.
-
+report_progress(Receiver,Progress) ->
+ Receiver ! {progress,Progress},
+ ok.
open(File) ->
{ok,Fd} = file:open(File,[read,read_ahead,raw,binary]),
@@ -861,6 +621,26 @@ get_chunk(Fd) ->
{ok,Bin}
end.
+%% Read and report progress
+progress_read(Fd) ->
+ {R,RBytes} =
+ case read(Fd) of
+ {ok,Bin} ->
+ {{ok,Bin},size(Bin)};
+ Other ->
+ {Other,0}
+ end,
+ {Receiver,Bytes,Size} = get(progress),
+ Bytes1 = Bytes + RBytes,
+ Percent0 = (100*Bytes) div Size,
+ Percent = (100*Bytes1) div Size,
+ if Percent > Percent0 ->
+ report_progress(Receiver,{ok,Percent});
+ true -> ok
+ end,
+ put(progress,{Receiver,Bytes1,Size}),
+ R.
+
read(Fd) ->
file:read(Fd,?chunk_size).
@@ -962,73 +742,30 @@ get_rest_of_line_1(Fd, <<>>, Acc) ->
eof -> {eof,lists:reverse(Acc)}
end.
-count_rest_of_line(Fd) ->
- case get_chunk(Fd) of
- {ok,Bin} -> count_rest_of_line(Fd,Bin,0);
- eof -> {eof,0}
- end.
-count_rest_of_line(Fd,<<$\n:8,Bin/binary>>,N) ->
- put_chunk(Fd,Bin),
- N;
-count_rest_of_line(Fd,<<$\r:8,Bin/binary>>,N) ->
- count_rest_of_line(Fd,Bin,N);
-count_rest_of_line(Fd,<<_Char:8,Bin/binary>>,N) ->
- count_rest_of_line(Fd,Bin,N+1);
-count_rest_of_line(Fd,<<>>,N) ->
- case get_chunk(Fd) of
- {ok,Bin} -> count_rest_of_line(Fd,Bin,N);
- eof -> {eof,N}
- end.
-
-get_n_lines_of_tag(Fd,N) ->
+get_lines_to_empty(Fd) ->
case get_chunk(Fd) of
- {ok,Bin} ->
- {AllOrPart,Rest,Lines} = get_n_lines_of_tag(Fd,N,Bin,[]),
- {AllOrPart,N-Rest,Lines};
+ {ok,Bin} ->
+ get_lines_to_empty(Fd,Bin,[],[]);
eof ->
- empty
- end.
-get_n_lines_of_tag(Fd,N,<<"\n=",_/binary>>=Bin,Acc) ->
- put_chunk(Fd,Bin),
- {all,N-1,lists:reverse(Acc)};
-get_n_lines_of_tag(Fd,0,Bin,Acc) ->
- put_chunk(Fd,Bin),
- {part,0,lists:reverse(Acc)};
-get_n_lines_of_tag(Fd,N,<<$\n:8,Bin/binary>>,Acc) ->
- get_n_lines_of_tag(Fd,N-1,Bin,[$\n|Acc]);
-get_n_lines_of_tag(Fd,N,<<$\r:8,Bin/binary>>,Acc) ->
- get_n_lines_of_tag(Fd,N,Bin,Acc);
-get_n_lines_of_tag(Fd,N,<<Char:8,Bin/binary>>,Acc) ->
- get_n_lines_of_tag(Fd,N,Bin,[Char|Acc]);
-get_n_lines_of_tag(Fd,N,<<>>,Acc) ->
- case get_chunk(Fd) of
- {ok,Bin} ->
- get_n_lines_of_tag(Fd,N,Bin,Acc);
- eof ->
- case Acc of
- [$\n|_] ->
- {all,N,lists:reverse(Acc)};
- _ ->
- {all,N-1,lists:reverse(Acc)}
- end
- end.
-
-count_rest_of_tag(Fd) ->
- case get_chunk(Fd) of
- {ok,Bin} -> count_rest_of_tag(Fd,Bin,0);
- eof -> 0
+ []
end.
-count_rest_of_tag(Fd,<<"\n=",Bin/binary>>,N) ->
+get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,[],Lines) ->
put_chunk(Fd,Bin),
- N;
-count_rest_of_tag(Fd,<<$\r:8,Bin/binary>>,N) ->
- count_rest_of_tag(Fd,Bin,N);
-count_rest_of_tag(Fd,<<_Char:8,Bin/binary>>,N) ->
- count_rest_of_tag(Fd,Bin,N+1);
-count_rest_of_tag(Fd,<<>>,N) ->
+ lists:reverse(Lines);
+get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) ->
+ get_lines_to_empty(Fd,Bin,[],[lists:reverse(Acc)|Lines]);
+get_lines_to_empty(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) ->
+ get_lines_to_empty(Fd,Bin,Acc,Lines);
+get_lines_to_empty(Fd,<<$\s:8,Bin/binary>>,[],Lines) ->
+ get_lines_to_empty(Fd,Bin,[],Lines);
+get_lines_to_empty(Fd,<<Char:8,Bin/binary>>,Acc,Lines) ->
+ get_lines_to_empty(Fd,Bin,[Char|Acc],Lines);
+get_lines_to_empty(Fd,<<>>,Acc,Lines) ->
case get_chunk(Fd) of
- {ok,Bin} -> count_rest_of_tag(Fd,Bin,N);
- eof -> N
+ {ok,Bin} ->
+ get_lines_to_empty(Fd,Bin,Acc,Lines);
+ eof ->
+ lists:reverse(Lines,[lists:reverse(Acc)])
end.
split(Str) ->
@@ -1046,150 +783,33 @@ split(Char,[H|T],Acc) ->
split(_Char,[],Acc) ->
{lists:reverse(Acc),[]}.
-size_or_term(Fd) ->
- size_or_term(Fd,get(pos)).
-size_or_term(Fd,Pos) ->
- case count_rest_of_line(Fd) of
- {eof,Size} ->
- {size,true,Size,Pos};
- Size when Size > ?max_display_size ->
- {size,false,Size,Pos};
- _Size ->
- {ok,Pos} = pos_bof(Fd,Pos),
- val(Fd)
- end.
-
%%%-----------------------------------------------------------------
%%%
-get_value(Key,List) ->
- case lists:keysearch(Key,1,List) of
- {value,{Key,Value}} -> {ok,Value};
- false -> error
- end.
-
-parse_vsn_str([],WS,false) ->
- %% If the log is translated, crashdump_translate might have written
- %% shared_heap=true in dictionary.
- case erase(shared_heap) of
- true -> {WS,true};
- _ -> {WS,false}
- end;
-parse_vsn_str([],WS,SH) ->
- {WS,SH};
-parse_vsn_str(Str,WS,SH) ->
+parse_vsn_str([],WS) ->
+ WS;
+parse_vsn_str(Str,WS) ->
case Str of
- "[64-bit]" ++ Rest ->
- case SH of
- false ->
- parse_vsn_str(Rest,8,false);
- _ ->
- {8,SH}
- end;
- "[shared heap]" ++ Rest ->
- case WS of
- 4 ->
- parse_vsn_str(Rest,WS,true);
- _ ->
- {WS,true}
- end;
+ "[64-bit]" ++ _Rest ->
+ 8;
[_Char|Rest] ->
- parse_vsn_str(Rest,WS,SH)
+ parse_vsn_str(Rest,WS)
end.
%%%-----------------------------------------------------------------
-%%%
-initial_menu() ->
- insert_items(
- [menu_item(0, {"./general_info","General information"},0),
- menu_item(0, {"./processes","Processes"}, 0),
- menu_item(0, {"./ports","Ports"}, 0),
- menu_item(2, "ETS tables", 0),
- menu_item(0, {"./ets_tables","ETS tables"}, 1),
- menu_item(0, {"./internal_ets_tables","Internal ETS tables"}, 1),
- menu_item(0, {"./timers","Timers"}, 0),
- menu_item(0, {"./fun_table","Fun table"}, 0),
- menu_item(0, {"./atoms","Atoms"}, 0),
- menu_item(0, {"./dist_info","Distribution information"}, 0),
- menu_item(0, {"./loaded_modules","Loaded modules"}, 0),
- menu_item(2, "Internal Tables", 0),
- menu_item(0, {"./hash_tables","Hash tables"}, 1),
- menu_item(0, {"./index_tables","Index tables"}, 1),
- menu_item(3, "Memory information", 0),
- menu_item(0, {"./memory","Memory"}, 1),
- menu_item(0, {"./allocated_areas","Allocated areas"}, 1),
- menu_item(0, {"./allocator_info","Allocator information"}, 1),
- menu_item(2, "Documentation", 0),
- menu_item(0, {"/crashdump_doc/crashdump_help.html",
- "Crashdump Viewer help"}, 1,"doc"),
- menu_item(0, {"/crashdump_erts_doc/crash_dump.html",
- "How to interpret Erlang crashdumps"}, 1,"doc")]).
-
-menu_item(Children,Text,Depth) ->
- menu_item(Children,Text,Depth,"main").
-menu_item(Children,Text,Depth,Target) ->
- #menu_item{picture=get_pic(Children),
- text=Text,
- depth=Depth,
- children=Children,
- state=if Depth==0 -> true; true -> false end,
- target=Target}.
-
-insert_items(Items) ->
- insert_items(Items,1).
-insert_items([Item|Items],Index) ->
- ets:insert(cdv_menu_table,Item#menu_item{index=Index}),
- insert_items(Items,Index+1);
-insert_items([],_) ->
- ok.
-
-get_pic(0) ->
- "";
-get_pic(_) ->
- "/crashdump_viewer/collapsd.gif".
-
-do_toggle(Index) ->
- [Item]= ets:lookup(cdv_menu_table,Index),
- case toggle_children(Index,Index+Item#menu_item.children,
- Item#menu_item.depth+1,undefined) of
- true ->
- ets:insert(cdv_menu_table,
- Item#menu_item{picture=
- "/crashdump_viewer/exploded.gif"});
- false ->
- ets:insert(cdv_menu_table,
- Item#menu_item{picture=
- "/crashdump_viewer/collapsd.gif"})
- end.
-
-toggle_children(Index,Max,_Depth,ToggleState) when Index>Max->
- ToggleState;
-toggle_children(Index,Max,Depth,ToggleState) ->
- case ets:lookup(cdv_menu_table,Index+1) of
- [#menu_item{depth=Depth}=Child] ->
- NewState = not Child#menu_item.state,
- ets:insert(cdv_menu_table,Child#menu_item{state=NewState}),
- toggle_children(Index+1,Max,Depth,NewState);
- _ ->
- toggle_children(Index+1,Max,Depth,ToggleState)
- end.
-
-%%%-----------------------------------------------------------------
%%% Traverse crash dump and insert index in table for each heading
-%%%
-%%% This function is executed in a background process in order to
-%%% avoid a timeout in the web browser. The browser displays "Please
-%%% wait..." while this is going on.
%%%
-%%% Variable written to process dictionary in this function are copied
-%%% to the crashdump_viewer_server when the function is completed (see
-%%% background_done/1).
-read_file(File) ->
+%%% Progress is reported during the time and MUST be checked with
+%%% crashdump_viewer:get_progress/0 until it returns {ok,done}.
+do_read_file(File,ProgressReceiver) ->
case file:read_file_info(File) of
- {ok,#file_info{type=regular,access=FileA}} when FileA=:=read;
- FileA=:=read_write ->
+ {ok,#file_info{type=regular,
+ access=FileA,
+ size=Size}} when FileA=:=read; FileA=:=read_write ->
Fd = open(File),
- case read(Fd) of
+ report_progress(ProgressReceiver,{ok,"Reading file"}),
+ put(progress,{ProgressReceiver,0,Size}),
+ case progress_read(Fd) of
{ok,<<$=:8,TagAndRest/binary>>} ->
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1),
case Tag of
@@ -1197,41 +817,37 @@ read_file(File) ->
reset_index_table(),
insert_index(Tag,Id,N1+1),
put(last_tag,{Tag,""}),
- Status = background_status(processing,File),
- background_status(Status),
indexify(Fd,Rest,N1),
+ erase(progress),
check_if_truncated(),
- initial_menu(),
- Binaries = read_binaries(Fd),
- R = crashdump_viewer_html:start_page(),
+ Binaries = read_binaries(Fd,ProgressReceiver),
close(Fd),
- background_done({R,File,Binaries});
+ {ok,Binaries};
_Other ->
- R = crashdump_viewer_html:error(
+ R = io_lib:format(
"~s is not an Erlang crash dump~n",
[File]),
close(Fd),
- background_done({R,undefined,undefined})
+ {error,R}
end;
{ok,<<"<Erlang crash dump>",_Rest/binary>>} ->
%% old version - no longer supported
- R = crashdump_viewer_html:error(
+ R = io_lib:format(
"The crashdump ~s is in the pre-R10B format, "
"which is no longer supported.~n",
- [File]),
+ [File]),
close(Fd),
- background_done({R,undefined,undefined});
+ {error,R};
_Other ->
- R = crashdump_viewer_html:error(
+ R = io_lib:format(
"~s is not an Erlang crash dump~n",
[File]),
close(Fd),
- background_done({R,undefined,undefined})
+ {error,R}
end;
_other ->
- R = crashdump_viewer_html:error("~s is not an Erlang crash dump~n",
- [File]),
- background_done({R,undefined,undefined})
+ R = io_lib:format("~s is not an Erlang crash dump~n",[File]),
+ {error,R}
end.
indexify(Fd,Bin,N) ->
@@ -1244,7 +860,7 @@ indexify(Fd,Bin,N) ->
put(last_tag,{Tag,Id}),
indexify(Fd,Rest,N1);
nomatch ->
- case read(Fd) of
+ case progress_read(Fd) of
{ok,Chunk0} when is_binary(Chunk0) ->
{Chunk,N1} =
case binary:last(Bin) of
@@ -1272,7 +888,7 @@ tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,tag) ->
tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,id) ->
tag(Fd,Rest,N+1,Gat,[Char|Di],id);
tag(Fd,<<>>,N,Gat,Di,Now) ->
- case read(Fd) of
+ case progress_read(Fd) of
{ok,Chunk} when is_binary(Chunk) ->
tag(Fd,Chunk,N,Gat,Di,Now);
eof ->
@@ -1304,21 +920,12 @@ find_truncated_proc({Tag,Pid}) ->
is_proc_tag(Tag) when Tag==?proc;
Tag==?proc_dictionary;
Tag==?proc_messages;
- Tag==?debug_proc_dictionary;
Tag==?proc_stack;
Tag==?proc_heap ->
true;
is_proc_tag(_) ->
false.
-%%% Inform the crashdump_viewer_server that a background job is completed.
-background_done(Result) ->
- Dict = get(),
- cast({background_done,Result,Dict}).
-
-background_status(Status) ->
- cast({background_status,Status}).
-
%%%-----------------------------------------------------------------
%%% Functions for reading information from the dump
general_info(File) ->
@@ -1332,7 +939,7 @@ general_info(File) ->
GI0 = get_general_info(Fd,#general_info{created=Created}),
GI = case GI0#general_info.num_atoms of
- ?space -> GI0#general_info{num_atoms=get_num_atoms(Fd)};
+ undefined -> GI0#general_info{num_atoms=get_num_atoms(Fd)};
_ -> GI0
end,
@@ -1341,11 +948,11 @@ general_info(File) ->
[{_,MemStart}] ->
pos_bof(Fd,MemStart),
Memory = get_meminfo(Fd,[]),
- Tot = case lists:keysearch("total",1,Memory) of
+ Tot = case lists:keysearch(total,1,Memory) of
{value,{_,T}} -> T;
false -> ""
end,
- Max = case lists:keysearch("maximum",1,Memory) of
+ Max = case lists:keysearch(maximum,1,Memory) of
{value,{_,M}} -> M;
false -> ""
end,
@@ -1426,7 +1033,7 @@ get_num_atoms(Fd) ->
get_num_atoms2() ->
case lookup_index(?num_atoms) of
[] ->
- ?space;
+ undefined;
[{NA,_Pos}] ->
%% If dump is translated this will exist
case get(truncated) of
@@ -1443,52 +1050,20 @@ count() ->
%%-----------------------------------------------------------------
%% Page with all processes
-%%
-%% If there are less than ?max_sort_process_num processes in the dump,
-%% we will store the list of processes in the server state in order to
-%% allow sorting according to the different columns of the
-%% table. Since ?max_sort_process_num=:=?items_chunk_size, there will
-%% never be more than one chunk in this case.
-%%
-%% If there are more than ?max_sort_process_num processes in the dump,
-%% no sorting will be allowed, and the processes must be read (chunk
-%% by chunk) from the file each time the page is opened. This is to
-%% avoid really big data in the server state.
-procs_summary(SessionId,TW,_,State=#state{procs_summary=too_many}) ->
- chunk_page(SessionId,State#state.file,TW,?proc,processes,
- {no_sort,State#state.shared_heap,State#state.dump_vsn},
- procs_summary_parsefun()),
- State;
-procs_summary(SessionId,TW,SortOn,State) ->
- ProcsSummary =
- case State#state.procs_summary of
- undefined -> % first time - read from file
- Fd = open(State#state.file),
- {PS,_}=lookup_and_parse_index_chunk(first_chunk_pointer(?proc),
- Fd,procs_summary_parsefun()),
- close(Fd),
- PS;
- PS ->
- PS
- end,
- {SortedPS,NewSorted} = do_sort_procs(SortOn,ProcsSummary,State),
- HtmlInfo =
- crashdump_viewer_html:chunk_page(processes,SessionId,TW,
- {SortOn,
- State#state.shared_heap,
- State#state.dump_vsn},
- SortedPS),
- crashdump_viewer_html:chunk(SessionId,done,HtmlInfo),
- State#state{procs_summary=ProcsSummary,sorted=NewSorted}.
-
-procs_summary_parsefun() ->
- fun(Fd,Pid) ->
- get_procinfo(Fd,fun main_procinfo/4,#proc{pid=Pid})
- end.
+procs_summary(File,WS) ->
+ ParseFun = fun(Fd,Pid) ->
+ Proc = get_procinfo(Fd,fun main_procinfo/5,
+ #proc{pid=list_to_pid(Pid)},WS),
+ case Proc#proc.memory of
+ undefined -> Proc#proc{memory=Proc#proc.stack_heap};
+ _ -> Proc
+ end
+ end,
+ lookup_and_parse_index(File,?proc,ParseFun).
%%-----------------------------------------------------------------
%% Page with one process
-get_proc_details(File,Pid,DumpVsn) ->
+get_proc_details(File,Pid,DumpVsn,WS) ->
case lookup_index(?proc,Pid) of
[{_,Start}] ->
Fd = open(File),
@@ -1502,22 +1077,13 @@ get_proc_details(File,Pid,DumpVsn) ->
#proc{pid=Pid,
stack_dump=if_exist(?proc_stack,Pid),
msg_q=if_exist(?proc_messages,Pid),
- dict=if_exist(?proc_dictionary,Pid),
- debug_dict=if_exist(?debug_proc_dictionary,Pid)}
+ dict=if_exist(?proc_dictionary,Pid)}
end,
- Proc = get_procinfo(Fd,fun all_procinfo/4,Proc0),
+ Proc = get_procinfo(Fd,fun all_procinfo/5,Proc0,WS),
close(Fd),
{ok,Proc};
_ ->
- case maybe_other_node(File,Pid) of
- {other_node,Type,Node} ->
- Info = "The process you are searching for was residing on "
- "a remote node. No process information is available. "
- "Information about the remote node is show below.",
- {other_node,{Type,Info,Node}};
- not_found ->
- not_found
- end
+ maybe_other_node(Pid)
end.
if_exist(Tag,Key) ->
@@ -1530,90 +1096,50 @@ if_exist(Tag,Key) ->
end,
case truncated_here({Tag1,Key}) of
true -> truncated;
- false -> ?space
+ false -> undefined
end;
_ ->
expand
end.
-get_procinfo(Fd,Fun,Proc) ->
+get_procinfo(Fd,Fun,Proc,WS) ->
case line_head(Fd) of
"State" ->
State = case val(Fd) of
"Garbing" -> "Garbing\n(limited info)";
State0 -> State0
end,
- get_procinfo(Fd,Fun,Proc#proc{state=State});
+ get_procinfo(Fd,Fun,Proc#proc{state=State},WS);
"Name" ->
- get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)});
+ get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)},WS);
"Spawned as" ->
IF = val(Fd),
case Proc#proc.name of
- ?space ->
- get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF});
+ undefined ->
+ get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF},WS);
_ ->
- get_procinfo(Fd,Fun,Proc#proc{init_func=IF})
- end;
- "Spawned by" ->
- case val(Fd) of
- "[]" ->
- get_procinfo(Fd,Fun,Proc);
- Parent ->
- get_procinfo(Fd,Fun,Proc#proc{parent=Parent})
+ get_procinfo(Fd,Fun,Proc#proc{init_func=IF},WS)
end;
- "Started" ->
- get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)});
- "Last scheduled in for" ->
- get_procinfo(Fd,Fun,Proc#proc{current_func=
- {"Last scheduled in for",
- val(Fd)}});
- "Current call" ->
- get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call",
- val(Fd)}});
- "Message queue length" ->
+ "Message queue length" ->
%% stored as integer so we can sort on it
- get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))});
+ get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))},WS);
"Reductions" ->
%% stored as integer so we can sort on it
- get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))});
- "Number of heap fragments" ->
- get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)});
- "Heap fragment data" ->
- get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)});
- Stack when Stack=:="Stack+heap"; Stack=:="Stack" ->
+ get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))},WS);
+ "Stack+heap" ->
%% stored as integer so we can sort on it
get_procinfo(Fd,Fun,Proc#proc{stack_heap=
- list_to_integer(val(Fd))});
- "OldHeap" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap=val(Fd)});
- "Heap unused" ->
- get_procinfo(Fd,Fun,Proc#proc{heap_unused=val(Fd)});
- "OldHeap unused" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=val(Fd)});
- "New heap start" ->
- get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)});
- "New heap top" ->
- get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)});
- "Stack top" ->
- get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)});
- "Stack end" ->
- get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)});
- "Old heap start" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)});
- "Old heap top" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)});
- "Old heap end" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_end=val(Fd)});
+ list_to_integer(val(Fd))*WS},WS);
"Memory" ->
%% stored as integer so we can sort on it
- get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))});
+ get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))},WS);
{eof,_} ->
Proc; % truncated file
Other ->
- Fun(Fd,Fun,Proc,Other)
+ Fun(Fd,Fun,Proc,WS,Other)
end.
-main_procinfo(Fd,Fun,Proc,LineHead) ->
+main_procinfo(Fd,Fun,Proc,WS,LineHead) ->
case LineHead of
"Stack dump" ->
%% This is the last element in older dumps (DumpVsn=0.0)
@@ -1623,54 +1149,128 @@ main_procinfo(Fd,Fun,Proc,LineHead) ->
Proc;
"arity = " ++ _ ->
%%! Temporary workaround
- get_procinfo(Fd,Fun,Proc);
+ get_procinfo(Fd,Fun,Proc,WS);
_Other ->
skip_rest_of_line(Fd),
- get_procinfo(Fd,Fun,Proc)
+ get_procinfo(Fd,Fun,Proc,WS)
end.
-all_procinfo(Fd,Fun,Proc,LineHead) ->
+all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
case LineHead of
- "Message queue" ->
- get_procinfo(Fd,Fun,Proc#proc{msg_q=size_or_term(Fd)});
+ %% - START - moved from get_procinfo -
+ "Spawned by" ->
+ case val(Fd) of
+ "[]" ->
+ get_procinfo(Fd,Fun,Proc,WS);
+ Parent ->
+ get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS)
+ end;
+ "Started" ->
+ get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)},WS);
+ "Last scheduled in for" ->
+ get_procinfo(Fd,Fun,Proc#proc{current_func=
+ {"Last scheduled in for",
+ val(Fd)}},WS);
+ "Current call" ->
+ get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call",
+ val(Fd)}},WS);
+ "Number of heap fragments" ->
+ get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)},WS);
+ "Heap fragment data" ->
+ get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)},WS);
+ "OldHeap" ->
+ Bytes = list_to_integer(val(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS);
+ "Heap unused" ->
+ Bytes = list_to_integer(val(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS);
+ "OldHeap unused" ->
+ Bytes = list_to_integer(val(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS);
+ "New heap start" ->
+ get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)},WS);
+ "New heap top" ->
+ get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)},WS);
+ "Stack top" ->
+ get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)},WS);
+ "Stack end" ->
+ get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)},WS);
+ "Old heap start" ->
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)},WS);
+ "Old heap top" ->
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)},WS);
+ "Old heap end" ->
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_end=val(Fd)},WS);
+ %% - END - moved from get_procinfo -
"Last calls" ->
- R = case size_or_term(Fd) of
- SizeThing when is_tuple(SizeThing) ->
- Proc#proc{last_calls=SizeThing};
- Term ->
- Proc#proc{last_calls=replace_all($ ,$\n,Term,[])}
- end,
- get_procinfo(Fd,Fun,R);
+ get_procinfo(Fd,Fun,Proc#proc{last_calls=get_lines_to_empty(Fd)},WS);
"Link list" ->
- get_procinfo(Fd,Fun,Proc#proc{links=val(Fd)});
+ {Links,Monitors,MonitoredBy} = parse_link_list(val(Fd),[],[],[]),
+ get_procinfo(Fd,Fun,Proc#proc{links=Links,
+ monitors=Monitors,
+ mon_by=MonitoredBy},WS);
"Program counter" ->
- get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)});
+ get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)},WS);
"CP" ->
- get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)});
+ get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)},WS);
"arity = " ++ Arity ->
%%! Temporary workaround
- get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"});
- "Dictionary" ->
- get_procinfo(Fd,Fun,Proc#proc{dict=size_or_term(Fd)});
- "$Dictionary" ->
- get_procinfo(Fd,Fun,Proc#proc{debug_dict=size_or_term(Fd)});
- "Stack dump" ->
- %% This is the last element in older dumps (DumpVsn=0.0)
- get_stack_dump(Fd,Proc);
+ get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"},WS);
"=" ++ _next_tag ->
- %% DumpVsn=0.1 or newer: No stack dump here
Proc;
Other ->
unexpected(Fd,Other,"process info"),
- get_procinfo(Fd,Fun,Proc)
+ get_procinfo(Fd,Fun,Proc,WS)
+ end.
+
+parse_link_list([SB|Str],Links,Monitors,MonitoredBy) when SB==$[; SB==$] ->
+ parse_link_list(Str,Links,Monitors,MonitoredBy);
+parse_link_list("#Port"++_=Str,Links,Monitors,MonitoredBy) ->
+ {Link,Rest} = parse_port(Str),
+ parse_link_list(Rest,[Link|Links],Monitors,MonitoredBy);
+parse_link_list("<"++_=Str,Links,Monitors,MonitoredBy) ->
+ {Link,Rest} = parse_pid(Str),
+ parse_link_list(Rest,[Link|Links],Monitors,MonitoredBy);
+parse_link_list("{to,"++Str,Links,Monitors,MonitoredBy) ->
+ {Mon,Rest} = parse_monitor(Str),
+ parse_link_list(Rest,Links,[Mon|Monitors],MonitoredBy);
+parse_link_list("{from,"++Str,Links,Monitors,MonitoredBy) ->
+ {Mon,Rest} = parse_monitor(Str),
+ parse_link_list(Rest,Links,Monitors,[Mon|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)}.
+
+parse_port(Str) ->
+ {Port,Rest} = parse_link(Str,[]),
+ {{Port,Port},Rest}.
+
+parse_pid(Str) ->
+ {Pid,Rest} = parse_link(Str,[]),
+ {{Pid,Pid},Rest}.
+
+parse_monitor(Str) ->
+ case parse_link(Str,[]) of
+ {Pid,","++Rest1} ->
+ case parse_link(Rest1,[]) of
+ {Ref,"}"++Rest2} ->
+ {{Pid,Pid++" ("++Ref++")"},Rest2};
+ {Ref,[]} ->
+ {{Pid,Pid++" ("++Ref++")"},[]}
+ end;
+ {Pid,[]} ->
+ {{Pid,Pid++" (unknown_ref)"},[]}
end.
-get_stack_dump(Fd,Proc) ->
- %% Always show stackdump as "Expand" link
- Pos = get(pos),
- Size = count_rest_of_tag(Fd),
- Proc#proc{stack_dump={size,true,Size,Pos}}.
+parse_link(">"++Rest,Acc) ->
+ {lists:reverse(Acc,">"),Rest};
+parse_link([H|T],Acc) ->
+ parse_link(T,[H|Acc]);
+parse_link([],Acc) ->
+ %% truncated
+ {lists:reverse(Acc),[]}.
-maybe_other_node(File,Id) ->
+maybe_other_node(Id) ->
Channel =
case split($.,Id) of
{"<" ++ N, _Rest} ->
@@ -1688,13 +1288,10 @@ maybe_other_node(File,Id) ->
end),
case ets:select(cdv_dump_index_table,Ms) of
- [] ->
+ [] ->
not_found;
- [{Type,Pos}] ->
- Fd = open(File),
- NodeInfo = get_nodeinfo(Fd,Channel,Pos),
- close(Fd),
- {other_node,Type,NodeInfo}
+ [_] ->
+ {other_node,Channel}
end.
@@ -1706,8 +1303,7 @@ expand_memory(File,What,Pid,Binaries) ->
case What of
"StackDump" -> read_stack_dump(Fd,Pid,Dict);
"MsgQueue" -> read_messages(Fd,Pid,Dict);
- "Dictionary" -> read_dictionary(Fd,?proc_dictionary,Pid,Dict);
- "DebugDictionary" -> read_dictionary(Fd,?debug_proc_dictionary,Pid,Dict)
+ "Dictionary" -> read_dictionary(Fd,?proc_dictionary,Pid,Dict)
end,
erase(fd),
close(Fd),
@@ -1716,11 +1312,19 @@ expand_memory(File,What,Pid,Binaries) ->
%%%
%%% Read binaries.
%%%
-read_binaries(Fd) ->
+read_binaries(Fd,ProgressReceiver) ->
AllBinaries = lookup_index(?binary),
- read_binaries(Fd,AllBinaries, gb_trees:empty()).
-
-read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0) ->
+ NumBinaries = length(AllBinaries),
+ ReportInterval = (NumBinaries div 100) + 1,
+ report_progress(ProgressReceiver,{ok,"Processing binaries"}),
+ read_binaries(Fd, AllBinaries, gb_trees:empty(),
+ ProgressReceiver,ReportInterval,ReportInterval,0).
+
+read_binaries(Fd,Bins,Dict,Receiver,0,ReportInterval,Percent0) ->
+ Percent = Percent0+1,
+ report_progress(Receiver,{ok,Percent}),
+ read_binaries(Fd,Bins,Dict,Receiver,ReportInterval,ReportInterval,Percent);
+read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0,Receiver,Count,ReportInterval,Percent) ->
pos_bof(Fd,Pos),
{Addr,_} = get_hex(Addr0),
Dict =
@@ -1737,8 +1341,9 @@ read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0) ->
parse_binary(Addr,Line,Dict0)
end
end,
- read_binaries(Fd,Bins,Dict);
-read_binaries(_Fd,[],Dict) ->
+ read_binaries(Fd,Bins,Dict,Receiver,Count-1,ReportInterval,Percent);
+read_binaries(_Fd,[],Dict,Receiver,_Count,_ReportInterval,_Percent) ->
+ report_progress(Receiver,{ok,100}),
Dict.
parse_binary(Addr, Line0, Dict) ->
@@ -1875,56 +1480,6 @@ parse(Line0, Dict0) ->
Dict.
-do_sort_procs("state",Procs,#state{sorted="state"}) ->
- {lists:reverse(lists:keysort(#proc.state,Procs)),"rstate"};
-do_sort_procs("state",Procs,_) ->
- {lists:keysort(#proc.state,Procs),"state"};
-do_sort_procs("pid",Procs,#state{sorted="pid"}) ->
- {lists:reverse(Procs),"rpid"};
-do_sort_procs("pid",Procs,_) ->
- {Procs,"pid"};
-do_sort_procs("msg_q_len",Procs,#state{sorted="msg_q_len"}) ->
- {lists:keysort(#proc.msg_q_len,Procs),"rmsg_q_len"};
-do_sort_procs("msg_q_len",Procs,_) ->
- {lists:reverse(lists:keysort(#proc.msg_q_len,Procs)),"msg_q_len"};
-do_sort_procs("reds",Procs,#state{sorted="reds"}) ->
- {lists:keysort(#proc.reds,Procs),"rreds"};
-do_sort_procs("reds",Procs,_) ->
- {lists:reverse(lists:keysort(#proc.reds,Procs)),"reds"};
-do_sort_procs("mem",Procs,#state{sorted="mem",dump_vsn=DumpVsn}) ->
- KeyPos = if DumpVsn>=?r16b01_dump_vsn -> #proc.memory;
- true -> #proc.stack_heap
- end,
- {lists:keysort(KeyPos,Procs),"rmem"};
-do_sort_procs("mem",Procs,#state{dump_vsn=DumpVsn}) ->
- KeyPos = if DumpVsn>=?r16b01_dump_vsn -> #proc.memory;
- true -> #proc.stack_heap
- end,
- {lists:reverse(lists:keysort(KeyPos,Procs)),"mem"};
-do_sort_procs("init_func",Procs,#state{sorted="init_func"}) ->
- {lists:reverse(lists:keysort(#proc.init_func,Procs)),"rinit_func"};
-do_sort_procs("init_func",Procs,_) ->
- {lists:keysort(#proc.init_func,Procs),"init_func"};
-do_sort_procs("name_func",Procs,#state{sorted="name_func"}) ->
- {lists:reverse(lists:keysort(#proc.name,Procs)),"rname_func"};
-do_sort_procs("name_func",Procs,_) ->
- {lists:keysort(#proc.name,Procs),"name_func"};
-do_sort_procs("name",Procs,#state{sorted=Sorted}) ->
- {No,Yes} =
- lists:foldl(fun(P,{N,Y}) ->
- case P#proc.name of
- ?space -> {[P|N],Y};
- _other -> {N,[P|Y]}
- end
- end,
- {[],[]},
- Procs),
- Result = lists:keysort(#proc.name,Yes) ++ No,
- case Sorted of
- "name" -> {lists:reverse(Result),"rname"};
- _ -> {Result,"name"}
- end.
-
%%-----------------------------------------------------------------
%% Page with one port
get_port(File,Port) ->
@@ -1936,46 +1491,59 @@ get_port(File,Port) ->
close(Fd),
{ok,R};
[] ->
- case maybe_other_node(File,Port) of
- {other_node,Type,Node} ->
- Info = "The port you are searching for was residing on "
- "a remote node. No port information is available. "
- "Information about the remote node is show below.",
- {other_node,{Type,Info,Node}};
- not_found ->
- not_found
- end
+ maybe_other_node(Port)
end.
%%-----------------------------------------------------------------
%% Page with all ports
-get_ports(SessionId,File,TW) ->
- ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=Id}) end,
- chunk_page(SessionId,File,TW,?port,ports,[],ParseFun).
+get_ports(File) ->
+ ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=port_to_tuple(Id)}) end,
+ lookup_and_parse_index(File,?port,ParseFun).
+
+%% Converting port string to tuple to secure correct sorting. This is
+%% converted back in cdv_port_wx:format/1.
+port_to_tuple("#Port<"++Port) ->
+ [I1,I2] = string:tokens(Port,".>"),
+ {list_to_integer(I1),list_to_integer(I2)}.
get_portinfo(Fd,Port) ->
case line_head(Fd) of
"Slot" ->
- get_portinfo(Fd,Port#port{slot=val(Fd)});
+ %% stored as integer so we can sort on it
+ get_portinfo(Fd,Port#port{slot=list_to_integer(val(Fd))});
"Connected" ->
- get_portinfo(Fd,Port#port{connected=val(Fd)});
+ %% stored as pid so we can sort on it
+ Connected0 = val(Fd),
+ Connected =
+ try list_to_pid(Connected0)
+ catch error:badarg -> Connected0
+ end,
+ get_portinfo(Fd,Port#port{connected=Connected});
"Links" ->
- get_portinfo(Fd,Port#port{links=val(Fd)});
+ Pids = split_pid_list_no_space(val(Fd)),
+ Links = [{Pid,Pid} || Pid <- Pids],
+ get_portinfo(Fd,Port#port{links=Links});
"Registered as" ->
get_portinfo(Fd,Port#port{name=val(Fd)});
"Monitors" ->
- get_portinfo(Fd,Port#port{monitors=val(Fd)});
+ Monitors0 = string:tokens(val(Fd),"()"),
+ Monitors = [begin
+ [Pid,Ref] = string:tokens(Mon,","),
+ {Pid,Pid++" ("++Ref++")"}
+ end || Mon <- Monitors0],
+ get_portinfo(Fd,Port#port{monitors=Monitors});
"Port controls linked-in driver" ->
- get_portinfo(Fd,Port#port{controls=["Linked in driver: " |
- val(Fd)]});
+ Str = lists:flatten(["Linked in driver: " | val(Fd)]),
+ get_portinfo(Fd,Port#port{controls=Str});
"Port controls external process" ->
- get_portinfo(Fd,Port#port{controls=["External proc: " | val(Fd)]});
+ Str = lists:flatten(["External proc: " | val(Fd)]),
+ get_portinfo(Fd,Port#port{controls=Str});
"Port is a file" ->
- get_portinfo(Fd,Port#port{controls=["File: "| val(Fd)]});
+ Str = lists:flatten(["File: "| val(Fd)]),
+ get_portinfo(Fd,Port#port{controls=Str});
"Port is UNIX fd not opened by emulator" ->
- get_portinfo(Fd,Port#port{
- controls=["UNIX fd not opened by emulator: "|
- val(Fd)]});
+ Str = lists:flatten(["UNIX fd not opened by emulator: "| val(Fd)]),
+ get_portinfo(Fd,Port#port{controls=Str});
"=" ++ _next_tag ->
Port;
Other ->
@@ -1983,17 +1551,27 @@ get_portinfo(Fd,Port) ->
Port
end.
+split_pid_list_no_space(String) ->
+ split_pid_list_no_space(String,[],[]).
+split_pid_list_no_space([$>|Rest],Acc,Pids) ->
+ split_pid_list_no_space(Rest,[],[lists:reverse(Acc,[$>])|Pids]);
+split_pid_list_no_space([H|T],Acc,Pids) ->
+ split_pid_list_no_space(T,[H|Acc],Pids);
+split_pid_list_no_space([],[],Pids) ->
+ lists:reverse(Pids).
%%-----------------------------------------------------------------
%% Page with external ets tables
-get_ets_tables(SessionId,File,Heading,TW,Pid,WS) ->
- ParseFun = fun(Fd,Id) -> get_etsinfo(Fd,#ets_table{pid=Id},WS) end,
- chunk_page(SessionId,File,TW,{?ets,Pid},ets_tables,Heading,ParseFun).
+get_ets_tables(File,Pid,WS) ->
+ ParseFun = fun(Fd,Id) ->
+ get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS)
+ end,
+ lookup_and_parse_index(File,{?ets,Pid},ParseFun).
get_etsinfo(Fd,EtsTable,WS) ->
case line_head(Fd) of
"Slot" ->
- get_etsinfo(Fd,EtsTable#ets_table{slot=val(Fd)},WS);
+ get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(val(Fd))},WS);
"Table" ->
get_etsinfo(Fd,EtsTable#ets_table{id=val(Fd)},WS);
"Name" ->
@@ -2002,15 +1580,18 @@ get_etsinfo(Fd,EtsTable,WS) ->
skip_rest_of_line(Fd),
get_etsinfo(Fd,EtsTable#ets_table{type="tree",buckets="-"},WS);
"Buckets" ->
- get_etsinfo(Fd,EtsTable#ets_table{buckets=val(Fd)},WS);
+ %% A bug in erl_db_hash.c prints a space after the buckets
+ %% - need to strip the string to make list_to_integer/1 happy.
+ Buckets = list_to_integer(string:strip(val(Fd))),
+ get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS);
"Objects" ->
- get_etsinfo(Fd,EtsTable#ets_table{size=val(Fd)},WS);
+ get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(val(Fd))},WS);
"Words" ->
Words = list_to_integer(val(Fd)),
Bytes =
case Words of
- -1 -> "-1"; % probably truncated
- _ -> integer_to_list(Words * WS)
+ -1 -> -1; % probably truncated
+ _ -> Words * WS
end,
get_etsinfo(Fd,EtsTable#ets_table{memory=Bytes},WS);
"=" ++ _next_tag ->
@@ -2036,16 +1617,17 @@ get_internal_ets_tables(File,WS) ->
%%-----------------------------------------------------------------
%% Page with list of all timers
-get_timers(SessionId,File,Heading,TW,Pid) ->
- ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=Id}) end,
- chunk_page(SessionId,File,TW,{?timer,Pid},timers,Heading,ParseFun).
+get_timers(File,Pid) ->
+ ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=list_to_pid(Id)}) end,
+ lookup_and_parse_index(File,{?timer,Pid},ParseFun).
get_timerinfo_1(Fd,Timer) ->
case line_head(Fd) of
"Message" ->
get_timerinfo_1(Fd,Timer#timer{msg=val(Fd)});
"Time left" ->
- get_timerinfo_1(Fd,Timer#timer{time=val(Fd)});
+ TimeLeft = list_to_integer(val(Fd) -- " ms"),
+ get_timerinfo_1(Fd,Timer#timer{time=TimeLeft});
"=" ++ _next_tag ->
Timer;
Other ->
@@ -2054,6 +1636,28 @@ get_timerinfo_1(Fd,Timer) ->
end.
%%-----------------------------------------------------------------
+%% Page with information about a node in the distribution
+get_node(File,Channel) ->
+ Ms = ets:fun2ms(
+ fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel ->
+ {visible,Start};
+ ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel ->
+ {hidden,Start};
+ ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel ->
+ {not_connected,Start}
+ end),
+
+ case ets:select(cdv_dump_index_table,Ms) of
+ [] ->
+ not_found;
+ [{Type,Pos}] ->
+ Fd = open(File),
+ NodeInfo = get_nodeinfo(Fd,Channel,Type,Pos),
+ close(Fd),
+ NodeInfo
+ end.
+
+%%-----------------------------------------------------------------
%% Page with information about the erlang distribution
nods(File) ->
case lookup_index(?no_distribution) of
@@ -2064,28 +1668,29 @@ nods(File) ->
Fd = open(File),
Visible = lists:map(
fun({Channel,Start}) ->
- get_nodeinfo(Fd,Channel,Start)
+ get_nodeinfo(Fd,Channel,visible,Start)
end,
V),
Hidden = lists:map(
fun({Channel,Start}) ->
- get_nodeinfo(Fd,Channel,Start)
+ get_nodeinfo(Fd,Channel,hidden,Start)
end,
H),
NotConnected = lists:map(
fun({Channel,Start}) ->
- get_nodeinfo(Fd,Channel,Start)
+ get_nodeinfo(Fd,Channel,not_connected,Start)
end,
N),
close(Fd),
- {Visible,Hidden,NotConnected};
+ Visible++Hidden++NotConnected;
[_] ->
- no_distribution
+ %% no_distribution
+ []
end.
-get_nodeinfo(Fd,Channel,Start) ->
+get_nodeinfo(Fd,Channel,Type,Start) ->
pos_bof(Fd,Start),
- get_nodeinfo(Fd,#nod{channel=Channel}).
+ get_nodeinfo(Fd,#nod{channel=list_to_integer(Channel),conn_type=Type}).
get_nodeinfo(Fd,Nod) ->
case line_head(Fd) of
@@ -2094,21 +1699,27 @@ get_nodeinfo(Fd,Nod) ->
"Controller" ->
get_nodeinfo(Fd,Nod#nod{controller=val(Fd)});
"Creation" ->
- get_nodeinfo(Fd,Nod#nod{creation=val(Fd)});
+ get_nodeinfo(Fd,Nod#nod{creation=list_to_integer(val(Fd))});
"Remote link" ->
Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
- RemoteLinks = Nod#nod.remote_links,
- get_nodeinfo(Fd,Nod#nod{remote_links=[split(Procs)|RemoteLinks]});
+ {Local,Remote} = split(Procs),
+ Str = Local++" <-> "++Remote,
+ NewRemLinks = [{Local,Str} | Nod#nod.remote_links],
+ get_nodeinfo(Fd,Nod#nod{remote_links=NewRemLinks});
"Remote monitoring" ->
Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
- RemoteMon = Nod#nod.remote_mon,
- get_nodeinfo(Fd,Nod#nod{remote_mon=[split(Procs)|RemoteMon]});
+ {Local,Remote} = split(Procs),
+ Str = Local++" -> "++Remote,
+ NewRemMon = [{Local,Str} | Nod#nod.remote_mon],
+ get_nodeinfo(Fd,Nod#nod{remote_mon=NewRemMon});
"Remotely monitored by" ->
Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
- RemoteMonBy = Nod#nod.remote_mon_by,
- get_nodeinfo(Fd,Nod#nod{remote_mon_by=[split(Procs)|RemoteMonBy]});
+ {Local,Remote} = split(Procs),
+ Str = Local++" <- "++Remote,
+ NewRemMonBy = [{Local,Str} | Nod#nod.remote_mon_by],
+ get_nodeinfo(Fd,Nod#nod{remote_mon_by=NewRemMonBy});
"Error" ->
- get_nodeinfo(Fd,Nod#nod{error=val(Fd)});
+ get_nodeinfo(Fd,Nod#nod{error="ERROR: "++val(Fd)});
"=" ++ _next_tag ->
Nod;
Other ->
@@ -2129,10 +1740,11 @@ get_loaded_mod_details(File,Mod) ->
%%-----------------------------------------------------------------
%% Page with list of all loaded modules
-loaded_mods(SessionId,File,TW) ->
+loaded_mods(File) ->
ParseFun =
fun(Fd,Id) ->
- get_loaded_mod_info(Fd,#loaded_mod{mod=Id},
+ get_loaded_mod_info(Fd,
+ #loaded_mod{mod=get_atom(list_to_binary(Id))},
fun main_modinfo/3)
end,
{CC,OC} =
@@ -2146,7 +1758,7 @@ loaded_mods(SessionId,File,TW) ->
[] ->
{"unknown","unknown"}
end,
- chunk_page(SessionId,File,TW,?mod,loaded_mods,{CC,OC},ParseFun).
+ {CC,OC,lookup_and_parse_index(File,?mod,ParseFun)}.
get_loaded_mod_totals(Fd,{CC,OC}) ->
case line_head(Fd) of
@@ -2164,9 +1776,11 @@ get_loaded_mod_totals(Fd,{CC,OC}) ->
get_loaded_mod_info(Fd,LM,Fun) ->
case line_head(Fd) of
"Current size" ->
- get_loaded_mod_info(Fd,LM#loaded_mod{current_size=val(Fd)},Fun);
+ CS = list_to_integer(val(Fd)),
+ get_loaded_mod_info(Fd,LM#loaded_mod{current_size=CS},Fun);
"Old size" ->
- get_loaded_mod_info(Fd,LM#loaded_mod{old_size=val(Fd)},Fun);
+ OS = list_to_integer(val(Fd)),
+ get_loaded_mod_info(Fd,LM#loaded_mod{old_size=OS},Fun);
"=" ++ _next_tag ->
LM;
{eof,_} ->
@@ -2229,24 +1843,24 @@ hex_to_dec(N) -> list_to_integer(N).
%%-----------------------------------------------------------------
%% Page with list of all funs
-funs(SessionId,File,TW) ->
+funs(File) ->
ParseFun = fun(Fd,_Id) -> get_funinfo(Fd,#fu{}) end,
- chunk_page(SessionId,File,TW,?fu,funs,[],ParseFun).
+ lookup_and_parse_index(File,?fu,ParseFun).
get_funinfo(Fd,Fu) ->
case line_head(Fd) of
"Module" ->
get_funinfo(Fd,Fu#fu{module=val(Fd)});
"Uniq" ->
- get_funinfo(Fd,Fu#fu{uniq=val(Fd)});
+ get_funinfo(Fd,Fu#fu{uniq=list_to_integer(val(Fd))});
"Index" ->
- get_funinfo(Fd,Fu#fu{index=val(Fd)});
+ get_funinfo(Fd,Fu#fu{index=list_to_integer(val(Fd))});
"Address" ->
get_funinfo(Fd,Fu#fu{address=val(Fd)});
"Native_address" ->
get_funinfo(Fd,Fu#fu{native_address=val(Fd)});
"Refc" ->
- get_funinfo(Fd,Fu#fu{refc=val(Fd)});
+ get_funinfo(Fd,Fu#fu{refc=list_to_integer(val(Fd))});
"=" ++ _next_tag ->
Fu;
Other ->
@@ -2256,45 +1870,51 @@ get_funinfo(Fd,Fu) ->
%%-----------------------------------------------------------------
%% Page with list of all atoms
-atoms(SessionId,File,TW,Num) ->
+atoms(File,NumAtoms) ->
case lookup_index(?atoms) of
[{_Id,Start}] ->
Fd = open(File),
pos_bof(Fd,Start),
- case get_atoms(Fd,?items_chunk_size) of
- {Atoms,Cont} ->
- crashdump_viewer_html:atoms(SessionId,TW,Num,Atoms),
- atoms_chunks(Fd,SessionId,Cont);
- done ->
- crashdump_viewer_html:atoms(SessionId,TW,Num,done)
- end;
+ get_atoms(Fd,NumAtoms);
_ ->
- crashdump_viewer_html:atoms(SessionId,TW,Num,done)
+ []
end.
-get_atoms(Fd,Number) ->
- case get_n_lines_of_tag(Fd,Number) of
- {all,_,Lines} ->
- close(Fd),
- {Lines,done};
- {part,_,Lines} ->
- {Lines,Number};
- empty ->
- close(Fd),
- done
+get_atoms(Fd,NumAtoms) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ get_atoms(Fd,Bin,NumAtoms,[]);
+ eof ->
+ []
end.
-atoms_chunks(_Fd,SessionId,done) ->
- crashdump_viewer_html:atoms_chunk(SessionId,done);
-atoms_chunks(Fd,SessionId,Number) ->
- case get_atoms(Fd,Number) of
- {Atoms,Cont} ->
- crashdump_viewer_html:atoms_chunk(SessionId,Atoms),
- atoms_chunks(Fd,SessionId,Cont);
- done ->
- atoms_chunks(Fd,SessionId,done)
- end.
+%% Atoms are written one per line in the crash dump, in creation order
+%% from last to first.
+get_atoms(Fd,Bin,NumAtoms,Atoms) ->
+ Bins = binary:split(Bin,<<"\n">>,[global]),
+ get_atoms1(Fd,Bins,NumAtoms,Atoms).
+
+get_atoms1(_Fd,[<<"=",_/binary>>|_],_N,Atoms) ->
+ Atoms;
+get_atoms1(Fd,[LastBin],N,Atoms) ->
+ case get_chunk(Fd) of
+ {ok,Bin0} ->
+ get_atoms(Fd,<<LastBin/binary,Bin0/binary>>,N,Atoms);
+ eof ->
+ Atoms
+ end;
+get_atoms1(Fd,[Bin|Bins],N,Atoms) ->
+ get_atoms1(Fd,Bins,N-1,[{N,get_atom(Bin)}|Atoms]).
+
+
+%% This ensures sorting according to first actual letter in the atom,
+%% disregarding possible single quote. It is formatted back to correct
+%% syntax in cdv_atom_wx:format/1
+get_atom(<<"\'",Atom/binary>>) ->
+ {Atom,q}; % quoted
+get_atom(Atom) when is_binary(Atom) ->
+ {Atom,nq}. % not quoted
%%-----------------------------------------------------------------
%% Page with memory information
@@ -2317,7 +1937,7 @@ get_meminfo(Fd,Acc) ->
{eof,_last_line} ->
lists:reverse(Acc);
Key ->
- get_meminfo(Fd,[{Key,val(Fd)}|Acc])
+ get_meminfo(Fd,[{list_to_atom(Key),val(Fd)}|Acc])
end.
%%-----------------------------------------------------------------
@@ -2345,7 +1965,7 @@ get_allocareainfo(Fd,Acc) ->
AllocInfo =
case split(Val) of
{Alloc,[]} ->
- {Key,Alloc,?space};
+ {Key,Alloc,""};
{Alloc,Used} ->
{Key,Alloc,Used}
end,
@@ -2361,7 +1981,7 @@ allocator_info(File) ->
AllAllocators ->
Fd = open(File),
R = lists:map(fun({Heading,Start}) ->
- {Heading,get_allocatorinfo(Fd,Start)}
+ {Heading,get_allocatorinfo(Fd,Start)}
end,
AllAllocators),
close(Fd),
@@ -2370,17 +1990,19 @@ allocator_info(File) ->
get_allocatorinfo(Fd,Start) ->
pos_bof(Fd,Start),
- get_allocatorinfo1(Fd,[]).
+ get_allocatorinfo1(Fd,[],0).
-get_allocatorinfo1(Fd,Acc) ->
+get_allocatorinfo1(Fd,Acc,Max) ->
case line_head(Fd) of
"=" ++ _next_tag ->
- lists:reverse(Acc);
+ pad_and_reverse(Acc,Max,[]);
{eof,_last_line} ->
- lists:reverse(Acc);
+ pad_and_reverse(Acc,Max,[]);
Key ->
Values = get_all_vals(val(Fd),[]),
- get_allocatorinfo1(Fd,[{Key,Values}|Acc])
+ L = length(Values),
+ Max1 = if L > Max -> L; true -> Max end,
+ get_allocatorinfo1(Fd,[{Key,Values}|Acc],Max1)
end.
get_all_vals([$ |Rest],Acc) ->
@@ -2390,6 +2012,16 @@ get_all_vals([],Acc) ->
get_all_vals([Char|Rest],Acc) ->
get_all_vals(Rest,[Char|Acc]).
+%% Make sure all V have the same length by padding with "".
+pad_and_reverse([{K,V}|T],Len,Rev) ->
+ VLen = length(V),
+ V1 = if VLen == Len -> V;
+ true -> V ++ lists:duplicate(Len-VLen,"")
+ end,
+ pad_and_reverse(T,Len,[{K,V1}|Rev]);
+pad_and_reverse([],_,Rev) ->
+ Rev.
+
%% Calculate allocator summary:
%%
%% System totals:
@@ -2473,7 +2105,8 @@ allocator_summary(Allocators) ->
{TBS,TCS} ->
{integer_to_list(TBS),integer_to_list(TCS)}
end,
- {{"Summary",["blocks size","carriers size","mseg carriers size"]},
+ {"Allocator Summary",
+ ["blocks size","carriers size","mseg carriers size"],
[{"total",[TotalBS,TotalCS,TotalMCS]} |
format_allocator_summary(lists:reverse(TypeTotals))]}.
@@ -2673,32 +2306,6 @@ get_indextableinfo1(Fd,IndexTable) ->
IndexTable
end.
-
-
-
-
-%%-----------------------------------------------------------------
-%% Expand a set of data which was shown in a truncated form on
-get_expanded(File,Pos,Size) ->
- Fd = open(File),
- R = case file:pread(Fd,Pos,Size) of
- {ok,Bin}->
- binary_to_list(Bin);
- eof ->
- ?space
- end,
- close(Fd),
- R.
-
-
-replace_all(From,To,[From|Rest],Acc) ->
- replace_all(From,To,Rest,[To|Acc]);
-replace_all(From,To,[Char|Rest],Acc) ->
- replace_all(From,To,Rest,[Char|Acc]);
-replace_all(_From,_To,[],Acc) ->
- lists:reverse(Acc).
-
-
%%%-----------------------------------------------------------------
%%% Parse memory in crashdump version 0.1 and newer
%%%
@@ -2947,25 +2554,13 @@ reset_index_table() ->
insert_index(Tag,Id,Pos) ->
ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}).
+lookup_index({Tag,Id}) ->
+ lookup_index(Tag,Id);
lookup_index(Tag) ->
lookup_index(Tag,'$2').
lookup_index(Tag,Id) ->
ets:select(cdv_dump_index_table,[{{{Tag,'$1'},Id},[],[{{Id,'$1'}}]}]).
-lookup_index_chunk({'#CDVFirstChunk',Tag,Id}) ->
- ets:select(cdv_dump_index_table,
- [{{{Tag,'$1'},Id},[],[{{Id,'$1'}}]}],
- ?items_chunk_size);
-lookup_index_chunk(Cont) ->
- ets:select(Cont).
-
-%% Create a tag which can be used instead of an ets Continuation for
-%% the first call to lookup_index_chunk.
-first_chunk_pointer({Tag,Id}) ->
- {'#CDVFirstChunk',Tag,Id};
-first_chunk_pointer(Tag) ->
- first_chunk_pointer({Tag,'$2'}).
-
count_index(Tag) ->
ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]).
count_index(Tag,Id) ->
@@ -2979,7 +2574,6 @@ tag_to_atom("allocated_areas") -> ?allocated_areas;
tag_to_atom("allocator") -> ?allocator;
tag_to_atom("atoms") -> ?atoms;
tag_to_atom("binary") -> ?binary;
-tag_to_atom("debug_proc_dictionary") -> ?debug_proc_dictionary;
tag_to_atom("end") -> ?ende;
tag_to_atom("erl_crash_dump") -> ?erl_crash_dump;
tag_to_atom("ets") -> ?ets;
@@ -3010,37 +2604,26 @@ tag_to_atom(UnknownTag) ->
list_to_atom(UnknownTag).
%%%-----------------------------------------------------------------
-%%% Create a page by sending chunk by chunk to crashdump_viewer_html
-chunk_page(SessionId,File,TW,What,HtmlCB,HtmlExtra,ParseFun) ->
+%%% Fetch next chunk from crashdump file
+lookup_and_parse_index(File,What,ParseFun) when is_list(File) ->
Fd = open(File),
- case lookup_and_parse_index_chunk(first_chunk_pointer(What),Fd,ParseFun) of
- done ->
- crashdump_viewer_html:chunk_page(HtmlCB,SessionId,TW,HtmlExtra,done);
- {Chunk,Cont} ->
- HtmlInfo = crashdump_viewer_html:chunk_page(
- HtmlCB,
- SessionId,TW,HtmlExtra,Chunk),
- chunk_page_1(Fd,HtmlInfo,SessionId,ParseFun,
- lookup_and_parse_index_chunk(Cont,Fd,ParseFun))
- end.
-
-chunk_page_1(_Fd,HtmlInfo,SessionId,_ParseFun,done) ->
- crashdump_viewer_html:chunk(SessionId,done,HtmlInfo);
-chunk_page_1(Fd,HtmlInfo,SessionId,ParseFun,{Chunk,Cont}) ->
- crashdump_viewer_html:chunk(SessionId,Chunk,HtmlInfo),
- chunk_page_1(Fd,HtmlInfo,SessionId,ParseFun,
- lookup_and_parse_index_chunk(Cont,Fd,ParseFun)).
-
-lookup_and_parse_index_chunk(Pointer,Fd,ParseFun) ->
- case lookup_index_chunk(Pointer) of
- '$end_of_table' ->
- close(Fd),
- done;
- {Chunk,Cont} ->
- R = lists:map(fun({Id,Start}) ->
- pos_bof(Fd,Start),
- ParseFun(Fd,Id)
- end,
- Chunk),
- {R,Cont}
- end.
+ Indices = lookup_index(What),
+ R = lists:map(fun({Id,Start}) ->
+ pos_bof(Fd,Start),
+ ParseFun(Fd,Id)
+ end,
+ Indices),
+ close(Fd),
+ R.
+
+%%%-----------------------------------------------------------------
+%%% Convert a record to a proplist
+to_proplist(Fields,Record) ->
+ Values = to_value_list(Record),
+ lists:zip(Fields,Values).
+
+%%%-----------------------------------------------------------------
+%%% Convert a record to a simple list of field values
+to_value_list(Record) ->
+ [_RecordName|Values] = tuple_to_list(Record),
+ Values.
diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl
index 2e0ea5cf96..ae288ed573 100644
--- a/lib/observer/src/crashdump_viewer.hrl
+++ b/lib/observer/src/crashdump_viewer.hrl
@@ -16,7 +16,7 @@
%%
%% %CopyrightEnd%
%%
--define(space, "&nbsp;").
+-define(space, undefined).
-define(unknown, "unknown").
-define(r16b01_dump_vsn, [0,2]). % =erl_crash_dump:0.2
@@ -24,28 +24,28 @@
-record(general_info,
{created,
- slogan=?space,
- system_vsn=?space,
- compile_time=?space,
- taints=?space,
- node_name=?space,
- num_atoms=?space,
- num_procs=?space,
- num_ets=?space,
- num_timers=?space,
- num_fun=?space,
- mem_tot=?space,
- mem_max=?space,
- instr_info=?space}).
+ slogan,
+ system_vsn,
+ compile_time,
+ taints,
+ node_name,
+ num_atoms,
+ num_procs,
+ num_ets,
+ num_timers,
+ num_fun,
+ mem_tot,
+ mem_max,
+ instr_info}).
-record(proc,
%% Initial data according to the follwoing:
%%
- %% msg_q_len, reds and stack_heap are integers because it must
+ %% msg_q_len, reds, memory and stack_heap are integers because it must
%% be possible to sort on them. All other fields are strings
%%
- %% for old dumps start_time, parent and number of heap frament
- %% does not exist
+ %% for old dumps start_time, parent and number of heap framents
+ %% do not exist
%%
%% current_func can be both "current function" and
%% "last scheduled in for"
@@ -54,100 +54,102 @@
%% displayed as a link to "Expand" (if dump is from OTP R9B
%% or newer)
{pid,
- name=?space,
- init_func=?space,
+ name,
+ init_func,
parent=?unknown,
start_time=?unknown,
- state=?space,
- current_func={"Current Function",?space},
+ state,
+ current_func,
msg_q_len=0,
- msg_q=?space,
- last_calls=?space,
- links=?space,
- prog_count=?space,
- cp=?space,
- arity=?space,
- dict=?space,
- debug_dict=?space,
+ msg_q,
+ last_calls,
+ links,
+ monitors,
+ mon_by,
+ prog_count,
+ cp,
+ arity,
+ dict,
reds=0,
num_heap_frag=?unknown,
- heap_frag_data=?space,
+ heap_frag_data,
stack_heap=0,
- old_heap=?space,
- heap_unused=?space,
- old_heap_unused=?space,
- new_heap_start=?space,
- new_heap_top=?space,
- stack_top=?space,
- stack_end=?space,
- old_heap_start=?space,
- old_heap_top=?space,
- old_heap_end=?space,
+ old_heap,
+ heap_unused,
+ old_heap_unused,
+ new_heap_start,
+ new_heap_top,
+ stack_top,
+ stack_end,
+ old_heap_start,
+ old_heap_top,
+ old_heap_end,
memory,
- stack_dump=?space}).
+ stack_dump}).
-record(port,
{id,
- slot=?space,
- connected=?space,
- links=?space,
- name=?space,
- monitors=?space,
- controls=?space}).
+ slot,
+ connected,
+ links,
+ name,
+ monitors,
+ controls}).
-record(ets_table,
{pid,
- slot=?space,
- id=?space,
- name=?space,
+ slot,
+ id,
+ name,
type="hash",
- buckets=?space,
- size=?space,
- memory=?space}).
+ buckets,
+ size,
+ memory}).
-record(timer,
{pid,
- msg=?space,
- time=?space}).
+ msg,
+ time}).
-record(fu,
- {module=?space,
- uniq=?space,
- index=?space,
- address=?space,
- native_address=?space,
- refc=?space}).
+ {module,
+ uniq,
+ index,
+ address,
+ native_address,
+ refc}).
-record(nod,
- {name=?space,
+ {name,
channel,
- controller=?space,
- creation=?space,
- remote_links=?space,
- remote_mon=?space,
- remote_mon_by=?space,
- error=?space}).
+ conn_type,
+ controller,
+ creation,
+ remote_links=[],
+ remote_mon=[],
+ remote_mon_by=[],
+ error}).
-record(loaded_mod,
{mod,
- current_size=?space,
- current_attrib=?space,
- current_comp_info=?space,
- old_size=?space,
- old_attrib=?space,
- old_comp_info=?space}).
+ current_size,
+ current_attrib,
+ current_comp_info,
+ old_size,
+ old_attrib,
+ old_comp_info}).
-record(hash_table,
{name,
- size=?space,
- used=?space,
- objs=?space,
- depth=?space}).
+ size,
+ used,
+ objs,
+ depth}).
-record(index_table,
{name,
- size=?space,
- used=?space,
- limit=?space,
- rate=?space,
- entries=?space}).
+ size,
+ limit,
+ used,
+ rate,
+ entries}).
diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl
index 93c1a842b5..23d6fd6b16 100644
--- a/lib/observer/src/crashdump_viewer_html.erl
+++ b/lib/observer/src/crashdump_viewer_html.erl
@@ -31,6 +31,7 @@
menu_frame/0,
general_info/1,
pretty_info_page/2,
+ plain_page/1,
info_page/2,
proc_details/4,
expanded_memory/2,
@@ -47,6 +48,7 @@
hash_tables/2,
index_tables/2,
error/2,
+ warning/1,
chunk_page/5,
chunk/3]).
@@ -262,6 +264,23 @@ error(Text,Args) ->
error_body(Str) ->
[h1("An error occured:"),Str,"\n"].
+%%%-----------------------------------------------------------------
+%%% Display the given information as is, no heading
+%%% Empty body if no info exists.
+warning(Info) ->
+ header(body(warning_body(Info))).
+
+warning_body(Info) ->
+ [warn(Info)].
+
+%%%-----------------------------------------------------------------
+%%% Display the given information as is, no heading
+%%% Empty body if no info exists.
+plain_page(Info) ->
+ header(body(plain_body(Info))).
+
+plain_body(Info) ->
+ [pre(href_proc_port(lists:flatten(Info)))].
%%%-----------------------------------------------------------------
%%% Display the given information as is
@@ -412,7 +431,6 @@ proc_details_body(Heading,Proc,TW,SharedHeap) ->
%% These are displayed only if data exist
display_or_link_to_expand("MsgQueue",Proc#proc.msg_q,Pid),
display_or_link_to_expand("Dictionary",Proc#proc.dict,Pid),
- display_or_link_to_expand("DebugDictionary",Proc#proc.debug_dict,Pid),
display_or_link_to_expand("LastCalls",Proc#proc.last_calls,Pid),
display_or_link_to_expand("StackDump",Proc#proc.stack_dump,Pid)]),
@@ -477,16 +495,13 @@ expanded_memory(Heading,Expanded) ->
header(Heading,body(expanded_memory_body(Heading,Expanded))).
expanded_memory_body(Heading,[]) ->
- [heading(Heading,"processes"),
- case Heading of
+ [case Heading of
"MsgQueue" -> "No messages were found";
"StackDump" -> "No stack dump was found";
- "Dictionary" -> "No dictionary was found";
- "DebugDictionary" -> "No debug dictionary was found"
+ "Dictionary" -> "No dictionary was found"
end];
expanded_memory_body(Heading,Expanded) ->
- [heading(Heading,"processes"),
- case Heading of
+ [case Heading of
"MsgQueue" ->
table(
"BORDER=4 CELLPADDING=4",
@@ -512,7 +527,7 @@ expanded_memory_body(Heading,Expanded) ->
msgq_table({Msg0,Token0}) ->
Token = case Token0 of
- [] -> ?space;
+ [] -> "";
_ -> io_lib:fwrite("~w",[Token0])
end,
Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))),
@@ -924,8 +939,7 @@ index_tables_table(IndexTable) ->
%%%-----------------------------------------------------------------
%%% Internal library
start_html_page(Title) ->
- [only_http_header(),
- start_html(),
+ [start_html(),
only_html_header(Title),
start_html_body()].
@@ -933,10 +947,6 @@ stop_html_page() ->
[stop_html_body(),
stop_html()].
-only_http_header() ->
- ["Pragma:no-cache\r\n",
- "Content-type: text/html\r\n\r\n"].
-
only_html_header(Title) ->
only_html_header(Title,"").
only_html_header(Title,JavaScript) ->
@@ -959,7 +969,7 @@ header(Body) ->
header(Title,Body) ->
header(Title,"",Body).
header(Title,JavaScript,Body) ->
- [only_http_header(),
+ [%only_http_header(),
html_header(Title,JavaScript,Body)].
html_header(Title,JavaScript,Body) ->
@@ -1048,34 +1058,30 @@ href_proc_port([$#,$F,$u,$n,$<|T],Acc) ->
%% No links to funs
href_proc_port(T,[$;,$t,$l,$&,$n,$u,$F,$#|Acc]);
href_proc_port([$#,$P,$o,$r,$t,$<|T],Acc) ->
- {[$#|Port]=HashPort,Rest} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
- href_proc_port(Rest,[href("TARGET=\"main\"",
- ["./port?port=",Port],HashPort)|Acc]);
+ {Port,Rest} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
+ href_proc_port(Rest,[href(Port,Port)|Acc]);
href_proc_port([$<,$<|T],Acc) ->
%% No links to binaries
href_proc_port(T,[$;,$t,$l,$&,$;,$t,$l,$&|Acc]);
href_proc_port([$<,C|T],Acc) when $0 =< C, C =< $9 ->
%% Pid
{Pid,Rest} = to_gt(T,[C,$;,$t,$l,$&]),
- href_proc_port(Rest,[href("TARGET=\"main\"",
- ["./proc_details?pid=",Pid],Pid)|Acc]);
+ href_proc_port(Rest,[href(Pid,Pid)|Acc]);
href_proc_port([$",$#,$C,$D,$V,$B,$i,$n,$<|T],Acc) ->
%% Binary written by crashdump_viewer:parse_heap_term(...)
{SizeAndPos,[$"|Rest]} = split($>,T),
{Size,Pos} = split($,,SizeAndPos),
href_proc_port(Rest,[href("TARGET=\"expanded\"",
- ["./expand_binary?pos=",Pos],
+ ["#Binary<",Pos,">"],
["&lt;&lt; ",Size," bytes &gt;&gt;"]) | Acc]);
href_proc_port([$",$#,$C,$D,$V,$P,$o,$r,$t,$<|T],Acc) ->
%% Port written by crashdump_viewer:parse_term(...)
- {[$#|Port]=HashPort,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
- href_proc_port(Rest,[href("TARGET=\"main\"",
- ["./port?port=",Port],HashPort)|Acc]);
+ {Port,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
+ href_proc_port(Rest,[href(Port,Port)|Acc]);
href_proc_port([$",$#,$C,$D,$V,$P,$i,$d,$<|T],Acc) ->
%% Pid written by crashdump_viewer:parse_term(...)
{Pid,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&]),
- href_proc_port(Rest,[href("TARGET=\"main\"",
- ["./proc_details?pid=",Pid],Pid)|Acc]);
+ href_proc_port(Rest,[href(Pid,Pid)|Acc]);
href_proc_port([$',$#,$C,$D,$V,$I,$n,$c,$o,$m,$p,$l,$e,$t,$e,$H,$e,$a,$p,$'|T],
Acc)->
%% The heap is incomplete! Written by crashdump_viewer:deref_pts(...)
@@ -1437,4 +1443,5 @@ funs_table(Fu) ->
td("ALIGN=right",Index),
td(Address),
td(NativeAddress),
- td("ALIGN=right",Refc)]).
+ td("ALIGN=right",Refc)])
+.
diff --git a/lib/observer/src/crashdump_viewer_wx.erl b/lib/observer/src/crashdump_viewer_wx.erl
new file mode 100644
index 0000000000..aeb89b54f4
--- /dev/null
+++ b/lib/observer/src/crashdump_viewer_wx.erl
@@ -0,0 +1,481 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(crashdump_viewer_wx).
+
+-behaviour(wx_object).
+
+-export([start/1]).
+-export([get_attrib/1, set_status/1, create_txt_dialog/4]).
+
+-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
+ handle_call/3, handle_info/2, check_page_title/1]).
+
+%% Includes
+-include_lib("wx/include/wx.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-include("observer_defs.hrl").
+
+%% Defines
+
+-define(SERVER, cdv_wx).
+
+-define(ID_UG, 1).
+-define(ID_HOWTO, 2).
+-define(ID_NOTEBOOK, 3).
+
+-define(GEN_STR, "General").
+-define(PRO_STR, "Processes").
+-define(PORT_STR, "Ports").
+-define(ETS_STR, "ETS Tables").
+-define(TIMER_STR, "Timers").
+-define(FUN_STR, "Funs").
+-define(ATOM_STR, "Atoms").
+-define(DIST_STR, "Nodes").
+-define(MOD_STR, "Modules").
+-define(MEM_STR, "Memory").
+-define(INT_STR, "Internal Tables").
+
+%% Records
+-record(state,
+ {server,
+ file,
+ frame,
+ menubar,
+ menus = [],
+ status_bar,
+ notebook,
+ main_panel,
+ gen_panel,
+ pro_panel,
+ port_panel,
+ ets_panel,
+ timer_panel,
+ fun_panel,
+ atom_panel,
+ dist_panel,
+ mod_panel,
+ mem_panel,
+ int_panel,
+ active_tab
+ }).
+
+start(File) ->
+ case wx_object:start(?MODULE, File, []) of
+ Err = {error, _} -> Err;
+ _Obj -> ok
+ end.
+
+get_attrib(What) ->
+ wx_object:call(?SERVER, {get_attrib, What}).
+
+set_status(What) ->
+ wx_object:cast(?SERVER, {status_bar, What}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init(File) ->
+ {ok,CdvServer} = crashdump_viewer:start_link(),
+
+ register(?SERVER, self()),
+ wx:new(),
+ catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump viewer",
+ [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
+ IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
+ Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
+ wxFrame:setIcon(Frame, Icon),
+ wxIcon:destroy(Icon),
+
+ State = #state{server=CdvServer, file = File, frame = Frame},
+ UpdState = setup(State),
+ process_flag(trap_exit, true),
+ {Frame, UpdState}.
+
+setup(#state{file = File0, frame = Frame} = State) ->
+ %% Setup Menubar & Menus
+ MenuBar = wxMenuBar:new(),
+ DefMenus = default_menus(),
+ observer_lib:create_menus(DefMenus, MenuBar, default),
+ wxFrame:setMenuBar(Frame, MenuBar),
+
+ %% Setup panels
+ Panel = wxPanel:new(Frame, []),
+ Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
+
+ %% Setup "statusbar" to show warnings
+ StatusBar = observer_lib:create_status_bar(Panel),
+
+ %% Load a crashdump
+ File = load_dump(Panel,File0),
+
+ %% Set window title
+ T1 = "Crashdump Viewer: ",
+ Title =
+ if length(File) > 70 ->
+ T1 ++ filename:basename(File);
+ true ->
+ T1 ++ File
+ end,
+ wxFrame:setTitle(Frame, Title),
+
+ %% General information Panel
+ GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx),
+
+ %% Setup sizer create early to get it when window shows
+ MainSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
+ wxSizer:add(MainSizer, StatusBar, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 0},
+ {border,4}]),
+ wxPanel:setSizer(Panel, MainSizer),
+
+ wxNotebook:connect(Notebook, command_notebook_page_changing),
+ wxFrame:connect(Frame, close_window, [{skip, true}]),
+ wxMenu:connect(Frame, command_menu_selected),
+ wxFrame:show(Frame),
+
+ %% I postpone the creation of the other tabs so they can query/use
+ %% the window size
+
+ %% Process Panel
+ ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list, cdv_proc_wx),
+
+ %% Port Panel
+ PortPanel = add_page(Notebook, ?PORT_STR, cdv_virtual_list, cdv_port_wx),
+
+ %% Table Panel
+ EtsPanel = add_page(Notebook, ?ETS_STR, cdv_virtual_list, cdv_ets_wx),
+
+ %% Timer Panel
+ TimerPanel = add_page(Notebook, ?TIMER_STR, cdv_virtual_list, cdv_timer_wx),
+
+ %% Fun Panel
+ FunPanel = add_page(Notebook, ?FUN_STR, cdv_virtual_list, cdv_fun_wx),
+
+ %% Atom Panel
+ AtomPanel = add_page(Notebook, ?ATOM_STR, cdv_virtual_list, cdv_atom_wx),
+
+ %% Distribution Panel
+ DistPanel = add_page(Notebook, ?DIST_STR, cdv_virtual_list, cdv_dist_wx),
+
+ %% Loaded Modules Panel
+ ModPanel = add_page(Notebook, ?MOD_STR, cdv_virtual_list, cdv_mod_wx),
+
+ %% Memory Panel
+ MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_panel, cdv_mem_wx),
+
+ %% Memory Panel
+ IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_panel, cdv_int_tab_wx),
+
+ %% Force redraw (window needs it)
+ wxWindow:refresh(Panel),
+
+ GenPid = wx_object:get_pid(GenPanel),
+ GenPid ! active,
+ UpdState = State#state{file = File,
+ main_panel = Panel,
+ notebook = Notebook,
+ menubar = MenuBar,
+ status_bar = StatusBar,
+ gen_panel = GenPanel,
+ pro_panel = ProPanel,
+ port_panel = PortPanel,
+ ets_panel = EtsPanel,
+ timer_panel = TimerPanel,
+ fun_panel = FunPanel,
+ atom_panel = AtomPanel,
+ dist_panel = DistPanel,
+ mod_panel = ModPanel,
+ mem_panel = MemPanel,
+ int_panel = IntPanel,
+ active_tab = GenPid
+ },
+ %% Create resources which we don't want to duplicate
+ SysFont = wxSystemSettings:getFont(?wxSYS_SYSTEM_FIXED_FONT),
+ Fixed = case wxFont:isFixedWidth(SysFont) of
+ true -> SysFont;
+ false -> %% Sigh
+ SysFontSize = wxFont:getPointSize(SysFont),
+ wxFont:new(SysFontSize,
+ ?wxFONTFAMILY_MODERN,
+ ?wxFONTSTYLE_NORMAL,
+ ?wxFONTWEIGHT_NORMAL)
+ end,
+ put({font, fixed}, Fixed),
+ UpdState.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%Callbacks
+handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}},
+ #state{active_tab=Previous} = State) ->
+ case get_active_pid(State) of
+ Previous -> {noreply, State};
+ Pid ->
+ Previous ! not_active,
+ Pid ! active,
+ {noreply, State#state{active_tab=Pid}}
+ end;
+
+handle_event(#wx{event = #wxClose{}}, State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id = ?wxID_OPEN,
+ event = #wxCommand{type = command_menu_selected}},
+ State) ->
+ File = load_dump(State#state.main_panel,undefined),
+ Panels = [State#state.gen_panel,
+ State#state.pro_panel,
+ State#state.port_panel,
+ State#state.ets_panel,
+ State#state.timer_panel,
+ State#state.fun_panel,
+ State#state.atom_panel,
+ State#state.dist_panel,
+ State#state.mod_panel,
+ State#state.mem_panel,
+ State#state.int_panel],
+ _ = [wx_object:get_pid(Panel) ! new_dump || Panel<-Panels],
+ State#state.active_tab ! active,
+ {noreply, State#state{file=File}};
+
+handle_event(#wx{id = ?wxID_EXIT,
+ event = #wxCommand{type = command_menu_selected}},
+ State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id = HelpId,
+ event = #wxCommand{type = command_menu_selected}},
+ State) when HelpId==?wxID_HELP; HelpId==?ID_UG; HelpId==?ID_HOWTO ->
+ Help = get_help_doc(HelpId),
+ wx_misc:launchDefaultBrowser(Help) orelse
+ create_txt_dialog(State#state.frame,
+ "Could not launch browser: ~n " ++ Help,
+ "Error", ?wxICON_ERROR),
+ {noreply, State};
+
+handle_event(#wx{id = ?wxID_ABOUT,
+ event = #wxCommand{type = command_menu_selected}},
+ State = #state{frame=Frame}) ->
+ AboutString = "Display information from an erlang crash dump",
+ Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP},
+ {caption, "About"}],
+ wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)),
+ {noreply, State};
+
+handle_event(Event, State) ->
+ Pid = get_active_pid(State),
+ Pid ! Event,
+ {noreply, State}.
+
+handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) ->
+ wxTextCtrl:clear(SB),
+ wxTextCtrl:writeText(SB, Msg),
+ {noreply, State};
+
+handle_cast(_Cast, State) ->
+ {noreply, State}.
+
+handle_call({get_attrib, Attrib}, _From, State) ->
+ {reply, get(Attrib), State};
+
+handle_call(_Msg, _From, State) ->
+ {reply, ok, State}.
+
+handle_info({'EXIT', Pid, normal}, #state{server=Pid}=State) ->
+ {stop, normal, State};
+
+handle_info({'EXIT', Pid, _Reason}, State) ->
+ io:format("Child (~s) crashed exiting: ~p ~p~n",
+ [pid2panel(Pid, State), Pid,_Reason]),
+ {stop, normal, State};
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{frame = Frame}) ->
+ wxFrame:destroy(Frame),
+ crashdump_viewer:stop(),
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+add_page(Notebook,Title,Callback,Extra) ->
+ Panel = Callback:start_link(Notebook, Extra),
+ wxNotebook:addPage(Notebook, Panel, Title, []),
+ Panel.
+
+create_txt_dialog(Frame, Msg, Title, Style) ->
+ MD = wxMessageDialog:new(Frame, Msg, [{style, Style}]),
+ wxMessageDialog:setTitle(MD, Title),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD).
+
+check_page_title(Notebook) ->
+ Selection = wxNotebook:getSelection(Notebook),
+ wxNotebook:getPageText(Notebook, Selection).
+
+get_active_pid(#state{notebook=Notebook, gen_panel=Gen, pro_panel=Pro,
+ port_panel=Ports, ets_panel=Ets, timer_panel=Timers,
+ fun_panel=Funs, atom_panel=Atoms, dist_panel=Dist,
+ mod_panel=Mods, mem_panel=Mem, int_panel=Int
+ }) ->
+ Panel = case check_page_title(Notebook) of
+ ?GEN_STR -> Gen;
+ ?PRO_STR -> Pro;
+ ?PORT_STR -> Ports;
+ ?ETS_STR -> Ets;
+ ?TIMER_STR -> Timers;
+ ?FUN_STR -> Funs;
+ ?ATOM_STR -> Atoms;
+ ?DIST_STR -> Dist;
+ ?MOD_STR -> Mods;
+ ?MEM_STR -> Mem;
+ ?INT_STR -> Int
+ end,
+ wx_object:get_pid(Panel).
+
+pid2panel(Pid, #state{gen_panel=Gen, pro_panel=Pro, port_panel=Ports,
+ ets_panel=Ets, timer_panel=Timers, fun_panel=Funs,
+ atom_panel=Atoms, dist_panel=Dist, mod_panel=Mods,
+ mem_panel=Mem, int_panel=Int}) ->
+ case Pid of
+ Gen -> ?GEN_STR;
+ Pro -> ?PRO_STR;
+ Ports -> ?PORT_STR;
+ Ets -> ?ETS_STR;
+ Timers -> ?TIMER_STR;
+ Funs -> ?FUN_STR;
+ Atoms -> ?ATOM_STR;
+ Dist -> ?DIST_STR;
+ Mods -> ?MOD_STR;
+ Mem -> ?MEM_STR;
+ Int -> ?INT_STR;
+ _ -> "unknown"
+ end.
+
+default_menus() ->
+ Open = #create_menu{id = ?wxID_OPEN, text = "Open new crash dump"},
+ Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"},
+ About = #create_menu{id = ?wxID_ABOUT, text = "About"},
+ Help = #create_menu{id = ?wxID_HELP},
+ UG = #create_menu{id = ?ID_UG, text = "Crashdump viewer user's guide"},
+ Howto = #create_menu{id = ?ID_HOWTO, text = "How to interpret crash dump"},
+ case os:type() =:= {unix, darwin} of
+ false ->
+ FileMenu = {"File", [Open,Quit]},
+ HelpMenu = {"Help", [About,Help,UG,Howto]},
+ [FileMenu, HelpMenu];
+ true ->
+ %% On Mac quit and about will be moved to the "default' place
+ %% automagicly, so just add them to a menu that always exist.
+ %% But not to the help menu for some reason
+%! {Tag, Menus} = NodeMenu,
+%! [{Tag, Menus ++ [Quit,About]}, {"&Help", [Help]}]
+ [{"File", [Quit,About]}, {"&Help", [Help,UG,Howto]}] %?siri: does this work?
+ end.
+
+
+load_dump(Panel,undefined) ->
+ FD = wxFileDialog:new(Panel,
+ [{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]),
+ case wxFileDialog:showModal(FD) of
+ ?wxID_OK ->
+ Path = wxFileDialog:getPath(FD),
+ wxDialog:destroy(FD),
+ load_dump(Panel, Path);
+ _ ->
+ wxDialog:destroy(FD)
+ end;
+load_dump(Panel, FileName) ->
+ crashdump_viewer:read_file(FileName),
+ update_progress(Panel,false),
+ FileName.
+
+update_progress(Panel,PD) ->
+ case crashdump_viewer:get_progress() of
+ {ok, done} ->
+ wxProgressDialog:destroy(PD);
+ {ok, Percent} when is_integer(Percent) ->
+ wxProgressDialog:update(PD,Percent),
+ update_progress(Panel,PD);
+ {ok,Msg} ->
+ case PD of
+ false -> ok;
+ _ -> wxProgressDialog:destroy(PD)
+ end,
+ update_progress(Panel,new_progress(Msg));
+ {error, Reason} ->
+ wxProgressDialog:destroy(PD),
+ FailMsg = file:format_error(Reason),
+ MD = wxMessageDialog:new(Panel, FailMsg),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD)
+ end.
+
+new_progress(Msg) ->
+ wxProgressDialog:new("Crashdump viewer",Msg,
+ [{maximum,100},
+ {style,
+ ?wxPD_APP_MODAL bor
+ ?wxPD_SMOOTH bor
+ ?wxPD_AUTO_HIDE}]).
+%%%-----------------------------------------------------------------
+%%% Find help document (HTML files)
+get_help_doc(HelpId) ->
+ Internal = get_internal_help_doc(HelpId),
+ case filelib:is_file(Internal) of
+ true -> Internal;
+ false -> get_external_help_doc(HelpId)
+ end.
+
+get_internal_help_doc(?ID_HOWTO) ->
+ filename:join(erts_doc_dir(),help_file(?ID_HOWTO));
+get_internal_help_doc(HelpId) ->
+ filename:join(observer_doc_dir(),help_file(HelpId)).
+
+get_external_help_doc(?ID_HOWTO) ->
+ filename:join("http://www.erlang.org/doc/apps/erts",help_file(?ID_HOWTO));
+get_external_help_doc(HelpId) ->
+ filename:join("http://www.erlang.org/doc/apps/observer",help_file(HelpId)).
+
+observer_doc_dir() ->
+ filename:join([code:lib_dir(observer),"doc","html"]).
+
+erts_doc_dir() ->
+ ErtsVsn = erlang:system_info(version),
+ RootDir = code:root_dir(),
+ VsnErtsDir = filename:join(RootDir,"erts-"++ErtsVsn),
+ DocDir = filename:join(["doc","html"]),
+ case filelib:is_dir(VsnErtsDir) of
+ true ->
+ filename:join(VsnErtsDir,DocDir);
+ false ->
+ %% So this can be run in source tree
+ filename:join([RootDir,"erts",DocDir])
+ end.
+
+help_file(?wxID_HELP) -> "crashdump_help.html";
+help_file(?ID_UG) -> "crashdump_ug.html";
+help_file(?ID_HOWTO) -> "crash_dump.html".
diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl
index f7712cf3da..ce85fb4af6 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -19,17 +19,24 @@
-module(observer_lib).
-export([get_wx_parent/1,
- display_info_dialog/1, user_term/3, user_term_multiline/3,
+ display_info_dialog/1, display_yes_no_dialog/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,
create_menus/3, create_menu_item/3,
create_attrs/0,
- set_listctrl_col_size/2
+ set_listctrl_col_size/2,
+ create_status_bar/1,
+ html_window/2
]).
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
+-define(SINGLE_LINE_STYLE, ?wxBORDER_NONE bor ?wxTE_READONLY bor ?wxTE_RICH2).
+-define(MULTI_LINE_STYLE, ?SINGLE_LINE_STYLE bor ?wxTE_MULTILINE).
+
+
get_wx_parent(Window) ->
Parent = wxWindow:getParent(Window),
case wx:is_null(Parent) of
@@ -101,6 +108,12 @@ display_info_dialog(Str) ->
wxMessageDialog:destroy(Dlg),
ok.
+display_yes_no_dialog(Str) ->
+ Dlg = wxMessageDialog:new(wx:null(), Str, [{style,?wxYES_NO}]),
+ R = wxMessageDialog:showModal(Dlg),
+ wxMessageDialog:destroy(Dlg),
+ R.
+
%% display_info(Parent, [{Title, [{Label, Info}]}]) -> {Panel, Sizer, InfoFieldsToUpdate}
display_info(Frame, Info) ->
Panel = wxPanel:new(Frame),
@@ -108,24 +121,50 @@ display_info(Frame, Info) ->
Sizer = wxBoxSizer:new(?wxVERTICAL),
wxSizer:addSpacer(Sizer, 5),
Add = fun(BoxInfo) ->
- {Box, InfoFs} = create_box(Panel, BoxInfo),
- wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL},
- {border, 5}]),
- wxSizer:addSpacer(Sizer, 5),
- InfoFs
+ case create_box(Panel, BoxInfo) of
+ {Box, InfoFs} ->
+ 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}.
+fill_info([{dynamic, Key}|Rest], Data)
+ when is_atom(Key); is_function(Key) ->
+ %% Special case used by crashdump_viewer when the value decides
+ %% which header to use
+ case get_value(Key, Data) of
+ undefined -> fill_info(Rest, Data);
+ {Str,Value} -> [{Str, Value} | fill_info(Rest, Data)]
+ end;
fill_info([{Str, Key}|Rest], Data) when is_atom(Key); is_function(Key) ->
- [{Str, get_value(Key, Data)} | fill_info(Rest, Data)];
+ case get_value(Key, Data) of
+ undefined -> fill_info(Rest, Data);
+ Value -> [{Str, Value} | fill_info(Rest, Data)]
+ end;
+fill_info([{Str,Attrib,Key}|Rest], Data) when is_atom(Key); is_function(Key) ->
+ case get_value(Key, Data) of
+ undefined -> fill_info(Rest, Data);
+ Value -> [{Str,Attrib,Value} | fill_info(Rest, Data)]
+ end;
fill_info([{Str, {Format, Key}}|Rest], Data)
when is_atom(Key); is_function(Key), is_atom(Format) ->
case get_value(Key, Data) of
- undefined -> [{Str, undefined} | fill_info(Rest, Data)];
+ undefined -> fill_info(Rest, Data);
Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)]
end;
+fill_info([{Str, Attrib, {Format, Key}}|Rest], Data)
+ when is_atom(Key); is_function(Key), is_atom(Format) ->
+ case get_value(Key, Data) of
+ undefined -> fill_info(Rest, Data);
+ Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)]
+ end;
fill_info([{Str,SubStructure}|Rest], Data) when is_list(SubStructure) ->
[{Str, fill_info(SubStructure, Data)}|fill_info(Rest,Data)];
fill_info([{Str,Attrib,SubStructure}|Rest], Data) ->
@@ -147,13 +186,18 @@ update_info([], []) ->
ok.
update_info2([Field|Fs], [{_Str, Value}|Rest]) ->
- wxStaticText:setLabel(Field, to_str(Value)),
+ wxTextCtrl:setValue(Field, to_str(Value)),
update_info2(Fs, Rest);
update_info2([], []) -> ok.
to_str(Value) when is_atom(Value) ->
atom_to_list(Value);
+to_str({Unit, X}) when (Unit==bytes orelse Unit==time_ms) andalso is_list(X) ->
+ try list_to_integer(X) of
+ B -> to_str({Unit,B})
+ catch error:badarg -> X
+ end;
to_str({bytes, B}) ->
KB = B div 1024,
MB = KB div 1024,
@@ -289,26 +333,142 @@ get_box_info({Title, List}) when is_list(List) -> {Title, ?wxALIGN_LEFT, List};
get_box_info({Title, left, List}) -> {Title, ?wxALIGN_LEFT, List};
get_box_info({Title, right, List}) -> {Title, ?wxALIGN_RIGHT, List}.
+create_box(_Panel, {scroll_boxes,[]}) ->
+ undefined;
+create_box(Panel, {scroll_boxes,Data}) ->
+ OuterBox = wxBoxSizer:new(?wxHORIZONTAL),
+ Cursor = wxCursor:new(?wxCURSOR_HAND),
+ AddBox = fun({Title,Proportion,{Format,List}}) ->
+ Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel,
+ [{label, Title}]),
+ Scroll = wxScrolledWindow:new(Panel),
+ wxScrolledWindow:enableScrolling(Scroll,true,true),
+ wxScrolledWindow:setScrollbars(Scroll,1,1,0,0),
+ ScrollSizer = wxBoxSizer:new(?wxVERTICAL),
+ wxScrolledWindow:setSizer(Scroll, ScrollSizer),
+ BC = wxWindow:getBackgroundColour(Panel),
+ wxWindow:setBackgroundColour(Scroll,BC),
+ case Format of
+ click ->
+ [begin
+ TC = link_entry(Scroll, Link, Cursor),
+ wxWindow:setBackgroundColour(TC,BC),
+ wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}])
+ end || Link <- List];
+ plain ->
+ [begin
+ TC = wxTextCtrl:new(Scroll, ?wxID_ANY,
+ [{style,?SINGLE_LINE_STYLE},
+ {value,String}]),
+ wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}])
+ end || String <- List]
+ end,
+ wxSizer:add(Box,Scroll,[{flag,?wxEXPAND}]),
+ wxSizer:add(OuterBox,Box,
+ [{proportion,Proportion},{flag,?wxEXPAND}]),
+ {Scroll,length(List)}
+ end,
+ Boxes = [AddBox(Entry) || Entry <- Data],
+ wxCursor:destroy(Cursor),
+
+ MaxL = lists:foldl(fun({_,L},Max) when L>Max -> L;
+ (_,Max) -> Max
+ end,
+ 0,
+ Boxes),
+
+ Dummy = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?SINGLE_LINE_STYLE}]),
+ {_,H} = wxWindow:getSize(Dummy),
+ wxTextCtrl:destroy(Dummy),
+
+ MaxH = if MaxL > 8 -> 8*H;
+ true -> MaxL*H
+ end,
+ [wxWindow:setMinSize(B,{0,MaxH}) || {B,_} <- Boxes],
+ wxSizer:layout(OuterBox),
+ {OuterBox, []};
+
create_box(Panel, Data) ->
{Title, Align, Info} = get_box_info(Data),
- Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]),
- Left = wxBoxSizer:new(?wxVERTICAL),
- Right = wxBoxSizer:new(?wxVERTICAL),
- Expand = [{flag, ?wxEXPAND}],
- ExpAlign = [{flag, Align}],
- AddRow = fun({Desc, Value}) ->
- wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand),
- Field = wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)),
- wxSizer:add(Right, Field, ExpAlign),
+ Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, Title}]),
+ LeftSize = get_max_size(Panel,Info),
+ LeftProportion = [{proportion,0}],
+ RightProportion = [{proportion,1}, {flag, Align bor ?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),
+ Field =
+ case Value0 of
+ {click,"unknown"} ->
+ wxTextCtrl:new(Panel, ?wxID_ANY,
+ [{style,?MULTI_LINE_STYLE},
+ {value,"unknown"}]);
+ {click,Value} ->
+ link_entry(Panel,{Value,Value});
+ _ ->
+ Value = to_str(Value0),
+ wxTextCtrl:new(Panel, ?wxID_ANY,
+ [{style,?MULTI_LINE_STYLE},
+ {value,Value}])
+ end,
+ wxSizer:add(Line, 10, 0), % space of size 10 horisontally
+ wxSizer:add(Line, Field, RightProportion),
+
+ {_,H,_,_} = wxTextCtrl:getTextExtent(Field,"W"),
+ wxTextCtrl:setMinSize(Field,{0,H}),
+
+ wxSizer:add(Box, Line, [{proportion,0},{flag,?wxEXPAND}]),
Field
end,
InfoFields = [AddRow(Entry) || Entry <- Info],
- wxSizer:add(Box, Left),
- wxSizer:addSpacer(Box, 10),
- wxSizer:add(Box, Right),
- wxSizer:addSpacer(Box, 30),
{Box, InfoFields}.
+link_entry(Panel, Link) ->
+ Cursor = wxCursor:new(?wxCURSOR_HAND),
+ TC = link_entry(Panel, Link, Cursor),
+ wxCursor:destroy(Cursor),
+ TC.
+link_entry(Panel,{Target,Str},Cursor) ->
+ TC = wxTextCtrl:new(Panel, ?wxID_ANY,
+ [{style, ?SINGLE_LINE_STYLE}]),
+ wxTextCtrl:setForegroundColour(TC,?wxBLUE),
+ wxTextCtrl:appendText(TC, Str),
+ wxWindow:setCursor(TC, Cursor),
+ wxTextCtrl:connect(TC, left_down, [{userData,Target}]),
+ wxTextCtrl:connect(TC, enter_window),
+ wxTextCtrl:connect(TC, leave_window),
+ ToolTip = wxToolTip:new("Click to see properties for " ++ Target),
+ wxWindow:setToolTip(TC, ToolTip),
+ TC.
+
+html_window(Panel,Html) ->
+ Win = wxHtmlWindow:new(Panel, [{style, ?wxHW_SCROLLBAR_AUTO}]),
+ wxHtmlWindow:setPage(Win,Html),
+ wxHtmlWindow:connect(Win,command_html_link_clicked),
+ 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(_,[],X,Y) ->
+ {X+2,Y}.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_listctrl_col_size(LCtrl, Total) ->
wx:batch(fun() -> calc_last(LCtrl, Total) end).
@@ -430,3 +590,22 @@ ensure_last_is_dot(String) ->
false ->
String ++ "."
end.
+
+%%%-----------------------------------------------------------------
+%%% Status bar for warnings
+create_status_bar(Panel) ->
+ StatusStyle = ?wxTE_MULTILINE bor ?wxTE_READONLY bor ?wxTE_RICH2,
+ Red = wxTextAttr:new(?wxRED),
+
+ %% wxTextCtrl:setSize/3 does not work, so we must create a dummy
+ %% text ctrl first to get the size of the text, then set it when
+ %% creating the real text ctrl.
+ Dummy = wxTextCtrl:new(Panel, ?wxID_ANY,[{style,StatusStyle}]),
+ {X,Y,_,_} = wxTextCtrl:getTextExtent(Dummy,"WARNING"),
+ wxTextCtrl:destroy(Dummy),
+ StatusBar = wxTextCtrl:new(Panel, ?wxID_ANY,
+ [{style,StatusStyle},
+ {size,{X,Y+2}}]), % Y+2 to avoid scrollbar
+ wxTextCtrl:setDefaultStyle(StatusBar,Red),
+ wxTextAttr:destroy(Red),
+ StatusBar.
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index 47740581f0..3afe933e5a 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -36,6 +36,7 @@
-define(ID_PING, 1).
-define(ID_CONNECT, 2).
-define(ID_NOTEBOOK, 3).
+-define(ID_CDV, 4).
-define(FIRST_NODES_MENU_ID, 1000).
-define(LAST_NODES_MENU_ID, 2000).
@@ -130,7 +131,6 @@ setup(#state{frame = Frame} = State) ->
wxFrame:connect(Frame, close_window, [{skip, true}]),
wxMenu:connect(Frame, command_menu_selected),
wxFrame:show(Frame),
-
%% I postpone the creation of the other tabs so they can query/use
%% the window size
@@ -157,6 +157,8 @@ setup(#state{frame = Frame} = State) ->
%% Force redraw (window needs it)
wxWindow:refresh(Panel),
+ wxFrame:raise(Frame),
+ wxFrame:setFocus(Frame),
SysPid = wx_object:get_pid(SysPanel),
SysPid ! {active, node()},
@@ -206,6 +208,10 @@ handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}},
handle_event(#wx{event = #wxClose{}}, State) ->
{stop, normal, State};
+handle_event(#wx{id = ?ID_CDV, event = #wxCommand{type = command_menu_selected}}, State) ->
+ crashdump_viewer:start(),
+ {noreply, State};
+
handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) ->
{stop, normal, State};
@@ -517,9 +523,11 @@ create_connect_dialog(connect, #state{frame = Frame}) ->
end.
default_menus(NodesMenuItems) ->
+ CDV = #create_menu{id = ?ID_CDV, text = "Examine Crashdump"},
Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"},
About = #create_menu{id = ?wxID_ABOUT, text = "About"},
Help = #create_menu{id = ?wxID_HELP},
+ FileMenu = {"File", [CDV, Quit]},
NodeMenu = case erlang:is_alive() of
true -> {"Nodes", NodesMenuItems ++
[#create_menu{id = ?ID_PING, text = "Connect Node"}]};
@@ -528,15 +536,15 @@ default_menus(NodesMenuItems) ->
end,
case os:type() =:= {unix, darwin} of
false ->
- FileMenu = {"File", [Quit]},
+ FileMenu = {"File", [CDV, Quit]},
HelpMenu = {"Help", [About,Help]},
[FileMenu, NodeMenu, HelpMenu];
true ->
%% On Mac quit and about will be moved to the "default' place
%% automagicly, so just add them to a menu that always exist.
%% But not to the help menu for some reason
- {Tag, Menus} = NodeMenu,
- [{Tag, Menus ++ [Quit,About]}, {"&Help", [Help]}]
+ {Tag, Menus} = FileMenu,
+ [{Tag, Menus ++ [About]}, NodeMenu, {"&Help", [Help]}]
end.
clean_menus(Menus, MenuBar) ->