aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer')
-rw-r--r--lib/observer/doc/src/Makefile25
-rw-r--r--lib/observer/doc/src/fascicules.xml18
-rw-r--r--lib/observer/doc/src/note.gifbin1539 -> 0 bytes
-rw-r--r--lib/observer/doc/src/notes.xml176
-rw-r--r--lib/observer/doc/src/part_notes.xml39
-rw-r--r--lib/observer/doc/src/part_notes_history.xml39
-rw-r--r--lib/observer/include/etop.hrl2
-rwxr-xr-xlib/observer/priv/bin/cdv2
-rw-r--r--lib/observer/priv/bin/cdv.bat2
-rw-r--r--lib/observer/src/cdv_bin_cb.erl2
-rw-r--r--lib/observer/src/cdv_detail_wx.erl37
-rw-r--r--lib/observer/src/cdv_dist_cb.erl2
-rw-r--r--lib/observer/src/cdv_html_wx.erl15
-rw-r--r--lib/observer/src/cdv_info_wx.erl4
-rw-r--r--lib/observer/src/cdv_mem_cb.erl4
-rw-r--r--lib/observer/src/cdv_port_cb.erl17
-rw-r--r--lib/observer/src/cdv_proc_cb.erl4
-rw-r--r--lib/observer/src/cdv_sched_cb.erl18
-rw-r--r--lib/observer/src/cdv_term_cb.erl8
-rw-r--r--lib/observer/src/cdv_virtual_list_wx.erl2
-rw-r--r--lib/observer/src/cdv_wx.erl44
-rw-r--r--lib/observer/src/crashdump_viewer.erl1116
-rw-r--r--lib/observer/src/crashdump_viewer.hrl16
-rw-r--r--lib/observer/src/observer.app.src5
-rw-r--r--lib/observer/src/observer_alloc_wx.erl4
-rw-r--r--lib/observer/src/observer_html_lib.erl45
-rw-r--r--lib/observer/src/observer_lib.erl152
-rw-r--r--lib/observer/src/observer_perf_wx.erl4
-rw-r--r--lib/observer/src/observer_port_wx.erl4
-rw-r--r--lib/observer/src/observer_pro_wx.erl38
-rw-r--r--lib/observer/src/observer_procinfo.erl13
-rw-r--r--lib/observer/src/observer_trace_wx.erl2
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl2
-rw-r--r--lib/observer/src/observer_tv_wx.erl355
-rw-r--r--lib/observer/src/observer_wx.erl2
-rw-r--r--lib/observer/src/ttb.erl6
-rw-r--r--lib/observer/test/Makefile2
-rw-r--r--lib/observer/test/crashdump_helper.erl53
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl311
-rw-r--r--lib/observer/test/observer_SUITE.erl16
-rw-r--r--lib/observer/test/ttb_SUITE.erl32
-rw-r--r--lib/observer/vsn.mk2
42 files changed, 1852 insertions, 788 deletions
diff --git a/lib/observer/doc/src/Makefile b/lib/observer/doc/src/Makefile
index b38278a156..11bfee1bdb 100644
--- a/lib/observer/doc/src/Makefile
+++ b/lib/observer/doc/src/Makefile
@@ -9,11 +9,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+#
# 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$
#
include $(ERL_TOP)/make/target.mk
@@ -45,17 +45,15 @@ XML_REF3_FILES = \
XML_REF6_FILES = observer_app.xml
XML_PART_FILES = \
- part.xml \
- part_notes.xml \
- part_notes_history.xml
+ part.xml
XML_CHAPTER_FILES = \
+ introduction_ug.xml \
crashdump_ug.xml \
etop_ug.xml \
observer_ug.xml \
ttb_ug.xml \
- notes.xml \
- notes_history.xml
+ notes.xml
BOOK_FILES = book.xml
@@ -69,9 +67,7 @@ ONLY_HTML_FILE =
GIF_FILES = \
et_processes.gif \
- et_modsprocs.gif \
- note.gif
-
+ et_modsprocs.gif
# ----------------------------------------------------
HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
@@ -88,9 +84,9 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
# ----------------------------------------------------
-# FLAGS
+# FLAGS
# ----------------------------------------------------
-XML_FLAGS +=
+XML_FLAGS +=
# ----------------------------------------------------
# Targets
@@ -123,12 +119,12 @@ man: $(MAN1_FILES) $(MAN3_FILES) $(MAN6_FILES)
gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-debug opt:
+debug opt:
# ----------------------------------------------------
# Release Target
-# ----------------------------------------------------
+# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
@@ -148,4 +144,3 @@ release_docs_spec: docs
release_spec:
-
diff --git a/lib/observer/doc/src/fascicules.xml b/lib/observer/doc/src/fascicules.xml
deleted file mode 100644
index 37feca543f..0000000000
--- a/lib/observer/doc/src/fascicules.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
-
-<fascicules>
- <fascicule file="part" href="part_frame.html" entry="no">
- User's Guide
- </fascicule>
- <fascicule file="ref_man" href="ref_man_frame.html" entry="yes">
- Reference Manual
- </fascicule>
- <fascicule file="part_notes" href="part_notes_frame.html" entry="no">
- Release Notes
- </fascicule>
- <fascicule file="" href="../../../../doc/print.html" entry="no">
- Off-Print
- </fascicule>
-</fascicules>
-
diff --git a/lib/observer/doc/src/note.gif b/lib/observer/doc/src/note.gif
deleted file mode 100644
index 6fffe30419..0000000000
--- a/lib/observer/doc/src/note.gif
+++ /dev/null
Binary files differ
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index d329be5d5a..c0b8309af6 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,182 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ etop.hrl used a relative path to include
+ observer_backend.hrl, this is now changed to use
+ include_lib instead. runtime_tools/include is added to
+ the tertiary bootstrap.</p>
+ <p>
+ Own Id: OTP-14842 Aux Id: ERL-534 </p>
+ </item>
+ <item>
+ <p>
+ If a crashdump was truncated in the attributes section
+ for a module, crashdump_viewer would crash when a module
+ view was opened from the GUI. This bug was introduced in
+ OTP-20.2 and is now corrected.</p>
+ <p>
+ Own Id: OTP-14846 Aux Id: ERL-537 </p>
+ </item>
+ <item>
+ <p>
+ Optimized ets and mnesia table view tab in observer gui,
+ listing 10000 tables was previously very slow.</p>
+ <p>
+ Own Id: OTP-14856 Aux Id: ERIERL-117 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ When a process has many links and/or monitors, it could
+ earlier take very long time to display the process
+ information window. This is now improved by only showing
+ a few links and monitors, and then an link named
+ "more..." to expand the rest.</p>
+ <p>
+ Own Id: OTP-14725</p>
+ </item>
+ <item>
+ <p>
+ More crash dump info such as: process binary virtual heap
+ stats, full info for process causing out-of-mem during
+ GC, more port related info, and dirty scheduler info.</p>
+ <p>
+ Own Id: OTP-14820</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug introduced in OTP-20 would make Crashdump Viewer
+ crash when trying to expand an empty binary. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-14642</p>
+ </item>
+ <item>
+ <p>
+ If a match spec in the config file contained more than
+ one clause, observer would earlier crash when trying to
+ display it in the GUI. This is now corrected.</p>
+ <p>
+ Own Id: OTP-14643 Aux Id: ERL-489 </p>
+ </item>
+ <item>
+ <p>Writing of crash dumps is significantly faster.</p>
+ <p>Maps are now included in crash dumps.</p>
+ <p>Constants terms would only be shown in one process,
+ while other processes referencing the same constant term
+ would show a marker for incomplete heap. </p>
+ <p>
+ Own Id: OTP-14685 Aux Id: OTP-14611, OTP-14603, OTP-14595 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Binaries and some other data in crash dumps are now
+ encoded in base64 (instead of in hex), which will reduce
+ the size of crash dumps.</p>
+ <p>A few bugs in the handling of sub binaries in
+ <c>crashdump_viewer</c> have been fixed.</p>
+ <p>
+ Own Id: OTP-14686</p>
+ </item>
+ <item>
+ <p>
+ In order to allow future improvements, Crashdump Viewer
+ now checks the version tag of the crashdump to see that
+ it is a known format. If the crashdump version is newer
+ than Crashdump Viewer is prepared to read, then an
+ information dialog is displayed before Crashdump Viewer
+ terminates.</p>
+ <p>
+ If an incomplete process heap is discovered in a
+ crashdump, Crashdump Viewer will now display a warning
+ for this, similar to the warning displayed when a
+ crashdump is truncated. Incomplete heaps can occur if for
+ instance the literals are not included, which is the case
+ for all dumps prior to OTP-20.2.</p>
+ <p>
+ Own Id: OTP-14755</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The following improvements are done to Crashdump
+ Viewer:</p> <list> <item>Reading of crash dumps with many
+ binaries is optimized.</item> <item>A progress bar is
+ shown when the detail view for a process is
+ opened.</item> <item>The <c>cdv</c> script now sets
+ <c>ERL_CRASH_DUMP_SECONDS=0</c> to avoid generating a new
+ crash dump from the node running the Crashdump
+ Viewer.</item> <item>A warning dialog is shown if the
+ node running the Crashdump Viewer could potentially
+ overwrite the crash dump under inspection.</item>
+ <item>Bugfix: In some situations, Crashdump Viewer could
+ not find the end of the 'Last calls' section in a crash
+ dump, and would erroneously mark the crash dump as
+ truncated. This is now corrected.</item> <item>Bugfix: In
+ some situations, process info for a specific process
+ would be marked as truncated by Crashdump Viewer, even if
+ the crash dump was truncated in the binary section - and
+ not related to the process in question. This is now
+ corrected.</item> </list>
+ <p>
+ Own Id: OTP-14386</p>
+ </item>
+ <item>
+ <p>
+ General Unicode improvements.</p>
+ <p>
+ Own Id: OTP-14462</p>
+ </item>
+ <item>
+ <p>
+ Tools are updated to show Unicode atoms correctly.</p>
+ <p>
+ Own Id: OTP-14464</p>
+ </item>
+ <item>
+ <p>
+ Add system statistics and limits to frontpage in
+ observer.</p>
+ <p>
+ Own Id: OTP-14536</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/doc/src/part_notes.xml b/lib/observer/doc/src/part_notes.xml
deleted file mode 100644
index ba15c39cda..0000000000
--- a/lib/observer/doc/src/part_notes.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE part SYSTEM "part.dtd">
-
-<part xmlns:xi="http://www.w3.org/2001/XInclude">
- <header>
- <copyright>
- <year>2004</year><year>2016</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>Observer Release Notes</title>
- <prepared></prepared>
- <docno></docno>
- <date></date>
- <rev></rev>
- </header>
- <description>
- <p>The <em>OBSERVER</em> application contains tools for tracing
- and investigation of distributed systems.</p>
- <p>For information about older versions, see
- <url href="part_notes_history_frame.html">Release Notes History</url>.</p>
- </description>
- <xi:include href="notes.xml"/>
-</part>
-
diff --git a/lib/observer/doc/src/part_notes_history.xml b/lib/observer/doc/src/part_notes_history.xml
deleted file mode 100644
index e60210924c..0000000000
--- a/lib/observer/doc/src/part_notes_history.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE part SYSTEM "part.dtd">
-
-<part>
- <header>
- <copyright>
- <year>2006</year>
- <year>2016</year>
- <holder>Ericsson AB, All Rights Reserved</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
- </legalnotice>
-
- <title>Observer Release Notes History</title>
- <prepared></prepared>
- <docno></docno>
- <date></date>
- <rev></rev>
- </header>
- <description>
- <p>The <em>OBSERVER</em> application contains tools for tracing
- and investigation of distributed systems.</p>
- </description>
- <include file="notes_history"></include>
-</part>
-
diff --git a/lib/observer/include/etop.hrl b/lib/observer/include/etop.hrl
index 002937e522..f8d370450b 100644
--- a/lib/observer/include/etop.hrl
+++ b/lib/observer/include/etop.hrl
@@ -18,4 +18,4 @@
%% %CopyrightEnd%
%%
--include("../../runtime_tools/include/observer_backend.hrl").
+-include_lib("runtime_tools/include/observer_backend.hrl").
diff --git a/lib/observer/priv/bin/cdv b/lib/observer/priv/bin/cdv
index d14fd47e41..2a509c16af 100755
--- a/lib/observer/priv/bin/cdv
+++ b/lib/observer/priv/bin/cdv
@@ -1,4 +1,4 @@
#!/bin/sh
-erl -noinput -s crashdump_viewer script_start $@
+erl -env ERL_CRASH_DUMP_SECONDS 0 -noinput -s crashdump_viewer script_start $@
diff --git a/lib/observer/priv/bin/cdv.bat b/lib/observer/priv/bin/cdv.bat
index 18136a30d6..fa87c08adf 100644
--- a/lib/observer/priv/bin/cdv.bat
+++ b/lib/observer/priv/bin/cdv.bat
@@ -1,2 +1,2 @@
@ECHO OFF
-CALL werl -s crashdump_viewer script_start %*
+CALL werl -env ERL_CRASH_DUMP_SECONDS 0 -s crashdump_viewer script_start %*
diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl
index 5502869973..a4a542297c 100644
--- a/lib/observer/src/cdv_bin_cb.erl
+++ b/lib/observer/src/cdv_bin_cb.erl
@@ -71,6 +71,8 @@ hex_binary_fun(Bin) ->
plain_html(io_lib:format("~s",[S]))
end.
+format_hex(<<>>,_) ->
+ [];
format_hex(<<B1:4,B2:4>>,_) ->
[integer_to_list(B1,16),integer_to_list(B2,16)];
format_hex(<<B1:4,B2:4,Bin/binary>>,0) ->
diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl
index 4c26e447a6..6c4739042b 100644
--- a/lib/observer/src/cdv_detail_wx.erl
+++ b/lib/observer/src/cdv_detail_wx.erl
@@ -20,7 +20,7 @@
-behaviour(wx_object).
--export([start_link/4]).
+-export([start_link/5]).
-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
handle_call/3, handle_info/2]).
@@ -39,27 +39,51 @@
-define(ID_NOTEBOOK, 604).
%% Detail view
-start_link(Id, Data, ParentFrame, Callback) ->
- wx_object:start_link(?MODULE, [Id, Data, ParentFrame, Callback, self()], []).
+start_link(Id, Data, ParentFrame, Callback, App) ->
+ wx_object:start_link(?MODULE,[Id,Data,ParentFrame,Callback,App,self()],[]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-init([Id, Data, ParentFrame, Callback, Parent]) ->
+init([Id, Data, ParentFrame, Callback, App, Parent]) ->
+ display_progress(ParentFrame,App),
case Callback:get_details(Id, Data) of
{ok,Details} ->
- init(Id,ParentFrame,Callback,Parent,Details);
+ display_progress_pulse(Callback,Id),
+ init(Id,ParentFrame,Callback,App,Parent,Details);
{yes_no, Info, Fun} ->
+ destroy_progress(App),
case observer_lib:display_yes_no_dialog(Info) of
?wxID_YES -> Fun();
?wxID_NO -> ok
end,
{stop,normal};
{info,Info} ->
+ destroy_progress(App),
observer_lib:display_info_dialog(ParentFrame,Info),
{stop,normal}
end.
-init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
+%% Display progress bar only if the calling app is crashdump_viewer
+display_progress(ParentFrame,cdv) ->
+ observer_lib:display_progress_dialog(ParentFrame,
+ "Crashdump Viewer",
+ "Reading data");
+display_progress(_,_) ->
+ ok.
+
+%% Display pulse while creating process detail page with much data
+display_progress_pulse(cdv_proc_cb,Pid) ->
+ observer_lib:report_progress({ok,"Displaying data for "++Pid}),
+ observer_lib:report_progress({ok,start_pulse});
+display_progress_pulse(_,_) ->
+ ok.
+
+destroy_progress(cdv) ->
+ observer_lib:sync_destroy_progress_dialog();
+destroy_progress(_) ->
+ ok.
+
+init(Id,ParentFrame,Callback,App,Parent,{Title,Info,TW}) ->
Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [Title],
[{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
MenuBar = wxMenuBar:new(),
@@ -88,6 +112,7 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
wxFrame:connect(Frame, close_window),
wxMenu:connect(Frame, command_menu_selected),
wxFrame:show(Frame),
+ destroy_progress(App),
{Frame, #state{parent=Parent,
id=Id,
frame=Frame,
diff --git a/lib/observer/src/cdv_dist_cb.erl b/lib/observer/src/cdv_dist_cb.erl
index 2b4c9f56d1..aeb34e5baf 100644
--- a/lib/observer/src/cdv_dist_cb.erl
+++ b/lib/observer/src/cdv_dist_cb.erl
@@ -78,7 +78,7 @@ init_gen_page(Parent, Info) ->
cdv_info_wx:start_link(Parent,{Fields,Info,[]}).
format({creations,Creations}) ->
- string:join([integer_to_list(C) || C <- Creations],",");
+ lists:flatten(lists:join(",",[integer_to_list(C) || C <- Creations]));
format(D) ->
D.
diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl
index 5158e95a65..77a9d3207b 100644
--- a/lib/observer/src/cdv_html_wx.erl
+++ b/lib/observer/src/cdv_html_wx.erl
@@ -52,8 +52,12 @@ init([ParentWin, HtmlText]) ->
init(ParentWin, HtmlText, undefined, cdv).
init(ParentWin, HtmlText, Tab, App) ->
+ %% If progress dialog is shown, remove it now - and sett cursor busy instead
+ observer_lib:destroy_progress_dialog(),
+ wx_misc:beginBusyCursor(),
HtmlWin = observer_lib:html_window(ParentWin),
wxHtmlWindow:setPage(HtmlWin,HtmlText),
+ wx_misc:endBusyCursor(),
{HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}.
%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -90,21 +94,21 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,
case Target of
"#Binary?" ++ BinSpec ->
[{"offset",Off},{"size",Size},{"pos",Pos}] =
- httpd:parse_query(BinSpec),
+ uri_string:dissect_query(BinSpec),
Id = {cdv, {list_to_integer(Off),
list_to_integer(Size),
list_to_integer(Pos)}},
expand(Id,cdv_bin_cb,State);
"#OBSBinary?" ++ BinSpec ->
[{"key1",Preview},{"key2",Size},{"key3",Hash}] =
- httpd:parse_query(BinSpec),
+ uri_string:dissect_query(BinSpec),
Id = {obs, {Tab, {list_to_integer(Preview),
list_to_integer(Size),
list_to_integer(Hash)}}},
expand(Id,cdv_bin_cb,State);
"#Term?" ++ TermKeys ->
[{"key1",Key1},{"key2",Key2},{"key3",Key3}] =
- httpd:parse_query(TermKeys),
+ uri_string:dissect_query(TermKeys),
Id = {cdv, {Tab,{list_to_integer(Key1),
list_to_integer(Key2),
list_to_integer(Key3)}}},
@@ -123,11 +127,12 @@ handle_event(Event, State) ->
%%%-----------------------------------------------------------------
%%% Internal
-expand(Id,Callback,#state{expand_wins=Opened0}=State) ->
+expand(Id,Callback,#state{expand_wins=Opened0, app=App}=State) ->
Opened =
case lists:keyfind(Id,1,Opened0) of
false ->
- EW = cdv_detail_wx:start_link(Id,[],State#state.panel,Callback),
+ EW = cdv_detail_wx:start_link(Id,[],State#state.panel,
+ Callback,App),
wx_object:get_pid(EW) ! active,
[{Id,EW}|Opened0];
{_,EW} ->
diff --git a/lib/observer/src/cdv_info_wx.erl b/lib/observer/src/cdv_info_wx.erl
index 7e416dd11a..07c28610e2 100644
--- a/lib/observer/src/cdv_info_wx.erl
+++ b/lib/observer/src/cdv_info_wx.erl
@@ -95,6 +95,10 @@ handle_cast(Msg, State) ->
io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
+handle_event(#wx{obj=MoreEntry,event=#wxMouse{type=left_down},userData={more,More}}, State) ->
+ observer_lib:add_scroll_entries(MoreEntry,More),
+ {noreply, State};
+
handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) ->
cdv_virtual_list_wx:start_detail_win(Target),
{noreply, State};
diff --git a/lib/observer/src/cdv_mem_cb.erl b/lib/observer/src/cdv_mem_cb.erl
index abeddc7335..925487786c 100644
--- a/lib/observer/src/cdv_mem_cb.erl
+++ b/lib/observer/src/cdv_mem_cb.erl
@@ -49,9 +49,7 @@ gen_mem_info_fields([]) ->
[].
upper(Key) ->
- string:join([string:to_upper([H]) ++ T ||
- [H|T] <- string:tokens(Key,"_")]," ").
-
+ lists:join(" ", [string:titlecase(Word) || Word <- string:split(Key, "_", all)]).
%%%-----------------------------------------------------------------
%%% Allocated areas page
diff --git a/lib/observer/src/cdv_port_cb.erl b/lib/observer/src/cdv_port_cb.erl
index b5cbe8132d..6bb8f07a74 100644
--- a/lib/observer/src/cdv_port_cb.erl
+++ b/lib/observer/src/cdv_port_cb.erl
@@ -34,7 +34,8 @@
-define(COL_CONN, ?COL_ID+1).
-define(COL_NAME, ?COL_CONN+1).
-define(COL_CTRL, ?COL_NAME+1).
--define(COL_SLOT, ?COL_CTRL+1).
+-define(COL_QUEUE, ?COL_CTRL+1).
+-define(COL_SLOT, ?COL_QUEUE+1).
@@ -44,6 +45,7 @@ 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_QUEUE) -> #port.queue;
col_to_elem(?COL_SLOT) -> #port.slot.
col_spec() ->
@@ -51,6 +53,7 @@ col_spec() ->
{"Connected", ?wxLIST_FORMAT_LEFT, 120},
{"Name", ?wxLIST_FORMAT_LEFT, 150},
{"Controls", ?wxLIST_FORMAT_LEFT, 200},
+ {"Queue", ?wxLIST_FORMAT_RIGHT, 100},
{"Slot", ?wxLIST_FORMAT_RIGHT, 50}].
get_info(_) ->
@@ -96,9 +99,17 @@ format(D) ->
info_fields() ->
[{"Overview",
[{"Name", name},
+ {"State", state},
+ {"Task Flags", task_flags},
{"Connected", {click,connected}},
{"Slot", slot},
- {"Controls", controls}]},
+ {"Controls", controls},
+ {"Input bytes", input},
+ {"Output bytes", output},
+ {"Queue bytes", queue},
+ {"Port data", port_data}]},
{scroll_boxes,
[{"Links",1,{click,links}},
- {"Monitors",1,{click,monitors}}]}].
+ {"Monitors",1,{click,monitors}},
+ {"Suspended",1,{click,suspended}}
+ ]}].
diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl
index f10650bbb7..0ea23dd7cb 100644
--- a/lib/observer/src/cdv_proc_cb.erl
+++ b/lib/observer/src/cdv_proc_cb.erl
@@ -149,6 +149,10 @@ info_fields() ->
{"Old Heap", old_heap},
{"Heap Unused", heap_unused},
{"Old Heap Unused", old_heap_unused},
+ {"Binary vheap", bin_vheap},
+ {"Old Binary vheap", old_bin_vheap},
+ {"Binary vheap unused", bin_vheap_unused},
+ {"Old Binary vheap unused", old_bin_vheap_unused},
{"Number of Heap Fragements", num_heap_frag},
{"Heap Fragment Data",heap_frag_data},
{"New Heap Start", new_heap_start},
diff --git a/lib/observer/src/cdv_sched_cb.erl b/lib/observer/src/cdv_sched_cb.erl
index 192aaf31a7..d2696a276f 100644
--- a/lib/observer/src/cdv_sched_cb.erl
+++ b/lib/observer/src/cdv_sched_cb.erl
@@ -31,7 +31,8 @@
%% Columns
-define(COL_ID, 0).
--define(COL_PROC, ?COL_ID+1).
+-define(COL_TYPE, ?COL_ID+1).
+-define(COL_PROC, ?COL_TYPE+1).
-define(COL_PORT, ?COL_PROC+1).
-define(COL_RQL, ?COL_PORT+1).
-define(COL_PQL, ?COL_RQL+1).
@@ -39,6 +40,7 @@
%% Callbacks for cdv_virtual_list_wx
col_to_elem(id) -> col_to_elem(?COL_ID);
col_to_elem(?COL_ID) -> #sched.name;
+col_to_elem(?COL_TYPE) -> #sched.type;
col_to_elem(?COL_PROC) -> #sched.process;
col_to_elem(?COL_PORT) -> #sched.port;
col_to_elem(?COL_RQL) -> #sched.run_q;
@@ -46,6 +48,7 @@ col_to_elem(?COL_PQL) -> #sched.port_q.
col_spec() ->
[{"Id", ?wxLIST_FORMAT_RIGHT, 50},
+ {"Type", ?wxLIST_FORMAT_CENTER, 100},
{"Current Process", ?wxLIST_FORMAT_CENTER, 130},
{"Current Port", ?wxLIST_FORMAT_CENTER, 130},
{"Run Queue Length", ?wxLIST_FORMAT_RIGHT, 180},
@@ -73,7 +76,8 @@ detail_pages() ->
[{"Scheduler Information", fun init_gen_page/2}].
init_gen_page(Parent, Info0) ->
- Fields = info_fields(),
+ Type = proplists:get_value(type, Info0),
+ Fields = info_fields(Type),
Details = proplists:get_value(details, Info0),
Info = if is_map(Details) -> Info0 ++ maps:to_list(Details);
true -> Info0
@@ -81,15 +85,16 @@ init_gen_page(Parent, Info0) ->
cdv_info_wx:start_link(Parent,{Fields,Info,[]}).
%%% Internal
-info_fields() ->
+info_fields(Type) ->
[{"Scheduler Overview",
[{"Id", id},
+ {"Type", type},
{"Current Process",process},
{"Current Port", port},
{"Sleep Info Flags", sleep_info},
{"Sleep Aux Work", sleep_aux}
]},
- {"Run Queues",
+ {run_queues_header(Type),
[{"Flags", runq_flags},
{"Priority Max Length", runq_max},
{"Priority High Length", runq_high},
@@ -116,3 +121,8 @@ info_fields() ->
{" ", {currp_stack, 11}}
]}
].
+
+run_queues_header(normal) ->
+ "Run Queues";
+run_queues_header(DirtyX) ->
+ "Run Queues (common for all '" ++ atom_to_list(DirtyX) ++ "' schedulers)".
diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl
index f206d7e4c9..bdcb13f22d 100644
--- a/lib/observer/src/cdv_term_cb.erl
+++ b/lib/observer/src/cdv_term_cb.erl
@@ -30,23 +30,31 @@ detail_pages() ->
[{"Term", fun init_term_page/2}].
init_term_page(ParentWin, {Type, [Term, Tab]}) ->
+ observer_lib:report_progress({ok,"Expanding term"}),
+ observer_lib:report_progress({ok,start_pulse}),
Expanded = expand(Term, true),
BinSaved = expand(Term, Tab),
+ observer_lib:report_progress({ok,stop_pulse}),
cdv_multi_wx:start_link(
ParentWin,
[{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}},
{"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}},
{"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}},
+ {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab)}},
{"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}},
{"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]).
format_term_fun(Format,Term,Tab) ->
fun() ->
+ observer_lib:report_progress({ok,"Formatting term"}),
+ observer_lib:report_progress({ok,start_pulse}),
try io_lib:format(Format,[Term]) of
Str -> {expand, plain_html(Str), Tab}
catch error:badarg ->
Warning = "This term can not be formatted with " ++ Format,
observer_html_lib:warning(Warning)
+ after
+ observer_lib:report_progress({ok,stop_pulse})
end
end.
diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl
index f3daae8e4d..33c0c880b1 100644
--- a/lib/observer/src/cdv_virtual_list_wx.erl
+++ b/lib/observer/src/cdv_virtual_list_wx.erl
@@ -174,7 +174,7 @@ do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened,
case lists:keyfind(Id, 1, Opened) of
false ->
Data = call(Holder, {get_data, self(), Id}),
- case cdv_detail_wx:start_link(Id, Data, Panel, Callback) of
+ case cdv_detail_wx:start_link(Id, Data, Panel, Callback, cdv) of
{error, _} -> Opened;
IW -> [{Id, IW} | Opened]
end;
diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl
index 898f39ded2..0e33c9e618 100644
--- a/lib/observer/src/cdv_wx.erl
+++ b/lib/observer/src/cdv_wx.erl
@@ -130,8 +130,9 @@ init(File0) ->
{ok,File} ->
%% Set window title
T1 = "Crashdump Viewer: ",
+ FileLength = string:length(File),
Title =
- if length(File) > 70 ->
+ if FileLength > 70 ->
T1 ++ filename:basename(File);
true ->
T1 ++ File
@@ -412,15 +413,25 @@ load_dump(Frame,undefined) ->
error
end;
load_dump(Frame,FileName) ->
- ok = observer_lib:display_progress_dialog("Crashdump Viewer",
+ case maybe_warn_filename(FileName) of
+ continue ->
+ do_load_dump(Frame,FileName);
+ stop ->
+ error
+ end.
+
+do_load_dump(Frame,FileName) ->
+ ok = observer_lib:display_progress_dialog(wx:null(),
+ "Crashdump Viewer",
"Loading crashdump"),
crashdump_viewer:read_file(FileName),
case observer_lib:wait_for_progress() of
ok ->
%% Set window title
T1 = "Crashdump Viewer: ",
+ FileLength = string:length(FileName),
Title =
- if length(FileName) > 70 ->
+ if FileLength > 70 ->
T1 ++ filename:basename(FileName);
true ->
T1 ++ FileName
@@ -431,6 +442,33 @@ load_dump(Frame,FileName) ->
error
end.
+maybe_warn_filename(FileName) ->
+ case os:getenv("ERL_CRASH_DUMP_SECONDS")=="0" orelse
+ os:getenv("ERL_CRASH_DUMP_BYTES")=="0" of
+ true ->
+ continue;
+ false ->
+ DumpName = case os:getenv("ERL_CRASH_DUMP") of
+ false -> filename:absname("erl_crash.dump");
+ Name -> filename:absname(Name)
+ end,
+ case filename:absname(FileName) of
+ DumpName ->
+ Warning =
+ "WARNING: the current crashdump might be overwritten "
+ "if the crashdump_viewer node crashes.\n\n"
+ "Renaming the file before inspecting it will "
+ "remove the problem.\n\n"
+ "Do you want to continue?",
+ case observer_lib:display_yes_no_dialog(Warning) of
+ ?wxID_YES -> continue;
+ ?wxID_NO -> stop
+ end;
+ _ ->
+ continue
+ end
+ end.
+
%%%-----------------------------------------------------------------
%%% Find help document (HTML files)
get_help_doc(HelpId) ->
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index fd8b3b9e67..d0c14db486 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -26,17 +26,31 @@
%% Tables
%% ------
%% 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.
+%% crashdump, except the 'binary' tag. 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.
+%%
+%% cdv_binary_index_table: This table holds all 'binary' tags. The hex
+%% address for each binary is converted to its integer value before
+%% storing Address -> Start Position in this table. The hex value of
+%% the address is never used for lookup.
+%%
+%% cdv_reg_proc_table: This table holds mappings between pid and
+%% registered name. This is used for timers and monitors.
+%%
+%% cdv_heap_file_chars: For each 'proc_heap' and 'literals' tag, this
+%% table contains the number of characters to read from the crash dump
+%% file. This is used for giving an indication in percent of the
+%% progress when parsing this data.
+%%
%%
%% Process state
%% -------------
%% file: The name of the crashdump currently viewed.
%% dump_vsn: The version number of the crashdump
%% 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
@@ -74,6 +88,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
+%% Test support
+-export([get_dump_versions/0]).
+
%% Debug support
-export([debug/1,stop_debug/0]).
@@ -87,7 +104,11 @@
-define(max_line_size,100). % max number of bytes (i.e. characters) the
% line_head/1 function can return
-define(not_available,"N/A").
+-define(binary_size_progress_limit,10000).
+-define(max_dump_version,[0,5]).
+%% The value of the next define must be divisible by 4.
+-define(base64_chunk_size, (4*256)).
%% All possible tags - use macros in order to avoid misspelling in the code
-define(abort,abort).
@@ -95,6 +116,10 @@
-define(allocator,allocator).
-define(atoms,atoms).
-define(binary,binary).
+-define(dirty_cpu_scheduler,dirty_cpu_scheduler).
+-define(dirty_cpu_run_queue,dirty_cpu_run_queue).
+-define(dirty_io_scheduler,dirty_io_scheduler).
+-define(dirty_io_run_queue,dirty_io_run_queue).
-define(ende,ende).
-define(erl_crash_dump,erl_crash_dump).
-define(ets,ets).
@@ -104,8 +129,11 @@
-define(index_table,index_table).
-define(instr_data,instr_data).
-define(internal_ets,internal_ets).
+-define(literals,literals).
-define(loaded_modules,loaded_modules).
-define(memory,memory).
+-define(memory_map,memory_map).
+-define(memory_status,memory_status).
-define(mod,mod).
-define(no_distribution,no_distribution).
-define(node,node).
@@ -122,7 +150,8 @@
-define(visible_node,visible_node).
--record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown",binaries}).
+-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown"}).
+-record(dec_opts, {bin_addr_adj=0,base64=true}).
%%%-----------------------------------------------------------------
%%% Debugging
@@ -203,7 +232,8 @@ do_script_start(StartFun) ->
io:format("\ncdv crash: ~tp\n",[Reason])
end;
_ ->
- io:format("\ncdv crash: ~p\n",[unknown_reason])
+ %io:format("\ncdv crash: ~p\n",[unknown_reason])
+ ok
end;
Error ->
io:format("\ncdv start failed: ~tp\n",[Error])
@@ -290,6 +320,11 @@ port(Id) ->
expand_binary(Pos) ->
call({expand_binary,Pos}).
+%%%-----------------------------------------------------------------
+%%% For testing only - called from crashdump_viewer_SUITE
+get_dump_versions() ->
+ call(get_dump_versions).
+
%%====================================================================
%% Server functions
%%====================================================================
@@ -305,6 +340,8 @@ expand_binary(Pos) ->
init([]) ->
ets:new(cdv_dump_index_table,[ordered_set,named_table,public]),
ets:new(cdv_reg_proc_table,[ordered_set,named_table,public]),
+ ets:new(cdv_binary_index_table,[ordered_set,named_table,public]),
+ ets:new(cdv_heap_file_chars,[ordered_set,named_table,public]),
{ok, #state{}}.
%%--------------------------------------------------------------------
@@ -337,10 +374,12 @@ handle_call(general_info,_From,State=#state{file=File}) ->
ets:insert(cdv_reg_proc_table,
{cdv_dump_node_name,GenInfo#general_info.node_name}),
{reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}};
-handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) ->
+handle_call({expand_binary,{Offset,Size,Pos}},_From,
+ #state{file=File,dump_vsn=DumpVsn}=State) ->
Fd = open(File),
pos_bof(Fd,Pos),
- {Bin,_Line} = get_binary(Offset,Size,bytes(Fd)),
+ DecodeOpts = get_decode_opts(DumpVsn),
+ {Bin,_Line} = get_binary(Offset,Size,bytes(Fd),DecodeOpts),
close(Fd),
{reply,{ok,Bin},State};
handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
@@ -348,9 +387,9 @@ handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
Procs = procs_summary(File,WS),
{reply,{ok,Procs,TW},State};
handle_call({proc_details,Pid},_From,
- State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn,binaries=B})->
+ State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn})->
Reply =
- case get_proc_details(File,Pid,WS,DumpVsn,B) of
+ case get_proc_details(File,Pid,WS,DumpVsn) of
{ok,Proc,TW} ->
{ok,Proc,TW};
Other ->
@@ -413,9 +452,11 @@ handle_call(loaded_mods,_From,State=#state{file=File}) ->
TW = truncated_warning([?mod]),
{_CC,_OC,Mods} = loaded_mods(File),
{reply,{ok,Mods,TW},State};
-handle_call({loaded_mod_details,Mod},_From,State=#state{file=File}) ->
+handle_call({loaded_mod_details,Mod},_From,
+ #state{dump_vsn=DumpVsn,file=File}=State) ->
TW = truncated_warning([{?mod,Mod}]),
- ModInfo = get_loaded_mod_details(File,Mod),
+ DecodeOpts = get_decode_opts(DumpVsn),
+ ModInfo = get_loaded_mod_details(File,Mod,DecodeOpts),
{reply,{ok,ModInfo,TW},State};
handle_call(funs,_From,State=#state{file=File}) ->
TW = truncated_warning([?fu]),
@@ -449,8 +490,9 @@ handle_call(index_tables,_From,State=#state{file=File}) ->
handle_call(schedulers,_From,State=#state{file=File}) ->
Schedulers=schedulers(File),
TW = truncated_warning([?scheduler]),
- {reply,{ok,Schedulers,TW},State}.
-
+ {reply,{ok,Schedulers,TW},State};
+handle_call(get_dump_versions,_From,State=#state{dump_vsn=DumpVsn}) ->
+ {reply,{ok,{?max_dump_version,DumpVsn}},State}.
%%--------------------------------------------------------------------
@@ -462,9 +504,9 @@ handle_call(schedulers,_From,State=#state{file=File}) ->
%%--------------------------------------------------------------------
handle_cast({read_file,File}, _State) ->
case do_read_file(File) of
- {ok,Binaries,DumpVsn} ->
+ {ok,DumpVsn} ->
observer_lib:report_progress({ok,done}),
- {noreply, #state{file=File,binaries=Binaries,dump_vsn=DumpVsn}};
+ {noreply, #state{file=File,dump_vsn=DumpVsn}};
Error ->
end_progress(Error),
{noreply, #state{}}
@@ -746,32 +788,6 @@ get_rest_of_line_1(Fd, <<>>, Acc) ->
eof -> {eof,lists:reverse(Acc)}
end.
-get_lines_to_empty(Fd) ->
- case get_chunk(Fd) of
- {ok,Bin} ->
- get_lines_to_empty(Fd,Bin,[],[]);
- eof ->
- []
- end.
-get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,[],Lines) ->
- put_chunk(Fd,Bin),
- lists:reverse(Lines);
-get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) ->
- get_lines_to_empty(Fd,Bin,[],[byte_list_to_string(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} ->
- get_lines_to_empty(Fd,Bin,Acc,Lines);
- eof ->
- lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))])
- end.
-
split(Str) ->
split($ ,Str,[]).
split(Char,Str) ->
@@ -801,11 +817,12 @@ parse_vsn_str(Str,WS) ->
%%%-----------------------------------------------------------------
-%%% Traverse crash dump and insert index in table for each heading
-%%%
-%%% Progress is reported during the time and MUST be checked with
-%%% crashdump_viewer:get_progress/0 until it returns {ok,done}.
+%%% Traverse crash dump and insert index in table for each heading.
+%%% Progress is reported during the time.
do_read_file(File) ->
+ erase(?literals), %Clear literal cache.
+ put(truncated,false), %Not truncated (yet).
+ erase(truncated_reason), %Not truncated (yet).
case file:read_file_info(File) of
{ok,#file_info{type=regular,
access=FileA,
@@ -817,18 +834,21 @@ do_read_file(File) ->
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1),
case Tag of
?erl_crash_dump ->
- reset_index_table(),
- insert_index(Tag,Id,N1+1),
- put_last_tag(Tag,""),
- indexify(Fd,Rest,N1),
- end_progress(),
- check_if_truncated(),
- [{DumpVsn0,_}] = lookup_index(?erl_crash_dump),
- DumpVsn = [list_to_integer(L) ||
- L<-string:tokens(DumpVsn0,".")],
- Binaries = read_binaries(Fd,DumpVsn),
- close(Fd),
- {ok,Binaries,DumpVsn};
+ case check_dump_version(Id) of
+ {ok,DumpVsn} ->
+ reset_tables(),
+ insert_index(Tag,Id,N1+1),
+ put_last_tag(Tag,""),
+ DecodeOpts = get_decode_opts(DumpVsn),
+ indexify(Fd,DecodeOpts,Rest,N1),
+ end_progress(),
+ check_if_truncated(),
+ close(Fd),
+ {ok,DumpVsn};
+ Error ->
+ close(Fd),
+ Error
+ end;
_Other ->
R = io_lib:format(
"~ts is not an Erlang crash dump~n",
@@ -856,15 +876,56 @@ do_read_file(File) ->
{error,R}
end.
-indexify(Fd,Bin,N) ->
+check_dump_version(Vsn) ->
+ DumpVsn = [list_to_integer(L) || L<-string:lexemes(Vsn,".")],
+ if DumpVsn > ?max_dump_version ->
+ Info =
+ "This Crashdump Viewer is too old for the given "
+ "Erlang crash dump. Please use a newer version of "
+ "Crashdump Viewer.",
+ {error,Info};
+ true ->
+ {ok,DumpVsn}
+ end.
+
+indexify(Fd,DecodeOpts,Bin,N) ->
case binary:match(Bin,<<"\n=">>) of
{Start,Len} ->
Pos = Start+Len,
<<_:Pos/binary,TagAndRest/binary>> = Bin,
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos),
- insert_index(Tag,Id,N1+1), % +1 to get past newline
- put_last_tag(Tag,Id),
- indexify(Fd,Rest,N1);
+ NewPos = N1+1, % +1 to get past newline
+ case Tag of
+ ?binary ->
+ %% Binaries are stored in a separate table in
+ %% order to minimize lookup time. Key is the
+ %% translated address.
+ {HexAddr,_} = get_hex(Id),
+ Addr = HexAddr bor DecodeOpts#dec_opts.bin_addr_adj,
+ insert_binary_index(Addr,NewPos);
+ _ ->
+ insert_index(Tag,Id,NewPos)
+ end,
+ case put_last_tag(Tag,Id) of
+ {?proc_heap,LastId} ->
+ [{_,LastPos}] = lookup_index(?proc_heap,LastId),
+ ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos});
+ {?literals,[]} ->
+ case get(truncated_reason) of
+ undefined ->
+ [{_,LastPos}] = lookup_index(?literals,[]),
+ ets:insert(cdv_heap_file_chars,
+ {literals,N+Start+1-LastPos});
+ _ ->
+ %% Literals are truncated. Make sure we never
+ %% attempt to read in the literals. (Heaps that
+ %% references literals will show markers for
+ %% incomplete heaps, but will otherwise work.)
+ delete_index(?literals, [])
+ end;
+ _ -> ok
+ end,
+ indexify(Fd,DecodeOpts,Rest,N1);
nomatch ->
case progress_read(Fd) of
{ok,Chunk0} when is_binary(Chunk0) ->
@@ -875,7 +936,7 @@ indexify(Fd,Bin,N) ->
_ ->
{Chunk0,N+byte_size(Bin)}
end,
- indexify(Fd,Chunk,N1);
+ indexify(Fd,DecodeOpts,Chunk,N1);
eof ->
eof
end
@@ -911,7 +972,12 @@ check_if_truncated() ->
find_truncated_proc(TruncatedTag)
end.
-find_truncated_proc({?atoms,_Id}) ->
+find_truncated_proc({Tag,_Id}) when Tag==?atoms;
+ Tag==?binary;
+ Tag==?instr_data;
+ Tag==?literals;
+ Tag==?memory_status;
+ Tag==?memory_map ->
put(truncated_proc,false);
find_truncated_proc({Tag,Pid}) ->
case is_proc_tag(Tag) of
@@ -1060,14 +1126,14 @@ procs_summary(File,WS) ->
%%-----------------------------------------------------------------
%% Page with one process
-get_proc_details(File,Pid,WS,DumpVsn,Binaries) ->
+get_proc_details(File,Pid,WS,DumpVsn) ->
case lookup_index(?proc,Pid) of
[{_,Start}] ->
Fd = open(File),
{{Stack,MsgQ,Dict},TW} =
case truncated_warning([{?proc,Pid}]) of
[] ->
- {expand_memory(Fd,Pid,DumpVsn,Binaries),[]};
+ expand_memory(Fd,Pid,DumpVsn);
TW0 ->
{{[],[],[]},TW0}
end,
@@ -1160,6 +1226,18 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
"OldHeap unused" ->
Bytes = list_to_integer(bytes(Fd))*WS,
get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS);
+ "BinVHeap" ->
+ Bytes = list_to_integer(bytes(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{bin_vheap=Bytes},WS);
+ "OldBinVHeap" ->
+ Bytes = list_to_integer(bytes(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{old_bin_vheap=Bytes},WS);
+ "BinVHeap unused" ->
+ Bytes = list_to_integer(bytes(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{bin_vheap_unused=Bytes},WS);
+ "OldBinVHeap unused" ->
+ Bytes = list_to_integer(bytes(Fd))*WS,
+ get_procinfo(Fd,Fun,Proc#proc{old_bin_vheap_unused=Bytes},WS);
"New heap start" ->
get_procinfo(Fd,Fun,Proc#proc{new_heap_start=bytes(Fd)},WS);
"New heap top" ->
@@ -1176,9 +1254,9 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
get_procinfo(Fd,Fun,Proc#proc{old_heap_end=bytes(Fd)},WS);
%% - END - moved from get_procinfo -
"Last calls" ->
- get_procinfo(Fd,Fun,Proc#proc{last_calls=get_lines_to_empty(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{last_calls=get_last_calls(Fd)},WS);
"Link list" ->
- {Links,Monitors,MonitoredBy} = parse_link_list(bytes(Fd),[],[],[]),
+ {Links,Monitors,MonitoredBy} = get_link_list(Fd),
get_procinfo(Fd,Fun,Proc#proc{links=Links,
monitors=Monitors,
mon_by=MonitoredBy},WS);
@@ -1200,86 +1278,124 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
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_link_list(Unexpected,Links,Monitors,MonitoredBy) ->
- io:format("WARNING: found unexpected data in link list:~n~ts~n",[Unexpected]),
- parse_link_list([],Links,Monitors,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) ->
- %% Named process
- {Name,Node,Rest1} = parse_name_node(Str,[]),
- Pid = get_pid_from_name(Name,Node),
- case parse_link(string:strip(Rest1,left,$,),[]) of
- {Ref,"}"++Rest2} ->
- %% Bug in break.c - prints an extra "}" for remote
- %% nodes... thus the strip
- {{Pid,"{"++Name++","++Node++"} ("++Ref++")"},
- string:strip(Rest2,left,$})};
- {Ref,[]} ->
- {{Pid,"{"++Name++","++Node++"} ("++Ref++")"},[]}
+%% The end of the 'Last calls' section is meant to be an empty line,
+%% but in some cases this is not the case, so we also need to look for
+%% the next heading which currently (OTP-20.1) can be "Link list: ",
+%% "Dictionary: " or "Reductions: ". We do this by looking for ": "
+%% and when found, pushing the heading back into the saved chunk.
+%%
+%% Note that the 'Last calls' section is only present if the
+%% 'save_calls' process flag is set.
+get_last_calls(Fd) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ get_last_calls(Fd,Bin,[],[]);
+ eof ->
+ []
+ end.
+get_last_calls(Fd,<<$\n:8,Bin/binary>>,[],Lines) ->
+ %% Empty line - we're done
+ put_chunk(Fd,Bin),
+ lists:reverse(Lines);
+get_last_calls(Fd,<<$::8>>,Acc,Lines) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ %% Could be a colon followed by a space - see next function clause
+ get_last_calls(Fd,<<$::8,Bin/binary>>,Acc,Lines);
+ eof ->
+ %% Truncated here - either we've got the next heading, or
+ %% it was truncated in a last call function, in which case
+ %% we note that it was truncated
+ case byte_list_to_string(lists:reverse(Acc)) of
+ NextHeading when NextHeading=="Link list";
+ NextHeading=="Dictionary";
+ NextHeading=="Reductions" ->
+ put_chunk(Fd,list_to_binary(NextHeading++":")),
+ lists:reverse(Lines);
+ LastCallFunction->
+ lists:reverse(Lines,[LastCallFunction++":...(truncated)"])
+ end
end;
-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)"},[]}
+get_last_calls(Fd,<<$\::8,$\s:8,Bin/binary>>,Acc,Lines) ->
+ %% ": " - means we have the next heading in Acc - save it back
+ %% into the chunk and return the lines we've found
+ HeadingBin = list_to_binary(lists:reverse(Acc,[$:])),
+ put_chunk(Fd,<<HeadingBin/binary,Bin/binary>>),
+ lists:reverse(Lines);
+get_last_calls(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) ->
+ get_last_calls(Fd,Bin,[],[byte_list_to_string(lists:reverse(Acc))|Lines]);
+get_last_calls(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) ->
+ get_last_calls(Fd,Bin,Acc,Lines);
+get_last_calls(Fd,<<$\s:8,Bin/binary>>,[],Lines) ->
+ get_last_calls(Fd,Bin,[],Lines);
+get_last_calls(Fd,<<Char:8,Bin/binary>>,Acc,Lines) ->
+ get_last_calls(Fd,Bin,[Char|Acc],Lines);
+get_last_calls(Fd,<<>>,Acc,Lines) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ get_last_calls(Fd,Bin,Acc,Lines);
+ eof ->
+ lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))])
end.
-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),[]}.
+get_link_list(Fd) ->
+ case get_chunk(Fd) of
+ {ok,<<"[",Bin/binary>>} ->
+ #{links:=Links,
+ mons:=Monitors,
+ mon_by:=MonitoredBy} =
+ get_link_list(Fd,Bin,#{links=>[],mons=>[],mon_by=>[]}),
+ {lists:reverse(Links),
+ lists:reverse(Monitors),
+ lists:reverse(MonitoredBy)};
+ eof ->
+ {[],[],[]}
+ end.
+
+get_link_list(Fd,<<NL:8,_/binary>>=Bin,Acc) when NL=:=$\r; NL=:=$\n->
+ skip(Fd,Bin),
+ Acc;
+get_link_list(Fd,Bin,Acc) ->
+ case binary:split(Bin,[<<", ">>,<<"]">>]) of
+ [Link,Rest] ->
+ get_link_list(Fd,Rest,get_link(Link,Acc));
+ [Incomplete] ->
+ case get_chunk(Fd) of
+ {ok,More} ->
+ get_link_list(Fd,<<Incomplete/binary,More/binary>>,Acc);
+ eof ->
+ Acc
+ end
+ end.
+
+get_link(<<"#Port",_/binary>>=PortBin,#{links:=Links}=Acc) ->
+ PortStr = binary_to_list(PortBin),
+ Acc#{links=>[{PortStr,PortStr}|Links]};
+get_link(<<"<",_/binary>>=PidBin,#{links:=Links}=Acc) ->
+ PidStr = binary_to_list(PidBin),
+ Acc#{links=>[{PidStr,PidStr}|Links]};
+get_link(<<"{to,",Bin/binary>>,#{mons:=Monitors}=Acc) ->
+ Acc#{mons=>[parse_monitor(Bin)|Monitors]};
+get_link(<<"{from,",Bin/binary>>,#{mon_by:=MonitoredBy}=Acc) ->
+ Acc#{mon_by=>[parse_monitor(Bin)|MonitoredBy]};
+get_link(Unexpected,Acc) ->
+ io:format("WARNING: found unexpected data in link list:~n~ts~n",[Unexpected]),
+ Acc.
-parse_name_node(","++Rest,Name) ->
- parse_name_node(Rest,Name,[]);
-parse_name_node([H|T],Name) ->
- parse_name_node(T,[H|Name]);
-parse_name_node([],Name) ->
- %% truncated
- {lists:reverse(Name),[],[]}.
-
-parse_name_node("}"++Rest,Name,Node) ->
- {lists:reverse(Name),lists:reverse(Node),Rest};
-parse_name_node([H|T],Name,Node) ->
- parse_name_node(T,Name,[H|Node]);
-parse_name_node([],Name,Node) ->
- %% truncated
- {lists:reverse(Name),lists:reverse(Node),[]}.
+parse_monitor(MonBin) ->
+ case binary:split(MonBin,[<<",">>,<<"{">>,<<"}">>],[global]) of
+ [PidBin,RefBin,<<>>] ->
+ PidStr = binary_to_list(PidBin),
+ RefStr = binary_to_list(RefBin),
+ {PidStr,PidStr++" ("++RefStr++")"};
+ [<<>>,NameBin,NodeBin,<<>>,RefBin,<<>>] ->
+ %% Named process
+ NameStr = binary_to_list(NameBin),
+ NodeStr = binary_to_list(NodeBin),
+ PidStr = get_pid_from_name(NameStr,NodeStr),
+ RefStr = binary_to_list(RefBin),
+ {PidStr,"{"++NameStr++","++NodeStr++"} ("++RefStr++")"}
+ end.
get_pid_from_name(Name,Node) ->
case ets:lookup(cdv_reg_proc_table,cdv_dump_node_name) of
@@ -1325,70 +1441,86 @@ maybe_other_node2(Channel) ->
end.
-expand_memory(Fd,Pid,DumpVsn,Binaries) ->
- BinAddrAdj = get_bin_addr_adj(DumpVsn),
+expand_memory(Fd,Pid,DumpVsn) ->
+ DecodeOpts = get_decode_opts(DumpVsn),
put(fd,Fd),
- Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries),
- Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict),
- read_messages(Fd,Pid,BinAddrAdj,Dict),
- read_dictionary(Fd,Pid,BinAddrAdj,Dict)},
+ Dict0 = case get(?literals) of
+ undefined ->
+ Literals = read_literals(Fd,DecodeOpts),
+ put(?literals,Literals),
+ put(fd,Fd),
+ Literals;
+ Literals ->
+ Literals
+ end,
+ Dict = read_heap(Fd,Pid,DecodeOpts,Dict0),
+ Expanded = {read_stack_dump(Fd,Pid,DecodeOpts,Dict),
+ read_messages(Fd,Pid,DecodeOpts,Dict),
+ read_dictionary(Fd,Pid,DecodeOpts,Dict)},
erase(fd),
- Expanded.
-
-%%%-----------------------------------------------------------------
-%%% This is a workaround for a bug in dump versions prior to 0.3:
-%%% Addresses were truncated to 32 bits. This could cause binaries to
-%%% get the same address as heap terms in the dump. To work around it
-%%% we always store binaries on very high addresses in the gb_tree.
-get_bin_addr_adj(DumpVsn) when DumpVsn < [0,3] ->
- 16#f bsl 64;
-get_bin_addr_adj(_) ->
- 0.
-
-%%%
-%%% Read binaries.
-%%%
-read_binaries(Fd,DumpVsn) ->
- AllBinaries = lookup_index(?binary),
- AddrAdj = get_bin_addr_adj(DumpVsn),
- Fun = fun({Addr0,Pos},Dict0) ->
- pos_bof(Fd,Pos),
- {HexAddr,_} = get_hex(Addr0),
- Addr = HexAddr bor AddrAdj,
- Bin =
- case line_head(Fd) of
- {eof,_} -> '#CDVTruncatedBinary';
- _Size -> {'#CDVBin',Pos}
- end,
- gb_trees:enter(Addr,Bin,Dict0)
- end,
- progress_foldl("Processing binaries",Fun,gb_trees:empty(),AllBinaries).
+ IncompleteWarning =
+ case erase(incomplete_heap) of
+ undefined ->
+ [];
+ true ->
+ ["WARNING: This process has an incomplete heap. "
+ "Some information might be missing."]
+ end,
+ {Expanded,IncompleteWarning}.
+
+read_literals(Fd,DecodeOpts) ->
+ case lookup_index(?literals,[]) of
+ [{_,Start}] ->
+ [{_,Chars}] = ets:lookup(cdv_heap_file_chars,literals),
+ init_progress("Reading literals",Chars),
+ pos_bof(Fd,Start),
+ read_heap(DecodeOpts,gb_trees:empty());
+ [] ->
+ gb_trees:empty()
+ end.
+
+get_decode_opts(DumpVsn) ->
+ BinAddrAdj = if
+ DumpVsn < [0,3] ->
+ %% This is a workaround for a bug in dump
+ %% versions prior to 0.3: Addresses were
+ %% truncated to 32 bits. This could cause
+ %% binaries to get the same address as heap
+ %% terms in the dump. To work around it we
+ %% always store binaries on very high
+ %% addresses in the gb_tree.
+ 16#f bsl 64;
+ true ->
+ 0
+ end,
+ Base64 = DumpVsn >= [0,5],
+ #dec_opts{bin_addr_adj=BinAddrAdj,base64=Base64}.
%%%
%%% Read top level section.
%%%
-read_stack_dump(Fd,Pid,BinAddrAdj,Dict) ->
+read_stack_dump(Fd,Pid,DecodeOpts,Dict) ->
case lookup_index(?proc_stack,Pid) of
[{_,Start}] ->
pos_bof(Fd,Start),
- read_stack_dump1(Fd,BinAddrAdj,Dict,[]);
+ read_stack_dump1(Fd,DecodeOpts,Dict,[]);
[] ->
[]
end.
-read_stack_dump1(Fd,BinAddrAdj,Dict,Acc) ->
+read_stack_dump1(Fd,DecodeOpts,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case bytes(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
- Stack = parse_top(Line,BinAddrAdj,Dict),
- read_stack_dump1(Fd,BinAddrAdj,Dict,[Stack|Acc])
+ Stack = parse_top(Line,DecodeOpts,Dict),
+ read_stack_dump1(Fd,DecodeOpts,Dict,[Stack|Acc])
end.
-parse_top(Line0, BinAddrAdj, D) ->
+parse_top(Line0, DecodeOpts, D) ->
{Label,Line1} = get_label(Line0),
- {Term,Line,D} = parse_term(Line1, BinAddrAdj, D),
+ {Term,Line,D} = parse_term(Line1, DecodeOpts, D),
[] = skip_blanks(Line),
{Label,Term}.
@@ -1396,27 +1528,27 @@ parse_top(Line0, BinAddrAdj, D) ->
%%% Read message queue.
%%%
-read_messages(Fd,Pid,BinAddrAdj,Dict) ->
+read_messages(Fd,Pid,DecodeOpts,Dict) ->
case lookup_index(?proc_messages,Pid) of
[{_,Start}] ->
pos_bof(Fd,Start),
- read_messages1(Fd,BinAddrAdj,Dict,[]);
+ read_messages1(Fd,DecodeOpts,Dict,[]);
[] ->
[]
end.
-read_messages1(Fd,BinAddrAdj,Dict,Acc) ->
+read_messages1(Fd,DecodeOpts,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case bytes(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
- Msg = parse_message(Line,BinAddrAdj,Dict),
- read_messages1(Fd,BinAddrAdj,Dict,[Msg|Acc])
+ Msg = parse_message(Line,DecodeOpts,Dict),
+ read_messages1(Fd,DecodeOpts,Dict,[Msg|Acc])
end.
-parse_message(Line0, BinAddrAdj, D) ->
- {Msg,":"++Line1,_} = parse_term(Line0, BinAddrAdj, D),
- {Token,Line,_} = parse_term(Line1, BinAddrAdj, D),
+parse_message(Line0, DecodeOpts, D) ->
+ {Msg,":"++Line1,_} = parse_term(Line0, DecodeOpts, D),
+ {Token,Line,_} = parse_term(Line1, DecodeOpts, D),
[] = skip_blanks(Line),
{Msg,Token}.
@@ -1424,26 +1556,26 @@ parse_message(Line0, BinAddrAdj, D) ->
%%% Read process dictionary
%%%
-read_dictionary(Fd,Pid,BinAddrAdj,Dict) ->
+read_dictionary(Fd,Pid,DecodeOpts,Dict) ->
case lookup_index(?proc_dictionary,Pid) of
[{_,Start}] ->
pos_bof(Fd,Start),
- read_dictionary1(Fd,BinAddrAdj,Dict,[]);
+ read_dictionary1(Fd,DecodeOpts,Dict,[]);
[] ->
[]
end.
-read_dictionary1(Fd,BinAddrAdj,Dict,Acc) ->
+read_dictionary1(Fd,DecodeOpts,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case bytes(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
- Msg = parse_dictionary(Line,BinAddrAdj,Dict),
- read_dictionary1(Fd,BinAddrAdj,Dict,[Msg|Acc])
+ Msg = parse_dictionary(Line,DecodeOpts,Dict),
+ read_dictionary1(Fd,DecodeOpts,Dict,[Msg|Acc])
end.
-parse_dictionary(Line0, BinAddrAdj, D) ->
- {Entry,Line,_} = parse_term(Line0, BinAddrAdj, D),
+parse_dictionary(Line0, DecodeOpts, D) ->
+ {Entry,Line,_} = parse_term(Line0, DecodeOpts, D),
[] = skip_blanks(Line),
Entry.
@@ -1451,34 +1583,39 @@ parse_dictionary(Line0, BinAddrAdj, D) ->
%%% Read heap data.
%%%
-read_heap(Fd,Pid,BinAddrAdj,Dict0) ->
+read_heap(Fd,Pid,DecodeOpts,Dict0) ->
case lookup_index(?proc_heap,Pid) of
[{_,Pos}] ->
+ [{_,Chars}] = ets:lookup(cdv_heap_file_chars,Pid),
+ init_progress("Reading process heap",Chars),
pos_bof(Fd,Pos),
- read_heap(BinAddrAdj,Dict0);
+ read_heap(DecodeOpts,Dict0);
[] ->
Dict0
end.
-read_heap(BinAddrAdj,Dict0) ->
+read_heap(DecodeOpts,Dict0) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case get(fd) of
end_of_heap ->
+ end_progress(),
Dict0;
Fd ->
case bytes(Fd) of
"=" ++ _next_tag ->
+ end_progress(),
put(fd, end_of_heap),
Dict0;
Line ->
- Dict = parse(Line,BinAddrAdj,Dict0),
- read_heap(BinAddrAdj,Dict)
+ update_progress(length(Line)+1),
+ Dict = parse(Line,DecodeOpts,Dict0),
+ read_heap(DecodeOpts,Dict)
end
end.
-parse(Line0, BinAddrAdj, Dict0) ->
+parse(Line0, DecodeOpts, Dict0) ->
{Addr,":"++Line1} = get_hex(Line0),
- {_Term,Line,Dict} = parse_heap_term(Line1, Addr, BinAddrAdj, Dict0),
+ {_Term,Line,Dict} = parse_heap_term(Line1, Addr, DecodeOpts, Dict0),
[] = skip_blanks(Line),
Dict.
@@ -1506,11 +1643,15 @@ get_ports(File) ->
%% Converting port string to tuple to secure correct sorting. This is
%% converted back in cdv_port_cb:format/1.
port_to_tuple("#Port<"++Port) ->
- [I1,I2] = string:tokens(Port,".>"),
+ [I1,I2] = string:lexemes(Port,".>"),
{list_to_integer(I1),list_to_integer(I2)}.
get_portinfo(Fd,Port) ->
case line_head(Fd) of
+ "State" ->
+ get_portinfo(Fd,Port#port{state=bytes(Fd)});
+ "Task Flags" ->
+ get_portinfo(Fd,Port#port{task_flags=bytes(Fd)});
"Slot" ->
%% stored as integer so we can sort on it
get_portinfo(Fd,Port#port{slot=list_to_integer(bytes(Fd))});
@@ -1529,12 +1670,16 @@ get_portinfo(Fd,Port) ->
"Registered as" ->
get_portinfo(Fd,Port#port{name=string(Fd)});
"Monitors" ->
- Monitors0 = string:tokens(bytes(Fd),"()"),
+ Monitors0 = string:lexemes(bytes(Fd),"()"),
Monitors = [begin
- [Pid,Ref] = string:tokens(Mon,","),
+ [Pid,Ref] = string:lexemes(Mon,","),
{Pid,Pid++" ("++Ref++")"}
end || Mon <- Monitors0],
get_portinfo(Fd,Port#port{monitors=Monitors});
+ "Suspended" ->
+ Pids = split_pid_list_no_space(bytes(Fd)),
+ Suspended = [{Pid,Pid} || Pid <- Pids],
+ get_portinfo(Fd,Port#port{suspended=Suspended});
"Port controls linked-in driver" ->
Str = lists:flatten(["Linked in driver: " | string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
@@ -1550,6 +1695,15 @@ get_portinfo(Fd,Port) ->
"Port is UNIX fd not opened by emulator" ->
Str = lists:flatten(["UNIX fd not opened by emulator: "| string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
+ "Input" ->
+ get_portinfo(Fd,Port#port{input=list_to_integer(bytes(Fd))});
+ "Output" ->
+ get_portinfo(Fd,Port#port{output=list_to_integer(bytes(Fd))});
+ "Queue" ->
+ get_portinfo(Fd,Port#port{queue=list_to_integer(bytes(Fd))});
+ "Port Data" ->
+ get_portinfo(Fd,Port#port{port_data=string(Fd)});
+
"=" ++ _next_tag ->
Port;
Other ->
@@ -1592,7 +1746,7 @@ get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->
"Buckets" ->
%% 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(bytes(Fd))),
+ Buckets = list_to_integer(string:trim(bytes(Fd),both,"\s")),
get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS);
"Objects" ->
get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(bytes(Fd))},WS);
@@ -1610,7 +1764,9 @@ get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->
Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_min=>Val}},WS);
"Chain Length Avg" ->
- Val = try list_to_float(string:strip(bytes(Fd))) catch _:_ -> "-" end,
+ Val = try list_to_float(string:trim(bytes(Fd),both,"\s"))
+ catch _:_ -> "-"
+ end,
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_avg=>Val}},WS);
"Chain Length Max" ->
Val = bytes(Fd),
@@ -1767,7 +1923,7 @@ get_nodeinfo(Fd,Nod) ->
Creations = lists:flatmap(fun(C) -> try [list_to_integer(C)]
catch error:badarg -> []
end
- end, string:tokens(bytes(Fd)," ")),
+ end, string:lexemes(bytes(Fd)," ")),
get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}});
"Remote link" ->
Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
@@ -1798,12 +1954,15 @@ get_nodeinfo(Fd,Nod) ->
%%-----------------------------------------------------------------
%% Page with details about one loaded modules
-get_loaded_mod_details(File,Mod) ->
+get_loaded_mod_details(File,Mod,DecodeOpts) ->
[{_,Start}] = lookup_index(?mod,Mod),
Fd = open(File),
pos_bof(Fd,Start),
InitLM = #loaded_mod{mod=Mod,old_size="No old code exists"},
- ModInfo = get_loaded_mod_info(Fd,InitLM,fun all_modinfo/3),
+ Fun = fun(F, LM, LineHead) ->
+ all_modinfo(F, LM, LineHead, DecodeOpts)
+ end,
+ ModInfo = get_loaded_mod_info(Fd,InitLM,Fun),
close(Fd),
ModInfo.
@@ -1861,59 +2020,48 @@ get_loaded_mod_info(Fd,LM,Fun) ->
main_modinfo(_Fd,LM,_LineHead) ->
LM.
-all_modinfo(Fd,LM,LineHead) ->
+all_modinfo(Fd,LM,LineHead,DecodeOpts) ->
case LineHead of
"Current attributes" ->
- Str = hex_to_str(bytes(Fd,"")),
+ Str = get_attribute(Fd, DecodeOpts),
LM#loaded_mod{current_attrib=Str};
"Current compilation info" ->
- Str = hex_to_str(bytes(Fd,"")),
+ Str = get_attribute(Fd, DecodeOpts),
LM#loaded_mod{current_comp_info=Str};
"Old attributes" ->
- Str = hex_to_str(bytes(Fd,"")),
+ Str = get_attribute(Fd, DecodeOpts),
LM#loaded_mod{old_attrib=Str};
"Old compilation info" ->
- Str = hex_to_str(bytes(Fd,"")),
+ Str = get_attribute(Fd, DecodeOpts),
LM#loaded_mod{old_comp_info=Str};
Other ->
unexpected(Fd,Other,"loaded modules info"),
LM
end.
-
-hex_to_str(Hex) ->
- Term = hex_to_term(Hex,[]),
+get_attribute(Fd, DecodeOpts) ->
+ Term = do_get_attribute(Fd, DecodeOpts),
io_lib:format("~tp~n",[Term]).
-hex_to_term([X,Y|Hex],Acc) ->
- MS = hex_to_dec([X]),
- LS = hex_to_dec([Y]),
- Z = 16*MS+LS,
- hex_to_term(Hex,[Z|Acc]);
-hex_to_term([],Acc) ->
- Bin = list_to_binary(lists:reverse(Acc)),
- case catch binary_to_term(Bin) of
- {'EXIT',_Reason} ->
- {"WARNING: The term is probably truncated!",
- "I can not do binary_to_term.",
- Bin};
- Term ->
- Term
- end;
-hex_to_term(Rest,Acc) ->
- {"WARNING: The term is probably truncated!",
- "I can not convert hex to term.",
- Rest,list_to_binary(lists:reverse(Acc))}.
-
-
-hex_to_dec("F") -> 15;
-hex_to_dec("E") -> 14;
-hex_to_dec("D") -> 13;
-hex_to_dec("C") -> 12;
-hex_to_dec("B") -> 11;
-hex_to_dec("A") -> 10;
-hex_to_dec(N) -> list_to_integer(N).
-
+do_get_attribute(Fd, DecodeOpts) ->
+ Bytes = bytes(Fd, ""),
+ try get_binary(Bytes, DecodeOpts) of
+ {Bin,_} ->
+ try binary_to_term(Bin) of
+ Term ->
+ Term
+ catch
+ _:_ ->
+ {"WARNING: The term is probably truncated!",
+ "I cannot do binary_to_term/1.",
+ Bin}
+ end
+ catch
+ _:_ ->
+ {"WARNING: The term is probably truncated!",
+ "I cannot convert to binary.",
+ Bytes}
+ end.
%%-----------------------------------------------------------------
%% Page with list of all funs
@@ -2213,7 +2361,7 @@ get_size_value(Key,Data) ->
%% and Value is the sum over all allocator instances of each type.
sort_allocator_types([{Name,Data}|Allocators],Acc,DoTotal) ->
Type =
- case string:tokens(Name,"[]") of
+ case string:lexemes(Name,"[]") of
[T,_Id] -> T;
[Name] -> Name;
Other -> Other
@@ -2388,73 +2536,142 @@ get_indextableinfo1(Fd,IndexTable) ->
%%-----------------------------------------------------------------
%% Page with scheduler table information
schedulers(File) ->
- case lookup_index(?scheduler) of
- [] ->
- [];
- Schedulers ->
- Fd = open(File),
- R = lists:map(fun({Name,Start}) ->
- get_schedulerinfo(Fd,Name,Start)
- end,
- Schedulers),
- close(Fd),
- R
- end.
+ Fd = open(File),
+
+ Schds0 = case lookup_index(?scheduler) of
+ [] ->
+ [];
+ Normals ->
+ [{Normals, #sched{type=normal}}]
+ end,
+ Schds1 = case lookup_index(?dirty_cpu_scheduler) of
+ [] ->
+ Schds0;
+ DirtyCpus ->
+ [{DirtyCpus, get_dirty_runqueue(Fd, ?dirty_cpu_run_queue)}
+ | Schds0]
+ end,
+ Schds2 = case lookup_index(?dirty_io_scheduler) of
+ [] ->
+ Schds1;
+ DirtyIos ->
+ [{DirtyIos, get_dirty_runqueue(Fd, ?dirty_io_run_queue)}
+ | Schds1]
+ end,
+
+ R = schedulers1(Fd, Schds2, []),
+ close(Fd),
+ R.
-get_schedulerinfo(Fd,Name,Start) ->
+schedulers1(_Fd, [], Acc) ->
+ Acc;
+schedulers1(Fd, [{Scheds,Sched0} | Tail], Acc0) ->
+ Acc1 = lists:foldl(fun({Name,Start}, AccIn) ->
+ [get_schedulerinfo(Fd,Name,Start,Sched0) | AccIn]
+ end,
+ Acc0,
+ Scheds),
+ schedulers1(Fd, Tail, Acc1).
+
+get_schedulerinfo(Fd,Name,Start,Sched0) ->
pos_bof(Fd,Start),
- get_schedulerinfo1(Fd,#sched{name=Name}).
+ get_schedulerinfo1(Fd,Sched0#sched{name=list_to_integer(Name)}).
+
+sched_type(?dirty_cpu_run_queue) -> dirty_cpu;
+sched_type(?dirty_io_run_queue) -> dirty_io.
+
+get_schedulerinfo1(Fd, Sched) ->
+ case get_schedulerinfo2(Fd, Sched) of
+ {more, Sched2} ->
+ get_schedulerinfo1(Fd, Sched2);
+ {done, Sched2} ->
+ Sched2
+ end.
-get_schedulerinfo1(Fd,Sched=#sched{details=Ds}) ->
+get_schedulerinfo2(Fd, Sched=#sched{details=Ds}) ->
case line_head(Fd) of
"Current Process" ->
- get_schedulerinfo1(Fd,Sched#sched{process=bytes(Fd, "None")});
+ {more, Sched#sched{process=bytes(Fd, "None")}};
"Current Port" ->
- get_schedulerinfo1(Fd,Sched#sched{port=bytes(Fd, "None")});
+ {more, Sched#sched{port=bytes(Fd, "None")}};
+
+ "Scheduler Sleep Info Flags" ->
+ {more, Sched#sched{details=Ds#{sleep_info=>bytes(Fd, "None")}}};
+ "Scheduler Sleep Info Aux Work" ->
+ {more, Sched#sched{details=Ds#{sleep_aux=>bytes(Fd, "None")}}};
+
+ "Current Process State" ->
+ {more, Sched#sched{details=Ds#{currp_state=>bytes(Fd)}}};
+ "Current Process Internal State" ->
+ {more, Sched#sched{details=Ds#{currp_int_state=>bytes(Fd)}}};
+ "Current Process Program counter" ->
+ {more, Sched#sched{details=Ds#{currp_prg_cnt=>string(Fd)}}};
+ "Current Process CP" ->
+ {more, Sched#sched{details=Ds#{currp_cp=>string(Fd)}}};
+ "Current Process Limited Stack Trace" ->
+ %% If there shall be last in scheduler information block
+ {done, Sched#sched{details=get_limited_stack(Fd, 0, Ds)}};
+
+ "=" ++ _next_tag ->
+ {done, Sched};
+
+ Other ->
+ case Sched#sched.type of
+ normal ->
+ get_runqueue_info2(Fd, Other, Sched);
+ _ ->
+ unexpected(Fd,Other,"dirty scheduler information"),
+ {done, Sched}
+ end
+ end.
+
+get_dirty_runqueue(Fd, Tag) ->
+ case lookup_index(Tag) of
+ [{_, Start}] ->
+ pos_bof(Fd,Start),
+ get_runqueue_info1(Fd,#sched{type=sched_type(Tag)});
+ [] ->
+ #sched{}
+ end.
+
+get_runqueue_info1(Fd, Sched) ->
+ case get_runqueue_info2(Fd, line_head(Fd), Sched) of
+ {more, Sched2} ->
+ get_runqueue_info1(Fd, Sched2);
+ {done, Sched2} ->
+ Sched2
+ end.
+
+get_runqueue_info2(Fd, LineHead, Sched=#sched{details=Ds}) ->
+ case LineHead of
"Run Queue Max Length" ->
RQMax = list_to_integer(bytes(Fd)),
RQ = RQMax + Sched#sched.run_q,
- get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_max=>RQMax}});
+ {more, Sched#sched{run_q=RQ, details=Ds#{runq_max=>RQMax}}};
"Run Queue High Length" ->
RQHigh = list_to_integer(bytes(Fd)),
RQ = RQHigh + Sched#sched.run_q,
- get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_high=>RQHigh}});
+ {more, Sched#sched{run_q=RQ, details=Ds#{runq_high=>RQHigh}}};
"Run Queue Normal Length" ->
RQNorm = list_to_integer(bytes(Fd)),
RQ = RQNorm + Sched#sched.run_q,
- get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_norm=>RQNorm}});
+ {more, Sched#sched{run_q=RQ, details=Ds#{runq_norm=>RQNorm}}};
"Run Queue Low Length" ->
RQLow = list_to_integer(bytes(Fd)),
RQ = RQLow + Sched#sched.run_q,
- get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_low=>RQLow}});
+ {more, Sched#sched{run_q=RQ, details=Ds#{runq_low=>RQLow}}};
"Run Queue Port Length" ->
RQ = list_to_integer(bytes(Fd)),
- get_schedulerinfo1(Fd,Sched#sched{port_q=RQ});
-
- "Scheduler Sleep Info Flags" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_info=>bytes(Fd, "None")}});
- "Scheduler Sleep Info Aux Work" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_aux=>bytes(Fd, "None")}});
+ {more, Sched#sched{port_q=RQ}};
"Run Queue Flags" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{runq_flags=>bytes(Fd, "None")}});
+ {more, Sched#sched{details=Ds#{runq_flags=>bytes(Fd, "None")}}};
- "Current Process State" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_state=>bytes(Fd)}});
- "Current Process Internal State" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_int_state=>bytes(Fd)}});
- "Current Process Program counter" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_prg_cnt=>string(Fd)}});
- "Current Process CP" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_cp=>string(Fd)}});
- "Current Process Limited Stack Trace" ->
- %% If there shall be last in scheduler information block
- Sched#sched{details=get_limited_stack(Fd, 0, Ds)};
"=" ++ _next_tag ->
- Sched;
+ {done, Sched};
Other ->
unexpected(Fd,Other,"scheduler information"),
- Sched
+ {done, Sched}
end.
get_limited_stack(Fd, N, Ds) ->
@@ -2470,100 +2687,134 @@ get_limited_stack(Fd, N, Ds) ->
%%%-----------------------------------------------------------------
%%% Parse memory in crashdump version 0.1 and newer
%%%
-parse_heap_term([$l|Line0], Addr, BinAddrAdj, D0) -> %Cons cell.
- {H,"|"++Line1,D1} = parse_term(Line0, BinAddrAdj, D0),
- {T,Line,D2} = parse_term(Line1, BinAddrAdj, D1),
+parse_heap_term([$l|Line0], Addr, DecodeOpts, D0) -> %Cons cell.
+ {H,"|"++Line1,D1} = parse_term(Line0, DecodeOpts, D0),
+ {T,Line,D2} = parse_term(Line1, DecodeOpts, D1),
Term = [H|T],
D = gb_trees:insert(Addr, Term, D2),
{Term,Line,D};
-parse_heap_term([$t|Line0], Addr, BinAddrAdj, D) -> %Tuple
+parse_heap_term([$t|Line0], Addr, DecodeOpts, D) -> %Tuple
{N,":"++Line} = get_hex(Line0),
- parse_tuple(N, Line, Addr, BinAddrAdj, D, []);
-parse_heap_term([$F|Line0], Addr, _BinAddrAdj, D0) -> %Float
+ parse_tuple(N, Line, Addr, DecodeOpts, D, []);
+parse_heap_term([$F|Line0], Addr, _DecodeOpts, D0) -> %Float
{N,":"++Line1} = get_hex(Line0),
{Chars,Line} = get_chars(N, Line1),
Term = list_to_float(Chars),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("B16#"++Line0, Addr, _BinAddrAdj, D0) -> %Positive big number.
+parse_heap_term("B16#"++Line0, Addr, _DecodeOpts, D0) -> %Positive big number.
{Term,Line} = get_hex(Line0),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("B-16#"++Line0, Addr, _BinAddrAdj, D0) -> %Negative big number
+parse_heap_term("B-16#"++Line0, Addr, _DecodeOpts, D0) -> %Negative big number
{Term0,Line} = get_hex(Line0),
Term = -Term0,
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("B"++Line0, Addr, _BinAddrAdj, D0) -> %Decimal big num
+parse_heap_term("B"++Line0, Addr, _DecodeOpts, D0) -> %Decimal big num
case string:to_integer(Line0) of
{Int,Line} when is_integer(Int) ->
D = gb_trees:insert(Addr, Int, D0),
{Int,Line,D}
end;
-parse_heap_term([$P|Line0], Addr, _BinAddrAdj, D0) -> % External Pid.
+parse_heap_term([$P|Line0], Addr, _DecodeOpts, D0) -> % External Pid.
{Pid0,Line} = get_id(Line0),
Pid = ['#CDVPid'|Pid0],
D = gb_trees:insert(Addr, Pid, D0),
{Pid,Line,D};
-parse_heap_term([$p|Line0], Addr, _BinAddrAdj, D0) -> % External Port.
+parse_heap_term([$p|Line0], Addr, _DecodeOpts, D0) -> % External Port.
{Port0,Line} = get_id(Line0),
Port = ['#CDVPort'|Port0],
D = gb_trees:insert(Addr, Port, D0),
{Port,Line,D};
-parse_heap_term("E"++Line0, Addr, _BinAddrAdj, D0) -> %Term encoded in external format.
- {Bin,Line} = get_binary(Line0),
+parse_heap_term("E"++Line0, Addr, DecodeOpts, D0) -> %Term encoded in external format.
+ {Bin,Line} = get_binary(Line0, DecodeOpts),
Term = binary_to_term(Bin),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("Yh"++Line0, Addr, _BinAddrAdj, D0) -> %Heap binary.
- {Term,Line} = get_binary(Line0),
+parse_heap_term("Yh"++Line0, Addr, DecodeOpts, D0) -> %Heap binary.
+ {Term,Line} = get_binary(Line0, DecodeOpts),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) -> %Reference-counted binary.
+parse_heap_term("Yc"++Line0, Addr, DecodeOpts, D0) -> %Reference-counted binary.
{Binp0,":"++Line1} = get_hex(Line0),
{Offset,":"++Line2} = get_hex(Line1),
{Sz,Line} = get_hex(Line2),
- Binp = Binp0 bor BinAddrAdj,
- Term = case gb_trees:lookup(Binp, D0) of
- {value,Bin} -> cdvbin(Offset,Sz,Bin);
- none -> '#CDVNonexistingBinary'
- end,
- D = gb_trees:insert(Addr, Term, D0),
- {Term,Line,D};
-parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) -> %Sub binary.
+ Binp = Binp0 bor DecodeOpts#dec_opts.bin_addr_adj,
+ case lookup_binary_index(Binp) of
+ [{_,Start}] ->
+ SymbolicBin = {'#CDVBin',Start},
+ Term = cdvbin(Offset, Sz, SymbolicBin),
+ D1 = gb_trees:insert(Addr, Term, D0),
+ D = gb_trees:insert(Binp, SymbolicBin, D1),
+ {Term,Line,D};
+ [] ->
+ Term = '#CDVNonexistingBinary',
+ D1 = gb_trees:insert(Addr, Term, D0),
+ D = gb_trees:insert(Binp, Term, D1),
+ {Term,Line,D}
+ end;
+parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) -> %Sub binary.
{Binp0,":"++Line1} = get_hex(Line0),
{Offset,":"++Line2} = get_hex(Line1),
- {Sz,Line} = get_hex(Line2),
- Binp = Binp0 bor BinAddrAdj,
- Term = case gb_trees:lookup(Binp, D0) of
- {value,Bin} -> cdvbin(Offset,Sz,Bin);
- none when Binp0=/=Binp ->
- %% Might it be on the heap?
- case gb_trees:lookup(Binp0, D0) of
- {value,Bin} -> cdvbin(Offset,Sz,Bin);
- none -> '#CDVNonexistingBinary'
- end;
- none -> '#CDVNonexistingBinary'
- end,
- D = gb_trees:insert(Addr, Term, D0),
- {Term,Line,D}.
-
+ {Sz,Line3} = get_hex(Line2),
+ {Term,Line,D1} = deref_bin(Binp0, Offset, Sz, Line3, DecodeOpts, D0),
+ D = gb_trees:insert(Addr, Term, D1),
+ {Term,Line,D};
+parse_heap_term("Mf"++Line0, Addr, DecodeOpts, D0) -> %Flatmap.
+ {Size,":"++Line1} = get_hex(Line0),
+ {Keys,":"++Line2,D1} = parse_term(Line1, DecodeOpts, D0),
+ {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []),
+ Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []),
+ Map = maps:from_list(Pairs),
+ D = gb_trees:update(Addr, Map, D2),
+ {Map,Line,D};
+parse_heap_term("Mh"++Line0, Addr, DecodeOpts, D0) -> %Head node in a hashmap.
+ {MapSize,":"++Line1} = get_hex(Line0),
+ {N,":"++Line2} = get_hex(Line1),
+ {Nodes,Line,D1} = parse_tuple(N, Line2, Addr, DecodeOpts, D0, []),
+ Map = maps:from_list(flatten_hashmap_nodes(Nodes)),
+ MapSize = maps:size(Map), %Assertion.
+ D = gb_trees:update(Addr, Map, D1),
+ {Map,Line,D};
+parse_heap_term("Mn"++Line0, Addr, DecodeOpts, D) -> %Interior node in a hashmap.
+ {N,":"++Line} = get_hex(Line0),
+ parse_tuple(N, Line, Addr, DecodeOpts, D, []).
parse_tuple(0, Line, Addr, _, D0, Acc) ->
Tuple = list_to_tuple(lists:reverse(Acc)),
D = gb_trees:insert(Addr, Tuple, D0),
{Tuple,Line,D};
-parse_tuple(N, Line0, Addr, BinAddrAdj, D0, Acc) ->
- case parse_term(Line0, BinAddrAdj, D0) of
+parse_tuple(N, Line0, Addr, DecodeOpts, D0, Acc) ->
+ case parse_term(Line0, DecodeOpts, D0) of
{Term,[$,|Line],D} when N > 1 ->
- parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc]);
+ parse_tuple(N-1, Line, Addr, DecodeOpts, D, [Term|Acc]);
{Term,Line,D}->
- parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc])
+ parse_tuple(N-1, Line, Addr, DecodeOpts, D, [Term|Acc])
end.
-parse_term([$H|Line0], BinAddrAdj, D) -> %Pointer to heap term.
+zip_tuples(0, _T1, _T2, Acc) ->
+ Acc;
+zip_tuples(N, T1, T2, Acc) when N =< tuple_size(T1) ->
+ zip_tuples(N-1, T1, T2, [{element(N, T1),element(N, T2)}|Acc]).
+
+flatten_hashmap_nodes(Tuple) ->
+ flatten_hashmap_nodes_1(tuple_size(Tuple), Tuple, []).
+
+flatten_hashmap_nodes_1(0, _Tuple, Acc) ->
+ Acc;
+flatten_hashmap_nodes_1(N, Tuple0, Acc0) ->
+ case element(N, Tuple0) of
+ [K|V] ->
+ flatten_hashmap_nodes_1(N-1, Tuple0, [{K,V}|Acc0]);
+ Tuple when is_tuple(Tuple) ->
+ Acc = flatten_hashmap_nodes_1(N-1, Tuple0, Acc0),
+ flatten_hashmap_nodes_1(tuple_size(Tuple), Tuple, Acc)
+ end.
+
+parse_term([$H|Line0], DecodeOpts, D) -> %Pointer to heap term.
{Ptr,Line} = get_hex(Line0),
- deref_ptr(Ptr, Line, BinAddrAdj, D);
+ deref_ptr(Ptr, Line, DecodeOpts, D);
parse_term([$N|Line], _, D) -> %[] (nil).
{[],Line,D};
parse_term([$I|Line0], _, D) -> %Small.
@@ -2580,11 +2831,11 @@ parse_term([$p|Line0], _, D) -> %Port.
parse_term([$S|Str0], _, D) -> %Information string.
Str = lists:reverse(skip_blanks(lists:reverse(Str0))),
{Str,[],D};
-parse_term([$D|Line0], _, D) -> %DistExternal
+parse_term([$D|Line0], DecodeOpts, D) -> %DistExternal
try
{AttabSize,":"++Line1} = get_hex(Line0),
{Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []),
- {Bin,Line3} = get_binary(Line2),
+ {Bin,Line3} = get_binary(Line2, DecodeOpts),
{try
erts_debug:dist_ext_to_term(Attab, Bin)
catch
@@ -2617,25 +2868,55 @@ parse_atom_translation_table(0, Line0, As) ->
parse_atom_translation_table(N, Line0, As) ->
{A, Line1, _} = parse_atom(Line0, []),
parse_atom_translation_table(N-1, Line1, [A|As]).
-
-
-deref_ptr(Ptr, Line, BinAddrAdj, D0) ->
- case gb_trees:lookup(Ptr, D0) of
+
+deref_ptr(Ptr, Line, DecodeOpts, D) ->
+ Lookup = fun(D0) ->
+ gb_trees:lookup(Ptr, D0)
+ end,
+ do_deref_ptr(Lookup, Line, DecodeOpts, D).
+
+deref_bin(Binp0, Offset, Sz, Line, DecodeOpts, D) ->
+ Binp = Binp0 bor DecodeOpts#dec_opts.bin_addr_adj,
+ Lookup = fun(D0) ->
+ lookup_binary(Binp, Offset, Sz, D0)
+ end,
+ do_deref_ptr(Lookup, Line, DecodeOpts, D).
+
+lookup_binary(Binp, Offset, Sz, D) ->
+ case lookup_binary_index(Binp) of
+ [{_,Start}] ->
+ Term = cdvbin(Offset, Sz, {'#CDVBin',Start}),
+ {value,Term};
+ [] ->
+ case gb_trees:lookup(Binp, D) of
+ {value,<<_:Offset/bytes,Sub:Sz/bytes,_/bytes>>} ->
+ {value,Sub};
+ {value,SymbolicBin} ->
+ {value,cdvbin(Offset, Sz, SymbolicBin)};
+ none ->
+ none
+ end
+ end.
+
+do_deref_ptr(Lookup, Line, DecodeOpts, D0) ->
+ case Lookup(D0) of
{value,Term} ->
{Term,Line,D0};
none ->
case get(fd) of
end_of_heap ->
+ put(incomplete_heap,true),
{['#CDVIncompleteHeap'],Line,D0};
Fd ->
case bytes(Fd) of
"="++_ ->
put(fd, end_of_heap),
- deref_ptr(Ptr, Line, BinAddrAdj, D0);
+ do_deref_ptr(Lookup, Line, DecodeOpts, D0);
L ->
- D = parse(L, BinAddrAdj, D0),
- deref_ptr(Ptr, Line, BinAddrAdj, D)
+ update_progress(length(L)+1),
+ D = parse(L, DecodeOpts, D0),
+ do_deref_ptr(Lookup, Line, DecodeOpts, D)
end
end
end.
@@ -2698,37 +2979,104 @@ get_label([$:|Line], Acc) ->
get_label([H|T], Acc) ->
get_label(T, [H|Acc]).
-get_binary(Line0) ->
- {N,":"++Line} = get_hex(Line0),
- do_get_binary(N, Line, []).
-
-get_binary(Offset,Size,Line0) ->
- {_N,":"++Line} = get_hex(Line0),
- do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), []).
-
-do_get_binary(0, Line, Acc) ->
+get_binary(Line0,DecodeOpts) ->
+ case get_hex(Line0) of
+ {N,":"++Line} ->
+ get_binary_1(N, Line, DecodeOpts);
+ _ ->
+ {'#CDVTruncatedBinary',[]}
+ end.
+
+get_binary_1(N,Line,#dec_opts{base64=false}) ->
+ get_binary_hex(N, Line, [], false);
+get_binary_1(N,Line0,#dec_opts{base64=true}) ->
+ NumBytes = ((N+2) div 3) * 4,
+ {Base64,Line} = lists:split(NumBytes, Line0),
+ Bin = get_binary_base64(list_to_binary(Base64), <<>>, false),
+ {Bin,Line}.
+
+get_binary(Offset,Size,Line0,DecodeOpts) ->
+ case get_hex(Line0) of
+ {_N,":"++Line} ->
+ get_binary_1(Offset,Size,Line,DecodeOpts);
+ _ ->
+ {'#CDVTruncatedBinary',[]}
+ end.
+
+get_binary_1(Offset,Size,Line,#dec_opts{base64=false}) ->
+ Progress = Size > ?binary_size_progress_limit,
+ Progress andalso init_progress("Reading binary",Size),
+ get_binary_hex(Size, lists:sublist(Line,(Offset*2)+1,Size*2), [],
+ Progress);
+get_binary_1(StartOffset,Size,Line,#dec_opts{base64=true}) ->
+ Progress = Size > ?binary_size_progress_limit,
+ Progress andalso init_progress("Reading binary",Size),
+ EndOffset = StartOffset + Size,
+ StartByte = (StartOffset div 3) * 4,
+ EndByte = ((EndOffset + 2) div 3) * 4,
+ NumBytes = EndByte - StartByte,
+ case list_to_binary(Line) of
+ <<_:StartByte/bytes,Base64:NumBytes/bytes,_/bytes>> ->
+ Bin0 = get_binary_base64(Base64, <<>>, Progress),
+ Skip = StartOffset - (StartOffset div 3) * 3,
+ <<_:Skip/bytes,Bin:Size/bytes,_/bytes>> = Bin0,
+ {Bin,[]};
+ _ ->
+ {'#CDVTruncatedBinary',[]}
+ end.
+
+get_binary_hex(0, Line, Acc, Progress) ->
+ Progress andalso end_progress(),
{list_to_binary(lists:reverse(Acc)),Line};
-do_get_binary(N, [A,B|Line], Acc) ->
+get_binary_hex(N, [A,B|Line], Acc, Progress) ->
Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B),
- do_get_binary(N-1, Line, [Byte|Acc]);
-do_get_binary(_N, [], _Acc) ->
+ Progress andalso update_progress(),
+ get_binary_hex(N-1, Line, [Byte|Acc], Progress);
+get_binary_hex(_N, [], _Acc, Progress) ->
+ Progress andalso end_progress(),
{'#CDVTruncatedBinary',[]}.
+get_binary_base64(<<Chunk0:?base64_chunk_size/bytes,T/bytes>>,
+ Acc0, Progress) ->
+ Chunk = base64:decode(Chunk0),
+ Acc = <<Acc0/binary,Chunk/binary>>,
+ Progress andalso update_progress(?base64_chunk_size * 3 div 4),
+ get_binary_base64(T, Acc, Progress);
+get_binary_base64(Chunk0, Acc, Progress) ->
+ case Progress of
+ true ->
+ update_progress(?base64_chunk_size * 3 div 4),
+ end_progress();
+ false ->
+ ok
+ end,
+ Chunk = base64:decode(Chunk0),
+ <<Acc/binary,Chunk/binary>>.
+
cdvbin(Offset,Size,{'#CDVBin',Pos}) ->
['#CDVBin',Offset,Size,Pos];
cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) ->
['#CDVBin',Offset,Size,Pos];
cdvbin(_,_,'#CDVTruncatedBinary') ->
- '#CDVTruncatedBinary'.
+ '#CDVTruncatedBinary';
+cdvbin(_,_,'#CDVNonexistingBinary') ->
+ '#CDVNonexistingBinary'.
%%-----------------------------------------------------------------
-%% Functions for accessing the cdv_dump_index_table
-reset_index_table() ->
- ets:delete_all_objects(cdv_dump_index_table).
+%% Functions for accessing tables
+reset_tables() ->
+ ets:delete_all_objects(cdv_dump_index_table),
+ ets:delete_all_objects(cdv_reg_proc_table),
+ ets:delete_all_objects(cdv_binary_index_table),
+ ets:delete_all_objects(cdv_heap_file_chars).
insert_index(Tag,Id,Pos) ->
ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}).
+delete_index(Tag,Id) ->
+ Ms = [{{{Tag,'$1'},Id},[],[true]}],
+ ets:select_delete(cdv_dump_index_table, Ms).
+
lookup_index({Tag,Id}) ->
lookup_index(Tag,Id);
lookup_index(Tag) ->
@@ -2739,6 +3087,12 @@ lookup_index(Tag,Id) ->
count_index(Tag) ->
ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]).
+insert_binary_index(Addr,Pos) ->
+ ets:insert(cdv_binary_index_table,{Addr,Pos}).
+
+lookup_binary_index(Addr) ->
+ ets:lookup(cdv_binary_index_table,Addr).
+
%%-----------------------------------------------------------------
%% Convert tags read from crashdump to atoms used as first part of key
@@ -2748,6 +3102,10 @@ 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("dirty_cpu_scheduler") -> ?dirty_cpu_scheduler;
+tag_to_atom("dirty_cpu_run_queue") -> ?dirty_cpu_run_queue;
+tag_to_atom("dirty_io_scheduler") -> ?dirty_io_scheduler;
+tag_to_atom("dirty_io_run_queue") -> ?dirty_io_run_queue;
tag_to_atom("end") -> ?ende;
tag_to_atom("erl_crash_dump") -> ?erl_crash_dump;
tag_to_atom("ets") -> ?ets;
@@ -2757,6 +3115,7 @@ tag_to_atom("hidden_node") -> ?hidden_node;
tag_to_atom("index_table") -> ?index_table;
tag_to_atom("instr_data") -> ?instr_data;
tag_to_atom("internal_ets") -> ?internal_ets;
+tag_to_atom("literals") -> ?literals;
tag_to_atom("loaded_modules") -> ?loaded_modules;
tag_to_atom("memory") -> ?memory;
tag_to_atom("mod") -> ?mod;
@@ -2780,8 +3139,10 @@ tag_to_atom(UnknownTag) ->
%%%-----------------------------------------------------------------
%%% Store last tag for use when truncated, and reason if aborted
put_last_tag(?abort,Reason) ->
- %% Don't overwrite the real last tag
- put(truncated_reason,Reason);
+ %% Don't overwrite the real last tag, and make sure to return
+ %% the previous last tag.
+ put(truncated_reason,Reason),
+ get(last_tag);
put_last_tag(Tag,Id) ->
put(last_tag,{Tag,Id}).
@@ -2809,23 +3170,6 @@ to_value_list(Record) ->
Values.
%%%-----------------------------------------------------------------
-%%% Fold over List and report progress in percent.
-%%% Report is the text to be presented in the progress dialog.
-%%% Acc0 is the initial accumulator and will be passed to Fun as the
-%%% second arguement, i.e. Fun = fun(Item,Acc) -> NewAcc end.
-progress_foldl(Report,Fun,Acc0,List) ->
- init_progress(Report, length(List)),
- progress_foldl1(Fun,Acc0,List).
-
-progress_foldl1(Fun,Acc,[H|T]) ->
- update_progress(),
- progress_foldl1(Fun,Fun(H,Acc),T);
-progress_foldl1(_Fun,Acc,[]) ->
- end_progress(),
- Acc.
-
-
-%%%-----------------------------------------------------------------
%%% Map over List and report progress in percent.
%%% Report is the text to be presented in the progress dialog.
%%% Distribute the load over a number of processes, and File is opened
diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl
index 6a93a089fd..252e19379d 100644
--- a/lib/observer/src/crashdump_viewer.hrl
+++ b/lib/observer/src/crashdump_viewer.hrl
@@ -80,6 +80,10 @@
old_heap,
heap_unused,
old_heap_unused,
+ bin_vheap,
+ old_bin_vheap,
+ bin_vheap_unused,
+ old_bin_vheap_unused,
new_heap_start,
new_heap_top,
stack_top,
@@ -95,19 +99,27 @@
-record(port,
{id,
+ state,
+ task_flags=0,
slot,
connected,
links,
name,
monitors,
- controls}).
+ suspended,
+ controls,
+ input,
+ output,
+ queue,
+ port_data}).
-record(sched,
{name,
+ type,
process,
port,
run_q=0,
- port_q=0,
+ port_q,
details=#{}
}).
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index 3a5bd172e7..cd4aa2e299 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -65,8 +65,7 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-1.2","stdlib-2.0","runtime_tools-1.8.14",
- "kernel-3.0","inets-5.10","et-1.5",
- "erts-7.0"]}]}.
+ {runtime_dependencies, ["wx-1.2","stdlib-3.5","runtime_tools-1.8.14",
+ "kernel-3.0","et-1.5","erts-7.0"]}]}.
diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl
index 7f4b3dd484..e0f319b6cc 100644
--- a/lib/observer/src/observer_alloc_wx.erl
+++ b/lib/observer/src/observer_alloc_wx.erl
@@ -79,8 +79,8 @@ init([Notebook, Parent, Config]) ->
max = #{}
}
}
- catch _:Err ->
- io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, erlang:get_stacktrace()]),
+ catch _:Err:Stacktrace ->
+ io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, Stacktrace]),
{stop, Err}
end.
diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl
index 3dfcc42ada..8202f91030 100644
--- a/lib/observer/src/observer_html_lib.erl
+++ b/lib/observer/src/observer_html_lib.erl
@@ -278,24 +278,24 @@ href_proc_port("['#CDVPort'"++T,Acc,LTB) ->
%% Port written by crashdump_viewer:parse_term(...)
{Port0,Rest} = split($],T),
PortStr=
- case string:tokens(Port0,",.|") of
+ case string:lexemes(Port0,",.|") of
[X,Y] ->
Port = "#Port&lt;"++X++"."++Y++"&gt;",
href(Port,Port);
Ns ->
- "#Port&lt;" ++ string:join(Ns,".") ++"...&gt;"
+ "#Port&lt;" ++ lists:join($.,Ns) ++"...&gt;"
end,
href_proc_port(Rest,[PortStr|Acc],LTB);
href_proc_port("['#CDVPid'"++T,Acc,LTB) ->
%% Pid written by crashdump_viewer:parse_term(...)
{Pid0,Rest} = split($],T),
PidStr =
- case string:tokens(Pid0,",.|") of
+ case string:lexemes(Pid0,",.|") of
[X,Y,Z] ->
Pid = "&lt;"++X++"."++Y++"."++Z++"&gt;",
href(Pid,Pid);
Ns ->
- "&lt;" ++ string:join(Ns,".") ++ "...&gt;"
+ "&lt;" ++ lists:join($.,Ns) ++ "...&gt;"
end,
href_proc_port(Rest,[PidStr|Acc],LTB);
href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)->
@@ -332,27 +332,34 @@ href_proc_port([],Acc,_) ->
href_proc_bin(From, T, Acc, LTB) ->
{OffsetSizePos,Rest} = split($],T),
BinStr =
- case string:tokens(OffsetSizePos,",.| \n") of
+ case string:lexemes(OffsetSizePos,",.| \n") of
[Offset,SizeStr,Pos] when From =:= cdv ->
Size = list_to_integer(SizeStr),
PreviewSize = min(Size,10),
Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)},
- {ok,PreviewBin} = crashdump_viewer:expand_binary(Id),
- PreviewStr = preview_string(Size, PreviewBin),
- if LTB ->
- href("TARGET=\"expanded\"",
- ["#Binary?offset="++Offset++
- "&size="++SizeStr++
- "&pos="++Pos],
- PreviewStr);
- true ->
- PreviewStr
- end;
- [PreviewIntStr,SizeStr,Md5] when From =:= obs ->
+ case crashdump_viewer:expand_binary(Id) of
+ {ok, '#CDVTruncatedBinary'} ->
+ lists:flatten(
+ "<FONT COLOR=\"#FF0000\">"
+ "&lt;&lt;...(Truncated Binary)&gt;&gt;"
+ "</FONT>");
+ {ok, PreviewBin} ->
+ PreviewStr = preview_string(Size, PreviewBin),
+ if LTB ->
+ href("TARGET=\"expanded\"",
+ ["#Binary?offset="++Offset++
+ "&size="++SizeStr++
+ "&pos="++Pos],
+ PreviewStr);
+ true ->
+ PreviewStr
+ end
+ end;
+ [PreviewIntStr,PreviewBitSizeStr,SizeStr,Md5] when From =:= obs ->
Size = list_to_integer(SizeStr),
PreviewInt = list_to_integer(PreviewIntStr),
- PrevSize = (trunc(math:log2(PreviewInt)/8)+1)*8,
- PreviewStr = preview_string(Size,<<PreviewInt:PrevSize>>),
+ PreviewBitSize = list_to_integer(PreviewBitSizeStr),
+ PreviewStr = preview_string(Size,<<PreviewInt:PreviewBitSize>>),
if LTB ->
href("TARGET=\"expanded\"",
["#OBSBinary?key1="++PreviewIntStr++
diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl
index 463fb5b8ef..0678b64134 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -21,7 +21,8 @@
-export([get_wx_parent/1,
display_info_dialog/2, display_yes_no_dialog/1,
- display_progress_dialog/2, destroy_progress_dialog/0,
+ display_progress_dialog/3,
+ destroy_progress_dialog/0, sync_destroy_progress_dialog/0,
wait_for_progress/0, report_progress/1,
user_term/3, user_term_multiline/3,
interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1,
@@ -31,7 +32,8 @@
set_listctrl_col_size/2,
create_status_bar/1,
html_window/1, html_window/2,
- make_obsbin/2
+ make_obsbin/2,
+ add_scroll_entries/2
]).
-include_lib("wx/include/wx.hrl").
@@ -40,6 +42,9 @@
-define(SINGLE_LINE_STYLE, ?wxBORDER_NONE bor ?wxTE_READONLY bor ?wxTE_RICH2).
-define(MULTI_LINE_STYLE, ?SINGLE_LINE_STYLE bor ?wxTE_MULTILINE).
+-define(NUM_SCROLL_ITEMS,8).
+
+-define(pulse_timeout,50).
get_wx_parent(Window) ->
Parent = wxWindow:getParent(Window),
@@ -396,17 +401,18 @@ get_box_info({Title, left, List}) -> {Title, ?wxALIGN_LEFT, List};
get_box_info({Title, right, List}) -> {Title, ?wxALIGN_RIGHT, List}.
add_box(Panel, OuterBox, Cursor, Title, Proportion, {Format, List}) ->
- Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, Title}]),
+ NumStr = " ("++integer_to_list(length(List))++")",
+ Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, Title ++ NumStr}]),
Scroll = wxScrolledWindow:new(Panel),
wxScrolledWindow:enableScrolling(Scroll,true,true),
wxScrolledWindow:setScrollbars(Scroll,1,1,0,0),
ScrollSizer = wxBoxSizer:new(?wxVERTICAL),
wxScrolledWindow:setSizer(Scroll, ScrollSizer),
wxWindow:setBackgroundStyle(Scroll, ?wxBG_STYLE_SYSTEM),
- add_entries(Format, List, Scroll, ScrollSizer, Cursor),
+ Entries = add_entries(Format, List, Scroll, ScrollSizer, Cursor),
wxSizer:add(Box,Scroll,[{proportion,1},{flag,?wxEXPAND}]),
wxSizer:add(OuterBox,Box,[{proportion,Proportion},{flag,?wxEXPAND}]),
- {Scroll,ScrollSizer,length(List)}.
+ {Scroll,ScrollSizer,length(Entries)}.
add_entries(click, List, Scroll, ScrollSizer, Cursor) ->
Add = fun(Link) ->
@@ -414,7 +420,20 @@ add_entries(click, List, Scroll, ScrollSizer, Cursor) ->
wxWindow:setBackgroundStyle(TC, ?wxBG_STYLE_SYSTEM),
wxSizer:add(ScrollSizer,TC, [{flag,?wxEXPAND}])
end,
- [Add(Link) || Link <- List];
+ if length(List) > ?NUM_SCROLL_ITEMS ->
+ {List1,Rest} = lists:split(?NUM_SCROLL_ITEMS,List),
+ LinkEntries = [Add(Link) || Link <- List1],
+ NStr = integer_to_list(length(Rest)),
+ TC = link_entry2(Scroll,
+ {{more,{Rest,Scroll,ScrollSizer}},"more..."},
+ Cursor,
+ "Click to see " ++ NStr ++ " more entries"),
+ wxWindow:setBackgroundStyle(TC, ?wxBG_STYLE_SYSTEM),
+ E = wxSizer:add(ScrollSizer,TC, [{flag,?wxEXPAND}]),
+ LinkEntries ++ [E];
+ true ->
+ [Add(Link) || Link <- List]
+ end;
add_entries(plain, List, Scroll, ScrollSizer, _) ->
Add = fun(String) ->
TC = wxStaticText:new(Scroll, ?wxID_ANY, String),
@@ -422,6 +441,23 @@ add_entries(plain, List, Scroll, ScrollSizer, _) ->
end,
[Add(String) || String <- List].
+add_scroll_entries(MoreEntry,{List, Scroll, ScrollSizer}) ->
+ wx:batch(
+ fun() ->
+ wxSizer:remove(ScrollSizer,?NUM_SCROLL_ITEMS),
+ wxStaticText:destroy(MoreEntry),
+ Cursor = wxCursor:new(?wxCURSOR_HAND),
+ Add = fun(Link) ->
+ TC = link_entry(Scroll, Link, Cursor),
+ wxWindow:setBackgroundStyle(TC, ?wxBG_STYLE_SYSTEM),
+ wxSizer:add(ScrollSizer,TC, [{flag,?wxEXPAND}])
+ end,
+ Entries = [Add(Link) || Link <- List],
+ wxCursor:destroy(Cursor),
+ wxSizer:layout(ScrollSizer),
+ wxSizer:setVirtualSizeHints(ScrollSizer,Scroll),
+ Entries
+ end).
create_box(_Panel, {scroll_boxes,[]}) ->
undefined;
@@ -448,7 +484,7 @@ create_box(Panel, {scroll_boxes,Data}) ->
{_,H} = wxWindow:getSize(Dummy),
wxTextCtrl:destroy(Dummy),
- MaxH = if MaxL > 8 -> 8*H;
+ MaxH = if MaxL > ?NUM_SCROLL_ITEMS -> ?NUM_SCROLL_ITEMS*H;
true -> MaxL*H
end,
[wxWindow:setMinSize(B,{0,MaxH}) || {B,_,_} <- Boxes],
@@ -477,7 +513,7 @@ create_box(Parent, Data) ->
link_entry(Panel,Value);
_ ->
Value = to_str(Value0),
- case string:sub_word(lists:sublist(Value, 80),1,$\n) of
+ case string:nth_lexeme(lists:sublist(Value, 80),1, [$\n]) of
Value ->
%% Short string, no newlines - show all
wxStaticText:new(Panel, ?wxID_ANY, Value);
@@ -503,20 +539,22 @@ create_box(Parent, Data) ->
link_entry(Panel, Link) ->
Cursor = wxCursor:new(?wxCURSOR_HAND),
- TC = link_entry2(Panel, to_link(Link), Cursor),
+ TC = link_entry(Panel, Link, Cursor),
wxCursor:destroy(Cursor),
TC.
link_entry(Panel, Link, Cursor) ->
- link_entry2(Panel, to_link(Link), Cursor).
+ link_entry2(Panel,to_link(Link),Cursor).
link_entry2(Panel,{Target,Str},Cursor) ->
+ link_entry2(Panel,{Target,Str},Cursor,"Click to see properties for " ++ Str).
+link_entry2(Panel,{Target,Str},Cursor,ToolTipText) ->
TC = wxStaticText:new(Panel, ?wxID_ANY, Str),
wxWindow:setForegroundColour(TC,?wxBLUE),
wxWindow:setCursor(TC, Cursor),
wxWindow:connect(TC, left_down, [{userData,Target}]),
wxWindow:connect(TC, enter_window),
wxWindow:connect(TC, leave_window),
- ToolTip = wxToolTip:new("Click to see properties for " ++ Str),
+ ToolTip = wxToolTip:new(ToolTipText),
wxWindow:setToolTip(TC, ToolTip),
TC.
@@ -688,11 +726,11 @@ create_status_bar(Panel) ->
%%%-----------------------------------------------------------------
%%% Progress dialog
-define(progress_handler,cdv_progress_handler).
-display_progress_dialog(Title,Str) ->
+display_progress_dialog(Parent,Title,Str) ->
Caller = self(),
Env = wx:get_env(),
spawn_link(fun() ->
- progress_handler(Caller,Env,Title,Str)
+ progress_handler(Caller,Env,Parent,Title,Str)
end),
ok.
@@ -707,6 +745,11 @@ wait_for_progress() ->
destroy_progress_dialog() ->
report_progress(finish).
+sync_destroy_progress_dialog() ->
+ Ref = erlang:monitor(process,?progress_handler),
+ destroy_progress_dialog(),
+ receive {'DOWN',Ref,process,_,_} -> ok end.
+
report_progress(Progress) ->
case whereis(?progress_handler) of
Pid when is_pid(Pid) ->
@@ -716,31 +759,38 @@ report_progress(Progress) ->
ok
end.
-progress_handler(Caller,Env,Title,Str) ->
+progress_handler(Caller,Env,Parent,Title,Str) ->
register(?progress_handler,self()),
wx:set_env(Env),
- PD = progress_dialog(Env,Title,Str),
- try progress_loop(Title,PD,Caller)
+ PD = progress_dialog(Env,Parent,Title,Str),
+ try progress_loop(Title,PD,Caller,infinity)
catch closed -> normal end.
-progress_loop(Title,PD,Caller) ->
+progress_loop(Title,PD,Caller,Pulse) ->
receive
{progress,{ok,done}} -> % to make wait_for_progress/0 return
Caller ! continue,
- progress_loop(Title,PD,Caller);
+ progress_loop(Title,PD,Caller,Pulse);
+ {progress,{ok,start_pulse}} ->
+ update_progress_pulse(PD),
+ progress_loop(Title,PD,Caller,?pulse_timeout);
+ {progress,{ok,stop_pulse}} ->
+ progress_loop(Title,PD,Caller,infinity);
{progress,{ok,Percent}} when is_integer(Percent) ->
update_progress(PD,Percent),
- progress_loop(Title,PD,Caller);
+ progress_loop(Title,PD,Caller,Pulse);
{progress,{ok,Msg}} ->
update_progress_text(PD,Msg),
- progress_loop(Title,PD,Caller);
+ progress_loop(Title,PD,Caller,Pulse);
{progress,{error, Reason}} ->
+ {Dialog,_,_} = PD,
+ Parent = wxWindow:getParent(Dialog),
finish_progress(PD),
FailMsg =
if is_list(Reason) -> Reason;
true -> file:format_error(Reason)
end,
- display_info_dialog(PD,"Crashdump Viewer Error",FailMsg),
+ display_info_dialog(Parent,"Crashdump Viewer Error",FailMsg),
Caller ! error,
unregister(?progress_handler),
unlink(Caller);
@@ -748,32 +798,60 @@ progress_loop(Title,PD,Caller) ->
finish_progress(PD),
unregister(?progress_handler),
unlink(Caller)
+ after Pulse ->
+ update_progress_pulse(PD),
+ progress_loop(Title,PD,Caller,?pulse_timeout)
end.
-progress_dialog(_Env,Title,Str) ->
- PD = wxProgressDialog:new(Title,Str,
- [{maximum,101},
- {style,
- ?wxPD_APP_MODAL bor
- ?wxPD_SMOOTH bor
- ?wxPD_AUTO_HIDE}]),
- wxProgressDialog:setMinSize(PD,{200,-1}),
- PD.
+progress_dialog(_Env,Parent,Title,Str) ->
+ progress_dialog_new(Parent,Title,Str).
update_progress(PD,Value) ->
- try wxProgressDialog:update(PD,Value)
+ try progress_dialog_update(PD,Value)
catch _:_ -> throw(closed) %% Port or window have died
end.
update_progress_text(PD,Text) ->
- try wxProgressDialog:update(PD,0,[{newmsg,Text}])
+ try progress_dialog_update(PD,Text)
+ catch _:_ -> throw(closed) %% Port or window have died
+ end.
+update_progress_pulse(PD) ->
+ try progress_dialog_pulse(PD)
catch _:_ -> throw(closed) %% Port or window have died
end.
finish_progress(PD) ->
- wxProgressDialog:destroy(PD).
+ try progress_dialog_update(PD,100)
+ catch _:_ -> ok
+ after progress_dialog_destroy(PD)
+ end.
+
+progress_dialog_new(Parent,Title,Str) ->
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, Title,
+ [{style,?wxDEFAULT_DIALOG_STYLE}]),
+ Panel = wxPanel:new(Dialog),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ Message = wxStaticText:new(Panel, 1, Str,[{size,{220,-1}}]),
+ Gauge = wxGauge:new(Panel, 2, 100, [{style, ?wxGA_HORIZONTAL}]),
+ SizerFlags = ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT bor ?wxTOP,
+ wxSizer:add(Sizer, Message, [{flag,SizerFlags},{border,15}]),
+ wxSizer:add(Sizer, Gauge, [{flag, SizerFlags bor ?wxBOTTOM},{border,15}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxSizer:setSizeHints(Sizer, Dialog),
+ wxDialog:show(Dialog),
+ {Dialog,Message,Gauge}.
+
+progress_dialog_update({_,_,Gauge},Value) when is_integer(Value) ->
+ wxGauge:setValue(Gauge,Value);
+progress_dialog_update({_,Message,Gauge},Text) when is_list(Text) ->
+ wxGauge:setValue(Gauge,0),
+ wxStaticText:setLabel(Message,Text).
+progress_dialog_pulse({_,_,Gauge}) ->
+ wxGauge:pulse(Gauge).
+progress_dialog_destroy({Dialog,_,_}) ->
+ wxDialog:destroy(Dialog).
make_obsbin(Bin,Tab) ->
Size = byte_size(Bin),
- Preview =
+ {Preview,PreviewBitSize} =
try
%% The binary might be a unicode string, in which case we
%% don't want to split it in the middle of a grapheme
@@ -782,14 +860,14 @@ make_obsbin(Bin,Tab) ->
PB1 = string:slice(Bin,0,PL1),
PS1 = byte_size(PB1) * 8,
<<P1:PS1>> = PB1,
- P1
+ {P1,PS1}
catch _:_ ->
%% Probably not a string, so just split anywhere
PS2 = min(Size, 10) * 8,
<<P2:PS2, _/binary>> = Bin,
- P2
+ {P2,PS2}
end,
Hash = erlang:phash2(Bin),
Key = {Preview, Size, Hash},
ets:insert(Tab, {Key,Bin}),
- ['#OBSBin',Preview,Size,Hash].
+ ['#OBSBin',Preview,PreviewBitSize,Size,Hash].
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index 5adfadb16e..eddb9327f3 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -86,8 +86,8 @@ init([Notebook, Parent, Config]) ->
secs=maps:get(secs, Config, ?DISP_SECONDS)}
},
{Panel, State0}
- catch _:Err ->
- io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, erlang:get_stacktrace()]),
+ catch _:Err:Stacktrace ->
+ io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, Stacktrace]),
{stop, Err}
end.
diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl
index 5908e99e36..f7ae07fb85 100644
--- a/lib/observer/src/observer_port_wx.erl
+++ b/lib/observer/src/observer_port_wx.erl
@@ -242,6 +242,10 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL},
Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60),
{noreply, State#state{timer=Timer}};
+handle_event(#wx{obj=MoreEntry,event=#wxMouse{type=left_down},userData={more,More}}, State) ->
+ observer_lib:add_scroll_entries(MoreEntry,More),
+ {noreply, State};
+
handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) ->
observer ! {open_link, TargetPid},
{noreply, State};
diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl
index 2e5fe0bc1a..e2372cde33 100644
--- a/lib/observer/src/observer_pro_wx.erl
+++ b/lib/observer/src/observer_pro_wx.erl
@@ -27,7 +27,7 @@
handle_event/2, handle_cast/2]).
-include_lib("wx/include/wx.hrl").
--include("../include/etop.hrl").
+-include("etop.hrl").
-include("observer_defs.hrl").
-include("etop_defs.hrl").
@@ -50,6 +50,7 @@
-define(ID_TRACE_NEW, 208).
-define(ID_TRACE_ALL, 209).
-define(ID_ACCUMULATE, 210).
+-define(ID_GARBAGE_COLLECT, 211).
-define(TRACE_PIDS_STR, "Trace selected process identifiers").
-define(TRACE_NAMES_STR, "Trace selected processes, "
@@ -147,11 +148,11 @@ create_list_box(Panel, Holder) ->
ListCtrl = wxListCtrl:new(Panel, [{style, Style},
{onGetItemText,
fun(_, Row, Col) ->
- call(Holder, {get_row, self(), Row, Col})
+ safe_call(Holder, {get_row, self(), Row, Col})
end},
{onGetItemAttr,
fun(_, Item) ->
- call(Holder, {get_attr, self(), Item})
+ safe_call(Holder, {get_attr, self(), Item})
end}
]),
Li = wxListItem:new(),
@@ -208,17 +209,26 @@ start_procinfo(Pid, Frame, Opened) ->
Opened
end.
+
+safe_call(Holder, What) ->
+ case call(Holder, What, 2000) of
+ Res when is_atom(Res) -> "";
+ Res -> Res
+ end.
+
call(Holder, What) ->
+ call(Holder, What, infinity).
+
+call(Holder, What, TMO) ->
Ref = erlang:monitor(process, Holder),
Holder ! What,
receive
- {'DOWN', Ref, _, _, _} -> "";
+ {'DOWN', Ref, _, _, _} -> holder_dead;
{Holder, Res} ->
erlang:demonitor(Ref),
Res
- after 2000 ->
- io:format("Hanging call ~tp~n",[What]),
- ""
+ after TMO ->
+ timeout
end.
%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -269,7 +279,10 @@ code_change(_, _, State) ->
handle_call(get_config, _, #state{holder=Holder, timer=Timer}=State) ->
Conf = observer_lib:timer_config(Timer),
- Accum = call(Holder, {get_accum, self()}),
+ Accum = case safe_call(Holder, {get_accum, self()}) of
+ Bool when is_boolean(Bool) -> Bool;
+ _ -> false
+ end,
{reply, Conf#{acc=>Accum}, State};
handle_call(Msg, _From, State) ->
@@ -316,6 +329,9 @@ handle_event(#wx{id=?ID_KILL}, #state{right_clicked_pid=Pid, sel=Sel0}=State) ->
Sel = rm_selected(Pid,Sel0),
{noreply, State#state{sel=Sel}};
+handle_event(#wx{id=?ID_GARBAGE_COLLECT}, #state{sel={_, Pids}}=State) ->
+ _ = [rpc:call(node(Pid), erlang, garbage_collect, [Pid]) || Pid <- Pids],
+ {noreply, State};
handle_event(#wx{id=?ID_PROC},
#state{panel=Panel, right_clicked_pid=Pid, procinfo_menu_pids=Opened}=State) ->
@@ -370,6 +386,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click,
wxMenu:append(Menu, ?ID_TRACE_NAMES,
"Trace selected processes by name (all nodes)",
[{help, ?TRACE_NAMES_STR}]),
+ wxMenu:append(Menu, ?ID_GARBAGE_COLLECT, "Garbage collect processes"),
wxMenu:append(Menu, ?ID_KILL, "Kill process " ++ pid_to_list(P)),
wxWindow:popupMenu(Panel, Menu),
wxMenu:destroy(Menu),
@@ -465,7 +482,7 @@ init_table_holder(Parent, Accum0, Attrs) ->
Backend = spawn_link(node(), observer_backend, procs_info, [self()]),
Accum = case Accum0 of
true -> true;
- false -> []
+ _ -> []
end,
table_holder(#holder{parent=Parent,
info=array:new(),
@@ -572,7 +589,8 @@ change_accum(true, S0) ->
S0#holder{accum=true};
change_accum(false, S0=#holder{info=Info}) ->
self() ! refresh,
- S0#holder{accum=lists:sort(array:to_list(Info))}.
+ Accum = [{Pid, Reds} || #etop_proc_info{pid=Pid, reds=Reds} <- array:to_list(Info)],
+ S0#holder{accum=lists:sort(Accum)}.
handle_update_old(#etop_info{procinfo=ProcInfo0},
S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) ->
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index 963def958b..6a85d29c1e 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -120,6 +120,10 @@ handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages, expand_
end,
{noreply, State};
+handle_event(#wx{obj=MoreEntry,event=#wxMouse{type=left_down},userData={more,More}}, State) ->
+ observer_lib:add_scroll_entries(MoreEntry,More),
+ {noreply, State};
+
handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) ->
observer ! {open_link, TargetPid},
{noreply, State};
@@ -144,14 +148,14 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}},
observer ! {open_link, Href},
{noreply, State};
Callback ->
- [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(Rest),
+ [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = uri_string:dissect_query(Rest),
Id = {obs, {T,{list_to_integer(Key1),
list_to_integer(Key2),
list_to_integer(Key3)}}},
Opened =
case lists:keyfind(Id,1,Opened0) of
false ->
- Win = cdv_detail_wx:start_link(Id,[],Frame,Callback),
+ Win = cdv_detail_wx:start_link(Id,[],Frame,Callback,obs),
[{Id,Win}|Opened0];
{_,Win} ->
wxFrame:raise(Win),
@@ -253,8 +257,6 @@ init_stack_page(Parent, Pid) ->
[Pid, current_stacktrace])
of
{current_stacktrace,RawBt} ->
- observer_wx:try_rpc(node(Pid), erlang, process_info,
- [Pid, current_stacktrace]),
wxListCtrl:deleteAllItems(LCtrl),
wx:foldl(fun({M, F, A, Info}, Row) ->
_Item = wxListCtrl:insertItem(LCtrl, Row, ""),
@@ -454,7 +456,8 @@ local_pid_str(Pid) ->
global_pid_node_pref(Pid) ->
%% Global PID node prefix : X of <X.Y.Z>
- string:strip(string:sub_word(pid_to_list(Pid),1,$.),left,$<).
+ [NodePrefix|_] = string:lexemes(pid_to_list(Pid),"<."),
+ NodePrefix.
io_get_data(Pid) ->
Pid ! {self(), get_data_and_close},
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl
index 8127248262..2c3b46a3a1 100644
--- a/lib/observer/src/observer_trace_wx.erl
+++ b/lib/observer/src/observer_trace_wx.erl
@@ -1201,7 +1201,7 @@ make_ms(MS) ->
make_ms(Name,Term,FunStr).
make_ms(Name, Term, FunStr) ->
- #match_spec{name=Name, term=Term, str=io_lib:format("~tw", Term), func = FunStr}.
+ #match_spec{name=Name, term=Term, str=io_lib:format("~tw", [Term]), func = FunStr}.
parse_tp({tp, Mod, FAs}, State) ->
Patterns = [#tpattern{m=Mod,fa={F,A}, ms=make_ms(List)} ||
diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl
index fbcf6d7fe9..ff8c40ce77 100644
--- a/lib/observer/src/observer_traceoptions_wx.erl
+++ b/lib/observer/src/observer_traceoptions_wx.erl
@@ -536,7 +536,7 @@ ms_from_string(Str) ->
{error, List} -> throw([[Error, $\n] || {_, Error} <- List])
end
catch error:_Reason ->
- %% io:format("Bad term: ~ts~n ~tp in ~tp~n", [Str, _Reason, erlang:get_stacktrace()]),
+ %% io:format("Bad term: ~ts~n ~tp in ~tp~n", [Str, _Reason, Stacktrace]),
throw("Invalid term")
end.
diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl
index e16f3cab6b..2e387f7e74 100644
--- a/lib/observer/src/observer_tv_wx.erl
+++ b/lib/observer/src/observer_tv_wx.erl
@@ -38,13 +38,13 @@
-define(ID_SYSTEM_TABLES, 406).
-define(ID_TABLE_INFO, 407).
-define(ID_SHOW_TABLE, 408).
-
--record(opt, {type=ets,
- sys_hidden=true,
- unread_hidden=true,
- sort_key=2,
- sort_incr=true
- }).
+
+-record(opts, {type=ets,
+ sys_hidden=true,
+ unread_hidden=true}).
+
+-record(sort, {sort_incr=true,
+ sort_key=2}).
-record(state,
{
@@ -52,9 +52,9 @@
grid,
panel,
node=node(),
- opt=#opt{},
+ opts=#opts{},
+ holder,
selected,
- tabs,
timer
}).
@@ -64,8 +64,18 @@ start_link(Notebook, Parent, Config) ->
init([Notebook, Parent, Config]) ->
Panel = wxPanel:new(Notebook),
Sizer = wxBoxSizer:new(?wxVERTICAL),
- Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES,
- Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]),
+
+ Opts=#opts{type=maps:get(type, Config, ets),
+ sys_hidden=maps:get(sys_hidden, Config, true),
+ unread_hidden=maps:get(unread_hidden, Config, true)},
+
+ Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES,
+ Self = self(),
+ Attrs = observer_lib:create_attrs(),
+ Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end),
+ CBs = [{onGetItemText, fun(_, Item,Col) -> get_row(Holder, Item, Col) end},
+ {onGetItemAttr, fun(_, Item) -> get_attr(Holder, Item) end}],
+ Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style} | CBs]),
wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL},
{proportion, 1}, {border, 5}]),
wxWindow:setSizer(Panel, Sizer),
@@ -95,38 +105,26 @@ init([Notebook, Parent, Config]) ->
wxWindow:setFocus(Grid),
{Panel, #state{grid=Grid, parent=Parent, panel=Panel,
- timer=Config,
- opt=#opt{type=maps:get(type, Config, ets),
- sys_hidden=maps:get(sys_hidden, Config, true),
- unread_hidden=maps:get(unread_hidden, Config, true)}
- }}.
+ opts=Opts, timer=Config, holder=Holder}}.
handle_event(#wx{id=?ID_REFRESH},
- State = #state{node=Node, grid=Grid, opt=Opt}) ->
- Tables = get_tables(Node, Opt),
- {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables),
- Sel =/= undefined andalso wxListCtrl:ensureVisible(Grid, Sel),
- {noreply, State#state{tabs=Tabs, selected=Sel}};
+ State = #state{holder=Holder, node=Node, opts=Opts}) ->
+ Tables = get_tables(Node, Opts),
+ Holder ! {refresh, Tables},
+ {noreply, State};
handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
- State = #state{node=Node, grid=Grid,
- opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) ->
- Opt = case col2key(Col) of
- Key -> Opt0#opt{sort_incr=not Bool};
- NewKey -> Opt0#opt{sort_key=NewKey}
- end,
- Tables = get_tables(Node, Opt),
- {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables),
- wxWindow:setFocus(Grid),
- {noreply, State#state{opt=Opt, tabs=Tabs, selected=Sel}};
+ State = #state{holder=Holder}) ->
+ Holder ! {sort, Col},
+ {noreply, State};
-handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0})
+handle_event(#wx{id=Id}, State = #state{node=Node, holder=Holder, grid=Grid, opts=Opt0})
when Id >= ?ID_ETS, Id =< ?ID_SYSTEM_TABLES ->
Opt = case Id of
- ?ID_ETS -> Opt0#opt{type=ets};
- ?ID_MNESIA -> Opt0#opt{type=mnesia};
- ?ID_UNREADABLE -> Opt0#opt{unread_hidden= not Opt0#opt.unread_hidden};
- ?ID_SYSTEM_TABLES -> Opt0#opt{sys_hidden= not Opt0#opt.sys_hidden}
+ ?ID_ETS -> Opt0#opts{type=ets};
+ ?ID_MNESIA -> Opt0#opts{type=mnesia};
+ ?ID_UNREADABLE -> Opt0#opts{unread_hidden= not Opt0#opts.unread_hidden};
+ ?ID_SYSTEM_TABLES -> Opt0#opts{sys_hidden= not Opt0#opts.sys_hidden}
end,
case get_tables2(Node, Opt) of
Error = {error, _} ->
@@ -135,9 +133,9 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0})
self() ! Error,
{noreply, State};
Tables ->
- {Tabs, Sel} = update_grid(Grid, sel(State), Opt, Tables),
+ Holder ! {refresh, Tables},
wxWindow:setFocus(Grid),
- {noreply, State#state{opt=Opt, tabs=Tabs, selected=Sel}}
+ {noreply, State#state{opts=Opt}}
end;
handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) ->
@@ -146,19 +144,18 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) ->
handle_event(#wx{event=#wxList{type=command_list_item_activated,
itemIndex=Index}},
- State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs}) ->
- Table = lists:nth(Index+1, Tabs),
- case Table#tab.protection of
- private ->
- self() ! {error, "Table has 'private' protection and can not be read"};
- _ ->
- observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}])
+ State=#state{holder=Holder, node=Node, opts=#opts{type=Type}, grid=Grid}) ->
+ case get_table(Holder, Index) of
+ #tab{protection=private} ->
+ self() ! {error, "Table has 'private' protection and can not be read"};
+ #tab{}=Table ->
+ observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]);
+ _ -> ignore
end,
{noreply, State};
handle_event(#wx{event=#wxList{type=command_list_item_right_click}},
State=#state{panel=Panel}) ->
-
Menu = wxMenu:new(),
wxMenu:append(Menu, ?ID_TABLE_INFO, "Table info"),
wxMenu:append(Menu, ?ID_SHOW_TABLE, "Show Table Content"),
@@ -167,32 +164,33 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click}},
{noreply, State};
handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}},
- State) ->
+ State=#state{holder=Holder}) ->
+ Holder ! {selected, Index},
{noreply, State#state{selected=Index}};
handle_event(#wx{id=?ID_TABLE_INFO},
- State = #state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) ->
+ State = #state{holder=Holder, grid=Grid, node=Node, opts=#opts{type=Type}, selected=Sel}) ->
case Sel of
undefined ->
{noreply, State};
R when is_integer(R) ->
- Table = lists:nth(Sel+1, Tabs),
+ Table = get_table(Holder, Sel),
display_table_info(Grid, Node, Type, Table),
{noreply, State}
end;
handle_event(#wx{id=?ID_SHOW_TABLE},
- State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) ->
+ State=#state{holder=Holder, grid=Grid, node=Node, opts=#opts{type=Type}, selected=Sel}) ->
case Sel of
undefined ->
{noreply, State};
R when is_integer(R) ->
- Table = lists:nth(Sel+1, Tabs),
- case Table#tab.protection of
- private ->
+ case get_table(Holder, R) of
+ #tab{protection=private} ->
self() ! {error, "Table has 'private' protection and can not be read"};
- _ ->
- observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}])
+ #tab{}=Table ->
+ observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]);
+ _ -> ignore
end,
{noreply, State}
end;
@@ -202,14 +200,14 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL},
Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60),
{noreply, State#state{timer=Timer}};
-handle_event(Event, _State) ->
- error({unhandled_event, Event}).
+handle_event(_Event, State) ->
+ {noreply, State}.
handle_sync_event(_Event, _Obj, _State) ->
ok.
-handle_call(get_config, _, #state{timer=Timer, opt=Opt}=State) ->
- #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread} = Opt,
+handle_call(get_config, _, #state{timer=Timer, opts=Opt}=State) ->
+ #opts{type=Type, sys_hidden=Sys, unread_hidden=Unread} = Opt,
Conf0 = observer_lib:timer_config(Timer),
Conf = Conf0#{type=>Type, sys_hidden=>Sys, unread_hidden=>Unread},
{reply, Conf, State};
@@ -220,50 +218,68 @@ handle_call(Event, From, _State) ->
handle_cast(Event, _State) ->
error({unhandled_cast, Event}).
-handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt,
- tabs=OldTabs}) ->
- case get_tables(Node, Opt) of
- OldTabs ->
- %% no change
- {noreply, State};
- Tables ->
- {Tabs, Sel} = update_grid(Grid, sel(State), Opt, Tables),
- Sel =/= undefined andalso wxListCtrl:ensureVisible(Grid, Sel),
- {noreply, State#state{tabs=Tabs, selected=Sel}}
- end;
+handle_info(refresh_interval, State = #state{holder=Holder, node=Node, opts=Opt}) ->
+ Tables = get_tables(Node, Opt),
+ Holder ! {refresh, Tables},
+ {noreply, State};
-handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt0,
- timer=Timer0}) ->
- {Tables, Opt} = case Opt0#opt.type =:= mnesia andalso get_tables2(Node, Opt0) of
+handle_info({active, Node}, State = #state{parent=Parent, holder=Holder, grid=Grid,
+ opts=Opt0, timer=Timer0}) ->
+ {Tables, Opt} = case Opt0#opts.type =:= mnesia andalso get_tables2(Node, Opt0) of
Ts when is_list(Ts) ->
{Ts, Opt0};
_ -> % false or error getting mnesia tables
- Opt1 = Opt0#opt{type=ets},
+ Opt1 = Opt0#opts{type=ets},
{get_tables(Node, Opt1), Opt1}
end,
- {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables),
+ Holder ! {refresh, Tables},
wxWindow:setFocus(Grid),
create_menus(Parent, Opt),
Timer = observer_lib:start_timer(Timer0, 10),
- {noreply, State#state{node=Node, tabs=Tabs, timer=Timer, opt=Opt, selected=Sel}};
+ {noreply, State#state{node=Node, timer=Timer, opts=Opt}};
handle_info(not_active, State = #state{timer = Timer0}) ->
Timer = observer_lib:stop_timer(Timer0),
{noreply, State#state{timer=Timer}};
-handle_info({error, Error}, #state{panel=Panel,opt=Opt}=State) ->
+handle_info({error, Error}, #state{panel=Panel,opts=Opt}=State) ->
Str = io_lib:format("ERROR: ~ts~n",[Error]),
observer_lib:display_info_dialog(Panel,Str),
- case Opt#opt.type of
+ case Opt#opts.type of
mnesia -> wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true);
_ -> ok
end,
- {noreply, State#state{opt=Opt#opt{type=ets}}};
+ {noreply, State#state{opts=Opt#opts{type=ets}}};
+
+handle_info({refresh, Min, Min}, State = #state{grid=Grid}) ->
+ wxListCtrl:setItemCount(Grid, Min+1),
+ wxListCtrl:refreshItem(Grid, Min), %% Avoid assert in wx below if Max is 0
+ observer_wx:set_status(io_lib:format("Tables: ~w", [Min+1])),
+ {noreply, State};
+handle_info({refresh, Min, Max}, State = #state{grid=Grid}) ->
+ wxListCtrl:setItemCount(Grid, Max+1),
+ Max > 0 andalso wxListCtrl:refreshItems(Grid, Min, Max),
+ observer_wx:set_status(io_lib:format("Tables: ~w", [Max+1])),
+ {noreply, State};
+
+handle_info({selected, New, Size}, #state{grid=Grid, selected=Old} = State) ->
+ if
+ is_integer(Old), Old < Size ->
+ wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_SELECTED);
+ true -> ignore
+ end,
+ if is_integer(New) ->
+ wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_SELECTED),
+ wxListCtrl:ensureVisible(Grid, New);
+ true -> ignore
+ end,
+ {noreply, State#state{selected=New}};
handle_info(_Event, State) ->
{noreply, State}.
-terminate(_Event, _State) ->
+terminate(_Event, #state{holder=Holder}) ->
+ Holder ! stop,
ok.
code_change(_, _, State) ->
@@ -271,7 +287,7 @@ code_change(_, _, State) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-create_menus(Parent, #opt{sys_hidden=Sys, unread_hidden=UnR, type=Type}) ->
+create_menus(Parent, #opts{sys_hidden=Sys, unread_hidden=UnR, type=Type}) ->
MenuEntries = [{"View",
[#create_menu{id = ?ID_TABLE_INFO, text = "Table information\tCtrl-I"},
separator,
@@ -298,7 +314,7 @@ get_tables(Node, Opts) ->
Res ->
Res
end.
-get_tables2(Node, #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread}) ->
+get_tables2(Node, #opts{type=Type, sys_hidden=Sys, unread_hidden=Unread}) ->
Args = [Type, [{sys_hidden,Sys}, {unread_hidden,Unread}]],
case rpc:call(Node, observer_backend, get_table_list, Args) of
{badrpc, Error} ->
@@ -386,49 +402,134 @@ list_to_strings([A]) -> integer_to_list(A);
list_to_strings([A|B]) ->
integer_to_list(A) ++ " ," ++ list_to_strings(B).
-update_grid(Grid, Selected, Opt, Tables) ->
- wx:batch(fun() -> update_grid2(Grid, Selected, Opt, Tables) end).
-
-update_grid2(Grid, {SelName,SelId}, #opt{sort_key=Sort,sort_incr=Dir}, Tables) ->
- wxListCtrl:deleteAllItems(Grid),
- Update =
- fun(#tab{name = Name, id = Id, owner = Owner, size = Size, memory = Memory,
- protection = Protection, reg_name = RegName},
- {Row, Sel}) ->
- _Item = wxListCtrl:insertItem(Grid, Row, ""),
- if (Row rem 2) =:= 0 ->
- wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN);
- true -> ignore
- end,
- if Protection == private ->
- wxListCtrl:setItemTextColour(Grid, Row, {200,130,50});
- true -> ignore
- end,
-
- lists:foreach(fun({_, ignore}) -> ignore;
- ({Col, Val}) ->
- wxListCtrl:setItem(Grid, Row, Col, observer_lib:to_str(Val))
- end,
- [{0,Name}, {1,Size}, {2, Memory div 1024},
- {3,Owner}, {4,RegName}, {5,Id}]),
- if SelName =:= Name, SelId =:= Id ->
- wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED),
- {Row+1, Row};
- true ->
- wxListCtrl:setItemState(Grid, Row, 0, ?wxLIST_STATE_SELECTED),
- {Row+1, Sel}
- end
- end,
- ProcInfo = case Dir of
- false -> lists:reverse(lists:keysort(Sort, Tables));
- true -> lists:keysort(Sort, Tables)
- end,
- {_, Sel} = lists:foldl(Update, {0, undefined}, ProcInfo),
- {ProcInfo, Sel}.
-
-sel(#state{selected=Sel, tabs=Tabs}) ->
- try lists:nth(Sel+1, Tabs) of
- #tab{name=Name, id=Id} -> {Name, Id}
- catch _:_ ->
- {undefined, undefined}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Table holder needs to be in a separate process otherwise
+%% the callback get_row/3 may deadlock if the process do
+%% wx calls when callback is invoked.
+
+get_table(Table, Item) ->
+ get_row(Table, Item, all).
+
+get_row(Table, Item, Column) ->
+ Ref = erlang:monitor(process, Table),
+ Table ! {get_row, self(), Item, Column},
+ receive
+ {'DOWN', Ref, _, _, _} -> "";
+ {Table, Res} ->
+ erlang:demonitor(Ref),
+ Res
end.
+
+get_attr(Table, Item) ->
+ Ref = erlang:monitor(process, Table),
+ Table ! {get_attr, self(), Item},
+ receive
+ {'DOWN', Ref, _, _, _} -> wx:null();
+ {Table, Res} ->
+ erlang:demonitor(Ref),
+ Res
+ end.
+
+-record(holder, {node, parent, pid,
+ tabs=array:new(),
+ sort=#sort{},
+ attrs,
+ sel
+ }).
+
+init_table_holder(Parent, Attrs) ->
+ Parent ! refresh,
+ table_holder(#holder{node=node(), parent=Parent, attrs=Attrs}).
+
+table_holder(S0 = #holder{parent=Parent, tabs=Tabs0, sel=Sel0}) ->
+ receive
+ {get_attr, From, Row} ->
+ get_attr(From, Row, S0),
+ table_holder(S0);
+ {get_row, From, Row, Col} ->
+ get_row(From, Row, Col, Tabs0),
+ table_holder(S0);
+ {sort, Col} ->
+ STab = get_sel(Sel0, Tabs0),
+ Parent ! {refresh, 0, array:size(Tabs0)-1},
+ S1 = sort(col2key(Col), S0),
+ Sel = sel_idx(STab, S1#holder.tabs),
+ Parent ! {selected, Sel, array:size(Tabs0)},
+ table_holder(S1#holder{sel=Sel});
+ {refresh, Tabs1} ->
+ STab = get_sel(Sel0, Tabs0),
+ Tabs = case S0#holder.sort of
+ #sort{sort_incr=false, sort_key=Col} ->
+ array:from_list(lists:reverse(lists:keysort(Col, Tabs1)));
+ #sort{sort_key=Col} ->
+ array:from_list(lists:keysort(Col, Tabs1))
+ end,
+ Parent ! {refresh, 0, array:size(Tabs)-1},
+ Sel = sel_idx(STab, Tabs),
+ Parent ! {selected, Sel,array:size(Tabs)},
+ table_holder(S0#holder{tabs=Tabs, sel=Sel});
+ {selected, Sel} ->
+ table_holder(S0#holder{sel=Sel});
+ stop ->
+ ok;
+ What ->
+ io:format("Table holder got ~tp~n",[What]),
+ Parent ! {refresh, 0, array:size(Tabs0)-1},
+ table_holder(S0)
+ end.
+
+get_sel(undefined, _Tabs) ->
+ undefined;
+get_sel(Idx, Tabs) ->
+ array:get(Idx, Tabs).
+
+sel_idx(undefined, _Tabs) ->
+ undefined;
+sel_idx(Tab, Tabs) ->
+ Find = fun(Idx, C, Acc) -> C =:= Tab andalso throw({found, Idx}), Acc end,
+ try array:foldl(Find, undefined, Tabs)
+ catch {found, Idx} -> Idx
+ end.
+
+sort(Col, #holder{sort=#sort{sort_key=Col, sort_incr=Incr}=S, tabs=Table0}=H) ->
+ Table = lists:reverse(array:to_list(Table0)),
+ H#holder{sort=S#sort{sort_incr=(not Incr)},
+ tabs=array:from_list(Table)};
+sort(Col, #holder{sort=#sort{sort_incr=Incr}=S, tabs=Table0}=H) ->
+ Table = case Incr of
+ false -> lists:reverse(lists:keysort(Col, array:to_list(Table0)));
+ true -> lists:keysort(Col, array:to_list(Table0))
+ end,
+ H#holder{sort=S#sort{sort_key=Col},
+ tabs=array:from_list(Table)}.
+
+get_row(From, Row, Col, Table) ->
+ Object = array:get(Row, Table),
+ From ! {self(), get_col(Col, Object)}.
+
+get_col(all, Rec) ->
+ Rec;
+get_col(2, #tab{}=Rec) -> %% Memory in kB
+ observer_lib:to_str(element(#tab.memory, Rec) div 1024);
+get_col(Col, #tab{}=Rec) ->
+ case element(col2key(Col), Rec) of
+ ignore -> "";
+ Val -> observer_lib:to_str(Val)
+ end;
+get_col(_, _) ->
+ "".
+
+get_attr(From, Row, #holder{tabs=Tabs, attrs=Attrs}) ->
+ EvenOdd = case (Row rem 2) > 0 of
+ true -> Attrs#attrs.odd;
+ false -> Attrs#attrs.even
+ end,
+ What = try array:get(Row, Tabs) of
+ #tab{protection=private} ->
+ Attrs#attrs.deleted;
+ _ ->
+ EvenOdd
+ catch _ ->
+ EvenOdd
+ end,
+ From ! {self(), What}.
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index be93b1d5f1..453e3bdc2d 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -732,7 +732,7 @@ get_nodes() ->
{Nodes, lists:reverse(Menues)}.
epmd_nodes(Names) ->
- [_, Host] = string:tokens(atom_to_list(node()),"@"),
+ [_, Host] = string:lexemes(atom_to_list(node()),"@"),
[list_to_atom(Name ++ [$@|Host]) || {Name, _} <- Names].
update_node_list(State = #state{menubar=MenuBar}) ->
diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl
index 940fdc9818..29a572d7fe 100644
--- a/lib/observer/src/ttb.erl
+++ b/lib/observer/src/ttb.erl
@@ -100,7 +100,7 @@ do_tracer(Clients,PI,Traci) ->
{ok, H} = inet:gethostname(),
H;
_ ->
- [_,H] = string:tokens(atom_to_list(N),"@"),
+ [_,H] = string:lexemes(atom_to_list(N),"@"),
H
end,
case catch dbg:tracer(N,port,dbg:trace_port(ip,IpPortSpec)) of
@@ -976,7 +976,7 @@ decode_filename(3,Bin,latin1) ->
File.
host(Node) ->
- [_name,Host] = string:tokens(atom_to_list(Node),"@"),
+ [_name,Host] = string:lexemes(atom_to_list(Node),"@"),
Host.
wait_for_fetch([]) ->
@@ -1064,7 +1064,7 @@ collect_files(Dirs) ->
lists:map(fun(Dir) ->
MetaFiles = filelib:wildcard(filename:join(Dir,"*.ti")),
lists:map(fun(M) ->
- Sub = string:left(M,length(M)-3),
+ Sub = filename:rootname(M,".ti"),
case filelib:is_file(Sub) of
true -> Sub;
false -> Sub++".*.wrp"
diff --git a/lib/observer/test/Makefile b/lib/observer/test/Makefile
index fcb1b73911..a44e54fc52 100644
--- a/lib/observer/test/Makefile
+++ b/lib/observer/test/Makefile
@@ -47,7 +47,7 @@ RELSYSDIR = $(RELEASE_PATH)/observer_test
# FLAGS
# ----------------------------------------------------
ERL_MAKE_FLAGS +=
-ERL_COMPILE_FLAGS += +nowarn_export_all
+ERL_COMPILE_FLAGS += +warnings_as_errors +nowarn_export_all
EBIN = .
diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl
index f37d9057cb..b5e94a893a 100644
--- a/lib/observer/test/crashdump_helper.erl
+++ b/lib/observer/test/crashdump_helper.erl
@@ -19,7 +19,9 @@
%%
-module(crashdump_helper).
--export([n1_proc/2,remote_proc/2]).
+-export([n1_proc/2,remote_proc/2,
+ dump_maps/0,create_maps/0,
+ create_binaries/0,create_sub_binaries/1]).
-compile(r18).
-include_lib("common_test/include/ct.hrl").
@@ -60,7 +62,9 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) ->
put(ref,Ref),
put(pid,Pid),
put(bin,Bin),
+ put(bins,create_binaries()),
put(sub_bin,SubBin),
+ put(sub_bins,create_sub_binaries(get(bins))),
put(bignum,83974938738373873),
put(neg_bignum,-38748762783736367),
put(ext_pid,Pid2),
@@ -79,6 +83,7 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) ->
link(OtherPid), % own node
link(Pid2), % external node
erlang:monitor(process,OtherPid),
+ erlang:monitor(process,init), % named process
erlang:monitor(process,Pid2),
code:load_file(?MODULE),
@@ -92,3 +97,49 @@ remote_proc(P1,Creator) ->
Creator ! {self(),done},
receive after infinity -> ok end
end).
+
+create_binaries() ->
+ Sizes = lists:seq(60, 70) ++ lists:seq(120, 140),
+ [begin
+ <<H:16/unit:8>> = erlang:md5(<<Size:32>>),
+ Data = ((H bsl (8*150)) div (H+7919)),
+ <<Data:Size/unit:8>>
+ end || Size <- Sizes].
+
+create_sub_binaries(Bins) ->
+ [create_sub_binary(Bin, Start, LenSub) ||
+ Bin <- Bins,
+ Start <- [0,1,2,3,4,5,10,22],
+ LenSub <- [0,1,2,3,4,6,9]].
+
+create_sub_binary(Bin, Start, LenSub) ->
+ Len = byte_size(Bin) - LenSub - Start,
+ <<_:Start/bytes,Sub:Len/bytes,_/bytes>> = Bin,
+ Sub.
+
+%%%
+%%% Test dumping of maps. Dumping of maps only from OTP 20.2.
+%%%
+
+dump_maps() ->
+ Parent = self(),
+ F = fun() ->
+ register(aaaaaaaa_maps, self()),
+ put(maps, create_maps()),
+ Parent ! {self(),done},
+ receive _ -> ok end
+ end,
+ Pid = spawn_link(F),
+ receive
+ {Pid,done} ->
+ {ok,Pid}
+ end.
+
+create_maps() ->
+ Map0 = maps:from_list([{I,[I,I+1]} || I <- lists:seq(1, 40)]),
+ Map1 = maps:from_list([{I,{a,[I,I*I],{}}} || I <- lists:seq(1, 100)]),
+ Map2 = maps:from_list([{{I},(I*I) bsl 24} || I <- lists:seq(1, 10000)]),
+ Map3 = lists:foldl(fun(I, A) ->
+ A#{I=>I*I}
+ end, Map2, lists:seq(-10, 0)),
+ #{a=>Map0,b=>Map1,c=>Map2,d=>Map3,e=>#{}}.
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index 77cf086d4b..8bd7ad387b 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -25,7 +25,7 @@
%% Test functions
-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2,
start_stop/1,load_file/1,not_found_items/1,
- non_existing/1,not_a_crashdump/1,old_crashdump/1]).
+ non_existing/1,not_a_crashdump/1,old_crashdump/1,new_crashdump/1]).
-export([init_per_suite/1, end_per_suite/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -76,13 +76,14 @@ end_per_testcase(Case, Config) ->
end,
ok.
-suite() -> [{ct_hooks,[ts_install_cth]}].
+suite() -> [].
all() ->
[start_stop,
non_existing,
not_a_crashdump,
old_crashdump,
+ new_crashdump,
load_file,
not_found_items
].
@@ -212,6 +213,25 @@ not_a_crashdump(Config) when is_list(Config) ->
ok = crashdump_viewer:stop().
+%% Try to load a file with newer version than this crashdump viewer can handle
+new_crashdump(Config) ->
+ Dump = hd(?config(dumps,Config)),
+ ok = start_backend(Dump),
+ {ok,{MaxVsn,CurrentVsn}} = crashdump_viewer:get_dump_versions(),
+ if MaxVsn =/= CurrentVsn ->
+ ct:fail("Current dump version is not equal to cdv's max version");
+ true ->
+ ok
+ end,
+ ok = crashdump_viewer:stop(),
+ NewerVsn = lists:join($.,[integer_to_list(X+1) || X <- MaxVsn]),
+ PrivDir = ?config(priv_dir,Config),
+ NewDump = filename:join(PrivDir,"new_erl_crash.dump"),
+ ok = file:write_file(NewDump,"=erl_crash_dump:"++NewerVsn++"\n"),
+ {error, Reason} = start_backend(NewDump),
+ "This Crashdump Viewer is too old" ++_ = Reason,
+ ok = crashdump_viewer:stop().
+
%% Load files into the tool and view all pages
load_file(Config) when is_list(Config) ->
case ?t:is_debug() of
@@ -328,7 +348,7 @@ browse_file(File) ->
io:format(" info read",[]),
- lookat_all_pids(Procs),
+ lookat_all_pids(Procs,is_truncated(File),incomplete_allowed(File)),
io:format(" pids ok",[]),
lookat_all_ports(Ports),
io:format(" ports ok",[]),
@@ -339,6 +359,21 @@ browse_file(File) ->
Procs. % used as second arg to special/2
+is_truncated(File) ->
+ case filename:extension(File) of
+ ".trunc"++_ ->
+ true;
+ _ ->
+ false
+ end.
+
+incomplete_allowed(File) ->
+ %% Incomplete heap is allowed for native libs, since some literals
+ %% are not dumped - and for pre OTP-20 (really pre 20.2) releases,
+ %% since literals were not dumped at all then.
+ Rel = get_rel_from_dump_name(File),
+ Rel < 20 orelse test_server:is_native(lists).
+
special(File,Procs) ->
case filename:extension(File) of
".full_dist" ->
@@ -364,6 +399,17 @@ special(File,Procs) ->
crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
io:format(" expand binary ok",[]),
+ Binaries = crashdump_helper:create_binaries(),
+ verify_binaries(Binaries, proplists:get_value(bins,Dict)),
+ io:format(" binaries ok",[]),
+
+ SubBinaries = crashdump_helper:create_sub_binaries(Binaries),
+ verify_binaries(SubBinaries, proplists:get_value(sub_bins,Dict)),
+ io:format(" sub binaries ok",[]),
+
+ #proc{last_calls=LastCalls} = ProcDetails,
+ true = length(LastCalls) =< 4,
+
['#CDVPid',X1,Y1,Z1] = proplists:get_value(ext_pid,Dict),
ChannelStr1 = integer_to_list(X1),
ExtPid =
@@ -413,19 +459,113 @@ special(File,Procs) ->
old_attrib=undefined,
old_comp_info=undefined}=Mod2,
ok;
- %% ".strangemodname" ->
- %% {ok,Mods,[]} = crashdump_viewer:loaded_modules(),
- %% lookat_all_mods(Mods),
- %% ok;
- %% ".sort" ->
- %% %% sort ports, atoms and modules ????
- %% ok;
- %% ".trunc" ->
- %% %% ????
- %% ok;
- ".trunc.bytes" ->
+ ".trunc_mod" ->
+ ModName = atom_to_list(?helper_mod),
+ {ok,Mod=#loaded_mod{},[TW]} =
+ crashdump_viewer:loaded_mod_details(ModName),
+ "WARNING: The crash dump is truncated here."++_ = TW,
+ #loaded_mod{current_attrib=CA,current_comp_info=CCI,
+ old_attrib=OA,old_comp_info=OCI} = Mod,
+ case lists:all(fun(undefined) ->
+ true;
+ (S) when is_list(S) ->
+ io_lib:printable_unicode_list(lists:flatten(S));
+ (_) -> false
+ end,
+ [CA,CCI,OA,OCI]) of
+ true ->
+ ok;
+ false ->
+ ct:fail({should_be_printable_strings_or_undefined,
+ {CA,CCI,OA,OCI}})
+ end,
+ ok;
+ ".trunc_bin1" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:"
+ %% i.e. no binary exist in the dump
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ %%WarnIncompleteHeap = ["WARNING: This process has an incomplete heap. Some information might be missing."],
+ {ok,ProcDetails=#proc{},[]} =
+ crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ '#CDVNonexistingBinary' = proplists:get_value(bin,Dict),
+ '#CDVNonexistingBinary' = proplists:get_value(sub_bin,Dict),
+
+ io:format(" nonexisting binaries ok",[]),
+ ok;
+ ".trunc_bin2" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:Addr\n
+ %% Size"
+ %% i.e. binaries are truncated
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
+
+ io:format(" expand truncated binary ok",[]),
+ ok;
+ ".trunc_bin3" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:Addr\n
+ %% Size:"
+ %% i.e. same as 'trunc_bin2', except the colon exists also
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
+
+ io:format(" expand truncated binary ok",[]),
+ ok;
+ ".trunc_bin4" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:Addr\n
+ %% Size:BinaryMissinOneByte"
+ %% i.e. the full binary is truncated, but the sub binary is complete
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ io:format(" expand truncated binary ok",[]),
+ ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict),
+ {ok,<<_:SSize/binary>>} =
+ crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
+ io:format(" expand complete sub binary ok",[]),
+
+ ok;
+ ".trunc_bytes" ->
{ok,_,[TW]} = crashdump_viewer:general_info(),
{match,_} = re:run(TW,"CRASH DUMP SIZE LIMIT REACHED"),
+ io:format(" size limit information ok",[]),
ok;
".unicode" ->
#proc{pid=Pid0} =
@@ -439,20 +579,56 @@ special(File,Procs) ->
io:format(" unicode table name ok",[]),
ok;
+ ".maps" ->
+ %% I registered a process as aaaaaaaa_maps in the map dump
+ %% to make sure it will be the first in the list when sorted
+ %% on names.
+ [#proc{pid=Pid0,name=Name}|_Rest] = lists:keysort(#proc.name,Procs),
+ "aaaaaaaa_maps" = Name,
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+ %% io:format("~p\n", [Dict]),
+ Maps = crashdump_helper:create_maps(),
+ Maps = proplists:get_value(maps,Dict),
+ io:format(" maps ok",[]),
+ ok;
_ ->
ok
end,
ok.
+verify_binaries([H|T1], [H|T2]) ->
+ %% Heap binary.
+ verify_binaries(T1, T2);
+verify_binaries([Bin|T1], [['#CDVBin',Offset,Size,Pos]|T2]) ->
+ %% Refc binary.
+ {ok,<<Bin:Size/binary>>} = crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ verify_binaries(T1, T2);
+verify_binaries([], []) ->
+ ok.
-lookat_all_pids([]) ->
+lookat_all_pids([],_,_) ->
ok;
-lookat_all_pids([#proc{pid=Pid0}|Procs]) ->
+lookat_all_pids([#proc{pid=Pid0}|Procs],TruncAllowed,IncompAllowed) ->
Pid = pid_to_list(Pid0),
- {ok,_ProcDetails=#proc{},_ProcTW} = crashdump_viewer:proc_details(Pid),
- {ok,_Ets,_EtsTW} = crashdump_viewer:ets_tables(Pid),
- {ok,_Timers,_TimersTW} = crashdump_viewer:timers(Pid),
- lookat_all_pids(Procs).
+ {ok,_ProcDetails=#proc{},ProcTW} = crashdump_viewer:proc_details(Pid),
+ {ok,_Ets,EtsTW} = crashdump_viewer:ets_tables(Pid),
+ {ok,_Timers,TimersTW} = crashdump_viewer:timers(Pid),
+ case {ProcTW,EtsTW,TimersTW} of
+ {[],[],[]} ->
+ ok;
+ {["WARNING: This process has an incomplete heap."++_],[],[]}
+ when IncompAllowed ->
+ ok; % native libs, literals might not be included in dump
+ _ when TruncAllowed ->
+ ok; % truncated dump
+ TWs ->
+ ct:fail({unexpected_warning,TWs})
+ end,
+ lookat_all_pids(Procs,TruncAllowed,IncompAllowed).
lookat_all_ports([]) ->
ok;
@@ -498,18 +674,64 @@ do_create_dumps(DataDir,Rel) ->
end,
case Rel of
current ->
- CD3 = dump_with_args(DataDir,Rel,"instr","+Mim true"),
+ CD3 = dump_with_args(DataDir,Rel,"instr","+Muatags true"),
CD4 = dump_with_strange_module_name(DataDir,Rel,"strangemodname"),
- Bytes = rand:uniform(300000) + 100,
- CD5 = dump_with_args(DataDir,Rel,"trunc.bytes",
- "-env ERL_CRASH_DUMP_BYTES " ++
- integer_to_list(Bytes)),
+ CD5 = dump_with_size_limit_reached(DataDir,Rel,"trunc_bytes"),
CD6 = dump_with_unicode_atoms(DataDir,Rel,"unicode"),
- {[CD1,CD2,CD3,CD4,CD5,CD6], DosDump};
+ CD7 = dump_with_maps(DataDir,Rel,"maps"),
+ TruncDumpMod = truncate_dump_mod(CD1),
+ TruncatedDumpsBinary = truncate_dump_binary(CD1),
+ {[CD1,CD2,CD3,CD4,CD5,CD6,CD7,TruncDumpMod|TruncatedDumpsBinary],
+ DosDump};
_ ->
{[CD1,CD2], DosDump}
end.
+truncate_dump_mod(File) ->
+ {ok,Bin} = file:read_file(File),
+ ModNameBin = atom_to_binary(?helper_mod,latin1),
+ NewLine = case os:type() of
+ {win32,_} -> <<"\r\n">>;
+ _ -> <<"\n">>
+ end,
+ RE = <<NewLine/binary,"=mod:",ModNameBin/binary,
+ NewLine/binary,"Current size: [0-9]*",
+ NewLine/binary,"Current attributes: ...">>,
+ {match,[{Pos,Len}]} = re:run(Bin,RE),
+ Size = Pos + Len,
+ <<Truncated:Size/binary,_/binary>> = Bin,
+ DumpName = filename:rootname(File) ++ ".trunc_mod",
+ file:write_file(DumpName,Truncated),
+ DumpName.
+
+truncate_dump_binary(File) ->
+ {ok,Bin} = file:read_file(File),
+ BinTag = <<"\n=binary:">>,
+ Colon = <<":">>,
+ NewLine = case os:type() of
+ {win32,_} -> <<"\r\n">>;
+ _ -> <<"\n">>
+ end,
+ %% Split after "our binary" created by crashdump_helper
+ %% (it may not be the first binary).
+ RE = <<"\n=binary:(?=[0-9A-Z]+",NewLine/binary,"FF:AQID)">>,
+ [StartBin,AfterTag] = re:split(Bin,RE,[{parts,2}]),
+ [AddrAndSize,BinaryAndRest] = binary:split(AfterTag,Colon),
+ [Binary,_Rest] = binary:split(BinaryAndRest,NewLine),
+ TruncSize = byte_size(Binary) - 2,
+ <<TruncBinary:TruncSize/binary,_/binary>> = Binary,
+ TruncName = filename:rootname(File) ++ ".trunc_bin",
+ write_trunc_files(TruncName,StartBin,
+ [BinTag,AddrAndSize,Colon,TruncBinary],1).
+
+write_trunc_files(TruncName0,Bin,[Part|Parts],N) ->
+ TruncName = TruncName0++integer_to_list(N),
+ Bin1 = <<Bin/binary,Part/binary>>,
+ ok = file:write_file(TruncName,Bin1),
+ [TruncName|write_trunc_files(TruncName0,Bin1,Parts,N+1)];
+write_trunc_files(_,_,[],_) ->
+ [].
+
%% Create a dump which has three visible nodes, one hidden and one
%% not connected node, and with monitors and links between nodes.
@@ -586,6 +808,28 @@ dump_with_strange_module_name(DataDir,Rel,DumpName) ->
?t:stop_node(n1),
CD.
+dump_with_size_limit_reached(DataDir,Rel,DumpName) ->
+ Tmp = dump_with_args(DataDir,Rel,DumpName,""),
+ {ok,#file_info{size=Max}} = file:read_file_info(Tmp),
+ ok = file:delete(Tmp),
+ dump_with_size_limit_reached(DataDir,Rel,DumpName,Max).
+
+dump_with_size_limit_reached(DataDir,Rel,DumpName,Max) ->
+ Bytes = max(15,rand:uniform(Max)),
+ CD = dump_with_args(DataDir,Rel,DumpName,
+ "-env ERL_CRASH_DUMP_BYTES " ++
+ integer_to_list(Bytes)),
+ {ok,#file_info{size=Size}} = file:read_file_info(CD),
+ if Size =< Bytes ->
+ %% This means that the dump was actually smaller than the
+ %% randomly selected truncation size, so we'll just do it
+ %% again with a smaller number
+ ok = file:delete(CD),
+ dump_with_size_limit_reached(DataDir,Rel,DumpName,Size-3);
+ true ->
+ CD
+ end.
+
dump_with_unicode_atoms(DataDir,Rel,DumpName) ->
Opt = rel_opt(Rel),
Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"",
@@ -596,6 +840,16 @@ dump_with_unicode_atoms(DataDir,Rel,DumpName) ->
?t:stop_node(n1),
CD.
+dump_with_maps(DataDir,Rel,DumpName) ->
+ Opt = rel_opt(Rel),
+ Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"",
+ PzOpt = [{args,Pz}],
+ {ok,N1} = ?t:start_node(n1,peer,Opt ++ PzOpt),
+ {ok,_Pid} = rpc:call(N1,crashdump_helper,dump_maps,[]),
+ CD = dump(N1,DataDir,Rel,DumpName),
+ ?t:stop_node(n1),
+ CD.
+
dump(Node,DataDir,Rel,DumpName) ->
Crashdump = filename:join(DataDir, dump_prefix(Rel)++DumpName),
rpc:call(Node,os,putenv,["ERL_CRASH_DUMP",Crashdump]),
@@ -648,6 +902,11 @@ dump_prefix(current) ->
dump_prefix(Rel) ->
lists:concat(["r",Rel,"_dump."]).
+get_rel_from_dump_name(File) ->
+ Name = filename:basename(File),
+ ["r"++Rel|_] = string:split(Name,"_"),
+ list_to_integer(Rel).
+
compat_rel(current) ->
"";
compat_rel(Rel) ->
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index 41726b1521..fd4f93f662 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -113,8 +113,14 @@ appup_file(Config) when is_list(Config) ->
basic(suite) -> [];
basic(doc) -> [""];
basic(Config) when is_list(Config) ->
- timer:send_after(100, "foobar"), %% Otherwise the timer server gets added to procs
+ %% Start these before
+ wx:new(),
+ wx:destroy(),
+ timer:send_after(100, "foobar"),
+ {foo, node@machine} ! dummy_msg, %% start distribution stuff
+ %% Otherwise ever lasting servers gets added to procs
ProcsBefore = processes(),
+ ProcInfoBefore = [{P,process_info(P)} || P <- ProcsBefore],
NumProcsBefore = length(ProcsBefore),
ok = observer:start(),
@@ -145,8 +151,10 @@ basic(Config) when is_list(Config) ->
ProcsAfter = processes(),
NumProcsAfter = length(ProcsAfter),
if NumProcsAfter=/=NumProcsBefore ->
+ BeforeNotAfter = ProcsBefore -- ProcsAfter,
ct:log("Before but not after:~n~p~n",
- [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]),
+ [[{P,I} || {P,I} <- ProcInfoBefore,
+ lists:member(P,BeforeNotAfter)]]),
ct:log("After but not before:~n~p~n",
[[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]),
ct:fail("leaking processes");
@@ -304,10 +312,10 @@ table_win(Config) when is_list(Config) ->
%% Test PR-1296/OTP-14151
%% Clicking a link to a port before the port tab has been activated the
%% first time crashes observer.
-port_win_when_tab_not_initiated(Config) ->
+port_win_when_tab_not_initiated(_Config) ->
{ok,Port} = gen_tcp:listen(0,[]),
ok = observer:start(),
- Notebook = setup_whitebox_testing(),
+ _Notebook = setup_whitebox_testing(),
observer ! {open_link,erlang:port_to_list(Port)},
timer:sleep(1000),
observer:stop(),
diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl
index c06ec21f36..ed62efbb58 100644
--- a/lib/observer/test/ttb_SUITE.erl
+++ b/lib/observer/test/ttb_SUITE.erl
@@ -222,7 +222,7 @@ file_fetch(Config) when is_list(Config) ->
?line ?t:capture_stop(),
?line [StoreString] = ?t:capture_get(),
?line UploadDir =
- lists:last(string:tokens(lists:flatten(StoreString),"$ \n")),
+ lists:last(string:lexemes(lists:flatten(StoreString),"$ \n")),
%% check that files are no longer in original directories...
?line ok = check_gone(ThisDir,atom_to_list(Node)++"-file_fetch"),
@@ -778,37 +778,37 @@ otp_4967_2(suite) ->
otp_4967_2(doc) ->
["OTP-4967: Trace message sent to {Name, Node}"];
otp_4967_2(Config) when is_list(Config) ->
- io:format("1: ~p",[now()]),
+ io:format("1: ~p",[erlang:timestamp()]),
?line Privdir = priv_dir(Config),
- io:format("2: ~p",[now()]),
+ io:format("2: ~p",[erlang:timestamp()]),
?line File = filename:join(Privdir,"otp_4967"),
- io:format("3: ~p",[now()]),
+ io:format("3: ~p",[erlang:timestamp()]),
?line S = self(),
- io:format("4: ~p",[now()]),
+ io:format("4: ~p",[erlang:timestamp()]),
?line {ok,[Node]} =
ttb:tracer(node(),[{file, File},
{handler,{fun myhandler/4, S}}]),
- io:format("5: ~p",[now()]),
+ io:format("5: ~p",[erlang:timestamp()]),
%% Test that delayed registration of a process works.
receive after 200 -> ok end,
?line register(otp_4967,self()),
- io:format("6: ~p",[now()]),
+ io:format("6: ~p",[erlang:timestamp()]),
?line {ok,[{S,[{matched,Node,1}]}]} = ttb:p(self(),s),
- io:format("7: ~p",[now()]),
+ io:format("7: ~p",[erlang:timestamp()]),
?line {otp_4967,node()} ! heihopp,
- io:format("8: ~p",[now()]),
+ io:format("8: ~p",[erlang:timestamp()]),
?line stopped = ttb:stop([format]),
- io:format("9: ~p",[now()]),
+ io:format("9: ~p",[erlang:timestamp()]),
?line Msgs = flush(),
- io:format("10: ~p",[now()]),
+ io:format("10: ~p",[erlang:timestamp()]),
?line io:format("Messages received: \n~p\n",[Msgs]),
- io:format("11: ~p",[now()]),
+ io:format("11: ~p",[erlang:timestamp()]),
?line true = lists:member(heihopp,Msgs), % the heihopp message itself
- io:format("13: ~p",[now()]),
+ io:format("13: ~p",[erlang:timestamp()]),
?line {value,{trace_ts,_,send,heihopp,{_,otp_4967,Node},{_,_,_}}} =
lists:keysearch(heihopp,4,Msgs), % trace trace of the heihopp message
- io:format("14: ~p",[now()]),
+ io:format("14: ~p",[erlang:timestamp()]),
?line end_of_trace = lists:last(Msgs), % end of the trace
ok.
@@ -1035,8 +1035,8 @@ logfile_name_in_fetch_dir(Config) when is_list(Config) ->
?line {ServerNode, ClientNode} = start_client_and_server(),
?line begin_trace(ServerNode, ClientNode, {local, ?FNAME}),
?line {_,Dir} = ttb:stop([return_fetch_dir]),
- ?line P1 = lists:nth(3, string:tokens(filename:basename(Dir), "_")),
- ?line P2 = hd(string:tokens(P1, "-")),
+ ?line P1 = lists:nth(3, string:lexemes(filename:basename(Dir), "_")),
+ ?line P2 = hd(string:lexemes(P1, "-")),
?line _File = P2.
logfile_name_in_fetch_dir(cleanup,_Config) ->
?line stop_client_and_server().
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 21edfcd184..74a6db768e 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.4
+OBSERVER_VSN = 2.7