From e2d565532d25024c1c0552d8eaaddf90eed88629 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 11 Jul 2013 11:27:29 +0200 Subject: observer: add wx version of crashdump_viewer The old web base crashdump_viewer is now removed. --- lib/observer/src/Makefile | 18 + lib/observer/src/cdv_atom_wx.erl | 48 + lib/observer/src/cdv_bin_wx.erl | 36 + lib/observer/src/cdv_detail_win.erl | 193 ++++ lib/observer/src/cdv_dist_wx.erl | 86 ++ lib/observer/src/cdv_ets_wx.erl | 66 ++ lib/observer/src/cdv_fun_wx.erl | 58 + lib/observer/src/cdv_gen_wx.erl | 45 + lib/observer/src/cdv_info_page.erl | 119 ++ lib/observer/src/cdv_int_tab_wx.erl | 79 ++ lib/observer/src/cdv_mem_wx.erl | 78 ++ lib/observer/src/cdv_mod_wx.erl | 103 ++ lib/observer/src/cdv_multi_panel.erl | 190 ++++ lib/observer/src/cdv_port_wx.erl | 102 ++ lib/observer/src/cdv_proc_wx.erl | 152 +++ lib/observer/src/cdv_table_page.erl | 109 ++ lib/observer/src/cdv_timer_wx.erl | 51 + lib/observer/src/cdv_virtual_list.erl | 425 +++++++ lib/observer/src/crashdump_viewer.erl | 1677 +++++++++++----------------- lib/observer/src/crashdump_viewer.hrl | 168 +-- lib/observer/src/crashdump_viewer_html.erl | 61 +- lib/observer/src/crashdump_viewer_wx.erl | 481 ++++++++ lib/observer/src/observer_lib.erl | 225 +++- lib/observer/src/observer_wx.erl | 18 +- 24 files changed, 3403 insertions(+), 1185 deletions(-) create mode 100644 lib/observer/src/cdv_atom_wx.erl create mode 100644 lib/observer/src/cdv_bin_wx.erl create mode 100644 lib/observer/src/cdv_detail_win.erl create mode 100644 lib/observer/src/cdv_dist_wx.erl create mode 100644 lib/observer/src/cdv_ets_wx.erl create mode 100644 lib/observer/src/cdv_fun_wx.erl create mode 100644 lib/observer/src/cdv_gen_wx.erl create mode 100644 lib/observer/src/cdv_info_page.erl create mode 100644 lib/observer/src/cdv_int_tab_wx.erl create mode 100644 lib/observer/src/cdv_mem_wx.erl create mode 100644 lib/observer/src/cdv_mod_wx.erl create mode 100644 lib/observer/src/cdv_multi_panel.erl create mode 100644 lib/observer/src/cdv_port_wx.erl create mode 100644 lib/observer/src/cdv_proc_wx.erl create mode 100644 lib/observer/src/cdv_table_page.erl create mode 100644 lib/observer/src/cdv_timer_wx.erl create mode 100644 lib/observer/src/cdv_virtual_list.erl create mode 100644 lib/observer/src/crashdump_viewer_wx.erl (limited to 'lib/observer/src') 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,<>,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,<>,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,<<"",_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,<>,N,Gat,Di,tag) -> tag(Fd,<>,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 -> @@ -2053,6 +1635,28 @@ get_timerinfo_1(Fd,Timer) -> 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) -> @@ -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,<>,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, " "). +-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,">"], ["<< ",Size," bytes >>"]) | 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) -> -- cgit v1.2.3