diff options
Diffstat (limited to 'lib/observer')
53 files changed, 2048 insertions, 1040 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/etop.xml b/lib/observer/doc/src/etop.xml index d70d9d1d23..e7a83d0514 100644 --- a/lib/observer/doc/src/etop.xml +++ b/lib/observer/doc/src/etop.xml @@ -5,7 +5,7 @@    <header>      <copyright>        <year>2002</year> -      <year>2016</year> +      <year>2017</year>        <holder>Ericsson AB, All Rights Reserved</holder>      </copyright>      <legalnotice> @@ -35,7 +35,7 @@      <file></file>    </header>    <module>etop</module> -  <modulesummary>Erlang Top is a tool for presenting information about Erlang  +  <modulesummary>Erlang Top is a tool for presenting information about Erlang    processes similar to the information presented by "top" in UNIX.</modulesummary>    <description> @@ -60,11 +60,11 @@  	    <p>Value: <c>atom()</c></p>  	    <p>Mandatory</p></item>        <tag><c>setcookie</c></tag> -      <item><p>Cookie to use for the <c>etop</c> node. Must be same as the  +      <item><p>Cookie to use for the <c>etop</c> node. Must be same as the        cookie on the measured node.</p>              <p>Value: <c>atom()</c></p></item>        <tag><c>lines</c></tag> -      <item><p>Number of lines (processes) to display.</p>  +      <item><p>Number of lines (processes) to display.</p>              <p>Value: <c>integer()</c></p>  	    <p>Default: <c>10</c></p></item>        <tag><c>interval</c></tag> @@ -92,7 +92,7 @@  	    <p>Default: <c>on</c></p></item>      </taglist> -    <p>For detalis about Erlang Top, see the  +    <p>For details about Erlang Top, see the      <seealso marker="etop_ug">User's Guide</seealso>.</p>    </description> 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 Binary files differdeleted file mode 100644 index 6fffe30419..0000000000 --- a/lib/observer/doc/src/note.gif +++ /dev/null diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index 79e2b2b9db..05ea550964 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -4,7 +4,7 @@  <chapter>    <header>      <copyright> -      <year>2004</year><year>2016</year> +      <year>2004</year><year>2017</year>        <holder>Ericsson AB. All Rights Reserved.</holder>      </copyright>      <legalnotice> @@ -32,6 +32,114 @@    <p>This document describes the changes made to the Observer      application.</p> +<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> +      <list> +        <item> +          <p> +	    <c>etop</c> had a hardcoded timeout value of 1 second +	    when waiting for data from a remote node. When this +	    expired, which could happen for instance if there were +	    very many processes on the remote node, etop would exit +	    with reason <c>connection_lost</c>. To overcome this +	    problem, the timeout is now changed to be the same as the +	    update interval, which is configurable.</p> +          <p> +	    Own Id: OTP-14393</p> +        </item> +      </list> +    </section> + + +    <section><title>Improvements and New Features</title> +      <list> +        <item> +          <p> +	    Show dirty-scheduler threads in performance monitor graph +	    and add a column with maximum allocated memory in the +	    Memory Allocators table.</p> +          <p> +	    Own Id: OTP-14137</p> +        </item> +        <item> +          <p> +	    Keep table and port selection after refresh of tables. +	    Store settings before shutdown and restore when starting +	    application.</p> +          <p> +	    Own Id: OTP-14270</p> +        </item> +        <item> +	    <p> Miscellaneous updates due to atoms containing +	    arbitrary Unicode characters. </p> +          <p> +	    Own Id: OTP-14285</p> +        </item> +        <item> +          <p> +	    When observing a node older than OTP-19.0, a pop-up will +	    be displayed when trying to access port information. +	    Earlier, observer would crash in this situation.</p> +          <p> +	    Own Id: OTP-14345 Aux Id: ERL-399 </p> +        </item> +      </list> +    </section> + +</section> +  <section><title>Observer 2.3.1</title>      <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml index 4d43ffe39f..843be26ee1 100644 --- a/lib/observer/doc/src/observer.xml +++ b/lib/observer/doc/src/observer.xml @@ -4,7 +4,7 @@  <erlref>    <header>      <copyright> -      <year>2011</year><year>2016</year> +      <year>2011</year><year>2017</year>        <holder>Ericsson AB, All Rights Reserved</holder>      </copyright>      <legalnotice> @@ -43,7 +43,7 @@      <seealso marker="ttb"><c>ttb</c></seealso>.      </p> -    <p>For detalis about how to get started, see the  +    <p>For details about how to get started, see the      <seealso marker="observer_ug"><c>User's Guide</c></seealso>.</p>    </description>    <funcs> diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml index 6eb72f3e58..c9204f2bbe 100644 --- a/lib/observer/doc/src/observer_ug.xml +++ b/lib/observer/doc/src/observer_ug.xml @@ -4,7 +4,7 @@  <chapter>    <header>      <copyright> -      <year>2011</year><year>2016</year> +      <year>2011</year><year>2017</year>        <holder>Ericsson AB. All Rights Reserved.</holder>      </copyright>      <legalnotice> @@ -107,6 +107,11 @@      see module      <seealso marker="erts:erts_alloc"><c>erts_alloc</c></seealso>      in application ERTS.</p> +    <p>The <c>Max Carrier size</c> column shows the maximum value seen by observer +    since the last node change or since the start of the application, i.e. switching +    nodes will reset the max column. Values are sampled so higher values may have +    existed than what is shown. +    </p>    </section>    <section> 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/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_atom_cb.erl b/lib/observer/src/cdv_atom_cb.erl index a123354c8f..86cdf2fd6d 100644 --- a/lib/observer/src/cdv_atom_cb.erl +++ b/lib/observer/src/cdv_atom_cb.erl @@ -42,7 +42,7 @@ get_info(_) ->      {Info,TW}.  format({Bin,q}) when is_binary(Bin) -> -    [$'|binary_to_list(Bin)]; +    [$'|lists:flatten(io_lib:format("~ts",[Bin]))];  format({Bin,nq}) when is_binary(Bin) ->      lists:flatten(io_lib:format("~ts",[Bin]));  format(D) -> diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl index 5472d36a6f..a4a542297c 100644 --- a/lib/observer/src/cdv_bin_cb.erl +++ b/lib/observer/src/cdv_bin_cb.erl @@ -38,6 +38,7 @@ init_bin_page(Parent,{Type,Bin}) ->        [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin)}},         {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin)}},         {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin)}}, +       {"Format \~tw",cdv_html_wx,{Type,format_bin_fun("~tw",Bin)}},         {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin)}},         {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin)}},         {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin)}}, @@ -56,7 +57,7 @@ format_bin_fun(Format,Bin) ->  binary_to_term_fun(Bin) ->      fun() ->  	    try binary_to_term(Bin) of -		Term -> plain_html(io_lib:format("~p",[Term])) +		Term -> plain_html(io_lib:format("~tp",[Term]))  	    catch error:badarg ->  		    Warning = "This binary can not be converted to an Erlang term",  		    observer_html_lib:warning(Warning) @@ -70,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 27057fd27f..f6d282638a 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,42 @@  -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); +	    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. +destroy_progress(cdv) -> +    observer_lib: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 +103,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, @@ -133,7 +149,7 @@ handle_event(Event, _State) ->      error({unhandled_event, Event}).  handle_info(_Info, State) -> -    %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, _Info]), +    %% io:format("~p: ~p, Handle info: ~tp~n", [?MODULE, ?LINE, _Info]),      {noreply, State}.  handle_call(Call, From, _State) -> diff --git a/lib/observer/src/cdv_ets_cb.erl b/lib/observer/src/cdv_ets_cb.erl index ddd2d42df6..a652729ed3 100644 --- a/lib/observer/src/cdv_ets_cb.erl +++ b/lib/observer/src/cdv_ets_cb.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -30,26 +30,23 @@  -include("crashdump_viewer.hrl").  %% Defines --define(COL_ID,    0). --define(COL_NAME,  ?COL_ID+1). --define(COL_SLOT,  ?COL_NAME+1). --define(COL_OWNER, ?COL_SLOT+1). +-define(COL_NAME,  0). +-define(COL_IS_NAMED,  ?COL_NAME+1). +-define(COL_OWNER, ?COL_IS_NAMED+1).  -define(COL_OBJ,   ?COL_OWNER+1).  -define(COL_MEM,   ?COL_OBJ+1).  %% Callbacks for cdv_virtual_list_wx -col_to_elem(id) -> col_to_elem(?COL_ID); -col_to_elem(?COL_ID)    -> #ets_table.id; +col_to_elem(id) -> col_to_elem(?COL_NAME); +col_to_elem(?COL_IS_NAMED) -> #ets_table.is_named;  col_to_elem(?COL_NAME)  -> #ets_table.name; -col_to_elem(?COL_SLOT)  -> #ets_table.slot;  col_to_elem(?COL_OWNER) -> #ets_table.pid;  col_to_elem(?COL_OBJ)   -> #ets_table.size;  col_to_elem(?COL_MEM)   -> #ets_table.memory.  col_spec() -> -    [{"Id",      ?wxLIST_FORMAT_LEFT,   200}, -     {"Name",    ?wxLIST_FORMAT_LEFT,   200}, -     {"Slot",    ?wxLIST_FORMAT_RIGHT,  50}, +    [{"Name",    ?wxLIST_FORMAT_LEFT,   200}, +     {"Is Named", ?wxLIST_FORMAT_CENTRE, 70},       {"Owner",   ?wxLIST_FORMAT_CENTRE, 120},       {"Objects", ?wxLIST_FORMAT_RIGHT,  80},       {"Memory",  ?wxLIST_FORMAT_RIGHT,  80} @@ -68,7 +65,7 @@ get_details(Id, Data) ->      {ok,{"Table:" ++ Id,Proplist,""}}.  get_detail_cols(all) -> -    {[{ets, ?COL_ID}, {process, ?COL_OWNER}],true}; +    {[{ets, ?COL_NAME}, {process, ?COL_OWNER}],true};  get_detail_cols(_W) ->      {[],true}. diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index 0ab0ba4315..4b43b6a840 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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -62,7 +66,7 @@ handle_info(active, State) ->      {noreply, State};  handle_info(Info, State) -> -    io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, _State) -> @@ -72,7 +76,7 @@ code_change(_, _, State) ->      {ok, State}.  handle_call(Msg, _From, State) -> -    io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}.  handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) -> @@ -80,7 +84,7 @@ handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) ->      {noreply, State#state{expand_wins=Opened}};  handle_cast(Msg, State) -> -    io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),      {noreply, State}.  handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, @@ -118,16 +122,17 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,      {noreply, NewState};  handle_event(Event, State) -> -    io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), +    io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),      {noreply, 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 01fe6b15f2..7e416dd11a 100644 --- a/lib/observer/src/cdv_info_wx.erl +++ b/lib/observer/src/cdv_info_wx.erl @@ -65,7 +65,7 @@ handle_info(active, State) ->      {noreply, State};  handle_info(Info, State) -> -    io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, _State) -> @@ -88,11 +88,11 @@ handle_call(new_dump, _From, #state{callback=Callback,panel=Panel,      {reply, ok, State#state{fpanel=NewFPanel,trunc_warn=TW}};  handle_call(Msg, _From, State) -> -    io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}.  handle_cast(Msg, State) -> -    io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),      {noreply, State}.  handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) -> @@ -108,7 +108,7 @@ handle_event(#wx{obj=Obj,event=#wxMouse{type=leave_window}},State) ->      {noreply, State};  handle_event(Event, State) -> -    io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), +    io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),      {noreply, State}.  %%%----------------------------------------------------------------- diff --git a/lib/observer/src/cdv_multi_wx.erl b/lib/observer/src/cdv_multi_wx.erl index b511503752..93f045b1da 100644 --- a/lib/observer/src/cdv_multi_wx.erl +++ b/lib/observer/src/cdv_multi_wx.erl @@ -94,7 +94,7 @@ handle_info(active, State) ->      {noreply, NewState};  handle_info(Info, State) -> -    io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, _State) -> @@ -112,11 +112,11 @@ handle_call(new_dump, _From, State) ->      {reply, ok, NewState};  handle_call(Msg, _From, State) -> -    io:format("~p:~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p:~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}.  handle_cast(Msg, State) -> -    io:format("~p:~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p:~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),      {noreply, State}.  handle_event(#wx{event=#wxCommand{type=command_listbox_selected, @@ -136,7 +136,7 @@ handle_event(#wx{event=#wxCommand{type=command_listbox_selected,      {noreply,NewState};  handle_event(Event, State) -> -    io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), +    io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),      {noreply, State}.  %%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl index 592150146b..f10650bbb7 100644 --- a/lib/observer/src/cdv_proc_cb.erl +++ b/lib/observer/src/cdv_proc_cb.erl @@ -71,7 +71,7 @@ get_details(Id, _) ->  	    Proplist0 =  		crashdump_viewer:to_proplist(record_info(fields,proc),Info),  	    Proplist = [{expand_table,Tab}|Proplist0], -	    Title = io_lib:format("~s (~s)",[Info#proc.name, Id]), +	    Title = io_lib:format("~ts (~s)",[Info#proc.name, Id]),  	    {ok,{Title,Proplist,TW}};  	{error,{other_node,NodeId}} ->  	    Info = "The process you are searching for was residing on " diff --git a/lib/observer/src/cdv_table_wx.erl b/lib/observer/src/cdv_table_wx.erl index df16230b70..ba23758ea6 100644 --- a/lib/observer/src/cdv_table_wx.erl +++ b/lib/observer/src/cdv_table_wx.erl @@ -74,7 +74,7 @@ handle_info(active, State) ->      {noreply, State};  handle_info(Info, State) -> -    io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, _State) -> @@ -84,15 +84,15 @@ code_change(_, _, State) ->      {ok, State}.  handle_call(Msg, _From, State) -> -    io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}.  handle_cast(Msg, State) -> -    io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),      {noreply, State}.  handle_event(Event, State) -> -    io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), +    io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),      {noreply, State}.  %%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl index f0d90dde7c..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. @@ -57,13 +65,7 @@ expand(['#CDVBin',Offset,Size,Pos], true) ->      {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}),      Bin;  expand(Bin, Tab) when is_binary(Bin), not is_boolean(Tab) -> -    Size = byte_size(Bin), -    PrevSize = min(Size, 10) * 8, -    <<Preview:PrevSize, _/binary>> = Bin, -    Hash = erlang:phash2(Bin), -    Key = {Preview, Size, Hash}, -    ets:insert(Tab, {Key,Bin}), -    ['#OBSBin',Preview,Size,Hash]; +    observer_lib:make_obsbin(Bin, Tab);  expand([H|T], Expand) ->      case expand(T, Expand) of  	ET when is_list(ET) -> diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl index ebf58865e9..33c0c880b1 100644 --- a/lib/observer/src/cdv_virtual_list_wx.erl +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -73,7 +73,7 @@ start_detail_win(Id) ->  	"#Port"++_ ->  	    start_detail_win(Id, port);  	_ -> -	    io:format("cdv: unknown identifier: ~p~n",[Id]), +	    io:format("cdv: unknown identifier: ~tp~n",[Id]),  	    ignore      end. @@ -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; @@ -195,7 +195,7 @@ call(Holder, What) when is_pid(Holder) ->  	    erlang:demonitor(Ref),  	    Res      after 5000 -> -	    io:format("Hanging call ~p~n",[What]), +	    io:format("Hanging call ~tp~n",[What]),  	    ""      end;  call(_,_) -> @@ -214,7 +214,7 @@ handle_info(active, State) ->      {noreply, State};  handle_info(Info, State) -> -    io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p, Unexpected info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, #state{holder=Holder}) -> @@ -236,7 +236,7 @@ handle_call(new_dump, _From,      {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}};  handle_call(Msg, _From, State) -> -    io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p:~p: Unhandled call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}.  handle_cast({start_detail_win,Id}, State) -> @@ -248,7 +248,7 @@ handle_cast({detail_win_closed, Id},#state{detail_wins=Opened}=State) ->      {noreply, State#state{detail_wins=Opened2}};  handle_cast(Msg, State) -> -    io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), +    io:format("~p:~p: Unhandled cast ~tp~n", [?MODULE, ?LINE, Msg]),      {noreply, State}.  %%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -322,7 +322,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated,      {noreply, State};  handle_event(Event, State) -> -    io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]), +    io:format("~p:~p: handle event ~tp\n", [?MODULE, ?LINE, Event]),      {noreply, State}. @@ -382,7 +382,7 @@ table_holder(#holder{callback=Callback, attrs=Attrs, info=Info}=S0) ->  	stop ->  	    ok;  	What -> -	    io:format("Table holder got ~p~n",[What]), +	    io:format("Table holder got ~tp~n",[What]),  	    table_holder(S0)      end. diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl index 2587a6e64e..c3f36cd689 100644 --- a/lib/observer/src/cdv_wx.erl +++ b/lib/observer/src/cdv_wx.erl @@ -17,7 +17,7 @@  %%  %% %CopyrightEnd%  -module(cdv_wx). --compile(export_all). +  -behaviour(wx_object).  -export([start/1]). @@ -306,7 +306,7 @@ handle_info({'EXIT', Pid, normal}, #state{server=Pid}=State) ->      {stop, normal, State};  handle_info({'EXIT', Pid, _Reason}, State) -> -    io:format("Child (~s) crashed exiting:  ~p ~p~n", +    io:format("Child (~s) crashed exiting:  ~p ~tp~n",  	      [pid2panel(Pid, State), Pid,_Reason]),      {stop, normal, State}; @@ -412,7 +412,16 @@ 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 @@ -431,6 +440,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 13e73f027d..40450a2873 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %%  -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2017. All Rights Reserved.  %%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -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,6 +104,8 @@  -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,4]).  %% All possible tags - use macros in order to avoid misspelling in the code @@ -104,8 +123,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 +144,7 @@  -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"}).  %%%-----------------------------------------------------------------  %%% Debugging @@ -200,13 +222,14 @@ do_script_start(StartFun) ->  			{'EXIT', Pid, normal} ->  			    ok;  			{'EXIT', Pid, Reason} -> -			    io:format("\ncdv crash: ~p\n",[Reason]) +			    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: ~p\n",[Error]) +	    io:format("\ncdv start failed: ~tp\n",[Error])      end.  usage() -> @@ -290,6 +313,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 +333,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{}}.  %%-------------------------------------------------------------------- @@ -340,7 +370,7 @@ handle_call(general_info,_From,State=#state{file=File}) ->  handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) ->      Fd = open(File),      pos_bof(Fd,Pos), -    {Bin,_Line} = get_binary(Offset,Size,val(Fd)), +    {Bin,_Line} = get_binary(Offset,Size,bytes(Fd)),      close(Fd),      {reply,{ok,Bin},State};  handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) -> @@ -348,9 +378,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 -> @@ -449,8 +479,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 +493,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{}} @@ -512,9 +543,9 @@ unexpected(_Fd,{eof,_LastLine},_Where) ->      ok; % truncated file  unexpected(Fd,{part,What},Where) ->      skip_rest_of_line(Fd), -    io:format("WARNING: Found unexpected line in ~s:~n~s ...~n",[Where,What]); +    io:format("WARNING: Found unexpected line in ~ts:~n~ts ...~n",[Where,What]);  unexpected(_Fd,What,Where) -> -    io:format("WARNING: Found unexpected line in ~s:~n~s~n",[Where,What]). +    io:format("WARNING: Found unexpected line in ~ts:~n~ts~n",[Where,What]).  truncated_warning([]) ->      []; @@ -701,9 +732,24 @@ skip(Fd,<<>>) ->      end. -val(Fd) -> -    val(Fd, "-1"). -val(Fd, NoExist) -> +string(Fd) -> +    string(Fd, "-1"). +string(Fd,NoExist) -> +    case bytes(Fd,noexist) of +        noexist -> NoExist; +        Val -> byte_list_to_string(Val) +    end. + +byte_list_to_string(ByteList) -> +    Bin = list_to_binary(ByteList), +    case unicode:characters_to_list(Bin) of +        Str when is_list(Str) -> Str; +        _ -> ByteList +    end. + +bytes(Fd) -> +    bytes(Fd, "-1"). +bytes(Fd, NoExist) ->      case get_rest_of_line(Fd) of  	{eof,[]} -> NoExist;  	[] -> NoExist; @@ -731,32 +777,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,[],[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,[lists:reverse(Acc)]) -    end. -  split(Str) ->      split($ ,Str,[]).      split(Char,Str) -> @@ -786,11 +806,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, @@ -802,21 +823,24 @@ 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,""), +                                    AddrAdj = get_bin_addr_adj(DumpVsn), +                                    indexify(Fd,AddrAdj,Rest,N1), +                                    end_progress(), +                                    check_if_truncated(), +                                    close(Fd), +                                    {ok,DumpVsn}; +                                Error -> +                                    close(Fd), +                                    Error +                            end;  			_Other ->  			    R = io_lib:format( -				  "~s is not an Erlang crash dump~n", +				  "~ts is not an Erlang crash dump~n",  				  [File]),  			    close(Fd),  			    {error,R} @@ -824,32 +848,73 @@ do_read_file(File) ->  		{ok,<<"<Erlang crash dump>",_Rest/binary>>} ->   		    %% old version - no longer supported  		    R = io_lib:format( -			  "The crashdump ~s is in the pre-R10B format, " +			  "The crashdump ~ts is in the pre-R10B format, "  			  "which is no longer supported.~n",  			     [File]),  		    close(Fd),  		    {error,R};  		_Other ->  		    R = io_lib:format( -			  "~s is not an Erlang crash dump~n", +			  "~ts is not an Erlang crash dump~n",  			  [File]),  		    close(Fd),  		    {error,R}  	    end;  	_other -> -	    R = io_lib:format("~s is not an Erlang crash dump~n",[File]), +	    R = io_lib:format("~ts is not an Erlang crash dump~n",[File]),  	    {error,R}      end. -indexify(Fd,Bin,N) -> +check_dump_version(Vsn) -> +    DumpVsn = [list_to_integer(L) || L<-string:tokens(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,AddrAdj,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 AddrAdj, +                    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,AddrAdj,Rest,N1);  	nomatch ->  	    case progress_read(Fd) of  		{ok,Chunk0} when is_binary(Chunk0) -> @@ -860,7 +925,7 @@ indexify(Fd,Bin,N) ->  			    _ ->  				{Chunk0,N+byte_size(Bin)}  			end, -		    indexify(Fd,Chunk,N1); +		    indexify(Fd,AddrAdj,Chunk,N1);  		eof ->  		    eof  	    end @@ -896,7 +961,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 @@ -986,7 +1056,7 @@ general_info(File) ->  		    instr_info=InstrInfo}.  get_slogan_and_sysvsn(Fd,Acc) -> -    case val(Fd,eof) of +    case string(Fd,eof) of          "Slogan: " ++ SloganPart when Acc==[] ->              get_slogan_and_sysvsn(Fd,[SloganPart]);          "System version: " ++ SystemVsn -> @@ -1000,14 +1070,14 @@ get_slogan_and_sysvsn(Fd,Acc) ->  get_general_info(Fd,GenInfo) ->      case line_head(Fd) of  	"Compiled" -> -	    get_general_info(Fd,GenInfo#general_info{compile_time=val(Fd)}); +	    get_general_info(Fd,GenInfo#general_info{compile_time=bytes(Fd)});  	"Taints" -> -	    Val = case val(Fd) of "-1" -> "(none)"; Line -> Line end, +	    Val = case string(Fd) of "-1" -> "(none)"; Line -> Line end,  	    get_general_info(Fd,GenInfo#general_info{taints=Val});  	"Atoms" -> -	    get_general_info(Fd,GenInfo#general_info{num_atoms=val(Fd)}); +	    get_general_info(Fd,GenInfo#general_info{num_atoms=bytes(Fd)});  	"Calling Thread" -> -	    get_general_info(Fd,GenInfo#general_info{thread=val(Fd)}); +	    get_general_info(Fd,GenInfo#general_info{thread=bytes(Fd)});  	"=" ++ _next_tag ->  	    GenInfo;  	Other -> @@ -1045,14 +1115,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, @@ -1068,15 +1138,15 @@ get_proc_details(File,Pid,WS,DumpVsn,Binaries) ->  get_procinfo(Fd,Fun,Proc,WS) ->      case line_head(Fd) of  	"State" -> -	    State = case val(Fd) of +	    State = case bytes(Fd) of  			"Garbing" -> "Garbing\n(limited info)";  			State0 -> State0  		    end,  	    get_procinfo(Fd,Fun,Proc#proc{state=State},WS);  	"Name" -> -	    get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{name=string(Fd)},WS);  	"Spawned as" -> -	    IF = val(Fd), +	    IF = string(Fd),  	    case Proc#proc.name of  		undefined ->  		    get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF},WS); @@ -1085,17 +1155,17 @@ get_procinfo(Fd,Fun,Proc,WS) ->  	    end;  	"Message queue length" ->  	    %% stored as integer so we can sort on it -	    get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))},WS); +	    get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(bytes(Fd))},WS);  	"Reductions" ->  	    %% stored as integer so we can sort on it -	    get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))},WS); +	    get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(bytes(Fd))},WS);  	"Stack+heap" ->  	    %% stored as integer so we can sort on it  	    get_procinfo(Fd,Fun,Proc#proc{stack_heap= -					  list_to_integer(val(Fd))*WS},WS); +					  list_to_integer(bytes(Fd))*WS},WS);  	"Memory" ->  	    %% stored as integer so we can sort on it -	    get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))},WS); +	    get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(bytes(Fd))},WS);  	{eof,_} ->  	    Proc; % truncated file  	Other -> @@ -1117,67 +1187,67 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->      case LineHead of  	%% - START - moved from get_procinfo -  	"Spawned by" -> -	    case val(Fd) of +	    case bytes(Fd) of  		"[]" ->  		    get_procinfo(Fd,Fun,Proc,WS);  		Parent ->  		    get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS)  	    end;  	"Started" -> -	    get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{start_time=bytes(Fd)},WS);  	"Last scheduled in for" ->  	    get_procinfo(Fd,Fun,Proc#proc{current_func=  					  {"Last scheduled in for", -					   val(Fd)}},WS); +					   string(Fd)}},WS);  	"Current call" ->  	    get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call", -							val(Fd)}},WS); +							string(Fd)}},WS);  	"Number of heap fragments" -> -	    get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=bytes(Fd)},WS);  	"Heap fragment data" -> -	    get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=bytes(Fd)},WS);  	"OldHeap" -> -	    Bytes = list_to_integer(val(Fd))*WS, +	    Bytes = list_to_integer(bytes(Fd))*WS,  	    get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS);  	"Heap unused" -> -	    Bytes = list_to_integer(val(Fd))*WS, +	    Bytes = list_to_integer(bytes(Fd))*WS,  	    get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS);  	"OldHeap unused" -> -	    Bytes = list_to_integer(val(Fd))*WS, +	    Bytes = list_to_integer(bytes(Fd))*WS,  	    get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS);  	"New heap start" -> -	    get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{new_heap_start=bytes(Fd)},WS);  	"New heap top" -> -	    get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{new_heap_top=bytes(Fd)},WS);  	"Stack top" -> -	    get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{stack_top=bytes(Fd)},WS);  	"Stack end" -> -	    get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{stack_end=bytes(Fd)},WS);  	"Old heap start" -> -	    get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{old_heap_start=bytes(Fd)},WS);  	"Old heap top" -> -	    get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{old_heap_top=bytes(Fd)},WS);  	"Old heap end" -> -	    get_procinfo(Fd,Fun,Proc#proc{old_heap_end=val(Fd)},WS); +	    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(val(Fd),[],[],[]), +	    {Links,Monitors,MonitoredBy} = parse_link_list(bytes(Fd),[],[],[]),  	    get_procinfo(Fd,Fun,Proc#proc{links=Links,  					  monitors=Monitors,  					  mon_by=MonitoredBy},WS);  	"Program counter" -> -	    get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{prog_count=string(Fd)},WS);  	"CP" -> -	    get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{cp=string(Fd)},WS);  	"arity = " ++ Arity ->  	    %%! Temporary workaround  	    get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"},WS);  	"Run queue" -> -	    get_procinfo(Fd,Fun,Proc#proc{run_queue=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{run_queue=string(Fd)},WS);  	"Internal State" -> -	    get_procinfo(Fd,Fun,Proc#proc{int_state=val(Fd)},WS); +	    get_procinfo(Fd,Fun,Proc#proc{int_state=string(Fd)},WS);  	"=" ++ _next_tag ->  	    Proc;  	Other -> @@ -1185,6 +1255,66 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->  	    get_procinfo(Fd,Fun,Proc,WS)      end. +%% 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; +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_list([SB|Str],Links,Monitors,MonitoredBy) when SB==$[; SB==$] ->      parse_link_list(Str,Links,Monitors,MonitoredBy);  parse_link_list("#Port"++_=Str,Links,Monitors,MonitoredBy) -> @@ -1204,7 +1334,7 @@ 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~s~n",[Unexpected]), +    io:format("WARNING: found unexpected data in link list:~n~ts~n",[Unexpected]),      parse_link_list([],Links,Monitors,MonitoredBy). @@ -1310,15 +1440,43 @@ maybe_other_node2(Channel) ->      end. -expand_memory(Fd,Pid,DumpVsn,Binaries) -> +expand_memory(Fd,Pid,DumpVsn) ->      BinAddrAdj = get_bin_addr_adj(DumpVsn),      put(fd,Fd), -    Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries), +    Dict0 = case get(?literals) of +                undefined -> +                    Literals = read_literals(Fd), +                    put(?literals,Literals), +                    put(fd,Fd), +                    Literals; +                Literals -> +                    Literals +            end, +    Dict = read_heap(Fd,Pid,BinAddrAdj,Dict0),      Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict),  		read_messages(Fd,Pid,BinAddrAdj,Dict),  		read_dictionary(Fd,Pid,BinAddrAdj,Dict)},      erase(fd), -    Expanded. +    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) -> +    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(0,gb_trees:empty()); +        [] -> +            gb_trees:empty() +    end.  %%%-----------------------------------------------------------------  %%% This is a workaround for a bug in dump versions prior to 0.3: @@ -1331,25 +1489,6 @@ 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). - -%%%  %%% Read top level section.  %%% @@ -1363,7 +1502,7 @@ read_stack_dump(Fd,Pid,BinAddrAdj,Dict) ->      end.  read_stack_dump1(Fd,BinAddrAdj,Dict,Acc) ->      %% This function is never called if the dump is truncated in {?proc_heap,Pid} -    case val(Fd) of +    case bytes(Fd) of  	"=" ++ _next_tag ->  	    lists:reverse(Acc);  	Line -> @@ -1391,7 +1530,7 @@ read_messages(Fd,Pid,BinAddrAdj,Dict) ->      end.  read_messages1(Fd,BinAddrAdj,Dict,Acc) ->      %% This function is never called if the dump is truncated in {?proc_heap,Pid} -    case val(Fd) of +    case bytes(Fd) of  	"=" ++ _next_tag ->  	    lists:reverse(Acc);  	Line -> @@ -1419,7 +1558,7 @@ read_dictionary(Fd,Pid,BinAddrAdj,Dict) ->      end.  read_dictionary1(Fd,BinAddrAdj,Dict,Acc) ->      %% This function is never called if the dump is truncated in {?proc_heap,Pid} -    case val(Fd) of +    case bytes(Fd) of  	"=" ++ _next_tag ->  	    lists:reverse(Acc);  	Line -> @@ -1439,6 +1578,8 @@ parse_dictionary(Line0, BinAddrAdj, D) ->  read_heap(Fd,Pid,BinAddrAdj,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);  	[] -> @@ -1449,13 +1590,16 @@ read_heap(BinAddrAdj,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 val(Fd) of +	    case bytes(Fd) of  		"=" ++ _next_tag -> +                    end_progress(),  		    put(fd, end_of_heap),  		    Dict0;  		Line -> +                    update_progress(length(Line)+1),  		    Dict = parse(Line,BinAddrAdj,Dict0),  		    read_heap(BinAddrAdj,Dict)  	    end @@ -1498,42 +1642,42 @@ get_portinfo(Fd,Port) ->      case line_head(Fd) of  	"Slot" ->  	    %% stored as integer so we can sort on it -	    get_portinfo(Fd,Port#port{slot=list_to_integer(val(Fd))}); +	    get_portinfo(Fd,Port#port{slot=list_to_integer(bytes(Fd))});  	"Connected" ->  	    %% stored as pid so we can sort on it -	    Connected0 = val(Fd), +	    Connected0 = bytes(Fd),  	    Connected =  		try list_to_pid(Connected0)  		catch error:badarg -> Connected0  		end,  	    get_portinfo(Fd,Port#port{connected=Connected});  	"Links" -> -	    Pids = split_pid_list_no_space(val(Fd)), +	    Pids = split_pid_list_no_space(bytes(Fd)),  	    Links = [{Pid,Pid} || Pid <- Pids],  	    get_portinfo(Fd,Port#port{links=Links});  	"Registered as" -> -	    get_portinfo(Fd,Port#port{name=val(Fd)}); +	    get_portinfo(Fd,Port#port{name=string(Fd)});  	"Monitors" -> -	    Monitors0 = string:tokens(val(Fd),"()"), +	    Monitors0 = string:tokens(bytes(Fd),"()"),  	    Monitors = [begin  			    [Pid,Ref] = string:tokens(Mon,","),  			    {Pid,Pid++" ("++Ref++")"}  			end || Mon <- Monitors0],  	    get_portinfo(Fd,Port#port{monitors=Monitors});  	"Port controls linked-in driver" -> -	    Str = lists:flatten(["Linked in driver: " | val(Fd)]), +	    Str = lists:flatten(["Linked in driver: " | string(Fd)]),  	    get_portinfo(Fd,Port#port{controls=Str});  	"Port controls forker process" -> -	    Str = lists:flatten(["Forker process: " | val(Fd)]), +	    Str = lists:flatten(["Forker process: " | string(Fd)]),  	    get_portinfo(Fd,Port#port{controls=Str});  	"Port controls external process" -> -	    Str = lists:flatten(["External proc: " | val(Fd)]), +	    Str = lists:flatten(["External proc: " | string(Fd)]),  	    get_portinfo(Fd,Port#port{controls=Str});  	"Port is a file" -> -	    Str = lists:flatten(["File: "| val(Fd)]), +	    Str = lists:flatten(["File: "| string(Fd)]),  	    get_portinfo(Fd,Port#port{controls=Str});  	"Port is UNIX fd not opened by emulator" -> -	    Str = lists:flatten(["UNIX fd not opened by emulator: "| val(Fd)]), +	    Str = lists:flatten(["UNIX fd not opened by emulator: "| string(Fd)]),  	    get_portinfo(Fd,Port#port{controls=Str});  	"=" ++ _next_tag ->  	    Port; @@ -1555,30 +1699,34 @@ split_pid_list_no_space([],[],Pids) ->  %% Page with external ets tables  get_ets_tables(File,Pid,WS) ->      ParseFun = fun(Fd,Id) -> -		       get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS) +		       ET = get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS), +                       ET#ets_table{is_named=tab_is_named(ET)}  	       end,      lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets"). +tab_is_named(#ets_table{id=Name,name=Name}) -> "yes"; +tab_is_named(#ets_table{}) -> "no". +  get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->      case line_head(Fd) of  	"Slot" -> -	    get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(val(Fd))},WS); +	    get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(bytes(Fd))},WS);  	"Table" -> -	    get_etsinfo(Fd,EtsTable#ets_table{id=val(Fd)},WS); +	    get_etsinfo(Fd,EtsTable#ets_table{id=string(Fd)},WS);  	"Name" -> -	    get_etsinfo(Fd,EtsTable#ets_table{name=val(Fd)},WS); +	    get_etsinfo(Fd,EtsTable#ets_table{name=string(Fd)},WS);  	"Ordered set (AVL tree), Elements" ->  	    skip_rest_of_line(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{data_type="tree"},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(val(Fd))), +	    Buckets = list_to_integer(string:strip(bytes(Fd))),  	    get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS);  	"Objects" -> -	    get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(val(Fd))},WS); +	    get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(bytes(Fd))},WS);  	"Words" -> -	    Words = list_to_integer(val(Fd)), +	    Words = list_to_integer(bytes(Fd)),  	    Bytes =   		case Words of  		    -1 -> -1; % probably truncated @@ -1588,37 +1736,37 @@ get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->  	"=" ++ _next_tag ->  	    EtsTable;  	"Chain Length Min" -> -	    Val = val(Fd), +	    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(val(Fd))) catch _:_ -> "-" end, +	    Val = try list_to_float(string:strip(bytes(Fd))) catch _:_ -> "-" end,  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_avg=>Val}},WS);  	"Chain Length Max" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_max=>Val}},WS);  	"Chain Length Std Dev" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_stddev=>Val}},WS);  	"Chain Length Expected Std Dev" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_exp_stddev=>Val}},WS);  	"Fixed" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{fixed=>Val}},WS);  	"Type" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{data_type=Val},WS);  	"Protection" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{protection=>Val}},WS);  	"Compressed" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{compressed=>Val}},WS);  	"Write Concurrency" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{write_c=>Val}},WS);  	"Read Concurrency" -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{read_c=>Val}},WS);  	Other ->  	    unexpected(Fd,Other,"ETS info"), @@ -1668,9 +1816,9 @@ get_timerinfo(Fd,Id) ->  get_timerinfo_1(Fd,Timer) ->      case line_head(Fd) of  	"Message" -> -	    get_timerinfo_1(Fd,Timer#timer{msg=val(Fd)}); +	    get_timerinfo_1(Fd,Timer#timer{msg=string(Fd)});  	"Time left" -> -	    TimeLeft = list_to_integer(val(Fd) -- " ms"), +	    TimeLeft = list_to_integer(bytes(Fd) -- " ms"),  	    get_timerinfo_1(Fd,Timer#timer{time=TimeLeft});  	"=" ++ _next_tag ->  	    Timer; @@ -1739,37 +1887,37 @@ get_nodeinfo(Fd,Channel,Type,Start) ->  get_nodeinfo(Fd,Nod) ->      case line_head(Fd) of  	"Name" -> -	    get_nodeinfo(Fd,Nod#nod{name=val(Fd)}); +	    get_nodeinfo(Fd,Nod#nod{name=bytes(Fd)});  	"Controller" -> -	    get_nodeinfo(Fd,Nod#nod{controller=val(Fd)}); +	    get_nodeinfo(Fd,Nod#nod{controller=bytes(Fd)});  	"Creation" ->  	    %% Throwing away elements like "(refc=1)", which might be  	    %% printed from a debug compiled emulator.  	    Creations = lists:flatmap(fun(C) -> try [list_to_integer(C)]  						catch error:badarg -> []  						end -				      end, string:tokens(val(Fd)," ")), +				      end, string:tokens(bytes(Fd)," ")),  	    get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}});  	"Remote link" -> -	    Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" +	    Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"  	    {Local,Remote} = split(Procs),  	    Str = Local++" <-> "++Remote,  	    NewRemLinks = [{Local,Str} | Nod#nod.remote_links],  	    get_nodeinfo(Fd,Nod#nod{remote_links=NewRemLinks});  	"Remote monitoring" -> -	    Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" +	    Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"  	    {Local,Remote} = split(Procs),  	    Str = Local++" -> "++Remote,  	    NewRemMon = [{Local,Str} | Nod#nod.remote_mon],  	    get_nodeinfo(Fd,Nod#nod{remote_mon=NewRemMon});  	"Remotely monitored by" -> -	    Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" +	    Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"  	    {Local,Remote} = split(Procs),  	    Str = Local++" <- "++Remote,  	    NewRemMonBy = [{Local,Str} | Nod#nod.remote_mon_by],  	    get_nodeinfo(Fd,Nod#nod{remote_mon_by=NewRemMonBy});  	"Error" -> -	    get_nodeinfo(Fd,Nod#nod{error="ERROR: "++val(Fd)}); +	    get_nodeinfo(Fd,Nod#nod{error="ERROR: "++string(Fd)});  	"=" ++ _next_tag ->  	    Nod;  	Other -> @@ -1813,9 +1961,9 @@ loaded_mods(File) ->  get_loaded_mod_totals(Fd,{CC,OC}) ->      case line_head(Fd) of  	"Current code" -> -	    get_loaded_mod_totals(Fd,{val(Fd),OC}); +	    get_loaded_mod_totals(Fd,{bytes(Fd),OC});  	"Old code" -> -	    get_loaded_mod_totals(Fd,{CC,val(Fd)}); +	    get_loaded_mod_totals(Fd,{CC,bytes(Fd)});  	"=" ++ _next_tag ->  	    {CC,OC};  	Other -> @@ -1826,10 +1974,10 @@ get_loaded_mod_totals(Fd,{CC,OC}) ->  get_loaded_mod_info(Fd,LM,Fun) ->      case line_head(Fd) of  	"Current size" -> -	    CS = list_to_integer(val(Fd)), +	    CS = list_to_integer(bytes(Fd)),  	    get_loaded_mod_info(Fd,LM#loaded_mod{current_size=CS},Fun);  	"Old size" -> -	    OS = list_to_integer(val(Fd)), +	    OS = list_to_integer(bytes(Fd)),  	    get_loaded_mod_info(Fd,LM#loaded_mod{old_size=OS},Fun);  	"=" ++ _next_tag ->  	    LM; @@ -1845,16 +1993,16 @@ main_modinfo(_Fd,LM,_LineHead) ->  all_modinfo(Fd,LM,LineHead) ->      case LineHead of  	"Current attributes" -> -	    Str = hex_to_str(val(Fd,"")), +	    Str = hex_to_str(bytes(Fd,"")),  	    LM#loaded_mod{current_attrib=Str};  	"Current compilation info" -> -	    Str = hex_to_str(val(Fd,"")), +	    Str = hex_to_str(bytes(Fd,"")),  	    LM#loaded_mod{current_comp_info=Str};  	"Old attributes" -> -	    Str = hex_to_str(val(Fd,"")), +	    Str = hex_to_str(bytes(Fd,"")),  	    LM#loaded_mod{old_attrib=Str};  	"Old compilation info" -> -	    Str = hex_to_str(val(Fd,"")), +	    Str = hex_to_str(bytes(Fd,"")),  	    LM#loaded_mod{old_comp_info=Str};  	Other ->  	    unexpected(Fd,Other,"loaded modules info"), @@ -1864,7 +2012,7 @@ all_modinfo(Fd,LM,LineHead) ->  hex_to_str(Hex) ->      Term = hex_to_term(Hex,[]), -    io_lib:format("~p~n",[Term]). +    io_lib:format("~tp~n",[Term]).  hex_to_term([X,Y|Hex],Acc) ->      MS = hex_to_dec([X]), @@ -1905,17 +2053,17 @@ funs(File) ->  get_funinfo(Fd,Fu) ->      case line_head(Fd) of  	"Module" -> -	    get_funinfo(Fd,Fu#fu{module=val(Fd)}); +	    get_funinfo(Fd,Fu#fu{module=bytes(Fd)});  	"Uniq" -> -	    get_funinfo(Fd,Fu#fu{uniq=list_to_integer(val(Fd))}); +	    get_funinfo(Fd,Fu#fu{uniq=list_to_integer(bytes(Fd))});  	"Index" -> -	    get_funinfo(Fd,Fu#fu{index=list_to_integer(val(Fd))}); +	    get_funinfo(Fd,Fu#fu{index=list_to_integer(bytes(Fd))});  	"Address" -> -	    get_funinfo(Fd,Fu#fu{address=val(Fd)}); +	    get_funinfo(Fd,Fu#fu{address=bytes(Fd)});  	"Native_address" -> -	    get_funinfo(Fd,Fu#fu{native_address=val(Fd)}); +	    get_funinfo(Fd,Fu#fu{native_address=bytes(Fd)});  	"Refc" -> -	    get_funinfo(Fd,Fu#fu{refc=list_to_integer(val(Fd))}); +	    get_funinfo(Fd,Fu#fu{refc=list_to_integer(bytes(Fd))});  	"=" ++ _next_tag ->  	    Fu;  	Other -> @@ -1995,7 +2143,7 @@ get_meminfo(Fd,Acc) ->  	{eof,_last_line} ->  	    lists:reverse(Acc);  	Key -> -	    get_meminfo(Fd,[{list_to_atom(Key),val(Fd)}|Acc]) +	    get_meminfo(Fd,[{list_to_atom(Key),bytes(Fd)}|Acc])      end.  %%----------------------------------------------------------------- @@ -2019,7 +2167,7 @@ get_allocareainfo(Fd,Acc) ->  	{eof,_last_line} ->  	    lists:reverse(Acc);  	Key -> -	    Val = val(Fd), +	    Val = bytes(Fd),  	    AllocInfo =  		case split(Val) of  		    {Alloc,[]} -> @@ -2057,7 +2205,7 @@ get_allocatorinfo1(Fd,Acc,Max) ->  	{eof,_last_line} ->  	    pad_and_reverse(Acc,Max,[]);  	Key -> -	    Values = get_all_vals(val(Fd),[]), +	    Values = get_all_vals(bytes(Fd),[]),  	    L = length(Values),  	    Max1 = if L > Max -> L; true -> Max end,  	    get_allocatorinfo1(Fd,[{Key,Values}|Acc],Max1) @@ -2312,13 +2460,13 @@ get_hashtableinfo(Fd,Name,Start) ->  get_hashtableinfo1(Fd,HashTable) ->      case line_head(Fd) of  	"size" -> -	    get_hashtableinfo1(Fd,HashTable#hash_table{size=val(Fd)}); +	    get_hashtableinfo1(Fd,HashTable#hash_table{size=bytes(Fd)});  	"used" -> -	    get_hashtableinfo1(Fd,HashTable#hash_table{used=val(Fd)}); +	    get_hashtableinfo1(Fd,HashTable#hash_table{used=bytes(Fd)});  	"objs" -> -	    get_hashtableinfo1(Fd,HashTable#hash_table{objs=val(Fd)}); +	    get_hashtableinfo1(Fd,HashTable#hash_table{objs=bytes(Fd)});  	"depth" -> -	    get_hashtableinfo1(Fd,HashTable#hash_table{depth=val(Fd)}); +	    get_hashtableinfo1(Fd,HashTable#hash_table{depth=bytes(Fd)});      	"=" ++ _next_tag ->  	    HashTable;  	Other -> @@ -2349,15 +2497,15 @@ get_indextableinfo(Fd,Name,Start) ->  get_indextableinfo1(Fd,IndexTable) ->      case line_head(Fd) of  	"size" -> -	    get_indextableinfo1(Fd,IndexTable#index_table{size=val(Fd)}); +	    get_indextableinfo1(Fd,IndexTable#index_table{size=bytes(Fd)});  	"used" -> -	    get_indextableinfo1(Fd,IndexTable#index_table{used=val(Fd)}); +	    get_indextableinfo1(Fd,IndexTable#index_table{used=bytes(Fd)});  	"limit" -> -	    get_indextableinfo1(Fd,IndexTable#index_table{limit=val(Fd)}); +	    get_indextableinfo1(Fd,IndexTable#index_table{limit=bytes(Fd)});  	"rate" -> -	    get_indextableinfo1(Fd,IndexTable#index_table{rate=val(Fd)}); +	    get_indextableinfo1(Fd,IndexTable#index_table{rate=bytes(Fd)});  	"entries" -> -	    get_indextableinfo1(Fd,IndexTable#index_table{entries=val(Fd)}); +	    get_indextableinfo1(Fd,IndexTable#index_table{entries=bytes(Fd)});      	"=" ++ _next_tag ->  	    IndexTable;  	Other -> @@ -2389,45 +2537,45 @@ get_schedulerinfo(Fd,Name,Start) ->  get_schedulerinfo1(Fd,Sched=#sched{details=Ds}) ->      case line_head(Fd) of  	"Current Process" -> -	    get_schedulerinfo1(Fd,Sched#sched{process=val(Fd, "None")}); +	    get_schedulerinfo1(Fd,Sched#sched{process=bytes(Fd, "None")});  	"Current Port" -> -	    get_schedulerinfo1(Fd,Sched#sched{port=val(Fd, "None")}); +	    get_schedulerinfo1(Fd,Sched#sched{port=bytes(Fd, "None")});  	"Run Queue Max Length" -> -	    RQMax = list_to_integer(val(Fd)), +	    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}});  	"Run Queue High Length" -> -	    RQHigh = list_to_integer(val(Fd)), +	    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}});  	"Run Queue Normal Length" -> -	    RQNorm = list_to_integer(val(Fd)), +	    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}});  	"Run Queue Low Length" -> -	    RQLow = list_to_integer(val(Fd)), +	    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}});  	"Run Queue Port Length" -> -	    RQ = list_to_integer(val(Fd)), +	    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=>val(Fd, "None")}}); +	    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=>val(Fd, "None")}}); +	    get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_aux=>bytes(Fd, "None")}});  	"Run Queue Flags" -> -	    get_schedulerinfo1(Fd,Sched#sched{details=Ds#{runq_flags=>val(Fd, "None")}}); +	    get_schedulerinfo1(Fd,Sched#sched{details=Ds#{runq_flags=>bytes(Fd, "None")}});  	"Current Process State" -> -	    get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_state=>val(Fd)}}); +	    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=>val(Fd)}}); +	    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=>val(Fd)}}); +	    get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_prg_cnt=>string(Fd)}});  	"Current Process CP" -> -	    get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_cp=>val(Fd)}}); +	    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)}; @@ -2439,7 +2587,7 @@ get_schedulerinfo1(Fd,Sched=#sched{details=Ds}) ->      end.  get_limited_stack(Fd, N, Ds) -> -    case val(Fd) of +    case string(Fd) of  	Addr = "0x" ++ _ ->  	    get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Addr});  	"=" ++ _next_tag -> @@ -2505,9 +2653,9 @@ parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) ->	%Reference-counted binary.      {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' +    Term = case lookup_binary_index(Binp) of +               [{_,Start}] -> cdvbin(Offset,Sz,{'#CDVBin',Start}); +	       [] -> '#CDVNonexistingBinary'  	   end,      D = gb_trees:insert(Addr, Term, D0),      {Term,Line,D}; @@ -2516,19 +2664,36 @@ parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) ->	%Sub binary.      {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 -> +    Term = case lookup_binary_index(Binp) of +               [{_,Start}] -> cdvbin(Offset,Sz,{'#CDVBin',Start}); +	       [] ->  		   %% Might it be on the heap? -		   case gb_trees:lookup(Binp0, D0) of +		   case gb_trees:lookup(Binp, D0) of  		       {value,Bin} -> cdvbin(Offset,Sz,Bin);  		       none -> '#CDVNonexistingBinary' -		   end; -	       none -> '#CDVNonexistingBinary' +		   end  	   end,      D = gb_trees:insert(Addr, Term, D0), -    {Term,Line,D}. - +    {Term,Line,D}; +parse_heap_term("Mf"++Line0, Addr, BinAddrAdj, D0) -> %Flatmap. +    {Size,":"++Line1} = get_hex(Line0), +    {Keys,":"++Line2,D1} = parse_term(Line1, BinAddrAdj, D0), +    {Values,Line,D2} = parse_tuple(Size, Line2, Addr,BinAddrAdj, 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, BinAddrAdj, D0) -> %Head node in a hashmap. +    {MapSize,":"++Line1} = get_hex(Line0), +    {N,":"++Line2} = get_hex(Line1), +    {Nodes,Line,D1} = parse_tuple(N, Line2, Addr, BinAddrAdj, 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, BinAddrAdj, D) -> %Interior node in a hashmap. +    {N,":"++Line} = get_hex(Line0), +    parse_tuple(N, Line, Addr, BinAddrAdj, D, []).  parse_tuple(0, Line, Addr, _, D0, Acc) ->      Tuple = list_to_tuple(lists:reverse(Acc)), @@ -2542,6 +2707,25 @@ parse_tuple(N, Line0, Addr, BinAddrAdj, D0, Acc) ->  	    parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc])      end. +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], BinAddrAdj, D) ->        %Pointer to heap term.      {Ptr,Line} = get_hex(Line0),      deref_ptr(Ptr, Line, BinAddrAdj, D); @@ -2591,7 +2775,7 @@ skip_dist_ext([C|Cs], KeptCs) ->  parse_atom([$A|Line0], D) ->      {N,":"++Line1} = get_hex(Line0),      {Chars, Line} = get_chars(N, Line1), -    {list_to_atom(Chars), Line, D}. +    {binary_to_atom(list_to_binary(Chars),utf8), Line, D}.  parse_atom_translation_table(0, Line0, As) ->      {list_to_tuple(lists:reverse(As)), Line0}; @@ -2608,13 +2792,15 @@ deref_ptr(Ptr, Line, BinAddrAdj, D0) ->  	none ->  	    case get(fd) of  		end_of_heap -> +                    put(incomplete_heap,true),  		    {['#CDVIncompleteHeap'],Line,D0};  		Fd -> -		    case val(Fd) of +		    case bytes(Fd) of  			"="++_ ->  			    put(fd, end_of_heap),  			    deref_ptr(Ptr, Line, BinAddrAdj, D0);  			L -> +                            update_progress(length(L)+1),  			    D = parse(L, BinAddrAdj, D0),  			    deref_ptr(Ptr, Line, BinAddrAdj, D)  		    end @@ -2680,19 +2866,33 @@ get_label([H|T], Acc) ->      get_label(T, [H|Acc]).  get_binary(Line0) -> -    {N,":"++Line} = get_hex(Line0), -    do_get_binary(N, Line, []). +    case get_hex(Line0) of +        {N,":"++Line} -> +            do_get_binary(N, Line, [], false); +        _  -> +           {'#CDVTruncatedBinary',[]} +    end.  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) -> +    case get_hex(Line0) of +        {_N,":"++Line} -> +            Progress = Size>?binary_size_progress_limit, +            Progress andalso init_progress("Reading binary",Size), +            do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), [], +                          Progress); +        _  -> +           {'#CDVTruncatedBinary',[]} +    end. + +do_get_binary(0, Line, Acc, Progress) -> +    Progress andalso end_progress(),      {list_to_binary(lists:reverse(Acc)),Line}; -do_get_binary(N, [A,B|Line], Acc) -> +do_get_binary(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(), +    do_get_binary(N-1, Line, [Byte|Acc], Progress); +do_get_binary(_N, [], _Acc, Progress) -> +    Progress andalso end_progress(),      {'#CDVTruncatedBinary',[]}.  cdvbin(Offset,Size,{'#CDVBin',Pos}) -> @@ -2700,16 +2900,25 @@ cdvbin(Offset,Size,{'#CDVBin',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) -> @@ -2720,6 +2929,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 @@ -2738,6 +2953,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; @@ -2755,14 +2971,16 @@ tag_to_atom("scheduler") -> ?scheduler;  tag_to_atom("timer") -> ?timer;  tag_to_atom("visible_node") -> ?visible_node;  tag_to_atom(UnknownTag) -> -    io:format("WARNING: Found unexpected tag:~s~n",[UnknownTag]), +    io:format("WARNING: Found unexpected tag:~ts~n",[UnknownTag]),      list_to_atom(UnknownTag).  %%%-----------------------------------------------------------------  %%% Store last tag for use when truncated, and reason if aborted  put_last_tag(?abort,Reason) -> -    %% Don't overwrite the real last tag -    put(truncated_reason,Reason); +    %% 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}). @@ -2790,23 +3008,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 a08659efd6..6a93a089fd 100644 --- a/lib/observer/src/crashdump_viewer.hrl +++ b/lib/observer/src/crashdump_viewer.hrl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %%  -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2017. All Rights Reserved.  %%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -118,6 +118,7 @@  	 slot,  	 id,  	 name, +         is_named,  	 data_type="hash",  	 buckets="-",  	 size, diff --git a/lib/observer/src/etop.erl b/lib/observer/src/etop.erl index 925f4456bb..f0990f1f25 100644 --- a/lib/observer/src/etop.erl +++ b/lib/observer/src/etop.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %%  -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2017. All Rights Reserved.  %%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ check_runtime_config(accumulate,A) when A=:=true; A=:=false -> ok;  check_runtime_config(_Key,_Value) -> error.  dump(File) -> -    case file:open(File,[write]) of +    case file:open(File,[write,{encoding,utf8}]) of  	{ok,Fd} -> etop_server ! {dump,Fd};  	Error -> Error      end. @@ -161,7 +161,7 @@ data_handler(Reader, Opts) ->  	{'EXIT', EPid, Reason} when EPid == Opts#opts.out_proc ->  	    case Reason of  		normal -> ok; -		_ -> io:format("Output server crashed: ~p~n",[Reason]) +		_ -> io:format("Output server crashed: ~tp~n",[Reason])  	    end,  	    stop(Opts),  	    out_proc_stopped; @@ -180,10 +180,16 @@ stop(Opts) ->      end,      unregister(etop_server). -update(#opts{store=Store,node=Node,tracing=Tracing}=Opts) -> +update(#opts{store=Store,node=Node,tracing=Tracing,intv=Interval}=Opts) ->      Pid = spawn_link(Node,observer_backend,etop_collect,[self()]),      Info = receive {Pid,I} -> I  -	   after 1000 -> exit(connection_lost) +	   after Interval -> +                   %% Took more than the update interval to fetch +                   %% data. Either the connection is lost or the +                   %% fetching took too long... +                   io:format("Timeout when waiting for process info from " +                             "node ~p; exiting~n", [Node]), +                   exit(timeout)  	   end,      #etop_info{procinfo=ProcInfo} = Info,      ProcInfo1 =  diff --git a/lib/observer/src/etop_tr.erl b/lib/observer/src/etop_tr.erl index 8e43f8bb35..1e48fefca4 100644 --- a/lib/observer/src/etop_tr.erl +++ b/lib/observer/src/etop_tr.erl @@ -89,14 +89,14 @@ handle_data(Last, {_, Pid, out, _, Time2} = G, Store) ->               end,               New;           false -> -             io:format("Erlang top got garbage ~p~n", [G]), +             io:format("Erlang top got garbage ~tp~n", [G]),               Last      end;  handle_data(_W, {drop, D}, _) ->  %% Error case we are missing data here!      io:format("Erlang top dropped data ~p~n", [D]),      [];  handle_data(Last, G, _) -> -    io:format("Erlang top got garbage ~p~n", [G]), +    io:format("Erlang top got garbage ~tp~n", [G]),      Last.  elapsed({Me1, S1, Mi1}, {Me2, S2, Mi2}) -> diff --git a/lib/observer/src/etop_txt.erl b/lib/observer/src/etop_txt.erl index 6b8f9df24f..cd3ec62c13 100644 --- a/lib/observer/src/etop_txt.erl +++ b/lib/observer/src/etop_txt.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %%  -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2017. All Rights Reserved.  %%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -29,8 +29,6 @@  -import(etop,[loadinfo/2,meminfo/2]). --define(PROCFORM,"~-15w~-20s~8w~8w~8w~8w ~-20s~n"). -  stop(Pid) -> Pid ! stop.  init(Config) -> @@ -73,7 +71,7 @@ do_update(Fd,Info,Prev,Config) ->      io:nl(Fd),      writepinfo_header(Fd),      writesingleline(Fd), -    writepinfo(Fd,Info#etop_info.procinfo), +    writepinfo(Fd,Info#etop_info.procinfo,modifier(Fd)),      writedoubleline(Fd),      io:nl(Fd),      Info. @@ -93,19 +91,34 @@ writepinfo(Fd,[#etop_proc_info{pid=Pid,  			       runtime=Time,  			       cf=MFA,  			       mq=MQ} -	       |T]) -> -    io:fwrite(Fd,?PROCFORM,[Pid,to_list(Name),Time,Reds,Mem,MQ,formatmfa(MFA)]),  -    writepinfo(Fd,T); -writepinfo(_Fd,[]) -> +	       |T], +           Modifier) -> +    io:fwrite(Fd,proc_format(Modifier), +              [Pid,to_string(Name,Modifier),Time,Reds,Mem,MQ, +               to_string(MFA,Modifier)]), +    writepinfo(Fd,T,Modifier); +writepinfo(_Fd,[],_) ->      ok. +proc_format(Modifier) -> +    "~-15w~-20"++Modifier++"s~8w~8w~8w~8w ~-20"++Modifier++"s~n". + +to_string({M,F,A},Modifier) -> +    io_lib:format("~w:~"++Modifier++"w/~w",[M,F,A]); +to_string(Other,Modifier) -> +    io_lib:format("~"++Modifier++"w",[Other]). -formatmfa({M, F, A}) -> -    io_lib:format("~w:~w/~w",[M, F, A]); -formatmfa(Other) -> -    %% E.g. when running hipe - the current_function for some -    %% processes will be 'undefined' -    io_lib:format("~w",[Other]). +modifier(Device) -> +    case encoding(Device) of +        latin1 -> ""; +        _ -> "t" +    end. + +encoding(Device) -> +    case io:getopts(Device) of +        List when is_list(List) -> +            proplists:get_value(encoding,List,latin1); +        _ -> +            latin1 +    end. -to_list(Name) when is_atom(Name) -> atom_to_list(Name); -to_list({_M,_F,_A}=MFA) -> formatmfa(MFA). diff --git a/lib/observer/src/multitrace.erl b/lib/observer/src/multitrace.erl index a01eeec6ae..82aec05e0d 100644 --- a/lib/observer/src/multitrace.erl +++ b/lib/observer/src/multitrace.erl @@ -103,16 +103,16 @@ print_func(Out,{trace_ts,P,call,{M,F,A},C,Ts},N) ->      io:format(Out,  	      "~w: ~s~n"  	      "Process   : ~w~n" -	      "Call      : ~w:~w/~w~n" -	      "Arguments : ~p~n" -	      "Caller    : ~w~n~n", +	      "Call      : ~w:~tw/~w~n" +	      "Arguments : ~tp~n" +	      "Caller    : ~tw~n~n",  	      [N,ts(Ts),P,M,F,length(A),A,C]);  print_func(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->      io:format(Out,  	      "~w: ~s~n"  	      "Process      : ~w~n" -	      "Return from  : ~w:~w/~w~n" -	      "Return value : ~p~n~n", +	      "Return from  : ~w:~tw/~w~n" +	      "Return value : ~tp~n~n",  	      [N,ts(Ts),P,M,F,A,R]). @@ -181,7 +181,7 @@ handle_schedule(Out,{trace_ts,P,out,Info,Ts},_TI,S) ->  	      "out:~n"  	      "Process  : ~w~n"  	      "Time     : ~s~n" -	      "Function : ~w~n~n",[P,ts(Ts),Info]), +	      "Function : ~tw~n~n",[P,ts(Ts),Info]),      case lists:keysearch(P,1,S) of  	{value,{P,List}} ->  	    lists:keyreplace(P,1,S,{P,[{out,Ts}|List]}); @@ -193,7 +193,7 @@ handle_schedule(Out,{trace_ts,P,in,Info,Ts},_TI,S) ->  	      "in:~n"  	      "Process  : ~w~n"  	      "Time     : ~s~n" -	      "Function : ~w~n~n",[P,ts(Ts),Info]), +	      "Function : ~tw~n~n",[P,ts(Ts),Info]),      case lists:keysearch(P,1,S) of  	{value,{P,List}} ->  	    lists:keyreplace(P,1,S,{P,[{in,Ts}|List]}); diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src index 3a5bd172e7..f682e3dc7b 100644 --- a/lib/observer/src/observer.app.src +++ b/lib/observer/src/observer.app.src @@ -65,7 +65,7 @@      {registered, []},      {applications, [kernel, stdlib]},      {env, []}, -    {runtime_dependencies, ["wx-1.2","stdlib-2.0","runtime_tools-1.8.14", +    {runtime_dependencies, ["wx-1.2","stdlib-3.4","runtime_tools-1.8.14",  			    "kernel-3.0","inets-5.10","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 ca54080e15..7f4b3dd484 100644 --- a/lib/observer/src/observer_alloc_wx.erl +++ b/lib/observer/src/observer_alloc_wx.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2015-2016. All Rights Reserved. +%% Copyright Ericsson AB 2015-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@  %% %CopyrightEnd%  -module(observer_alloc_wx). --export([start_link/2]). +-export([start_link/3]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -36,6 +36,7 @@  	  wins,  	  mem,  	  samples, +          max,  	  panel,  	  paint,  	  appmon, @@ -48,10 +49,10 @@  	[make_win/4, setup_graph_drawing/1, refresh_panel/4, interval_dialog/2,  	 add_data/5, precalc/4]). -start_link(Notebook, Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) ->      try  	TopP  = wxPanel:new(Notebook),  	Main  = wxBoxSizer:new(?wxVERTICAL), @@ -74,17 +75,20 @@ init([Notebook, Parent]) ->  		      wins  = Windows,  		      mem   = MemWin,  		      paint = PaintInfo, -		      time  = setup_time() +		      time  = setup_time(Config), +                      max   = #{}  		     }  	}      catch _:Err -> -	    io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]), +	    io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, erlang:get_stacktrace()]),  	    {stop, Err}      end. -setup_time() -> -    Freq = 1, -    #ti{fetch=Freq, disp=?DISP_FREQ/Freq}. +setup_time(Config) -> +    Freq = maps:get(fetch, Config, 1), +    #ti{disp=?DISP_FREQ/Freq, +        fetch=Freq, +        secs=maps:get(secs, Config, ?DISP_SECONDS)}.  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}}, @@ -115,6 +119,10 @@ handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,      refresh_panel(Active, Win, Ti, Paint),      ok.  %%%%%%%%%% +handle_call(get_config, _, #state{time=Ti}=State) -> +    #ti{fetch=Fetch, secs=Range} = Ti, +    {reply, #{fetch=>Fetch, secs=>Range}, State}; +  handle_call(Event, From, _State) ->      error({unhandled_call, Event, From}). @@ -126,16 +134,17 @@ handle_info({Key, {promise_reply, {badrpc, _}}}, #state{async=Key} = State) ->      {noreply, State#state{active=false, appmon=undefined}};  handle_info({Key, {promise_reply, SysInfo}}, -	    #state{async=Key, panel=_Panel, samples=Data, active=Active, wins=Wins0, -		   time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) -> +	    #state{async=Key, samples=Data, max=Max0, +                   active=Active, wins=Wins0, time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) ->      Disp = trunc(Disp0),      Next = max(Tick - Disp, 0),      erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),      Info = alloc_info(SysInfo), +    Max = lists:foldl(fun calc_max/2, Max0, Info),      {Wins, Samples} = add_data(Info, Data, Wins0, Ti, Active), -    S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, async=undefined}, +    S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, max=Max, async=undefined},      if Active -> -	    update_alloc(S0, Info), +	    update_alloc(S0, Info, Max),  	    State = precalc(S1),  	    {noreply, State};         true -> @@ -174,7 +183,7 @@ handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) ->      {noreply, State#state{active=false, appmon=undefined}};  handle_info(_Event, State) -> -    %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), +    %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),      {noreply, State}.  terminate(_Event, #state{}) -> @@ -185,27 +194,40 @@ code_change(_, _, State) ->  %%%%%%%%%%  restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) -> -    SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []), -    Info = alloc_info(SysInfo), -    {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true), -    erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}), -    wxWindow:refresh(Panel), -    precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0}, -			wins=Wins, samples=Samples}). +    case rpc:call(Node, observer_backend, sys_info, []) of +        {badrpc, _} -> State; +        SysInfo -> +            Info = alloc_info(SysInfo), +            Max = lists:foldl(fun calc_max/2, #{}, Info), +            {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true), +            erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}), +            wxWindow:refresh(Panel), +            precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0}, +                                wins=Wins, samples=Samples, max=Max}) +    end.  precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) ->      Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0],      State#state{wins=Wins}. +calc_max({Name, _, Cs}, Max0) -> +    case maps:get(Name, Max0, 0) of +        Value when Value < Cs -> +            Max0#{Name=>Cs}; +        _V -> +            Max0 +    end. -update_alloc(#state{mem=Grid}, Fields) -> +update_alloc(#state{mem=Grid}, Fields, Max) ->      wxWindow:freeze(Grid), -    Max = wxListCtrl:getItemCount(Grid), +    Last = wxListCtrl:getItemCount(Grid),      Update = fun({Name, BS, CS}, Row) -> -		     (Row >= Max) andalso wxListCtrl:insertItem(Grid, Row, ""), +		     (Row >= Last) andalso wxListCtrl:insertItem(Grid, Row, ""), +                     MaxV = maps:get(Name, Max, CS),  		     wxListCtrl:setItem(Grid, Row, 0, observer_lib:to_str(Name)),  		     wxListCtrl:setItem(Grid, Row, 1, observer_lib:to_str(BS div 1024)),  		     wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)), +                     wxListCtrl:setItem(Grid, Row, 3, observer_lib:to_str(MaxV div 1024)),  		     Row + 1  	     end,      wx:foldl(Update, 0, Fields), @@ -269,7 +291,9 @@ create_mem_info(Parent) ->  		   end,      ListItems = [{"Allocator Type",  ?wxLIST_FORMAT_LEFT,  200},  		 {"Block size (kB)",  ?wxLIST_FORMAT_RIGHT, 150}, -		 {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}], +		 {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}, +                 {"Max Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150} +                ],      lists:foldl(AddListEntry, 0, ListItems),      wxListItem:destroy(Li), diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index d41cb8ca42..2a481966da 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -18,7 +18,7 @@  %% %CopyrightEnd%  -module(observer_app_wx). --export([start_link/2]). +-export([start_link/3]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -73,10 +73,10 @@  -define(wxGC, wxGraphicsContext). -start_link(Notebook, Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, _Config]) ->      Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)},  				   {winid, 1}  				  ]), @@ -258,6 +258,8 @@ handle_sync_event(#wx{event = #wxPaint{}},_,      destroy_gc(GC),      ok.  %%%%%%%%%% +handle_call(get_config, _, State) -> +    {reply, #{}, State};  handle_call(Event, From, _State) ->      error({unhandled_call, Event, From}). @@ -318,7 +320,7 @@ handle_info({'EXIT', _, noconnection}, State) ->  handle_info({'EXIT', _, normal}, State) ->      {noreply, State};  handle_info(_Event, State) -> -    %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), +    %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),      {noreply, State}.  %%%%%%%%%% diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl index 1f1306c370..22b4714d63 100644 --- a/lib/observer/src/observer_html_lib.erl +++ b/lib/observer/src/observer_html_lib.erl @@ -142,13 +142,13 @@ dict_table(Tab,{Key0,Value0}, Even) ->      tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]).  proc_state(Tab,{Key0,Value0}, Even) -> -    Key = lists:flatten(io_lib:format("~s",[Key0])), +    Key = lists:flatten(io_lib:format("~ts",[Key0])),      Value = all_or_expand(Tab,Value0),      tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]).  all_or_expand(Tab,Term) -> -    Preview = io_lib:format("~P",[Term,8]), -    Check = io_lib:format("~P",[Term,100]), +    Preview = io_lib:format("~tP",[Term,8]), +    Check = io_lib:format("~tP",[Term,100]),      Exp = Preview=/=Check,      all_or_expand(Tab,Term,Preview,Exp).  all_or_expand(_Tab,Term,Str,false) @@ -166,13 +166,8 @@ all_or_expand(Tab,Term,Preview,true)  	  "Click to expand above term")];  all_or_expand(Tab,Bin,_PreviewStr,_Expand)    when is_binary(Bin) -> -    Size = byte_size(Bin), -    PrevSize = min(Size, 10) * 8, -    <<Preview:PrevSize, _/binary>> = Bin, -    Hash = erlang:phash2(Bin), -    Key = {Preview, Size, Hash}, -    ets:insert(Tab,{Key,Bin}), -    Term = io_lib:format("~p", [['#OBSBin',Preview,Size,Hash]]), +    OBSBin = observer_lib:make_obsbin(Bin,Tab), +    Term = io_lib:format("~tp", [OBSBin]),      href_proc_port(lists:flatten(Term), true).  color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); @@ -339,26 +334,35 @@ href_proc_bin(From, T, Acc, LTB) ->      BinStr =  	case string:tokens(OffsetSizePos,",.| \n") of  	    [Offset,SizeStr,Pos] when From =:= cdv -> -		Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, -		{ok,PreviewBin} = crashdump_viewer:expand_binary(Id), -		PreviewStr = preview_string(list_to_integer(SizeStr), PreviewBin), -		if LTB -> -			href("TARGET=\"expanded\"", -			     ["#Binary?offset="++Offset++ -				  "&size="++SizeStr++ -				  "&pos="++Pos], -			     PreviewStr); -		   true -> -			PreviewStr -		end; -	    [Preview,SizeStr,Md5] when From =:= obs -> +                Size = list_to_integer(SizeStr), +                PreviewSize = min(Size,10), +		Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)}, +                case crashdump_viewer:expand_binary(Id) of +                    {ok, '#CDVTruncatedBinary'} -> +                          lists:flatten( +                            "<FONT COLOR=\"#FF0000\">" +                            "<<...(Truncated Binary)>>" +                            "</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), -		PrevSize =  min(Size, 10) * 8, -		PreviewStr = preview_string(Size, -					    <<(list_to_integer(Preview)):PrevSize>>), +                PreviewInt = list_to_integer(PreviewIntStr), +                PreviewBitSize = list_to_integer(PreviewBitSizeStr), +		PreviewStr = preview_string(Size,<<PreviewInt:PreviewBitSize>>),  		if LTB ->  			href("TARGET=\"expanded\"", -			     ["#OBSBinary?key1="++Preview++ +			     ["#OBSBinary?key1="++PreviewIntStr++  				  "&key2="++SizeStr++  				  "&key3="++Md5],  			     PreviewStr); @@ -372,14 +376,14 @@ href_proc_bin(From, T, Acc, LTB) ->  preview_string(Size, PreviewBin) when Size > 10 ->      ["<<", -     remove_lgt(io_lib:format("~p",[PreviewBin])), +     remove_lgt(io_lib:format("~tp",[PreviewBin])),       "...(",       observer_lib:to_str({bytes,Size}),       ")",       ">>"];  preview_string(_, PreviewBin) ->      ["<<", -     remove_lgt(io_lib:format("~p",[PreviewBin])), +     remove_lgt(io_lib:format("~tp",[PreviewBin])),       ">>"].  remove_lgt(Deep) -> diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 7ce4cf45c7..94d199e688 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -21,16 +21,17 @@  -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,  	 wait_for_progress/0, report_progress/1,  	 user_term/3, user_term_multiline/3, -	 interval_dialog/4, start_timer/1, stop_timer/1, +	 interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1,  	 display_info/2, display_info/3, fill_info/2, update_info/2, to_str/1,  	 create_menus/3, create_menu_item/3,  	 create_attrs/0,  	 set_listctrl_col_size/2,  	 create_status_bar/1, -	 html_window/1, html_window/2 +	 html_window/1, html_window/2, +         make_obsbin/2  	]).  -include_lib("wx/include/wx.hrl"). @@ -39,6 +40,7 @@  -define(SINGLE_LINE_STYLE, ?wxBORDER_NONE bor ?wxTE_READONLY bor ?wxTE_RICH2).  -define(MULTI_LINE_STYLE, ?SINGLE_LINE_STYLE bor ?wxTE_MULTILINE). +-define(pulse_timeout,50).  get_wx_parent(Window) ->      Parent = wxWindow:getParent(Window), @@ -90,6 +92,12 @@ stop_timer(Timer = {true, _}) -> Timer;  stop_timer(Timer = {_, Intv}) ->      setup_timer(false, Timer),      {true, Intv}. + +start_timer(#{interval:=Intv}, _Def) -> +    setup_timer(true, {false, Intv}); +start_timer(_, Def) -> +    setup_timer(true, {false, Def}). +  start_timer(Intv) when is_integer(Intv) ->      setup_timer(true, {true, Intv});  start_timer(Timer) -> @@ -105,6 +113,11 @@ setup_timer(Bool, {Timer, Old}) ->      timer:cancel(Timer),      setup_timer(Bool, {false, Old}). +timer_config({_, Interval}) -> +    #{interval=>Interval}; +timer_config(#{}=Config) -> +    Config. +  display_info_dialog(Parent,Str) ->      display_info_dialog(Parent,"",Str).  display_info_dialog(Parent,Title,Str) -> @@ -162,13 +175,13 @@ fill_info([{Str,Attrib,Key}|Rest], Data) when is_atom(Key); is_function(Key) ->  	Value -> [{Str,Attrib,Value} | fill_info(Rest, Data)]      end;  fill_info([{Str, {Format, Key}}|Rest], Data) -  when is_atom(Key); is_function(Key), is_atom(Format) -> +  when is_atom(Key); is_function(Key) ->      case get_value(Key, Data) of  	undefined -> [undefined | fill_info(Rest, Data)];  	Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)]      end;  fill_info([{Str, Attrib, {Format, Key}}|Rest], Data) -  when is_atom(Key); is_function(Key), is_atom(Format) -> +  when is_atom(Key); is_function(Key) ->      case get_value(Key, Data) of  	undefined -> [undefined | fill_info(Rest, Data)];  	Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)] @@ -241,6 +254,8 @@ to_str({bytes, B}) ->  	KB >  0 -> integer_to_list(KB) ++ " kB";  	true -> integer_to_list(B) ++ " B"      end; +to_str({{words,WSz}, Sz}) -> +    to_str({bytes, WSz*Sz});  to_str({time_ms, MS}) ->      S = MS div 1000,      Min = S div 60, @@ -284,8 +299,10 @@ to_str(No) when is_integer(No) ->      integer_to_list(No);  to_str(Float) when is_float(Float) ->      io_lib:format("~.3f", [Float]); +to_str({trunc, Float}) when is_float(Float) -> +    float_to_list(Float, [{decimals,0}]);  to_str(Term) -> -    io_lib:format("~w", [Term]). +    io_lib:format("~tw", [Term]).  create_menus([], _MenuBar, _Type) -> ok;  create_menus(Menus, MenuBar, Type) -> @@ -505,7 +522,7 @@ link_entry2(Panel,{Target,Str},Cursor) ->      TC.  to_link(RegName={Name, Node}) when is_atom(Name), is_atom(Node) -> -    Str = io_lib:format("{~p,~p}", [Name, Node]), +    Str = io_lib:format("{~tp,~p}", [Name, Node]),      {RegName, Str};  to_link(TI = {_Target, _Identifier}) ->      TI; @@ -623,14 +640,14 @@ user_term_multiline(Parent, Title, Default) ->  parse_string(Str) ->      try -	Tokens = case erl_scan:string(Str) of +	Tokens = case erl_scan:string(Str, 1, [text]) of  		     {ok, Ts, _} -> Ts;  		     {error, {_SLine, SMod, SError}, _} -> -			 throw(io_lib:format("~s", [SMod:format_error(SError)])) +			 throw(io_lib:format("~ts", [SMod:format_error(SError)]))  		 end, -	case erl_parse:parse_term(Tokens) of +	case lib:extended_parse_term(Tokens) of  	    {error, {_PLine, PMod, PError}} -> -		throw(io_lib:format("~s", [PMod:format_error(PError)])); +		throw(io_lib:format("~ts", [PMod:format_error(PError)]));  	    Res -> Res  	end      catch @@ -672,11 +689,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. @@ -700,31 +717,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); @@ -732,25 +756,77 @@ 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), +    Gauge = wxGauge:new(Panel, 2, 100, [{size, {170, -1}}, +                                        {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,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 +            %% cluster - thus trying string:length and slice. +            PL1 = min(string:length(Bin), 10), +            PB1 = string:slice(Bin,0,PL1), +            PS1 = byte_size(PB1) * 8, +            <<P1:PS1>> = PB1, +            {P1,PS1} +        catch _:_ -> +                %% Probably not a string, so just split anywhere +                PS2 = min(Size, 10) * 8, +                <<P2:PS2, _/binary>> = Bin, +                {P2,PS2} +        end, +    Hash = erlang:phash2(Bin), +    Key = {Preview, Size, Hash}, +    ets:insert(Tab, {Key,Bin}), +    ['#OBSBin',Preview,PreviewBitSize,Size,Hash]. diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index b0ead42e3f..5adfadb16e 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2012-2016. All Rights Reserved. +%% Copyright Ericsson AB 2012-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@  %% %CopyrightEnd%  -module(observer_perf_wx). --export([start_link/2]). +-export([start_link/3]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -55,12 +55,12 @@  -define(wxGC, wxGraphicsContext). --record(paint, {font, small, pen, pen2, pens, usegc = false}). +-record(paint, {font, small, pen, pen2, pens, dot_pens, usegc = false}). -start_link(Notebook, Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) ->      try  	Panel = wxPanel:new(Notebook),  	Main  = wxBoxSizer:new(?wxVERTICAL), @@ -81,11 +81,13 @@ init([Notebook, Parent]) ->  			panel =Panel,  			wins = Windows,  			paint=PaintInfo, -			samples=reset_data() +			samples=reset_data(), +                        time=#ti{fetch=maps:get(fetch, Config, ?FETCH_DATA), +                                 secs=maps:get(secs, Config, ?DISP_SECONDS)}  		       },  	{Panel, State0}      catch _:Err -> -	    io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]), +	    io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, erlang:get_stacktrace()]),  	    {stop, Err}      end. @@ -124,13 +126,17 @@ setup_graph_drawing(Panels) ->  		  {F, SF}  	  end,      BlackPen = wxPen:new({0,0,0}, [{width, 1}]), -    Pens = [wxPen:new(Col, [{width, 1}]) || Col <- tuple_to_list(colors())], +    Pens = [wxPen:new(Col, [{width, 1}, {style, ?wxSOLID}]) +            || Col <- tuple_to_list(colors())], +    DotPens = [wxPen:new(Col, [{width, 1}, {style, ?wxDOT}]) +               || Col <- tuple_to_list(colors())],      #paint{usegc = UseGC,  	   font  = Font,  	   small = SmallFont,  	   pen   = ?wxGREY_PEN,  	   pen2  = BlackPen, -	   pens  = list_to_tuple(Pens) +	   pens  = list_to_tuple(Pens), +           dot_pens = list_to_tuple(DotPens)  	  }. @@ -173,6 +179,10 @@ refresh_panel(Active, #win{name=_Id, panel=Panel}=Win, Ti, #paint{usegc=UseGC}=P      destroy_gc(GC).  %%%%%%%%%% +handle_call(get_config, _, #state{time=Ti}=State) -> +    #ti{fetch=Fetch, secs=Range} = Ti, +    {reply, #{fetch=>Fetch, secs=>Range}, State}; +  handle_call(Event, From, _State) ->      error({unhandled_call, Event, From}). @@ -181,17 +191,17 @@ handle_cast(Event, _State) ->  %%%%%%%%%%  handle_info({stats, 1, _, _, _} = Stats,  	    #state{panel=Panel, samples=Data, active=Active, wins=Wins0, -		   time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) -> +                   appmon=Node, time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) ->      if Active ->  	    Disp = trunc(Disp0),  	    Next = max(Tick - Disp, 0),  	    erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), -	    {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active), +	    {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node),  	    State = precalc(State0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples}),  	    wxWindow:refresh(Panel),  	    {noreply, State};         true -> -	    {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active), +	    {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node),  	    Wins = [W#win{max=undefined} || W <- Wins1],  	    {noreply, State0#state{samples=Samples, wins=Wins, time=Ti#ti{tick=0}}}      end; @@ -206,7 +216,7 @@ handle_info({refresh, Seq}, #state{panel=Panel, time=#ti{tick=Seq, disp=DispF}=T  handle_info({refresh, _}, State) ->      {noreply, State}; -handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old, time=_Ti} = State) -> +handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old} = State) ->      create_menus(Parent, []),      try  	Node = node(Old), @@ -225,7 +235,7 @@ handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) ->      {noreply, State#state{active=false, appmon=undefined}};  handle_info(_Event, State) -> -    %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), +    %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),      {noreply, State}.  %%%%%%%%%% @@ -247,13 +257,17 @@ restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti, w  reset_data() ->      {0, queue:new()}. -add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active) when N > (Secs*Fetch+1) -> +add_data(Stats, Q, Wins, Ti, Active) -> +    add_data(Stats, Q, Wins, Ti, Active, ignore). + +add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active, Node) +  when N > (Secs*Fetch+1) ->      {{value, Drop}, Q} = queue:out(Q0), -    add_data_1(Wins, Stats, N, {Drop,Q}, Active); -add_data(Stats, {N, Q}, Wins, _, Active) -> -    add_data_1(Wins, Stats, N+1, {empty, Q}, Active). +    add_data_1(Wins, Stats, N, {Drop,Q}, Active, Node); +add_data(Stats, {N, Q}, Wins, _, Active, Node) -> +    add_data_1(Wins, Stats, N+1, {empty, Q}, Active, Node). -add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active) +add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active, Node)    when St /= undefined ->      try  	{Wins, Stat} = @@ -269,14 +283,12 @@ add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active)  			   end, #{}, Wins0),  	{Wins, {N,queue:in(Stat#{}, Q)}}      catch no_scheduler_change -> -	    {[Win#win{state=init_data(Id, Last), -		      info = info(Id, Last)} +	    {[Win#win{state=init_data(Id, Last), info=info(Id, Last, Node)}  	      || #win{name=Id}=Win <- Wins0], {0,queue:new()}}      end; -add_data_1(Wins, Stats, 1, {_, Q}, _) -> -    {[Win#win{state=init_data(Id, Stats), -	      info = info(Id, Stats)} +add_data_1(Wins, Stats, 1, {_, Q}, _, Node) -> +    {[Win#win{state=init_data(Id, Stats), info=info(Id, Stats, Node)}        || #win{name=Id}=Win <- Wins], {0,Q}}.  add_data_2(#win{name=Id, state=S0}=Win, Stats, Map) -> @@ -382,16 +394,24 @@ lmax(MState, Values, State) ->  init_data(runq, {stats, _, T0, _, _}) -> {mk_max(),lists:sort(T0)};  init_data(io,   {stats, _, _, {{_,In0}, {_,Out0}}, _}) -> {mk_max(), {In0,Out0}}; -init_data(memory, _) -> {mk_max(), info(memory, undefined)}; +init_data(memory, _) -> {mk_max(), info(memory, undefined, undefined)};  init_data(alloc, _) -> {mk_max(), unused};  init_data(utilz, _) -> {mk_max(), unused}. -info(runq, {stats, _, T0, _, _}) -> lists:seq(1, length(T0)); -info(memory, _) -> [total, processes, atom, binary, code, ets]; -info(io, _) -> [input, output]; -info(alloc, First) -> [Type || {Type, _, _} <- First]; -info(utilz, First) -> [Type || {Type, _, _} <- First]; -info(_, []) -> []. +info(runq, {stats, _, T0, _, _}, Node) -> +    Dirty = get_dirty_cpu(Node), +    {lists:seq(1, length(T0)-Dirty), Dirty}; +info(memory, _, _) -> [total, processes, atom, binary, code, ets]; +info(io, _, _) -> [input, output]; +info(alloc, First, _) -> [Type || {Type, _, _} <- First]; +info(utilz, First, _) -> [Type || {Type, _, _} <- First]; +info(_, [], _) -> []. + +get_dirty_cpu(Node) -> +    case rpc:call(node(Node), erlang, system_info, [dirty_cpu_schedulers]) of +        {badrpc,_R} -> 0; +        N -> N +    end.  collect_data(runq, {stats, _, T0, _, _}, {Max,S0}) ->      S1 = lists:sort(T0), @@ -471,9 +491,10 @@ window_geom({W,H}, {_, Max, _Unit, MaxUnit},  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -draw_win(DC, #win{no_samples=Samples, geom=#{scale:={WS,HS}}, graphs=Graphs, max={_,Max,_,_}}=Win, +draw_win(DC, #win{name=Name, no_samples=Samples, geom=#{scale:={WS,HS}}, +                  graphs=Graphs, max={_,Max,_,_}, info=Info}=Win,  	 #ti{tick=Tick, fetch=FetchFreq, secs=Secs, disp=DispFreq}=Ti, -	 Paint=#paint{pens=Pens}) when Samples >= 2, Graphs =/= [] -> +	 Paint=#paint{pens=Pens, dot_pens=Dots}) when Samples >= 2, Graphs =/= [] ->      %% Draw graphs      {X0,Y0,DrawBs} = draw_borders(DC, Ti, Win, Paint),      Offset = Tick / DispFreq, @@ -483,14 +504,23 @@ draw_win(DC, #win{no_samples=Samples, geom=#{scale:={WS,HS}}, graphs=Graphs, max  	   end,      Start = X0 + (max(Secs*FetchFreq+Full-Samples, 0) - Offset)*WS,      Last = Secs*FetchFreq*WS+X0, +    Dirty = case {Name, Info} of +                {runq, {_, DCpu}} -> DCpu; +                _ -> 0 +            end, +    NoGraphs = length(Graphs), +    NoCpu = NoGraphs - Dirty,      Draw = fun(Lines0, N) -> -		   setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)), +                   case Dirty > 0 andalso N > NoCpu of +                       true  -> setPen(DC, element(1+ ((N-NoCpu-1) rem tuple_size(Dots)), Dots)); +                       false -> setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)) +                   end,  		   Order = lists:reverse(Lines0),  		   [{_,Y}|Lines] = translate(Order, {Start, Y0}, 0, WS, {X0,Max*HS,Last}, []),  		   strokeLines(DC, [{Last,Y}|Lines]),  		   N-1  	   end, -    lists:foldl(Draw, length(Graphs), Graphs), +    lists:foldl(Draw, NoGraphs, Graphs),      DrawBs(),      ok; @@ -655,11 +685,17 @@ draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq},      case Type of  	runq -> +            {TextInfo, DirtyCpus} = Info,  	    drawText(DC, "Scheduler Utilization (%) ", TopTextX, ?BH),  	    TN0 = Text(TopTextX, BottomTextY, "Scheduler: ", 0), -	    lists:foldl(fun(Id, Pos0) -> -				Text(Pos0, BottomTextY, integer_to_list(Id), Id) -			end, TN0, Info); +            Id = fun(Id, Pos0) -> +                         Text(Pos0, BottomTextY, integer_to_list(Id), Id) +                 end, +	    TN1 = lists:foldl(Id, TN0, TextInfo), +            TN2 = Text(TN1, BottomTextY, "Dirty cpu: ", 0), +	    TN3 = lists:foldl(Id, TN2, lists:seq(1, DirtyCpus)), +            _ = Text(TN3, BottomTextY, "(dotted)", 0), +            ok;  	memory ->  	    drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH),  	    lists:foldl(fun(MType, {PenId, Pos0}) -> @@ -748,10 +784,10 @@ calc_max1(Max) ->      end.  colors() -> -    {{240, 100, 100}, {100, 240, 100}, {100, 100, 240}, -     {220, 220, 80}, {100, 240, 240}, {240, 100, 240}, -     {100, 25, 25}, {25, 100, 25}, {25, 25, 100}, -     {120, 120, 0}, {25, 100, 100}, {100, 50, 100} +    {{240, 100, 100}, {0, 128, 0},     {25, 45, 170},  {255, 165, 0}, +     {220, 220, 40},  {100, 240, 240},{240, 100, 240}, {160, 40,  40}, +     {100, 100, 240}, {140, 140, 0},  {25, 200, 100},  {120, 25, 240}, +     {255, 140, 163}, {25, 120, 120}, {120, 25, 120},  {110, 90, 60}      }.  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index 5b9e5c5671..5908e99e36 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -18,7 +18,7 @@  %% %CopyrightEnd%  -module(observer_port_wx). --export([start_link/2]). +-export([start_link/3]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -69,7 +69,7 @@  	  parent,  	  grid,  	  panel, -	  node=node(), +	  node={node(),true},  	  opt=#opt{},  	  right_clicked_port,  	  ports, @@ -77,10 +77,10 @@  	  open_wins=[]  	}). -start_link(Notebook,  Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook,  Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) ->      Panel = wxPanel:new(Notebook),      Sizer = wxBoxSizer:new(?wxVERTICAL),      Style = ?wxLC_REPORT bor ?wxLC_HRULES, @@ -110,12 +110,12 @@ init([Notebook, Parent]) ->      wxListCtrl:connect(Grid, size, [{skip, true}]),      wxWindow:setFocus(Grid), -    {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}. +    {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config}}.  handle_event(#wx{id=?ID_REFRESH},  	     State = #state{node=Node, grid=Grid, opt=Opt}) ->      Ports0 = get_ports(Node), -    Ports = update_grid(Grid, Opt, Ports0), +    Ports = update_grid(Grid, sel(State), Opt, Ports0),      {noreply, State#state{ports=Ports}};  handle_event(#wx{obj=Obj, event=#wxClose{}}, #state{open_wins=Opened} = State) -> @@ -134,7 +134,7 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},  	      NewKey -> Opt0#opt{sort_key=NewKey}  	  end,      Ports0 = get_ports(Node), -    Ports = update_grid(Grid, Opt, Ports0), +    Ports = update_grid(Grid, sel(State), Opt, Ports0),      wxWindow:setFocus(Grid),      {noreply, State#state{opt=Opt, ports=Ports}}; @@ -260,6 +260,9 @@ handle_event(Event, _State) ->  handle_sync_event(_Event, _Obj, _State) ->      ok. +handle_call(get_config, _, #state{timer=Timer}=State) -> +    {reply, observer_lib:timer_config(Timer), State}; +  handle_call(Event, From, _State) ->      error({unhandled_call, Event, From}). @@ -267,19 +270,39 @@ handle_cast(Event, _State) ->      error({unhandled_cast, Event}).  handle_info({portinfo_open, PortIdStr}, -	    State = #state{node=Node, grid=Grid, opt=Opt, open_wins=Opened}) -> -    Ports0 = get_ports(Node), -    Ports = update_grid(Grid, Opt, Ports0), -    Port = lists:keyfind(PortIdStr, #port.id_str, Ports), -    NewOpened = -        case Port of -            false -> -                self() ! {error,"No such port: " ++ PortIdStr}, -                Opened; +	    State = #state{node={ActiveNodeName,ActiveAvailable}, grid=Grid, +                           opt=Opt, open_wins=Opened}) -> +    NodeName = node(list_to_port(PortIdStr)), +    Available = +        case NodeName of +            ActiveNodeName -> +                ActiveAvailable;              _ -> -                display_port_info(Grid, Port, Opened) +                portinfo_available(NodeName)          end, -    {noreply, State#state{ports=Ports, open_wins=NewOpened}}; +    if Available -> +            Ports0 = get_ports({NodeName,Available}), +            Port = lists:keyfind(PortIdStr, #port.id_str, Ports0), +            NewOpened = +                case Port of +                    false -> +                        self() ! {error,"No such port: " ++ PortIdStr}, +                        Opened; +                    _ -> +                        display_port_info(Grid, Port, Opened) +                end, +            Ports = +                case NodeName of +                    ActiveNodeName -> +                        update_grid(Grid, sel(State), Opt, Ports0); +                    _ -> +                        State#state.ports +                end, +            {noreply, State#state{ports=Ports, open_wins=NewOpened}}; +       true -> +            popup_unavailable_info(NodeName), +            {noreply, State} +    end;  handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt,                                               ports=OldPorts}) -> @@ -288,25 +311,34 @@ handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt,              %% no change              {noreply, State};          Ports0 -> -            Ports = update_grid(Grid, Opt, Ports0), +            Ports = update_grid(Grid, sel(State), Opt, Ports0),              {noreply, State#state{ports=Ports}}      end; -handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, -					   timer=Timer0}) -> -    Ports0 = get_ports(Node), -    Ports = update_grid(Grid, Opt, Ports0), +handle_info({active, NodeName}, State = #state{parent=Parent, grid=Grid, opt=Opt, +                                               timer=Timer0}) -> +    Available = portinfo_available(NodeName), +    Available orelse popup_unavailable_info(NodeName), +    Ports0 = get_ports({NodeName,Available}), +    Ports = update_grid(Grid, sel(State), Opt, Ports0),      wxWindow:setFocus(Grid),      create_menus(Parent), -    Timer = observer_lib:start_timer(Timer0), -    {noreply, State#state{node=Node, ports=Ports, timer=Timer}}; +    Timer = observer_lib:start_timer(Timer0, 10), +    {noreply, State#state{node={NodeName,Available}, ports=Ports, timer=Timer}};  handle_info(not_active, State = #state{timer = Timer0}) ->      Timer = observer_lib:stop_timer(Timer0),      {noreply, State#state{timer=Timer}}; +handle_info({info, {port_info_not_available,NodeName}}, +            State = #state{panel=Panel}) -> +    Str = io_lib:format("Can not fetch port info from ~p.~n" +                        "Too old OTP version.",[NodeName]), +    observer_lib:display_info_dialog(Panel, Str), +    {noreply, State}; +  handle_info({error, Error}, #state{panel=Panel} = State) -> -    Str = io_lib:format("ERROR: ~s~n",[Error]), +    Str = io_lib:format("ERROR: ~ts~n",[Error]),      observer_lib:display_info_dialog(Panel, Str),      {noreply, State}; @@ -338,16 +370,18 @@ create_menus(Parent) ->  	],      observer_wx:create_menus(Parent, MenuEntries). -get_ports(Node) -> -    case get_ports2(Node) of +get_ports({_NodeName,false}) -> +    []; +get_ports({NodeName,true}) -> +    case get_ports2(NodeName) of  	Error = {error, _} ->  	    self() ! Error,  	    [];  	Res ->  	    Res      end. -get_ports2(Node) -> -    case rpc:call(Node, observer_backend, get_port_list, []) of +get_ports2(NodeName) -> +    case rpc:call(NodeName, observer_backend, get_port_list, []) of  	{badrpc, Error} ->  	    {error, Error};  	Error = {error, _} -> @@ -511,9 +545,9 @@ filter_monitor_info() ->  	    [Pid || {process, Pid} <- Ms]      end. -update_grid(Grid, Opt, Ports) -> -    wx:batch(fun() -> update_grid2(Grid, Opt, Ports) end). -update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> +update_grid(Grid, Sel, Opt, Ports) -> +    wx:batch(fun() -> update_grid2(Grid, Sel, Opt, Ports) end). +update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir}, Ports) ->      wxListCtrl:deleteAllItems(Grid),      Update =  	fun(#port{id = Id, @@ -533,6 +567,12 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) ->  							 observer_lib:to_str(Val))  			      end,  			      [{0,Id},{1,Connected},{2,Name},{3,Ctrl},{4,Slot}]), +                case lists:member(Id, Sel) of +                    true -> +                        wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED); +                    false -> +                        wxListCtrl:setItemState(Grid, Row, 0, ?wxLIST_STATE_SELECTED) +                end,  		Row + 1  	end,      PortInfo = case Dir of @@ -542,6 +582,8 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) ->      lists:foldl(Update, 0, PortInfo),      PortInfo. +sel(#state{grid=Grid, ports=Ports}) -> +    [Id || #port{id=Id} <- get_selected_items(Grid, Ports)].  get_selected_items(Grid, Data) ->      get_indecies(get_selected_items(Grid, -1, []), Data). @@ -563,3 +605,15 @@ get_indecies(Rest = [_|_], I, [_|T]) ->      get_indecies(Rest, I+1, T);  get_indecies(_, _, _) ->      []. + +portinfo_available(NodeName) -> +    _ = rpc:call(NodeName, code, ensure_loaded, [observer_backend]), +    case rpc:call(NodeName, erlang, function_exported, +                  [observer_backend, get_port_list, 0]) of +        true  -> true; +        false -> false +    end. + +popup_unavailable_info(NodeName) -> +    self() ! {info, {port_info_not_available, NodeName}}, +    ok. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index f07b9e295a..2e5fe0bc1a 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@  -behaviour(wx_object). --export([start_link/2]). +-export([start_link/3]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -67,12 +67,14 @@  -record(holder, {parent,  		 info, -		 etop, +                 next=[],  		 sort=#sort{},  		 accum=[], +                 next_accum=[],  		 attrs,  		 node, -		 backend_pid +		 backend_pid, +                 old_backend=false  		}).  -record(state, {parent, @@ -86,18 +88,19 @@  		right_clicked_pid,  		holder}). -start_link(Notebook, Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []).  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) ->      Attrs = observer_lib:create_attrs(),      Self = self(), -    Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), -    {ProPanel, State} = setup(Notebook, Parent, Holder), +    Acc = maps:get(acc, Config, false), +    Holder = spawn_link(fun() -> init_table_holder(Self, Acc, Attrs) end), +    {ProPanel, State} = setup(Notebook, Parent, Holder, Config),      {ProPanel, State#state{holder=Holder}}. -setup(Notebook, Parent, Holder) -> +setup(Notebook, Parent, Holder, Config) ->      ProPanel = wxPanel:new(Notebook, []),      Grid  = create_list_box(ProPanel, Holder), @@ -113,7 +116,7 @@ setup(Notebook, Parent, Holder) ->  		   panel=ProPanel,  		   parent_notebook=Notebook,  		   holder=Holder, -		   timer={false, 10} +		   timer=Config  		   },      {ProPanel, State}. @@ -214,7 +217,7 @@ call(Holder, What) ->  	    erlang:demonitor(Ref),  	    Res      after 2000 -> -	    io:format("Hanging call ~p~n",[What]), +	    io:format("Hanging call ~tp~n",[What]),  	    ""      end. @@ -225,7 +228,7 @@ handle_info({holder_updated, Count}, State0=#state{grid=Grid}) ->      wxListCtrl:setItemCount(Grid, Count),      Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1), - +    observer_wx:set_status(io_lib:format("Number of Processes: ~w", [Count])),      {noreply, State};  handle_info(refresh_interval, #state{holder=Holder}=State) -> @@ -246,14 +249,14 @@ handle_info({active, Node},  	    #state{holder=Holder, timer=Timer, parent=Parent}=State) ->      create_pro_menu(Parent, Holder),      Holder ! {change_node, Node}, -    {noreply, State#state{timer=observer_lib:start_timer(Timer)}}; +    {noreply, State#state{timer=observer_lib:start_timer(Timer, 10)}};  handle_info(not_active, #state{timer=Timer0}=State) ->      Timer = observer_lib:stop_timer(Timer0),      {noreply, State#state{timer=Timer}};  handle_info(Info, State) -> -    io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p, Unexpected info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, #state{holder=Holder}) -> @@ -264,13 +267,17 @@ terminate(_Reason, #state{holder=Holder}) ->  code_change(_, _, State) ->      {ok, State}. +handle_call(get_config, _, #state{holder=Holder, timer=Timer}=State) -> +    Conf = observer_lib:timer_config(Timer), +    Accum = call(Holder, {get_accum, self()}), +    {reply, Conf#{acc=>Accum}, State}; +  handle_call(Msg, _From, State) -> -    io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p:~p: Unhandled call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}. -  handle_cast(Msg, State) -> -    io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), +    io:format("~p:~p: Unhandled cast ~tp~n", [?MODULE, ?LINE, Msg]),      {noreply, State}.  %%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -394,7 +401,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated}},      {noreply, State#state{procinfo_menu_pids=Opened2}};  handle_event(Event, State) -> -    io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]), +    io:format("~p:~p: handle event ~tp\n", [?MODULE, ?LINE, Event]),      {noreply, State}. @@ -453,18 +460,23 @@ rm_selected(_, [], [], AccIds, AccPids) ->  %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init_table_holder(Parent, Attrs) -> -    Backend = spawn_link(node(), observer_backend,etop_collect,[self()]), +init_table_holder(Parent, Accum0, Attrs) -> +    process_flag(trap_exit, true), +    Backend = spawn_link(node(), observer_backend, procs_info, [self()]), +    Accum = case Accum0 of +                true -> true; +                false -> [] +            end,      table_holder(#holder{parent=Parent, -			 etop=#etop_info{},  			 info=array:new(),  			 node=node(),  			 backend_pid=Backend, -			 attrs=Attrs +			 attrs=Attrs, +                         accum=Accum  			}).  table_holder(#holder{info=Info, attrs=Attrs, -		     node=Node, backend_pid=Backend}=S0) -> +		     node=Node, backend_pid=Backend, old_backend=Old}=S0) ->      receive  	{get_row, From, Row, Col} ->  	    get_row(From, Row, Col, Info), @@ -472,14 +484,25 @@ table_holder(#holder{info=Info, attrs=Attrs,  	{get_attr, From, Row} ->  	    get_attr(From, Row, Attrs),  	    table_holder(S0); +        {procs_info, Backend, Procs} -> +	    State = handle_update(Procs, S0), +	    table_holder(State); +        {'EXIT', Backend, normal} when Old =:= false -> +            S1 = update_complete(S0), +            table_holder(S1#holder{backend_pid=undefined});  	{Backend, EtopInfo=#etop_info{}} -> -	    State = handle_update(EtopInfo, S0), +	    State = handle_update_old(EtopInfo, S0),  	    table_holder(State#holder{backend_pid=undefined});  	refresh when is_pid(Backend)->  	    table_holder(S0); %% Already updating  	refresh -> -	    Pid = spawn_link(Node,observer_backend,etop_collect,[self()]), -	    table_holder(S0#holder{backend_pid=Pid}); +            Pid = case Old of +                      true -> +                          spawn_link(Node, observer_backend, etop_collect, [self()]); +                      false -> +                          spawn_link(Node, observer_backend, procs_info, [self()]) +                  end, +            table_holder(S0#holder{backend_pid=Pid});  	{change_sort, Col} ->  	    State = change_sort(Col, S0),  	    table_holder(State); @@ -492,7 +515,6 @@ table_holder(#holder{info=Info, attrs=Attrs,  	{get_name_or_pid, From, Indices} ->  	    get_name_or_pid(From, Indices, Info),  	    table_holder(S0); -  	{get_node, From} ->  	    From ! {self(), Node},  	    table_holder(S0); @@ -501,36 +523,50 @@ table_holder(#holder{info=Info, attrs=Attrs,  		true ->  		    table_holder(S0);  		false -> -		    self() ! refresh, -		    table_holder(S0#holder{node=NewNode}) -	    end; +                    _ = rpc:call(NewNode, code, ensure_loaded, [observer_backend]), +                    case rpc:call(NewNode, erlang, function_exported, +                                  [observer_backend,procs_info, 1]) of +                        true -> +                            self() ! refresh, +                            table_holder(S0#holder{node=NewNode, old_backend=false}); +                        false -> +                            self() ! refresh, +                            table_holder(S0#holder{node=NewNode, old_backend=true}); +                        _ -> +                            table_holder(S0) +                    end +            end;  	{accum, Bool} ->  	    table_holder(change_accum(Bool,S0));  	{get_accum, From} ->  	    From ! {self(), S0#holder.accum == true},  	    table_holder(S0);  	{dump, Fd} -> -	    EtopInfo = (S0#holder.etop)#etop_info{procinfo=array:to_list(Info)}, -            %% The empty #etop_info{} below is a dummy previous info -            %% value. It is used by etop to calculate the scheduler -            %% utilization since last update. When dumping to file, -            %% there is no previous measurement to use, so we just add -            %% a dummy here, and the value shown will be since the -            %% tool was started. -	    etop_txt:do_update(Fd, EtopInfo, #etop_info{}, #opts{node=Node}), -	    file:close(Fd), -	    table_holder(S0); +            Collector = spawn_link(Node, observer_backend, etop_collect,[self()]), +            receive +                {Collector, EtopInfo=#etop_info{}} -> +                    etop_txt:do_update(Fd, EtopInfo, #etop_info{}, #opts{node=Node}), +                    file:close(Fd), +                    table_holder(S0); +                {'EXIT', Collector, _} -> +                    table_holder(S0) +            end;  	stop ->  	    ok; -	What -> -	    io:format("Table holder got ~p~n",[What]), +        {'EXIT', Backend, normal} -> +            table_holder(S0); +        {'EXIT', Backend, _Reason} -> +            %% Node crashed will be noticed soon.. +            table_holder(S0#holder{backend_pid=undefined}); +	_What -> +            %% io:format("~p: Table holder got ~tp~n",[?MODULE, _What]),  	    table_holder(S0)      end.  change_sort(Col, S0=#holder{parent=Parent, info=Data, sort=Sort0}) ->      {Sort, ProcInfo}=sort(Col, Sort0, Data),      Parent ! {holder_updated, array:size(Data)}, -    S0#holder{info=ProcInfo, sort=Sort}. +    S0#holder{info=array:from_list(ProcInfo), sort=Sort}.  change_accum(true, S0) ->      S0#holder{accum=true}; @@ -538,23 +574,45 @@ change_accum(false, S0=#holder{info=Info}) ->      self() ! refresh,      S0#holder{accum=lists:sort(array:to_list(Info))}. -handle_update(EI=#etop_info{procinfo=ProcInfo0}, -	      S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> -    {ProcInfo1, S1} = accum(ProcInfo0, S0), +handle_update_old(#etop_info{procinfo=ProcInfo0}, +                  S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> +    {ProcInfo1, Accum} = accum(ProcInfo0, S0),      {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1), -    Parent ! {holder_updated, array:size(ProcInfo)}, -    S1#holder{info=ProcInfo, etop=EI#etop_info{procinfo=[]}}. +    Info = array:from_list(ProcInfo), +    Parent ! {holder_updated, array:size(Info)}, +    S0#holder{info=Info, accum=Accum}. + +handle_update(ProcInfo0, S0=#holder{next=Next, sort=#sort{sort_key=KeyField}}) -> +    {ProcInfo1, Accum} = accum(ProcInfo0, S0), +    Sort = sort_fun(KeyField, true), +    Merge = merge_fun(KeyField), +    Merged = Merge(Sort(ProcInfo1), Next), +    case Accum of +        true ->  S0#holder{next=Merged}; +        _List -> S0#holder{next=Merged, next_accum=Accum} +    end. -accum(ProcInfo, State=#holder{accum=true}) -> -    {ProcInfo, State}; -accum(ProcInfo0, State=#holder{accum=Previous}) -> +update_complete(#holder{parent=Parent, sort=#sort{sort_incr=Incr}, +                        next=ProcInfo, accum=Accum, next_accum=NextAccum}=S0) -> +    Info = case Incr of +               true -> array:from_list(ProcInfo); +               false -> array:from_list(lists:reverse(ProcInfo)) +           end, +    Parent ! {holder_updated, array:size(Info)}, +    S0#holder{info=Info, accum= Accum =:= true orelse NextAccum, +              next=[], next_accum=[]}. + +accum(ProcInfo, #holder{accum=true}) -> +    {ProcInfo, true}; +accum(ProcInfo0, #holder{accum=Previous, next_accum=Next}) -> +    Accum = [{Pid, Reds} || #etop_proc_info{pid=Pid, reds=Reds} <- ProcInfo0],      ProcInfo = lists:sort(ProcInfo0), -    {accum2(ProcInfo,Previous,[]), State#holder{accum=ProcInfo}}. +    {accum2(ProcInfo,Previous,[]), lists:merge(lists:sort(Accum), Next)}. -accum2([PI=#etop_proc_info{pid=Pid, reds=Reds, runtime=RT}|PIs], -       [#etop_proc_info{pid=Pid, reds=OldReds, runtime=OldRT}|Old], Acc) -> -    accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds, runtime=RT-OldRT}|Acc]); -accum2(PIs=[#etop_proc_info{pid=Pid}|_], [#etop_proc_info{pid=OldPid}|Old], Acc) +accum2([PI=#etop_proc_info{pid=Pid, reds=Reds}|PIs], +       [{Pid, OldReds}|Old], Acc) -> +    accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds}|Acc]); +accum2(PIs=[#etop_proc_info{pid=Pid}|_], [{OldPid,_}|Old], Acc)    when Pid > OldPid ->      accum2(PIs, Old, Acc);  accum2([PI|PIs], Old, Acc) -> @@ -565,14 +623,52 @@ sort(Col, Opt, Table)    when not is_list(Table) ->      sort(Col,Opt,array:to_list(Table));  sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) -> -    {Opt#sort{sort_incr=not Bool}, -     array:from_list(lists:reverse(Table))}; -sort(Col, S=#sort{sort_incr=true}, Table) -> -    {S#sort{sort_key=Col}, -     array:from_list(lists:keysort(col_to_element(Col), Table))}; -sort(Col, S=#sort{sort_incr=false}, Table) -> -    {S#sort{sort_key=Col}, -     array:from_list(lists:reverse(lists:keysort(col_to_element(Col), Table)))}. +    {Opt#sort{sort_incr=not Bool},lists:reverse(Table)}; +sort(Col, S=#sort{sort_incr=Incr}, Table) -> +    Sort = sort_fun(Col, Incr), +    {S#sort{sort_key=Col}, Sort(Table)}. + +sort_fun(?COL_NAME, true) -> +    fun(Table) -> lists:sort(fun sort_name/2, Table) end; +sort_fun(?COL_NAME, false) -> +    fun(Table) -> lists:sort(fun sort_name_rev/2, Table) end; +sort_fun(Col, true) -> +    N = col_to_element(Col), +    fun(Table) -> lists:keysort(N, Table) end; +sort_fun(Col, false) -> +    N = col_to_element(Col), +    fun(Table) -> lists:reverse(lists:keysort(N, Table)) end. + +merge_fun(?COL_NAME) -> +    fun(A,B) -> lists:merge(fun sort_name/2, A, B) end; +merge_fun(Col) -> +    KeyField = col_to_element(Col), +    fun(A,B) -> lists:keymerge(KeyField, A, B) end. + + +sort_name(#etop_proc_info{name={_,_,_}=A}, #etop_proc_info{name={_,_,_}=B}) -> +    A =< B; +sort_name(#etop_proc_info{name=A}, #etop_proc_info{name=B}) +  when is_atom(A), is_atom(B) -> +    A =< B; +sort_name(#etop_proc_info{name=Reg}, #etop_proc_info{name={M,_F,_A}}) +  when is_atom(Reg) -> +    Reg < M; +sort_name(#etop_proc_info{name={M,_,_}}, #etop_proc_info{name=Reg}) +  when is_atom(Reg) -> +    M < Reg. + +sort_name_rev(#etop_proc_info{name={_,_,_}=A}, #etop_proc_info{name={_,_,_}=B}) -> +    A >= B; +sort_name_rev(#etop_proc_info{name=A}, #etop_proc_info{name=B}) +  when is_atom(A), is_atom(B) -> +    A >= B; +sort_name_rev(#etop_proc_info{name=Reg}, #etop_proc_info{name={M,_F,_A}}) +  when is_atom(Reg) -> +    Reg >= M; +sort_name_rev(#etop_proc_info{name={M,_,_}}, #etop_proc_info{name=Reg}) +  when is_atom(Reg) -> +    M >= Reg.  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 8d19d77488..fb02ae2728 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -56,7 +56,7 @@ init([Pid, ParentFrame, Parent]) ->  	Table = ets:new(observer_expand,[set,public]),  	Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of  		  [] -> io_lib:format("~p",[Pid]); -		  {registered_name, Registered} -> io_lib:format("~p (~p)",[Registered, Pid]); +		  {registered_name, Registered} -> io_lib:format("~tp (~p)",[Registered, Pid]);  		  undefined -> throw(process_undefined)  	      end,  	Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title], @@ -151,7 +151,7 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}},  	    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), @@ -171,7 +171,7 @@ handle_info({get_debug_info, From}, State = #state{notebook=Notebook}) ->      From ! {procinfo_debug, Notebook},      {noreply, State};  handle_info(_Info, State) -> -    %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), +    %% io:format("~p: ~p, Handle info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  handle_call(Call, From, _State) -> @@ -198,10 +198,11 @@ code_change(_, _, State) ->  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  init_process_page(Panel, Pid) -> -    Fields0 = process_info_fields(Pid), +    WSz = observer_wx:try_rpc(node(Pid), erlang, system_info,[wordsize]), +    Fields0 = process_info_fields(Pid, WSz),      {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields0),      {FPanel, fun() -> -		     Fields = process_info_fields(Pid), +		     Fields = process_info_fields(Pid, WSz),  		     observer_lib:update_info(UpFields, Fields)  	     end}. @@ -262,7 +263,7 @@ init_stack_page(Parent, Pid) ->  					      wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})),  					      FileLine = case Info of  							     [{file,File},{line,Line}] -> -								 io_lib:format("~s:~w", [File,Line]); +								 io_lib:format("~ts:~w", [File,Line]);  							     _ ->  								 []  							 end, @@ -359,7 +360,7 @@ create_menus(MenuBar) ->  	     {"View", [#create_menu{id=?REFRESH, text="Refresh\tCtrl-R"}]}],      observer_lib:create_menus(Menus, MenuBar, new_window). -process_info_fields(Pid) -> +process_info_fields(Pid, WSz) ->      Struct = [{"Overview",  	       [{"Initial Call",     initial_call},  		{"Current Function", current_function}, @@ -383,10 +384,10 @@ process_info_fields(Pid) ->  		{"Monitored by",     {click, monitored_by}}]},  	      {"Memory and Garbage Collection", right,  	       [{"Memory",           {bytes, memory}}, -		{"Stack and Heaps",  {bytes, total_heap_size}}, -		{"Heap Size",        {bytes, heap_size}}, -		{"Stack Size",       {bytes, stack_size}}, -		{"GC Min Heap Size", {bytes, get_gc_info(min_heap_size)}}, +		{"Stack and Heaps",  {{words,WSz}, total_heap_size}}, +		{"Heap Size",        {{words,WSz}, heap_size}}, +		{"Stack Size",       {{words,WSz}, stack_size}}, +		{"GC Min Heap Size", {{words,WSz}, get_gc_info(min_heap_size)}},  		{"GC FullSweep After", get_gc_info(fullsweep_after)}  	       ]}],      case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]) of @@ -486,5 +487,5 @@ io_request({put_chars, Encoding, Module, Function, Args}, State) ->  	    {error, {error, Function}, State}      end;  io_request(_Req, State) -> -    %% io:format("~p: Unknown req: ~p ~n",[?LINE, _Req]), +    %% io:format("~p: Unknown req: ~tp ~n",[?LINE, _Req]),      {ok, {error, request}, State}. diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index fa824995f7..8c2ffd77b4 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@  -behaviour(wx_object). --export([start_link/2]). +-export([start_link/3]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,  	 handle_event/2, handle_cast/2]). @@ -41,14 +41,14 @@  	 fields,  	 timer}). -start_link(Notebook, Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []).  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) ->      SysInfo = observer_backend:sys_info(), -    {Sys, Mem, Cpu, Stats} = info_fields(), +    {Sys, Mem, Cpu, Stats, Limits} = info_fields(),      Panel = wxPanel:new(Notebook),      Sizer = wxBoxSizer:new(?wxVERTICAL),      HSizer0 = wxBoxSizer:new(?wxHORIZONTAL), @@ -63,17 +63,26 @@ init([Notebook, Parent]) ->      wxSizer:add(HSizer1, FPanel2, [{flag, ?wxEXPAND}, {proportion, 1}]),      wxSizer:add(HSizer1, FPanel3, [{flag, ?wxEXPAND}, {proportion, 1}]), +    HSizer2 = wxBoxSizer:new(?wxHORIZONTAL), +    {FPanel4, _FSizer4, Fields4} = observer_lib:display_info(Panel, observer_lib:fill_info(Limits, SysInfo)), +    wxSizer:add(HSizer2, FPanel4, [{flag, ?wxEXPAND}, {proportion, 1}]), + +      BorderFlags = ?wxLEFT bor ?wxRIGHT,      wxSizer:add(Sizer, HSizer0, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},  				 {proportion, 0}, {border, 5}]),      wxSizer:add(Sizer, HSizer1, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},  				 {proportion, 0}, {border, 5}]), +    wxSizer:add(Sizer, HSizer2, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, +                                 {proportion, 0}, {border, 5}]), +      wxPanel:setSizer(Panel, Sizer), -    Timer = observer_lib:start_timer(10), +    Timer = observer_lib:start_timer(Config, 10),      {Panel, #sys_wx_state{parent=Parent,  			  parent_notebook=Notebook,  			  panel=Panel, sizer=Sizer, -			  timer=Timer, fields=Fields0 ++ Fields1++Fields2++Fields3}}. +			  timer=Timer, fields=Fields0 ++ Fields1++Fields2++Fields3++Fields4}}. +  create_sys_menu(Parent) ->      View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, @@ -83,14 +92,40 @@ create_sys_menu(Parent) ->  update_syspage(#sys_wx_state{node = undefined}) -> ignore;  update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) ->      SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []), -    {Sys, Mem, Cpu, Stats} = info_fields(), +    {Sys, Mem, Cpu, Stats, Limits} = info_fields(),      observer_lib:update_info(Fields,  			     observer_lib:fill_info(Sys, SysInfo) ++  				 observer_lib:fill_info(Mem, SysInfo) ++  				 observer_lib:fill_info(Cpu, SysInfo) ++ -				 observer_lib:fill_info(Stats, SysInfo)), +				 observer_lib:fill_info(Stats, SysInfo)++ +				 observer_lib:fill_info(Limits, SysInfo)), +      wxSizer:layout(Sizer). + +maybe_convert(undefined) -> "Not available"; +maybe_convert(V) -> observer_lib:to_str(V). + +get_dist_buf_busy_limit_info() -> +    fun(Data) -> +            maybe_convert(proplists:get_value(dist_buf_busy_limit, Data)) +    end. + +get_limit_count_info(Count, Limit) -> +    fun(Data) -> +            C = proplists:get_value(Count, Data), +            L = proplists:get_value(Limit, Data), +            lists:flatten( +              io_lib:format("~s / ~s ~s", +                            [maybe_convert(C), maybe_convert(L), +                             if +                                 C =:= undefined -> ""; +                                 L =:= undefined -> ""; +                                 true -> io_lib:format("(~s % used)",[observer_lib:to_str({trunc, (C / L) *100})]) +                             end])) +    end. + +  info_fields() ->      Sys = [{"System and Architecture",  	     [{"System Version", otp_release}, @@ -122,14 +157,20 @@ info_fields() ->  	    ]}],      Stats = [{"Statistics", right,  	      [{"Up time", {time_ms, uptime}}, -	       {"Max Processes", process_limit}, -	       {"Processes", process_count},  	       {"Run Queue", run_queue},  	       {"IO Input",  {bytes, io_input}},  	       {"IO Output", {bytes, io_output}}  	      ]}  	    ], -    {Sys, Mem, Cpu, Stats}. +    Limits = [{"System statistics / limit", +               [{"Atoms", get_limit_count_info(atom_count, atom_limit)}, +                {"Processes", get_limit_count_info(process_count, process_limit)}, +                {"Ports", get_limit_count_info(port_count, port_limit)}, +                {"ETS", get_limit_count_info(ets_count, ets_limit)}, +                {"Distribution buffer busy limit", get_dist_buf_busy_limit_info()} +               ]}], +    {Sys, Mem, Cpu, Stats, Limits}. +  %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -158,7 +199,7 @@ handle_info(not_active, #sys_wx_state{timer = Timer} = State) ->      {noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}};  handle_info(Info, State) -> -    io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), +    io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),      {noreply, State}.  terminate(_Reason, _State) -> @@ -167,12 +208,15 @@ terminate(_Reason, _State) ->  code_change(_, _, State) ->      {ok, State}. +handle_call(get_config, _, #sys_wx_state{timer=Timer}=State) -> +    {reply, observer_lib:timer_config(Timer), State}; +  handle_call(Msg, _From, State) -> -    io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),      {reply, ok, State}.  handle_cast(Msg, State) -> -    io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), +    io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),      {noreply, State}.  handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}}, @@ -191,5 +235,5 @@ handle_event(#wx{id = ?ID_REFRESH_INTERVAL,      {noreply, State#sys_wx_state{timer=Timer}};  handle_event(Event, State) -> -    io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), +    io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),      {noreply, State}. diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index af90e2100c..2c3b46a3a1 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@  -module(observer_trace_wx). --export([start_link/2, add_processes/1, add_ports/1]). +-export([start_link/3, add_processes/1, add_ports/1]).  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,  	 handle_event/2, handle_cast/2]). @@ -88,8 +88,8 @@  -record(titem, {id, opts}). -start_link(Notebook, ParentPid) -> -    wx_object:start_link(?MODULE, [Notebook, ParentPid], []). +start_link(Notebook, ParentPid, Config) -> +    wx_object:start_link(?MODULE, [Notebook, ParentPid, Config], []).  add_processes(Pids) when is_list(Pids) ->      wx_object:cast(observer_wx:get_tracer(), {add_processes, Pids}). @@ -99,10 +99,10 @@ add_ports(Ports) when is_list(Ports) ->  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, ParentPid]) -> -    wx:batch(fun() -> create_window(Notebook, ParentPid) end). +init([Notebook, ParentPid, Config]) -> +    wx:batch(fun() -> create_window(Notebook, ParentPid, Config) end). -create_window(Notebook, ParentPid) -> +create_window(Notebook, ParentPid, Config) ->      %% Create the window      Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]),      Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -130,11 +130,16 @@ create_window(Notebook, ParentPid) ->      wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN},  				 {border, 5}, {proportion,0}]),      wxWindow:setSizer(Panel, Sizer), +    MS = parse_ms(maps:get(match_specs, Config, []), default_matchspecs()),      {Panel, #state{parent=ParentPid, panel=Panel,  		   n_view=NodeView, proc_view=ProcessView, port_view=PortView,  		   m_view=ModView, f_view=FuncView,  		   toggle_button = ToggleButton, -		   match_specs=default_matchspecs()}}. +                   output=maps:get(output, Config, []), +                   def_proc_flags=maps:get(procflags, Config, []), +                   def_port_flags=maps:get(portflags, Config, []), +                   match_specs=MS +                  }}.  default_matchspecs() ->      [{Key,default_matchspecs(Key)} || Key <- [funcs,send,'receive']]. @@ -397,27 +402,19 @@ handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) ->      {noreply, State};  handle_event(#wx{id = ?SAVE_TRACEOPTS}, -	     #state{panel = Panel, -		    def_proc_flags = ProcFlags, -		    def_port_flags = PortFlags, -		    match_specs = MatchSpecs, -		    tpatterns = TracePatterns, -		    output = Output -		   } = State) -> +	     #state{panel = Panel} = State) ->      Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),      case wxFileDialog:showModal(Dialog) of  	?wxID_OK ->  	    Path = wxFileDialog:getPath(Dialog), -	    write_file(Panel, Path, -		       ProcFlags, PortFlags, MatchSpecs, Output, -		       dict:to_list(TracePatterns) -		      ); +	    write_file(Panel, Path, get_config(State));  	_ ->  	    ok      end,      wxDialog:destroy(Dialog),      {noreply, State}; +  handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) ->      Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]),      State2 = case wxFileDialog:showModal(Dialog) of @@ -686,10 +683,14 @@ handle_event(#wx{id=?REMOVE_NODES}, #state{n_view=Nview, nodes=Ns0} = State) ->      {noreply, State#state{nodes = Ns}};  handle_event(#wx{id=ID, event = What}, State) -> -    io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, ?LINE, ID, What]), +    io:format("~p:~p: Unhandled event: ~p, ~tp ~n", [?MODULE, ?LINE, ID, What]),      {noreply, State}.  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_call(get_config, _, State) -> +    Config0 = get_config(State), +    Config = lists:keydelete(trace_p, 1, Config0), +    {reply, maps:from_list(Config), State};  handle_call(Msg, From, _State) ->      error({unhandled_call, Msg, From}). @@ -728,7 +729,7 @@ handle_info({update_ms, NewMs}, State) ->      {noreply, State#state{match_specs=NewMs}};  handle_info(Any, State) -> -    io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]), +    io:format("~p~p: received unexpected message: ~tp\n", [?MODULE, self(), Any]),      {noreply, State}.  terminate(_Reason, #state{nodes=_Nodes}) -> @@ -1045,33 +1046,33 @@ format_trace(Trace, Size, TS0={_,_,MS}) ->  	    case element(4, Trace) of  		{dbg,ok} -> "";  		Message -> -		    io_lib:format("~s (~100p) << ~100p~n", [TS,From,Message]) +		    io_lib:format("~s (~100p) << ~100tp~n", [TS,From,Message])  	    end;  	'send' ->  	    Message = element(4, Trace),  	    To = element(5, Trace), -	    io_lib:format("~s (~100p) ~100p ! ~100p~n", [TS,From,To,Message]); +	    io_lib:format("~s (~100p) ~100p ! ~100tp~n", [TS,From,To,Message]);  	call ->  	    case element(4, Trace) of  		MFA when Size == 5 ->  		    Message = element(5, Trace), -		    io_lib:format("~s (~100p) call ~s (~100p) ~n", [TS,From,ffunc(MFA),Message]); +		    io_lib:format("~s (~100p) call ~ts (~100tp) ~n", [TS,From,ffunc(MFA),Message]);  		MFA -> -		    io_lib:format("~s (~100p) call ~s~n", [TS,From,ffunc(MFA)]) +		    io_lib:format("~s (~100p) call ~ts~n", [TS,From,ffunc(MFA)])  	    end;  	return_from ->  	    MFA = element(4, Trace),  	    Ret = element(5, Trace), -	    io_lib:format("~s (~100p) returned from ~s -> ~100p~n", [TS,From,ffunc(MFA),Ret]); +	    io_lib:format("~s (~100p) returned from ~ts -> ~100tp~n", [TS,From,ffunc(MFA),Ret]);  	return_to ->  	    MFA = element(4, Trace), -	    io_lib:format("~s (~100p) returning to ~s~n", [TS,From,ffunc(MFA)]); +	    io_lib:format("~s (~100p) returning to ~ts~n", [TS,From,ffunc(MFA)]);  	spawn when Size == 5 ->  	    Pid = element(4, Trace),  	    MFA = element(5, Trace), -	    io_lib:format("~s (~100p) spawn ~100p as ~s~n", [TS,From,Pid,ffunc(MFA)]); +	    io_lib:format("~s (~100p) spawn ~100p as ~ts~n", [TS,From,Pid,ffunc(MFA)]);  	Op -> -	    io_lib:format("~s (~100p) ~100p ~s~n", [TS,From,Op,ftup(Trace,4,Size)]) +	    io_lib:format("~s (~100p) ~100p ~ts~n", [TS,From,Op,ftup(Trace,4,Size)])      end.  %%% These f* functions returns non-flat strings @@ -1079,51 +1080,64 @@ format_trace(Trace, Size, TS0={_,_,MS}) ->  %% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)"  %% {M,F,A}                 -> "M:F/A"  ffunc({M,F,Argl}) when is_list(Argl) -> -    io_lib:format("~100p:~100p(~s)", [M, F, fargs(Argl)]); +    io_lib:format("~100p:~100tp(~ts)", [M, F, fargs(Argl)]);  ffunc({M,F,Arity}) -> -    io_lib:format("~100p:~100p/~100p", [M,F,Arity]); -ffunc(X) -> io_lib:format("~100p", [X]). +    io_lib:format("~100p:~100tp/~100p", [M,F,Arity]); +ffunc(X) -> io_lib:format("~100tp", [X]).  %% Integer           -> "Integer"  %% [A1, A2, ..., AN] -> "A1, A2, ..., AN"  fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity);  fargs([]) -> []; -fargs([A]) -> io_lib:format("~100p", [A]);  %% last arg -fargs([A|Args]) -> [io_lib:format("~100p,", [A]) | fargs(Args)]; -fargs(A) -> io_lib:format("~100p", [A]). % last or only arg +fargs([A]) -> io_lib:format("~100tp", [A]);  %% last arg +fargs([A|Args]) -> [io_lib:format("~100tp,", [A]) | fargs(Args)]; +fargs(A) -> io_lib:format("~100tp", [A]). % last or only arg  %% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size"  ftup(Trace, Index, Index) -> -    io_lib:format("~100p", [element(Index, Trace)]); +    io_lib:format("~100tp", [element(Index, Trace)]);  ftup(Trace, Index, Size) -> -    [io_lib:format("~100p ", [element(Index, Trace)]) +    [io_lib:format("~100tp ", [element(Index, Trace)])       | ftup(Trace, Index+1, Size)].  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -write_file(Frame, Filename, ProcFlags, PortFlags, MatchSpecs, Output, TPs) -> +get_config(#state{def_proc_flags = ProcFlags, +                  def_port_flags = PortFlags, +                  match_specs = MatchSpecs0, +                  tpatterns = TracePatterns, +                  output = Output}) ->      MSToList = fun(#match_spec{name=Id, term=T, func=F}) ->  		       [{name,Id},{term,T},{func,F}]  	       end, -    MSTermList = [{ms,Key,[MSToList(MS) || MS <- MSs]} || -		     {Key,MSs} <- MatchSpecs], +    MatchSpecs = [{ms,Key,[MSToList(MS) || MS <- MSs]} || +		     {Key,MSs} <- MatchSpecs0],      TPToTuple = fun(#tpattern{fa={F,A}, ms=Ms}) -> -		       {F,A,MSToList(Ms)} +                        {F,A,MSToList(Ms)}  		end,      ModuleTermList = [{tp, Module, [TPToTuple(FTP) || FTP <- FTPs]} || -			 {Module,FTPs} <- TPs], - +			 {Module,FTPs} <- dict:to_list(TracePatterns)], +    [{procflags,ProcFlags}, +     {portflags,PortFlags}, +     {match_specs,MatchSpecs}, +     {output,Output}, +     {trace_p,ModuleTermList}]. + +write_file(Frame, Filename, Config) ->      Str = -	["%%%\n%%% This file is generated by Observer\n", +	["%%% ",epp:encoding_to_string(utf8), "\n" +         "%%%\n%%% This file is generated by Observer\n",  	 "%%%\n%%% DO NOT EDIT!\n%%%\n", -	 [io_lib:format("~p.~n",[MSTerm]) || MSTerm <- MSTermList], -	 io_lib:format("~p.~n",[{procflags,ProcFlags}]), -	 io_lib:format("~p.~n",[{portflags,PortFlags}]), -	 io_lib:format("~p.~n",[{output,Output}]), -	 [io_lib:format("~p.~n",[ModuleTerm]) || ModuleTerm <- ModuleTermList] +	 [io_lib:format("~tp.~n",[MSTerm]) || +             MSTerm <- proplists:get_value(match_specs, Config)], +	 io_lib:format("~p.~n",[lists:keyfind(procflags, 1, Config)]), +	 io_lib:format("~p.~n",[lists:keyfind(portflags, 1, Config)]), +	 io_lib:format("~tp.~n",[lists:keyfind(output, 1, Config)]), +	 [io_lib:format("~tp.~n",[ModuleTerm]) || +             ModuleTerm <- proplists:get_value(trace_p, Config)]  	], -    case file:write_file(Filename, list_to_binary(Str)) of +    case file:write_file(Filename, unicode:characters_to_binary(Str)) of  	ok ->  	    success;  	{error, Reason} -> @@ -1187,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("~w", 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 285c298c4b..4f46426cf6 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -487,7 +487,7 @@ edit_ms(TextCtrl, Label0, Parent) ->  		    _ -> Label0  		end,  	#match_spec{name=Label, term=MatchSpec, -		    str=io_lib:format("~w",[MatchSpec]), +		    str=io_lib:format("~tw",[MatchSpec]),  		    func=Str}      catch  	throw:cancel -> @@ -511,18 +511,18 @@ ms_from_string(Str) ->  	Tokens = case erl_scan:string(Str) of  		     {ok, Ts, _} -> Ts;  		     {error, {SLine, SMod, SError}, _} -> -			 throw(io_lib:format("~w: ~s", [SLine,SMod:format_error(SError)])) +			 throw(io_lib:format("~w: ~ts", [SLine,SMod:format_error(SError)]))  		 end,  	Exprs  = case erl_parse:parse_exprs(Tokens) of  		     {ok, T} -> T;  		     {error, {PLine, PMod, PError}} -> -			 throw(io_lib:format("~w: ~s", [PLine,PMod:format_error(PError)])) +			 throw(io_lib:format("~w: ~ts", [PLine,PMod:format_error(PError)]))  		 end,  	Term = case Exprs of  		   [{'fun', _, {clauses, Clauses}}|_] ->  		       case ms_transform:transform_from_shell(dbg,Clauses,orddict:new()) of  			   {error, [{_,[{MSLine,Mod,MSInfo}]}],_} -> -			       throw(io_lib:format("~w: ~p", [MSLine,Mod:format_error(MSInfo)])); +			       throw(io_lib:format("~w: ~tp", [MSLine,Mod:format_error(MSInfo)]));  			   {error, _} ->  			       throw("Could not convert fun() to match spec");  			   Ms -> @@ -536,7 +536,7 @@ ms_from_string(Str) ->  	    {error, List} -> throw([[Error, $\n] || {_, Error} <- List])  	end      catch error:_Reason -> -	    %% io:format("Bad term: ~s~n ~p in ~p~n", [Str, _Reason, erlang:get_stacktrace()]), +	    %% io:format("Bad term: ~ts~n ~tp in ~tp~n", [Str, _Reason, erlang:get_stacktrace()]),  	    throw("Invalid term")      end. @@ -556,7 +556,8 @@ filter_listbox_data(Input, Data, ListBox) ->      filter_listbox_data(Input, Data, ListBox, true).  filter_listbox_data(Input, Data, ListBox, AddClientData) -> -    FilteredData = [X || X = {Str, _} <- Data, re:run(Str, Input) =/= nomatch], +    FilteredData = [X || X = {Str, _} <- Data, +                         re:run(Str, Input, [unicode]) =/= nomatch],      wxListBox:clear(ListBox),      wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]),      AddClientData andalso @@ -648,9 +649,9 @@ parse_function_names(Choices) ->  parse_function_names([], Acc) ->      lists:reverse(Acc);  parse_function_names([{H, Term}|T], Acc) -> -    IsFun = re:run(H, ".*-fun-\\d*?-"), -    IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-"), -    IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-"), +    IsFun = re:run(H, ".*-fun-\\d*?-", [unicode,ucp]), +    IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]), +    IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]),      Parsed =  	if IsFun =/= nomatch -> "Fun: " ++ H;  	   IsLc =/= nomatch -> "List comprehension: " ++ H; diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 75e6919642..d6dcee2cda 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -233,9 +233,22 @@ handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) ->      {noreply, State};  handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, -	     State = #state{pid=Pid}) -> +	     State = #state{pid=Pid, grid=Grid, selected=OldSel}) -> +    SelObj = case OldSel of +                 undefined -> undefined; +                 _ -> get_row(Pid, OldSel, term) +             end,      Pid ! {sort, Col+1}, -    {noreply, State}; +    case SelObj =/= undefined andalso search(Pid, SelObj, -1, true, term) of +        false when is_integer(OldSel) -> +            wxListCtrl:setItemState(Grid, OldSel, 0, ?wxLIST_STATE_SELECTED), +            {noreply, State#state{selected=undefined}}; +        false -> +            {noreply, State#state{selected=undefined}}; +        Row -> +            wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), +            {noreply, State#state{selected=Row}} +    end;  handle_event(#wx{event=#wxSize{size={W,_}}},  State=#state{grid=Grid}) ->      observer_lib:set_listctrl_col_size(Grid, W), @@ -245,7 +258,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}  	     State = #state{pid=Pid, grid=Grid, status=StatusBar}) ->      N = wxListCtrl:getItemCount(Grid),      Str = get_row(Pid, Index, all), -    wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w: ~s",[N, Str])), +    wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w: ~ts",[N, Str])),      {noreply, State#state{selected=Index}};  handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Index}}, @@ -265,7 +278,7 @@ handle_event(#wx{id=?ID_DELETE},  	     State = #state{grid=Grid, pid=Pid, status=StatusBar, selected=Index}) ->      Str = get_row(Pid, Index, all),      Pid ! {delete, Index}, -    wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~s",[Str])), +    wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~ts",[Str])),      wxListCtrl:setItemState(Grid, Index, 0, ?wxLIST_STATE_FOCUSED),      {noreply, State#state{selected=undefined}}; @@ -325,7 +338,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdS      Pid ! {mark_search_hit, false},      case search(Pid, Str, Pos, Dir, Case) of  	false -> -	    wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~s",[Str])), +	    wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~ts",[Str])),  	    Pid ! {mark_search_hit, Find#find.start},  	    wxListCtrl:refreshItem(Grid, Find#find.start),  	    {noreply, State#state{search=Search#search{find=Find#find{found=false}}}}; @@ -359,7 +372,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}},  	Pid ! {mark_search_hit, false},  	case search(Pid, Str, Cont#find.start, Dir, Case) of  	    false -> -		wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~s",[Str])), +		wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~ts",[Str])),  		{noreply, State};  	    Row ->  		wxListCtrl:ensureVisible(Grid, Row), @@ -382,19 +395,19 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL},      {noreply, State#state{timer=Timer}};  handle_event(_Event, State) -> -    %io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), +    %io:format("~p:~p, handle event ~tp\n", [?MODULE, ?LINE, Event]),      {noreply, State}.  handle_sync_event(_Event, _Obj, _State) -> -    %io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), +    %io:format("~p:~p, handle sync_event ~tp\n", [?MODULE, ?LINE, Event]),      ok.  handle_call(_Event, _From, State) -> -    %io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), +    %io:format("~p:~p, handle call (~p) ~tp\n", [?MODULE, ?LINE, From, Event]),      {noreply, State}.  handle_cast(_Event, State) -> -    %io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), +    %io:format("~p:~p, handle cast ~tp\n", [?MODULE, ?LINE, Event]),      {noreply, State}.  handle_info({no_rows, N}, State = #state{grid=Grid, status=StatusBar}) -> @@ -420,7 +433,7 @@ handle_info(refresh_interval, State = #state{pid=Pid}) ->  handle_info({error, Error}, State = #state{frame=Frame}) ->      ErrorStr =  	try io_lib:format("~ts", [Error]), Error -	catch _:_ -> io_lib:format("~p", [Error]) +	catch _:_ -> io_lib:format("~tp", [Error])  	end,      Dlg = wxMessageDialog:new(Frame, ErrorStr),      wxMessageDialog:showModal(Dlg), @@ -428,7 +441,7 @@ handle_info({error, Error}, State = #state{frame=Frame}) ->      {noreply, State};  handle_info(_Event, State) -> -    %% io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, _Event]), +    %% io:format("~p:~p, handle info ~tp\n", [?MODULE, ?LINE, _Event]),      {noreply, State}.  terminate(_Event, #state{pid=Pid, attrs=Attrs}) -> @@ -541,7 +554,7 @@ table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) ->  	    edit_row(Row, Term, S0),  	    table_holder(S0);  	What -> -	    io:format("Table holder got ~p~n",[What]), +	    io:format("Table holder got ~tp~n",[What]),  	    Parent ! {refresh, 0, S0#holder.n-1},  	    table_holder(S0)      end. @@ -607,6 +620,17 @@ keysort(Col, Table) ->  	   end,      lists:sort(Sort, Table). +search([Term, -1, true, term], S=#holder{parent=Parent, table=Table}) -> +    Search = fun(Idx, [Tuple|_]) -> +                     Tuple =:= Term andalso throw(Idx), +                     Tuple +             end, +    try array:map(Search, Table) of +        _ -> Parent ! {self(), false} +    catch Index -> +            Parent ! {self(), Index} +    end, +    S;  search([Str, Row, Dir0, CaseSens],         S=#holder{parent=Parent, n=N, table=Table}) ->      Opt = case CaseSens of @@ -617,7 +641,7 @@ search([Str, Row, Dir0, CaseSens],  	      true -> 1;  	      false -> -1  	  end, -    Res = case re:compile(Str, Opt) of +    Res = case re:compile(Str, [unicode|Opt]) of  	      {ok, Re} -> re_search(Row, Dir, N, Re, Table);  	      {error, _} -> false  	  end, @@ -641,7 +665,9 @@ get_row(From, Row, Col, Table) ->  	[Object|_] when Col =:= all ->  	    From ! {self(), format(Object)};  	[Object|_] when Col =:= all_multiline -> -	    From ! {self(), io_lib:format("~p", [Object])}; +	    From ! {self(), io_lib:format("~tp", [Object])}; +        [Object|_] when Col =:= term -> +	    From ! {self(), Object};  	[Object|_] when tuple_size(Object) >= Col ->  	    From ! {self(), format(element(Col, Object))};  	_ -> @@ -775,7 +801,7 @@ format(Bin) when is_binary(Bin), byte_size(Bin) > 100 ->      io_lib:format("<<#Bin:~w>>", [byte_size(Bin)]);  format(Bin) when is_binary(Bin) ->      try -	true = printable_list(unicode:characters_to_list(Bin)), +	true = io_lib:printable_list(unicode:characters_to_list(Bin)),  	io_lib:format("<<\"~ts\">>", [Bin])      catch _:_ ->  	    io_lib:format("~w", [Bin]) @@ -783,7 +809,7 @@ format(Bin) when is_binary(Bin) ->  format(Float) when is_float(Float) ->      io_lib:format("~.3g", [Float]);  format(Term) -> -    io_lib:format("~w", [Term]). +    io_lib:format("~tw", [Term]).  format_tuple(Tuple, I, Max) when I < Max ->      [format(element(I, Tuple)), $,|format_tuple(Tuple, I+1, Max)]; @@ -794,7 +820,7 @@ format_tuple(_Tuple, 1, 0) ->  format_list([]) -> "[]";  format_list(List) -> -    case printable_list(List) of +    case io_lib:printable_list(List) of  	true ->  io_lib:format("\"~ts\"", [map_printable_list(List)]);  	false -> [$[ | make_list(List)]      end. @@ -823,26 +849,3 @@ map_printable_list([$\e|Cs]) ->  map_printable_list([]) -> [];  map_printable_list([C|Cs]) ->      [C|map_printable_list(Cs)]. - -%% printable_list([Char]) -> bool() -%%  Return true if CharList is a list of printable characters, else -%%  false. - -printable_list([C|Cs]) when is_integer(C), C >= $ , C =< 255 -> -    printable_list(Cs); -printable_list([$\n|Cs]) -> -    printable_list(Cs); -printable_list([$\r|Cs]) -> -    printable_list(Cs); -printable_list([$\t|Cs]) -> -    printable_list(Cs); -printable_list([$\v|Cs]) -> -    printable_list(Cs); -printable_list([$\b|Cs]) -> -    printable_list(Cs); -printable_list([$\f|Cs]) -> -    printable_list(Cs); -printable_list([$\e|Cs]) -> -    printable_list(Cs); -printable_list([]) -> true; -printable_list(_Other) -> false.	     %Everything else is false diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index bf2afee2a5..e16f3cab6b 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -18,7 +18,7 @@  %% %CopyrightEnd%  -module(observer_tv_wx). --export([start_link/2, display_table_info/4]). +-export([start_link/3, display_table_info/4]).  %% wx_object callbacks  -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -58,10 +58,10 @@  	  timer  	}). -start_link(Notebook,  Parent) -> -    wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook,  Parent, Config) -> +    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) ->      Panel = wxPanel:new(Notebook),      Sizer = wxBoxSizer:new(?wxVERTICAL),      Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, @@ -78,11 +78,11 @@ init([Notebook, Parent]) ->  			   Col + 1  		   end,      ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT,  200}, -		 {"Table Id",   ?wxLIST_FORMAT_RIGHT, 100},  		 {"Objects",    ?wxLIST_FORMAT_RIGHT, 100},  		 {"Size (kB)",  ?wxLIST_FORMAT_RIGHT, 100},  		 {"Owner Pid",  ?wxLIST_FORMAT_CENTER, 150}, -		 {"Owner Name", ?wxLIST_FORMAT_LEFT,  200} +		 {"Owner Name", ?wxLIST_FORMAT_LEFT,  200}, +		 {"Table Id",   ?wxLIST_FORMAT_LEFT, 250}  		],      lists:foldl(AddListEntry, 0, ListItems),      wxListItem:destroy(Li), @@ -94,25 +94,31 @@ init([Notebook, Parent]) ->      wxListCtrl:connect(Grid, size, [{skip, true}]),      wxWindow:setFocus(Grid), -    {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}. +    {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)} +                  }}.  handle_event(#wx{id=?ID_REFRESH},  	     State = #state{node=Node, grid=Grid, opt=Opt}) ->      Tables = get_tables(Node, Opt), -    Tabs = update_grid(Grid, Opt, Tables), -    {noreply, State#state{tabs=Tabs}}; +    {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables), +    Sel =/= undefined andalso wxListCtrl:ensureVisible(Grid, Sel), +    {noreply, State#state{tabs=Tabs, selected=Sel}};  handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},  	     State = #state{node=Node, grid=Grid,  			    opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> -    Opt = case Col+2 of +    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 = update_grid(Grid, Opt, Tables), +    {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables),      wxWindow:setFocus(Grid), -    {noreply, State#state{opt=Opt, tabs=Tabs}}; +    {noreply, State#state{opt=Opt, tabs=Tabs, selected=Sel}};  handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0})    when Id >= ?ID_ETS, Id =< ?ID_SYSTEM_TABLES -> @@ -129,9 +135,9 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0})  	    self() ! Error,  	    {noreply, State};  	Tables -> -	    Tabs = update_grid(Grid, Opt, Tables), +	    {Tabs, Sel} = update_grid(Grid, sel(State), Opt, Tables),  	    wxWindow:setFocus(Grid), -	    {noreply, State#state{opt=Opt, tabs=Tabs}} +	    {noreply, State#state{opt=Opt, tabs=Tabs, selected=Sel}}      end;  handle_event(#wx{event=#wxSize{size={W,_}}},  State=#state{grid=Grid}) -> @@ -202,6 +208,12 @@ handle_event(Event, _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, +    Conf0 = observer_lib:timer_config(Timer), +    Conf = Conf0#{type=>Type, sys_hidden=>Sys, unread_hidden=>Unread}, +    {reply, Conf, State}; +  handle_call(Event, From, _State) ->      error({unhandled_call, Event, From}). @@ -215,8 +227,9 @@ handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt,              %% no change              {noreply, State};          Tables -> -            Tabs = update_grid(Grid, Opt, Tables), -            {noreply, State#state{tabs=Tabs}} +            {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({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt0, @@ -228,18 +241,18 @@ handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt0,                              Opt1 = Opt0#opt{type=ets},                              {get_tables(Node, Opt1), Opt1}                      end, -    Tabs = update_grid(Grid, Opt, Tables), +    {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables),      wxWindow:setFocus(Grid),      create_menus(Parent, Opt), -    Timer = observer_lib:start_timer(Timer0), -    {noreply, State#state{node=Node, tabs=Tabs, timer=Timer, opt=Opt}}; +    Timer = observer_lib:start_timer(Timer0, 10), +    {noreply, State#state{node=Node, tabs=Tabs, timer=Timer, opt=Opt, selected=Sel}};  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) -> -    Str = io_lib:format("ERROR: ~s~n",[Error]), +    Str = io_lib:format("ERROR: ~ts~n",[Error]),      observer_lib:display_info_dialog(Panel,Str),      case Opt#opt.type of          mnesia -> wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true); @@ -296,6 +309,13 @@ get_tables2(Node, #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread}) ->  	    [list_to_tabrec(Tab) || Tab <- Result]      end. +col2key(0) -> #tab.name; +col2key(1) -> #tab.size; +col2key(2) -> #tab.memory; +col2key(3) -> #tab.owner; +col2key(4) -> #tab.reg_name; +col2key(5) -> #tab.id. +  list_to_tabrec(PL) ->      #tab{name = proplists:get_value(name, PL),  	 id = proplists:get_value(id, PL, ignore), @@ -366,13 +386,15 @@ list_to_strings([A]) -> integer_to_list(A);  list_to_strings([A|B]) ->      integer_to_list(A) ++ " ," ++ list_to_strings(B). -update_grid(Grid, Opt, Tables) -> -    wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end). -update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> +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) -> +		 protection = Protection, reg_name = RegName}, +            {Row, Sel}) ->  		_Item = wxListCtrl:insertItem(Grid, Row, ""),  		if (Row rem 2) =:= 0 ->  			wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN); @@ -387,13 +409,26 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) ->  				 ({Col, Val}) ->  				      wxListCtrl:setItem(Grid, Row, Col, observer_lib:to_str(Val))  			      end, -			      [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, -			       {4,Owner}, {5,RegName}]), -		Row + 1 +			      [{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, -    lists:foldl(Update, 0, ProcInfo), -    ProcInfo. +    {_, 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} +    end. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 738a00180f..be93b1d5f1 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -54,20 +54,14 @@  	 status_bar,  	 notebook,  	 main_panel, -	 pro_panel, -	 port_panel, -	 tv_panel, -	 sys_panel, -	 trace_panel, -	 app_panel, -	 perf_panel, -	 allc_panel, +         panels,  	 active_tab,  	 node,  	 nodes,  	 prev_node="",  	 log = false, -	 reply_to=false +	 reply_to=false, +         config  	}).  start() -> @@ -118,6 +112,10 @@ init(_Args) ->  setup(#state{frame = Frame} = State) ->      %% Setup Menubar & Menus +    Config = load_config(), +    Cnf = fun(Who) -> +                  proplists:get_value(Who, Config, #{}) +          end,      MenuBar = wxMenuBar:new(),      {Nodes, NodeMenus} = get_nodes(), @@ -131,7 +129,7 @@ setup(#state{frame = Frame} = State) ->      Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),      %% System Panel -    SysPanel = observer_sys_wx:start_link(Notebook, self()), +    SysPanel = observer_sys_wx:start_link(Notebook, self(), Cnf(sys_panel)),      wxNotebook:addPage(Notebook, SysPanel, "System", []),      %% Setup sizer create early to get it when window shows @@ -145,43 +143,45 @@ setup(#state{frame = Frame} = State) ->      wxFrame:setTitle(Frame, atom_to_list(node())),      wxStatusBar:setStatusText(StatusBar, atom_to_list(node())), -    wxNotebook:connect(Notebook, command_notebook_page_changing), -    wxFrame:connect(Frame, close_window, [{skip, true}]), +    wxNotebook:connect(Notebook, command_notebook_page_changed, +                       [{skip, true}, {id, ?ID_NOTEBOOK}]), +    wxFrame:connect(Frame, close_window, []),      wxMenu:connect(Frame, command_menu_selected),      wxFrame:show(Frame),      %% Freeze and thaw is buggy currently -    DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9], +    DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9] +        orelse element(1, os:type()) =:= win32,      DoFreeze andalso wxWindow:freeze(Panel),      %% I postpone the creation of the other tabs so they can query/use      %% the window size      %% Perf Viewer Panel -    PerfPanel = observer_perf_wx:start_link(Notebook, self()), +    PerfPanel = observer_perf_wx:start_link(Notebook, self(), Cnf(perf_panel)),      wxNotebook:addPage(Notebook, PerfPanel, "Load Charts", []),      %% Memory Allocator Viewer Panel -    AllcPanel = observer_alloc_wx:start_link(Notebook, self()), +    AllcPanel = observer_alloc_wx:start_link(Notebook, self(), Cnf(allc_panel)),      wxNotebook:addPage(Notebook, AllcPanel, ?ALLOC_STR, []),      %% App Viewer Panel -    AppPanel = observer_app_wx:start_link(Notebook, self()), +    AppPanel = observer_app_wx:start_link(Notebook, self(), Cnf(app_panel)),      wxNotebook:addPage(Notebook, AppPanel, "Applications", []),      %% Process Panel -    ProPanel = observer_pro_wx:start_link(Notebook, self()), +    ProPanel = observer_pro_wx:start_link(Notebook, self(), Cnf(pro_panel)),      wxNotebook:addPage(Notebook, ProPanel, "Processes", []),      %% Port Panel -    PortPanel = observer_port_wx:start_link(Notebook, self()), +    PortPanel = observer_port_wx:start_link(Notebook, self(), Cnf(port_panel)),      wxNotebook:addPage(Notebook, PortPanel, "Ports", []),      %% Table Viewer Panel -    TVPanel = observer_tv_wx:start_link(Notebook, self()), +    TVPanel = observer_tv_wx:start_link(Notebook, self(), Cnf(tv_panel)),      wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []),      %% Trace Viewer Panel -    TracePanel = observer_trace_wx:start_link(Notebook, self()), +    TracePanel = observer_trace_wx:start_link(Notebook, self(), Cnf(trace_panel)),      wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []),      %% Force redraw (windows needs it) @@ -193,19 +193,21 @@ setup(#state{frame = Frame} = State) ->      SysPid = wx_object:get_pid(SysPanel),      SysPid ! {active, node()}, +    Panels = [{sys_panel, SysPanel, "System"},   %% In order +              {perf_panel, PerfPanel, "Load Charts"}, +              {allc_panel, AllcPanel, ?ALLOC_STR}, +              {app_panel,  AppPanel, "Applications"}, +              {pro_panel, ProPanel, "Processes"}, +              {port_panel, PortPanel, "Ports"}, +              {tv_panel, TVPanel, "Table Viewer"}, +              {trace_panel, TracePanel, ?TRACE_STR}], +      UpdState = State#state{main_panel = Panel,  			   notebook = Notebook,  			   menubar = MenuBar,  			   status_bar = StatusBar, -			   sys_panel = SysPanel, -			   pro_panel = ProPanel, -			   port_panel = PortPanel, -			   tv_panel  = TVPanel, -			   trace_panel = TracePanel, -			   app_panel = AppPanel, -			   perf_panel = PerfPanel, -			   allc_panel = AllcPanel,  			   active_tab = SysPid, +                           panels = Panels,  			   node  = node(),  			   nodes = Nodes  			  }, @@ -228,11 +230,14 @@ setup(#state{frame = Frame} = State) ->  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  %%Callbacks -handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}}, -	     #state{active_tab=Previous, node=Node} = State) -> -    case get_active_pid(State) of -	Previous -> {noreply, State}; +handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changed, nSel=Next}}, +	     #state{active_tab=Previous, node=Node, panels=Panels, status_bar=SB} = State) -> +    {_, Obj, _} = lists:nth(Next+1, Panels), +    case wx_object:get_pid(Obj) of +	Previous -> +            {noreply, State};  	Pid -> +            wxStatusBar:setStatusText(SB, ""),  	    Previous ! not_active,  	    Pid ! {active, Node},  	    {noreply, State#state{active_tab=Pid}} @@ -362,8 +367,7 @@ handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}},               end,      {noreply, change_node_view(Node, LState)}; -handle_event(Event, State) -> -    Pid = get_active_pid(State), +handle_event(Event, #state{active_tab=Pid} = State) ->      Pid ! Event,      {noreply, State}. @@ -388,7 +392,8 @@ handle_call({create_menus, TabMenus}, _From,  handle_call({get_attrib, Attrib}, _From, State) ->      {reply, get(Attrib), State}; -handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) -> +handle_call(get_tracer, _From, State=#state{panels=Panels}) -> +    {_, TraceP, _} = lists:keyfind(trace_panel, 1, Panels),      {reply, TraceP, State};  handle_call(get_active_node, _From, State=#state{node=Node}) -> @@ -424,9 +429,7 @@ handle_info({nodedown, Node},      create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION),      {noreply, State3}; -handle_info({open_link, Id0}, State = #state{pro_panel=ProcViewer, -					     port_panel=PortViewer, -					     frame=Frame}) -> +handle_info({open_link, Id0}, State = #state{panels=Panels,frame=Frame}) ->      Id = case Id0 of  	      [_|_] -> try list_to_pid(Id0) catch _:_ -> Id0 end;  	      _ -> Id0 @@ -434,8 +437,10 @@ handle_info({open_link, Id0}, State = #state{pro_panel=ProcViewer,      %% Forward to process tab      case Id of  	Pid when is_pid(Pid) -> +            {pro_panel, ProcViewer, _} = lists:keyfind(pro_panel, 1, Panels),  	    wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid};  	"#Port" ++ _ = Port -> +            {port_panel, PortViewer, _} = lists:keyfind(port_panel, 1, Panels),  	    wx_object:get_pid(PortViewer) ! {portinfo_open, Port};  	_ ->  	    Msg = io_lib:format("Information about ~p is not available or implemented",[Id]), @@ -454,7 +459,7 @@ handle_info({'EXIT', Pid, Reason}, State) ->  	normal ->  	    {noreply, State};  	_ -> -	    io:format("Observer: Child (~s) crashed exiting:  ~p ~p~n", +	    io:format("Observer: Child (~s) crashed exiting:  ~p ~tp~n",  		      [pid2panel(Pid, State), Pid, Reason]),  	    {stop, normal, State}      end; @@ -465,15 +470,13 @@ handle_info({stop, Me}, State) when Me =:= self() ->  handle_info(_Info, State) ->      {noreply, State}. -stop_servers(#state{node=Node, log=LogOn, sys_panel=Sys, pro_panel=Procs, tv_panel=TVs, -		    trace_panel=Trace, app_panel=Apps, perf_panel=Perfs, -		    allc_panel=Alloc, port_panel=Ports} = _State) -> +stop_servers(#state{node=Node, log=LogOn, panels=Panels} = _State) ->      LogOn andalso rpc:block_call(Node, rb, stop, []),      Me = self(), -    Tabs = [Sys, Procs, Ports, TVs, Trace, Apps, Perfs, Alloc], +    save_config(Panels),      Stop = fun() ->  		   try -		       _ = [wx_object:stop(Panel) || Panel <- Tabs], +		       _ = [wx_object:stop(Panel) || {_, Panel, _} <- Panels],  		       ok  		   catch _:_ -> ok  		   end, @@ -490,6 +493,27 @@ terminate(_Reason, #state{frame = Frame, reply_to=From}) ->      end,      ok. +load_config() -> +    case file:consult(config_file()) of +        {ok, Config} -> Config; +        _ -> [] +    end. + +save_config(Panels) -> +    Configs = [{Name, wx_object:call(Panel, get_config)} || {Name, Panel, _} <- Panels], +    File = config_file(), +    case filelib:ensure_dir(File) of +        ok -> +            Format = [io_lib:format("~tp.~n",[Conf]) || Conf <- Configs], +            _ = file:write_file(File, Format); +        _ -> +            ignore +    end. + +config_file() -> +    Dir = filename:basedir(user_config, "erl_observer"), +    filename:join(Dir, "config.txt"). +  code_change(_, _, State) ->      {ok, State}. @@ -549,8 +573,7 @@ connect2(NodeName, Opts, Cookie) ->  	    {error, net_kernel, Reason}      end. -change_node_view(Node, State) -> -    Tab = get_active_pid(State), +change_node_view(Node, #state{active_tab=Tab} = State) ->      Tab ! not_active,      Tab ! {active, Node},      StatusText = ["Observer - " | atom_to_list(Node)], @@ -562,38 +585,13 @@ check_page_title(Notebook) ->      Selection = wxNotebook:getSelection(Notebook),      wxNotebook:getPageText(Notebook, Selection). -get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, -		      tv_panel=Tv, trace_panel=Trace, app_panel=App, -		      perf_panel=Perf, allc_panel=Alloc, port_panel=Port -		     }) -> -    Panel = case check_page_title(Notebook) of -		"Processes" -> Pro; -		"Ports" -> Port; -		"System" -> Sys; -		"Table Viewer" -> Tv; -		?TRACE_STR -> Trace; -		"Load Charts" -> Perf; -		"Applications" -> App; -		?ALLOC_STR -> Alloc -	    end, -    wx_object:get_pid(Panel). - -pid2panel(Pid, #state{pro_panel=Pro, sys_panel=Sys, -		      tv_panel=Tv, trace_panel=Trace, app_panel=App, -		      perf_panel=Perf, allc_panel=Alloc, port_panel=Port}) -> -    case Pid of -	Pro -> "Processes"; -        Port -> "Ports"; -	Sys -> "System"; -	Tv -> "Table Viewer" ; -	Trace -> ?TRACE_STR; -	Perf -> "Load Charts"; -	App -> "Applications"; -	Alloc -> ?ALLOC_STR; -	_ -> "unknown" +pid2panel(Pid, #state{panels=Panels}) -> +    PanelPids = [{Name, wx_object:get_pid(Obj)} || {Name, Obj, _} <- Panels], +    case lists:keyfind(Pid, 2, PanelPids) of +        false -> "unknown"; +        {Name,_} -> Name      end. -  create_connect_dialog(ping, #state{frame = Frame, prev_node=Prev}) ->      Dialog = wxTextEntryDialog:new(Frame, "Connect to node", [{value, Prev}]),      case wxDialog:showModal(Dialog) of diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl index 87a50e046b..940fdc9818 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %%  -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2017. All Rights Reserved.  %%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -391,16 +391,16 @@ run_config(ConfigFile,N) ->  print_func(M,F,A) ->      Args = arg_list(A,[]), -    io:format("~w:~w(~s) ->~n",[M,F,Args]). +    io:format("~w:~tw(~ts) ->~n",[M,F,Args]).  print_result(R) -> -    io:format("~p~n~n",[R]). +    io:format("~tp~n~n",[R]).  arg_list([],[]) ->      "";  arg_list([A1],Acc) -> -    Acc++io_lib:format("~w",[A1]); +    Acc++io_lib:format("~tw",[A1]);  arg_list([A1|A],Acc) -> -    arg_list(A,Acc++io_lib:format("~w,",[A1])). +    arg_list(A,Acc++io_lib:format("~tw,",[A1])).  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -635,7 +635,7 @@ stop(Opts) when is_list(Opts) ->              ok;          {_, {stopped, _}} ->              %% Printout moved out of the ttb loop to avoid occasional deadlock -            io:format("Stored logs in ~s~n", [element(2, Result)]); +            io:format("Stored logs in ~ts~n", [element(2, Result)]);          {_, _} ->              ok      end, @@ -792,7 +792,7 @@ do_stop({FetchOrFormat, UserDir}, Sender, NodeInfo, SessionInfo) ->      write_config(?last_config, all),      Localhost = host(node()),      Dir = get_fetch_dir(UserDir, proplists:get_value(logfile, SessionInfo)), -    file:make_dir(Dir), +    ok = filelib:ensure_dir(filename:join(Dir,"*")),      %% The nodes are traversed twice here because      %% the meta tracing in observer_backend must be      %% stopped before dbg is stopped, and dbg must @@ -900,21 +900,29 @@ fetch_report(Localhost, Dir, Node, MetaFile) ->  fetch(Localhost,Dir,Node,MetaFile) ->      case (host(Node) == Localhost) orelse is_local(MetaFile) of -    true -> % same host, just move the files +        true -> % same host, just move the files  	    Files = get_filenames(Node,MetaFile),  	    lists:foreach( -            fun(File0) -> -                Dest = filename:join(Dir,filename:basename(File0)), -                file:rename(File0, Dest) -            end, -        Files); +              fun(File0) -> +                      Dest = filename:join(Dir,filename:basename(File0)), +                      file:rename(File0, Dest) +              end, +              Files);  	false ->  	    {ok, LSock} = gen_tcp:listen(0, [binary,{packet,2},{active,false}]),  	    {ok,Port} = inet:port(LSock), -	    rpc:cast(Node,observer_backend,ttb_fetch, -		     [MetaFile,{Port,Localhost}]), +            Enc = file:native_name_encoding(), +            Args = +                case rpc:call(Node,erlang,function_exported, +                              [observer_backend,ttb_fetch,3]) of +                    true -> +                        [MetaFile,{Port,Localhost},Enc]; +                    false -> +                        [MetaFile,{Port,Localhost}] +                end, +            rpc:cast(Node,observer_backend,ttb_fetch,Args),  	    {ok, Sock} = gen_tcp:accept(LSock), -	    receive_files(Dir,Sock,undefined), +	    receive_files(Dir,Sock,undefined,Enc),  	    ok = gen_tcp:close(LSock),  	    ok = gen_tcp:close(Sock)      end. @@ -929,25 +937,48 @@ get_filenames(_N, {local,F,_}) ->  get_filenames(N, F) ->      rpc:call(N, observer_backend,ttb_get_filenames,[F]). -receive_files(Dir,Sock,Fd) -> +receive_files(Dir,Sock,Fd,Enc) ->      case gen_tcp:recv(Sock, 0) of  	{ok, <<0,Bin/binary>>} ->  	    file:write(Fd,Bin), -	    receive_files(Dir,Sock,Fd); -	{ok, <<1,Bin/binary>>} -> -	    File0 = binary_to_list(Bin), +	    receive_files(Dir,Sock,Fd,Enc); +	{ok, <<Code,Bin/binary>>} when Code==1; Code==2; Code==3 -> +            File0 = decode_filename(Code,Bin,Enc),  	    File = filename:join(Dir,File0),  	    {ok,Fd1} = file:open(File,[raw,write]), -	    receive_files(Dir,Sock,Fd1); +	    receive_files(Dir,Sock,Fd1,Enc);  	{error, closed} ->  	    ok = file:close(Fd)      end.     +decode_filename(1,Bin,_Enc) -> +    %% Old version of observer_backend - filename encoded with +    %% list_to_binary +    binary_to_list(Bin); +decode_filename(2,Bin,Enc) -> +    %% Successfully encoded filename with correct encoding +    unicode:characters_to_list(Bin,Enc); +decode_filename(3,Bin,latin1) -> +    %% Filename encoded with faulty encoding. This has to be utf8 +    %% remote and latin1 here, and the filename actually containing +    %% characters outside the latin1 range. So making an escaped +    %% variant of the filename and warning about it. +    File0 = unicode:characters_to_list(Bin,utf8), +    File = [ case X of +                     High when High > 255 -> +                         ["\\\\x{",erlang:integer_to_list(X, 16),$}]; +                     Low -> +                         Low +                 end || X <- File0 ], +    io:format("Warning: fetching file with faulty filename encoding ~ts~n" +              "Will be written as ~ts~n", +              [File0,File]), +    File. +  host(Node) ->      [_name,Host] = string:tokens(atom_to_list(Node),"@"),      Host. -  wait_for_fetch([]) ->      ok;  wait_for_fetch(Nodes) -> @@ -1087,7 +1118,7 @@ read_traci(File) ->  	{ok,B} ->   	    interpret_binary(B,dict:new(),[]);  	_ ->  -	    io:format("Warning: no meta data file: ~s~n",[MetaFile]), +	    io:format("Warning: no meta data file: ~ts~n",[MetaFile]),  	    {dict:new(),[]}      end. @@ -1117,7 +1148,7 @@ get_fd(Out) ->  	    Out;  	_file ->  	    file:delete(Out), -	    case file:open(Out,[append]) of +	    case file:open(Out,[append,{encoding,utf8}]) of  		{ok,Fd} -> Fd;  		Error -> exit(Error)  	    end @@ -1303,7 +1334,7 @@ get_term(B) ->      end.  display_warning(Item,Warning) -> -    io:format("Warning: {~w,~w}~n",[Warning,Item]). +    io:format("Warning: {~tw,~tw}~n",[Warning,Item]).  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/ttb_et.erl b/lib/observer/src/ttb_et.erl index 95e8e9aa07..1b828eebc0 100644 --- a/lib/observer/src/ttb_et.erl +++ b/lib/observer/src/ttb_et.erl @@ -137,13 +137,13 @@ processes(E0) ->      E = label(E0),      {{FromProc,FromNode},{ToProc,ToNode}} =   	get_actors(E#event.from,E#event.to), -    {true,E#event{from = io_lib:format("~w~n~w",[FromProc,FromNode]), -		  to = io_lib:format("~w~n~w",[ToProc,ToNode])}}. +    {true,E#event{from = io_lib:format("~tw~n~w",[FromProc,FromNode]), +		  to = io_lib:format("~tw~n~w",[ToProc,ToNode])}}.  mods_and_procs(E) ->      ActorFun = fun({M,_F,_A},{Proc,Node}) ->  -		       io_lib:format("~w~n~w~n~w",[M,Proc,Node]) +		       io_lib:format("~w~n~tw~n~w",[M,Proc,Node])  	       end,      calltrace_filter(E,ActorFun). @@ -155,13 +155,13 @@ modules(E) ->  funcs_and_procs(E) ->      ActorFun = fun({M,F,A},{Proc,Node}) ->  -		       io_lib:format("~s~n~w~n~w",[mfa(M,F,A),Proc,Node]) +		       io_lib:format("~ts~n~tw~n~w",[mfa(M,F,A),Proc,Node])  	       end,      calltrace_filter(E,ActorFun).  functions(E) ->      ActorFun = fun({M,F,A},{_Proc,Node}) ->  -		       io_lib:format("~s~n~w",[mfa(M,F,A),Node]) +		       io_lib:format("~ts~n~w",[mfa(M,F,A),Node])  	       end,      calltrace_filter(E,ActorFun). @@ -221,7 +221,7 @@ label(Event=#event{label=L,contents=C}) ->  	false -> Event      end.  label(L,{M,F,A}) -> label(L,M,F,A); -label(L,Other) -> io_lib:format("~w ~w",[L,Other]). +label(L,Other) -> io_lib:format("~w ~tw",[L,Other]).  label(call,M,F,A) -> "call " ++ mfa(M,F,A);  label(return_from,M,F,A) -> "return_from " ++ mfa(M,F,A);  label(return_to,M,F,A) -> "return_to " ++ mfa(M,F,A); diff --git a/lib/observer/test/Makefile b/lib/observer/test/Makefile index 6100af5e17..a44e54fc52 100644 --- a/lib/observer/test/Makefile +++ b/lib/observer/test/Makefile @@ -27,7 +27,8 @@ MODULES =  \  	ttb_SUITE \  	client \  	server \ -	crashdump_helper +	crashdump_helper \ +	crashdump_helper_unicode  ERL_FILES= $(MODULES:%=%.erl) @@ -46,7 +47,7 @@ RELSYSDIR = $(RELEASE_PATH)/observer_test  # FLAGS  # ----------------------------------------------------  ERL_MAKE_FLAGS += -ERL_COMPILE_FLAGS += +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 4239a3d0d1..41041682c2 100644 --- a/lib/observer/test/crashdump_helper.erl +++ b/lib/observer/test/crashdump_helper.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2017. All Rights Reserved.  %%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License. @@ -19,8 +19,10 @@  %%  -module(crashdump_helper). --export([n1_proc/2,remote_proc/2]). --compile(r13). +-export([n1_proc/2,remote_proc/2, +         dump_maps/0,create_maps/0, +         create_binaries/0]). +-compile(r18).  -include_lib("common_test/include/ct.hrl").  n1_proc(N2,Creator) -> @@ -44,7 +46,7 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) ->      Ref = make_ref(),      Pid = self(),      Bin = list_to_binary(lists:seq(1, 255)), -    SubBin = element(1, split_binary(element(2, split_binary(Bin, 8)), 17)), +    <<_:2,SubBin:17/binary,_/bits>> = Bin,      register(named_port,Port), @@ -60,6 +62,7 @@ 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(bignum,83974938738373873),      put(neg_bignum,-38748762783736367), @@ -92,3 +95,38 @@ 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]. + +%%% +%%% 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_helper_unicode.erl b/lib/observer/test/crashdump_helper_unicode.erl new file mode 100644 index 0000000000..60c3d20315 --- /dev/null +++ b/lib/observer/test/crashdump_helper_unicode.erl @@ -0,0 +1,22 @@ +-module(crashdump_helper_unicode). +-behaviour(gen_server). +-export([start/0, init/1, handle_call/3, handle_cast/2]). +-record(state, {s,a,b,lb}). + +start() -> +    gen_server:start({local, 'unicode_reg_name_αβ'}, ?MODULE, [], []). + +init([]) -> +    process_flag(trap_exit, true), +    ets:new('tab_αβ',[set,named_table]), +    Bin = <<"bin αβ"/utf8>>, +    LongBin = <<"long bin αβ - a utf8 binary which can be expanded αβ"/utf8>>, +    {ok, #state{s = "unicode_string_αβ", +                a = 'unicode_atom_αβ', +                b = Bin, +                lb = LongBin}}. + +handle_call(_Info, _From, State) -> +    {reply, ok, State}. +handle_cast(_Info, State) -> +    {noreply, State}. diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl index 8df69c6624..29b9e406ae 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      ]. @@ -101,7 +102,10 @@ end_per_group(_GroupName, Config) ->  init_per_suite(Config) when is_list(Config) ->      delete_saved(Config),      DataDir = ?config(data_dir,Config), -    Rels = [R || R <- ['17','18'], ?t:is_release_available(R)] ++ [current], +    CurrVsn = list_to_integer(erlang:system_info(otp_release)), +    OldRels = [R || R <- [CurrVsn-2,CurrVsn-1], +		    ?t:is_release_available(list_to_atom(integer_to_list(R)))], +    Rels = OldRels ++ [current],      io:format("Creating crash dumps for the following releases: ~p", [Rels]),      AllDumps = create_dumps(DataDir,Rels),      [{dumps,AllDumps}|Config]. @@ -209,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 @@ -325,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",[]), @@ -336,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" -> @@ -361,6 +399,13 @@ 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",[]), + +	    #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 = @@ -410,34 +455,153 @@ 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_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), +	    {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} = +                lists:keyfind("'unicode_reg_name_αβ'",#proc.name,Procs), +            Pid = pid_to_list(Pid0), +	    {ok,#proc{},[]} = crashdump_viewer:proc_details(Pid), +            io:format("  unicode registered name ok",[]), + +	    {ok,[#ets_table{id="'tab_αβ'",name="'tab_αβ'"}],[]} = +                crashdump_viewer:ets_tables(Pid), +            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; @@ -485,15 +649,43 @@ do_create_dumps(DataDir,Rel) ->  	current ->  	    CD3 = dump_with_args(DataDir,Rel,"instr","+Mim 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)), -	    {[CD1,CD2,CD3,CD4,CD5], DosDump}; +            CD5 = dump_with_size_limit_reached(DataDir,Rel,"trunc_bytes"), +            CD6 = dump_with_unicode_atoms(DataDir,Rel,"unicode"), +            CD7 = dump_with_maps(DataDir,Rel,"maps"), +            TruncatedDumps = truncate_dump(CD1), +	    {[CD1,CD2,CD3,CD4,CD5,CD6,CD7|TruncatedDumps], DosDump};  	_ ->  	    {[CD1,CD2], DosDump}      end. +truncate_dump(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:010203)">>, +    [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. @@ -570,6 +762,48 @@ 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 numer +            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)) ++ "\"", +    PzOpt = [{args,Pz}], +    {ok,N1} = ?t:start_node(n1,peer,Opt ++ PzOpt), +    {ok,_Pid} = rpc:call(N1,crashdump_helper_unicode,start,[]), +    CD = dump(N1,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]), @@ -612,23 +846,22 @@ dos_dump(DataDir,Rel,Dump) ->  	    []      end. +rel_opt(current) -> +    [];  rel_opt(Rel) -> -    case Rel of -	'17' -> [{erl,[{release,"17_latest"}]}]; -	'18' -> [{erl,[{release,"18_latest"}]}]; -	current -> [] -    end. +    [{erl,[{release,lists:concat([Rel,"_latest"])}]}]. +dump_prefix(current) -> +    dump_prefix(erlang:system_info(otp_release));  dump_prefix(Rel) -> -    case Rel of -	'17' -> "r17_dump."; -	'18' -> "r18_dump."; -	current -> "r19_dump." -    end. +    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) -> -    case Rel of -	'17' -> "+R17 "; -	'18' -> "+R18 "; -	current -> "" -    end. +    lists:concat(["+R",Rel," "]). diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl index 41726b1521..0db2c1ea77 100644 --- a/lib/observer/test/observer_SUITE.erl +++ b/lib/observer/test/observer_SUITE.erl @@ -115,6 +115,7 @@ basic(doc) -> [""];  basic(Config) when is_list(Config) ->      timer:send_after(100, "foobar"), %% Otherwise the timer server gets added to procs      ProcsBefore = processes(), +    ProcInfoBefore = [{P,process_info(P)} || P <- ProcsBefore],      NumProcsBefore = length(ProcsBefore),      ok = observer:start(), @@ -145,8 +146,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 +307,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..e8c2f9e37d 100644 --- a/lib/observer/test/ttb_SUITE.erl +++ b/lib/observer/test/ttb_SUITE.erl @@ -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. diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index ca9ad72473..5f43198f85 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 2.3.1 +OBSERVER_VSN = 2.5  | 
