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