aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSiri Hansen <siri@erlang.org>2013-10-16 16:53:53 +0200
committerDan Gudmundsson <dgud@erlang.org>2014-01-27 16:13:47 +0100
commit41380c0ff6c4fb56aad5702b9d9554ae36580063 (patch)
tree5c16ed3916a700b5f0176d8a33d2ffa28e65c911 /lib
parent549205db3dee21e83a64a01f03b1e8ed2225b276 (diff)
downloadotp-41380c0ff6c4fb56aad5702b9d9554ae36580063.tar.gz
otp-41380c0ff6c4fb56aad5702b9d9554ae36580063.tar.bz2
otp-41380c0ff6c4fb56aad5702b9d9554ae36580063.zip
observer: improve wx version of crashdump_viewer
* bugfixes * better progress dialogs * show expanded binaries in different formats * speed up reading of big crashdumps
Diffstat (limited to 'lib')
-rw-r--r--lib/observer/src/Makefile4
-rw-r--r--lib/observer/src/cdv_bin_wx.erl59
-rw-r--r--lib/observer/src/cdv_detail_win.erl81
-rw-r--r--lib/observer/src/cdv_dist_wx.erl10
-rw-r--r--lib/observer/src/cdv_ets_wx.erl15
-rw-r--r--lib/observer/src/cdv_fun_wx.erl6
-rw-r--r--lib/observer/src/cdv_gen_wx.erl4
-rw-r--r--lib/observer/src/cdv_html_page.erl121
-rw-r--r--lib/observer/src/cdv_info_page.erl39
-rw-r--r--lib/observer/src/cdv_int_tab_wx.erl13
-rw-r--r--lib/observer/src/cdv_mem_wx.erl14
-rw-r--r--lib/observer/src/cdv_mod_wx.erl25
-rw-r--r--lib/observer/src/cdv_multi_panel.erl64
-rw-r--r--lib/observer/src/cdv_port_wx.erl14
-rw-r--r--lib/observer/src/cdv_proc_wx.erl75
-rw-r--r--lib/observer/src/cdv_table_page.erl3
-rw-r--r--lib/observer/src/cdv_term_wx.erl66
-rw-r--r--lib/observer/src/cdv_timer_wx.erl6
-rw-r--r--lib/observer/src/cdv_virtual_list.erl65
-rw-r--r--lib/observer/src/crashdump_viewer.erl593
-rw-r--r--lib/observer/src/crashdump_viewer_html.erl187
-rw-r--r--lib/observer/src/crashdump_viewer_wx.erl224
-rw-r--r--lib/observer/src/observer_lib.erl134
-rw-r--r--lib/observer/src/observer_procinfo.erl59
-rw-r--r--lib/observer/src/observer_term_wx.erl113
-rw-r--r--lib/observer/src/observer_wx.erl16
26 files changed, 1247 insertions, 763 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile
index dfebe71282..0b1813db0a 100644
--- a/lib/observer/src/Makefile
+++ b/lib/observer/src/Makefile
@@ -43,6 +43,7 @@ MODULES= \
cdv_detail_win \
cdv_table_page \
cdv_multi_panel \
+ cdv_html_page \
cdv_gen_wx \
cdv_proc_wx \
cdv_port_wx \
@@ -55,6 +56,8 @@ MODULES= \
cdv_mod_wx \
cdv_mem_wx \
cdv_int_tab_wx \
+ cdv_bin_wx \
+ cdv_term_wx \
etop \
etop_gui \
etop_tr \
@@ -71,6 +74,7 @@ MODULES= \
observer_traceoptions_wx \
observer_tv_table \
observer_tv_wx \
+ observer_term_wx \
ttb \
ttb_et
diff --git a/lib/observer/src/cdv_bin_wx.erl b/lib/observer/src/cdv_bin_wx.erl
index d79f0dbc22..d2cbcfb747 100644
--- a/lib/observer/src/cdv_bin_wx.erl
+++ b/lib/observer/src/cdv_bin_wx.erl
@@ -20,17 +20,60 @@
-export([get_details/1,
detail_pages/0]).
--include_lib("wx/include/wx.hrl").
--include("crashdump_viewer.hrl").
-
%% Callbacks for cdv_detail_win
get_details(Id) ->
{ok,Bin} = crashdump_viewer:expand_binary(Id),
- {ok,{"Expanded Binary", io_lib:format("~tp",[Bin]), []}}.
+ {ok,{"Expanded Binary", Bin, []}}.
detail_pages() ->
- [{simple, "Binary", fun init_bin_page/3}].
+ [{"Binary", fun init_bin_page/2}].
+
+init_bin_page(Parent,Bin) ->
+ cdv_multi_panel:start_link(
+ Parent,
+ [{"Format \~p",cdv_html_page,format_bin_fun("~p",Bin)},
+ {"Format \~tp",cdv_html_page,format_bin_fun("~tp",Bin)},
+ {"Format \~w",cdv_html_page,format_bin_fun("~w",Bin)},
+ {"Format \~s",cdv_html_page,format_bin_fun("~s",Bin)},
+ {"Format \~ts",cdv_html_page,format_bin_fun("~ts",Bin)},
+ {"Hex",cdv_html_page,hex_binary_fun(Bin)},
+ {"Term",cdv_html_page, binary_to_term_fun(Bin)}]).
+
+format_bin_fun(Format,Bin) ->
+ fun() ->
+ try io_lib:format(Format,[Bin]) of
+ Str -> plain_html(lists:flatten(Str))
+ catch error:badarg ->
+ Warning = "This binary can not be formatted with " ++ Format,
+ crashdump_viewer_html:warning(Warning)
+ end
+ end.
+
+binary_to_term_fun(Bin) ->
+ fun() ->
+ try binary_to_term(Bin) of
+ Term -> plain_html(io_lib:format("~p",[Term]))
+ catch error:badarg ->
+ Warning = "This binary can not be coverted to an Erlang term",
+ crashdump_viewer_html:warning(Warning)
+ end
+ end.
+
+-define(line_break,25).
+hex_binary_fun(Bin) ->
+ fun() ->
+ S = "<<" ++ format_hex(Bin,?line_break) ++ ">>",
+ plain_html(io_lib:format("~s",[S]))
+ end.
+
+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) ->
+ [integer_to_list(B1,16),integer_to_list(B2,16),$,,$\n,$\s,$\s
+ | format_hex(Bin,?line_break)];
+format_hex(<<B1:4,B2:4,Bin/binary>>,N) ->
+ [integer_to_list(B1,16),integer_to_list(B2,16),$,
+ | format_hex(Bin,N-1)].
-init_bin_page(Parent, _, Bin) ->
- Html = crashdump_viewer_html:plain_page(Bin),
- observer_lib:html_window(Parent,Html).
+plain_html(Text) ->
+ crashdump_viewer_html:plain_page(Text).
diff --git a/lib/observer/src/cdv_detail_win.erl b/lib/observer/src/cdv_detail_win.erl
index 014ed41e70..4445169379 100644
--- a/lib/observer/src/cdv_detail_win.erl
+++ b/lib/observer/src/cdv_detail_win.erl
@@ -19,14 +19,11 @@
-behaviour(wx_object).
--export([start/3]).
+-export([start_link/3]).
-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
handle_call/3, handle_info/2]).
--export([init_detail_page/3]).
-
-
-include_lib("wx/include/wx.hrl").
-include("crashdump_viewer.hrl").
-include("observer_defs.hrl").
@@ -37,16 +34,13 @@
pages=[]
}).
-
%% Defines
-define(ID_NOTEBOOK, 604).
%% Detail view
-start(Id, ParentFrame, Callback) ->
+start_link(Id, ParentFrame, Callback) ->
wx_object:start_link(?MODULE, [Id, ParentFrame, Callback, self()], []).
-
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([Id, ParentFrame, Callback, Parent]) ->
@@ -72,9 +66,9 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
wxFrame:setMenuBar(Frame, MenuBar),
Panel = wxPanel:new(Frame, []),
- Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
Sizer = wxBoxSizer:new(?wxVERTICAL),
- wxSizer:add(Sizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
+ {InfoPanel,Pages} = create_pages(Panel,Callback:detail_pages(),[Info]),
+ wxSizer:add(Sizer, InfoPanel, [{proportion, 1}, {flag, ?wxEXPAND}]),
case TW of
[] ->
@@ -90,9 +84,6 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
wxPanel:setSizer(Panel, Sizer),
- Pages = [init_panel(Type, Notebook, PageTitle, Fun, [Id,Info])
- || {Type,PageTitle,Fun} <- Callback:detail_pages()],
-
wxFrame:connect(Frame, close_window),
wxMenu:connect(Frame, command_menu_selected),
wxFrame:show(Frame),
@@ -102,8 +93,23 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
pages=Pages
}}.
-init_panel(simple, Notebook, Str, Fun, FunArgs) ->
- Panel = wxScrolledWindow:new(Notebook),
+create_pages(Panel,[{_PageTitle,Fun}],FunArgs) ->
+ %% Only one page - don't create notebook
+ Page = init_panel(Panel, Fun, FunArgs),
+ {Page,[Page]};
+create_pages(Panel,PageSpecs,FunArgs) ->
+ Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
+ Pages = [init_tab(Notebook, PageTitle, Fun, FunArgs)
+ || {PageTitle,Fun} <- PageSpecs],
+ {Notebook, Pages}.
+
+init_tab(Notebook,Title,Fun,FunArgs) ->
+ Panel = init_panel(Notebook,Fun,FunArgs),
+ true = wxNotebook:addPage(Notebook, Panel, Title),
+ Panel.
+
+init_panel(ParentWin, Fun, FunArgs) ->
+ Panel = wxScrolledWindow:new(ParentWin),
wxScrolledWindow:enableScrolling(Panel,true,true),
wxScrolledWindow:setScrollbars(Panel,1,1,0,0),
Sizer = wxBoxSizer:new(?wxHORIZONTAL),
@@ -112,14 +118,8 @@ init_panel(simple, Notebook, Str, Fun, FunArgs) ->
{proportion, 1},
{border, 5}]),
wxPanel:setSizer(Panel, Sizer),
- true = wxNotebook:addPage(Notebook, Panel, Str),
- Panel;
-init_panel(list, Notebook, Str, Fun, FunArgs) ->
- Panel = apply(Fun, [Notebook | FunArgs]),
- true = wxNotebook:addPage(Notebook, Panel, Str),
Panel.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_event(#wx{event=#wxClose{type=close_window}}, State) ->
{stop, normal, State};
@@ -128,41 +128,11 @@ handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}},
State) ->
{stop, normal, State};
-handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,
- linkInfo=#wxHtmlLinkInfo{href=Target}}},
- State) ->
- case Target of
- "#Binary<" ++ Id0 ->
- Id = string:strip(Id0,right,$>),
- start(Id, State#state.frame, cdv_bin_wx);
- _ ->
- cdv_virtual_list:start_detail_win(Target)
- end,
- {noreply, State};
-
-handle_event(#wx{event=#wxMouse{type=left_down},
- userData=Target},
- State) ->
- cdv_virtual_list:start_detail_win(Target),
- {noreply, State};
-
-handle_event(#wx{obj=Obj,
- event=#wxMouse{type=enter_window}},
- State) ->
- wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}),
- {noreply, State};
-
-handle_event(#wx{obj=Obj,
- event=#wxMouse{type=leave_window}},
- State) ->
- wxTextCtrl:setForegroundColour(Obj,?wxBLUE),
- {noreply, State};
-
handle_event(Event, _State) ->
error({unhandled_event, Event}).
handle_info(_Info, State) ->
- %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, _Info]),
{noreply, State}.
handle_call(Call, From, _State) ->
@@ -172,7 +142,7 @@ handle_cast(Cast, _State) ->
error({unhandled_cast, Cast}).
terminate(_Reason, #state{parent=Parent,id=Id,frame=Frame}) ->
- Parent ! {detail_win_closed, Id},
+ wx_object:cast(Parent,{detail_win_closed, Id}),
case Frame of
undefined -> ok;
_ -> wxFrame:destroy(Frame)
@@ -183,11 +153,6 @@ code_change(_, _, State) ->
{ok, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-init_detail_page(Panel, Fields, Info) ->
- Filled = observer_lib:fill_info(Fields, Info),
- {FPanel, _, _UpFields} = observer_lib:display_info(Panel, Filled),
- FPanel.
-
create_menus(MenuBar) ->
Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}],
observer_lib:create_menus(Menus, MenuBar, new_window).
diff --git a/lib/observer/src/cdv_dist_wx.erl b/lib/observer/src/cdv_dist_wx.erl
index 6a4a6da422..6e6284a6ba 100644
--- a/lib/observer/src/cdv_dist_wx.erl
+++ b/lib/observer/src/cdv_dist_wx.erl
@@ -44,8 +44,8 @@ col_to_elem(?COL_TYPE) -> #nod.conn_type.
col_spec() ->
[{"Name", ?wxLIST_FORMAT_LEFT, 300},
- {"Connection type", ?wxLIST_FORMAT_LEFT, 150},
- {"Controller", ?wxLIST_FORMAT_LEFT, 150},
+ {"Connection type", ?wxLIST_FORMAT_LEFT, 130},
+ {"Controller", ?wxLIST_FORMAT_LEFT, 130},
{"Channel", ?wxLIST_FORMAT_RIGHT, 80},
{"Creation", ?wxLIST_FORMAT_RIGHT, 80}].
@@ -64,11 +64,11 @@ get_details(Id) ->
{ok,{Title,Proplist,TW}}.
detail_pages() ->
- [{simple, "General Information", fun init_gen_page/3}].
+ [{"General Information", fun init_gen_page/2}].
-init_gen_page(Parent, _Id, Info) ->
+init_gen_page(Parent, Info) ->
Fields = info_fields(),
- cdv_detail_win:init_detail_page(Parent, Fields, Info).
+ cdv_info_page:start_link(Parent,{Fields,Info,[]}).
%%%-----------------------------------------------------------------
%%% Internal
diff --git a/lib/observer/src/cdv_ets_wx.erl b/lib/observer/src/cdv_ets_wx.erl
index 7a2e80a989..ac45aa297e 100644
--- a/lib/observer/src/cdv_ets_wx.erl
+++ b/lib/observer/src/cdv_ets_wx.erl
@@ -30,10 +30,10 @@
-define(COL_NAME, ?COL_ID+1).
-define(COL_SLOT, ?COL_NAME+1).
-define(COL_OWNER, ?COL_SLOT+1).
--define(COL_TYPE, ?COL_OWNER+1).
--define(COL_BUCK, ?COL_TYPE+1).
+-define(COL_BUCK, ?COL_OWNER+1).
-define(COL_OBJ, ?COL_BUCK+1).
-define(COL_MEM, ?COL_OBJ+1).
+-define(COL_TYPE, ?COL_MEM+1).
%% Callbacks for cdv_virtual_list
col_to_elem(id) -> col_to_elem(?COL_ID);
@@ -49,12 +49,13 @@ col_to_elem(?COL_MEM) -> #ets_table.memory.
col_spec() ->
[{"Id", ?wxLIST_FORMAT_LEFT, 200},
{"Name", ?wxLIST_FORMAT_LEFT, 200},
- {"Slot", ?wxLIST_FORMAT_RIGHT, 60},
+ {"Slot", ?wxLIST_FORMAT_RIGHT, 50},
{"Owner", ?wxLIST_FORMAT_CENTRE, 90},
- {"Type", ?wxLIST_FORMAT_LEFT, 60},
- {"Buckets", ?wxLIST_FORMAT_RIGHT, 60},
- {"Objects", ?wxLIST_FORMAT_RIGHT, 80},
- {"Memory", ?wxLIST_FORMAT_RIGHT, 80}].
+ {"Buckets", ?wxLIST_FORMAT_RIGHT, 50},
+ {"Objects", ?wxLIST_FORMAT_RIGHT, 50},
+ {"Memory", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Type", ?wxLIST_FORMAT_LEFT, 50}
+ ].
get_info(Owner) ->
{ok,Info,TW} = crashdump_viewer:ets_tables(Owner),
diff --git a/lib/observer/src/cdv_fun_wx.erl b/lib/observer/src/cdv_fun_wx.erl
index c4a78594fa..296abd2719 100644
--- a/lib/observer/src/cdv_fun_wx.erl
+++ b/lib/observer/src/cdv_fun_wx.erl
@@ -44,11 +44,11 @@ col_to_elem(?COL_REFC) -> #fu.refc.
col_spec() ->
[{"Module", ?wxLIST_FORMAT_LEFT, 200},
- {"Uniq", ?wxLIST_FORMAT_RIGHT, 150},
- {"Index", ?wxLIST_FORMAT_RIGHT, 60},
+ {"Uniq", ?wxLIST_FORMAT_RIGHT, 100},
+ {"Index", ?wxLIST_FORMAT_RIGHT, 50},
{"Address", ?wxLIST_FORMAT_LEFT, 120},
{"Native Address", ?wxLIST_FORMAT_LEFT, 120},
- {"Refc", ?wxLIST_FORMAT_RIGHT, 60}].
+ {"Refc", ?wxLIST_FORMAT_RIGHT, 50}].
get_info(_) ->
{ok,Info,TW} = crashdump_viewer:funs(),
diff --git a/lib/observer/src/cdv_gen_wx.erl b/lib/observer/src/cdv_gen_wx.erl
index 92eee203a7..92759aaa6d 100644
--- a/lib/observer/src/cdv_gen_wx.erl
+++ b/lib/observer/src/cdv_gen_wx.erl
@@ -22,11 +22,11 @@
-include("crashdump_viewer.hrl").
get_info() ->
- {ok,Info} = crashdump_viewer:init_general_info(),
+ {ok,Info,TW} = crashdump_viewer:general_info(),
Fields = info_fields(),
Proplist =
crashdump_viewer:to_proplist(record_info(fields,general_info),Info),
- {Fields,Proplist,[]}.
+ {Fields,Proplist,TW}.
info_fields() ->
[{"General Information",
diff --git a/lib/observer/src/cdv_html_page.erl b/lib/observer/src/cdv_html_page.erl
new file mode 100644
index 0000000000..b2d059f7f8
--- /dev/null
+++ b/lib/observer/src/cdv_html_page.erl
@@ -0,0 +1,121 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_html_page).
+
+-behaviour(wx_object).
+
+-export([start_link/2]).
+%% wx_object callbacks
+-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
+ handle_event/2, handle_cast/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+%% Records
+-record(state,
+ {panel,
+ expand_table,
+ expand_wins=[]}).
+
+start_link(ParentWin, Info) ->
+ wx_object:start_link(?MODULE, [ParentWin, Info], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([ParentWin, Fun]) when is_function(Fun) ->
+ init([ParentWin, Fun()]);
+init([ParentWin, {expand,HtmlText,Tab}]) ->
+ HtmlWin = observer_lib:html_window(ParentWin),
+ wxHtmlWindow:setPage(HtmlWin,HtmlText),
+ {HtmlWin, #state{panel=HtmlWin,expand_table=Tab}};
+init([ParentWin, HtmlText]) ->
+ HtmlWin = observer_lib:html_window(ParentWin),
+ wxHtmlWindow:setPage(HtmlWin,HtmlText),
+ {HtmlWin, #state{panel=HtmlWin}}.
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info(active, State) ->
+ {noreply, State};
+
+handle_info(Info, State) ->
+ io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) ->
+ Opened = lists:keydelete(Id, 1, Opened0),
+ {noreply, State#state{expand_wins=Opened}};
+
+handle_cast(Msg, State) ->
+ io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,
+ linkInfo=#wxHtmlLinkInfo{href=Target}}},
+ #state{expand_table=Tab}=State) ->
+ NewState=
+ case Target of
+ "#Binary?" ++ BinSpec ->
+ [{"offset",Off},{"size",Size},{"pos",Pos}] =
+ httpd:parse_query(BinSpec),
+ Id = {list_to_integer(Off),
+ list_to_integer(Size),
+ list_to_integer(Pos)},
+ expand(Id,cdv_bin_wx,State);
+ "#Term?" ++ TermKeys ->
+ [{"key1",Key1},{"key2",Key2},{"key3",Key3}] =
+ httpd:parse_query(TermKeys),
+ Id = {Tab,{list_to_integer(Key1),
+ list_to_integer(Key2),
+ list_to_integer(Key3)}},
+ expand(Id,cdv_term_wx,State);
+ _ ->
+ cdv_virtual_list:start_detail_win(Target),
+ State
+ end,
+ {noreply, NewState};
+
+handle_event(Event, State) ->
+ io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ {noreply, State}.
+
+%%%-----------------------------------------------------------------
+%%% Internal
+expand(Id,Callback,#state{expand_wins=Opened0}=State) ->
+ Opened =
+ case lists:keyfind(Id,1,Opened0) of
+ false ->
+ EW = cdv_detail_win:start_link(Id,State#state.panel,Callback),
+ wx_object:get_pid(EW) ! active,
+ [{Id,EW}|Opened0];
+ {_,EW} ->
+ wxFrame:raise(EW),
+ Opened0
+ end,
+ State#state{expand_wins=Opened}.
diff --git a/lib/observer/src/cdv_info_page.erl b/lib/observer/src/cdv_info_page.erl
index b2f94fbc7f..27eb71225d 100644
--- a/lib/observer/src/cdv_info_page.erl
+++ b/lib/observer/src/cdv_info_page.erl
@@ -63,11 +63,18 @@ handle_info(active, State) ->
crashdump_viewer_wx:set_status(State#state.trunc_warn),
{noreply, State};
-handle_info(not_active, #state{} = State) ->
- {noreply, State};
+handle_info(Info, State) ->
+ io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
-handle_info(new_dump, #state{callback=Callback,panel=Panel,
- sizer=Sizer,fpanel=FPanel} = State) ->
+code_change(_, _, State) ->
+ {ok, State}.
+
+handle_call(new_dump, _From, #state{callback=Callback,panel=Panel,
+ sizer=Sizer,fpanel=FPanel} = State) ->
{InfoFields,Info,TW} = Callback:get_info(),
NewFPanel =
wx:batch(
@@ -77,17 +84,7 @@ handle_info(new_dump, #state{callback=Callback,panel=Panel,
wxSizer:layout(Sizer),
FP
end),
- {noreply, State#state{fpanel=NewFPanel,trunc_warn=TW}};
-
-handle_info(Info, State) ->
- io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_, _, State) ->
- {ok, State}.
+ {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]),
@@ -97,6 +94,18 @@ handle_cast(Msg, State) ->
io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
+handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) ->
+ cdv_virtual_list:start_detail_win(Target),
+ {noreply, State};
+
+handle_event(#wx{obj=Obj,event=#wxMouse{type=enter_window}},State) ->
+ wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}),
+ {noreply, State};
+
+handle_event(#wx{obj=Obj,event=#wxMouse{type=leave_window}},State) ->
+ wxTextCtrl:setForegroundColour(Obj,?wxBLUE),
+ {noreply, State};
+
handle_event(Event, State) ->
io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
{noreply, State}.
diff --git a/lib/observer/src/cdv_int_tab_wx.erl b/lib/observer/src/cdv_int_tab_wx.erl
index f039bb3502..b4fa6ca889 100644
--- a/lib/observer/src/cdv_int_tab_wx.erl
+++ b/lib/observer/src/cdv_int_tab_wx.erl
@@ -23,9 +23,16 @@
-include("crashdump_viewer.hrl").
get_info() ->
- [{"Hash Tables",cdv_table_page,get_hash_info()},
- {"Index Tables",cdv_table_page,get_index_info()},
- {"Internal ETS Tables",cdv_table_page,get_internal_ets_info()}].
+ observer_lib:report_progress({ok,"Processing internal tables"}),
+ HashInfo = get_hash_info(),
+ observer_lib:report_progress({ok,33}),
+ IndexInfo = get_index_info(),
+ observer_lib:report_progress({ok,66}),
+ IntEtsInfo = get_internal_ets_info(),
+ observer_lib:report_progress({ok,100}),
+ [{"Hash Tables",cdv_table_page,HashInfo},
+ {"Index Tables",cdv_table_page,IndexInfo},
+ {"Internal ETS Tables",cdv_table_page,IntEtsInfo}].
%%%-----------------------------------------------------------------
%%% Hash tables
diff --git a/lib/observer/src/cdv_mem_wx.erl b/lib/observer/src/cdv_mem_wx.erl
index 413c7de761..673795f39d 100644
--- a/lib/observer/src/cdv_mem_wx.erl
+++ b/lib/observer/src/cdv_mem_wx.erl
@@ -23,11 +23,17 @@
-include_lib("wx/include/wx.hrl").
get_info() ->
+ observer_lib:report_progress({ok,"Processing memory info"}),
+ MemInfo = get_mem_info(),
+ observer_lib:report_progress({ok,33}),
{AllocInfo,AllocTW} = get_alloc_info(),
- [{"Memory",cdv_info_page,get_mem_info()}
+ observer_lib:report_progress({ok,66}),
+ AreaInfo = get_area_info(),
+ observer_lib:report_progress({ok,100}),
+ [{"Memory",cdv_info_page,MemInfo}
| [{Title,cdv_table_page,{Cols,Data,AllocTW}} ||
{Title,Cols,Data} <- AllocInfo]] ++
- [{"Allocated Areas",cdv_table_page,get_area_info()}].
+ [{"Allocated Areas",cdv_table_page,AreaInfo}].
%%%-----------------------------------------------------------------
@@ -74,5 +80,5 @@ fix_alloc([]) ->
[].
alloc_columns(Columns) ->
- [{"", ?wxLIST_FORMAT_LEFT, 200} |
- [{Column, ?wxLIST_FORMAT_RIGHT, 150} || Column <- Columns]].
+ [{"", ?wxLIST_FORMAT_LEFT, 180} |
+ [{Column, ?wxLIST_FORMAT_RIGHT, 140} || Column <- Columns]].
diff --git a/lib/observer/src/cdv_mod_wx.erl b/lib/observer/src/cdv_mod_wx.erl
index 601da1f4e8..8751651fec 100644
--- a/lib/observer/src/cdv_mod_wx.erl
+++ b/lib/observer/src/cdv_mod_wx.erl
@@ -59,33 +59,32 @@ get_details(Id) ->
{ok,{Title,Proplist,TW}}.
detail_pages() ->
- [{simple, "General Information", fun init_gen_page/3},
- {simple, "Current Attributes", fun init_curr_attr_page/3},
- {simple, "Current Compilation Info", fun init_curr_comp_page/3},
- {simple, "Old Attributes", fun init_old_attr_page/3},
- {simple, "Old Compilation Info", fun init_old_comp_page/3}].
+ [{"General Information", fun init_gen_page/2},
+ {"Current Attributes", fun init_curr_attr_page/2},
+ {"Current Compilation Info", fun init_curr_comp_page/2},
+ {"Old Attributes", fun init_old_attr_page/2},
+ {"Old Compilation Info", fun init_old_comp_page/2}].
-init_gen_page(Parent, _Id, Info) ->
+init_gen_page(Parent, Info) ->
Fields = info_fields(),
- cdv_detail_win:init_detail_page(Parent, Fields, Info).
+ cdv_info_page:start_link(Parent,{Fields,Info,[]}).
-init_curr_attr_page(Parent, _Id, Info) ->
+init_curr_attr_page(Parent, Info) ->
init_info_page(Parent, proplists:get_value(current_attrib,Info)).
-init_curr_comp_page(Parent, _Id, Info) ->
+init_curr_comp_page(Parent, Info) ->
init_info_page(Parent, proplists:get_value(current_comp_info,Info)).
-init_old_attr_page(Parent, _Id, Info) ->
+init_old_attr_page(Parent, Info) ->
init_info_page(Parent, proplists:get_value(old_attrib,Info)).
-init_old_comp_page(Parent, _Id, Info) ->
+init_old_comp_page(Parent, Info) ->
init_info_page(Parent, proplists:get_value(old_comp_info,Info)).
init_info_page(Parent, undefined) ->
init_info_page(Parent, "");
init_info_page(Parent, String) ->
- Html = crashdump_viewer_html:plain_page(String),
- observer_lib:html_window(Parent,Html).
+ cdv_html_page:start_link(Parent,crashdump_viewer_html:plain_page(String)).
format({Bin,q}) when is_binary(Bin) ->
[$'|binary_to_list(Bin)];
diff --git a/lib/observer/src/cdv_multi_panel.erl b/lib/observer/src/cdv_multi_panel.erl
index 3744863710..8218824ff2 100644
--- a/lib/observer/src/cdv_multi_panel.erl
+++ b/lib/observer/src/cdv_multi_panel.erl
@@ -40,13 +40,16 @@
dyn_page
}).
-start_link(Notebook, Callback) ->
- wx_object:start_link(?MODULE, [Notebook, Callback], []).
+start_link(Notebook, Info) ->
+ wx_object:start_link(?MODULE, [Notebook, Info], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-init([Notebook, Callback]) ->
+init([Notebook, Callback]) when is_atom(Callback) ->
Pages = Callback:get_info(),
+ {MainPanel,State0} = init([Notebook, Pages]),
+ {MainPanel,State0#state{callback=Callback}};
+init([Notebook, Pages]) ->
MainPanel = wxPanel:new(Notebook),
Sizer = wxBoxSizer:new(?wxHORIZONTAL),
LeftMenuSizer = wxStaticBoxSizer:new(?wxVERTICAL,MainPanel,
@@ -70,14 +73,14 @@ init([Notebook, Callback]) ->
{proportion, 1}, {border, 5}]),
wxPanel:setSizer(MainPanel, Sizer),
- {MainPanel, #state{main_panel=MainPanel,
- main_sizer=Sizer,
- menu=LeftMenu,
- menu_sizer=LeftMenuSizer,
- callback=Callback,
- pages=Pages,
- dyn_panel=DynPanel
- }}.
+ State = load_dyn_page(#state{main_panel=MainPanel,
+ main_sizer=Sizer,
+ menu=LeftMenu,
+ menu_sizer=LeftMenuSizer,
+ pages=Pages,
+ dyn_panel=DynPanel
+ }),
+ {MainPanel, State}.
%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -89,17 +92,6 @@ handle_info(active, State) ->
end),
{noreply, NewState};
-handle_info(not_active, State) ->
- {noreply, State};
-
-handle_info(new_dump, State) ->
- NewState =
- wx:batch(
- fun() ->
- update_left_menu(State)
- end),
- {noreply, NewState};
-
handle_info(Info, State) ->
io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
@@ -110,12 +102,20 @@ terminate(_Reason, _State) ->
code_change(_, _, State) ->
{ok, State}.
+handle_call(new_dump, _From, State) ->
+ NewState =
+ wx:batch(
+ fun() ->
+ update_left_menu(State)
+ end),
+ {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 ~p~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 ~p~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
handle_event(#wx{event=#wxCommand{type=command_listbox_selected,
@@ -167,15 +167,13 @@ load_dyn_page(#state{main_sizer=MainSizer,
dyn_panel=DynPanel,
menu=Menu,
pages=Pages} = State) ->
- {Page,Sizer} =
- wx:batch(fun() ->
- wxWindow:freeze(DynPanel),
- Name = wxListBox:getStringSelection(Menu),
- Res = load_dyn_page(DynPanel,Name,Pages),
- wxSizer:layout(MainSizer),
- wxWindow:thaw(DynPanel),
- Res
- end),
+ %% Freeze and thaw causes a hang (and is not needed) on 2.9 and higher
+ DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9],
+ DoFreeze andalso wxWindow:freeze(DynPanel),
+ Name = wxListBox:getStringSelection(Menu),
+ {Page,Sizer} = load_dyn_page(DynPanel,Name,Pages),
+ wxSizer:layout(MainSizer),
+ DoFreeze andalso wxWindow:thaw(DynPanel),
wx_object:get_pid(Page) ! active,
State#state{dyn_page=Page,dyn_sizer=Sizer}.
diff --git a/lib/observer/src/cdv_port_wx.erl b/lib/observer/src/cdv_port_wx.erl
index ca591aeaaa..e34198ea0b 100644
--- a/lib/observer/src/cdv_port_wx.erl
+++ b/lib/observer/src/cdv_port_wx.erl
@@ -46,10 +46,10 @@ col_to_elem(?COL_CTRL) -> #port.controls;
col_to_elem(?COL_SLOT) -> #port.slot.
col_spec() ->
- [{"Id", ?wxLIST_FORMAT_LEFT, 120},
+ [{"Id", ?wxLIST_FORMAT_LEFT, 100},
{"Connected", ?wxLIST_FORMAT_LEFT, 120},
- {"Name", ?wxLIST_FORMAT_LEFT, 200},
- {"Controls", ?wxLIST_FORMAT_LEFT, 250},
+ {"Name", ?wxLIST_FORMAT_LEFT, 150},
+ {"Controls", ?wxLIST_FORMAT_LEFT, 200},
{"Slot", ?wxLIST_FORMAT_RIGHT, 50}].
get_info(_) ->
@@ -77,11 +77,11 @@ get_details(Id) ->
end.
detail_pages() ->
- [{simple, "General Information", fun init_gen_page/3}].
+ [{"General Information", fun init_gen_page/2}].
-init_gen_page(Parent, _Id, Info) ->
+init_gen_page(Parent, Info) ->
Fields = info_fields(),
- cdv_detail_win:init_detail_page(Parent, Fields, Info).
+ cdv_info_page:start_link(Parent,{Fields,Info,[]}).
format({I1,I2}) ->
"#Port<"++integer_to_list(I1) ++ "." ++ integer_to_list(I2) ++ ">";
@@ -94,7 +94,7 @@ format(D) ->
info_fields() ->
[{"Overview",
[{"Name", name},
- {"Connected", connected},
+ {"Connected", {click,connected}},
{"Slot", slot},
{"Controls", controls}]},
{scroll_boxes,
diff --git a/lib/observer/src/cdv_proc_wx.erl b/lib/observer/src/cdv_proc_wx.erl
index 1320afce28..11f83c79a2 100644
--- a/lib/observer/src/cdv_proc_wx.erl
+++ b/lib/observer/src/cdv_proc_wx.erl
@@ -48,9 +48,9 @@ col_spec() ->
[{"Pid", ?wxLIST_FORMAT_CENTRE, 120},
{"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 250},
{"State", ?wxLIST_FORMAT_LEFT, 100},
- {"Reds", ?wxLIST_FORMAT_RIGHT, 100},
- {"Memory", ?wxLIST_FORMAT_RIGHT, 100},
- {"MsgQ", ?wxLIST_FORMAT_RIGHT, 100}].
+ {"Reds", ?wxLIST_FORMAT_RIGHT, 80},
+ {"Memory", ?wxLIST_FORMAT_RIGHT, 80},
+ {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50}].
get_info(_) ->
{ok,Info,TW} = crashdump_viewer:processes(),
@@ -63,8 +63,13 @@ get_detail_cols(_) ->
get_details(Id) ->
case crashdump_viewer:proc_details(Id) of
{ok,Info,TW} ->
- Proplist =
+ %% The following table is used by crashdump_viewer_html
+ %% for storing expanded terms and it is read by
+ %% cdv_html_page when a link to an expandable term is clicked.
+ Tab = ets:new(cdv_expand,[set,public]),
+ Proplist0 =
crashdump_viewer:to_proplist(record_info(fields,proc),Info),
+ Proplist = [{expand_table,Tab}|Proplist0],
Title = io_lib:format("~s (~p)",[Info#proc.name, Id]),
{ok,{Title,Proplist,TW}};
{error,{other_node,NodeId}} ->
@@ -78,42 +83,38 @@ get_details(Id) ->
end.
detail_pages() ->
- [{simple, "General Information", fun init_gen_page/3},
- {simple, "Messages", fun init_message_page/3},
- {simple, "Dictionary", fun init_dict_page/3},
- {simple, "Stack Dump", fun init_stack_page/3},
- {list, "ETS tables", fun init_ets_page/3},
- {list, "Timers", fun init_timer_page/3}].
-
-init_gen_page(Parent, _Pid, Info) ->
+ [{"General Information", fun init_gen_page/2},
+ {"Messages", fun init_message_page/2},
+ {"Dictionary", fun init_dict_page/2},
+ {"Stack Dump", fun init_stack_page/2},
+ {"ETS tables", fun init_ets_page/2},
+ {"Timers", fun init_timer_page/2}].
+
+init_gen_page(Parent, Info) ->
Fields = info_fields(),
- cdv_detail_win:init_detail_page(Parent, Fields, Info).
-
-init_message_page(Parent, Pid, _Info) ->
- init_memory_page(Parent, Pid, "MsgQueue").
-
-init_dict_page(Parent, Pid, _Info) ->
- init_memory_page(Parent, Pid, "Dictionary").
-
-init_stack_page(Parent, Pid, _Info) ->
- init_memory_page(Parent, Pid, "StackDump").
-
-init_memory_page(Parent, Pid, What) ->
- Win = observer_lib:html_window(Parent),
- Html =
- case crashdump_viewer:expand_memory(Pid,What) of
- {ok,Memory} ->
- crashdump_viewer_html:expanded_memory(What,Memory);
- {error,Reason} ->
- crashdump_viewer_html:warning(Reason)
- end,
- wxHtmlWindow:setPage(Win,Html),
- Win.
-
-init_ets_page(Parent, Pid, _Info) ->
+ cdv_info_page:start_link(Parent,{Fields,Info,[]}).
+
+init_message_page(Parent, Info) ->
+ init_memory_page(Parent, Info, msg_q, "MsgQueue").
+
+init_dict_page(Parent, Info) ->
+ init_memory_page(Parent, Info, dict, "Dictionary").
+
+init_stack_page(Parent, Info) ->
+ init_memory_page(Parent, Info, stack_dump, "StackDump").
+
+init_memory_page(Parent, Info0, Tag, Heading) ->
+ Info = proplists:get_value(Tag,Info0),
+ Tab = proplists:get_value(expand_table,Info0),
+ Html = crashdump_viewer_html:expandable_term(Heading,Info,Tab),
+ cdv_html_page:start_link(Parent,{expand,Html,Tab}).
+
+init_ets_page(Parent, Info) ->
+ Pid = proplists:get_value(pid,Info),
cdv_virtual_list:start_link(Parent, cdv_ets_wx, Pid).
-init_timer_page(Parent, Pid, _Info) ->
+init_timer_page(Parent, Info) ->
+ Pid = proplists:get_value(pid,Info),
cdv_virtual_list:start_link(Parent, cdv_timer_wx, Pid).
%%%-----------------------------------------------------------------
diff --git a/lib/observer/src/cdv_table_page.erl b/lib/observer/src/cdv_table_page.erl
index fecc20b95c..71ec7686ce 100644
--- a/lib/observer/src/cdv_table_page.erl
+++ b/lib/observer/src/cdv_table_page.erl
@@ -72,9 +72,6 @@ handle_info(active, State) ->
crashdump_viewer_wx:set_status(State#state.trunc_warn),
{noreply, State};
-handle_info(not_active, #state{} = State) ->
- {noreply, State};
-
handle_info(Info, State) ->
io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
diff --git a/lib/observer/src/cdv_term_wx.erl b/lib/observer/src/cdv_term_wx.erl
new file mode 100644
index 0000000000..e029a75b29
--- /dev/null
+++ b/lib/observer/src/cdv_term_wx.erl
@@ -0,0 +1,66 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(cdv_term_wx).
+
+-export([get_details/1,
+ detail_pages/0]).
+
+%% Callbacks for cdv_detail_win
+get_details({T,Key}) ->
+ [{Key,Term}] = ets:lookup(T,Key),
+ {ok,{"Expanded Term", Term, []}}.
+
+detail_pages() ->
+ [{"Term", fun init_term_page/2}].
+
+init_term_page(ParentWin, Term) ->
+ cdv_multi_panel:start_link(
+ ParentWin,
+ [{"Format \~p",cdv_html_page,format_term_fun("~p",Term)},
+ {"Format \~tp",cdv_html_page,format_term_fun("~tp",Term)},
+ {"Format \~w",cdv_html_page,format_term_fun("~w",Term)},
+ {"Format \~s",cdv_html_page,format_term_fun("~s",expand(Term))},
+ {"Format \~ts",cdv_html_page,format_term_fun("~ts",expand(Term))}]).
+
+format_term_fun(Format,Term) ->
+ fun() ->
+ try io_lib:format(Format,[Term]) of
+ Str -> plain_html(Str)
+ catch error:badarg ->
+ Warning = "This term can not be formatted with " ++ Format,
+ crashdump_viewer_html:warning(Warning)
+ end
+ end.
+
+plain_html(Text) ->
+ crashdump_viewer_html:plain_page(Text).
+
+expand(['#CDVBin',Offset,Size,Pos]) ->
+ {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ Bin;
+expand([H|T]) ->
+ case expand(T) of
+ ET when is_list(ET) ->
+ [expand(H)|ET];
+ ET -> % The tail is an expanded binary - cannot append with |
+ [expand(H),ET]
+ end;
+expand(Tuple) when is_tuple(Tuple) ->
+ list_to_tuple(expand(tuple_to_list(Tuple)));
+expand(Term) ->
+ Term.
diff --git a/lib/observer/src/cdv_timer_wx.erl b/lib/observer/src/cdv_timer_wx.erl
index 0bfd958a15..2bd250a46c 100644
--- a/lib/observer/src/cdv_timer_wx.erl
+++ b/lib/observer/src/cdv_timer_wx.erl
@@ -37,9 +37,9 @@ col_to_elem(?COL_MSG) -> #timer.msg;
col_to_elem(?COL_TIME) -> #timer.time.
col_spec() ->
- [{"Owner", ?wxLIST_FORMAT_LEFT, 150},
- {"Message", ?wxLIST_FORMAT_LEFT, 500},
- {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 150}].
+ [{"Owner", ?wxLIST_FORMAT_LEFT, 110},
+ {"Message", ?wxLIST_FORMAT_LEFT, 400},
+ {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 80}].
get_info(Owner) ->
{ok,Info,TW} = crashdump_viewer:timers(Owner),
diff --git a/lib/observer/src/cdv_virtual_list.erl b/lib/observer/src/cdv_virtual_list.erl
index 03feadad45..5897b9d02a 100644
--- a/lib/observer/src/cdv_virtual_list.erl
+++ b/lib/observer/src/cdv_virtual_list.erl
@@ -162,7 +162,7 @@ do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened,
NewOpened =
case lists:keyfind(Id, 1, Opened) of
false ->
- case cdv_detail_win:start(Id, Panel, Callback) of
+ case cdv_detail_win:start_link(Id, Panel, Callback) of
{error, _} ->
Opened;
IW ->
@@ -197,31 +197,12 @@ call(_,_) ->
handle_info({holder_updated, Count}, State=#state{grid=Grid}) ->
wxListCtrl:setItemCount(Grid, Count),
Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1),
-
{noreply, State};
-handle_info({detail_win_closed, Id},#state{detail_wins=Opened}=State) ->
- Opened2 = lists:keydelete(Id, 1, Opened),
- {noreply, State#state{detail_wins=Opened2}};
-
handle_info(active, State) ->
crashdump_viewer_wx:set_status(State#state.trunc_warn),
{noreply, State};
-handle_info(not_active, State) ->
- {noreply, State};
-
-handle_info(new_dump,
- #state{grid=Grid,detail_wins=Opened,
- holder=Holder,callback=Callback}=State) ->
- lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened),
- wxListCtrl:deleteAllItems(Grid),
- Ref = erlang:monitor(process,Holder),
- Holder ! stop,
- receive {'DOWN',Ref,_,_,_} -> ok end,
- {NewHolder,TW} = spawn_table_holder(Callback, all),
- {noreply, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}};
-
handle_info(Info, State) ->
io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
@@ -233,6 +214,17 @@ terminate(_Reason, #state{holder=Holder}) ->
code_change(_, _, State) ->
{ok, State}.
+handle_call(new_dump, _From,
+ #state{grid=Grid,detail_wins=Opened,
+ holder=Holder,callback=Callback}=State) ->
+ lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened),
+ wxListCtrl:deleteAllItems(Grid),
+ Ref = erlang:monitor(process,Holder),
+ Holder ! stop,
+ receive {'DOWN',Ref,_,_,_} -> ok end,
+ {NewHolder,TW} = spawn_table_holder(Callback, all),
+ {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]),
{reply, ok, State}.
@@ -241,6 +233,10 @@ handle_cast({start_detail_win,Id}, State) ->
State2 = do_start_detail_win(Id, State),
{noreply, State2};
+handle_cast({detail_win_closed, Id},#state{detail_wins=Opened}=State) ->
+ Opened2 = lists:keydelete(Id, 1, Opened),
+ {noreply, State#state{detail_wins=Opened2}};
+
handle_cast(Msg, State) ->
io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
{noreply, State}.
@@ -333,16 +329,17 @@ spawn_table_holder(Callback, Owner) ->
end,
{Holder,TW}.
-init_table_holder(Parent, Attrs, Callback, Info0) ->
- Info = array:from_list(Info0),
- S = handle_update(Info, #holder{parent=Parent,
- info=Info,
- sort=#sort{sort_key=
- Callback:col_to_elem(id)},
- attrs=Attrs,
- callback=Callback
- }),
- table_holder(S).
+init_table_holder(Parent, Attrs, Callback, InfoList0) ->
+ Sort = #sort{sort_key=Callback:col_to_elem(id)},
+ {_Sort, InfoList} = do_sort(Sort,InfoList0),
+ Info = array:from_list(InfoList),
+ NRows = array:size(Info),
+ Parent ! {holder_updated, NRows},
+ table_holder(#holder{parent=Parent,
+ info=Info,
+ sort=Sort,
+ attrs=Attrs,
+ callback=Callback}).
table_holder(#holder{callback=Callback, attrs=Attrs}=S0) ->
receive
@@ -373,14 +370,6 @@ change_sort(Col, S0=#holder{parent=Parent, info=Info0, sort=Sort0}) ->
Parent ! {holder_updated, NRows},
S0#holder{info=Info, last_row=undefined, sort=Sort}.
-handle_update(Info0, S0=#holder{parent=Parent, sort=Sort}) ->
- NRows = array:size(Info0),
- InfoList0 = array:to_list(Info0),
- {_Sort, InfoList} = do_sort(Sort, InfoList0),
- Info = array:from_list(InfoList),
- Parent ! {holder_updated, NRows},
- S0#holder{info=Info, last_row=undefined}.
-
sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) ->
do_sort(Opt#sort{sort_incr=not Bool}, Table);
sort(Col, Sort,Table) ->
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index bd3d4cc72b..ca91f9061a 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -42,10 +42,9 @@
-export([start/0,start/1,stop/0,script_start/0,script_start/1]).
%% GUI API
--export([start_link/0,
- get_progress/0]).
+-export([start_link/0]).
-export([read_file/1,
- init_general_info/0,
+ general_info/0,
processes/0,
proc_details/1,
port/1,
@@ -64,8 +63,7 @@
allocator_info/0,
hash_tables/0,
index_tables/0,
- expand_binary/1,
- expand_memory/2]).
+ expand_binary/1]).
%% Library function
-export([to_proplist/2, to_value_list/1]).
@@ -86,8 +84,6 @@
-define(chunk_size,1000). % number of bytes read from crashdump at a time
-define(max_line_size,100). % max number of bytes (i.e. characters) the
% line_head/1 function can return
--define(max_display_binary_size,50). % max size of a binary that will be
- % directly displayed.
-define(not_available,"N/A").
@@ -233,13 +229,13 @@ start_link() ->
%%%-----------------------------------------------------------------
%%% Called by crashdump_viewer_wx
read_file(File) ->
- cast({read_file,File,self()}).
+ cast({read_file,File}).
%%%-----------------------------------------------------------------
%%% The following functions are called when the different tabs are
%%% created
-init_general_info() ->
- call(init_general_info).
+general_info() ->
+ call(general_info).
processes() ->
call(procs_summary).
ports() ->
@@ -284,26 +280,11 @@ port(Id) ->
call({port,Id}).
%%%-----------------------------------------------------------------
-%%% Stack dump, message queue and dictionary tabs in process detail
-%%% window
-expand_memory(Pid,What) ->
- call({expand_memory,Pid,What}).
-
-%%%-----------------------------------------------------------------
%%% Called when "<< xxx bytes>>" link is clicket to open a new window
%%% displaying the whole binary.
expand_binary(Pos) ->
call({expand_binary,Pos}).
-%%%-----------------------------------------------------------------
-%%% Wait for a progress report when reading/processing crash dump.
-%%% Called from crashdump_viewer_wx.
-get_progress() ->
- receive
- {progress,Status} ->
- Status
- end.
-
%%====================================================================
%% Server functions
%%====================================================================
@@ -330,50 +311,34 @@ init([]) ->
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
-handle_call(init_general_info,_From,State=#state{file=File}) ->
+handle_call(general_info,_From,State=#state{file=File}) ->
GenInfo = general_info(File),
- [{DumpVsn,_}] = lookup_index(?erl_crash_dump),
NumAtoms = GenInfo#general_info.num_atoms,
WS = parse_vsn_str(GenInfo#general_info.system_vsn,4),
- NewState = State#state{dump_vsn=[list_to_integer(L) ||
- L<-string:tokens(DumpVsn,".")],
- wordsize=WS,
- num_atoms=NumAtoms},
- {reply,{ok,GenInfo},NewState};
-handle_call({expand_memory,Pid,What},_From,State=#state{file=File,binaries=B}) ->
- Reply =
- case truncated_warning([{?proc,Pid}]) of
- [] ->
- {ok,expand_memory(File,What,Pid,B)};
- _TW ->
- Info =
- "The crashdump is truncated in the middle of this "
- "process' memory information, so this information "
- "can not be extracted.",
- {error,Info}
- end,
- {reply,Reply,State};
-handle_call({expand_binary,Pos0},_From,State=#state{file=File}) ->
- Pos = list_to_integer(Pos0),
+ TW = case get(truncated) of
+ true -> ["WARNING: The crash dump is truncated. "
+ "Some information might be missing."];
+ false -> []
+ end,
+ {reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}};
+handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) ->
Fd = open(File),
pos_bof(Fd,Pos),
- {Bin,_Line} = get_binary(val(Fd)),
+ {Bin,_Line} = get_binary(Offset,Size,val(Fd)),
close(Fd),
{reply,{ok,Bin},State};
handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
TW = truncated_warning([?proc]),
Procs = procs_summary(File,WS),
{reply,{ok,Procs,TW},State};
-handle_call({proc_details,Pid},_From,State=#state{file=File,wordsize=WS})->
+handle_call({proc_details,Pid},_From,
+ State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn,binaries=B})->
Reply =
- case get_proc_details(File,Pid,State#state.dump_vsn,WS) of
- {ok,Proc} ->
- TW = truncated_warning([{?proc,Pid}]),
+ case get_proc_details(File,Pid,WS,DumpVsn,B) of
+ {ok,Proc,TW} ->
{ok,Proc,TW};
- {other_node,Node} ->
- {error,{other_node,Node}};
- not_found ->
- {error,not_found}
+ Other ->
+ {error,Other}
end,
{reply, Reply, State};
handle_call({port,Id},_From,State=#state{file=File}) ->
@@ -469,13 +434,13 @@ handle_call(index_tables,_From,State=#state{file=File}) ->
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
-handle_cast({read_file,File,ProgressReceiver}, _State) ->
- case do_read_file(File,ProgressReceiver) of
- {ok,Binaries} ->
- report_progress(ProgressReceiver,{ok,done}),
- {noreply, #state{file=File,binaries=Binaries}};
+handle_cast({read_file,File}, _State) ->
+ case do_read_file(File) of
+ {ok,Binaries,DumpVsn} ->
+ observer_lib:report_progress({ok,done}),
+ {noreply, #state{file=File,binaries=Binaries,dump_vsn=DumpVsn}};
Error ->
- report_progress(ProgressReceiver,Error),
+ end_progress(Error),
{noreply, #state{}}
end;
handle_cast(stop,State) ->
@@ -565,10 +530,6 @@ compare_pid("<"++Id,"<"++OtherId) ->
compare_pid(_,_) ->
false.
-report_progress(Receiver,Progress) ->
- Receiver ! {progress,Progress},
- ok.
-
open(File) ->
{ok,Fd} = file:open(File,[read,read_ahead,raw,binary]),
Fd.
@@ -623,22 +584,14 @@ get_chunk(Fd) ->
%% Read and report progress
progress_read(Fd) ->
- {R,RBytes} =
+ {R,Bytes} =
case read(Fd) of
{ok,Bin} ->
{{ok,Bin},size(Bin)};
Other ->
{Other,0}
end,
- {Receiver,Bytes,Size} = get(progress),
- Bytes1 = Bytes + RBytes,
- Percent0 = (100*Bytes) div Size,
- Percent = (100*Bytes1) div Size,
- if Percent > Percent0 ->
- report_progress(Receiver,{ok,Percent});
- true -> ok
- end,
- put(progress,{Receiver,Bytes1,Size}),
+ update_progress(Bytes),
R.
read(Fd) ->
@@ -801,14 +754,13 @@ parse_vsn_str(Str,WS) ->
%%%
%%% Progress is reported during the time and MUST be checked with
%%% crashdump_viewer:get_progress/0 until it returns {ok,done}.
-do_read_file(File,ProgressReceiver) ->
+do_read_file(File) ->
case file:read_file_info(File) of
{ok,#file_info{type=regular,
access=FileA,
size=Size}} when FileA=:=read; FileA=:=read_write ->
Fd = open(File),
- report_progress(ProgressReceiver,{ok,"Reading file"}),
- put(progress,{ProgressReceiver,0,Size}),
+ init_progress("Reading file",Size),
case progress_read(Fd) of
{ok,<<$=:8,TagAndRest/binary>>} ->
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1),
@@ -818,11 +770,14 @@ do_read_file(File,ProgressReceiver) ->
insert_index(Tag,Id,N1+1),
put(last_tag,{Tag,""}),
indexify(Fd,Rest,N1),
- erase(progress),
+ end_progress(),
check_if_truncated(),
- Binaries = read_binaries(Fd,ProgressReceiver),
+ [{DumpVsn0,_}] = lookup_index(?erl_crash_dump),
+ DumpVsn = [list_to_integer(L) ||
+ L<-string:tokens(DumpVsn0,".")],
+ Binaries = read_binaries(Fd,DumpVsn),
close(Fd),
- {ok,Binaries};
+ {ok,Binaries,DumpVsn};
_Other ->
R = io_lib:format(
"~s is not an Erlang crash dump~n",
@@ -1059,49 +1014,30 @@ procs_summary(File,WS) ->
_ -> Proc
end
end,
- lookup_and_parse_index(File,?proc,ParseFun).
+ lookup_and_parse_index(File,?proc,ParseFun,"processes").
%%-----------------------------------------------------------------
%% Page with one process
-get_proc_details(File,Pid,DumpVsn,WS) ->
+get_proc_details(File,Pid,WS,DumpVsn,Binaries) ->
case lookup_index(?proc,Pid) of
[{_,Start}] ->
Fd = open(File),
- pos_bof(Fd,Start),
- Proc0 =
- case DumpVsn of
- [0,0] ->
- %% Old version (translated)
- #proc{pid=Pid};
- _ ->
- #proc{pid=Pid,
- stack_dump=if_exist(?proc_stack,Pid),
- msg_q=if_exist(?proc_messages,Pid),
- dict=if_exist(?proc_dictionary,Pid)}
+ {{Stack,MsgQ,Dict},TW} =
+ case truncated_warning([{?proc,Pid}]) of
+ [] ->
+ {expand_memory(Fd,Pid,DumpVsn,Binaries),[]};
+ TW0 ->
+ {{[],[],[]},TW0}
end,
+ pos_bof(Fd,Start),
+ Proc0 = #proc{pid=Pid,stack_dump=Stack,msg_q=MsgQ,dict=Dict},
Proc = get_procinfo(Fd,fun all_procinfo/5,Proc0,WS),
close(Fd),
- {ok,Proc};
+ {ok,Proc,TW};
_ ->
maybe_other_node(Pid)
end.
-if_exist(Tag,Key) ->
- case count_index(Tag,Key) of
- 0 ->
- Tag1 =
- case is_proc_tag(Tag) of
- true -> ?proc;
- false -> Tag
- end,
- case truncated_here({Tag1,Key}) of
- true -> truncated;
- false -> undefined
- end;
- _ ->
- expand
- end.
-
get_procinfo(Fd,Fun,Proc,WS) ->
case line_head(Fd) of
"State" ->
@@ -1141,11 +1077,7 @@ get_procinfo(Fd,Fun,Proc,WS) ->
main_procinfo(Fd,Fun,Proc,WS,LineHead) ->
case LineHead of
- "Stack dump" ->
- %% This is the last element in older dumps (DumpVsn=0.0)
- Proc;
"=" ++ _next_tag ->
- %% DumpVsn=0.1 or newer: No stack dump here
Proc;
"arity = " ++ _ ->
%%! Temporary workaround
@@ -1295,97 +1227,70 @@ maybe_other_node(Id) ->
end.
-expand_memory(File,What,Pid,Binaries) ->
- Fd = open(File),
+expand_memory(Fd,Pid,DumpVsn,Binaries) ->
+ BinAddrAdj = get_bin_addr_adj(DumpVsn),
put(fd,Fd),
- Dict = read_heap(Fd,Pid,Binaries),
- Expanded =
- case What of
- "StackDump" -> read_stack_dump(Fd,Pid,Dict);
- "MsgQueue" -> read_messages(Fd,Pid,Dict);
- "Dictionary" -> read_dictionary(Fd,?proc_dictionary,Pid,Dict)
- end,
+ Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries),
+ Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict),
+ read_messages(Fd,Pid,BinAddrAdj,Dict),
+ read_dictionary(Fd,Pid,BinAddrAdj,Dict)},
erase(fd),
- close(Fd),
Expanded.
-
+
+%%%-----------------------------------------------------------------
+%%% This is a workaround for a bug in dump versions prior to 0.3:
+%%% Addresses were truncated to 32 bits. This could cause binaries to
+%%% get the same address as heap terms in the dump. To work around it
+%%% we always store binaries on very high addresses in the gb_tree.
+get_bin_addr_adj(DumpVsn) when DumpVsn < [0,3] ->
+ 16#f bsl 64;
+get_bin_addr_adj(_) ->
+ 0.
+
%%%
%%% Read binaries.
%%%
-read_binaries(Fd,ProgressReceiver) ->
+read_binaries(Fd,DumpVsn) ->
AllBinaries = lookup_index(?binary),
- NumBinaries = length(AllBinaries),
- ReportInterval = (NumBinaries div 100) + 1,
- report_progress(ProgressReceiver,{ok,"Processing binaries"}),
- read_binaries(Fd, AllBinaries, gb_trees:empty(),
- ProgressReceiver,ReportInterval,ReportInterval,0).
-
-read_binaries(Fd,Bins,Dict,Receiver,0,ReportInterval,Percent0) ->
- Percent = Percent0+1,
- report_progress(Receiver,{ok,Percent}),
- read_binaries(Fd,Bins,Dict,Receiver,ReportInterval,ReportInterval,Percent);
-read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0,Receiver,Count,ReportInterval,Percent) ->
- pos_bof(Fd,Pos),
- {Addr,_} = get_hex(Addr0),
- Dict =
- case line_head(Fd) of
- {eof,_} ->
- gb_trees:enter(Addr,'#CDVTruncatedBinary',Dict0);
- Size0 ->
- {Size,_} = get_hex(Size0),
- if Size > ?max_display_binary_size ->
- gb_trees:enter(Addr,{'#CDVTooBig',binary,Pos},Dict0);
- true ->
- pos_bof(Fd,Pos),
- Line = val(Fd),
- parse_binary(Addr,Line,Dict0)
- end
- end,
- read_binaries(Fd,Bins,Dict,Receiver,Count-1,ReportInterval,Percent);
-read_binaries(_Fd,[],Dict,Receiver,_Count,_ReportInterval,_Percent) ->
- report_progress(Receiver,{ok,100}),
- Dict.
-
-parse_binary(Addr, Line0, Dict) ->
- case get_hex(Line0) of
- {N,":"++Line1} ->
- {Bin,Line} = get_binary(N, Line1, []),
- [] = skip_blanks(Line),
- gb_trees:enter(Addr, Bin, Dict);
- {_N,[]} ->
- %% If the dump is truncated before the ':' in this line, then
- %% line_head/1 might not discover it (if a \n has been inserted
- %% somehow???)
- gb_trees:enter(Addr,'#CDVTruncatedBinary',Dict)
- end.
-
-
+ 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.
%%%
-read_stack_dump(Fd,Pid,Dict) ->
+read_stack_dump(Fd,Pid,BinAddrAdj,Dict) ->
case lookup_index(?proc_stack,Pid) of
[{_,Start}] ->
pos_bof(Fd,Start),
- read_stack_dump1(Fd,Dict,[]);
+ read_stack_dump1(Fd,BinAddrAdj,Dict,[]);
[] ->
[]
end.
-read_stack_dump1(Fd,Dict,Acc) ->
+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
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
- Stack = parse_top(Line,Dict),
- read_stack_dump1(Fd,Dict,[Stack|Acc])
+ Stack = parse_top(Line,BinAddrAdj,Dict),
+ read_stack_dump1(Fd,BinAddrAdj,Dict,[Stack|Acc])
end.
-parse_top(Line0, D) ->
+parse_top(Line0, BinAddrAdj, D) ->
{Label,Line1} = get_label(Line0),
- {Term,Line,D} = parse_term(Line1, D),
+ {Term,Line,D} = parse_term(Line1, BinAddrAdj, D),
[] = skip_blanks(Line),
{Label,Term}.
@@ -1393,27 +1298,27 @@ parse_top(Line0, D) ->
%%% Read message queue.
%%%
-read_messages(Fd,Pid,Dict) ->
+read_messages(Fd,Pid,BinAddrAdj,Dict) ->
case lookup_index(?proc_messages,Pid) of
[{_,Start}] ->
pos_bof(Fd,Start),
- read_messages1(Fd,Dict,[]);
+ read_messages1(Fd,BinAddrAdj,Dict,[]);
[] ->
[]
end.
-read_messages1(Fd,Dict,Acc) ->
+read_messages1(Fd,BinAddrAdj,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case val(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
- Msg = parse_message(Line,Dict),
- read_messages1(Fd,Dict,[Msg|Acc])
+ Msg = parse_message(Line,BinAddrAdj,Dict),
+ read_messages1(Fd,BinAddrAdj,Dict,[Msg|Acc])
end.
-parse_message(Line0, D) ->
- {Msg,":"++Line1,_} = parse_term(Line0, D),
- {Token,Line,_} = parse_term(Line1, D),
+parse_message(Line0, BinAddrAdj, D) ->
+ {Msg,":"++Line1,_} = parse_term(Line0, BinAddrAdj, D),
+ {Token,Line,_} = parse_term(Line1, BinAddrAdj, D),
[] = skip_blanks(Line),
{Msg,Token}.
@@ -1421,26 +1326,26 @@ parse_message(Line0, D) ->
%%% Read process dictionary
%%%
-read_dictionary(Fd,Tag,Pid,Dict) ->
- case lookup_index(Tag,Pid) of
+read_dictionary(Fd,Pid,BinAddrAdj,Dict) ->
+ case lookup_index(?proc_dictionary,Pid) of
[{_,Start}] ->
pos_bof(Fd,Start),
- read_dictionary1(Fd,Dict,[]);
+ read_dictionary1(Fd,BinAddrAdj,Dict,[]);
[] ->
[]
end.
-read_dictionary1(Fd,Dict,Acc) ->
+read_dictionary1(Fd,BinAddrAdj,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case val(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
- Msg = parse_dictionary(Line,Dict),
- read_dictionary1(Fd,Dict,[Msg|Acc])
+ Msg = parse_dictionary(Line,BinAddrAdj,Dict),
+ read_dictionary1(Fd,BinAddrAdj,Dict,[Msg|Acc])
end.
-parse_dictionary(Line0, D) ->
- {Entry,Line,_} = parse_term(Line0, D),
+parse_dictionary(Line0, BinAddrAdj, D) ->
+ {Entry,Line,_} = parse_term(Line0, BinAddrAdj, D),
[] = skip_blanks(Line),
Entry.
@@ -1448,16 +1353,16 @@ parse_dictionary(Line0, D) ->
%%% Read heap data.
%%%
-read_heap(Fd,Pid,Dict0) ->
+read_heap(Fd,Pid,BinAddrAdj,Dict0) ->
case lookup_index(?proc_heap,Pid) of
[{_,Pos}] ->
pos_bof(Fd,Pos),
- read_heap(Dict0);
+ read_heap(BinAddrAdj,Dict0);
[] ->
Dict0
end.
-read_heap(Dict0) ->
+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 ->
@@ -1468,14 +1373,14 @@ read_heap(Dict0) ->
put(fd, end_of_heap),
Dict0;
Line ->
- Dict = parse(Line,Dict0),
- read_heap(Dict)
+ Dict = parse(Line,BinAddrAdj,Dict0),
+ read_heap(BinAddrAdj,Dict)
end
end.
-parse(Line0, Dict0) ->
+parse(Line0, BinAddrAdj, Dict0) ->
{Addr,":"++Line1} = get_hex(Line0),
- {_Term,Line,Dict} = parse_heap_term(Line1, Addr, Dict0),
+ {_Term,Line,Dict} = parse_heap_term(Line1, Addr, BinAddrAdj, Dict0),
[] = skip_blanks(Line),
Dict.
@@ -1498,7 +1403,7 @@ get_port(File,Port) ->
%% Page with all ports
get_ports(File) ->
ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=port_to_tuple(Id)}) end,
- lookup_and_parse_index(File,?port,ParseFun).
+ lookup_and_parse_index(File,?port,ParseFun,"ports").
%% Converting port string to tuple to secure correct sorting. This is
%% converted back in cdv_port_wx:format/1.
@@ -1566,7 +1471,7 @@ get_ets_tables(File,Pid,WS) ->
ParseFun = fun(Fd,Id) ->
get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS)
end,
- lookup_and_parse_index(File,{?ets,Pid},ParseFun).
+ lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets").
get_etsinfo(Fd,EtsTable,WS) ->
case line_head(Fd) of
@@ -1619,7 +1524,7 @@ get_internal_ets_tables(File,WS) ->
%% Page with list of all timers
get_timers(File,Pid) ->
ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=list_to_pid(Id)}) end,
- lookup_and_parse_index(File,{?timer,Pid},ParseFun).
+ lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers").
get_timerinfo_1(Fd,Timer) ->
case line_head(Fd) of
@@ -1758,7 +1663,7 @@ loaded_mods(File) ->
[] ->
{"unknown","unknown"}
end,
- {CC,OC,lookup_and_parse_index(File,?mod,ParseFun)}.
+ {CC,OC,lookup_and_parse_index(File,?mod,ParseFun,"modules")}.
get_loaded_mod_totals(Fd,{CC,OC}) ->
case line_head(Fd) of
@@ -1845,7 +1750,7 @@ hex_to_dec(N) -> list_to_integer(N).
%% Page with list of all funs
funs(File) ->
ParseFun = fun(Fd,_Id) -> get_funinfo(Fd,#fu{}) end,
- lookup_and_parse_index(File,?fu,ParseFun).
+ lookup_and_parse_index(File,?fu,ParseFun,"funs").
get_funinfo(Fd,Fu) ->
case line_head(Fd) of
@@ -1883,6 +1788,7 @@ atoms(File,NumAtoms) ->
get_atoms(Fd,NumAtoms) ->
case get_chunk(Fd) of
{ok,Bin} ->
+ init_progress("Processing atoms",NumAtoms),
get_atoms(Fd,Bin,NumAtoms,[]);
eof ->
[]
@@ -1896,18 +1802,20 @@ get_atoms(Fd,Bin,NumAtoms,Atoms) ->
get_atoms1(Fd,Bins,NumAtoms,Atoms).
get_atoms1(_Fd,[<<"=",_/binary>>|_],_N,Atoms) ->
+ end_progress(),
Atoms;
get_atoms1(Fd,[LastBin],N,Atoms) ->
case get_chunk(Fd) of
{ok,Bin0} ->
get_atoms(Fd,<<LastBin/binary,Bin0/binary>>,N,Atoms);
eof ->
- Atoms
+ end_progress(),
+ [{N,get_atom(LastBin)}|Atoms]
end;
get_atoms1(Fd,[Bin|Bins],N,Atoms) ->
+ update_progress(),
get_atoms1(Fd,Bins,N-1,[{N,get_atom(Bin)}|Atoms]).
-
%% This ensures sorting according to first actual letter in the atom,
%% disregarding possible single quote. It is formatted back to correct
%% syntax in cdv_atom_wx:format/1
@@ -2309,113 +2217,117 @@ get_indextableinfo1(Fd,IndexTable) ->
%%%-----------------------------------------------------------------
%%% Parse memory in crashdump version 0.1 and newer
%%%
-parse_heap_term([$l|Line0], Addr, D0) -> %Cons cell.
- {H,"|"++Line1,D1} = parse_term(Line0, D0),
- {T,Line,D2} = parse_term(Line1, D1),
+parse_heap_term([$l|Line0], Addr, BinAddrAdj, D0) -> %Cons cell.
+ {H,"|"++Line1,D1} = parse_term(Line0, BinAddrAdj, D0),
+ {T,Line,D2} = parse_term(Line1, BinAddrAdj, D1),
Term = [H|T],
D = gb_trees:insert(Addr, Term, D2),
{Term,Line,D};
-parse_heap_term([$t|Line0], Addr, D) -> %Tuple
+parse_heap_term([$t|Line0], Addr, BinAddrAdj, D) -> %Tuple
{N,":"++Line} = get_hex(Line0),
- parse_tuple(N, Line, Addr, D, []);
-parse_heap_term([$F|Line0], Addr, D0) -> %Float
+ parse_tuple(N, Line, Addr, BinAddrAdj, D, []);
+parse_heap_term([$F|Line0], Addr, _BinAddrAdj, D0) -> %Float
{N,":"++Line1} = get_hex(Line0),
{Chars,Line} = get_chars(N, Line1),
Term = list_to_float(Chars),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("B16#"++Line0, Addr, D0) -> %Positive big number.
+parse_heap_term("B16#"++Line0, Addr, _BinAddrAdj, D0) -> %Positive big number.
{Term,Line} = get_hex(Line0),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("B-16#"++Line0, Addr, D0) -> %Negative big number
+parse_heap_term("B-16#"++Line0, Addr, _BinAddrAdj, D0) -> %Negative big number
{Term0,Line} = get_hex(Line0),
Term = -Term0,
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("B"++Line0, Addr, D0) -> %Decimal big num (new in R10B-something).
+parse_heap_term("B"++Line0, Addr, _BinAddrAdj, D0) -> %Decimal big num
case string:to_integer(Line0) of
{Int,Line} when is_integer(Int) ->
D = gb_trees:insert(Addr, Int, D0),
{Int,Line,D}
end;
-parse_heap_term([$P|Line0], Addr, D0) -> % External Pid.
+parse_heap_term([$P|Line0], Addr, _BinAddrAdj, D0) -> % External Pid.
{Pid0,Line} = get_id(Line0),
- Pid = "#CDVPid"++Pid0,
+ Pid = ['#CDVPid'|Pid0],
D = gb_trees:insert(Addr, Pid, D0),
{Pid,Line,D};
-parse_heap_term([$p|Line0], Addr, D0) -> % External Port.
+parse_heap_term([$p|Line0], Addr, _BinAddrAdj, D0) -> % External Port.
{Port0,Line} = get_id(Line0),
- Port = "#CDVPort"++Port0,
+ Port = ['#CDVPort'|Port0],
D = gb_trees:insert(Addr, Port, D0),
{Port,Line,D};
-parse_heap_term("E"++Line0, Addr, D0) -> %Term encoded in external format.
+parse_heap_term("E"++Line0, Addr, _BinAddrAdj, D0) -> %Term encoded in external format.
{Bin,Line} = get_binary(Line0),
Term = binary_to_term(Bin),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("Yh"++Line0, Addr, D0) -> %Heap binary.
+parse_heap_term("Yh"++Line0, Addr, _BinAddrAdj, D0) -> %Heap binary.
{Term,Line} = get_binary(Line0),
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("Yc"++Line0, Addr, D0) -> %Reference-counted binary.
- {Binp,":"++Line1} = get_hex(Line0),
- {First,":"++Line2} = get_hex(Line1),
+parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) -> %Reference-counted binary.
+ {Binp0,":"++Line1} = get_hex(Line0),
+ {Offset,":"++Line2} = get_hex(Line1),
{Sz,Line} = get_hex(Line2),
+ Binp = Binp0 bor BinAddrAdj,
Term = case gb_trees:lookup(Binp, D0) of
- {value,<<_:First/binary,T:Sz/binary,_/binary>>} -> T;
- {value,{'#CDVTooBig',binary,Pos}} -> cdvbin(Sz,Pos);
- {value,'#CDVTruncatedBinary'} -> '#CDVTruncatedBinary';
+ {value,Bin} -> cdvbin(Offset,Sz,Bin);
none -> '#CDVNonexistingBinary'
end,
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
-parse_heap_term("Ys"++Line0, Addr, D0) -> %Sub binary.
- {Binp,":"++Line1} = get_hex(Line0),
- {First,":"++Line2} = get_hex(Line1),
+parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) -> %Sub binary.
+ {Binp0,":"++Line1} = get_hex(Line0),
+ {Offset,":"++Line2} = get_hex(Line1),
{Sz,Line} = get_hex(Line2),
+ Binp = Binp0 bor BinAddrAdj,
Term = case gb_trees:lookup(Binp, D0) of
- {value,<<_:First/binary,T:Sz/binary,_/binary>>} -> T;
- {value,{'#CDVTooBig',binary,Pos}} -> cdvbin(Sz,Pos);
- {value,'#CDVTruncatedBinary'} -> '#CDVTruncatedBinary';
+ {value,Bin} -> cdvbin(Offset,Sz,Bin);
+ none when Binp0=/=Binp ->
+ %% Might it be on the heap?
+ case gb_trees:lookup(Binp0, D0) of
+ {value,Bin} -> cdvbin(Offset,Sz,Bin);
+ none -> '#CDVNonexistingBinary'
+ end;
none -> '#CDVNonexistingBinary'
end,
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D}.
-parse_tuple(0, Line, Addr, D0, Acc) ->
+parse_tuple(0, Line, Addr, _, D0, Acc) ->
Tuple = list_to_tuple(lists:reverse(Acc)),
D = gb_trees:insert(Addr, Tuple, D0),
{Tuple,Line,D};
-parse_tuple(N, Line0, Addr, D0, Acc) ->
- case parse_term(Line0, D0) of
+parse_tuple(N, Line0, Addr, BinAddrAdj, D0, Acc) ->
+ case parse_term(Line0, BinAddrAdj, D0) of
{Term,[$,|Line],D} when N > 1 ->
- parse_tuple(N-1, Line, Addr, D, [Term|Acc]);
+ parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc]);
{Term,Line,D}->
- parse_tuple(N-1, Line, Addr, D, [Term|Acc])
+ parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc])
end.
-parse_term([$H|Line0], D) -> %Pointer to heap term.
+parse_term([$H|Line0], BinAddrAdj, D) -> %Pointer to heap term.
{Ptr,Line} = get_hex(Line0),
- deref_ptr(Ptr, Line, D);
-parse_term([$N|Line], D) -> %[] (nil).
+ deref_ptr(Ptr, Line, BinAddrAdj, D);
+parse_term([$N|Line], _, D) -> %[] (nil).
{[],Line,D};
-parse_term([$I|Line0], D) -> %Small.
+parse_term([$I|Line0], _, D) -> %Small.
{Int,Line} = string:to_integer(Line0),
{Int,Line,D};
-parse_term([$A|_]=Line, D) -> %Atom.
+parse_term([$A|_]=Line, _, D) -> %Atom.
parse_atom(Line, D);
-parse_term([$P|Line0], D) -> %Pid.
+parse_term([$P|Line0], _, D) -> %Pid.
{Pid,Line} = get_id(Line0),
- {"#CDVPid"++Pid,Line,D};
-parse_term([$p|Line0], D) -> %Port.
+ {['#CDVPid'|Pid],Line,D};
+parse_term([$p|Line0], _, D) -> %Port.
{Port,Line} = get_id(Line0),
- {"#CDVPort"++Port,Line,D};
-parse_term([$S|Str0], D) -> %Information string.
+ {['#CDVPort'|Port],Line,D};
+parse_term([$S|Str0], _, D) -> %Information string.
Str = lists:reverse(skip_blanks(lists:reverse(Str0))),
{Str,[],D};
-parse_term([$D|Line0], D) -> %DistExternal
+parse_term([$D|Line0], _, D) -> %DistExternal
try
{AttabSize,":"++Line1} = get_hex(Line0),
{Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []),
@@ -2455,7 +2367,7 @@ parse_atom_translation_table(N, Line0, As) ->
-deref_ptr(Ptr, Line, D0) ->
+deref_ptr(Ptr, Line, BinAddrAdj, D0) ->
case gb_trees:lookup(Ptr, D0) of
{value,Term} ->
{Term,Line,D0};
@@ -2467,10 +2379,10 @@ deref_ptr(Ptr, Line, D0) ->
case val(Fd) of
"="++_ ->
put(fd, end_of_heap),
- deref_ptr(Ptr, Line, D0);
+ deref_ptr(Ptr, Line, BinAddrAdj, D0);
L ->
- D = parse(L, D0),
- deref_ptr(Ptr, Line, D)
+ D = parse(L, BinAddrAdj, D0),
+ deref_ptr(Ptr, Line, BinAddrAdj, D)
end
end
end.
@@ -2508,13 +2420,16 @@ get_chars(0, Line, Acc) ->
get_chars(N, [H|T], Acc) ->
get_chars(N-1, T, [H|Acc]).
-get_id(Line) ->
- get_id(Line, []).
+get_id(Line0) ->
+ [$<|Line] = lists:dropwhile(fun($<) -> false; (_) -> true end,Line0),
+ get_id(Line, [], []).
-get_id([$>|Line], Acc) ->
- {lists:reverse(Acc, [$>]),Line};
-get_id([H|T], Acc) ->
- get_id(T, [H|Acc]).
+get_id([$>|Line], Acc, Id) ->
+ {lists:reverse(Id,[list_to_integer(lists:reverse(Acc))]),Line};
+get_id([$.|Line], Acc, Id) ->
+ get_id(Line,[],[list_to_integer(lists:reverse(Acc))|Id]);
+get_id([H|T], Acc, Id) ->
+ get_id(T, [H|Acc], Id).
get_label(L) ->
get_label(L, []).
@@ -2532,19 +2447,26 @@ get_label([H|T], Acc) ->
get_binary(Line0) ->
{N,":"++Line} = get_hex(Line0),
- get_binary(N, Line, []).
+ do_get_binary(N, Line, []).
-get_binary(0, Line, Acc) ->
+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) ->
{list_to_binary(lists:reverse(Acc)),Line};
-get_binary(N, [A,B|Line], Acc) ->
+do_get_binary(N, [A,B|Line], Acc) ->
Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B),
- get_binary(N-1, Line, [Byte|Acc]);
-get_binary(_N, [], _Acc) ->
+ do_get_binary(N-1, Line, [Byte|Acc]);
+do_get_binary(_N, [], _Acc) ->
{'#CDVTruncatedBinary',[]}.
-cdvbin(Sz,Pos) ->
- "#CDVBin<"++integer_to_list(Sz)++","++integer_to_list(Pos)++">".
-
+cdvbin(Offset,Size,{'#CDVBin',Pos}) ->
+ ['#CDVBin',Offset,Size,Pos];
+cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) ->
+ ['#CDVBin',Offset,Size,Pos];
+cdvbin(_,_,'#CDVTruncatedBinary') ->
+ '#CDVTruncatedBinary'.
%%-----------------------------------------------------------------
%% Functions for accessing the cdv_dump_index_table
@@ -2563,8 +2485,6 @@ lookup_index(Tag,Id) ->
count_index(Tag) ->
ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]).
-count_index(Tag,Id) ->
- ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},Id},[],[true]}]).
%%-----------------------------------------------------------------
@@ -2605,16 +2525,14 @@ tag_to_atom(UnknownTag) ->
%%%-----------------------------------------------------------------
%%% Fetch next chunk from crashdump file
-lookup_and_parse_index(File,What,ParseFun) when is_list(File) ->
- Fd = open(File),
+lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) ->
Indices = lookup_index(What),
- R = lists:map(fun({Id,Start}) ->
- pos_bof(Fd,Start),
- ParseFun(Fd,Id)
- end,
- Indices),
- close(Fd),
- R.
+ Fun = fun(Fd,{Id,Start}) ->
+ pos_bof(Fd,Start),
+ ParseFun(Fd,Id)
+ end,
+ Report = "Processing " ++ Str,
+ progress_pmap(Report,File,Fun,Indices).
%%%-----------------------------------------------------------------
%%% Convert a record to a proplist
@@ -2627,3 +2545,112 @@ to_proplist(Fields,Record) ->
to_value_list(Record) ->
[_RecordName|Values] = tuple_to_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
+%%% on each process and passed to the Fun as first argument.
+%%% I.e. Fun = fun(Fd,Item) -> ItemResult end.
+progress_pmap(Report,File,Fun,List) ->
+ NTot = length(List),
+ NProcs = erlang:system_info(schedulers) * 2,
+ NPerProc = (NTot div NProcs) + 1,
+
+ %% Worker processes send message to collector for each ReportInterval.
+ ReportInterval = (NTot div 100) + 1,
+
+ %% Progress reporter on collector process reports 1 percent for
+ %% each message from worker process.
+ init_progress(Report,99),
+
+ Collector = self(),
+ {[],Pids} =
+ lists:foldl(
+ fun(_,{L,Ps}) ->
+ {L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L);
+ true -> {L,[]} % last chunk
+ end,
+ P = spawn(
+ fun() ->
+ progress_map(Collector,ReportInterval,File,Fun,L1)
+ end),
+ erlang:monitor(process,P),
+ {L2,[P|Ps]}
+ end,
+ {List,[]},
+ lists:seq(1,NProcs)),
+ collect(Pids,[]).
+
+progress_map(Collector,ReportInterval,File,Fun,List) ->
+ Fd = open(File),
+ init_progress(ReportInterval, fun(_) -> Collector ! progress end, ok),
+ progress_map(Fd,Fun,List,[]).
+progress_map(Fd,Fun,[H|T],Acc) ->
+ update_progress(),
+ progress_map(Fd,Fun,T,[Fun(Fd,H)|Acc]);
+progress_map(Fd,_Fun,[],Acc) ->
+ close(Fd),
+ exit({pmap_done,Acc}).
+
+collect([],Acc) ->
+ end_progress(),
+ lists:append(Acc);
+collect(Pids,Acc) ->
+ receive
+ progress ->
+ update_progress(),
+ collect(Pids,Acc);
+ {'DOWN', _Ref, process, Pid, {pmap_done,Result}} ->
+ collect(lists:delete(Pid,Pids),[Result|Acc])
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Help functions for progress reporting
+
+%% Set text in progress dialog and initialize the progress counter
+init_progress(Report,N) ->
+ observer_lib:report_progress({ok,Report}),
+ Interval = (N div 100) + 1,
+ Fun = fun(P0) -> P=P0+1,observer_lib:report_progress({ok,P}),P end,
+ init_progress(Interval,Fun,0).
+init_progress(Interval,Fun,Acc) ->
+ put(progress,{Interval,Interval,Fun,Acc}),
+ ok.
+
+%% Count progress and report on given interval
+update_progress() ->
+ update_progress(1).
+update_progress(Processed) ->
+ do_update_progress(get(progress),Processed).
+
+do_update_progress({Count,Interval,Fun,Acc},Processed) when Processed>Count ->
+ do_update_progress({Interval,Interval,Fun,Fun(Acc)},Processed-Count);
+do_update_progress({Count,Interval,Fun,Acc},Processed) ->
+ put(progress,{Count-Processed,Interval,Fun,Acc}),
+ ok.
+
+%% End progress reporting for this item
+end_progress() ->
+ end_progress({ok,100}).
+end_progress(Report) ->
+ observer_lib:report_progress(Report),
+ erase(progress),
+ ok.
diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl
index 038126288b..9cd4d6748a 100644
--- a/lib/observer/src/crashdump_viewer_html.erl
+++ b/lib/observer/src/crashdump_viewer_html.erl
@@ -34,7 +34,7 @@
plain_page/1,
info_page/2,
proc_details/4,
- expanded_memory/2,
+ expandable_term/3,
expanded_binary/1,
port/3,
internal_ets_tables/2,
@@ -492,10 +492,10 @@ format(_Heading,Data) ->
%%%-----------------------------------------------------------------
%%% Expanded memory
-expanded_memory(Heading,Expanded) ->
- header(Heading,body(expanded_memory_body(Heading,Expanded))).
+expandable_term(Heading,Expanded,Tab) ->
+ header(Heading,body(expandable_term_body(Heading,Expanded,Tab))).
-expanded_memory_body(Heading,[]) ->
+expandable_term_body(Heading,[],_Tab) ->
[case Heading of
"MsgQueue" -> "No messages were found";
"Message Queue" -> "No messages were found";
@@ -504,7 +504,7 @@ expanded_memory_body(Heading,[]) ->
"ProcState" -> "Information could not be retrieved,"
" system messages may not be handled by this process."
end];
-expanded_memory_body(Heading,Expanded) ->
+expandable_term_body(Heading,Expanded,Tab) ->
Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%",
[case Heading of
"MsgQueue" ->
@@ -513,7 +513,7 @@ expanded_memory_body(Heading,Expanded) ->
[th("WIDTH=70%","Message"),
th("WIDTH=30%","SeqTraceToken")]) |
element(1, lists:mapfoldl(fun(Msg, Even) ->
- {msgq_table(Msg, Even),
+ {msgq_table(Tab, Msg, Even),
not Even}
end,
true, Expanded))]);
@@ -523,7 +523,7 @@ expanded_memory_body(Heading,Expanded) ->
[th("WIDTH=10%","Id"),
th("WIDTH=90%","Message")]) |
element(1, lists:mapfoldl(fun(Msg, {Even,N}) ->
- {msgq_table(Msg, N, Even),
+ {msgq_table(Tab, Msg, N, Even),
{not Even, N+1}}
end,
{true,1}, Expanded))]);
@@ -533,7 +533,7 @@ expanded_memory_body(Heading,Expanded) ->
[th("WIDTH=20%","Label"),
th("WIDTH=80%","Term")]) |
element(1, lists:mapfoldl(fun(Entry, Even) ->
- {stackdump_table(Entry, Even),
+ {stackdump_table(Tab, Entry, Even),
not Even}
end, true, Expanded))]);
"ProcState" ->
@@ -542,7 +542,7 @@ expanded_memory_body(Heading,Expanded) ->
[th("WIDTH=20%","Label"),
th("WIDTH=80%","Information")]) |
element(1, lists:mapfoldl(fun(Entry, Even) ->
- {proc_state(Entry,Even),
+ {proc_state(Tab, Entry,Even),
not Even}
end, true, Expanded))]);
_ ->
@@ -551,43 +551,57 @@ expanded_memory_body(Heading,Expanded) ->
[th("WIDTH=30%","Key"),
th("WIDTH=70%","Value")]) |
element(1, lists:mapfoldl(fun(Entry, Even) ->
- {dict_table(Entry,Even),
+ {dict_table(Tab, Entry,Even),
not Even}
end, true, Expanded))])
end].
-msgq_table({Msg0,Token0}, Even) ->
+msgq_table(Tab,{Msg0,Token0}, Even) ->
Token = case Token0 of
[] -> "";
_ -> io_lib:fwrite("~w",[Token0])
end,
- Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))),
+ Msg = all_or_expand(Tab,Msg0),
tr(color(Even),[td(pre(Msg)), td(Token)]).
-msgq_table(Msg0, Id, Even) ->
- Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))),
+msgq_table(Tab,Msg0, Id, Even) ->
+ Msg = all_or_expand(Tab,Msg0),
tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]).
-stackdump_table({Label0,Term0},Even) ->
+stackdump_table(Tab,{Label0,Term0},Even) ->
Label = io_lib:format("~w",[Label0]),
- Term = href_proc_port(lists:flatten(io_lib:format("~p",[Term0]))),
+ Term = all_or_expand(Tab,Term0),
tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]).
-dict_table({Key0,Value0}, Even) ->
- Key = href_proc_port(lists:flatten(io_lib:format("~p",[Key0]))),
- Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))),
+dict_table(Tab,{Key0,Value0}, Even) ->
+ Key = all_or_expand(Tab,Key0),
+ Value = all_or_expand(Tab,Value0),
tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]).
-proc_state({Key0,Value0}, Even) ->
+proc_state(Tab,{Key0,Value0}, Even) ->
Key = lists:flatten(io_lib:format("~s",[Key0])),
- Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))),
+ 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,9]),
+ Exp = Preview=/=Check,
+ all_or_expand(Tab,Term,Preview,Exp).
+all_or_expand(_Tab,_Term,Str,false) ->
+ href_proc_port(lists:flatten(Str));
+all_or_expand(Tab,Term,Preview,true) ->
+ Key = {Key1,Key2,Key3} = now(),
+ ets:insert(Tab,{Key,Term}),
+ [href_proc_port(lists:flatten(Preview),false), $\n,
+ href("TARGET=\"expanded\"",["#Term?key1="++integer_to_list(Key1)++
+ "&key2="++integer_to_list(Key2)++
+ "&key3="++integer_to_list(Key3)],
+ "Click to expand above term")].
color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN));
color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)).
-
%%%-----------------------------------------------------------------
%%% Display an expanded binary, i.e. the whole binary, not just the
%%% size of it.
@@ -1094,82 +1108,109 @@ br() ->
%% In all the following, "<" is changed to "&lt;" and ">" is changed to "&gt;"
href_proc_port(Text) ->
- href_proc_port(Text,[]).
-href_proc_port([$#,$R,$e,$f,$<|T],Acc) ->
+ href_proc_port(Text,true).
+href_proc_port(Text,LinkToBin) ->
+ href_proc_port(Text,[],LinkToBin).
+href_proc_port("#Ref<"++T,Acc,LTB) ->
%% No links to refs
- href_proc_port(T,[$;,$t,$l,$&,$f,$e,$R,$#|Acc]);
-href_proc_port([$#,$F,$u,$n,$<|T],Acc) ->
+ href_proc_port(T,["#Ref&lt;"|Acc],LTB);
+href_proc_port("#Fun<"++T,Acc,LTB) ->
%% No links to funs
- href_proc_port(T,[$;,$t,$l,$&,$n,$u,$F,$#|Acc]);
-href_proc_port([$#,$P,$o,$r,$t,$<|T],Acc) ->
- {Port,Rest} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
- href_proc_port(Rest,[href(Port,Port)|Acc]);
-href_proc_port([$<,$<|T],Acc) ->
+ href_proc_port(T,["#Fun&lt;"|Acc],LTB);
+href_proc_port("#Port<"++T,Acc,LTB) ->
+ {Port0,Rest} = split($>,T),
+ Port = "#Port&lt;"++Port0 ++ "&gt;",
+ href_proc_port(Rest,[href(Port,Port)|Acc],LTB);
+href_proc_port("<<"++T,Acc,LTB) ->
%% No links to binaries
- href_proc_port(T,[$;,$t,$l,$&,$;,$t,$l,$&|Acc]);
-href_proc_port([$<,C|T],Acc) when $0 =< C, C =< $9 ->
+ href_proc_port(T,["&lt;&lt;"|Acc],LTB);
+href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 ->
%% Pid
- {Pid,Rest} = to_gt(T,[C,$;,$t,$l,$&]),
- href_proc_port(Rest,[href(Pid,Pid)|Acc]);
-href_proc_port([$",$#,$C,$D,$V,$B,$i,$n,$<|T],Acc) ->
+ {Pid0,Rest} = split($>,T),
+ Pid = "&lt;" ++ Pid0 ++ "&gt",
+ href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB);
+href_proc_port("['#CDVBin'"++T,Acc,LTB) ->
%% Binary written by crashdump_viewer:parse_heap_term(...)
- {SizeAndPos,[$"|Rest]} = split($>,T),
- {Size,Pos} = split($,,SizeAndPos),
- href_proc_port(Rest,[href("TARGET=\"expanded\"",
- ["#Binary<",Pos,">"],
- ["&lt;&lt; ",Size," bytes &gt;&gt;"]) | Acc]);
-href_proc_port([$",$#,$C,$D,$V,$P,$o,$r,$t,$<|T],Acc) ->
+ {OffsetSizePos,Rest} = split($],T),
+ BinStr =
+ case string:tokens(OffsetSizePos,",.|") of
+ [Offset,Size,Pos] ->
+ Id = {list_to_integer(Offset),10,list_to_integer(Pos)},
+ {ok,PreviewBin} = crashdump_viewer:expand_binary(Id),
+ PreviewBytes = binary_to_list(PreviewBin),
+ PreviewStr = ["&lt;&lt;",
+ [integer_to_list(X)++"," || X <- PreviewBytes],
+ "...(",
+ observer_lib:to_str({bytes,Size}),
+ ")&gt;&gt;"],
+ if LTB ->
+ href("TARGET=\"expanded\"",
+ ["#Binary?offset="++Offset++
+ "&size="++Size++
+ "&pos="++Pos],
+ PreviewStr);
+ true ->
+ PreviewStr
+ end;
+ _ ->
+ "&lt;&lt; ... &gt;&gt;"
+ end,
+ href_proc_port(Rest,[BinStr|Acc],LTB);
+href_proc_port("['#CDVPort'"++T,Acc,LTB) ->
%% Port written by crashdump_viewer:parse_term(...)
- {Port,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
- href_proc_port(Rest,[href(Port,Port)|Acc]);
-href_proc_port([$",$#,$C,$D,$V,$P,$i,$d,$<|T],Acc) ->
+ {Port0,Rest} = split($],T),
+ PortStr=
+ case string:tokens(Port0,",.|") of
+ [X,Y] ->
+ Port = "#Port&lt;"++X++"."++Y++"&gt;",
+ href(Port,Port);
+ Ns ->
+ "#Port&lt;" ++ string:join(Ns,".") ++"...&gt;"
+ end,
+ href_proc_port(Rest,[PortStr|Acc],LTB);
+href_proc_port("['#CDVPid'"++T,Acc,LTB) ->
%% Pid written by crashdump_viewer:parse_term(...)
- {Pid,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&]),
- href_proc_port(Rest,[href(Pid,Pid)|Acc]);
-href_proc_port([$',$#,$C,$D,$V,$I,$n,$c,$o,$m,$p,$l,$e,$t,$e,$H,$e,$a,$p,$'|T],
- Acc)->
+ {Pid0,Rest} = split($],T),
+ PidStr =
+ case string:tokens(Pid0,",.|") of
+ [X,Y,Z] ->
+ Pid = "&lt;"++X++"."++Y++"."++Z++"&gt;",
+ href(Pid,Pid);
+ Ns ->
+ "&lt;" ++ string:join(Ns,".") ++ "...&gt;"
+ end,
+ href_proc_port(Rest,[PidStr|Acc],LTB);
+href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)->
%% The heap is incomplete! Written by crashdump_viewer:deref_pts(...)
IH = lists:reverse(
lists:flatten(
"<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")),
- href_proc_port(T,IH++Acc);
-href_proc_port([$',$#,$C,$D,$V,$T,$r,$u,$n,$c,$a,$t,$e,$d,$B,$i,$n,$a,$r,$y,$'
- |T], Acc)->
+ href_proc_port(T,IH++Acc,LTB);
+href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)->
%% A binary which is truncated! Written by
%% crashdump_viewer:parse_heap_term(...)
IH = lists:reverse(
lists:flatten(
"<FONT COLOR=\"#FF0000\">&lt;&lt;...(Truncated Binary)&gt;&gt;"
"</FONT>")),
- href_proc_port(T,IH++Acc);
-href_proc_port([$',$#,$C,$D,$V,$N,$o,$n,$e,$x,$i,$s,$t,$i,$n,$g,$B,$i,$n,$a,$r,
- $y,$'|T], Acc)->
+ href_proc_port(T,IH++Acc,LTB);
+href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)->
%% A binary which could not be found in the dump! Written by
%% crashdump_viewer:parse_heap_term(...)
IH = lists:reverse(
lists:flatten(
"<FONT COLOR=\"#FF0000\">&lt;&lt;...(Nonexisting Binary)&gt;&gt;"
"</FONT>")),
- href_proc_port(T,IH++Acc);
-href_proc_port([$<|T],Acc) ->
- href_proc_port(T,[$;,$t,$l,$&|Acc]);
-href_proc_port([$>|T],Acc) ->
- href_proc_port(T,[$;,$t,$g,$&|Acc]);
-href_proc_port([H|T],Acc) ->
- href_proc_port(T,[H|Acc]);
-href_proc_port([],Acc) ->
+ href_proc_port(T,IH++Acc,LTB);
+href_proc_port("<"++T,Acc,LTB) ->
+ href_proc_port(T,["&lt;"|Acc],LTB);
+href_proc_port(">"++T,Acc,LTB) ->
+ href_proc_port(T,["&gt;"|Acc],LTB);
+href_proc_port([H|T],Acc,LTB) ->
+ href_proc_port(T,[H|Acc],LTB);
+href_proc_port([],Acc,_) ->
lists:reverse(Acc).
-to_gt(Str,Acc) ->
- {Match,Rest} = to_gt_noreverse(Str,Acc),
- {lists:reverse(Match),Rest}.
-to_gt_noreverse([$>|T],Acc) ->
- {[$;,$t,$g,$&|Acc],T};
-to_gt_noreverse([H|T],Acc) ->
- to_gt_noreverse(T,[H|Acc]);
-to_gt_noreverse([],Acc) ->
- {Acc,[]}.
-
split(Char,Str) ->
split(Char,Str,[]).
split(Char,[Char|Str],Acc) -> % match Char
diff --git a/lib/observer/src/crashdump_viewer_wx.erl b/lib/observer/src/crashdump_viewer_wx.erl
index aeb89b54f4..17e43838d6 100644
--- a/lib/observer/src/crashdump_viewer_wx.erl
+++ b/lib/observer/src/crashdump_viewer_wx.erl
@@ -16,7 +16,7 @@
%%
%% %CopyrightEnd%
-module(crashdump_viewer_wx).
-
+-compile(export_all).
-behaviour(wx_object).
-export([start/1]).
@@ -89,31 +89,20 @@ set_status(What) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-init(File) ->
- {ok,CdvServer} = crashdump_viewer:start_link(),
-
+init(File0) ->
register(?SERVER, self()),
wx:new(),
+
+ {ok,CdvServer} = crashdump_viewer:start_link(),
+
catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
- Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump viewer",
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer",
[{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
wxFrame:setIcon(Frame, Icon),
wxIcon:destroy(Icon),
- State = #state{server=CdvServer, file = File, frame = Frame},
- UpdState = setup(State),
- process_flag(trap_exit, true),
- {Frame, UpdState}.
-
-setup(#state{file = File0, frame = Frame} = State) ->
- %% Setup Menubar & Menus
- MenuBar = wxMenuBar:new(),
- DefMenus = default_menus(),
- observer_lib:create_menus(DefMenus, MenuBar, default),
- wxFrame:setMenuBar(Frame, MenuBar),
-
%% Setup panels
Panel = wxPanel:new(Frame, []),
Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
@@ -121,22 +110,6 @@ setup(#state{file = File0, frame = Frame} = State) ->
%% Setup "statusbar" to show warnings
StatusBar = observer_lib:create_status_bar(Panel),
- %% Load a crashdump
- File = load_dump(Panel,File0),
-
- %% Set window title
- T1 = "Crashdump Viewer: ",
- Title =
- if length(File) > 70 ->
- T1 ++ filename:basename(File);
- true ->
- T1 ++ File
- end,
- wxFrame:setTitle(Frame, Title),
-
- %% General information Panel
- GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx),
-
%% Setup sizer create early to get it when window shows
MainSizer = wxBoxSizer:new(?wxVERTICAL),
@@ -151,8 +124,41 @@ setup(#state{file = File0, frame = Frame} = State) ->
wxMenu:connect(Frame, command_menu_selected),
wxFrame:show(Frame),
- %% I postpone the creation of the other tabs so they can query/use
- %% the window size
+ case load_dump(Frame,File0) of
+ {ok,File} ->
+ %% Set window title
+ T1 = "Crashdump Viewer: ",
+ Title =
+ if length(File) > 70 ->
+ T1 ++ filename:basename(File);
+ true ->
+ T1 ++ File
+ end,
+ wxFrame:setTitle(Frame, Title),
+
+ setup(#state{server=CdvServer,
+ file=File,
+ frame=Frame,
+ status_bar=StatusBar,
+ notebook=Notebook,
+ main_panel=Panel});
+ error ->
+ wxFrame:destroy(Frame),
+ wx:destroy(),
+ crashdump_viewer:stop(),
+ ignore
+ end.
+
+setup(#state{frame=Frame, notebook=Notebook, main_panel=Panel}=State) ->
+
+ %% Setup Menubar & Menus
+ MenuBar = wxMenuBar:new(),
+ DefMenus = default_menus(),
+ observer_lib:create_menus(DefMenus, MenuBar, default),
+ wxFrame:setMenuBar(Frame, MenuBar),
+
+ %% General information Panel
+ GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx),
%% Process Panel
ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list, cdv_proc_wx),
@@ -189,38 +195,22 @@ setup(#state{file = File0, frame = Frame} = State) ->
GenPid = wx_object:get_pid(GenPanel),
GenPid ! active,
- UpdState = State#state{file = File,
- main_panel = Panel,
- notebook = Notebook,
- menubar = MenuBar,
- status_bar = StatusBar,
- gen_panel = GenPanel,
- pro_panel = ProPanel,
- port_panel = PortPanel,
- ets_panel = EtsPanel,
- timer_panel = TimerPanel,
- fun_panel = FunPanel,
- atom_panel = AtomPanel,
- dist_panel = DistPanel,
- mod_panel = ModPanel,
- mem_panel = MemPanel,
- int_panel = IntPanel,
- active_tab = GenPid
- },
- %% Create resources which we don't want to duplicate
- SysFont = wxSystemSettings:getFont(?wxSYS_SYSTEM_FIXED_FONT),
- Fixed = case wxFont:isFixedWidth(SysFont) of
- true -> SysFont;
- false -> %% Sigh
- SysFontSize = wxFont:getPointSize(SysFont),
- wxFont:new(SysFontSize,
- ?wxFONTFAMILY_MODERN,
- ?wxFONTSTYLE_NORMAL,
- ?wxFONTWEIGHT_NORMAL)
- end,
- put({font, fixed}, Fixed),
- UpdState.
-
+ observer_lib:destroy_progress_dialog(),
+ process_flag(trap_exit, true),
+ {Frame, State#state{menubar = MenuBar,
+ gen_panel = GenPanel,
+ pro_panel = ProPanel,
+ port_panel = PortPanel,
+ ets_panel = EtsPanel,
+ timer_panel = TimerPanel,
+ fun_panel = FunPanel,
+ atom_panel = AtomPanel,
+ dist_panel = DistPanel,
+ mod_panel = ModPanel,
+ mem_panel = MemPanel,
+ int_panel = IntPanel,
+ active_tab = GenPid
+ }}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -230,7 +220,6 @@ handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}},
case get_active_pid(State) of
Previous -> {noreply, State};
Pid ->
- Previous ! not_active,
Pid ! active,
{noreply, State#state{active_tab=Pid}}
end;
@@ -241,21 +230,28 @@ handle_event(#wx{event = #wxClose{}}, State) ->
handle_event(#wx{id = ?wxID_OPEN,
event = #wxCommand{type = command_menu_selected}},
State) ->
- File = load_dump(State#state.main_panel,undefined),
- Panels = [State#state.gen_panel,
- State#state.pro_panel,
- State#state.port_panel,
- State#state.ets_panel,
- State#state.timer_panel,
- State#state.fun_panel,
- State#state.atom_panel,
- State#state.dist_panel,
- State#state.mod_panel,
- State#state.mem_panel,
- State#state.int_panel],
- _ = [wx_object:get_pid(Panel) ! new_dump || Panel<-Panels],
- State#state.active_tab ! active,
- {noreply, State#state{file=File}};
+ NewState =
+ case load_dump(State#state.frame,undefined) of
+ {ok,File} ->
+ Panels = [State#state.gen_panel,
+ State#state.pro_panel,
+ State#state.port_panel,
+ State#state.ets_panel,
+ State#state.timer_panel,
+ State#state.fun_panel,
+ State#state.atom_panel,
+ State#state.dist_panel,
+ State#state.mod_panel,
+ State#state.mem_panel,
+ State#state.int_panel],
+ _ = [wx_object:call(Panel,new_dump) || Panel<-Panels],
+ wxNotebook:setSelection(State#state.notebook,0),
+ observer_lib:destroy_progress_dialog(),
+ State#state{file=File};
+ error ->
+ State
+ end,
+ {noreply,NewState};
handle_event(#wx{id = ?wxID_EXIT,
event = #wxCommand{type = command_menu_selected}},
@@ -313,6 +309,7 @@ handle_info(_Info, State) ->
terminate(_Reason, #state{frame = Frame}) ->
wxFrame:destroy(Frame),
+ wx:destroy(),
crashdump_viewer:stop(),
ok.
@@ -390,57 +387,42 @@ default_menus() ->
true ->
%% On Mac quit and about will be moved to the "default' place
%% automagicly, so just add them to a menu that always exist.
- %% But not to the help menu for some reason
-%! {Tag, Menus} = NodeMenu,
-%! [{Tag, Menus ++ [Quit,About]}, {"&Help", [Help]}]
- [{"File", [Quit,About]}, {"&Help", [Help,UG,Howto]}] %?siri: does this work?
+ [{"File", [Open, About,Quit]}, {"&Help", [Help,UG,Howto]}]
end.
-load_dump(Panel,undefined) ->
- FD = wxFileDialog:new(Panel,
+load_dump(Frame,undefined) ->
+ FD = wxFileDialog:new(wx:null(),
[{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]),
case wxFileDialog:showModal(FD) of
?wxID_OK ->
Path = wxFileDialog:getPath(FD),
wxDialog:destroy(FD),
- load_dump(Panel, Path);
+ load_dump(Frame,Path);
_ ->
- wxDialog:destroy(FD)
+ wxDialog:destroy(FD),
+ error
end;
-load_dump(Panel, FileName) ->
+load_dump(Frame,FileName) ->
+ ok = observer_lib:display_progress_dialog("Crashdump Viewer",
+ "Loading crashdump"),
crashdump_viewer:read_file(FileName),
- update_progress(Panel,false),
- FileName.
-
-update_progress(Panel,PD) ->
- case crashdump_viewer:get_progress() of
- {ok, done} ->
- wxProgressDialog:destroy(PD);
- {ok, Percent} when is_integer(Percent) ->
- wxProgressDialog:update(PD,Percent),
- update_progress(Panel,PD);
- {ok,Msg} ->
- case PD of
- false -> ok;
- _ -> wxProgressDialog:destroy(PD)
- end,
- update_progress(Panel,new_progress(Msg));
- {error, Reason} ->
- wxProgressDialog:destroy(PD),
- FailMsg = file:format_error(Reason),
- MD = wxMessageDialog:new(Panel, FailMsg),
- wxDialog:showModal(MD),
- wxDialog:destroy(MD)
+ case observer_lib:wait_for_progress() of
+ ok ->
+ %% Set window title
+ T1 = "Crashdump Viewer: ",
+ Title =
+ if length(FileName) > 70 ->
+ T1 ++ filename:basename(FileName);
+ true ->
+ T1 ++ FileName
+ end,
+ wxFrame:setTitle(Frame, Title),
+ {ok,FileName};
+ error ->
+ error
end.
-new_progress(Msg) ->
- wxProgressDialog:new("Crashdump viewer",Msg,
- [{maximum,100},
- {style,
- ?wxPD_APP_MODAL bor
- ?wxPD_SMOOTH bor
- ?wxPD_AUTO_HIDE}]).
%%%-----------------------------------------------------------------
%%% Find help document (HTML files)
get_help_doc(HelpId) ->
diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl
index 159afae487..463c42308d 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -20,6 +20,8 @@
-export([get_wx_parent/1,
display_info_dialog/1, display_yes_no_dialog/1,
+ display_progress_dialog/2, destroy_progress_dialog/0,
+ wait_for_progress/0, report_progress/1,
user_term/3, user_term_multiline/3,
interval_dialog/4, start_timer/1, stop_timer/1,
display_info/2, fill_info/2, update_info/2, to_str/1,
@@ -103,7 +105,9 @@ setup_timer(Bool, {Timer, Old}) ->
setup_timer(Bool, {false, Old}).
display_info_dialog(Str) ->
- Dlg = wxMessageDialog:new(wx:null(), Str),
+ display_info_dialog("",Str).
+display_info_dialog(Title,Str) ->
+ Dlg = wxMessageDialog:new(wx:null(), Str, [{caption,Title}]),
wxMessageDialog:showModal(Dlg),
wxMessageDialog:destroy(Dlg),
ok.
@@ -267,27 +271,23 @@ create_menus(Menus, MenuBar, Type) ->
create_menu(Tag, Ms, Index, MenuBar, Type)
end,
[{First, _}|_] = Menus,
- OnMac = os:type() =:= {unix, darwin},
Index = if Type =:= default -> 0;
First =:= "File" -> 0;
- OnMac -> 0;
true -> 1
end,
wx:foldl(Add, Index, Menus),
ok.
create_menu("File", MenuItems, Index, MenuBar, Type) ->
- OnMac = os:type() =:= {unix, darwin},
- if OnMac, Type =:= default ->
- Index;
- not OnMac, Type =:= plugin ->
+ if
+ Type =:= plugin ->
MenuId = wxMenuBar:findMenu(MenuBar, "File"),
Menu = wxMenuBar:getMenu(MenuBar, MenuId),
lists:foldl(fun(Record, N) ->
create_menu_item(Record, Menu, N)
end, 0, MenuItems),
Index + 1;
- true ->
+ true ->
Menu = wxMenu:new(),
lists:foldl(fun(Record, N) ->
create_menu_item(Record, Menu, N)
@@ -429,15 +429,18 @@ create_box(Panel, Data) ->
case Value0 of
{click,"unknown"} ->
wxTextCtrl:new(Panel, ?wxID_ANY,
- [{style,?MULTI_LINE_STYLE},
+ [{style,?SINGLE_LINE_STYLE},
{value,"unknown"}]);
{click,Value} ->
link_entry(Panel,Value);
_ ->
Value = to_str(Value0),
- wxTextCtrl:new(Panel, ?wxID_ANY,
- [{style,?MULTI_LINE_STYLE},
- {value,Value}])
+ TCtrl = wxTextCtrl:new(Panel, ?wxID_ANY,
+ [{style,?SINGLE_LINE_STYLE},
+ {value,Value}]),
+ length(Value) > 50 andalso
+ wxWindow:setToolTip(TCtrl,wxToolTip:new(Value)),
+ TCtrl
end,
wxSizer:add(Line, 10, 0), % space of size 10 horisontally
wxSizer:add(Line, Field, RightProportion),
@@ -471,18 +474,15 @@ link_entry2(Panel,{Target,Str},Cursor) ->
wxWindow:setToolTip(TC, ToolTip),
TC.
-to_link(Tuple = {_Target, _Str}) -> Tuple;
-to_link(Target) -> {Target, to_str(Target)}.
+to_link(Tuple = {_Target, _Str}) ->
+ Tuple;
+to_link(Target0) ->
+ Target=to_str(Target0),
+ {Target, Target}.
html_window(Panel) ->
Win = wxHtmlWindow:new(Panel, [{style, ?wxHW_SCROLLBAR_AUTO}]),
- FixedName = case whereis(observer) of
- undefined -> "courier";
- _Pid ->
- Fixed = observer_wx:get_attrib({font,fixed}),
- wxFont:getFaceName(Fixed)
- end,
- wxHtmlWindow:setFonts(Win, "", FixedName),
+ %% wxHtmlWindow:setFonts(Win, "", FixedName),
wxHtmlWindow:connect(Win,command_html_link_clicked),
Win.
@@ -647,3 +647,95 @@ create_status_bar(Panel) ->
wxTextCtrl:setDefaultStyle(StatusBar,Red),
wxTextAttr:destroy(Red),
StatusBar.
+
+%%%-----------------------------------------------------------------
+%%% Progress dialog
+-define(progress_handler,cdv_progress_handler).
+display_progress_dialog(Title,Str) ->
+ Caller = self(),
+ Env = wx:get_env(),
+ spawn_link(fun() ->
+ progress_handler(Caller,Env,Title,Str)
+ end),
+ ok.
+
+wait_for_progress() ->
+ receive
+ continue ->
+ ok;
+ Error ->
+ Error
+ end.
+
+destroy_progress_dialog() ->
+ report_progress(finish).
+
+report_progress(Progress) ->
+ case whereis(?progress_handler) of
+ Pid when is_pid(Pid) ->
+ Pid ! {progress,Progress},
+ ok;
+ _ ->
+ ok
+ end.
+
+progress_handler(Caller,Env,Title,Str) ->
+ register(?progress_handler,self()),
+ wx:set_env(Env),
+ PD = progress_dialog(Env,Title,Str),
+ progress_loop(Title,PD,Caller).
+progress_loop(Title,PD,Caller) ->
+ receive
+ {progress,{ok,done}} -> % to make wait_for_progress/0 return
+ Caller ! continue,
+ progress_loop(Title,PD,Caller);
+ {progress,{ok,Percent}} when is_integer(Percent) ->
+ update_progress(PD,Percent),
+ progress_loop(Title,PD,Caller);
+ {progress,{ok,Msg}} ->
+ update_progress_text(PD,Msg),
+ progress_loop(Title,PD,Caller);
+ {progress,{error, Reason}} ->
+ finish_progress(PD),
+ FailMsg =
+ if is_list(Reason) -> Reason;
+ true -> file:format_error(Reason)
+ end,
+ display_info_dialog("Crashdump Viewer Error",FailMsg),
+ Caller ! error,
+ unregister(?progress_handler),
+ unlink(Caller);
+ {progress,finish} ->
+ finish_progress(PD),
+ unregister(?progress_handler),
+ unlink(Caller)
+ end.
+
+progress_dialog(Env,Title,Str) ->
+ %% Spawning separat process to hold this since we use showModal.
+ spawn_link(
+ fun() ->
+ wx:set_env(Env),
+ PD = wxProgressDialog:new(Title,Str,
+ [{maximum,101},
+ {style,
+ ?wxPD_APP_MODAL bor
+ ?wxPD_SMOOTH bor
+ ?wxPD_AUTO_HIDE}]),
+ wxProgressDialog:setMinSize(PD,{200,-1}),
+ ?progress_handler ! {progress_dialog,PD},
+ wxProgressDialog:showModal(PD),
+ wxDialog:destroy(PD)
+ end),
+ receive
+ {progress_dialog,PD} ->
+ timer:sleep(300), % To allow the window to show before reporting
+ PD
+ end.
+
+update_progress(PD,Value) ->
+ wxProgressDialog:update(PD,Value).
+update_progress_text(PD,Text) ->
+ wxProgressDialog:update(PD,0,[{newmsg,Text}]).
+finish_progress(PD) ->
+ wxProgressDialog:endModal(PD, ?wxID_OK).
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index db5eefaa6c..cfc22e7093 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -35,7 +35,9 @@
-record(state, {parent,
frame,
pid,
- pages=[]
+ pages=[],
+ expand_table,
+ expand_wins=[]
}).
-record(worker, {panel, callback}).
@@ -47,6 +49,7 @@ start(Process, ParentFrame, Parent) ->
init([Pid, ParentFrame, Parent]) ->
try
+ Table = ets:new(observer_expand,[set,protected]),
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]);
@@ -60,11 +63,11 @@ init([Pid, ParentFrame, Parent]) ->
Notebook = wxNotebook:new(Frame, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
- ProcessPage = init_panel(Notebook, "Process Information", Pid, fun init_process_page/2),
- MessagePage = init_panel(Notebook, "Messages", Pid, fun init_message_page/2),
- DictPage = init_panel(Notebook, "Dictionary", Pid, fun init_dict_page/2),
- StackPage = init_panel(Notebook, "Stack Trace", Pid, fun init_stack_page/2),
- StatePage = init_panel(Notebook, "State", Pid, fun init_state_page/2),
+ ProcessPage = init_panel(Notebook, "Process Information", [Pid], fun init_process_page/2),
+ MessagePage = init_panel(Notebook, "Messages", [Pid,Table], fun init_message_page/3),
+ DictPage = init_panel(Notebook, "Dictionary", [Pid,Table], fun init_dict_page/3),
+ StackPage = init_panel(Notebook, "Stack Trace", [Pid], fun init_stack_page/2),
+ StatePage = init_panel(Notebook, "State", [Pid,Table], fun init_state_page/3),
wxFrame:connect(Frame, close_window),
wxMenu:connect(Frame, command_menu_selected),
@@ -73,7 +76,8 @@ init([Pid, ParentFrame, Parent]) ->
{Frame, #state{parent=Parent,
pid=Pid,
frame=Frame,
- pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage]
+ pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage],
+ expand_table=Table
}}
catch error:{badrpc, _} ->
observer_wx:return_to_localnode(ParentFrame, node(Pid)),
@@ -83,10 +87,10 @@ init([Pid, ParentFrame, Parent]) ->
{stop, normal}
end.
-init_panel(Notebook, Str, Pid, Fun) ->
+init_panel(Notebook, Str, FunArgs, Fun) ->
Panel = wxPanel:new(Notebook),
Sizer = wxBoxSizer:new(?wxHORIZONTAL),
- {Window,Callback} = Fun(Panel, Pid),
+ {Window,Callback} = apply(Fun,[Panel|FunArgs]),
wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]),
wxPanel:setSizer(Panel, Sizer),
true = wxNotebook:addPage(Notebook, Panel, Str),
@@ -99,7 +103,8 @@ handle_event(#wx{event=#wxClose{type=close_window}}, State) ->
handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) ->
{stop, normal, State};
-handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages}=State) ->
+handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages, expand_table=T}=State) ->
+ ets:delete_all_objects(T),
try [(W#worker.callback)() || W <- Pages]
catch process_undefined ->
wxFrame:setTitle(Frame, io_lib:format("*DEAD* ~p",[Pid]))
@@ -118,6 +123,21 @@ handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) ->
wxTextCtrl:setForegroundColour(Obj,?wxBLUE),
{noreply, State};
+handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href="#Term?"++Keys}}},
+ #state{frame=Frame,expand_table=T,expand_wins=Opened0}=State) ->
+ [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(Keys),
+ Id = {T,{list_to_integer(Key1),list_to_integer(Key2),list_to_integer(Key3)}},
+ Opened =
+ case lists:keyfind(Id,1,Opened0) of
+ false ->
+ Win = observer_term_wx:start(Id,Frame),
+ [{Id,Win}|Opened0];
+ {_,Win} ->
+ wxFrame:raise(Win),
+ Opened0
+ end,
+ {noreply,State#state{expand_wins=Opened}};
+
handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) ->
observer ! {open_link, Info},
{noreply, State};
@@ -125,6 +145,10 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State)
handle_event(Event, _State) ->
error({unhandled_event, Event}).
+handle_info({expand_win_closed,Id}, #state{expand_wins=Opened0}=State) ->
+ Opened = lists:keydelete(Id,1,Opened0),
+ {noreply,State#state{expand_wins=Opened}};
+
handle_info(_Info, State) ->
%% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
@@ -135,7 +159,8 @@ handle_call(Call, From, _State) ->
handle_cast(Cast, _State) ->
error({unhandled_cast, Cast}).
-terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) ->
+terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame,expand_table=T}) ->
+ T=/=undefined andalso ets:delete(T),
Parent ! {procinfo_menu_closed, Pid},
case Frame of
undefined -> ok;
@@ -156,14 +181,14 @@ init_process_page(Panel, Pid) ->
end}.
-init_message_page(Parent, Pid) ->
+init_message_page(Parent, Pid, Table) ->
Win = observer_lib:html_window(Parent),
Update = fun() ->
case observer_wx:try_rpc(node(Pid), erlang, process_info,
[Pid, messages])
of
{messages, Messages} ->
- Html = crashdump_viewer_html:expanded_memory("Message Queue", Messages),
+ Html = crashdump_viewer_html:expandable_term("Message Queue", Messages, Table),
wxHtmlWindow:setPage(Win, Html);
_ ->
throw(process_undefined)
@@ -172,13 +197,13 @@ init_message_page(Parent, Pid) ->
Update(),
{Win, Update}.
-init_dict_page(Parent, Pid) ->
+init_dict_page(Parent, Pid, Table) ->
Win = observer_lib:html_window(Parent),
Update = fun() ->
case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary])
of
{dictionary,Dict} ->
- Html = crashdump_viewer_html:expanded_memory("Dictionary", Dict),
+ Html = crashdump_viewer_html:expandable_term("Dictionary", Dict, Table),
wxHtmlWindow:setPage(Win, Html);
_ ->
throw(process_undefined)
@@ -231,11 +256,11 @@ init_stack_page(Parent, Pid) ->
Update(),
{LCtrl, Update}.
-init_state_page(Parent, Pid) ->
+init_state_page(Parent, Pid, Table) ->
Win = observer_lib:html_window(Parent),
Update = fun() ->
StateInfo = fetch_state_info(Pid),
- Html = crashdump_viewer_html:expanded_memory("ProcState", StateInfo),
+ Html = crashdump_viewer_html:expandable_term("ProcState", StateInfo, Table),
wxHtmlWindow:setPage(Win, Html)
end,
Update(),
diff --git a/lib/observer/src/observer_term_wx.erl b/lib/observer/src/observer_term_wx.erl
new file mode 100644
index 0000000000..58cb650bef
--- /dev/null
+++ b/lib/observer/src/observer_term_wx.erl
@@ -0,0 +1,113 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(observer_term_wx).
+
+-behaviour(wx_object).
+
+-export([start/2]).
+
+-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
+ handle_call/3, handle_info/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+-define(REFRESH, 601).
+
+-record(state, {parent,
+ frame,
+ id
+ }).
+
+start(Id, ParentFrame) ->
+ wx_object:start_link(?MODULE, [Id, ParentFrame, self()], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([{T,Key}=Id, ParentFrame, Parent]) ->
+ case ets:lookup(T,Key) of
+ [{Key,Term}] ->
+ init(Id, ParentFrame, Parent, Term);
+ [] ->
+ observer_lib:display_info_dialog(
+ "The term does no longer exist.\n"
+ "Please refresh the process window!"),
+ {stop, normal}
+ end.
+
+
+
+init(Id, ParentFrame, Parent, Term) ->
+ Frame=wxFrame:new(ParentFrame, ?wxID_ANY, ["Expanded Term"],
+ [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
+ MenuBar = wxMenuBar:new(),
+ Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}],
+ observer_lib:create_menus(Menus, MenuBar, new_window),
+ wxFrame:setMenuBar(Frame, MenuBar),
+ Panel = wxPanel:new(Frame),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+
+ Window = observer_lib:html_window(Panel),
+ Html = crashdump_viewer_html:plain_page(io_lib:format("~p~n",[Term])),
+ wxHtmlWindow:setPage(Window, Html),
+
+ wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 1},
+ {border, 5}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxFrame:show(Frame),
+ {Frame, #state{parent=Parent,
+ frame=Frame,
+ id=Id
+ }}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+handle_event(#wx{event=#wxClose{type=close_window}}, State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) ->
+ {stop, normal, State};
+
+handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) ->
+ observer ! {open_link, Info},
+ {noreply, State};
+
+handle_event(Event, _State) ->
+ error({unhandled_event, Event}).
+
+handle_info(_Info, State) ->
+ %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+handle_call(Call, From, _State) ->
+ error({unhandled_call, Call, From}).
+
+handle_cast(Cast, _State) ->
+ error({unhandled_cast, Cast}).
+
+terminate(_Reason, #state{parent=Parent,id=Id,frame=Frame}) ->
+ Parent ! {expand_win_closed, Id},
+ case Frame of
+ undefined -> ok;
+ _ -> wxFrame:destroy(Frame)
+ end,
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index 9839f8bf7b..4c385b76aa 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -131,6 +131,10 @@ setup(#state{frame = Frame} = State) ->
wxFrame:connect(Frame, close_window, [{skip, true}]),
wxMenu:connect(Frame, command_menu_selected),
wxFrame:show(Frame),
+
+ %% Freeze and thaw is buggy currently
+ DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9],
+ DoFreeze andalso wxWindow:freeze(Panel),
%% I postpone the creation of the other tabs so they can query/use
%% the window size
@@ -154,9 +158,10 @@ setup(#state{frame = Frame} = State) ->
TracePanel = observer_trace_wx:start_link(Notebook, self()),
wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []),
-
- %% Force redraw (window needs it)
+ %% Force redraw (windows needs it)
wxWindow:refresh(Panel),
+ DoFreeze andalso wxWindow:thaw(Panel),
+
wxFrame:raise(Frame),
wxFrame:setFocus(Frame),
@@ -574,13 +579,6 @@ remove_menu_items([{MenuStr = "File", Menus}|Rest], MenuBar) ->
Menu = wxMenuBar:getMenu(MenuBar, MenuId),
Items = [wxMenu:findItem(Menu, Tag) || #create_menu{text=Tag} <- Menus],
[wxMenu:delete(Menu, MItem) || MItem <- Items],
- case os:type() =:= {unix, darwin} of
- true ->
- wxMenuBar:remove(MenuBar, MenuId),
- wxMenu:destroy(Menu);
- false ->
- ignore
- end,
remove_menu_items(Rest, MenuBar)
end;
remove_menu_items([{"Nodes", _}|_], _MB) ->