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 From 549205db3dee21e83a64a01f03b1e8ed2225b276 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 11 Oct 2013 16:56:24 +0200 Subject: observer: Consolidate the view of process information Add clickable links to processes in process info Use html pages (contains clickable processes) to view messages, dictionary and other information. --- lib/observer/src/cdv_proc_wx.erl | 4 +- lib/observer/src/crashdump_viewer_html.erl | 100 ++++++++++++----- lib/observer/src/observer_app_wx.erl | 10 +- lib/observer/src/observer_lib.erl | 120 ++++++++++++++------- lib/observer/src/observer_pro_wx.erl | 34 +++--- lib/observer/src/observer_procinfo.erl | 167 +++++++++++++++-------------- lib/observer/src/observer_wx.erl | 16 +++ 7 files changed, 276 insertions(+), 175 deletions(-) (limited to 'lib/observer/src') diff --git a/lib/observer/src/cdv_proc_wx.erl b/lib/observer/src/cdv_proc_wx.erl index a0f42b5e6b..1320afce28 100644 --- a/lib/observer/src/cdv_proc_wx.erl +++ b/lib/observer/src/cdv_proc_wx.erl @@ -99,6 +99,7 @@ init_stack_page(Parent, Pid, _Info) -> init_memory_page(Parent, Pid, "StackDump"). init_memory_page(Parent, Pid, What) -> + Win = observer_lib:html_window(Parent), Html = case crashdump_viewer:expand_memory(Pid,What) of {ok,Memory} -> @@ -106,7 +107,8 @@ init_memory_page(Parent, Pid, What) -> {error,Reason} -> crashdump_viewer_html:warning(Reason) end, - observer_lib:html_window(Parent,Html). + wxHtmlWindow:setPage(Win,Html), + Win. init_ets_page(Parent, Pid, _Info) -> cdv_virtual_list:start_link(Parent, cdv_ets_wx, Pid). diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl index 23d6fd6b16..038126288b 100644 --- a/lib/observer/src/crashdump_viewer_html.erl +++ b/lib/observer/src/crashdump_viewer_html.erl @@ -53,6 +53,7 @@ chunk/3]). -include("crashdump_viewer.hrl"). +-include("observer_defs.hrl"). %%%----------------------------------------------------------------- %%% Welcome frame @@ -497,51 +498,94 @@ expanded_memory(Heading,Expanded) -> expanded_memory_body(Heading,[]) -> [case Heading of "MsgQueue" -> "No messages were found"; - "StackDump" -> "No stack dump was found"; - "Dictionary" -> "No dictionary was found" + "Message Queue" -> "No messages were found"; + "StackDump" -> "No stack dump was found"; + "Dictionary" -> "No dictionary was found"; + "ProcState" -> "Information could not be retrieved," + " system messages may not be handled by this process." end]; expanded_memory_body(Heading,Expanded) -> + Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", [case Heading of "MsgQueue" -> - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Message"), - th("SeqTraceToken")]) | - lists:map(fun(Msg) -> msgq_table(Msg) end, Expanded)]); + table(Attr, + [tr( + [th("WIDTH=70%","Message"), + th("WIDTH=30%","SeqTraceToken")]) | + element(1, lists:mapfoldl(fun(Msg, Even) -> + {msgq_table(Msg, Even), + not Even} + end, + true, Expanded))]); + "Message Queue" -> + table(Attr, + [tr( + [th("WIDTH=10%","Id"), + th("WIDTH=90%","Message")]) | + element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> + {msgq_table(Msg, N, Even), + {not Even, N+1}} + end, + {true,1}, Expanded))]); "StackDump" -> - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Label"), - th("Term")]) | - lists:map(fun(Entry) -> stackdump_table(Entry) end, Expanded)]); + table(Attr, + [tr( + [th("WIDTH=20%","Label"), + th("WIDTH=80%","Term")]) | + element(1, lists:mapfoldl(fun(Entry, Even) -> + {stackdump_table(Entry, Even), + not Even} + end, true, Expanded))]); + "ProcState" -> + table(Attr, + [tr( + [th("WIDTH=20%","Label"), + th("WIDTH=80%","Information")]) | + element(1, lists:mapfoldl(fun(Entry, Even) -> + {proc_state(Entry,Even), + not Even} + end, true, Expanded))]); _ -> - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Key"), - th("Value")]) | - lists:map(fun(Entry) -> dict_table(Entry) end, Expanded)]) + table(Attr, + [tr( + [th("WIDTH=30%","Key"), + th("WIDTH=70%","Value")]) | + element(1, lists:mapfoldl(fun(Entry, Even) -> + {dict_table(Entry,Even), + not Even} + end, true, Expanded))]) end]. -msgq_table({Msg0,Token0}) -> +msgq_table({Msg0,Token0}, Even) -> Token = case Token0 of [] -> ""; _ -> io_lib:fwrite("~w",[Token0]) end, Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))), - tr([td(pre(Msg)), td(Token)]). - -stackdump_table({Label0,Term0}) -> + tr(color(Even),[td(pre(Msg)), td(Token)]). + +msgq_table(Msg0, Id, Even) -> + Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))), + tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]). + +stackdump_table({Label0,Term0},Even) -> Label = io_lib:format("~w",[Label0]), Term = href_proc_port(lists:flatten(io_lib:format("~p",[Term0]))), - tr([td("VALIGN=top",Label), td(pre(Term))]). - -dict_table({Key0,Value0}) -> + tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]). + +dict_table({Key0,Value0}, Even) -> Key = href_proc_port(lists:flatten(io_lib:format("~p",[Key0]))), Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))), - tr([td("VALIGN=top",pre(Key)), td(pre(Value))]). + tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]). + +proc_state({Key0,Value0}, Even) -> + Key = lists:flatten(io_lib:format("~s",[Key0])), + Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))), + tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]). + + +color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); +color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). %%%----------------------------------------------------------------- diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 72bafcc5e0..82395fbe08 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_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 @@ -190,8 +190,8 @@ handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, {noreply, State}; handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}}, - State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> - observer_procinfo:start(Pid, Panel, self()), + State = #state{sel={#box{s1=#str{pid=Pid}},_}}) -> + observer ! {open_link, Pid}, {noreply, State}; handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}}, @@ -337,8 +337,8 @@ code_change(_, _, State) -> handle_mouse_click(Node = {#box{s1=#str{pid=Pid}},_}, Type, State=#state{app_w=AppWin,panel=Panel}) -> case Type of - left_dclick -> observer_procinfo:start(Pid, Panel, self()); - right_down -> popup_menu(Panel); + left_dclick -> observer ! {open_link, Pid}; + right_down -> popup_menu(Panel); _ -> ok end, observer_wx:set_status(io_lib:format("Pid: ~p", [Pid])), diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index ce85fb4af6..159afae487 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -27,7 +27,7 @@ create_attrs/0, set_listctrl_col_size/2, create_status_bar/1, - html_window/2 + html_window/1, html_window/2 ]). -include_lib("wx/include/wx.hrl"). @@ -185,11 +185,27 @@ update_info([Fields|Fs], [{_Header, _Attrib, SubStructure}| Rest]) -> update_info([], []) -> ok. +update_info2([Scroll = {_, _, _}|Fs], [{_, NewInfo}|Rest]) -> + update_scroll_boxes(Scroll, NewInfo), + update_info2(Fs, Rest); +update_info2([Field|Fs], [{_Str, {click, Value}}|Rest]) -> + wxTextCtrl:setValue(Field, to_str(Value)), + update_info2(Fs, Rest); update_info2([Field|Fs], [{_Str, Value}|Rest]) -> wxTextCtrl:setValue(Field, to_str(Value)), update_info2(Fs, Rest); update_info2([], []) -> ok. +update_scroll_boxes({_, _, 0}, {_, []}) -> ok; +update_scroll_boxes({Win, Sizer, _}, {Type, List}) -> + [wxSizerItem:deleteWindows(Child) || Child <- wxSizer:getChildren(Sizer)], + BC = wxWindow:getBackgroundColour(Win), + Cursor = wxCursor:new(?wxCURSOR_HAND), + add_entries(Type, List, Win, Sizer, BC, Cursor), + wxCursor:destroy(Cursor), + wxSizer:recalcSizes(Sizer), + wxWindow:refresh(Win), + ok. to_str(Value) when is_atom(Value) -> atom_to_list(Value); @@ -333,45 +349,51 @@ 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}. +add_box(Panel, OuterBox, Cursor, 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), + add_entries(Format, List, Scroll, ScrollSizer, BC, Cursor), + wxSizer:add(Box,Scroll,[{proportion,1},{flag,?wxEXPAND}]), + wxSizer:add(OuterBox,Box,[{proportion,Proportion},{flag,?wxEXPAND}]), + {Scroll,ScrollSizer,length(List)}. + +add_entries(click, List, Scroll, ScrollSizer, BC, Cursor) -> + Add = fun(Link) -> + TC = link_entry(Scroll, Link, Cursor), + wxWindow:setBackgroundColour(TC,BC), + wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}]) + end, + [Add(Link) || Link <- List]; +add_entries(plain, List, Scroll, ScrollSizer, _, _) -> + Add = fun(String) -> + TC = wxTextCtrl:new(Scroll, ?wxID_ANY, + [{style,?SINGLE_LINE_STYLE}, + {value,String}]), + wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}]) + end, + [Add(String) || String <- 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)} + AddBox = fun({Title,Proportion,Format = {_,_}}) -> + add_box(Panel, OuterBox, Cursor, Title, Proportion, Format); + ({Title, Format = {_,_}}) -> + add_box(Panel, OuterBox, Cursor, Title, 1, Format) end, Boxes = [AddBox(Entry) || Entry <- Data], wxCursor:destroy(Cursor), - MaxL = lists:foldl(fun({_,L},Max) when L>Max -> L; + MaxL = lists:foldl(fun({_,_,L},Max) when L>Max -> L; (_,Max) -> Max end, 0, @@ -384,9 +406,9 @@ create_box(Panel, {scroll_boxes,Data}) -> MaxH = if MaxL > 8 -> 8*H; true -> MaxL*H end, - [wxWindow:setMinSize(B,{0,MaxH}) || {B,_} <- Boxes], + [wxWindow:setMinSize(B,{0,MaxH}) || {B,_,_} <- Boxes], wxSizer:layout(OuterBox), - {OuterBox, []}; + {OuterBox, Boxes}; create_box(Panel, Data) -> {Title, Align, Info} = get_box_info(Data), @@ -410,7 +432,7 @@ create_box(Panel, Data) -> [{style,?MULTI_LINE_STYLE}, {value,"unknown"}]); {click,Value} -> - link_entry(Panel,{Value,Value}); + link_entry(Panel,Value); _ -> Value = to_str(Value0), wxTextCtrl:new(Panel, ?wxID_ANY, @@ -431,28 +453,44 @@ create_box(Panel, Data) -> link_entry(Panel, Link) -> Cursor = wxCursor:new(?wxCURSOR_HAND), - TC = link_entry(Panel, Link, Cursor), + TC = link_entry2(Panel, to_link(Link), Cursor), wxCursor:destroy(Cursor), TC. -link_entry(Panel,{Target,Str},Cursor) -> - TC = wxTextCtrl:new(Panel, ?wxID_ANY, - [{style, ?SINGLE_LINE_STYLE}]), +link_entry(Panel, Link, Cursor) -> + link_entry2(Panel, to_link(Link), Cursor). + +link_entry2(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), + ToolTip = wxToolTip:new("Click to see properties for " ++ Str), wxWindow:setToolTip(TC, ToolTip), TC. -html_window(Panel,Html) -> +to_link(Tuple = {_Target, _Str}) -> Tuple; +to_link(Target) -> {Target, to_str(Target)}. + +html_window(Panel) -> Win = wxHtmlWindow:new(Panel, [{style, ?wxHW_SCROLLBAR_AUTO}]), - wxHtmlWindow:setPage(Win,Html), + FixedName = case whereis(observer) of + undefined -> "courier"; + _Pid -> + Fixed = observer_wx:get_attrib({font,fixed}), + wxFont:getFaceName(Fixed) + end, + wxHtmlWindow:setFonts(Win, "", FixedName), wxHtmlWindow:connect(Win,command_html_link_clicked), Win. +html_window(Panel, Html) -> + Win = html_window(Panel), + wxHtmlWindow:setPage(Win, Html), + Win. + get_max_size(Panel,Info) -> Txt = wxTextCtrl:new(Panel, ?wxID_ANY, []), Size = get_max_size(Txt,Info,0,0), diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 9aaf648ea2..10e2f12e0f 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-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 @@ -191,20 +191,16 @@ dump_to_file(Parent, FileName, Holder) -> start_procinfo(undefined, _Frame, Opened) -> Opened; start_procinfo(Pid, Frame, Opened) -> - %% This code doesn't work until we collect which windows have been - %% closed maybe it should moved to observer_wx.erl - %% and add a global menu which remembers windows. - %% case lists:keyfind(Pid, 1, Opened) of - %% false -> - case observer_procinfo:start(Pid, Frame, self()) of - {error, _} -> Opened; - PI -> [{Pid, PI} | Opened] + case lists:keyfind(Pid, 1, Opened) of + false -> + case observer_procinfo:start(Pid, Frame, self()) of + {error, _} -> Opened; + PI -> [{Pid, PI} | Opened] + end; + {_, PI} -> + wxFrame:raise(PI), + Opened end. - %%; - %% {_, PI} -> - %% wxFrame:raise(PI), - %% Opened - %% end. call(Holder, What) -> Ref = erlang:monitor(process, Holder), @@ -235,9 +231,14 @@ handle_info(refresh_interval, #state{holder=Holder}=State) -> handle_info({procinfo_menu_closed, Pid}, #state{procinfo_menu_pids=Opened}=State) -> - NewPids = lists:delete(Pid, Opened), + NewPids = lists:keydelete(Pid, 1, Opened), {noreply, State#state{procinfo_menu_pids=NewPids}}; +handle_info({procinfo_open, Pid}, + #state{panel=Panel, procinfo_menu_pids=Opened}=State) -> + Opened2 = start_procinfo(Pid, Panel, Opened), + {noreply, State#state{procinfo_menu_pids=Opened2}}; + handle_info({active, Node}, #state{holder=Holder, timer=Timer, parent=Parent}=State) -> create_pro_menu(Parent, Holder), @@ -378,8 +379,7 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, handle_event(#wx{event=#wxList{type=command_list_item_activated}}, #state{panel=Panel, procinfo_menu_pids=Opened, - sel={_, [Pid|_]}}=State) - when Pid =/= undefined -> + sel={_, [Pid|_]}}=State) -> Opened2 = start_procinfo(Pid, Panel, Opened), {noreply, State#state{procinfo_menu_pids=Opened2}}; diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 98d0403139..db5eefaa6c 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -106,6 +106,22 @@ handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages}=State) end, {noreply, State}; +handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) -> + observer ! {open_link, TargetPid}, + {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(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) -> + observer ! {open_link, Info}, + {noreply, State}; + handle_event(Event, _State) -> error({unhandled_event, Event}). @@ -139,58 +155,37 @@ init_process_page(Panel, Pid) -> observer_lib:update_info(UpFields, Fields) end}. -init_text_page(Parent) -> - Style = ?wxTE_MULTILINE bor ?wxTE_RICH2 bor ?wxTE_READONLY, - Text = wxTextCtrl:new(Parent, ?wxID_ANY, [{style, Style}]), - Font = observer_wx:get_attrib({font, fixed}), - Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), - true = wxTextCtrl:setDefaultStyle(Text, Attr), - wxTextAttr:destroy(Attr), - Text. init_message_page(Parent, Pid) -> - Text = init_text_page(Parent), - Format = fun(Message, Number) -> - {io_lib:format("~-4.w ~p~n", [Number, Message]), - Number+1} - end, + Win = observer_lib:html_window(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]) of - {messages,RawMessages} -> - {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - case Messages =:= [] of - true -> - wxTextCtrl:writeText(Text, "No messages"); - false -> - wxTextCtrl:writeText(Text, Messages) - end; + {messages, Messages} -> + Html = crashdump_viewer_html:expanded_memory("Message Queue", Messages), + wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) end end, Update(), - {Text, Update}. + {Win, Update}. init_dict_page(Parent, Pid) -> - Text = init_text_page(Parent), + Win = observer_lib:html_window(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) of - {dictionary,RawDict} -> - Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - wxTextCtrl:writeText(Text, Dict); + {dictionary,Dict} -> + Html = crashdump_viewer_html:expanded_memory("Dictionary", Dict), + wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) end end, Update(), - {Text, Update}. + {Win, Update}. init_stack_page(Parent, Pid) -> LCtrl = wxListCtrl:new(Parent, [{style, ?wxLC_REPORT bor ?wxLC_HRULES}]), @@ -236,58 +231,58 @@ init_stack_page(Parent, Pid) -> Update(), {LCtrl, Update}. - init_state_page(Parent, Pid) -> - Text = init_text_page(Parent), + Win = observer_lib:html_window(Parent), Update = fun() -> - %% First, test if sys:get_status/2 have any chance to return an answer - case rpc:call(node(Pid), proc_lib, translate_initial_call, [Pid]) - of - %% Not a gen process - {proc_lib,init_p,5} -> Misc = [{"Information", "Not available"}]; - %% May be a gen process - {M, _F, _A} -> - %% Get the behavio(u)r - I = rpc:call(node(Pid), M, module_info, [attributes]), - case lists:keyfind(behaviour, 1, I) of - false -> case lists:keyfind(behavior, 1, I) of - false -> B = undefined; - {behavior, [B]} -> B - end; - {behaviour, [B]} -> B - end, - %% but not sure that system messages are treated by this process - %% so using a rpc with a small timeout in order not to lag the display - case rpc:call(node(Pid), sys, get_status, [Pid, 200]) - of - {status, _, {module, _}, [_PDict, _SysState, _Parent, _Dbg, - [Header,{data, First},{data, Second}]]} -> - Misc = [{"Behaviour", B}] ++ [Header] ++ First ++ Second; - {status, _, {module, _}, [_PDict, _SysState, _Parent, _Dbg, - [Header,{data, First}, OtherFormat]]} -> - Misc = [{"Behaviour", B}] ++ [Header] ++ First ++ [{"State",OtherFormat}]; - {status, _, {module, _}, [_PDict, _SysState, _Parent, _Dbg, - OtherFormat]} -> - %% Formatted status ? - case lists:keyfind(format_status, 1, rpc:call(node(Pid), M, module_info, [exports])) of - false -> Opt = {"Format", unknown}; - _ -> Opt = {"Format", overriden} - end, - Misc = [{"Behaviour", B}] ++ [Opt, {"State",OtherFormat}]; - {badrpc,{'EXIT',{timeout, _}}} -> - Misc = [{"Information","Timed out"}, - {"Tip","system messages are probably not treated by this process"}] - end; - _ -> Misc=[], throw(process_undefined) - end, - Dict = [io_lib:format("~-20.s ~tp~n", [K, V]) || {K, V} <- Misc], - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - wxTextCtrl:writeText(Text, Dict) + StateInfo = fetch_state_info(Pid), + Html = crashdump_viewer_html:expanded_memory("ProcState", StateInfo), + wxHtmlWindow:setPage(Win, Html) end, Update(), - {Text, Update}. + {Win, Update}. + +fetch_state_info(Pid) -> + %% First, test if sys:get_status/2 have any chance to return an answer + case rpc:call(node(Pid), proc_lib, translate_initial_call, [Pid]) of + %% Not a gen process + {proc_lib,init_p,5} -> []; + %% May be a gen process + {M, _F, _A} -> fetch_state_info2(Pid, M); + _ -> throw(process_undefined) + end. +fetch_state_info2(Pid, M) -> + %% Get the behavio(u)r + I = rpc:call(node(Pid), M, module_info, [attributes]), + case lists:keyfind(behaviour, 1, I) of + false -> case lists:keyfind(behavior, 1, I) of + false -> B = undefined; + {behavior, [B]} -> B + end; + {behaviour, [B]} -> B + end, + %% but not sure that system messages are treated by this process + %% so using a rpc with a small timeout in order not to lag the display + case rpc:call(node(Pid), sys, get_status, [Pid, 200]) + of + {status, _, {module, _}, + [_PDict, _SysState, _Parent, _Dbg, + [Header,{data, First},{data, Second}]]} -> + [{"Behaviour", B}, Header] ++ First ++ Second; + {status, _, {module, _}, + [_PDict, _SysState, _Parent, _Dbg, + [Header,{data, First}, OtherFormat]]} -> + [{"Behaviour", B}, Header] ++ First ++ [{"State",OtherFormat}]; + {status, _, {module, _}, + [_PDict, _SysState, _Parent, _Dbg, OtherFormat]} -> + %% Formatted status ? + case lists:keyfind(format_status, 1, rpc:call(node(Pid), M, module_info, [exports])) of + false -> Opt = {"Format", unknown}; + _ -> Opt = {"Format", overriden} + end, + [{"Behaviour", B}, Opt, {"State",OtherFormat}]; + {badrpc,{'EXIT',{timeout, _}}} -> [] + end. create_menus(MenuBar) -> Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}, @@ -301,6 +296,7 @@ process_info_fields(Pid) -> {"Registered Name", registered_name}, {"Status", status}, {"Message Queue Len",message_queue_len}, + {"Group Leader", {click, group_leader}}, {"Priority", priority}, {"Trap Exit", trap_exit}, {"Reductions", reductions}, @@ -311,11 +307,10 @@ process_info_fields(Pid) -> {"Suspending", suspending}, {"Sequential Trace Token", sequential_trace_token}, {"Error Handler", error_handler}]}, - {"Connections", - [{"Group Leader", group_leader}, - {"Links", links}, - {"Monitors", monitors}, - {"Monitored by", monitored_by}]}, + {scroll_boxes, + [{"Links", {click, links}}, + {"Monitors", {click, filter_monitor_info()}}, + {"Monitored by", {click, monitored_by}}]}, {"Memory and Garbage Collection", right, [{"Memory", {bytes, memory}}, {"Stack and Heaps", {bytes, total_heap_size}}, @@ -365,3 +360,9 @@ get_gc_info(Arg) -> GC = proplists:get_value(garbage_collection, Data), proplists:get_value(Arg, GC) end. + +filter_monitor_info() -> + fun(Data) -> + Ms = proplists:get_value(monitors, Data), + [Pid || {process, Pid} <- Ms] + end. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 3afe933e5a..9839f8bf7b 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -346,6 +346,22 @@ handle_info({nodedown, Node}, create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), {noreply, State3}; +handle_info({open_link, Pid0}, State = #state{pro_panel=ProcViewer, frame=Frame}) -> + Pid = case Pid0 of + [_|_] -> try list_to_pid(Pid0) catch _:_ -> Pid0 end; + _ -> Pid0 + end, + %% Forward to process tab + case is_pid(Pid) of + true -> wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid}; + false -> + Msg = io_lib:format("Information about ~p is not available or implemented",[Pid]), + Info = wxMessageDialog:new(Frame, Msg), + wxMessageDialog:showModal(Info), + wxMessageDialog:destroy(Info) + end, + {noreply, State}; + handle_info({'EXIT', Pid, _Reason}, State) -> io:format("Child (~s) crashed exiting: ~p ~p~n", [pid2panel(Pid, State), Pid,_Reason]), -- cgit v1.2.3 From 41380c0ff6c4fb56aad5702b9d9554ae36580063 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 16 Oct 2013 16:53:53 +0200 Subject: observer: improve wx version of crashdump_viewer * bugfixes * better progress dialogs * show expanded binaries in different formats * speed up reading of big crashdumps --- lib/observer/src/Makefile | 4 + lib/observer/src/cdv_bin_wx.erl | 59 ++- lib/observer/src/cdv_detail_win.erl | 81 ++-- lib/observer/src/cdv_dist_wx.erl | 10 +- lib/observer/src/cdv_ets_wx.erl | 15 +- lib/observer/src/cdv_fun_wx.erl | 6 +- lib/observer/src/cdv_gen_wx.erl | 4 +- lib/observer/src/cdv_html_page.erl | 121 ++++++ lib/observer/src/cdv_info_page.erl | 39 +- lib/observer/src/cdv_int_tab_wx.erl | 13 +- lib/observer/src/cdv_mem_wx.erl | 14 +- lib/observer/src/cdv_mod_wx.erl | 25 +- lib/observer/src/cdv_multi_panel.erl | 64 ++-- lib/observer/src/cdv_port_wx.erl | 14 +- lib/observer/src/cdv_proc_wx.erl | 75 ++-- lib/observer/src/cdv_table_page.erl | 3 - lib/observer/src/cdv_term_wx.erl | 66 ++++ lib/observer/src/cdv_timer_wx.erl | 6 +- lib/observer/src/cdv_virtual_list.erl | 65 ++-- lib/observer/src/crashdump_viewer.erl | 593 +++++++++++++++-------------- lib/observer/src/crashdump_viewer_html.erl | 187 +++++---- lib/observer/src/crashdump_viewer_wx.erl | 224 +++++------ lib/observer/src/observer_lib.erl | 134 ++++++- lib/observer/src/observer_procinfo.erl | 59 ++- lib/observer/src/observer_term_wx.erl | 113 ++++++ lib/observer/src/observer_wx.erl | 16 +- 26 files changed, 1247 insertions(+), 763 deletions(-) create mode 100644 lib/observer/src/cdv_html_page.erl create mode 100644 lib/observer/src/cdv_term_wx.erl create mode 100644 lib/observer/src/observer_term_wx.erl (limited to 'lib/observer/src') diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index dfebe71282..0b1813db0a 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -43,6 +43,7 @@ MODULES= \ cdv_detail_win \ cdv_table_page \ cdv_multi_panel \ + cdv_html_page \ cdv_gen_wx \ cdv_proc_wx \ cdv_port_wx \ @@ -55,6 +56,8 @@ MODULES= \ cdv_mod_wx \ cdv_mem_wx \ cdv_int_tab_wx \ + cdv_bin_wx \ + cdv_term_wx \ etop \ etop_gui \ etop_tr \ @@ -71,6 +74,7 @@ MODULES= \ observer_traceoptions_wx \ observer_tv_table \ observer_tv_wx \ + observer_term_wx \ ttb \ ttb_et diff --git a/lib/observer/src/cdv_bin_wx.erl b/lib/observer/src/cdv_bin_wx.erl index d79f0dbc22..d2cbcfb747 100644 --- a/lib/observer/src/cdv_bin_wx.erl +++ b/lib/observer/src/cdv_bin_wx.erl @@ -20,17 +20,60 @@ -export([get_details/1, detail_pages/0]). --include_lib("wx/include/wx.hrl"). --include("crashdump_viewer.hrl"). - %% Callbacks for cdv_detail_win get_details(Id) -> {ok,Bin} = crashdump_viewer:expand_binary(Id), - {ok,{"Expanded Binary", io_lib:format("~tp",[Bin]), []}}. + {ok,{"Expanded Binary", Bin, []}}. detail_pages() -> - [{simple, "Binary", fun init_bin_page/3}]. + [{"Binary", fun init_bin_page/2}]. + +init_bin_page(Parent,Bin) -> + cdv_multi_panel:start_link( + Parent, + [{"Format \~p",cdv_html_page,format_bin_fun("~p",Bin)}, + {"Format \~tp",cdv_html_page,format_bin_fun("~tp",Bin)}, + {"Format \~w",cdv_html_page,format_bin_fun("~w",Bin)}, + {"Format \~s",cdv_html_page,format_bin_fun("~s",Bin)}, + {"Format \~ts",cdv_html_page,format_bin_fun("~ts",Bin)}, + {"Hex",cdv_html_page,hex_binary_fun(Bin)}, + {"Term",cdv_html_page, binary_to_term_fun(Bin)}]). + +format_bin_fun(Format,Bin) -> + fun() -> + try io_lib:format(Format,[Bin]) of + Str -> plain_html(lists:flatten(Str)) + catch error:badarg -> + Warning = "This binary can not be formatted with " ++ Format, + crashdump_viewer_html:warning(Warning) + end + end. + +binary_to_term_fun(Bin) -> + fun() -> + try binary_to_term(Bin) of + Term -> plain_html(io_lib:format("~p",[Term])) + catch error:badarg -> + Warning = "This binary can not be coverted to an Erlang term", + crashdump_viewer_html:warning(Warning) + end + end. + +-define(line_break,25). +hex_binary_fun(Bin) -> + fun() -> + S = "<<" ++ format_hex(Bin,?line_break) ++ ">>", + plain_html(io_lib:format("~s",[S])) + end. + +format_hex(<>,_) -> + [integer_to_list(B1,16),integer_to_list(B2,16)]; +format_hex(<>,0) -> + [integer_to_list(B1,16),integer_to_list(B2,16),$,,$\n,$\s,$\s + | format_hex(Bin,?line_break)]; +format_hex(<>,N) -> + [integer_to_list(B1,16),integer_to_list(B2,16),$, + | format_hex(Bin,N-1)]. -init_bin_page(Parent, _, Bin) -> - Html = crashdump_viewer_html:plain_page(Bin), - observer_lib:html_window(Parent,Html). +plain_html(Text) -> + crashdump_viewer_html:plain_page(Text). diff --git a/lib/observer/src/cdv_detail_win.erl b/lib/observer/src/cdv_detail_win.erl index 014ed41e70..4445169379 100644 --- a/lib/observer/src/cdv_detail_win.erl +++ b/lib/observer/src/cdv_detail_win.erl @@ -19,14 +19,11 @@ -behaviour(wx_object). --export([start/3]). +-export([start_link/3]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, handle_call/3, handle_info/2]). --export([init_detail_page/3]). - - -include_lib("wx/include/wx.hrl"). -include("crashdump_viewer.hrl"). -include("observer_defs.hrl"). @@ -37,16 +34,13 @@ pages=[] }). - %% Defines -define(ID_NOTEBOOK, 604). %% Detail view -start(Id, ParentFrame, Callback) -> +start_link(Id, ParentFrame, Callback) -> wx_object:start_link(?MODULE, [Id, ParentFrame, Callback, self()], []). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Id, ParentFrame, Callback, Parent]) -> @@ -72,9 +66,9 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) -> wxFrame:setMenuBar(Frame, MenuBar), Panel = wxPanel:new(Frame, []), - Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), Sizer = wxBoxSizer:new(?wxVERTICAL), - wxSizer:add(Sizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), + {InfoPanel,Pages} = create_pages(Panel,Callback:detail_pages(),[Info]), + wxSizer:add(Sizer, InfoPanel, [{proportion, 1}, {flag, ?wxEXPAND}]), case TW of [] -> @@ -90,9 +84,6 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) -> wxPanel:setSizer(Panel, Sizer), - Pages = [init_panel(Type, Notebook, PageTitle, Fun, [Id,Info]) - || {Type,PageTitle,Fun} <- Callback:detail_pages()], - wxFrame:connect(Frame, close_window), wxMenu:connect(Frame, command_menu_selected), wxFrame:show(Frame), @@ -102,8 +93,23 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) -> pages=Pages }}. -init_panel(simple, Notebook, Str, Fun, FunArgs) -> - Panel = wxScrolledWindow:new(Notebook), +create_pages(Panel,[{_PageTitle,Fun}],FunArgs) -> + %% Only one page - don't create notebook + Page = init_panel(Panel, Fun, FunArgs), + {Page,[Page]}; +create_pages(Panel,PageSpecs,FunArgs) -> + Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), + Pages = [init_tab(Notebook, PageTitle, Fun, FunArgs) + || {PageTitle,Fun} <- PageSpecs], + {Notebook, Pages}. + +init_tab(Notebook,Title,Fun,FunArgs) -> + Panel = init_panel(Notebook,Fun,FunArgs), + true = wxNotebook:addPage(Notebook, Panel, Title), + Panel. + +init_panel(ParentWin, Fun, FunArgs) -> + Panel = wxScrolledWindow:new(ParentWin), wxScrolledWindow:enableScrolling(Panel,true,true), wxScrolledWindow:setScrollbars(Panel,1,1,0,0), Sizer = wxBoxSizer:new(?wxHORIZONTAL), @@ -112,14 +118,8 @@ init_panel(simple, Notebook, Str, Fun, FunArgs) -> {proportion, 1}, {border, 5}]), wxPanel:setSizer(Panel, Sizer), - true = wxNotebook:addPage(Notebook, Panel, Str), - Panel; -init_panel(list, Notebook, Str, Fun, FunArgs) -> - Panel = apply(Fun, [Notebook | FunArgs]), - true = wxNotebook:addPage(Notebook, Panel, Str), Panel. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_event(#wx{event=#wxClose{type=close_window}}, State) -> {stop, normal, State}; @@ -128,41 +128,11 @@ handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> {stop, normal, State}; -handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, - linkInfo=#wxHtmlLinkInfo{href=Target}}}, - State) -> - case Target of - "#Binary<" ++ Id0 -> - Id = string:strip(Id0,right,$>), - start(Id, State#state.frame, cdv_bin_wx); - _ -> - cdv_virtual_list:start_detail_win(Target) - end, - {noreply, State}; - -handle_event(#wx{event=#wxMouse{type=left_down}, - userData=Target}, - State) -> - cdv_virtual_list:start_detail_win(Target), - {noreply, State}; - -handle_event(#wx{obj=Obj, - event=#wxMouse{type=enter_window}}, - State) -> - wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), - {noreply, State}; - -handle_event(#wx{obj=Obj, - event=#wxMouse{type=leave_window}}, - State) -> - wxTextCtrl:setForegroundColour(Obj,?wxBLUE), - {noreply, State}; - handle_event(Event, _State) -> error({unhandled_event, Event}). handle_info(_Info, State) -> - %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), + %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, _Info]), {noreply, State}. handle_call(Call, From, _State) -> @@ -172,7 +142,7 @@ handle_cast(Cast, _State) -> error({unhandled_cast, Cast}). terminate(_Reason, #state{parent=Parent,id=Id,frame=Frame}) -> - Parent ! {detail_win_closed, Id}, + wx_object:cast(Parent,{detail_win_closed, Id}), case Frame of undefined -> ok; _ -> wxFrame:destroy(Frame) @@ -183,11 +153,6 @@ code_change(_, _, State) -> {ok, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init_detail_page(Panel, Fields, Info) -> - Filled = observer_lib:fill_info(Fields, Info), - {FPanel, _, _UpFields} = observer_lib:display_info(Panel, Filled), - FPanel. - create_menus(MenuBar) -> Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}], observer_lib:create_menus(Menus, MenuBar, new_window). diff --git a/lib/observer/src/cdv_dist_wx.erl b/lib/observer/src/cdv_dist_wx.erl index 6a4a6da422..6e6284a6ba 100644 --- a/lib/observer/src/cdv_dist_wx.erl +++ b/lib/observer/src/cdv_dist_wx.erl @@ -44,8 +44,8 @@ col_to_elem(?COL_TYPE) -> #nod.conn_type. col_spec() -> [{"Name", ?wxLIST_FORMAT_LEFT, 300}, - {"Connection type", ?wxLIST_FORMAT_LEFT, 150}, - {"Controller", ?wxLIST_FORMAT_LEFT, 150}, + {"Connection type", ?wxLIST_FORMAT_LEFT, 130}, + {"Controller", ?wxLIST_FORMAT_LEFT, 130}, {"Channel", ?wxLIST_FORMAT_RIGHT, 80}, {"Creation", ?wxLIST_FORMAT_RIGHT, 80}]. @@ -64,11 +64,11 @@ get_details(Id) -> {ok,{Title,Proplist,TW}}. detail_pages() -> - [{simple, "General Information", fun init_gen_page/3}]. + [{"General Information", fun init_gen_page/2}]. -init_gen_page(Parent, _Id, Info) -> +init_gen_page(Parent, Info) -> Fields = info_fields(), - cdv_detail_win:init_detail_page(Parent, Fields, Info). + cdv_info_page:start_link(Parent,{Fields,Info,[]}). %%%----------------------------------------------------------------- %%% Internal diff --git a/lib/observer/src/cdv_ets_wx.erl b/lib/observer/src/cdv_ets_wx.erl index 7a2e80a989..ac45aa297e 100644 --- a/lib/observer/src/cdv_ets_wx.erl +++ b/lib/observer/src/cdv_ets_wx.erl @@ -30,10 +30,10 @@ -define(COL_NAME, ?COL_ID+1). -define(COL_SLOT, ?COL_NAME+1). -define(COL_OWNER, ?COL_SLOT+1). --define(COL_TYPE, ?COL_OWNER+1). --define(COL_BUCK, ?COL_TYPE+1). +-define(COL_BUCK, ?COL_OWNER+1). -define(COL_OBJ, ?COL_BUCK+1). -define(COL_MEM, ?COL_OBJ+1). +-define(COL_TYPE, ?COL_MEM+1). %% Callbacks for cdv_virtual_list col_to_elem(id) -> col_to_elem(?COL_ID); @@ -49,12 +49,13 @@ col_to_elem(?COL_MEM) -> #ets_table.memory. col_spec() -> [{"Id", ?wxLIST_FORMAT_LEFT, 200}, {"Name", ?wxLIST_FORMAT_LEFT, 200}, - {"Slot", ?wxLIST_FORMAT_RIGHT, 60}, + {"Slot", ?wxLIST_FORMAT_RIGHT, 50}, {"Owner", ?wxLIST_FORMAT_CENTRE, 90}, - {"Type", ?wxLIST_FORMAT_LEFT, 60}, - {"Buckets", ?wxLIST_FORMAT_RIGHT, 60}, - {"Objects", ?wxLIST_FORMAT_RIGHT, 80}, - {"Memory", ?wxLIST_FORMAT_RIGHT, 80}]. + {"Buckets", ?wxLIST_FORMAT_RIGHT, 50}, + {"Objects", ?wxLIST_FORMAT_RIGHT, 50}, + {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, + {"Type", ?wxLIST_FORMAT_LEFT, 50} + ]. get_info(Owner) -> {ok,Info,TW} = crashdump_viewer:ets_tables(Owner), diff --git a/lib/observer/src/cdv_fun_wx.erl b/lib/observer/src/cdv_fun_wx.erl index c4a78594fa..296abd2719 100644 --- a/lib/observer/src/cdv_fun_wx.erl +++ b/lib/observer/src/cdv_fun_wx.erl @@ -44,11 +44,11 @@ col_to_elem(?COL_REFC) -> #fu.refc. col_spec() -> [{"Module", ?wxLIST_FORMAT_LEFT, 200}, - {"Uniq", ?wxLIST_FORMAT_RIGHT, 150}, - {"Index", ?wxLIST_FORMAT_RIGHT, 60}, + {"Uniq", ?wxLIST_FORMAT_RIGHT, 100}, + {"Index", ?wxLIST_FORMAT_RIGHT, 50}, {"Address", ?wxLIST_FORMAT_LEFT, 120}, {"Native Address", ?wxLIST_FORMAT_LEFT, 120}, - {"Refc", ?wxLIST_FORMAT_RIGHT, 60}]. + {"Refc", ?wxLIST_FORMAT_RIGHT, 50}]. get_info(_) -> {ok,Info,TW} = crashdump_viewer:funs(), diff --git a/lib/observer/src/cdv_gen_wx.erl b/lib/observer/src/cdv_gen_wx.erl index 92eee203a7..92759aaa6d 100644 --- a/lib/observer/src/cdv_gen_wx.erl +++ b/lib/observer/src/cdv_gen_wx.erl @@ -22,11 +22,11 @@ -include("crashdump_viewer.hrl"). get_info() -> - {ok,Info} = crashdump_viewer:init_general_info(), + {ok,Info,TW} = crashdump_viewer:general_info(), Fields = info_fields(), Proplist = crashdump_viewer:to_proplist(record_info(fields,general_info),Info), - {Fields,Proplist,[]}. + {Fields,Proplist,TW}. info_fields() -> [{"General Information", diff --git a/lib/observer/src/cdv_html_page.erl b/lib/observer/src/cdv_html_page.erl new file mode 100644 index 0000000000..b2d059f7f8 --- /dev/null +++ b/lib/observer/src/cdv_html_page.erl @@ -0,0 +1,121 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(cdv_html_page). + +-behaviour(wx_object). + +-export([start_link/2]). +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_cast/2]). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +%% Records +-record(state, + {panel, + expand_table, + expand_wins=[]}). + +start_link(ParentWin, Info) -> + wx_object:start_link(?MODULE, [ParentWin, Info], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([ParentWin, Fun]) when is_function(Fun) -> + init([ParentWin, Fun()]); +init([ParentWin, {expand,HtmlText,Tab}]) -> + HtmlWin = observer_lib:html_window(ParentWin), + wxHtmlWindow:setPage(HtmlWin,HtmlText), + {HtmlWin, #state{panel=HtmlWin,expand_table=Tab}}; +init([ParentWin, HtmlText]) -> + HtmlWin = observer_lib:html_window(ParentWin), + wxHtmlWindow:setPage(HtmlWin,HtmlText), + {HtmlWin, #state{panel=HtmlWin}}. + +%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info(active, State) -> + {noreply, State}; + +handle_info(Info, State) -> + io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_, _, State) -> + {ok, State}. + +handle_call(Msg, _From, State) -> + io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) -> + Opened = lists:keydelete(Id, 1, Opened0), + {noreply, State#state{expand_wins=Opened}}; + +handle_cast(Msg, State) -> + io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), + {noreply, State}. + +handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, + linkInfo=#wxHtmlLinkInfo{href=Target}}}, + #state{expand_table=Tab}=State) -> + NewState= + case Target of + "#Binary?" ++ BinSpec -> + [{"offset",Off},{"size",Size},{"pos",Pos}] = + httpd:parse_query(BinSpec), + Id = {list_to_integer(Off), + list_to_integer(Size), + list_to_integer(Pos)}, + expand(Id,cdv_bin_wx,State); + "#Term?" ++ TermKeys -> + [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = + httpd:parse_query(TermKeys), + Id = {Tab,{list_to_integer(Key1), + list_to_integer(Key2), + list_to_integer(Key3)}}, + expand(Id,cdv_term_wx,State); + _ -> + cdv_virtual_list:start_detail_win(Target), + State + end, + {noreply, NewState}; + +handle_event(Event, State) -> + io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), + {noreply, State}. + +%%%----------------------------------------------------------------- +%%% Internal +expand(Id,Callback,#state{expand_wins=Opened0}=State) -> + Opened = + case lists:keyfind(Id,1,Opened0) of + false -> + EW = cdv_detail_win:start_link(Id,State#state.panel,Callback), + wx_object:get_pid(EW) ! active, + [{Id,EW}|Opened0]; + {_,EW} -> + wxFrame:raise(EW), + Opened0 + end, + State#state{expand_wins=Opened}. diff --git a/lib/observer/src/cdv_info_page.erl b/lib/observer/src/cdv_info_page.erl index b2f94fbc7f..27eb71225d 100644 --- a/lib/observer/src/cdv_info_page.erl +++ b/lib/observer/src/cdv_info_page.erl @@ -63,11 +63,18 @@ handle_info(active, State) -> crashdump_viewer_wx:set_status(State#state.trunc_warn), {noreply, State}; -handle_info(not_active, #state{} = State) -> - {noreply, State}; +handle_info(Info, State) -> + io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. -handle_info(new_dump, #state{callback=Callback,panel=Panel, - sizer=Sizer,fpanel=FPanel} = State) -> +code_change(_, _, State) -> + {ok, State}. + +handle_call(new_dump, _From, #state{callback=Callback,panel=Panel, + sizer=Sizer,fpanel=FPanel} = State) -> {InfoFields,Info,TW} = Callback:get_info(), NewFPanel = wx:batch( @@ -77,17 +84,7 @@ handle_info(new_dump, #state{callback=Callback,panel=Panel, wxSizer:layout(Sizer), FP end), - {noreply, State#state{fpanel=NewFPanel,trunc_warn=TW}}; - -handle_info(Info, State) -> - io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_, _, State) -> - {ok, State}. + {reply, ok, State#state{fpanel=NewFPanel,trunc_warn=TW}}; handle_call(Msg, _From, State) -> io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), @@ -97,6 +94,18 @@ handle_cast(Msg, State) -> io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), {noreply, State}. +handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) -> + cdv_virtual_list:start_detail_win(Target), + {noreply, State}; + +handle_event(#wx{obj=Obj,event=#wxMouse{type=enter_window}},State) -> + wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), + {noreply, State}; + +handle_event(#wx{obj=Obj,event=#wxMouse{type=leave_window}},State) -> + wxTextCtrl:setForegroundColour(Obj,?wxBLUE), + {noreply, State}; + handle_event(Event, State) -> io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), {noreply, State}. diff --git a/lib/observer/src/cdv_int_tab_wx.erl b/lib/observer/src/cdv_int_tab_wx.erl index f039bb3502..b4fa6ca889 100644 --- a/lib/observer/src/cdv_int_tab_wx.erl +++ b/lib/observer/src/cdv_int_tab_wx.erl @@ -23,9 +23,16 @@ -include("crashdump_viewer.hrl"). get_info() -> - [{"Hash Tables",cdv_table_page,get_hash_info()}, - {"Index Tables",cdv_table_page,get_index_info()}, - {"Internal ETS Tables",cdv_table_page,get_internal_ets_info()}]. + observer_lib:report_progress({ok,"Processing internal tables"}), + HashInfo = get_hash_info(), + observer_lib:report_progress({ok,33}), + IndexInfo = get_index_info(), + observer_lib:report_progress({ok,66}), + IntEtsInfo = get_internal_ets_info(), + observer_lib:report_progress({ok,100}), + [{"Hash Tables",cdv_table_page,HashInfo}, + {"Index Tables",cdv_table_page,IndexInfo}, + {"Internal ETS Tables",cdv_table_page,IntEtsInfo}]. %%%----------------------------------------------------------------- %%% Hash tables diff --git a/lib/observer/src/cdv_mem_wx.erl b/lib/observer/src/cdv_mem_wx.erl index 413c7de761..673795f39d 100644 --- a/lib/observer/src/cdv_mem_wx.erl +++ b/lib/observer/src/cdv_mem_wx.erl @@ -23,11 +23,17 @@ -include_lib("wx/include/wx.hrl"). get_info() -> + observer_lib:report_progress({ok,"Processing memory info"}), + MemInfo = get_mem_info(), + observer_lib:report_progress({ok,33}), {AllocInfo,AllocTW} = get_alloc_info(), - [{"Memory",cdv_info_page,get_mem_info()} + observer_lib:report_progress({ok,66}), + AreaInfo = get_area_info(), + observer_lib:report_progress({ok,100}), + [{"Memory",cdv_info_page,MemInfo} | [{Title,cdv_table_page,{Cols,Data,AllocTW}} || {Title,Cols,Data} <- AllocInfo]] ++ - [{"Allocated Areas",cdv_table_page,get_area_info()}]. + [{"Allocated Areas",cdv_table_page,AreaInfo}]. %%%----------------------------------------------------------------- @@ -74,5 +80,5 @@ fix_alloc([]) -> []. alloc_columns(Columns) -> - [{"", ?wxLIST_FORMAT_LEFT, 200} | - [{Column, ?wxLIST_FORMAT_RIGHT, 150} || Column <- Columns]]. + [{"", ?wxLIST_FORMAT_LEFT, 180} | + [{Column, ?wxLIST_FORMAT_RIGHT, 140} || Column <- Columns]]. diff --git a/lib/observer/src/cdv_mod_wx.erl b/lib/observer/src/cdv_mod_wx.erl index 601da1f4e8..8751651fec 100644 --- a/lib/observer/src/cdv_mod_wx.erl +++ b/lib/observer/src/cdv_mod_wx.erl @@ -59,33 +59,32 @@ get_details(Id) -> {ok,{Title,Proplist,TW}}. detail_pages() -> - [{simple, "General Information", fun init_gen_page/3}, - {simple, "Current Attributes", fun init_curr_attr_page/3}, - {simple, "Current Compilation Info", fun init_curr_comp_page/3}, - {simple, "Old Attributes", fun init_old_attr_page/3}, - {simple, "Old Compilation Info", fun init_old_comp_page/3}]. + [{"General Information", fun init_gen_page/2}, + {"Current Attributes", fun init_curr_attr_page/2}, + {"Current Compilation Info", fun init_curr_comp_page/2}, + {"Old Attributes", fun init_old_attr_page/2}, + {"Old Compilation Info", fun init_old_comp_page/2}]. -init_gen_page(Parent, _Id, Info) -> +init_gen_page(Parent, Info) -> Fields = info_fields(), - cdv_detail_win:init_detail_page(Parent, Fields, Info). + cdv_info_page:start_link(Parent,{Fields,Info,[]}). -init_curr_attr_page(Parent, _Id, Info) -> +init_curr_attr_page(Parent, Info) -> init_info_page(Parent, proplists:get_value(current_attrib,Info)). -init_curr_comp_page(Parent, _Id, Info) -> +init_curr_comp_page(Parent, Info) -> init_info_page(Parent, proplists:get_value(current_comp_info,Info)). -init_old_attr_page(Parent, _Id, Info) -> +init_old_attr_page(Parent, Info) -> init_info_page(Parent, proplists:get_value(old_attrib,Info)). -init_old_comp_page(Parent, _Id, Info) -> +init_old_comp_page(Parent, Info) -> init_info_page(Parent, proplists:get_value(old_comp_info,Info)). init_info_page(Parent, undefined) -> init_info_page(Parent, ""); init_info_page(Parent, String) -> - Html = crashdump_viewer_html:plain_page(String), - observer_lib:html_window(Parent,Html). + cdv_html_page:start_link(Parent,crashdump_viewer_html:plain_page(String)). format({Bin,q}) when is_binary(Bin) -> [$'|binary_to_list(Bin)]; diff --git a/lib/observer/src/cdv_multi_panel.erl b/lib/observer/src/cdv_multi_panel.erl index 3744863710..8218824ff2 100644 --- a/lib/observer/src/cdv_multi_panel.erl +++ b/lib/observer/src/cdv_multi_panel.erl @@ -40,13 +40,16 @@ dyn_page }). -start_link(Notebook, Callback) -> - wx_object:start_link(?MODULE, [Notebook, Callback], []). +start_link(Notebook, Info) -> + wx_object:start_link(?MODULE, [Notebook, Info], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, Callback]) -> +init([Notebook, Callback]) when is_atom(Callback) -> Pages = Callback:get_info(), + {MainPanel,State0} = init([Notebook, Pages]), + {MainPanel,State0#state{callback=Callback}}; +init([Notebook, Pages]) -> MainPanel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxHORIZONTAL), LeftMenuSizer = wxStaticBoxSizer:new(?wxVERTICAL,MainPanel, @@ -70,14 +73,14 @@ init([Notebook, Callback]) -> {proportion, 1}, {border, 5}]), wxPanel:setSizer(MainPanel, Sizer), - {MainPanel, #state{main_panel=MainPanel, - main_sizer=Sizer, - menu=LeftMenu, - menu_sizer=LeftMenuSizer, - callback=Callback, - pages=Pages, - dyn_panel=DynPanel - }}. + State = load_dyn_page(#state{main_panel=MainPanel, + main_sizer=Sizer, + menu=LeftMenu, + menu_sizer=LeftMenuSizer, + pages=Pages, + dyn_panel=DynPanel + }), + {MainPanel, State}. %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -89,17 +92,6 @@ handle_info(active, State) -> end), {noreply, NewState}; -handle_info(not_active, State) -> - {noreply, State}; - -handle_info(new_dump, State) -> - NewState = - wx:batch( - fun() -> - update_left_menu(State) - end), - {noreply, NewState}; - handle_info(Info, State) -> io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. @@ -110,12 +102,20 @@ terminate(_Reason, _State) -> code_change(_, _, State) -> {ok, State}. +handle_call(new_dump, _From, State) -> + NewState = + wx:batch( + fun() -> + update_left_menu(State) + end), + {reply, ok, NewState}; + handle_call(Msg, _From, State) -> - io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), + io:format("~p:~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), {reply, ok, State}. handle_cast(Msg, State) -> - io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), + io:format("~p:~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), {noreply, State}. handle_event(#wx{event=#wxCommand{type=command_listbox_selected, @@ -167,15 +167,13 @@ load_dyn_page(#state{main_sizer=MainSizer, dyn_panel=DynPanel, menu=Menu, pages=Pages} = State) -> - {Page,Sizer} = - wx:batch(fun() -> - wxWindow:freeze(DynPanel), - Name = wxListBox:getStringSelection(Menu), - Res = load_dyn_page(DynPanel,Name,Pages), - wxSizer:layout(MainSizer), - wxWindow:thaw(DynPanel), - Res - end), + %% Freeze and thaw causes a hang (and is not needed) on 2.9 and higher + DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9], + DoFreeze andalso wxWindow:freeze(DynPanel), + Name = wxListBox:getStringSelection(Menu), + {Page,Sizer} = load_dyn_page(DynPanel,Name,Pages), + wxSizer:layout(MainSizer), + DoFreeze andalso wxWindow:thaw(DynPanel), wx_object:get_pid(Page) ! active, State#state{dyn_page=Page,dyn_sizer=Sizer}. diff --git a/lib/observer/src/cdv_port_wx.erl b/lib/observer/src/cdv_port_wx.erl index ca591aeaaa..e34198ea0b 100644 --- a/lib/observer/src/cdv_port_wx.erl +++ b/lib/observer/src/cdv_port_wx.erl @@ -46,10 +46,10 @@ col_to_elem(?COL_CTRL) -> #port.controls; col_to_elem(?COL_SLOT) -> #port.slot. col_spec() -> - [{"Id", ?wxLIST_FORMAT_LEFT, 120}, + [{"Id", ?wxLIST_FORMAT_LEFT, 100}, {"Connected", ?wxLIST_FORMAT_LEFT, 120}, - {"Name", ?wxLIST_FORMAT_LEFT, 200}, - {"Controls", ?wxLIST_FORMAT_LEFT, 250}, + {"Name", ?wxLIST_FORMAT_LEFT, 150}, + {"Controls", ?wxLIST_FORMAT_LEFT, 200}, {"Slot", ?wxLIST_FORMAT_RIGHT, 50}]. get_info(_) -> @@ -77,11 +77,11 @@ get_details(Id) -> end. detail_pages() -> - [{simple, "General Information", fun init_gen_page/3}]. + [{"General Information", fun init_gen_page/2}]. -init_gen_page(Parent, _Id, Info) -> +init_gen_page(Parent, Info) -> Fields = info_fields(), - cdv_detail_win:init_detail_page(Parent, Fields, Info). + cdv_info_page:start_link(Parent,{Fields,Info,[]}). format({I1,I2}) -> "#Port<"++integer_to_list(I1) ++ "." ++ integer_to_list(I2) ++ ">"; @@ -94,7 +94,7 @@ format(D) -> info_fields() -> [{"Overview", [{"Name", name}, - {"Connected", connected}, + {"Connected", {click,connected}}, {"Slot", slot}, {"Controls", controls}]}, {scroll_boxes, diff --git a/lib/observer/src/cdv_proc_wx.erl b/lib/observer/src/cdv_proc_wx.erl index 1320afce28..11f83c79a2 100644 --- a/lib/observer/src/cdv_proc_wx.erl +++ b/lib/observer/src/cdv_proc_wx.erl @@ -48,9 +48,9 @@ col_spec() -> [{"Pid", ?wxLIST_FORMAT_CENTRE, 120}, {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 250}, {"State", ?wxLIST_FORMAT_LEFT, 100}, - {"Reds", ?wxLIST_FORMAT_RIGHT, 100}, - {"Memory", ?wxLIST_FORMAT_RIGHT, 100}, - {"MsgQ", ?wxLIST_FORMAT_RIGHT, 100}]. + {"Reds", ?wxLIST_FORMAT_RIGHT, 80}, + {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, + {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50}]. get_info(_) -> {ok,Info,TW} = crashdump_viewer:processes(), @@ -63,8 +63,13 @@ get_detail_cols(_) -> get_details(Id) -> case crashdump_viewer:proc_details(Id) of {ok,Info,TW} -> - Proplist = + %% The following table is used by crashdump_viewer_html + %% for storing expanded terms and it is read by + %% cdv_html_page when a link to an expandable term is clicked. + Tab = ets:new(cdv_expand,[set,public]), + Proplist0 = crashdump_viewer:to_proplist(record_info(fields,proc),Info), + Proplist = [{expand_table,Tab}|Proplist0], Title = io_lib:format("~s (~p)",[Info#proc.name, Id]), {ok,{Title,Proplist,TW}}; {error,{other_node,NodeId}} -> @@ -78,42 +83,38 @@ get_details(Id) -> end. detail_pages() -> - [{simple, "General Information", fun init_gen_page/3}, - {simple, "Messages", fun init_message_page/3}, - {simple, "Dictionary", fun init_dict_page/3}, - {simple, "Stack Dump", fun init_stack_page/3}, - {list, "ETS tables", fun init_ets_page/3}, - {list, "Timers", fun init_timer_page/3}]. - -init_gen_page(Parent, _Pid, Info) -> + [{"General Information", fun init_gen_page/2}, + {"Messages", fun init_message_page/2}, + {"Dictionary", fun init_dict_page/2}, + {"Stack Dump", fun init_stack_page/2}, + {"ETS tables", fun init_ets_page/2}, + {"Timers", fun init_timer_page/2}]. + +init_gen_page(Parent, Info) -> Fields = info_fields(), - cdv_detail_win:init_detail_page(Parent, Fields, Info). - -init_message_page(Parent, Pid, _Info) -> - init_memory_page(Parent, Pid, "MsgQueue"). - -init_dict_page(Parent, Pid, _Info) -> - init_memory_page(Parent, Pid, "Dictionary"). - -init_stack_page(Parent, Pid, _Info) -> - init_memory_page(Parent, Pid, "StackDump"). - -init_memory_page(Parent, Pid, What) -> - Win = observer_lib:html_window(Parent), - Html = - case crashdump_viewer:expand_memory(Pid,What) of - {ok,Memory} -> - crashdump_viewer_html:expanded_memory(What,Memory); - {error,Reason} -> - crashdump_viewer_html:warning(Reason) - end, - wxHtmlWindow:setPage(Win,Html), - Win. - -init_ets_page(Parent, Pid, _Info) -> + cdv_info_page:start_link(Parent,{Fields,Info,[]}). + +init_message_page(Parent, Info) -> + init_memory_page(Parent, Info, msg_q, "MsgQueue"). + +init_dict_page(Parent, Info) -> + init_memory_page(Parent, Info, dict, "Dictionary"). + +init_stack_page(Parent, Info) -> + init_memory_page(Parent, Info, stack_dump, "StackDump"). + +init_memory_page(Parent, Info0, Tag, Heading) -> + Info = proplists:get_value(Tag,Info0), + Tab = proplists:get_value(expand_table,Info0), + Html = crashdump_viewer_html:expandable_term(Heading,Info,Tab), + cdv_html_page:start_link(Parent,{expand,Html,Tab}). + +init_ets_page(Parent, Info) -> + Pid = proplists:get_value(pid,Info), cdv_virtual_list:start_link(Parent, cdv_ets_wx, Pid). -init_timer_page(Parent, Pid, _Info) -> +init_timer_page(Parent, Info) -> + Pid = proplists:get_value(pid,Info), cdv_virtual_list:start_link(Parent, cdv_timer_wx, Pid). %%%----------------------------------------------------------------- diff --git a/lib/observer/src/cdv_table_page.erl b/lib/observer/src/cdv_table_page.erl index fecc20b95c..71ec7686ce 100644 --- a/lib/observer/src/cdv_table_page.erl +++ b/lib/observer/src/cdv_table_page.erl @@ -72,9 +72,6 @@ handle_info(active, State) -> crashdump_viewer_wx:set_status(State#state.trunc_warn), {noreply, State}; -handle_info(not_active, #state{} = State) -> - {noreply, State}; - handle_info(Info, State) -> io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. diff --git a/lib/observer/src/cdv_term_wx.erl b/lib/observer/src/cdv_term_wx.erl new file mode 100644 index 0000000000..e029a75b29 --- /dev/null +++ b/lib/observer/src/cdv_term_wx.erl @@ -0,0 +1,66 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(cdv_term_wx). + +-export([get_details/1, + detail_pages/0]). + +%% Callbacks for cdv_detail_win +get_details({T,Key}) -> + [{Key,Term}] = ets:lookup(T,Key), + {ok,{"Expanded Term", Term, []}}. + +detail_pages() -> + [{"Term", fun init_term_page/2}]. + +init_term_page(ParentWin, Term) -> + cdv_multi_panel:start_link( + ParentWin, + [{"Format \~p",cdv_html_page,format_term_fun("~p",Term)}, + {"Format \~tp",cdv_html_page,format_term_fun("~tp",Term)}, + {"Format \~w",cdv_html_page,format_term_fun("~w",Term)}, + {"Format \~s",cdv_html_page,format_term_fun("~s",expand(Term))}, + {"Format \~ts",cdv_html_page,format_term_fun("~ts",expand(Term))}]). + +format_term_fun(Format,Term) -> + fun() -> + try io_lib:format(Format,[Term]) of + Str -> plain_html(Str) + catch error:badarg -> + Warning = "This term can not be formatted with " ++ Format, + crashdump_viewer_html:warning(Warning) + end + end. + +plain_html(Text) -> + crashdump_viewer_html:plain_page(Text). + +expand(['#CDVBin',Offset,Size,Pos]) -> + {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), + Bin; +expand([H|T]) -> + case expand(T) of + ET when is_list(ET) -> + [expand(H)|ET]; + ET -> % The tail is an expanded binary - cannot append with | + [expand(H),ET] + end; +expand(Tuple) when is_tuple(Tuple) -> + list_to_tuple(expand(tuple_to_list(Tuple))); +expand(Term) -> + Term. diff --git a/lib/observer/src/cdv_timer_wx.erl b/lib/observer/src/cdv_timer_wx.erl index 0bfd958a15..2bd250a46c 100644 --- a/lib/observer/src/cdv_timer_wx.erl +++ b/lib/observer/src/cdv_timer_wx.erl @@ -37,9 +37,9 @@ col_to_elem(?COL_MSG) -> #timer.msg; col_to_elem(?COL_TIME) -> #timer.time. col_spec() -> - [{"Owner", ?wxLIST_FORMAT_LEFT, 150}, - {"Message", ?wxLIST_FORMAT_LEFT, 500}, - {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 150}]. + [{"Owner", ?wxLIST_FORMAT_LEFT, 110}, + {"Message", ?wxLIST_FORMAT_LEFT, 400}, + {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 80}]. get_info(Owner) -> {ok,Info,TW} = crashdump_viewer:timers(Owner), diff --git a/lib/observer/src/cdv_virtual_list.erl b/lib/observer/src/cdv_virtual_list.erl index 03feadad45..5897b9d02a 100644 --- a/lib/observer/src/cdv_virtual_list.erl +++ b/lib/observer/src/cdv_virtual_list.erl @@ -162,7 +162,7 @@ do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened, NewOpened = case lists:keyfind(Id, 1, Opened) of false -> - case cdv_detail_win:start(Id, Panel, Callback) of + case cdv_detail_win:start_link(Id, Panel, Callback) of {error, _} -> Opened; IW -> @@ -197,31 +197,12 @@ call(_,_) -> handle_info({holder_updated, Count}, State=#state{grid=Grid}) -> wxListCtrl:setItemCount(Grid, Count), Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1), - {noreply, State}; -handle_info({detail_win_closed, Id},#state{detail_wins=Opened}=State) -> - Opened2 = lists:keydelete(Id, 1, Opened), - {noreply, State#state{detail_wins=Opened2}}; - handle_info(active, State) -> crashdump_viewer_wx:set_status(State#state.trunc_warn), {noreply, State}; -handle_info(not_active, State) -> - {noreply, State}; - -handle_info(new_dump, - #state{grid=Grid,detail_wins=Opened, - holder=Holder,callback=Callback}=State) -> - lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened), - wxListCtrl:deleteAllItems(Grid), - Ref = erlang:monitor(process,Holder), - Holder ! stop, - receive {'DOWN',Ref,_,_,_} -> ok end, - {NewHolder,TW} = spawn_table_holder(Callback, all), - {noreply, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; - handle_info(Info, State) -> io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. @@ -233,6 +214,17 @@ terminate(_Reason, #state{holder=Holder}) -> code_change(_, _, State) -> {ok, State}. +handle_call(new_dump, _From, + #state{grid=Grid,detail_wins=Opened, + holder=Holder,callback=Callback}=State) -> + lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened), + wxListCtrl:deleteAllItems(Grid), + Ref = erlang:monitor(process,Holder), + Holder ! stop, + receive {'DOWN',Ref,_,_,_} -> ok end, + {NewHolder,TW} = spawn_table_holder(Callback, all), + {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; + handle_call(Msg, _From, State) -> io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), {reply, ok, State}. @@ -241,6 +233,10 @@ handle_cast({start_detail_win,Id}, State) -> State2 = do_start_detail_win(Id, State), {noreply, State2}; +handle_cast({detail_win_closed, Id},#state{detail_wins=Opened}=State) -> + Opened2 = lists:keydelete(Id, 1, Opened), + {noreply, State#state{detail_wins=Opened2}}; + handle_cast(Msg, State) -> io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), {noreply, State}. @@ -333,16 +329,17 @@ spawn_table_holder(Callback, Owner) -> end, {Holder,TW}. -init_table_holder(Parent, Attrs, Callback, Info0) -> - Info = array:from_list(Info0), - S = handle_update(Info, #holder{parent=Parent, - info=Info, - sort=#sort{sort_key= - Callback:col_to_elem(id)}, - attrs=Attrs, - callback=Callback - }), - table_holder(S). +init_table_holder(Parent, Attrs, Callback, InfoList0) -> + Sort = #sort{sort_key=Callback:col_to_elem(id)}, + {_Sort, InfoList} = do_sort(Sort,InfoList0), + Info = array:from_list(InfoList), + NRows = array:size(Info), + Parent ! {holder_updated, NRows}, + table_holder(#holder{parent=Parent, + info=Info, + sort=Sort, + attrs=Attrs, + callback=Callback}). table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> receive @@ -373,14 +370,6 @@ change_sort(Col, S0=#holder{parent=Parent, info=Info0, sort=Sort0}) -> Parent ! {holder_updated, NRows}, S0#holder{info=Info, last_row=undefined, sort=Sort}. -handle_update(Info0, S0=#holder{parent=Parent, sort=Sort}) -> - NRows = array:size(Info0), - InfoList0 = array:to_list(Info0), - {_Sort, InfoList} = do_sort(Sort, InfoList0), - Info = array:from_list(InfoList), - Parent ! {holder_updated, NRows}, - S0#holder{info=Info, last_row=undefined}. - sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) -> do_sort(Opt#sort{sort_incr=not Bool}, Table); sort(Col, Sort,Table) -> diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index bd3d4cc72b..ca91f9061a 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -42,10 +42,9 @@ -export([start/0,start/1,stop/0,script_start/0,script_start/1]). %% GUI API --export([start_link/0, - get_progress/0]). +-export([start_link/0]). -export([read_file/1, - init_general_info/0, + general_info/0, processes/0, proc_details/1, port/1, @@ -64,8 +63,7 @@ allocator_info/0, hash_tables/0, index_tables/0, - expand_binary/1, - expand_memory/2]). + expand_binary/1]). %% Library function -export([to_proplist/2, to_value_list/1]). @@ -86,8 +84,6 @@ -define(chunk_size,1000). % number of bytes read from crashdump at a time -define(max_line_size,100). % max number of bytes (i.e. characters) the % line_head/1 function can return --define(max_display_binary_size,50). % max size of a binary that will be - % directly displayed. -define(not_available,"N/A"). @@ -233,13 +229,13 @@ start_link() -> %%%----------------------------------------------------------------- %%% Called by crashdump_viewer_wx read_file(File) -> - cast({read_file,File,self()}). + cast({read_file,File}). %%%----------------------------------------------------------------- %%% The following functions are called when the different tabs are %%% created -init_general_info() -> - call(init_general_info). +general_info() -> + call(general_info). processes() -> call(procs_summary). ports() -> @@ -283,27 +279,12 @@ proc_details(Pid) -> port(Id) -> call({port,Id}). -%%%----------------------------------------------------------------- -%%% Stack dump, message queue and dictionary tabs in process detail -%%% window -expand_memory(Pid,What) -> - call({expand_memory,Pid,What}). - %%%----------------------------------------------------------------- %%% Called when "<< xxx bytes>>" link is clicket to open a new window %%% displaying the whole binary. expand_binary(Pos) -> call({expand_binary,Pos}). -%%%----------------------------------------------------------------- -%%% Wait for a progress report when reading/processing crash dump. -%%% Called from crashdump_viewer_wx. -get_progress() -> - receive - {progress,Status} -> - Status - end. - %%==================================================================== %% Server functions %%==================================================================== @@ -330,50 +311,34 @@ init([]) -> %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_call(init_general_info,_From,State=#state{file=File}) -> +handle_call(general_info,_From,State=#state{file=File}) -> GenInfo = general_info(File), - [{DumpVsn,_}] = lookup_index(?erl_crash_dump), NumAtoms = GenInfo#general_info.num_atoms, WS = parse_vsn_str(GenInfo#general_info.system_vsn,4), - NewState = State#state{dump_vsn=[list_to_integer(L) || - L<-string:tokens(DumpVsn,".")], - wordsize=WS, - num_atoms=NumAtoms}, - {reply,{ok,GenInfo},NewState}; -handle_call({expand_memory,Pid,What},_From,State=#state{file=File,binaries=B}) -> - Reply = - case truncated_warning([{?proc,Pid}]) of - [] -> - {ok,expand_memory(File,What,Pid,B)}; - _TW -> - Info = - "The crashdump is truncated in the middle of this " - "process' memory information, so this information " - "can not be extracted.", - {error,Info} - end, - {reply,Reply,State}; -handle_call({expand_binary,Pos0},_From,State=#state{file=File}) -> - Pos = list_to_integer(Pos0), + TW = case get(truncated) of + true -> ["WARNING: The crash dump is truncated. " + "Some information might be missing."]; + false -> [] + end, + {reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}}; +handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) -> Fd = open(File), pos_bof(Fd,Pos), - {Bin,_Line} = get_binary(val(Fd)), + {Bin,_Line} = get_binary(Offset,Size,val(Fd)), close(Fd), {reply,{ok,Bin},State}; handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) -> TW = truncated_warning([?proc]), Procs = procs_summary(File,WS), {reply,{ok,Procs,TW},State}; -handle_call({proc_details,Pid},_From,State=#state{file=File,wordsize=WS})-> +handle_call({proc_details,Pid},_From, + State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn,binaries=B})-> Reply = - case get_proc_details(File,Pid,State#state.dump_vsn,WS) of - {ok,Proc} -> - TW = truncated_warning([{?proc,Pid}]), + case get_proc_details(File,Pid,WS,DumpVsn,B) of + {ok,Proc,TW} -> {ok,Proc,TW}; - {other_node,Node} -> - {error,{other_node,Node}}; - not_found -> - {error,not_found} + Other -> + {error,Other} end, {reply, Reply, State}; handle_call({port,Id},_From,State=#state{file=File}) -> @@ -469,13 +434,13 @@ handle_call(index_tables,_From,State=#state{file=File}) -> %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_cast({read_file,File,ProgressReceiver}, _State) -> - case do_read_file(File,ProgressReceiver) of - {ok,Binaries} -> - report_progress(ProgressReceiver,{ok,done}), - {noreply, #state{file=File,binaries=Binaries}}; +handle_cast({read_file,File}, _State) -> + case do_read_file(File) of + {ok,Binaries,DumpVsn} -> + observer_lib:report_progress({ok,done}), + {noreply, #state{file=File,binaries=Binaries,dump_vsn=DumpVsn}}; Error -> - report_progress(ProgressReceiver,Error), + end_progress(Error), {noreply, #state{}} end; handle_cast(stop,State) -> @@ -565,10 +530,6 @@ compare_pid("<"++Id,"<"++OtherId) -> compare_pid(_,_) -> false. -report_progress(Receiver,Progress) -> - Receiver ! {progress,Progress}, - ok. - open(File) -> {ok,Fd} = file:open(File,[read,read_ahead,raw,binary]), Fd. @@ -623,22 +584,14 @@ get_chunk(Fd) -> %% Read and report progress progress_read(Fd) -> - {R,RBytes} = + {R,Bytes} = case read(Fd) of {ok,Bin} -> {{ok,Bin},size(Bin)}; Other -> {Other,0} end, - {Receiver,Bytes,Size} = get(progress), - Bytes1 = Bytes + RBytes, - Percent0 = (100*Bytes) div Size, - Percent = (100*Bytes1) div Size, - if Percent > Percent0 -> - report_progress(Receiver,{ok,Percent}); - true -> ok - end, - put(progress,{Receiver,Bytes1,Size}), + update_progress(Bytes), R. read(Fd) -> @@ -801,14 +754,13 @@ parse_vsn_str(Str,WS) -> %%% %%% Progress is reported during the time and MUST be checked with %%% crashdump_viewer:get_progress/0 until it returns {ok,done}. -do_read_file(File,ProgressReceiver) -> +do_read_file(File) -> case file:read_file_info(File) of {ok,#file_info{type=regular, access=FileA, size=Size}} when FileA=:=read; FileA=:=read_write -> Fd = open(File), - report_progress(ProgressReceiver,{ok,"Reading file"}), - put(progress,{ProgressReceiver,0,Size}), + init_progress("Reading file",Size), case progress_read(Fd) of {ok,<<$=:8,TagAndRest/binary>>} -> {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1), @@ -818,11 +770,14 @@ do_read_file(File,ProgressReceiver) -> insert_index(Tag,Id,N1+1), put(last_tag,{Tag,""}), indexify(Fd,Rest,N1), - erase(progress), + end_progress(), check_if_truncated(), - Binaries = read_binaries(Fd,ProgressReceiver), + [{DumpVsn0,_}] = lookup_index(?erl_crash_dump), + DumpVsn = [list_to_integer(L) || + L<-string:tokens(DumpVsn0,".")], + Binaries = read_binaries(Fd,DumpVsn), close(Fd), - {ok,Binaries}; + {ok,Binaries,DumpVsn}; _Other -> R = io_lib:format( "~s is not an Erlang crash dump~n", @@ -1059,49 +1014,30 @@ procs_summary(File,WS) -> _ -> Proc end end, - lookup_and_parse_index(File,?proc,ParseFun). + lookup_and_parse_index(File,?proc,ParseFun,"processes"). %%----------------------------------------------------------------- %% Page with one process -get_proc_details(File,Pid,DumpVsn,WS) -> +get_proc_details(File,Pid,WS,DumpVsn,Binaries) -> case lookup_index(?proc,Pid) of [{_,Start}] -> Fd = open(File), - pos_bof(Fd,Start), - Proc0 = - case DumpVsn of - [0,0] -> - %% Old version (translated) - #proc{pid=Pid}; - _ -> - #proc{pid=Pid, - stack_dump=if_exist(?proc_stack,Pid), - msg_q=if_exist(?proc_messages,Pid), - dict=if_exist(?proc_dictionary,Pid)} + {{Stack,MsgQ,Dict},TW} = + case truncated_warning([{?proc,Pid}]) of + [] -> + {expand_memory(Fd,Pid,DumpVsn,Binaries),[]}; + TW0 -> + {{[],[],[]},TW0} end, + pos_bof(Fd,Start), + Proc0 = #proc{pid=Pid,stack_dump=Stack,msg_q=MsgQ,dict=Dict}, Proc = get_procinfo(Fd,fun all_procinfo/5,Proc0,WS), close(Fd), - {ok,Proc}; + {ok,Proc,TW}; _ -> maybe_other_node(Pid) end. -if_exist(Tag,Key) -> - case count_index(Tag,Key) of - 0 -> - Tag1 = - case is_proc_tag(Tag) of - true -> ?proc; - false -> Tag - end, - case truncated_here({Tag1,Key}) of - true -> truncated; - false -> undefined - end; - _ -> - expand - end. - get_procinfo(Fd,Fun,Proc,WS) -> case line_head(Fd) of "State" -> @@ -1141,11 +1077,7 @@ get_procinfo(Fd,Fun,Proc,WS) -> main_procinfo(Fd,Fun,Proc,WS,LineHead) -> case LineHead of - "Stack dump" -> - %% This is the last element in older dumps (DumpVsn=0.0) - Proc; "=" ++ _next_tag -> - %% DumpVsn=0.1 or newer: No stack dump here Proc; "arity = " ++ _ -> %%! Temporary workaround @@ -1295,97 +1227,70 @@ maybe_other_node(Id) -> end. -expand_memory(File,What,Pid,Binaries) -> - Fd = open(File), +expand_memory(Fd,Pid,DumpVsn,Binaries) -> + BinAddrAdj = get_bin_addr_adj(DumpVsn), put(fd,Fd), - Dict = read_heap(Fd,Pid,Binaries), - Expanded = - case What of - "StackDump" -> read_stack_dump(Fd,Pid,Dict); - "MsgQueue" -> read_messages(Fd,Pid,Dict); - "Dictionary" -> read_dictionary(Fd,?proc_dictionary,Pid,Dict) - end, + Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries), + Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict), + read_messages(Fd,Pid,BinAddrAdj,Dict), + read_dictionary(Fd,Pid,BinAddrAdj,Dict)}, erase(fd), - close(Fd), Expanded. - + +%%%----------------------------------------------------------------- +%%% This is a workaround for a bug in dump versions prior to 0.3: +%%% Addresses were truncated to 32 bits. This could cause binaries to +%%% get the same address as heap terms in the dump. To work around it +%%% we always store binaries on very high addresses in the gb_tree. +get_bin_addr_adj(DumpVsn) when DumpVsn < [0,3] -> + 16#f bsl 64; +get_bin_addr_adj(_) -> + 0. + %%% %%% Read binaries. %%% -read_binaries(Fd,ProgressReceiver) -> +read_binaries(Fd,DumpVsn) -> AllBinaries = lookup_index(?binary), - NumBinaries = length(AllBinaries), - ReportInterval = (NumBinaries div 100) + 1, - report_progress(ProgressReceiver,{ok,"Processing binaries"}), - read_binaries(Fd, AllBinaries, gb_trees:empty(), - ProgressReceiver,ReportInterval,ReportInterval,0). - -read_binaries(Fd,Bins,Dict,Receiver,0,ReportInterval,Percent0) -> - Percent = Percent0+1, - report_progress(Receiver,{ok,Percent}), - read_binaries(Fd,Bins,Dict,Receiver,ReportInterval,ReportInterval,Percent); -read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0,Receiver,Count,ReportInterval,Percent) -> - pos_bof(Fd,Pos), - {Addr,_} = get_hex(Addr0), - Dict = - case line_head(Fd) of - {eof,_} -> - gb_trees:enter(Addr,'#CDVTruncatedBinary',Dict0); - Size0 -> - {Size,_} = get_hex(Size0), - if Size > ?max_display_binary_size -> - gb_trees:enter(Addr,{'#CDVTooBig',binary,Pos},Dict0); - true -> - pos_bof(Fd,Pos), - Line = val(Fd), - parse_binary(Addr,Line,Dict0) - end - end, - read_binaries(Fd,Bins,Dict,Receiver,Count-1,ReportInterval,Percent); -read_binaries(_Fd,[],Dict,Receiver,_Count,_ReportInterval,_Percent) -> - report_progress(Receiver,{ok,100}), - Dict. - -parse_binary(Addr, Line0, Dict) -> - case get_hex(Line0) of - {N,":"++Line1} -> - {Bin,Line} = get_binary(N, Line1, []), - [] = skip_blanks(Line), - gb_trees:enter(Addr, Bin, Dict); - {_N,[]} -> - %% If the dump is truncated before the ':' in this line, then - %% line_head/1 might not discover it (if a \n has been inserted - %% somehow???) - gb_trees:enter(Addr,'#CDVTruncatedBinary',Dict) - end. - - + AddrAdj = get_bin_addr_adj(DumpVsn), + Fun = fun({Addr0,Pos},Dict0) -> + pos_bof(Fd,Pos), + {HexAddr,_} = get_hex(Addr0), + Addr = HexAddr bor AddrAdj, + Bin = + case line_head(Fd) of + {eof,_} -> '#CDVTruncatedBinary'; + _Size -> {'#CDVBin',Pos} + end, + gb_trees:enter(Addr,Bin,Dict0) + end, + progress_foldl("Processing binaries",Fun,gb_trees:empty(),AllBinaries). %%% %%% Read top level section. %%% -read_stack_dump(Fd,Pid,Dict) -> +read_stack_dump(Fd,Pid,BinAddrAdj,Dict) -> case lookup_index(?proc_stack,Pid) of [{_,Start}] -> pos_bof(Fd,Start), - read_stack_dump1(Fd,Dict,[]); + read_stack_dump1(Fd,BinAddrAdj,Dict,[]); [] -> [] end. -read_stack_dump1(Fd,Dict,Acc) -> +read_stack_dump1(Fd,BinAddrAdj,Dict,Acc) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case val(Fd) of "=" ++ _next_tag -> lists:reverse(Acc); Line -> - Stack = parse_top(Line,Dict), - read_stack_dump1(Fd,Dict,[Stack|Acc]) + Stack = parse_top(Line,BinAddrAdj,Dict), + read_stack_dump1(Fd,BinAddrAdj,Dict,[Stack|Acc]) end. -parse_top(Line0, D) -> +parse_top(Line0, BinAddrAdj, D) -> {Label,Line1} = get_label(Line0), - {Term,Line,D} = parse_term(Line1, D), + {Term,Line,D} = parse_term(Line1, BinAddrAdj, D), [] = skip_blanks(Line), {Label,Term}. @@ -1393,27 +1298,27 @@ parse_top(Line0, D) -> %%% Read message queue. %%% -read_messages(Fd,Pid,Dict) -> +read_messages(Fd,Pid,BinAddrAdj,Dict) -> case lookup_index(?proc_messages,Pid) of [{_,Start}] -> pos_bof(Fd,Start), - read_messages1(Fd,Dict,[]); + read_messages1(Fd,BinAddrAdj,Dict,[]); [] -> [] end. -read_messages1(Fd,Dict,Acc) -> +read_messages1(Fd,BinAddrAdj,Dict,Acc) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case val(Fd) of "=" ++ _next_tag -> lists:reverse(Acc); Line -> - Msg = parse_message(Line,Dict), - read_messages1(Fd,Dict,[Msg|Acc]) + Msg = parse_message(Line,BinAddrAdj,Dict), + read_messages1(Fd,BinAddrAdj,Dict,[Msg|Acc]) end. -parse_message(Line0, D) -> - {Msg,":"++Line1,_} = parse_term(Line0, D), - {Token,Line,_} = parse_term(Line1, D), +parse_message(Line0, BinAddrAdj, D) -> + {Msg,":"++Line1,_} = parse_term(Line0, BinAddrAdj, D), + {Token,Line,_} = parse_term(Line1, BinAddrAdj, D), [] = skip_blanks(Line), {Msg,Token}. @@ -1421,26 +1326,26 @@ parse_message(Line0, D) -> %%% Read process dictionary %%% -read_dictionary(Fd,Tag,Pid,Dict) -> - case lookup_index(Tag,Pid) of +read_dictionary(Fd,Pid,BinAddrAdj,Dict) -> + case lookup_index(?proc_dictionary,Pid) of [{_,Start}] -> pos_bof(Fd,Start), - read_dictionary1(Fd,Dict,[]); + read_dictionary1(Fd,BinAddrAdj,Dict,[]); [] -> [] end. -read_dictionary1(Fd,Dict,Acc) -> +read_dictionary1(Fd,BinAddrAdj,Dict,Acc) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case val(Fd) of "=" ++ _next_tag -> lists:reverse(Acc); Line -> - Msg = parse_dictionary(Line,Dict), - read_dictionary1(Fd,Dict,[Msg|Acc]) + Msg = parse_dictionary(Line,BinAddrAdj,Dict), + read_dictionary1(Fd,BinAddrAdj,Dict,[Msg|Acc]) end. -parse_dictionary(Line0, D) -> - {Entry,Line,_} = parse_term(Line0, D), +parse_dictionary(Line0, BinAddrAdj, D) -> + {Entry,Line,_} = parse_term(Line0, BinAddrAdj, D), [] = skip_blanks(Line), Entry. @@ -1448,16 +1353,16 @@ parse_dictionary(Line0, D) -> %%% Read heap data. %%% -read_heap(Fd,Pid,Dict0) -> +read_heap(Fd,Pid,BinAddrAdj,Dict0) -> case lookup_index(?proc_heap,Pid) of [{_,Pos}] -> pos_bof(Fd,Pos), - read_heap(Dict0); + read_heap(BinAddrAdj,Dict0); [] -> Dict0 end. -read_heap(Dict0) -> +read_heap(BinAddrAdj,Dict0) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case get(fd) of end_of_heap -> @@ -1468,14 +1373,14 @@ read_heap(Dict0) -> put(fd, end_of_heap), Dict0; Line -> - Dict = parse(Line,Dict0), - read_heap(Dict) + Dict = parse(Line,BinAddrAdj,Dict0), + read_heap(BinAddrAdj,Dict) end end. -parse(Line0, Dict0) -> +parse(Line0, BinAddrAdj, Dict0) -> {Addr,":"++Line1} = get_hex(Line0), - {_Term,Line,Dict} = parse_heap_term(Line1, Addr, Dict0), + {_Term,Line,Dict} = parse_heap_term(Line1, Addr, BinAddrAdj, Dict0), [] = skip_blanks(Line), Dict. @@ -1498,7 +1403,7 @@ get_port(File,Port) -> %% Page with all ports get_ports(File) -> ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=port_to_tuple(Id)}) end, - lookup_and_parse_index(File,?port,ParseFun). + lookup_and_parse_index(File,?port,ParseFun,"ports"). %% Converting port string to tuple to secure correct sorting. This is %% converted back in cdv_port_wx:format/1. @@ -1566,7 +1471,7 @@ get_ets_tables(File,Pid,WS) -> ParseFun = fun(Fd,Id) -> get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS) end, - lookup_and_parse_index(File,{?ets,Pid},ParseFun). + lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets"). get_etsinfo(Fd,EtsTable,WS) -> case line_head(Fd) of @@ -1619,7 +1524,7 @@ get_internal_ets_tables(File,WS) -> %% Page with list of all timers get_timers(File,Pid) -> ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=list_to_pid(Id)}) end, - lookup_and_parse_index(File,{?timer,Pid},ParseFun). + lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"). get_timerinfo_1(Fd,Timer) -> case line_head(Fd) of @@ -1758,7 +1663,7 @@ loaded_mods(File) -> [] -> {"unknown","unknown"} end, - {CC,OC,lookup_and_parse_index(File,?mod,ParseFun)}. + {CC,OC,lookup_and_parse_index(File,?mod,ParseFun,"modules")}. get_loaded_mod_totals(Fd,{CC,OC}) -> case line_head(Fd) of @@ -1845,7 +1750,7 @@ hex_to_dec(N) -> list_to_integer(N). %% Page with list of all funs funs(File) -> ParseFun = fun(Fd,_Id) -> get_funinfo(Fd,#fu{}) end, - lookup_and_parse_index(File,?fu,ParseFun). + lookup_and_parse_index(File,?fu,ParseFun,"funs"). get_funinfo(Fd,Fu) -> case line_head(Fd) of @@ -1883,6 +1788,7 @@ atoms(File,NumAtoms) -> get_atoms(Fd,NumAtoms) -> case get_chunk(Fd) of {ok,Bin} -> + init_progress("Processing atoms",NumAtoms), get_atoms(Fd,Bin,NumAtoms,[]); eof -> [] @@ -1896,18 +1802,20 @@ get_atoms(Fd,Bin,NumAtoms,Atoms) -> get_atoms1(Fd,Bins,NumAtoms,Atoms). get_atoms1(_Fd,[<<"=",_/binary>>|_],_N,Atoms) -> + end_progress(), Atoms; get_atoms1(Fd,[LastBin],N,Atoms) -> case get_chunk(Fd) of {ok,Bin0} -> get_atoms(Fd,<>,N,Atoms); eof -> - Atoms + end_progress(), + [{N,get_atom(LastBin)}|Atoms] end; get_atoms1(Fd,[Bin|Bins],N,Atoms) -> + update_progress(), get_atoms1(Fd,Bins,N-1,[{N,get_atom(Bin)}|Atoms]). - %% This ensures sorting according to first actual letter in the atom, %% disregarding possible single quote. It is formatted back to correct %% syntax in cdv_atom_wx:format/1 @@ -2309,113 +2217,117 @@ get_indextableinfo1(Fd,IndexTable) -> %%%----------------------------------------------------------------- %%% Parse memory in crashdump version 0.1 and newer %%% -parse_heap_term([$l|Line0], Addr, D0) -> %Cons cell. - {H,"|"++Line1,D1} = parse_term(Line0, D0), - {T,Line,D2} = parse_term(Line1, D1), +parse_heap_term([$l|Line0], Addr, BinAddrAdj, D0) -> %Cons cell. + {H,"|"++Line1,D1} = parse_term(Line0, BinAddrAdj, D0), + {T,Line,D2} = parse_term(Line1, BinAddrAdj, D1), Term = [H|T], D = gb_trees:insert(Addr, Term, D2), {Term,Line,D}; -parse_heap_term([$t|Line0], Addr, D) -> %Tuple +parse_heap_term([$t|Line0], Addr, BinAddrAdj, D) -> %Tuple {N,":"++Line} = get_hex(Line0), - parse_tuple(N, Line, Addr, D, []); -parse_heap_term([$F|Line0], Addr, D0) -> %Float + parse_tuple(N, Line, Addr, BinAddrAdj, D, []); +parse_heap_term([$F|Line0], Addr, _BinAddrAdj, D0) -> %Float {N,":"++Line1} = get_hex(Line0), {Chars,Line} = get_chars(N, Line1), Term = list_to_float(Chars), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("B16#"++Line0, Addr, D0) -> %Positive big number. +parse_heap_term("B16#"++Line0, Addr, _BinAddrAdj, D0) -> %Positive big number. {Term,Line} = get_hex(Line0), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("B-16#"++Line0, Addr, D0) -> %Negative big number +parse_heap_term("B-16#"++Line0, Addr, _BinAddrAdj, D0) -> %Negative big number {Term0,Line} = get_hex(Line0), Term = -Term0, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("B"++Line0, Addr, D0) -> %Decimal big num (new in R10B-something). +parse_heap_term("B"++Line0, Addr, _BinAddrAdj, D0) -> %Decimal big num case string:to_integer(Line0) of {Int,Line} when is_integer(Int) -> D = gb_trees:insert(Addr, Int, D0), {Int,Line,D} end; -parse_heap_term([$P|Line0], Addr, D0) -> % External Pid. +parse_heap_term([$P|Line0], Addr, _BinAddrAdj, D0) -> % External Pid. {Pid0,Line} = get_id(Line0), - Pid = "#CDVPid"++Pid0, + Pid = ['#CDVPid'|Pid0], D = gb_trees:insert(Addr, Pid, D0), {Pid,Line,D}; -parse_heap_term([$p|Line0], Addr, D0) -> % External Port. +parse_heap_term([$p|Line0], Addr, _BinAddrAdj, D0) -> % External Port. {Port0,Line} = get_id(Line0), - Port = "#CDVPort"++Port0, + Port = ['#CDVPort'|Port0], D = gb_trees:insert(Addr, Port, D0), {Port,Line,D}; -parse_heap_term("E"++Line0, Addr, D0) -> %Term encoded in external format. +parse_heap_term("E"++Line0, Addr, _BinAddrAdj, D0) -> %Term encoded in external format. {Bin,Line} = get_binary(Line0), Term = binary_to_term(Bin), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("Yh"++Line0, Addr, D0) -> %Heap binary. +parse_heap_term("Yh"++Line0, Addr, _BinAddrAdj, D0) -> %Heap binary. {Term,Line} = get_binary(Line0), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("Yc"++Line0, Addr, D0) -> %Reference-counted binary. - {Binp,":"++Line1} = get_hex(Line0), - {First,":"++Line2} = get_hex(Line1), +parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) -> %Reference-counted binary. + {Binp0,":"++Line1} = get_hex(Line0), + {Offset,":"++Line2} = get_hex(Line1), {Sz,Line} = get_hex(Line2), + Binp = Binp0 bor BinAddrAdj, Term = case gb_trees:lookup(Binp, D0) of - {value,<<_:First/binary,T:Sz/binary,_/binary>>} -> T; - {value,{'#CDVTooBig',binary,Pos}} -> cdvbin(Sz,Pos); - {value,'#CDVTruncatedBinary'} -> '#CDVTruncatedBinary'; + {value,Bin} -> cdvbin(Offset,Sz,Bin); none -> '#CDVNonexistingBinary' end, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("Ys"++Line0, Addr, D0) -> %Sub binary. - {Binp,":"++Line1} = get_hex(Line0), - {First,":"++Line2} = get_hex(Line1), +parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) -> %Sub binary. + {Binp0,":"++Line1} = get_hex(Line0), + {Offset,":"++Line2} = get_hex(Line1), {Sz,Line} = get_hex(Line2), + Binp = Binp0 bor BinAddrAdj, Term = case gb_trees:lookup(Binp, D0) of - {value,<<_:First/binary,T:Sz/binary,_/binary>>} -> T; - {value,{'#CDVTooBig',binary,Pos}} -> cdvbin(Sz,Pos); - {value,'#CDVTruncatedBinary'} -> '#CDVTruncatedBinary'; + {value,Bin} -> cdvbin(Offset,Sz,Bin); + none when Binp0=/=Binp -> + %% Might it be on the heap? + case gb_trees:lookup(Binp0, D0) of + {value,Bin} -> cdvbin(Offset,Sz,Bin); + none -> '#CDVNonexistingBinary' + end; none -> '#CDVNonexistingBinary' end, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}. -parse_tuple(0, Line, Addr, D0, Acc) -> +parse_tuple(0, Line, Addr, _, D0, Acc) -> Tuple = list_to_tuple(lists:reverse(Acc)), D = gb_trees:insert(Addr, Tuple, D0), {Tuple,Line,D}; -parse_tuple(N, Line0, Addr, D0, Acc) -> - case parse_term(Line0, D0) of +parse_tuple(N, Line0, Addr, BinAddrAdj, D0, Acc) -> + case parse_term(Line0, BinAddrAdj, D0) of {Term,[$,|Line],D} when N > 1 -> - parse_tuple(N-1, Line, Addr, D, [Term|Acc]); + parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc]); {Term,Line,D}-> - parse_tuple(N-1, Line, Addr, D, [Term|Acc]) + parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc]) end. -parse_term([$H|Line0], D) -> %Pointer to heap term. +parse_term([$H|Line0], BinAddrAdj, D) -> %Pointer to heap term. {Ptr,Line} = get_hex(Line0), - deref_ptr(Ptr, Line, D); -parse_term([$N|Line], D) -> %[] (nil). + deref_ptr(Ptr, Line, BinAddrAdj, D); +parse_term([$N|Line], _, D) -> %[] (nil). {[],Line,D}; -parse_term([$I|Line0], D) -> %Small. +parse_term([$I|Line0], _, D) -> %Small. {Int,Line} = string:to_integer(Line0), {Int,Line,D}; -parse_term([$A|_]=Line, D) -> %Atom. +parse_term([$A|_]=Line, _, D) -> %Atom. parse_atom(Line, D); -parse_term([$P|Line0], D) -> %Pid. +parse_term([$P|Line0], _, D) -> %Pid. {Pid,Line} = get_id(Line0), - {"#CDVPid"++Pid,Line,D}; -parse_term([$p|Line0], D) -> %Port. + {['#CDVPid'|Pid],Line,D}; +parse_term([$p|Line0], _, D) -> %Port. {Port,Line} = get_id(Line0), - {"#CDVPort"++Port,Line,D}; -parse_term([$S|Str0], D) -> %Information string. + {['#CDVPort'|Port],Line,D}; +parse_term([$S|Str0], _, D) -> %Information string. Str = lists:reverse(skip_blanks(lists:reverse(Str0))), {Str,[],D}; -parse_term([$D|Line0], D) -> %DistExternal +parse_term([$D|Line0], _, D) -> %DistExternal try {AttabSize,":"++Line1} = get_hex(Line0), {Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []), @@ -2455,7 +2367,7 @@ parse_atom_translation_table(N, Line0, As) -> -deref_ptr(Ptr, Line, D0) -> +deref_ptr(Ptr, Line, BinAddrAdj, D0) -> case gb_trees:lookup(Ptr, D0) of {value,Term} -> {Term,Line,D0}; @@ -2467,10 +2379,10 @@ deref_ptr(Ptr, Line, D0) -> case val(Fd) of "="++_ -> put(fd, end_of_heap), - deref_ptr(Ptr, Line, D0); + deref_ptr(Ptr, Line, BinAddrAdj, D0); L -> - D = parse(L, D0), - deref_ptr(Ptr, Line, D) + D = parse(L, BinAddrAdj, D0), + deref_ptr(Ptr, Line, BinAddrAdj, D) end end end. @@ -2508,13 +2420,16 @@ get_chars(0, Line, Acc) -> get_chars(N, [H|T], Acc) -> get_chars(N-1, T, [H|Acc]). -get_id(Line) -> - get_id(Line, []). +get_id(Line0) -> + [$<|Line] = lists:dropwhile(fun($<) -> false; (_) -> true end,Line0), + get_id(Line, [], []). -get_id([$>|Line], Acc) -> - {lists:reverse(Acc, [$>]),Line}; -get_id([H|T], Acc) -> - get_id(T, [H|Acc]). +get_id([$>|Line], Acc, Id) -> + {lists:reverse(Id,[list_to_integer(lists:reverse(Acc))]),Line}; +get_id([$.|Line], Acc, Id) -> + get_id(Line,[],[list_to_integer(lists:reverse(Acc))|Id]); +get_id([H|T], Acc, Id) -> + get_id(T, [H|Acc], Id). get_label(L) -> get_label(L, []). @@ -2532,19 +2447,26 @@ get_label([H|T], Acc) -> get_binary(Line0) -> {N,":"++Line} = get_hex(Line0), - get_binary(N, Line, []). + do_get_binary(N, Line, []). -get_binary(0, Line, Acc) -> +get_binary(Offset,Size,Line0) -> + {_N,":"++Line} = get_hex(Line0), + do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), []). + +do_get_binary(0, Line, Acc) -> {list_to_binary(lists:reverse(Acc)),Line}; -get_binary(N, [A,B|Line], Acc) -> +do_get_binary(N, [A,B|Line], Acc) -> Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B), - get_binary(N-1, Line, [Byte|Acc]); -get_binary(_N, [], _Acc) -> + do_get_binary(N-1, Line, [Byte|Acc]); +do_get_binary(_N, [], _Acc) -> {'#CDVTruncatedBinary',[]}. -cdvbin(Sz,Pos) -> - "#CDVBin<"++integer_to_list(Sz)++","++integer_to_list(Pos)++">". - +cdvbin(Offset,Size,{'#CDVBin',Pos}) -> + ['#CDVBin',Offset,Size,Pos]; +cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) -> + ['#CDVBin',Offset,Size,Pos]; +cdvbin(_,_,'#CDVTruncatedBinary') -> + '#CDVTruncatedBinary'. %%----------------------------------------------------------------- %% Functions for accessing the cdv_dump_index_table @@ -2563,8 +2485,6 @@ lookup_index(Tag,Id) -> count_index(Tag) -> ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]). -count_index(Tag,Id) -> - ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},Id},[],[true]}]). %%----------------------------------------------------------------- @@ -2605,16 +2525,14 @@ tag_to_atom(UnknownTag) -> %%%----------------------------------------------------------------- %%% Fetch next chunk from crashdump file -lookup_and_parse_index(File,What,ParseFun) when is_list(File) -> - Fd = open(File), +lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) -> Indices = lookup_index(What), - R = lists:map(fun({Id,Start}) -> - pos_bof(Fd,Start), - ParseFun(Fd,Id) - end, - Indices), - close(Fd), - R. + Fun = fun(Fd,{Id,Start}) -> + pos_bof(Fd,Start), + ParseFun(Fd,Id) + end, + Report = "Processing " ++ Str, + progress_pmap(Report,File,Fun,Indices). %%%----------------------------------------------------------------- %%% Convert a record to a proplist @@ -2627,3 +2545,112 @@ to_proplist(Fields,Record) -> to_value_list(Record) -> [_RecordName|Values] = tuple_to_list(Record), Values. + +%%%----------------------------------------------------------------- +%%% Fold over List and report progress in percent. +%%% Report is the text to be presented in the progress dialog. +%%% Acc0 is the initial accumulator and will be passed to Fun as the +%%% second arguement, i.e. Fun = fun(Item,Acc) -> NewAcc end. +progress_foldl(Report,Fun,Acc0,List) -> + init_progress(Report, length(List)), + progress_foldl1(Fun,Acc0,List). + +progress_foldl1(Fun,Acc,[H|T]) -> + update_progress(), + progress_foldl1(Fun,Fun(H,Acc),T); +progress_foldl1(_Fun,Acc,[]) -> + end_progress(), + Acc. + + +%%%----------------------------------------------------------------- +%%% Map over List and report progress in percent. +%%% Report is the text to be presented in the progress dialog. +%%% Distribute the load over a number of processes, and File is opened +%%% on each process and passed to the Fun as first argument. +%%% I.e. Fun = fun(Fd,Item) -> ItemResult end. +progress_pmap(Report,File,Fun,List) -> + NTot = length(List), + NProcs = erlang:system_info(schedulers) * 2, + NPerProc = (NTot div NProcs) + 1, + + %% Worker processes send message to collector for each ReportInterval. + ReportInterval = (NTot div 100) + 1, + + %% Progress reporter on collector process reports 1 percent for + %% each message from worker process. + init_progress(Report,99), + + Collector = self(), + {[],Pids} = + lists:foldl( + fun(_,{L,Ps}) -> + {L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L); + true -> {L,[]} % last chunk + end, + P = spawn( + fun() -> + progress_map(Collector,ReportInterval,File,Fun,L1) + end), + erlang:monitor(process,P), + {L2,[P|Ps]} + end, + {List,[]}, + lists:seq(1,NProcs)), + collect(Pids,[]). + +progress_map(Collector,ReportInterval,File,Fun,List) -> + Fd = open(File), + init_progress(ReportInterval, fun(_) -> Collector ! progress end, ok), + progress_map(Fd,Fun,List,[]). +progress_map(Fd,Fun,[H|T],Acc) -> + update_progress(), + progress_map(Fd,Fun,T,[Fun(Fd,H)|Acc]); +progress_map(Fd,_Fun,[],Acc) -> + close(Fd), + exit({pmap_done,Acc}). + +collect([],Acc) -> + end_progress(), + lists:append(Acc); +collect(Pids,Acc) -> + receive + progress -> + update_progress(), + collect(Pids,Acc); + {'DOWN', _Ref, process, Pid, {pmap_done,Result}} -> + collect(lists:delete(Pid,Pids),[Result|Acc]) + end. + +%%%----------------------------------------------------------------- +%%% Help functions for progress reporting + +%% Set text in progress dialog and initialize the progress counter +init_progress(Report,N) -> + observer_lib:report_progress({ok,Report}), + Interval = (N div 100) + 1, + Fun = fun(P0) -> P=P0+1,observer_lib:report_progress({ok,P}),P end, + init_progress(Interval,Fun,0). +init_progress(Interval,Fun,Acc) -> + put(progress,{Interval,Interval,Fun,Acc}), + ok. + +%% Count progress and report on given interval +update_progress() -> + update_progress(1). +update_progress(Processed) -> + do_update_progress(get(progress),Processed). + +do_update_progress({Count,Interval,Fun,Acc},Processed) when Processed>Count -> + do_update_progress({Interval,Interval,Fun,Fun(Acc)},Processed-Count); +do_update_progress({Count,Interval,Fun,Acc},Processed) -> + put(progress,{Count-Processed,Interval,Fun,Acc}), + ok. + +%% End progress reporting for this item +end_progress() -> + end_progress({ok,100}). +end_progress(Report) -> + observer_lib:report_progress(Report), + erase(progress), + ok. diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl index 038126288b..9cd4d6748a 100644 --- a/lib/observer/src/crashdump_viewer_html.erl +++ b/lib/observer/src/crashdump_viewer_html.erl @@ -34,7 +34,7 @@ plain_page/1, info_page/2, proc_details/4, - expanded_memory/2, + expandable_term/3, expanded_binary/1, port/3, internal_ets_tables/2, @@ -492,10 +492,10 @@ format(_Heading,Data) -> %%%----------------------------------------------------------------- %%% Expanded memory -expanded_memory(Heading,Expanded) -> - header(Heading,body(expanded_memory_body(Heading,Expanded))). +expandable_term(Heading,Expanded,Tab) -> + header(Heading,body(expandable_term_body(Heading,Expanded,Tab))). -expanded_memory_body(Heading,[]) -> +expandable_term_body(Heading,[],_Tab) -> [case Heading of "MsgQueue" -> "No messages were found"; "Message Queue" -> "No messages were found"; @@ -504,7 +504,7 @@ expanded_memory_body(Heading,[]) -> "ProcState" -> "Information could not be retrieved," " system messages may not be handled by this process." end]; -expanded_memory_body(Heading,Expanded) -> +expandable_term_body(Heading,Expanded,Tab) -> Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", [case Heading of "MsgQueue" -> @@ -513,7 +513,7 @@ expanded_memory_body(Heading,Expanded) -> [th("WIDTH=70%","Message"), th("WIDTH=30%","SeqTraceToken")]) | element(1, lists:mapfoldl(fun(Msg, Even) -> - {msgq_table(Msg, Even), + {msgq_table(Tab, Msg, Even), not Even} end, true, Expanded))]); @@ -523,7 +523,7 @@ expanded_memory_body(Heading,Expanded) -> [th("WIDTH=10%","Id"), th("WIDTH=90%","Message")]) | element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> - {msgq_table(Msg, N, Even), + {msgq_table(Tab, Msg, N, Even), {not Even, N+1}} end, {true,1}, Expanded))]); @@ -533,7 +533,7 @@ expanded_memory_body(Heading,Expanded) -> [th("WIDTH=20%","Label"), th("WIDTH=80%","Term")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {stackdump_table(Entry, Even), + {stackdump_table(Tab, Entry, Even), not Even} end, true, Expanded))]); "ProcState" -> @@ -542,7 +542,7 @@ expanded_memory_body(Heading,Expanded) -> [th("WIDTH=20%","Label"), th("WIDTH=80%","Information")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {proc_state(Entry,Even), + {proc_state(Tab, Entry,Even), not Even} end, true, Expanded))]); _ -> @@ -551,43 +551,57 @@ expanded_memory_body(Heading,Expanded) -> [th("WIDTH=30%","Key"), th("WIDTH=70%","Value")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {dict_table(Entry,Even), + {dict_table(Tab, Entry,Even), not Even} end, true, Expanded))]) end]. -msgq_table({Msg0,Token0}, Even) -> +msgq_table(Tab,{Msg0,Token0}, Even) -> Token = case Token0 of [] -> ""; _ -> io_lib:fwrite("~w",[Token0]) end, - Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))), + Msg = all_or_expand(Tab,Msg0), tr(color(Even),[td(pre(Msg)), td(Token)]). -msgq_table(Msg0, Id, Even) -> - Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))), +msgq_table(Tab,Msg0, Id, Even) -> + Msg = all_or_expand(Tab,Msg0), tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]). -stackdump_table({Label0,Term0},Even) -> +stackdump_table(Tab,{Label0,Term0},Even) -> Label = io_lib:format("~w",[Label0]), - Term = href_proc_port(lists:flatten(io_lib:format("~p",[Term0]))), + Term = all_or_expand(Tab,Term0), tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]). -dict_table({Key0,Value0}, Even) -> - Key = href_proc_port(lists:flatten(io_lib:format("~p",[Key0]))), - Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))), +dict_table(Tab,{Key0,Value0}, Even) -> + Key = all_or_expand(Tab,Key0), + Value = all_or_expand(Tab,Value0), tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]). -proc_state({Key0,Value0}, Even) -> +proc_state(Tab,{Key0,Value0}, Even) -> Key = lists:flatten(io_lib:format("~s",[Key0])), - Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))), + Value = all_or_expand(Tab,Value0), tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]). +all_or_expand(Tab,Term) -> + Preview = io_lib:format("~P",[Term,8]), + Check = io_lib:format("~P",[Term,9]), + Exp = Preview=/=Check, + all_or_expand(Tab,Term,Preview,Exp). +all_or_expand(_Tab,_Term,Str,false) -> + href_proc_port(lists:flatten(Str)); +all_or_expand(Tab,Term,Preview,true) -> + Key = {Key1,Key2,Key3} = now(), + ets:insert(Tab,{Key,Term}), + [href_proc_port(lists:flatten(Preview),false), $\n, + href("TARGET=\"expanded\"",["#Term?key1="++integer_to_list(Key1)++ + "&key2="++integer_to_list(Key2)++ + "&key3="++integer_to_list(Key3)], + "Click to expand above term")]. color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). - %%%----------------------------------------------------------------- %%% Display an expanded binary, i.e. the whole binary, not just the %%% size of it. @@ -1094,82 +1108,109 @@ br() -> %% In all the following, "<" is changed to "<" and ">" is changed to ">" href_proc_port(Text) -> - href_proc_port(Text,[]). -href_proc_port([$#,$R,$e,$f,$<|T],Acc) -> + href_proc_port(Text,true). +href_proc_port(Text,LinkToBin) -> + href_proc_port(Text,[],LinkToBin). +href_proc_port("#Ref<"++T,Acc,LTB) -> %% No links to refs - href_proc_port(T,[$;,$t,$l,$&,$f,$e,$R,$#|Acc]); -href_proc_port([$#,$F,$u,$n,$<|T],Acc) -> + href_proc_port(T,["#Ref<"|Acc],LTB); +href_proc_port("#Fun<"++T,Acc,LTB) -> %% No links to funs - href_proc_port(T,[$;,$t,$l,$&,$n,$u,$F,$#|Acc]); -href_proc_port([$#,$P,$o,$r,$t,$<|T],Acc) -> - {Port,Rest} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]), - href_proc_port(Rest,[href(Port,Port)|Acc]); -href_proc_port([$<,$<|T],Acc) -> + href_proc_port(T,["#Fun<"|Acc],LTB); +href_proc_port("#Port<"++T,Acc,LTB) -> + {Port0,Rest} = split($>,T), + Port = "#Port<"++Port0 ++ ">", + href_proc_port(Rest,[href(Port,Port)|Acc],LTB); +href_proc_port("<<"++T,Acc,LTB) -> %% No links to binaries - href_proc_port(T,[$;,$t,$l,$&,$;,$t,$l,$&|Acc]); -href_proc_port([$<,C|T],Acc) when $0 =< C, C =< $9 -> + href_proc_port(T,["<<"|Acc],LTB); +href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 -> %% Pid - {Pid,Rest} = to_gt(T,[C,$;,$t,$l,$&]), - href_proc_port(Rest,[href(Pid,Pid)|Acc]); -href_proc_port([$",$#,$C,$D,$V,$B,$i,$n,$<|T],Acc) -> + {Pid0,Rest} = split($>,T), + Pid = "<" ++ Pid0 ++ ">", + href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB); +href_proc_port("['#CDVBin'"++T,Acc,LTB) -> %% Binary written by crashdump_viewer:parse_heap_term(...) - {SizeAndPos,[$"|Rest]} = split($>,T), - {Size,Pos} = split($,,SizeAndPos), - href_proc_port(Rest,[href("TARGET=\"expanded\"", - ["#Binary<",Pos,">"], - ["<< ",Size," bytes >>"]) | Acc]); -href_proc_port([$",$#,$C,$D,$V,$P,$o,$r,$t,$<|T],Acc) -> + {OffsetSizePos,Rest} = split($],T), + BinStr = + case string:tokens(OffsetSizePos,",.|") of + [Offset,Size,Pos] -> + Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, + {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), + PreviewBytes = binary_to_list(PreviewBin), + PreviewStr = ["<<", + [integer_to_list(X)++"," || X <- PreviewBytes], + "...(", + observer_lib:to_str({bytes,Size}), + ")>>"], + if LTB -> + href("TARGET=\"expanded\"", + ["#Binary?offset="++Offset++ + "&size="++Size++ + "&pos="++Pos], + PreviewStr); + true -> + PreviewStr + end; + _ -> + "<< ... >>" + end, + href_proc_port(Rest,[BinStr|Acc],LTB); +href_proc_port("['#CDVPort'"++T,Acc,LTB) -> %% Port written by crashdump_viewer:parse_term(...) - {Port,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]), - href_proc_port(Rest,[href(Port,Port)|Acc]); -href_proc_port([$",$#,$C,$D,$V,$P,$i,$d,$<|T],Acc) -> + {Port0,Rest} = split($],T), + PortStr= + case string:tokens(Port0,",.|") of + [X,Y] -> + Port = "#Port<"++X++"."++Y++">", + href(Port,Port); + Ns -> + "#Port<" ++ string:join(Ns,".") ++"...>" + end, + href_proc_port(Rest,[PortStr|Acc],LTB); +href_proc_port("['#CDVPid'"++T,Acc,LTB) -> %% Pid written by crashdump_viewer:parse_term(...) - {Pid,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&]), - href_proc_port(Rest,[href(Pid,Pid)|Acc]); -href_proc_port([$',$#,$C,$D,$V,$I,$n,$c,$o,$m,$p,$l,$e,$t,$e,$H,$e,$a,$p,$'|T], - Acc)-> + {Pid0,Rest} = split($],T), + PidStr = + case string:tokens(Pid0,",.|") of + [X,Y,Z] -> + Pid = "<"++X++"."++Y++"."++Z++">", + href(Pid,Pid); + Ns -> + "<" ++ string:join(Ns,".") ++ "...>" + end, + href_proc_port(Rest,[PidStr|Acc],LTB); +href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)-> %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...) IH = lists:reverse( lists:flatten( "...(Incomplete Heap)")), - href_proc_port(T,IH++Acc); -href_proc_port([$',$#,$C,$D,$V,$T,$r,$u,$n,$c,$a,$t,$e,$d,$B,$i,$n,$a,$r,$y,$' - |T], Acc)-> + href_proc_port(T,IH++Acc,LTB); +href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)-> %% A binary which is truncated! Written by %% crashdump_viewer:parse_heap_term(...) IH = lists:reverse( lists:flatten( "<<...(Truncated Binary)>>" "")), - href_proc_port(T,IH++Acc); -href_proc_port([$',$#,$C,$D,$V,$N,$o,$n,$e,$x,$i,$s,$t,$i,$n,$g,$B,$i,$n,$a,$r, - $y,$'|T], Acc)-> + href_proc_port(T,IH++Acc,LTB); +href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)-> %% A binary which could not be found in the dump! Written by %% crashdump_viewer:parse_heap_term(...) IH = lists:reverse( lists:flatten( "<<...(Nonexisting Binary)>>" "")), - href_proc_port(T,IH++Acc); -href_proc_port([$<|T],Acc) -> - href_proc_port(T,[$;,$t,$l,$&|Acc]); -href_proc_port([$>|T],Acc) -> - href_proc_port(T,[$;,$t,$g,$&|Acc]); -href_proc_port([H|T],Acc) -> - href_proc_port(T,[H|Acc]); -href_proc_port([],Acc) -> + href_proc_port(T,IH++Acc,LTB); +href_proc_port("<"++T,Acc,LTB) -> + href_proc_port(T,["<"|Acc],LTB); +href_proc_port(">"++T,Acc,LTB) -> + href_proc_port(T,[">"|Acc],LTB); +href_proc_port([H|T],Acc,LTB) -> + href_proc_port(T,[H|Acc],LTB); +href_proc_port([],Acc,_) -> lists:reverse(Acc). -to_gt(Str,Acc) -> - {Match,Rest} = to_gt_noreverse(Str,Acc), - {lists:reverse(Match),Rest}. -to_gt_noreverse([$>|T],Acc) -> - {[$;,$t,$g,$&|Acc],T}; -to_gt_noreverse([H|T],Acc) -> - to_gt_noreverse(T,[H|Acc]); -to_gt_noreverse([],Acc) -> - {Acc,[]}. - split(Char,Str) -> split(Char,Str,[]). split(Char,[Char|Str],Acc) -> % match Char diff --git a/lib/observer/src/crashdump_viewer_wx.erl b/lib/observer/src/crashdump_viewer_wx.erl index aeb89b54f4..17e43838d6 100644 --- a/lib/observer/src/crashdump_viewer_wx.erl +++ b/lib/observer/src/crashdump_viewer_wx.erl @@ -16,7 +16,7 @@ %% %% %CopyrightEnd% -module(crashdump_viewer_wx). - +-compile(export_all). -behaviour(wx_object). -export([start/1]). @@ -89,31 +89,20 @@ set_status(What) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init(File) -> - {ok,CdvServer} = crashdump_viewer:start_link(), - +init(File0) -> register(?SERVER, self()), wx:new(), + + {ok,CdvServer} = crashdump_viewer:start_link(), + catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), - Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump viewer", + Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer", [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]), IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"), Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), wxFrame:setIcon(Frame, Icon), wxIcon:destroy(Icon), - State = #state{server=CdvServer, file = File, frame = Frame}, - UpdState = setup(State), - process_flag(trap_exit, true), - {Frame, UpdState}. - -setup(#state{file = File0, frame = Frame} = State) -> - %% Setup Menubar & Menus - MenuBar = wxMenuBar:new(), - DefMenus = default_menus(), - observer_lib:create_menus(DefMenus, MenuBar, default), - wxFrame:setMenuBar(Frame, MenuBar), - %% Setup panels Panel = wxPanel:new(Frame, []), Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), @@ -121,22 +110,6 @@ setup(#state{file = File0, frame = Frame} = State) -> %% Setup "statusbar" to show warnings StatusBar = observer_lib:create_status_bar(Panel), - %% Load a crashdump - File = load_dump(Panel,File0), - - %% Set window title - T1 = "Crashdump Viewer: ", - Title = - if length(File) > 70 -> - T1 ++ filename:basename(File); - true -> - T1 ++ File - end, - wxFrame:setTitle(Frame, Title), - - %% General information Panel - GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx), - %% Setup sizer create early to get it when window shows MainSizer = wxBoxSizer:new(?wxVERTICAL), @@ -151,8 +124,41 @@ setup(#state{file = File0, frame = Frame} = State) -> wxMenu:connect(Frame, command_menu_selected), wxFrame:show(Frame), - %% I postpone the creation of the other tabs so they can query/use - %% the window size + case load_dump(Frame,File0) of + {ok,File} -> + %% Set window title + T1 = "Crashdump Viewer: ", + Title = + if length(File) > 70 -> + T1 ++ filename:basename(File); + true -> + T1 ++ File + end, + wxFrame:setTitle(Frame, Title), + + setup(#state{server=CdvServer, + file=File, + frame=Frame, + status_bar=StatusBar, + notebook=Notebook, + main_panel=Panel}); + error -> + wxFrame:destroy(Frame), + wx:destroy(), + crashdump_viewer:stop(), + ignore + end. + +setup(#state{frame=Frame, notebook=Notebook, main_panel=Panel}=State) -> + + %% Setup Menubar & Menus + MenuBar = wxMenuBar:new(), + DefMenus = default_menus(), + observer_lib:create_menus(DefMenus, MenuBar, default), + wxFrame:setMenuBar(Frame, MenuBar), + + %% General information Panel + GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx), %% Process Panel ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list, cdv_proc_wx), @@ -189,38 +195,22 @@ setup(#state{file = File0, frame = Frame} = State) -> GenPid = wx_object:get_pid(GenPanel), GenPid ! active, - UpdState = State#state{file = File, - main_panel = Panel, - notebook = Notebook, - menubar = MenuBar, - status_bar = StatusBar, - gen_panel = GenPanel, - pro_panel = ProPanel, - port_panel = PortPanel, - ets_panel = EtsPanel, - timer_panel = TimerPanel, - fun_panel = FunPanel, - atom_panel = AtomPanel, - dist_panel = DistPanel, - mod_panel = ModPanel, - mem_panel = MemPanel, - int_panel = IntPanel, - active_tab = GenPid - }, - %% Create resources which we don't want to duplicate - SysFont = wxSystemSettings:getFont(?wxSYS_SYSTEM_FIXED_FONT), - Fixed = case wxFont:isFixedWidth(SysFont) of - true -> SysFont; - false -> %% Sigh - SysFontSize = wxFont:getPointSize(SysFont), - wxFont:new(SysFontSize, - ?wxFONTFAMILY_MODERN, - ?wxFONTSTYLE_NORMAL, - ?wxFONTWEIGHT_NORMAL) - end, - put({font, fixed}, Fixed), - UpdState. - + observer_lib:destroy_progress_dialog(), + process_flag(trap_exit, true), + {Frame, State#state{menubar = MenuBar, + gen_panel = GenPanel, + pro_panel = ProPanel, + port_panel = PortPanel, + ets_panel = EtsPanel, + timer_panel = TimerPanel, + fun_panel = FunPanel, + atom_panel = AtomPanel, + dist_panel = DistPanel, + mod_panel = ModPanel, + mem_panel = MemPanel, + int_panel = IntPanel, + active_tab = GenPid + }}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -230,7 +220,6 @@ handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}}, case get_active_pid(State) of Previous -> {noreply, State}; Pid -> - Previous ! not_active, Pid ! active, {noreply, State#state{active_tab=Pid}} end; @@ -241,21 +230,28 @@ handle_event(#wx{event = #wxClose{}}, State) -> handle_event(#wx{id = ?wxID_OPEN, event = #wxCommand{type = command_menu_selected}}, State) -> - File = load_dump(State#state.main_panel,undefined), - Panels = [State#state.gen_panel, - State#state.pro_panel, - State#state.port_panel, - State#state.ets_panel, - State#state.timer_panel, - State#state.fun_panel, - State#state.atom_panel, - State#state.dist_panel, - State#state.mod_panel, - State#state.mem_panel, - State#state.int_panel], - _ = [wx_object:get_pid(Panel) ! new_dump || Panel<-Panels], - State#state.active_tab ! active, - {noreply, State#state{file=File}}; + NewState = + case load_dump(State#state.frame,undefined) of + {ok,File} -> + Panels = [State#state.gen_panel, + State#state.pro_panel, + State#state.port_panel, + State#state.ets_panel, + State#state.timer_panel, + State#state.fun_panel, + State#state.atom_panel, + State#state.dist_panel, + State#state.mod_panel, + State#state.mem_panel, + State#state.int_panel], + _ = [wx_object:call(Panel,new_dump) || Panel<-Panels], + wxNotebook:setSelection(State#state.notebook,0), + observer_lib:destroy_progress_dialog(), + State#state{file=File}; + error -> + State + end, + {noreply,NewState}; handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, @@ -313,6 +309,7 @@ handle_info(_Info, State) -> terminate(_Reason, #state{frame = Frame}) -> wxFrame:destroy(Frame), + wx:destroy(), crashdump_viewer:stop(), ok. @@ -390,57 +387,42 @@ default_menus() -> true -> %% On Mac quit and about will be moved to the "default' place %% automagicly, so just add them to a menu that always exist. - %% But not to the help menu for some reason -%! {Tag, Menus} = NodeMenu, -%! [{Tag, Menus ++ [Quit,About]}, {"&Help", [Help]}] - [{"File", [Quit,About]}, {"&Help", [Help,UG,Howto]}] %?siri: does this work? + [{"File", [Open, About,Quit]}, {"&Help", [Help,UG,Howto]}] end. -load_dump(Panel,undefined) -> - FD = wxFileDialog:new(Panel, +load_dump(Frame,undefined) -> + FD = wxFileDialog:new(wx:null(), [{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]), case wxFileDialog:showModal(FD) of ?wxID_OK -> Path = wxFileDialog:getPath(FD), wxDialog:destroy(FD), - load_dump(Panel, Path); + load_dump(Frame,Path); _ -> - wxDialog:destroy(FD) + wxDialog:destroy(FD), + error end; -load_dump(Panel, FileName) -> +load_dump(Frame,FileName) -> + ok = observer_lib:display_progress_dialog("Crashdump Viewer", + "Loading crashdump"), crashdump_viewer:read_file(FileName), - update_progress(Panel,false), - FileName. - -update_progress(Panel,PD) -> - case crashdump_viewer:get_progress() of - {ok, done} -> - wxProgressDialog:destroy(PD); - {ok, Percent} when is_integer(Percent) -> - wxProgressDialog:update(PD,Percent), - update_progress(Panel,PD); - {ok,Msg} -> - case PD of - false -> ok; - _ -> wxProgressDialog:destroy(PD) - end, - update_progress(Panel,new_progress(Msg)); - {error, Reason} -> - wxProgressDialog:destroy(PD), - FailMsg = file:format_error(Reason), - MD = wxMessageDialog:new(Panel, FailMsg), - wxDialog:showModal(MD), - wxDialog:destroy(MD) + case observer_lib:wait_for_progress() of + ok -> + %% Set window title + T1 = "Crashdump Viewer: ", + Title = + if length(FileName) > 70 -> + T1 ++ filename:basename(FileName); + true -> + T1 ++ FileName + end, + wxFrame:setTitle(Frame, Title), + {ok,FileName}; + error -> + error end. -new_progress(Msg) -> - wxProgressDialog:new("Crashdump viewer",Msg, - [{maximum,100}, - {style, - ?wxPD_APP_MODAL bor - ?wxPD_SMOOTH bor - ?wxPD_AUTO_HIDE}]). %%%----------------------------------------------------------------- %%% Find help document (HTML files) get_help_doc(HelpId) -> diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 159afae487..463c42308d 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -20,6 +20,8 @@ -export([get_wx_parent/1, display_info_dialog/1, display_yes_no_dialog/1, + display_progress_dialog/2, destroy_progress_dialog/0, + wait_for_progress/0, report_progress/1, user_term/3, user_term_multiline/3, interval_dialog/4, start_timer/1, stop_timer/1, display_info/2, fill_info/2, update_info/2, to_str/1, @@ -103,7 +105,9 @@ setup_timer(Bool, {Timer, Old}) -> setup_timer(Bool, {false, Old}). display_info_dialog(Str) -> - Dlg = wxMessageDialog:new(wx:null(), Str), + display_info_dialog("",Str). +display_info_dialog(Title,Str) -> + Dlg = wxMessageDialog:new(wx:null(), Str, [{caption,Title}]), wxMessageDialog:showModal(Dlg), wxMessageDialog:destroy(Dlg), ok. @@ -267,27 +271,23 @@ create_menus(Menus, MenuBar, Type) -> create_menu(Tag, Ms, Index, MenuBar, Type) end, [{First, _}|_] = Menus, - OnMac = os:type() =:= {unix, darwin}, Index = if Type =:= default -> 0; First =:= "File" -> 0; - OnMac -> 0; true -> 1 end, wx:foldl(Add, Index, Menus), ok. create_menu("File", MenuItems, Index, MenuBar, Type) -> - OnMac = os:type() =:= {unix, darwin}, - if OnMac, Type =:= default -> - Index; - not OnMac, Type =:= plugin -> + if + Type =:= plugin -> MenuId = wxMenuBar:findMenu(MenuBar, "File"), Menu = wxMenuBar:getMenu(MenuBar, MenuId), lists:foldl(fun(Record, N) -> create_menu_item(Record, Menu, N) end, 0, MenuItems), Index + 1; - true -> + true -> Menu = wxMenu:new(), lists:foldl(fun(Record, N) -> create_menu_item(Record, Menu, N) @@ -429,15 +429,18 @@ create_box(Panel, Data) -> case Value0 of {click,"unknown"} -> wxTextCtrl:new(Panel, ?wxID_ANY, - [{style,?MULTI_LINE_STYLE}, + [{style,?SINGLE_LINE_STYLE}, {value,"unknown"}]); {click,Value} -> link_entry(Panel,Value); _ -> Value = to_str(Value0), - wxTextCtrl:new(Panel, ?wxID_ANY, - [{style,?MULTI_LINE_STYLE}, - {value,Value}]) + TCtrl = wxTextCtrl:new(Panel, ?wxID_ANY, + [{style,?SINGLE_LINE_STYLE}, + {value,Value}]), + length(Value) > 50 andalso + wxWindow:setToolTip(TCtrl,wxToolTip:new(Value)), + TCtrl end, wxSizer:add(Line, 10, 0), % space of size 10 horisontally wxSizer:add(Line, Field, RightProportion), @@ -471,18 +474,15 @@ link_entry2(Panel,{Target,Str},Cursor) -> wxWindow:setToolTip(TC, ToolTip), TC. -to_link(Tuple = {_Target, _Str}) -> Tuple; -to_link(Target) -> {Target, to_str(Target)}. +to_link(Tuple = {_Target, _Str}) -> + Tuple; +to_link(Target0) -> + Target=to_str(Target0), + {Target, Target}. html_window(Panel) -> Win = wxHtmlWindow:new(Panel, [{style, ?wxHW_SCROLLBAR_AUTO}]), - FixedName = case whereis(observer) of - undefined -> "courier"; - _Pid -> - Fixed = observer_wx:get_attrib({font,fixed}), - wxFont:getFaceName(Fixed) - end, - wxHtmlWindow:setFonts(Win, "", FixedName), + %% wxHtmlWindow:setFonts(Win, "", FixedName), wxHtmlWindow:connect(Win,command_html_link_clicked), Win. @@ -647,3 +647,95 @@ create_status_bar(Panel) -> wxTextCtrl:setDefaultStyle(StatusBar,Red), wxTextAttr:destroy(Red), StatusBar. + +%%%----------------------------------------------------------------- +%%% Progress dialog +-define(progress_handler,cdv_progress_handler). +display_progress_dialog(Title,Str) -> + Caller = self(), + Env = wx:get_env(), + spawn_link(fun() -> + progress_handler(Caller,Env,Title,Str) + end), + ok. + +wait_for_progress() -> + receive + continue -> + ok; + Error -> + Error + end. + +destroy_progress_dialog() -> + report_progress(finish). + +report_progress(Progress) -> + case whereis(?progress_handler) of + Pid when is_pid(Pid) -> + Pid ! {progress,Progress}, + ok; + _ -> + ok + end. + +progress_handler(Caller,Env,Title,Str) -> + register(?progress_handler,self()), + wx:set_env(Env), + PD = progress_dialog(Env,Title,Str), + progress_loop(Title,PD,Caller). +progress_loop(Title,PD,Caller) -> + receive + {progress,{ok,done}} -> % to make wait_for_progress/0 return + Caller ! continue, + progress_loop(Title,PD,Caller); + {progress,{ok,Percent}} when is_integer(Percent) -> + update_progress(PD,Percent), + progress_loop(Title,PD,Caller); + {progress,{ok,Msg}} -> + update_progress_text(PD,Msg), + progress_loop(Title,PD,Caller); + {progress,{error, Reason}} -> + finish_progress(PD), + FailMsg = + if is_list(Reason) -> Reason; + true -> file:format_error(Reason) + end, + display_info_dialog("Crashdump Viewer Error",FailMsg), + Caller ! error, + unregister(?progress_handler), + unlink(Caller); + {progress,finish} -> + finish_progress(PD), + unregister(?progress_handler), + unlink(Caller) + end. + +progress_dialog(Env,Title,Str) -> + %% Spawning separat process to hold this since we use showModal. + spawn_link( + fun() -> + wx:set_env(Env), + PD = wxProgressDialog:new(Title,Str, + [{maximum,101}, + {style, + ?wxPD_APP_MODAL bor + ?wxPD_SMOOTH bor + ?wxPD_AUTO_HIDE}]), + wxProgressDialog:setMinSize(PD,{200,-1}), + ?progress_handler ! {progress_dialog,PD}, + wxProgressDialog:showModal(PD), + wxDialog:destroy(PD) + end), + receive + {progress_dialog,PD} -> + timer:sleep(300), % To allow the window to show before reporting + PD + end. + +update_progress(PD,Value) -> + wxProgressDialog:update(PD,Value). +update_progress_text(PD,Text) -> + wxProgressDialog:update(PD,0,[{newmsg,Text}]). +finish_progress(PD) -> + wxProgressDialog:endModal(PD, ?wxID_OK). diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index db5eefaa6c..cfc22e7093 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -35,7 +35,9 @@ -record(state, {parent, frame, pid, - pages=[] + pages=[], + expand_table, + expand_wins=[] }). -record(worker, {panel, callback}). @@ -47,6 +49,7 @@ start(Process, ParentFrame, Parent) -> init([Pid, ParentFrame, Parent]) -> try + Table = ets:new(observer_expand,[set,protected]), Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of [] -> io_lib:format("~p",[Pid]); {registered_name, Registered} -> io_lib:format("~p (~p)",[Registered, Pid]); @@ -60,11 +63,11 @@ init([Pid, ParentFrame, Parent]) -> Notebook = wxNotebook:new(Frame, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), - ProcessPage = init_panel(Notebook, "Process Information", Pid, fun init_process_page/2), - MessagePage = init_panel(Notebook, "Messages", Pid, fun init_message_page/2), - DictPage = init_panel(Notebook, "Dictionary", Pid, fun init_dict_page/2), - StackPage = init_panel(Notebook, "Stack Trace", Pid, fun init_stack_page/2), - StatePage = init_panel(Notebook, "State", Pid, fun init_state_page/2), + ProcessPage = init_panel(Notebook, "Process Information", [Pid], fun init_process_page/2), + MessagePage = init_panel(Notebook, "Messages", [Pid,Table], fun init_message_page/3), + DictPage = init_panel(Notebook, "Dictionary", [Pid,Table], fun init_dict_page/3), + StackPage = init_panel(Notebook, "Stack Trace", [Pid], fun init_stack_page/2), + StatePage = init_panel(Notebook, "State", [Pid,Table], fun init_state_page/3), wxFrame:connect(Frame, close_window), wxMenu:connect(Frame, command_menu_selected), @@ -73,7 +76,8 @@ init([Pid, ParentFrame, Parent]) -> {Frame, #state{parent=Parent, pid=Pid, frame=Frame, - pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage] + pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage], + expand_table=Table }} catch error:{badrpc, _} -> observer_wx:return_to_localnode(ParentFrame, node(Pid)), @@ -83,10 +87,10 @@ init([Pid, ParentFrame, Parent]) -> {stop, normal} end. -init_panel(Notebook, Str, Pid, Fun) -> +init_panel(Notebook, Str, FunArgs, Fun) -> Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxHORIZONTAL), - {Window,Callback} = Fun(Panel, Pid), + {Window,Callback} = apply(Fun,[Panel|FunArgs]), wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), wxPanel:setSizer(Panel, Sizer), true = wxNotebook:addPage(Notebook, Panel, Str), @@ -99,7 +103,8 @@ handle_event(#wx{event=#wxClose{type=close_window}}, State) -> handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> {stop, normal, State}; -handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages}=State) -> +handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages, expand_table=T}=State) -> + ets:delete_all_objects(T), try [(W#worker.callback)() || W <- Pages] catch process_undefined -> wxFrame:setTitle(Frame, io_lib:format("*DEAD* ~p",[Pid])) @@ -118,6 +123,21 @@ handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> wxTextCtrl:setForegroundColour(Obj,?wxBLUE), {noreply, State}; +handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href="#Term?"++Keys}}}, + #state{frame=Frame,expand_table=T,expand_wins=Opened0}=State) -> + [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(Keys), + Id = {T,{list_to_integer(Key1),list_to_integer(Key2),list_to_integer(Key3)}}, + Opened = + case lists:keyfind(Id,1,Opened0) of + false -> + Win = observer_term_wx:start(Id,Frame), + [{Id,Win}|Opened0]; + {_,Win} -> + wxFrame:raise(Win), + Opened0 + end, + {noreply,State#state{expand_wins=Opened}}; + handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) -> observer ! {open_link, Info}, {noreply, State}; @@ -125,6 +145,10 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) handle_event(Event, _State) -> error({unhandled_event, Event}). +handle_info({expand_win_closed,Id}, #state{expand_wins=Opened0}=State) -> + Opened = lists:keydelete(Id,1,Opened0), + {noreply,State#state{expand_wins=Opened}}; + handle_info(_Info, State) -> %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. @@ -135,7 +159,8 @@ handle_call(Call, From, _State) -> handle_cast(Cast, _State) -> error({unhandled_cast, Cast}). -terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) -> +terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame,expand_table=T}) -> + T=/=undefined andalso ets:delete(T), Parent ! {procinfo_menu_closed, Pid}, case Frame of undefined -> ok; @@ -156,14 +181,14 @@ init_process_page(Panel, Pid) -> end}. -init_message_page(Parent, Pid) -> +init_message_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]) of {messages, Messages} -> - Html = crashdump_viewer_html:expanded_memory("Message Queue", Messages), + Html = crashdump_viewer_html:expandable_term("Message Queue", Messages, Table), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -172,13 +197,13 @@ init_message_page(Parent, Pid) -> Update(), {Win, Update}. -init_dict_page(Parent, Pid) -> +init_dict_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) of {dictionary,Dict} -> - Html = crashdump_viewer_html:expanded_memory("Dictionary", Dict), + Html = crashdump_viewer_html:expandable_term("Dictionary", Dict, Table), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -231,11 +256,11 @@ init_stack_page(Parent, Pid) -> Update(), {LCtrl, Update}. -init_state_page(Parent, Pid) -> +init_state_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), Update = fun() -> StateInfo = fetch_state_info(Pid), - Html = crashdump_viewer_html:expanded_memory("ProcState", StateInfo), + Html = crashdump_viewer_html:expandable_term("ProcState", StateInfo, Table), wxHtmlWindow:setPage(Win, Html) end, Update(), diff --git a/lib/observer/src/observer_term_wx.erl b/lib/observer/src/observer_term_wx.erl new file mode 100644 index 0000000000..58cb650bef --- /dev/null +++ b/lib/observer/src/observer_term_wx.erl @@ -0,0 +1,113 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% + +-module(observer_term_wx). + +-behaviour(wx_object). + +-export([start/2]). + +-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, + handle_call/3, handle_info/2]). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-define(REFRESH, 601). + +-record(state, {parent, + frame, + id + }). + +start(Id, ParentFrame) -> + wx_object:start_link(?MODULE, [Id, ParentFrame, self()], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([{T,Key}=Id, ParentFrame, Parent]) -> + case ets:lookup(T,Key) of + [{Key,Term}] -> + init(Id, ParentFrame, Parent, Term); + [] -> + observer_lib:display_info_dialog( + "The term does no longer exist.\n" + "Please refresh the process window!"), + {stop, normal} + end. + + + +init(Id, ParentFrame, Parent, Term) -> + Frame=wxFrame:new(ParentFrame, ?wxID_ANY, ["Expanded Term"], + [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]), + MenuBar = wxMenuBar:new(), + Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}], + observer_lib:create_menus(Menus, MenuBar, new_window), + wxFrame:setMenuBar(Frame, MenuBar), + Panel = wxPanel:new(Frame), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + + Window = observer_lib:html_window(Panel), + Html = crashdump_viewer_html:plain_page(io_lib:format("~p~n",[Term])), + wxHtmlWindow:setPage(Window, Html), + + wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, + {border, 5}]), + wxPanel:setSizer(Panel, Sizer), + wxFrame:show(Frame), + {Frame, #state{parent=Parent, + frame=Frame, + id=Id + }}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_event(#wx{event=#wxClose{type=close_window}}, State) -> + {stop, normal, State}; + +handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> + {stop, normal, State}; + +handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) -> + observer ! {open_link, Info}, + {noreply, State}; + +handle_event(Event, _State) -> + error({unhandled_event, Event}). + +handle_info(_Info, State) -> + %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +handle_call(Call, From, _State) -> + error({unhandled_call, Call, From}). + +handle_cast(Cast, _State) -> + error({unhandled_cast, Cast}). + +terminate(_Reason, #state{parent=Parent,id=Id,frame=Frame}) -> + Parent ! {expand_win_closed, Id}, + case Frame of + undefined -> ok; + _ -> wxFrame:destroy(Frame) + end, + ok. + +code_change(_, _, State) -> + {ok, State}. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 9839f8bf7b..4c385b76aa 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -131,6 +131,10 @@ setup(#state{frame = Frame} = State) -> wxFrame:connect(Frame, close_window, [{skip, true}]), wxMenu:connect(Frame, command_menu_selected), wxFrame:show(Frame), + + %% Freeze and thaw is buggy currently + DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9], + DoFreeze andalso wxWindow:freeze(Panel), %% I postpone the creation of the other tabs so they can query/use %% the window size @@ -154,9 +158,10 @@ setup(#state{frame = Frame} = State) -> TracePanel = observer_trace_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), - - %% Force redraw (window needs it) + %% Force redraw (windows needs it) wxWindow:refresh(Panel), + DoFreeze andalso wxWindow:thaw(Panel), + wxFrame:raise(Frame), wxFrame:setFocus(Frame), @@ -574,13 +579,6 @@ remove_menu_items([{MenuStr = "File", Menus}|Rest], MenuBar) -> Menu = wxMenuBar:getMenu(MenuBar, MenuId), Items = [wxMenu:findItem(Menu, Tag) || #create_menu{text=Tag} <- Menus], [wxMenu:delete(Menu, MItem) || MItem <- Items], - case os:type() =:= {unix, darwin} of - true -> - wxMenuBar:remove(MenuBar, MenuId), - wxMenu:destroy(Menu); - false -> - ignore - end, remove_menu_items(Rest, MenuBar) end; remove_menu_items([{"Nodes", _}|_], _MB) -> -- cgit v1.2.3 From bfae535d4ac51d2c3bef146e0f058e105bb5e956 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 22 Oct 2013 13:37:03 +0200 Subject: observer: Optimize row lookups Use arrays instead of lists to cache data, gives faster lookups for large contents. Also update colors used in table viewer, indication new and changed rows. Other minor bugfixes in tables viewer. --- lib/observer/src/observer_defs.hrl | 9 +-- lib/observer/src/observer_lib.erl | 10 ++- lib/observer/src/observer_pro_wx.erl | 73 +++++++++++-------- lib/observer/src/observer_tv_table.erl | 123 ++++++++++++++++++--------------- 4 files changed, 127 insertions(+), 88 deletions(-) (limited to 'lib/observer/src') diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 586e7bbff9..a720e8c833 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. 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 @@ -35,13 +35,14 @@ check = false }). --record(attrs, {even, odd, deleted, changed, searched}). +-record(attrs, {even, odd, searched, deleted, changed_odd, changed_even, new_odd, new_even}). -define(EVEN(Row), ((Row rem 2) =:= 0)). -define(BG_EVEN, {230,230,250}). -define(BG_ODD, {255,255,255}). -define(BG_DELETED, {100,100,100}). --define(FG_DELETED, {240,30,30}). +-define(FG_DELETED, {230,230,230}). -define(BG_SEARCHED,{235,215,90}). --define(BG_CHANGED, {230,230,250}). +-define(BG_CHANGED, {184,207,184}). +-define(BG_NEW, {123,168,123}). -define(LCTRL_WDECR, 4). %% Remove some pixels in column width to avoid creating unnecessary scrollbar diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 463c42308d..f11ccfb752 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -339,10 +339,18 @@ create_attrs() -> #attrs{even = wxListItemAttr:new(Text, ?BG_EVEN, Font), odd = wxListItemAttr:new(Text, ?BG_ODD, Font), deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font), - changed = wxListItemAttr:new(Text, ?BG_CHANGED, Font), + changed_even = wxListItemAttr:new(Text, mix(?BG_CHANGED,?BG_EVEN), Font), + changed_odd = wxListItemAttr:new(Text, mix(?BG_CHANGED,?BG_ODD), Font), + new_even = wxListItemAttr:new(Text, mix(?BG_NEW,?BG_EVEN), Font), + new_odd = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_ODD), Font), searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font) }. +mix(RGB,_) -> RGB. + +%% mix({R,G,B},{MR,MG,MB}) -> +%% {trunc(R*MR/255), trunc(G*MG/255), trunc(B*MB/255)}. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% get_box_info({Title, List}) when is_list(List) -> {Title, ?wxALIGN_LEFT, List}; diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 10e2f12e0f..0be8c18893 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -66,6 +66,7 @@ -record(holder, {parent, info, + etop, sort=#sort{}, accum=[], attrs, @@ -435,13 +436,14 @@ set_focus([Old|_], [New|_], Grid) -> init_table_holder(Parent, Attrs) -> Backend = spawn_link(node(), observer_backend,etop_collect,[self()]), table_holder(#holder{parent=Parent, - info=#etop_info{procinfo=[]}, + etop=#etop_info{}, + info=array:new(), node=node(), backend_pid=Backend, attrs=Attrs }). -table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, +table_holder(#holder{info=Info, attrs=Attrs, node=Node, backend_pid=Backend}=S0) -> receive {get_row, From, Row, Col} -> @@ -488,7 +490,8 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, From ! {self(), S0#holder.accum == true}, table_holder(S0); {dump, Fd} -> - etop_txt:do_update(Fd, S0#holder.info, #opts{node=Node}), + EtopInfo = (S0#holder.etop)#etop_info{procinfo=array:to_list(Info)}, + etop_txt:do_update(Fd, EtopInfo, #opts{node=Node}), file:close(Fd), table_holder(S0); stop -> @@ -498,23 +501,23 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, table_holder(S0) end. -change_sort(Col, S0=#holder{parent=Parent, info=EI=#etop_info{procinfo=Data}, sort=Sort0}) -> +change_sort(Col, S0=#holder{parent=Parent, info=Data, sort=Sort0}) -> {Sort, ProcInfo}=sort(Col, Sort0, Data), - Parent ! {holder_updated, length(Data)}, - S0#holder{info=EI#etop_info{procinfo=ProcInfo}, sort=Sort}. + Parent ! {holder_updated, array:size(Data)}, + S0#holder{info=ProcInfo, sort=Sort}. change_accum(true, S0) -> S0#holder{accum=true}; -change_accum(false, S0=#holder{info=#etop_info{procinfo=Info}}) -> +change_accum(false, S0=#holder{info=Info}) -> self() ! refresh, - S0#holder{accum=lists:sort(Info)}. + S0#holder{accum=lists:sort(array:to_list(Info))}. handle_update(EI=#etop_info{procinfo=ProcInfo0}, S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> {ProcInfo1, S1} = accum(ProcInfo0, S0), {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1), - Parent ! {holder_updated, length(ProcInfo)}, - S1#holder{info=EI#etop_info{procinfo=ProcInfo}}. + Parent ! {holder_updated, array:size(ProcInfo)}, + S1#holder{info=ProcInfo, etop=EI#etop_info{procinfo=[]}}. accum(ProcInfo, State=#holder{accum=true}) -> {ProcInfo, State}; @@ -532,12 +535,18 @@ accum2([PI|PIs], Old, Acc) -> accum2(PIs, Old, [PI|Acc]); accum2([], _, Acc) -> Acc. +sort(Col, Opt, Table) + when not is_list(Table) -> + sort(Col,Opt,array:to_list(Table)); sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) -> - {Opt#sort{sort_incr=not Bool}, lists:reverse(Table)}; + {Opt#sort{sort_incr=not Bool}, + array:from_list(lists:reverse(Table))}; sort(Col, S=#sort{sort_incr=true}, Table) -> - {S#sort{sort_key=Col}, lists:keysort(col_to_element(Col), Table)}; + {S#sort{sort_key=Col}, + array:from_list(lists:keysort(col_to_element(Col), Table))}; sort(Col, S=#sort{sort_incr=false}, Table) -> - {S#sort{sort_key=Col}, lists:reverse(lists:keysort(col_to_element(Col), Table))}. + {S#sort{sort_key=Col}, + array:from_list(lists:reverse(lists:keysort(col_to_element(Col), Table)))}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -552,40 +561,50 @@ col_to_element(?COL_FUN) -> #etop_proc_info.cf; col_to_element(?COL_MSG) -> #etop_proc_info.mq. get_pids(From, Indices, ProcInfo) -> - Processes = [(lists:nth(I+1, ProcInfo))#etop_proc_info.pid || I <- Indices], + Processes = [(array:get(I, ProcInfo))#etop_proc_info.pid || I <- Indices], From ! {self(), Processes}. get_name_or_pid(From, Indices, ProcInfo) -> Get = fun(#etop_proc_info{name=Name}) when is_atom(Name) -> Name; (#etop_proc_info{pid=Pid}) -> Pid end, - Processes = [Get(lists:nth(I+1, ProcInfo)) || I <- Indices], + Processes = [Get(array:get(I, ProcInfo)) || I <- Indices], From ! {self(), Processes}. - get_row(From, Row, pid, Info) -> Pid = case Row =:= -1 of true -> {error, undefined}; - false -> {ok, get_procinfo_data(?COL_PID, lists:nth(Row+1, Info))} + false -> {ok, get_procinfo_data(?COL_PID, array:get(Row, Info))} end, From ! {self(), Pid}; get_row(From, Row, Col, Info) -> - Data = case Row+1 > length(Info) of + Data = case Row > array:size(Info) of true -> ""; false -> - ProcInfo = lists:nth(Row+1, Info), + ProcInfo = array:get(Row, Info), get_procinfo_data(Col, ProcInfo) end, From ! {self(), observer_lib:to_str(Data)}. get_rows_from_pids(From, Pids0, Info) -> - Res = lists:foldl(fun(Pid, Data = {Ids, Pids}) -> - case index(Pid, Info, 0) of - false -> Data; - Index -> {[Index|Ids], [Pid|Pids]} - end - end, {[],[]}, Pids0), + Search = fun(Idx, #etop_proc_info{pid=Pid}, Acc0={Pick0, {Idxs, Pids}}) -> + case ordsets:is_element(Pid, Pick0) of + true -> + Acc = {[Idx|Idxs],[Pid|Pids]}, + Pick = ordsets:del_element(Pid, Pick0), + case Pick =:= [] of + true -> throw(Acc); + false -> {Pick, Acc} + end; + false -> Acc0 + end + end, + Res = try + {_, R} = array:foldl(Search, {ordsets:from_list(Pids0), {[],[]}}, Info), + R + catch R0 -> R0 + end, From ! {self(), Res}. get_attr(From, Row, Attrs) -> @@ -594,7 +613,3 @@ get_attr(From, Row, Attrs) -> false -> Attrs#attrs.odd end, From ! {self(), Attribute}. - -index(Pid, [#etop_proc_info{pid=Pid}|_], Index) -> Index; -index(Pid, [_|PI], Index) -> index(Pid, PI, Index+1); -index(_, _, _) -> false. diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index b4832d9599..59fe5b5670 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -98,7 +98,7 @@ init([Parent, Opts]) -> ets -> "TV Ets: " ++ Title0; mnesia -> "TV Mnesia: " ++ Title0 end, - Frame = wxFrame:new(Parent, ?wxID_ANY, Title, [{size, {800, 300}}]), + Frame = wxFrame:new(Parent, ?wxID_ANY, Title, [{size, {800, 600}}]), IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"), Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), wxFrame:setIcon(Frame, Icon), @@ -261,11 +261,12 @@ handle_event(#wx{id=?ID_EDIT}, State = #state{selected=Index}) -> handle_event(#wx{id=?ID_DELETE}, State = #state{selected=undefined}) -> {noreply, State}; handle_event(#wx{id=?ID_DELETE}, - State = #state{pid=Pid, status=StatusBar, selected=Index}) -> + State = #state{grid=Grid, pid=Pid, status=StatusBar, selected=Index}) -> Str = get_row(Pid, Index, all), Pid ! {delete, Index}, wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~s",[Str])), - {noreply, State}; + wxListCtrl:setItemState(Grid, Index, 0, ?wxLIST_STATE_FOCUSED), + {noreply, State#state{selected=undefined}}; handle_event(#wx{id=?wxID_CLOSE}, State = #state{frame=Frame}) -> wxFrame:destroy(Frame), @@ -279,8 +280,8 @@ handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, State = #state{grid=Grid}) -> try Row0 = list_to_integer(Str), - Row1 = min(0, Row0), - Row = max(wxListCtrl:getItemCount(Grid)-1,Row1), + Row1 = max(0, Row0), + Row = min(wxListCtrl:getItemCount(Grid)-1,Row1), wxListCtrl:ensureVisible(Grid, Row), ok catch _:_ -> ok @@ -289,7 +290,9 @@ handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, %% Search functionality handle_event(#wx{id=?ID_SEARCH}, - State = #state{sizer=Sz, search=Search}) -> + State = #state{grid=Grid, sizer=Sz, search=Search, selected=Index}) -> + is_integer(Index) andalso + wxListCtrl:setItemState(Grid, Index, 0, ?wxLIST_STATE_FOCUSED), wxSizer:show(Sz, Search#search.win), wxWindow:setFocus(Search#search.search), wxSizer:layout(Sz), @@ -321,7 +324,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdS Pid ! {mark_search_hit, false}, case search(Pid, Str, Pos, Dir, Case) of false -> - wxStatusBar:setStatusText(SB, "Not found"), + wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~s",[Str])), Pid ! {mark_search_hit, Find#find.start}, wxListCtrl:refreshItem(Grid, Find#find.start), {noreply, State#state{search=Search#search{find=Find#find{found=false}}}}; @@ -355,7 +358,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}}, Pid ! {mark_search_hit, false}, case search(Pid, Str, Cont#find.start, Dir, Case) of false -> - wxStatusBar:setStatusText(SB, "Not found"), + wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~s",[Str])), {noreply, State}; Row -> wxListCtrl:ensureVisible(Grid, Row), @@ -402,8 +405,11 @@ handle_info({new_cols, New}, State = #state{grid=Grid, columns=Cols0}) -> Cols = add_columns(Grid, Cols0, New), {noreply, State#state{columns=Cols}}; +handle_info({refresh, Min, Min}, State = #state{grid=Grid}) -> + wxListCtrl:refreshItem(Grid, Min), %% Avoid assert in wx below if Max is 0 + {noreply, State}; handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> - Max > 0 andalso wxListCtrl:refreshItems(Grid, Min, Max), + wxListCtrl:refreshItems(Grid, Min, Max), {noreply, State}; handle_info(refresh_interval, State = #state{pid=Pid}) -> @@ -426,10 +432,14 @@ handle_info(_Event, State) -> terminate(_Event, #state{pid=Pid, attrs=Attrs}) -> %% ListItemAttr are not auto deleted - #attrs{odd=Odd, deleted=D, changed=Ch, searched=S} = Attrs, - wxListItemAttr:destroy(Odd), + #attrs{odd=Odd, even=Even, deleted=D, searched=S, + changed_odd=Ch1, changed_even=Ch2, + new_odd=New1, new_even=New2 + } = Attrs, + wxListItemAttr:destroy(Odd), wxListItemAttr:destroy(Even), wxListItemAttr:destroy(D), - wxListItemAttr:destroy(Ch), + wxListItemAttr:destroy(Ch1),wxListItemAttr:destroy(Ch2), + wxListItemAttr:destroy(New1),wxListItemAttr:destroy(New2), wxListItemAttr:destroy(S), unlink(Pid), exit(Pid, window_closed), @@ -473,7 +483,7 @@ search(Table, Str, Row, Dir, Case) -> end. -record(holder, {node, parent, pid, - table=[], n=0, columns, + table=array:new(), n=0, columns, temp=[], search, source, tabid, @@ -507,6 +517,7 @@ table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) -> S1 = handle_new_data_chunk(Data, S0), table_holder(S1); {sort, Col} -> + Parent ! {refresh, 0, S0#holder.n-1}, table_holder(sort(Col, S0)); {search, Data} -> table_holder(search(Data, S0)); @@ -530,11 +541,14 @@ table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) -> table_holder(S0); What -> io:format("Table holder got ~p~n",[What]), + Parent ! {refresh, 0, S0#holder.n-1}, table_holder(S0) end. handle_new_data_chunk(Data, S0 = #holder{columns=Cols, parent=Parent}) -> - S1 = #holder{columns=NewCols} = handle_new_data_chunk2(Data, S0), + S1 = #holder{n=N,columns=NewCols} = handle_new_data_chunk2(Data, S0), + Parent ! {no_rows, N}, + Parent ! {refresh, 0, N-1}, case NewCols =:= Cols of true -> S1; false -> @@ -543,15 +557,12 @@ handle_new_data_chunk(Data, S0 = #holder{columns=Cols, parent=Parent}) -> end. handle_new_data_chunk2('$end_of_table', - S0 = #holder{parent=Parent, sort=Opt, - key=Key, + S0 = #holder{sort=Opt0, key=Key, table=Old, temp=New}) -> - Table = merge(Old, New, Key), - N = length(Table), - Parent ! {no_rows, N}, - sort(Opt#opt.sort_key, S0#holder{n=N, pid=undefine, - sort=Opt#opt{sort_key = undefined}, - table=Table, temp=[]}); + Merged = merge(array:to_list(Old), New, Key), + {Opt,Sorted} = sort(Opt0#opt.sort_key, Opt0#opt{sort_key = undefined}, Merged), + SortedA = array:from_list(Sorted), + S0#holder{sort=Opt, table=SortedA, n=array:size(SortedA), temp=[], pid=undefined}; handle_new_data_chunk2(Data, S0 = #holder{columns=Cols0, source=ets, temp=Tab0}) -> {Tab, Cols} = parse_ets_data(Data, Cols0, Tab0), S0#holder{columns=Cols, temp=Tab}; @@ -566,10 +577,9 @@ parse_ets_data([Recs|Rs], C0, Tab0) -> parse_ets_data([], Cols, Tab) -> {Tab, Cols}. -sort(Col, S=#holder{n=N, parent=Parent, sort=Opt0, table=Table0}) -> - {Opt, Table} = sort(Col, Opt0, Table0), - Parent ! {refresh, 0, N-1}, - S#holder{sort=Opt, table=Table}. +sort(Col, S=#holder{sort=Opt0, table=Table0}) -> + {Opt, Table} = sort(Col, Opt0, array:to_list(Table0)), + S#holder{sort=Opt, table=array:from_list(Table)}. sort(Col, Opt = #opt{sort_key=Col, sort_incr=Bool}, Table) -> {Opt#opt{sort_incr=not Bool}, lists:reverse(Table)}; @@ -597,7 +607,7 @@ keysort(Col, Table) -> lists:sort(Sort, Table). search([Str, Row, Dir0, CaseSens], - S=#holder{parent=Parent, table=Table0}) -> + S=#holder{parent=Parent, n=N, table=Table}) -> Opt = case CaseSens of true -> []; false -> [caseless] @@ -607,32 +617,26 @@ search([Str, Row, Dir0, CaseSens], false -> -1 end, Res = case re:compile(Str, Opt) of - {ok, Re} -> - Table = - case Dir0 of - true -> - lists:nthtail(Row, Table0); - false -> - lists:reverse(lists:sublist(Table0, Row+1)) - end, - search(Row, Dir, Re, Table); + {ok, Re} -> re_search(Row, Dir, N, Re, Table); {error, _} -> false end, Parent ! {self(), Res}, S#holder{search=Res}. -search(Row, Dir, Re, [ [Term|_] |Table]) -> +re_search(Row, Dir, N, Re, Table) when Row >= 0, Row < N -> + [Term|_] = array:get(Row, Table), Str = format(Term), Res = re:run(Str, Re), case Res of - nomatch -> search(Row+Dir, Dir, Re, Table); - {match,_} -> Row + nomatch -> re_search(Row+Dir, Dir, N, Re, Table); + {match,_} -> + Row end; -search(_, _, _, []) -> +re_search(_, _, _, _, _) -> false. get_row(From, Row, Col, Table) -> - case lists:nth(Row+1, Table) of + case array:get(Row, Table) of [Object|_] when Col =:= all -> From ! {self(), format(Object)}; [Object|_] when Col =:= all_multiline -> @@ -647,14 +651,15 @@ get_attr(From, Row, #holder{attrs=Attrs, search=Row}) -> What = Attrs#attrs.searched, From ! {self(), What}; get_attr(From, Row, #holder{table=Table, attrs=Attrs}) -> - What = case lists:nth(Row+1, Table) of + Odd = (Row rem 2) > 0, + What = case array:get(Row, Table) of [_|deleted] -> Attrs#attrs.deleted; - [_|changed] -> Attrs#attrs.changed; - [_|new] -> Attrs#attrs.changed; - _ when (Row rem 2) > 0 -> - Attrs#attrs.odd; - _ -> - Attrs#attrs.even + [_|changed] when Odd -> Attrs#attrs.changed_odd; + [_|changed] -> Attrs#attrs.changed_even; + [_|new] when Odd -> Attrs#attrs.new_odd; + [_|new] -> Attrs#attrs.new_even; + _ when Odd -> Attrs#attrs.odd; + _ -> Attrs#attrs.even end, From ! {self(), What}. @@ -665,19 +670,29 @@ merge(Old, New, Key) -> merge2([[Obj|_]|Old], [Obj|New], Key) -> [[Obj]|merge2(Old, New, Key)]; -merge2([[A|_]|Old], [B|New], Key) +merge2([[A|Op]|Old], [B|New], Key) when element(Key, A) == element(Key, B) -> - [[B|changed]|merge2(Old, New, Key)]; -merge2([[A|_]|Old], New = [B|_], Key) + case Op of + deleted -> + [[B|new]|merge2(Old, New, Key)]; + _ -> + [[B|changed]|merge2(Old, New, Key)] + end; +merge2([[A|Op]|Old], New = [B|_], Key) when element(Key, A) < element(Key, B) -> - [[A|deleted]|merge2(Old, New, Key)]; + case Op of + deleted -> merge2(Old, New, Key); + _ -> [[A|deleted]|merge2(Old, New, Key)] + end; merge2(Old = [[A|_]|_], [B|New], Key) when element(Key, A) > element(Key, B) -> [[B|new]|merge2(Old, New, Key)]; merge2([], New, _Key) -> [[N|new] || N <- New]; merge2(Old, [], _Key) -> - [[O|deleted] || [O|_] <- Old]. + lists:foldl(fun([_O|deleted], Acc) -> Acc; + ([O|_], Acc) -> [[O|deleted]|Acc] + end, [], Old). delete_row(Row, S0 = #holder{parent=Parent}) -> @@ -691,7 +706,7 @@ delete_row(Row, S0 = #holder{parent=Parent}) -> delete(Row, #holder{tabid=Id, table=Table, source=Source, node=Node}) -> - [Object|_] = lists:nth(Row+1, Table), + [Object|_] = array:get(Row, Table), try case Source of ets -> -- cgit v1.2.3 From cf20035dc7a4fbab47ce17b99b674e4db5eb7a07 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Thu, 14 Nov 2013 15:34:55 +0100 Subject: observer: Fix memory and scheduler info and handle missing fields Fix app viewer crash --- lib/observer/src/observer_app_wx.erl | 15 ++++++++++----- lib/observer/src/observer_lib.erl | 31 +++++++++++++++++++++---------- lib/observer/src/observer_perf_wx.erl | 29 ++++++++++++++++------------- lib/observer/src/observer_sys_wx.erl | 9 ++++++--- 4 files changed, 53 insertions(+), 31 deletions(-) (limited to 'lib/observer/src') diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 82395fbe08..a710bd0a69 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -178,11 +178,16 @@ handle_event(#wx{id=Id, event=_Sz=#wxSize{size=Size}}, {noreply, State}; handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}}, - S0=#state{app=#app{ptree=Tree}, app_w=AppWin}) -> - {X,Y} = wxScrolledWindow:calcUnscrolledPosition(AppWin, X0, Y0), - Hit = locate_node(X,Y, [Tree]), - State = handle_mouse_click(Hit, Type, S0), - {noreply, State}; + S0=#state{app=App, app_w=AppWin}) -> + case App of + #app{ptree=Tree} -> + {X,Y} = wxScrolledWindow:calcUnscrolledPosition(AppWin, X0, Y0), + Hit = locate_node(X,Y, [Tree]), + State = handle_mouse_click(Hit, Type, S0), + {noreply, State}; + _ -> + {noreply, S0} + end; handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, State = #state{sel=undefined}) -> diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index f11ccfb752..6288dd3a45 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -144,29 +144,29 @@ fill_info([{dynamic, Key}|Rest], Data) %% 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); + undefined -> [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) -> case get_value(Key, Data) of - undefined -> fill_info(Rest, Data); + undefined -> [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); + undefined -> [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 -> fill_info(Rest, Data); + undefined -> [undefined | fill_info(Rest, Data)]; Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)] end; fill_info([{Str, Attrib, {Format, Key}}|Rest], Data) when is_atom(Key); is_function(Key), is_atom(Format) -> case get_value(Key, Data) of - undefined -> fill_info(Rest, Data); + undefined -> [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) -> @@ -189,6 +189,8 @@ update_info([Fields|Fs], [{_Header, _Attrib, SubStructure}| Rest]) -> update_info([], []) -> ok. +update_info2([undefined|Fs], [_|Rest]) -> + update_info2(Fs, Rest); update_info2([Scroll = {_, _, _}|Fs], [{_, NewInfo}|Rest]) -> update_scroll_boxes(Scroll, NewInfo), update_info2(Fs, Rest); @@ -198,6 +200,9 @@ update_info2([Field|Fs], [{_Str, {click, Value}}|Rest]) -> update_info2([Field|Fs], [{_Str, Value}|Rest]) -> wxTextCtrl:setValue(Field, to_str(Value)), update_info2(Fs, Rest); +update_info2([Field|Fs], [undefined|Rest]) -> + wxTextCtrl:setValue(Field, ""), + update_info2(Fs, Rest); update_info2([], []) -> ok. update_scroll_boxes({_, _, 0}, {_, []}) -> ok; @@ -223,10 +228,10 @@ to_str({bytes, B}) -> MB = KB div 1024, GB = MB div 1024, if - GB > 10 -> integer_to_list(GB) ++ " gB"; - MB > 10 -> integer_to_list(MB) ++ " mB"; + GB > 10 -> integer_to_list(GB) ++ " GB"; + MB > 10 -> integer_to_list(MB) ++ " MB"; KB > 0 -> integer_to_list(KB) ++ " kB"; - true -> integer_to_list(B) ++ " B " + true -> integer_to_list(B) ++ " B" end; to_str({time_ms, MS}) -> S = MS div 1000, @@ -396,7 +401,9 @@ create_box(Panel, {scroll_boxes,Data}) -> AddBox = fun({Title,Proportion,Format = {_,_}}) -> add_box(Panel, OuterBox, Cursor, Title, Proportion, Format); ({Title, Format = {_,_}}) -> - add_box(Panel, OuterBox, Cursor, Title, 1, Format) + add_box(Panel, OuterBox, Cursor, Title, 1, Format); + (undefined) -> + undefined end, Boxes = [AddBox(Entry) || Entry <- Data], wxCursor:destroy(Cursor), @@ -457,7 +464,9 @@ create_box(Panel, Data) -> wxTextCtrl:setMinSize(Field,{0,H}), wxSizer:add(Box, Line, [{proportion,0},{flag,?wxEXPAND}]), - Field + Field; + (undefined) -> + undefined end, InfoFields = [AddRow(Entry) || Entry <- Info], {Box, InfoFields}. @@ -512,6 +521,8 @@ get_max_size(Txt,[{Desc,_}|Info],MaxX,MaxY) -> true -> get_max_size(Txt,Info,MaxX,MaxY) end; +get_max_size(Txt,[undefined|Info],MaxX,MaxY) -> + get_max_size(Txt,Info,MaxX,MaxY); get_max_size(_,[],X,Y) -> {X+2,Y}. diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index 54c98f3ba3..8173349ed7 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012. All Rights Reserved. +%% Copyright Ericsson AB 2012-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 @@ -283,7 +283,12 @@ mem_types() -> lmax([]) -> 0; lmax(List) -> - lists:max([lists:max(tuple_to_list(T)) || T <- List]). + Max = [lists:max(tuple_to_list(T)) || T <- List, + tuple_size(T) > 0], + case Max of + [] -> 0; + _ -> lists:max(Max) + end. calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) -> [100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)]; @@ -301,25 +306,23 @@ draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active) {X0,Y0,WS,HS} = draw_borders(Id, NoGraphs, DC, Size, Max, Paint), Last = 60*WS+X0-1, Start = max(61-Len, 0)*WS+X0 - Offset*WS, - case Hs of - [] -> ignore; - [_] -> ignore; - _ -> + Samples = length(Hs), + case Active andalso Samples > 1 andalso NoGraphs > 0 of + true -> Draw = fun(N) -> Lines = make_lines(Hs, Start, N, {X0,Max*HS,Last}, Y0, WS, HS), setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)), strokeLines(DC, Lines), N+1 end, - [Draw(I) || I <- lists:seq(NoGraphs, 1, -1)] - end, - case Active of + [Draw(I) || I <- lists:seq(NoGraphs, 1, -1)]; false -> - NotActive = "Service not available", + Info = case Active andalso Samples =< 1 of + true -> "Waiting on data"; + false -> "Information not available" + end, setFont(DC, Small, {0,0,0}), - drawText(DC, NotActive, X0 + 100, element(2,Size) div 2); - true -> - ignore + drawText(DC, Info, X0 + 100, element(2,Size) div 2) end, ok. diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 31800cf12a..8321d6eefe 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -167,9 +167,12 @@ info_fields() -> {"Async thread pool size", thread_pool_size} ]}, {"CPU's and Threads", - [{"System Logical CPU's", logical_processors}, - {"Erlang Logical CPU's", logical_processors_online}, - {"Used Logical CPU's", logical_processors_available} + [{"Logical CPU's", logical_processors}, + {"Online Logical CPU's", logical_processors_online}, + {"Available Logical CPU's", logical_processors_available}, + {"Schedulers", schedulers}, + {"Online schedulers", schedulers_online}, + {"Available schedulers", schedulers_available} ]} ], Stat = [{"Memory Usage", right, -- cgit v1.2.3 From 5a30dd40a691d610def7b1f00cf39ed0d78eb900 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 19 Nov 2013 11:00:32 +0100 Subject: observer: Use crashdump_viewer's term viewer to display large terms and binaries --- lib/observer/src/cdv_bin_wx.erl | 5 +- lib/observer/src/cdv_html_page.erl | 19 +++-- lib/observer/src/cdv_term_wx.erl | 45 +++++++----- lib/observer/src/crashdump_viewer_html.erl | 97 +++++++++++++++++-------- lib/observer/src/observer_procinfo.erl | 49 ++++++++----- lib/observer/src/observer_term_wx.erl | 113 ----------------------------- 6 files changed, 142 insertions(+), 186 deletions(-) delete mode 100644 lib/observer/src/observer_term_wx.erl (limited to 'lib/observer/src') diff --git a/lib/observer/src/cdv_bin_wx.erl b/lib/observer/src/cdv_bin_wx.erl index d2cbcfb747..6cf344f945 100644 --- a/lib/observer/src/cdv_bin_wx.erl +++ b/lib/observer/src/cdv_bin_wx.erl @@ -21,7 +21,10 @@ detail_pages/0]). %% Callbacks for cdv_detail_win -get_details(Id) -> +get_details({_, {T,Key}}) -> + [{Key,Term}] = ets:lookup(T,Key), + {ok,{"Expanded Binary", Term, []}}; +get_details({cdv, Id}) -> {ok,Bin} = crashdump_viewer:expand_binary(Id), {ok,{"Expanded Binary", Bin, []}}. diff --git a/lib/observer/src/cdv_html_page.erl b/lib/observer/src/cdv_html_page.erl index b2d059f7f8..d77238c6cf 100644 --- a/lib/observer/src/cdv_html_page.erl +++ b/lib/observer/src/cdv_html_page.erl @@ -84,16 +84,23 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, "#Binary?" ++ BinSpec -> [{"offset",Off},{"size",Size},{"pos",Pos}] = httpd:parse_query(BinSpec), - Id = {list_to_integer(Off), - list_to_integer(Size), - list_to_integer(Pos)}, + Id = {cdv, {list_to_integer(Off), + list_to_integer(Size), + list_to_integer(Pos)}}, + expand(Id,cdv_bin_wx,State); + "#OBSBinary?" ++ BinSpec -> + [{"offset",Off},{"size",Size},{"pos",Pos}] = + httpd:parse_query(BinSpec), + Id = {obs, {Tab, {list_to_integer(Off), + list_to_integer(Size), + list_to_integer(Pos)}}}, expand(Id,cdv_bin_wx,State); "#Term?" ++ TermKeys -> [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(TermKeys), - Id = {Tab,{list_to_integer(Key1), - list_to_integer(Key2), - list_to_integer(Key3)}}, + Id = {cdv, {Tab,{list_to_integer(Key1), + list_to_integer(Key2), + list_to_integer(Key3)}}}, expand(Id,cdv_term_wx,State); _ -> cdv_virtual_list:start_detail_win(Target), diff --git a/lib/observer/src/cdv_term_wx.erl b/lib/observer/src/cdv_term_wx.erl index e029a75b29..9b83249eae 100644 --- a/lib/observer/src/cdv_term_wx.erl +++ b/lib/observer/src/cdv_term_wx.erl @@ -21,26 +21,28 @@ detail_pages/0]). %% Callbacks for cdv_detail_win -get_details({T,Key}) -> +get_details({_, {T,Key}}) -> [{Key,Term}] = ets:lookup(T,Key), - {ok,{"Expanded Term", Term, []}}. + {ok,{"Expanded Term", [Term, T], []}}. detail_pages() -> [{"Term", fun init_term_page/2}]. -init_term_page(ParentWin, Term) -> +init_term_page(ParentWin, [Term, Tab]) -> + Expanded = expand(Term, true), + BinSaved = expand(Term, Tab), cdv_multi_panel:start_link( ParentWin, - [{"Format \~p",cdv_html_page,format_term_fun("~p",Term)}, - {"Format \~tp",cdv_html_page,format_term_fun("~tp",Term)}, - {"Format \~w",cdv_html_page,format_term_fun("~w",Term)}, - {"Format \~s",cdv_html_page,format_term_fun("~s",expand(Term))}, - {"Format \~ts",cdv_html_page,format_term_fun("~ts",expand(Term))}]). + [{"Format \~p",cdv_html_page,format_term_fun("~p",BinSaved,Tab)}, + {"Format \~tp",cdv_html_page,format_term_fun("~tp",BinSaved,Tab)}, + {"Format \~w",cdv_html_page,format_term_fun("~w",BinSaved,Tab)}, + {"Format \~s",cdv_html_page,format_term_fun("~s",Expanded,Tab)}, + {"Format \~ts",cdv_html_page,format_term_fun("~ts",Expanded,Tab)}]). -format_term_fun(Format,Term) -> +format_term_fun(Format,Term,Tab) -> fun() -> try io_lib:format(Format,[Term]) of - Str -> plain_html(Str) + Str -> {expand, plain_html(Str), Tab} catch error:badarg -> Warning = "This term can not be formatted with " ++ Format, crashdump_viewer_html:warning(Warning) @@ -50,17 +52,24 @@ format_term_fun(Format,Term) -> plain_html(Text) -> crashdump_viewer_html:plain_page(Text). -expand(['#CDVBin',Offset,Size,Pos]) -> +expand(['#CDVBin',Offset,Size,Pos], true) -> {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), Bin; -expand([H|T]) -> - case expand(T) of +expand(Bin, Tab) when is_binary(Bin), not is_boolean(Tab) -> + <> = Bin, + Size = byte_size(Bin), + Hash = erlang:phash2(Bin), + Key = {Preview, Size, Hash}, + ets:insert(Tab, {Key,Bin}), + ['#OBSBin',Preview,Size,Hash]; +expand([H|T], Expand) -> + case expand(T, Expand) of ET when is_list(ET) -> - [expand(H)|ET]; + [expand(H, Expand)|ET]; ET -> % The tail is an expanded binary - cannot append with | - [expand(H),ET] + [expand(H, Expand),ET] end; -expand(Tuple) when is_tuple(Tuple) -> - list_to_tuple(expand(tuple_to_list(Tuple))); -expand(Term) -> +expand(Tuple, Expand) when is_tuple(Tuple) -> + list_to_tuple(expand(tuple_to_list(Tuple), Expand)); +expand(Term, _) -> Term. diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl index 9cd4d6748a..2cc28d04be 100644 --- a/lib/observer/src/crashdump_viewer_html.erl +++ b/lib/observer/src/crashdump_viewer_html.erl @@ -590,15 +590,31 @@ all_or_expand(Tab,Term) -> all_or_expand(Tab,Term,Preview,Exp). all_or_expand(_Tab,_Term,Str,false) -> href_proc_port(lists:flatten(Str)); -all_or_expand(Tab,Term,Preview,true) -> +all_or_expand(Tab,Term,Preview,true) + when not is_binary(Term) -> Key = {Key1,Key2,Key3} = now(), ets:insert(Tab,{Key,Term}), - [href_proc_port(lists:flatten(Preview),false), $\n, - href("TARGET=\"expanded\"",["#Term?key1="++integer_to_list(Key1)++ - "&key2="++integer_to_list(Key2)++ - "&key3="++integer_to_list(Key3)], + [href_proc_port(lists:flatten(Preview), false), $\n, + href("TARGET=\"expanded\"", + ["#Term?key1="++integer_to_list(Key1)++ + "&key2="++integer_to_list(Key2)++ + "&key3="++integer_to_list(Key3)], + "Click to expand above term")]; +all_or_expand(Tab,Bin,PreviewStr,true) when is_binary(Bin) -> + <> = Bin, + Size = byte_size(Bin), + Hash = erlang:phash2(Bin), + Key = {Preview, Size, Hash}, + ets:insert(Tab,{Key,Bin}), + [href_proc_port(lists:flatten(PreviewStr), false), $\n, + href("TARGET=\"expanded\"", + ["#OBSBinary?key1="++integer_to_list(Preview)++ + "&key2="++integer_to_list(Size)++ + "&key3="++integer_to_list(Hash)], "Click to expand above term")]. + + color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). @@ -1131,31 +1147,10 @@ href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 -> href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB); href_proc_port("['#CDVBin'"++T,Acc,LTB) -> %% Binary written by crashdump_viewer:parse_heap_term(...) - {OffsetSizePos,Rest} = split($],T), - BinStr = - case string:tokens(OffsetSizePos,",.|") of - [Offset,Size,Pos] -> - Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, - {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), - PreviewBytes = binary_to_list(PreviewBin), - PreviewStr = ["<<", - [integer_to_list(X)++"," || X <- PreviewBytes], - "...(", - observer_lib:to_str({bytes,Size}), - ")>>"], - if LTB -> - href("TARGET=\"expanded\"", - ["#Binary?offset="++Offset++ - "&size="++Size++ - "&pos="++Pos], - PreviewStr); - true -> - PreviewStr - end; - _ -> - "<< ... >>" - end, - href_proc_port(Rest,[BinStr|Acc],LTB); + href_proc_bin(cdv, T, Acc, LTB); +href_proc_port("['#OBSBin'"++T,Acc,LTB) -> + %% Binary written by crashdump_viewer:parse_heap_term(...) + href_proc_bin(obs, T, Acc, LTB); href_proc_port("['#CDVPort'"++T,Acc,LTB) -> %% Port written by crashdump_viewer:parse_term(...) {Port0,Rest} = split($],T), @@ -1211,6 +1206,48 @@ href_proc_port([H|T],Acc,LTB) -> href_proc_port([],Acc,_) -> lists:reverse(Acc). +href_proc_bin(From, T, Acc, LTB) -> + {OffsetSizePos,Rest} = split($],T), + BinStr = + case string:tokens(OffsetSizePos,",.| \n") of + [Offset,Size,Pos] when From =:= cdv -> + Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, + {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), + PreviewStr = ["<<", + [integer_to_list(X)++"," || <> <= PreviewBin], + "...(", + observer_lib:to_str({bytes,Size}), + ")>>"], + if LTB -> + href("TARGET=\"expanded\"", + ["#Binary?offset="++Offset++ + "&size="++Size++ + "&pos="++Pos], + PreviewStr); + true -> + PreviewStr + end; + [Preview,Size,Md5] when From =:= obs -> + PreviewBin = <<(list_to_integer(Preview)):80>>, + PreviewStr = ["<<", + [integer_to_list(X)++"," || <> <= PreviewBin], + "...(", + observer_lib:to_str({bytes,list_to_integer(Size)}), + ")>>"], + if LTB -> + href("TARGET=\"expanded\"", + ["#OBSBinary?offset="++Preview++ + "&size="++Size++ + "&pos="++Md5], + PreviewStr); + true -> + PreviewStr + end; + _ -> + "<< ... >>" + end, + href_proc_port(Rest,[BinStr|Acc],LTB). + split(Char,Str) -> split(Char,Str,[]). split(Char,[Char|Str],Acc) -> % match Char diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index cfc22e7093..af2b8dda2e 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -49,7 +49,7 @@ start(Process, ParentFrame, Parent) -> init([Pid, ParentFrame, Parent]) -> try - Table = ets:new(observer_expand,[set,protected]), + Table = ets:new(observer_expand,[set,public]), Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of [] -> io_lib:format("~p",[Pid]); {registered_name, Registered} -> io_lib:format("~p (~p)",[Registered, Pid]); @@ -123,20 +123,33 @@ handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> wxTextCtrl:setForegroundColour(Obj,?wxBLUE), {noreply, State}; -handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href="#Term?"++Keys}}}, +handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}}, #state{frame=Frame,expand_table=T,expand_wins=Opened0}=State) -> - [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(Keys), - Id = {T,{list_to_integer(Key1),list_to_integer(Key2),list_to_integer(Key3)}}, - Opened = - case lists:keyfind(Id,1,Opened0) of - false -> - Win = observer_term_wx:start(Id,Frame), - [{Id,Win}|Opened0]; - {_,Win} -> - wxFrame:raise(Win), - Opened0 - end, - {noreply,State#state{expand_wins=Opened}}; + {Type, Rest} = case Href of + "#Term?"++Keys -> {cdv_term_wx, Keys}; + "#OBSBinary?"++Keys -> {cdv_bin_wx, Keys}; + _ -> {other, Href} + end, + case Type of + other -> + observer ! {open_link, Href}, + {noreply, State}; + Callback -> + [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(Rest), + Id = {observer, {T,{list_to_integer(Key1), + list_to_integer(Key2), + list_to_integer(Key3)}}}, + Opened = + case lists:keyfind(Id,1,Opened0) of + false -> + Win = cdv_detail_win:start_link(Id,Frame,Callback), + [{Id,Win}|Opened0]; + {_,Win} -> + wxFrame:raise(Win), + Opened0 + end, + {noreply,State#state{expand_wins=Opened}} + end; handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) -> observer ! {open_link, Info}, @@ -145,10 +158,6 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) handle_event(Event, _State) -> error({unhandled_event, Event}). -handle_info({expand_win_closed,Id}, #state{expand_wins=Opened0}=State) -> - Opened = lists:keydelete(Id,1,Opened0), - {noreply,State#state{expand_wins=Opened}}; - handle_info(_Info, State) -> %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. @@ -156,6 +165,10 @@ handle_info(_Info, State) -> handle_call(Call, From, _State) -> error({unhandled_call, Call, From}). +handle_cast({detail_win_closed,Id}, #state{expand_wins=Opened0}=State) -> + Opened = lists:keydelete(Id,1,Opened0), + {noreply,State#state{expand_wins=Opened}}; + handle_cast(Cast, _State) -> error({unhandled_cast, Cast}). diff --git a/lib/observer/src/observer_term_wx.erl b/lib/observer/src/observer_term_wx.erl deleted file mode 100644 index 58cb650bef..0000000000 --- a/lib/observer/src/observer_term_wx.erl +++ /dev/null @@ -1,113 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% - --module(observer_term_wx). - --behaviour(wx_object). - --export([start/2]). - --export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, - handle_call/3, handle_info/2]). - --include_lib("wx/include/wx.hrl"). --include("observer_defs.hrl"). - --define(REFRESH, 601). - --record(state, {parent, - frame, - id - }). - -start(Id, ParentFrame) -> - wx_object:start_link(?MODULE, [Id, ParentFrame, self()], []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init([{T,Key}=Id, ParentFrame, Parent]) -> - case ets:lookup(T,Key) of - [{Key,Term}] -> - init(Id, ParentFrame, Parent, Term); - [] -> - observer_lib:display_info_dialog( - "The term does no longer exist.\n" - "Please refresh the process window!"), - {stop, normal} - end. - - - -init(Id, ParentFrame, Parent, Term) -> - Frame=wxFrame:new(ParentFrame, ?wxID_ANY, ["Expanded Term"], - [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]), - MenuBar = wxMenuBar:new(), - Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}], - observer_lib:create_menus(Menus, MenuBar, new_window), - wxFrame:setMenuBar(Frame, MenuBar), - Panel = wxPanel:new(Frame), - Sizer = wxBoxSizer:new(?wxHORIZONTAL), - - Window = observer_lib:html_window(Panel), - Html = crashdump_viewer_html:plain_page(io_lib:format("~p~n",[Term])), - wxHtmlWindow:setPage(Window, Html), - - wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, - {proportion, 1}, - {border, 5}]), - wxPanel:setSizer(Panel, Sizer), - wxFrame:show(Frame), - {Frame, #state{parent=Parent, - frame=Frame, - id=Id - }}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_event(#wx{event=#wxClose{type=close_window}}, State) -> - {stop, normal, State}; - -handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> - {stop, normal, State}; - -handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) -> - observer ! {open_link, Info}, - {noreply, State}; - -handle_event(Event, _State) -> - error({unhandled_event, Event}). - -handle_info(_Info, State) -> - %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), - {noreply, State}. - -handle_call(Call, From, _State) -> - error({unhandled_call, Call, From}). - -handle_cast(Cast, _State) -> - error({unhandled_cast, Cast}). - -terminate(_Reason, #state{parent=Parent,id=Id,frame=Frame}) -> - Parent ! {expand_win_closed, Id}, - case Frame of - undefined -> ok; - _ -> wxFrame:destroy(Frame) - end, - ok. - -code_change(_, _, State) -> - {ok, State}. -- cgit v1.2.3 From 2694299fe15c0553101d2107f6689975d16525dc Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 20 Nov 2013 10:01:16 +0100 Subject: observer: improve wx version of crashdump_viewer * bugfixes * add first test * remove unused code --- lib/observer/src/cdv_dist_wx.erl | 13 +- lib/observer/src/cdv_html_page.erl | 6 +- lib/observer/src/cdv_proc_wx.erl | 2 +- lib/observer/src/crashdump_viewer.erl | 65 +- lib/observer/src/crashdump_viewer_html.erl | 1205 +--------------------------- lib/observer/src/crashdump_viewer_wx.erl | 7 +- 6 files changed, 46 insertions(+), 1252 deletions(-) (limited to 'lib/observer/src') diff --git a/lib/observer/src/cdv_dist_wx.erl b/lib/observer/src/cdv_dist_wx.erl index 6e6284a6ba..3910f4fac5 100644 --- a/lib/observer/src/cdv_dist_wx.erl +++ b/lib/observer/src/cdv_dist_wx.erl @@ -58,10 +58,15 @@ get_detail_cols(_) -> %% 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}}. + case crashdump_viewer:node_info(Id) of + {ok,Info,TW} -> + Proplist = crashdump_viewer:to_proplist(record_info(fields,nod),Info), + Title = io_lib:format("~s (~s)",[Info#nod.name,Id]), + {ok,{Title,Proplist,TW}}; + {error,not_found} -> + Info = "The node you are searching for could not be found.", + {info,Info} + end. detail_pages() -> [{"General Information", fun init_gen_page/2}]. diff --git a/lib/observer/src/cdv_html_page.erl b/lib/observer/src/cdv_html_page.erl index d77238c6cf..0208d1d67a 100644 --- a/lib/observer/src/cdv_html_page.erl +++ b/lib/observer/src/cdv_html_page.erl @@ -89,11 +89,11 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, list_to_integer(Pos)}}, expand(Id,cdv_bin_wx,State); "#OBSBinary?" ++ BinSpec -> - [{"offset",Off},{"size",Size},{"pos",Pos}] = + [{"key1",Preview},{"key2",Size},{"key3",Hash}] = httpd:parse_query(BinSpec), - Id = {obs, {Tab, {list_to_integer(Off), + Id = {obs, {Tab, {list_to_integer(Preview), list_to_integer(Size), - list_to_integer(Pos)}}}, + list_to_integer(Hash)}}}, expand(Id,cdv_bin_wx,State); "#Term?" ++ TermKeys -> [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = diff --git a/lib/observer/src/cdv_proc_wx.erl b/lib/observer/src/cdv_proc_wx.erl index 11f83c79a2..6a8fa0b9be 100644 --- a/lib/observer/src/cdv_proc_wx.erl +++ b/lib/observer/src/cdv_proc_wx.erl @@ -70,7 +70,7 @@ get_details(Id) -> Proplist0 = crashdump_viewer:to_proplist(record_info(fields,proc),Info), Proplist = [{expand_table,Tab}|Proplist0], - Title = io_lib:format("~s (~p)",[Info#proc.name, Id]), + Title = io_lib:format("~s (~s)",[Info#proc.name, Id]), {ok,{Title,Proplist,TW}}; {error,{other_node,NodeId}} -> Info = "The process you are searching for was residing on " diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index ca91f9061a..53e0241711 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -107,7 +107,6 @@ -define(no_distribution,no_distribution). -define(node,node). -define(not_connected,not_connected). --define(num_atoms,num_atoms). -define(old_instr_data,old_instr_data). -define(port,port). -define(proc,proc). @@ -347,10 +346,8 @@ handle_call({port,Id},_From,State=#state{file=File}) -> {ok,PortInfo} -> TW = truncated_warning([{?port,Id}]), {ok,PortInfo,TW}; - {other_node,Node} -> - {error,{other_node,Node}}; - not_found -> - {error,not_found} + Other -> + {error,Other} end, {reply,Reply,State}; handle_call(ports,_From,State=#state{file=File}) -> @@ -384,9 +381,17 @@ handle_call(dist_info,_From,State=#state{file=File}) -> 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]), - Nod=get_node(File,Channel), - {reply,{ok,Nod,TW},State}; + Reply = + case get_node(File,Channel) of + {ok,Nod} -> + TW = truncated_warning([?visible_node, + ?hidden_node, + ?not_connected]), + {ok,Nod,TW}; + {error,Other} -> + {error,Other} + end, + {reply,Reply,State}; handle_call(loaded_mods,_From,State=#state{file=File}) -> TW = truncated_warning([?mod]), {_CC,_OC,Mods} = loaded_mods(File), @@ -400,7 +405,7 @@ handle_call(funs,_From,State=#state{file=File}) -> 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]), + TW = truncated_warning([?atoms]), NumAtoms = try list_to_integer(NumAtoms0) catch error:badarg -> -1 end, Atoms = atoms(File,NumAtoms), {reply,{ok,Atoms,TW},State}; @@ -587,7 +592,7 @@ progress_read(Fd) -> {R,Bytes} = case read(Fd) of {ok,Bin} -> - {{ok,Bin},size(Bin)}; + {{ok,Bin},byte_size(Bin)}; Other -> {Other,0} end, @@ -892,11 +897,7 @@ general_info(File) -> WholeLine -> WholeLine end, - GI0 = get_general_info(Fd,#general_info{created=Created}), - GI = case GI0#general_info.num_atoms of - undefined -> GI0#general_info{num_atoms=get_num_atoms(Fd)}; - _ -> GI0 - end, + GI = get_general_info(Fd,#general_info{created=Created}), {MemTot,MemMax} = case lookup_index(?memory) of @@ -970,35 +971,6 @@ get_general_info(Fd,GenInfo) -> GenInfo end. -get_num_atoms(Fd) -> - case lookup_index(?hash_table,"atom_tab") of - [{_,Pos}] -> - pos_bof(Fd,Pos), - skip_rest_of_line(Fd), % size - skip_rest_of_line(Fd), % used - case line_head(Fd) of - "objs" -> - val(Fd); - _1 -> - get_num_atoms2() - end; - [] -> - get_num_atoms2() - end. -get_num_atoms2() -> - case lookup_index(?num_atoms) of - [] -> - undefined; - [{NA,_Pos}] -> - %% If dump is translated this will exist - case get(truncated) of - true -> - [NA," (visible in dump)"]; % might be more - false -> - NA - end - end. - count() -> {count_index(?proc),count_index(?ets),count_index(?fu),count_index(?timer)}. @@ -1554,12 +1526,12 @@ get_node(File,Channel) -> case ets:select(cdv_dump_index_table,Ms) of [] -> - not_found; + {error,not_found}; [{Type,Pos}] -> Fd = open(File), NodeInfo = get_nodeinfo(Fd,Channel,Type,Pos), close(Fd), - NodeInfo + {ok,NodeInfo} end. %%----------------------------------------------------------------- @@ -2509,7 +2481,6 @@ tag_to_atom("mod") -> ?mod; tag_to_atom("no_distribution") -> ?no_distribution; tag_to_atom("node") -> ?node; tag_to_atom("not_connected") -> ?not_connected; -tag_to_atom("num_atoms") -> ?num_atoms; tag_to_atom("old_instr_data") -> ?old_instr_data; tag_to_atom("port") -> ?port; tag_to_atom("proc") -> ?proc; diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl index 2cc28d04be..4b7caaf41e 100644 --- a/lib/observer/src/crashdump_viewer_html.erl +++ b/lib/observer/src/crashdump_viewer_html.erl @@ -23,248 +23,13 @@ %% viewer. No logic or states are kept by this module. %% --export([welcome/0, - read_file_frame/0, - redirect/1, - start_page/0, - filename_frame/1, - menu_frame/0, - general_info/1, - pretty_info_page/2, - plain_page/1, - info_page/2, - proc_details/4, +-export([plain_page/1, expandable_term/3, - expanded_binary/1, - port/3, - internal_ets_tables/2, - nods/2, - loaded_mod_details/2, - atoms/4, - atoms_chunk/2, - memory/2, - allocated_areas/2, - allocator_info/2, - hash_tables/2, - index_tables/2, - error/2, - warning/1, - chunk_page/5, - chunk/3]). + warning/1]). -include("crashdump_viewer.hrl"). -include("observer_defs.hrl"). -%%%----------------------------------------------------------------- -%%% Welcome frame -welcome() -> - header(body(welcome_body())). - -welcome_body() -> - table( - "WIDTH=100% HEIGHT=60%", - [tr("VALIGN=middle", - td("ALIGN=center", - font("SIZE=6", - ["Welcome to the Web Based",br(), - "Erlang Crash Dump Analyser"]))), - tr("VALIGN=middle", - td("ALIGN=center", - form(["name=load_new ACTION=\"./read_file_frame\""], - input(["TYPE=submit VALUE=\"Load Crashdump\""]))))]). - -%%%----------------------------------------------------------------- -%%% Present a form to enter file name of erlang crash dump -read_file_frame() -> - header("Read File",body(read_file_frame_body())). - - -read_file_frame_body() -> - %% Using a plain text input field instead of a file input field - %% (e.g. ) because most - %% browsers can not forward the full path from this dialog even if - %% the browser is running on localhost (Ref 'fakepath'-problem) - Entry = input("TYPE=text NAME=path SIZE=60"), - Form = - form( - "NAME=read_file_form METHOD=post ACTION=\"./read_file\"", - table( - "BORDER=0", - [tr(td("COLSPAN=2","Enter file to analyse")), - tr( - [td(Entry), - td("ALIGN=center",input("TYPE=submit VALUE=Ok"))])])), - table( - "WIDTH=100% HEIGHT=60%", - tr("VALIGN=middle", - td("ALIGN=center",Form))). - - -%%%----------------------------------------------------------------- -%%% Display "Please wait..." while crashdump is being read -redirect(Status) -> - Head = [""], - header("Please wait...",Head,body([Status,br(),"Please wait..."])). - -%%%----------------------------------------------------------------- -%%% Frameset containing "filename", "menu", and "main" frames -start_page() -> - header("Crashdump Viewer Start Page",start_page_frameset()). - -start_page_frameset() -> - frameset( - "ROWS=\"70,*\"", - [frame(["NAME=\"filename\" SRC=\"./filename_frame\""]), - frameset( - "COLS=\"200,*\"", - [frame(["NAME=\"menu\" ", - "SRC=\"/cdv_erl/crashdump_viewer/menu_frame\""]), - frame("NAME=\"main\" SRC=\"./initial_info_frame\"")])]). - - - -%%%----------------------------------------------------------------- -%%% Topmost frame presents the filename of the crashdump currently -%%% viewed -filename_frame(File) -> - header("Filename",body(filename_body(File))). - -filename_body(File) -> - p("ALIGN=center",[b("Crashdump currently viewed:"),br(),File]). - - -%%%----------------------------------------------------------------- -%%% Left frame displays the menu -menu_frame() -> - header("Menu", body(menu_body())). - -menu_body() -> - [p(format_items(1,ets:info(cdv_menu_table,size),true)), - p([br(), - form(["name=load_new ACTION=\"./read_file_frame\" ", - "TARGET=app_frame"], - input("TYPE=submit VALUE=\"Load New Crashdump\""))])]. - -format_items(I,Max,_ParentState) when I>Max-> - []; -format_items(I,Max,ParentState) when I= - case ets:lookup(cdv_menu_table,I) of - [] -> []; - [#menu_item{state=false,children=0}] -> - format_items(I+1,Max,ParentState); - [#menu_item{state=false,children=Children}] -> - format_items(I+Children+1,Max,arentState); - [Item=#menu_item{state=true,children=0}] when ParentState -> - This = format_item(Item), - [This|format_items(I+1,Max,ParentState)]; - [Item=#menu_item{state=true,children=Children}] when ParentState -> - This = format_item(Item), - Ch = format_items(I+1,I+Children,true), - [[This | Ch] | format_items(I+Children+1,Max,ParentState)] - end. - -format_item(Item) -> - [lists:duplicate(Item#menu_item.depth*5,?space), - format_picture(Item#menu_item.index, - Item#menu_item.picture, - Item#menu_item.children), - format_title(Item#menu_item.text,Item#menu_item.target), - br()]. - -format_picture(_Index,Picture,0) -> - img(Picture); -format_picture(Index,Picture,_Children) -> - href( ["./toggle?index=", integer_to_list(Index)], img(Picture)). - -format_title({Link,Text},Target) -> - href(["TARGET=\"",Target,"\""],Link,Text); -format_title(Text,_Type) -> - Text. - -%%%----------------------------------------------------------------- -%%% Display the general information -general_info(GenInfo) -> - Heading = "General Information", - header(Heading,body(general_info_body(Heading,GenInfo))). - -general_info_body(Heading,GenInfo) -> - TruncatedInfo = - case get(truncated) of - true -> - p(font("SIZE=\"+1\" COLOR=\"#FF0000\"", - b(["WARNING:",br(), - "The crashdump is truncated",br(), - "Some information might be missing",br()]))); - false -> - "" - end, - - [heading(Heading,"general_info"), - TruncatedInfo, - table( - "BORDER=4 CELLPADDING=4", - [tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Slogan"), - td(GenInfo#general_info.slogan)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Node name"), - td(GenInfo#general_info.node_name)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Crashdump created on"), - td(GenInfo#general_info.created)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","System version"), - td(GenInfo#general_info.system_vsn)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Compiled"), - td(GenInfo#general_info.compile_time)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Taints"), - td(GenInfo#general_info.taints)]), - case GenInfo#general_info.mem_tot of - "" -> ""; - MemTot -> - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Memory allocated"), - td([MemTot," bytes"])]) - end, - case GenInfo#general_info.mem_max of - "" -> ""; - MemMax -> - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Memory maximum"), - td([MemMax," bytes"])]) - end, - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Atoms"), - td(GenInfo#general_info.num_atoms)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Processes"), - td(GenInfo#general_info.num_procs)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","ETS tables"), - td(GenInfo#general_info.num_ets)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Timers"), - td(GenInfo#general_info.num_timers)]), - tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Funs"), - td(GenInfo#general_info.num_fun)])]), - case GenInfo#general_info.instr_info of - old_instr_data -> - [br(),br(), - font("COLOR=\"#FF0000\"", - ["Instrumentation information is found at the end of ",br(), - "the dump. The information has an old format, and ",br(), - "is not presented in this tool. Please read the ",br(), - "crashdump manually to see this information."])]; - instr_data -> - [br(),br(), - font("COLOR=\"#FF0000\"", - ["Instrumentation information is found at the end of ",br(), - "the dump. The information is not presented in this ",br(), - "tool. Please read the crashdump manually to see",br(), - "this information."])]; - false -> - [] - end]. - -%%%----------------------------------------------------------------- -%%% Display an error message -error(Text,Args) -> - Str = io_lib:format(Text,Args), - header(body(error_body(Str))). - -error_body(Str) -> - [h1("An error occured:"),Str,"\n"]. - %%%----------------------------------------------------------------- %%% Display the given information as is, no heading %%% Empty body if no info exists. @@ -283,213 +48,6 @@ plain_page(Info) -> plain_body(Info) -> [pre(href_proc_port(lists:flatten(Info)))]. -%%%----------------------------------------------------------------- -%%% Display the given information as is -info_page(Heading,Info) -> - info_page(Heading,Info,[]). -info_page(Heading,Info,TW) -> - header(Heading,body(info_body(Heading,Info,TW))). - -info_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No information was found\n"]; -info_body(Heading,Info,TW) -> - [h1(Heading), - warn(TW), - pre(href_proc_port(lists:flatten(Info)))]. - -%%%----------------------------------------------------------------- -%%% Pretty print the given information -pretty_info_page(Heading,Info) -> - header(Heading,body(pretty_info_body(Heading,Info))). - -pretty_info_body(Heading,[]) -> - [h1(Heading), - "No information was found\n"]; -pretty_info_body(Heading,Info) -> - [h1(Heading), - pre(pretty_format(Info))]. - -%%%----------------------------------------------------------------- -%%% Print details for one process -proc_details(Pid,Proc,TW,SharedHeap) -> - Script = -"\n", - - Heading = ["Process ", Pid], - header(Heading,Script,body(proc_details_body(Heading,Proc,TW,SharedHeap))). - -proc_details_body(Heading,Proc,TW,SharedHeap) -> - Pid = Proc#proc.pid, - Name = if Proc#proc.name==Proc#proc.init_func -> ?space; - true -> Proc#proc.name - end, - [help("processes"), - warn(TW), - table( - "BORDER=4 COLS=4 WIDTH=\"100%\"", - [tr( - "BGCOLOR=\"#8899AA\"", - [td("COLSPAN=4 ALIGN=center",Heading)]), - tr( - [td("NOWRAP=true",b("Name")), - td("COLSPAN=1",Name), - td("NOWRAP=true",b("Spawned as")), - td("COLSPAN=1",Proc#proc.init_func)]), - tr( - [td("NOWRAP=true",b("State")), - td("COLSPAN=1",Proc#proc.state), - td("NOWRAP=true",b(element(1,Proc#proc.current_func))), - td("COLSPAN=1",element(2,Proc#proc.current_func))]), - tr( - [td("NOWRAP=true",b("Started")), - td("COLSPAN=1",Proc#proc.start_time), - td("NOWRAP=true",b("Spawned by")), - td("COLSPAN=1",href_proc_port(Proc#proc.parent))]), - tr( - [td("NOWRAP=true",b("Reductions")), - td("COLSPAN=1",integer_to_list(Proc#proc.reds))] ++ - case Proc#proc.memory of - undefined -> []; % before R16B01 - Mem -> - [td("NOWRAP=true",b("Memory (bytes)")), - td("COLSPAN=1",integer_to_list(Mem))] - end), - if SharedHeap -> - Stack = case Proc#proc.stack_heap of - -1 -> "unknown"; - S -> integer_to_list(S) - end, - tr( - [td("NOWRAP=true",b("Stack")), - td("COLSPAN=3",Stack)]); - true -> - [tr( - [td("NOWRAP=true",b("Stack+heap")), - td(integer_to_list(Proc#proc.stack_heap)), - td("NOWRAP=true",b("OldHeap")), - td(Proc#proc.old_heap)]), - tr( - [td("NOWRAP=true",b("Heap unused")), - td(Proc#proc.heap_unused), - td("NOWRAP=true",b("OldHeap unused")), - td(Proc#proc.old_heap_unused)]), - tr( - [td("NOWRAP=true",b("Number of heap fragments")), - td(Proc#proc.num_heap_frag), - td("NOWRAP=true",b("Heap fragment data")), - td(Proc#proc.heap_frag_data)])] - end, - case Proc#proc.new_heap_start of - ?space -> ""; - _ -> - %% Garbing - [tr( - [td("NOWRAP=true",b("New heap start")), - td("COLSPAN=1",Proc#proc.new_heap_start), - td("NOWRAP=true",b("New heap top")), - td("COLSPAN=1",Proc#proc.new_heap_top)]), - tr( - [td("NOWRAP=true",b("Stack top")), - td("COLSPAN=1",Proc#proc.stack_top), - td("NOWRAP=true",b("Stack end")), - td("COLSPAN=1",Proc#proc.stack_end)]), - tr( - [td("NOWRAP=true",b("Old heap start")), - td("COLSPAN=1",Proc#proc.old_heap_start), - td("NOWRAP=true",b("Old heap top")), - td("COLSPAN=1",Proc#proc.old_heap_top)]), - tr( - [td("NOWRAP=true",b("Old heap end")), - td("COLSPAN=3",Proc#proc.old_heap_end)])] - end, - case Proc#proc.prog_count of - ?space -> ""; - _ -> - [tr( - [td("NOWRAP=true",b("Program counter")), - td("COLSPAN=3",Proc#proc.prog_count)]), - tr( - [td("NOWRAP=true",b("Continuation pointer")), - td("COLSPAN=3",Proc#proc.cp)]), - tr( - [td("NOWRAP=true",b("Arity")), - td("COLSPAN=3",Proc#proc.arity)])] - end, - tr( - [td("NOWRAP=true",b("Link list")), - td("COLSPAN=3",href_proc_port(Proc#proc.links))]), - - tr( - [td("NOWRAP=true",b("Msg queue length")), - td("COLSPAN=3",integer_to_list(Proc#proc.msg_q_len))]), - - %% 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("LastCalls",Proc#proc.last_calls,Pid), - display_or_link_to_expand("StackDump",Proc#proc.stack_dump,Pid)]), - - p([href(["./ets_tables?pid=",Proc#proc.pid], - "ETS tables owned by this process"), - "     ", - href(["./timers?pid=",Proc#proc.pid], - "Timers owned by this process")])]. - -display_or_link_to_expand(Heading,Data,Pid) -> - case Data of - expand -> - link_to_read_memory(Heading,Pid); - truncated -> - Text = font("COLOR=\"#FF0000\"", - "The dump is truncated, no data available"), - tr( - [td("NOWRAP=true VALIGN=top",b(Heading)), - td("COLSPAN=3",Text)]); - ?space -> - ""; - {size,Truncated,Size,Pos} -> - %% Too much data, or truncated data - - %% display a link to expand it - tr( - [td("NOWRAP=true",b(Heading)), - td("COLSPAN=3", - href("TARGET=\"expanded\" onClick=popup()", - ["./expand?pos=",integer_to_list(Pos), - "&size=",integer_to_list(Size), - "&what=",Heading, - "&truncated=",atom_to_list(Truncated)], - ["Expand (",integer_to_list(Size)," bytes)"]))]); - _ -> - %% Not too much Data - display it - tr( - [td("NOWRAP=true VALIGN=top",b(Heading)), - td("COLSPAN=3",pre(format(Heading,Data)))]) - end. - -link_to_read_memory(Heading,Pid) -> - tr( - [td("NOWRAP=true",b(Heading)), - td("COLSPAN=3", - href("TARGET=\"expanded\" onClick=popup()", - ["./expand_memory?pid=",Pid, - "&what=",Heading], - ["Expand ", Heading]))]). - -format("LastCalls",Data) -> - Data; -format("StackDump",Data) -> - Data; -format(_Heading,Data) -> - pretty_format(Data). - - - %%%----------------------------------------------------------------- %%% Expanded memory expandable_term(Heading,Expanded,Tab) -> @@ -585,7 +143,7 @@ proc_state(Tab,{Key0,Value0}, Even) -> all_or_expand(Tab,Term) -> Preview = io_lib:format("~P",[Term,8]), - Check = io_lib:format("~P",[Term,9]), + Check = io_lib:format("~P",[Term,100]), Exp = Preview=/=Check, all_or_expand(Tab,Term,Preview,Exp). all_or_expand(_Tab,_Term,Str,false) -> @@ -618,417 +176,8 @@ all_or_expand(Tab,Bin,PreviewStr,true) when is_binary(Bin) -> color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). -%%%----------------------------------------------------------------- -%%% Display an expanded binary, i.e. the whole binary, not just the -%%% size of it. -expanded_binary(Bin) -> - Heading = "Expanded binary", - header(Heading,body(expanded_binary_body(Heading,Bin))). - -expanded_binary_body(Heading,Bin) -> - [h1(Heading), - pre(href_proc_port(lists:flatten(Bin))), - br(),br(), - href("javascript:history.go(-1)","BACK")]. - -%%%----------------------------------------------------------------- -%%% Print info for one port -port(Heading,Port,TW) -> - header(Heading,body(port_body(Heading,Port,TW))). - -port_body(Heading,Port,TW) -> - [heading(Heading,"ports"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr([th(Head) || Head <- port_table_head()]), ports_table(Port)])]. - -%%%----------------------------------------------------------------- -%%% Print table of internal ETS tables -internal_ets_tables(InternalEts,TW) -> - Heading = "Internal ETS tables", - header(Heading,body(internal_ets_tables_body(Heading,InternalEts,TW))). - -internal_ets_tables_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No internal ETS tables were found\n"]; -internal_ets_tables_body(Heading,InternalEts,TW) -> - [heading(Heading,"internal_ets_tables"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Description"), - th("Id"), - th("Name"), - th("Type"), - th("Buckets"), - th("Objects"), - th("Memory (bytes)")]) | - lists:map(fun(InternalEtsTable) -> - internal_ets_tables_table1(InternalEtsTable) - end, - InternalEts)])]. - -internal_ets_tables_table1({Descr,InternalEtsTable}) -> - #ets_table{id=Id,name=Name,type=Type,buckets=Buckets, - size=Size,memory=Memory} = InternalEtsTable, - tr( - [td(Descr), - td(Id), - td(Name), - td(Type), - td("ALIGN=right",Buckets), - td("ALIGN=right",Size), - td("ALIGN=right",Memory)]). - -%%%----------------------------------------------------------------- -%%% Print table of nodes in distribution -nods(Nods,TW) -> - header("Distribution Information",body(nodes_body(Nods,TW))). - -nodes_body(no_distribution,_TW) -> - [heading("Distribution Information","distribution_info"), - "Not alive\n"]; -nodes_body({Type,Info,Node},TW) when is_record(Node,nod) -> - %% Display only one node - used when a pid or port on a remote - %% node is clicked. - [heading("Remote Node","distribution_info"), - warn(TW), - Info, - make_nodes_table(Type,[Node])]; -nodes_body({Visible,Hidden,NotConnected},TW) -> - %% Display all nodes - this is the complete distribution info - [heading("Distribution Information","distribution_info"), - warn(TW), - make_nodes_table("Visible Nodes",Visible), - make_nodes_table("Hidden Nodes",Hidden), - make_nodes_table("Not Connected Nodes",NotConnected)]. - -make_nodes_table(Text,[]) -> - p(["No \"",Text,"\" were found"]); -make_nodes_table(Text,Nodes) -> - p(table( - "BORDER=4 CELLPADDING=4", - [nodes_table_heading(Text), - lists:map(fun(Node) -> nodes_table_row(Node) end, Nodes)])). - -nodes_table_heading(Text) -> - [tr("BGCOLOR=\"#8899AA\"",[th("COLSPAN=6",Text)]), - tr([th("Name"), - th("Channel"), - th("Controller"), - th("Creation(s)"), - th("Links/Monitors"), - th("Extra info")])]. - -nodes_table_row(Node) -> - #nod{name=Name,channel=Channel,controller=Controller,creation=Creation, - remote_links=Links,remote_mon=Mon,remote_mon_by=MonBy,error=Error}=Node, - tr( - [td(maybe_refcount(Name)), - td("ALIGN=right",Channel), - td(href_proc_port(Controller)), - td("ALIGN=right",break_lines_creation(Creation)), - td(format_links_and_monitors(Links,Mon,MonBy)), - td(format_extra_info(Error))]). - -maybe_refcount(Name) -> - maybe_refcount(Name, []). -maybe_refcount([$ ,$( | Rest], Acc) -> - [lists:reverse(Acc),br(),[$(|Rest]]; -maybe_refcount([Char | Rest], Acc) -> - maybe_refcount(Rest, [Char | Acc]); -maybe_refcount([],Acc) -> - lists:reverse(Acc). - -break_lines_creation(Creation) -> - break_lines_creation(Creation,[]). -break_lines_creation([$ ,$( | Rest1], Acc) -> - {RefCount,Rest2} = to_end_par(Rest1,[$(,$ ]), - [lists:reverse(Acc),RefCount,br(),break_lines_creation(Rest2)]; -break_lines_creation([$ | Rest], Acc) -> - [lists:reverse(Acc),br(),break_lines_creation(Rest)]; -break_lines_creation([Char | Rest], Acc) -> - break_lines_creation(Rest, [Char | Acc]); -break_lines_creation([],Acc) -> - lists:reverse(Acc). - -to_end_par([$),$ | Rest], Acc) -> - {lists:reverse([$) | Acc]),Rest}; -to_end_par([$) | Rest], Acc) -> - {lists:reverse([$) | Acc]),Rest}; -to_end_par([Char | Rest], Acc) -> - to_end_par(Rest, [Char | Acc]); -to_end_par([],Acc) -> - {lists:reverse(Acc),[]}. - - -format_links_and_monitors(?space,?space,?space) -> - ?space; -format_links_and_monitors(Links,Mon,MonBy) -> - [format_links_and_monitors(Links," is linked to "), - format_links_and_monitors(Mon," is monitoring "), - format_links_and_monitors(MonBy," is monitored by ")]. - -format_links_and_monitors(?space,_Text) -> - ""; -format_links_and_monitors([{Local,Remote}|Rest],Text) -> - [[href_proc_port(Local),Text,href_proc_port(Remote),br()] | - format_links_and_monitors(Rest,Text)]; -format_links_and_monitors([],_Text) -> - []. - -format_extra_info(?space) -> - ?space; -format_extra_info(Error) -> - case Error of - ?space -> ""; - _ -> font("COLOR=\"#FF0000\"",["ERROR: ",Error,"\n"]) - end. - -%%%----------------------------------------------------------------- -%%% Print detailed information about one module -loaded_mod_details(ModInfo,TW) -> - header(ModInfo#loaded_mod.mod,body(loaded_mod_details_body(ModInfo,TW))). - -loaded_mod_details_body(ModInfo,TW) -> - #loaded_mod{mod=Mod,current_size=CS,current_attrib=CA, - current_comp_info=CCI,old_size=OS, - old_attrib=OA,old_comp_info=OCI} = ModInfo, - [help("loaded_modules"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr(th("BGCOLOR=\"#8899AA\" COLSPAN=3", - ["Module: ",Mod])), - tr([td(?space),th("Current"),th("Old")]), - tr([th("ALIGN=left","Size (bytes)"), - td(CS), - td(OS)]), - tr([th("ALIGN=left","Attributes"), - td(pre(CA)), - td(pre(OA))]), - tr([th("ALIGN=left","Compilation info"), - td(pre(CCI)), - td(pre(OCI))])])]. - - -%%%----------------------------------------------------------------- -%%% Print atoms -atoms(SessionId,TW,Num,FirstChunk) -> - Heading = "Atoms", - case FirstChunk of - done -> - deliver_first(SessionId,[start_html_page(Heading), - h1(Heading), - warn(TW), - "No atoms were found in log",br(), - "Total number of atoms in node was ", Num, - br()]); - _ -> - deliver_first(SessionId,[start_html_page(Heading), - heading(Heading,"atoms"), - warn(TW), - "Total number of atoms in node was ", Num, - br(), - "The last created atom is shown first", - br(), - start_pre()]), - atoms_chunk(SessionId,FirstChunk) - end. - -atoms_chunk(SessionId,done) -> - deliver(SessionId,[stop_pre(),stop_html_page()]); -atoms_chunk(SessionId,Atoms) -> - deliver(SessionId,Atoms). - -%%%----------------------------------------------------------------- -%%% Print memory information -memory(Memory,TW) -> - Heading = "Memory Information", - header(Heading,body(memory_body(Heading,Memory,TW))). - -memory_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No memory information was found\n"]; -memory_body(Heading,Memory,TW) -> - [heading(Heading,"memory"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr("BGCOLOR=\"#8899AA\"", - [th(?space), - th("Bytes")]) | - lists:map(fun(Entry) -> memory_table(Entry) end, Memory)])]. - -memory_table({Key,Value}) -> - tr([th("ALIGN=left",Key),td("ALIGN=right",Value)]). - -%%%----------------------------------------------------------------- -%%% Print allocated areas information -allocated_areas(AllocatedAreas,TW) -> - Heading = "Information about allocated areas", - header(Heading,body(allocated_areas_body(Heading,AllocatedAreas,TW))). - -allocated_areas_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No information was found about allocated areas\n"]; -allocated_areas_body(Heading,AllocatedAreas,TW) -> - [heading(Heading,"memory"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr("BGCOLOR=\"#8899AA\"", - [th(?space), - th("Allocated (bytes)"), - th("Used (bytes)")]) | - lists:map(fun(Entry) -> allocated_areas_table(Entry) end, - AllocatedAreas)])]. - -allocated_areas_table({Key,Alloc,Used}) -> - tr( - [th("ALIGN=left",Key), - td("ALIGN=right",Alloc), - td("ALIGN=right",Used)]). - - -%%%----------------------------------------------------------------- -%%% Print allocator_info information -allocator_info(Allocators,TW) -> - Heading = "Allocator Information", - header(Heading,body(allocator_info_body(Heading,Allocators,TW))). - -allocator_info_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No information was found about allocators\n"]; -allocator_info_body(Heading,Allocators,TW) -> - [heading(Heading,"memory"), - warn(TW), - p(b("Sizes are in bytes")), - lists:map(fun({Head,Allocator}) -> - TableHead = - case Head of - {SubTitle,Columns} -> - tr("BGCOLOR=\"#8899AA\"", - [th("ALIGN=left", - font("SIZE=+1",SubTitle)) | - lists:map( - fun(CH) -> - th("ALIGN=right",CH) - end, - Columns)]); - SubTitle -> - tr("BGCOLOR=\"#8899AA\"", - th("COLSPAN=10 ALIGN=left", - font("SIZE=+1",SubTitle))) - end, - [table( - "BORDER=4 CELLPADDING=4", - [TableHead | - lists:map( - fun({Key,Values}) -> - tr([th("ALIGN=left",Key) | - lists:map( - fun(Val) -> - td("ALIGN=right",Val) - end,Values)]) - end, - Allocator)]), - br(),br()] - end, - Allocators)]. - -%%%----------------------------------------------------------------- -%%% Print informatin about internal tables -hash_tables(HashTables,TW) -> - Heading = "Hash Table Information", - header(Heading,body(hash_tables_body(Heading,HashTables,TW))). - -hash_tables_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No hash table information was found\n"]; -hash_tables_body(Heading,HashTables,TW) -> - [heading(Heading,"internal_tables"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Name"), - th("Size"), - th("Used"), - th("Objects"), - th("Depth")]) | - lists:map(fun(HashTable) -> hash_tables_table(HashTable) end, - HashTables)])]. - -hash_tables_table(HashTable) -> - #hash_table{name=Name,size=Size,used=Used,objs=Objs,depth=Depth}=HashTable, - tr( - [td(Name), - td("ALIGN=right",Size), - td("ALIGN=right",Used), - td("ALIGN=right",Objs), - td("ALIGN=right",Depth)]). - -index_tables(IndexTables,TW) -> - Heading = "Index Table Information", - header(Heading,body(index_tables_body(Heading,IndexTables,TW))). - -index_tables_body(Heading,[],TW) -> - [h1(Heading), - warn(TW), - "No index table information was found\n"]; -index_tables_body(Heading,IndexTables,TW) -> - [heading(Heading,"internal_tables"), - warn(TW), - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Name"), - th("Size"), - th("Limit"), - th("Used"), - th("Rate"), - th("Entries")]) | - lists:map(fun(IndexTable) -> index_tables_table(IndexTable) end, - IndexTables)])]. - -index_tables_table(IndexTable) -> - #index_table{name=Name,size=Size,limit=Limit,used=Used, - rate=Rate,entries=Entries} = IndexTable, - tr( - [td(Name), - td("ALIGN=right",Size), - td("ALIGN=right",Limit), - td("ALIGN=right",Used), - td("ALIGN=right",Rate), - td("ALIGN=right",Entries)]). - %%%----------------------------------------------------------------- %%% Internal library -start_html_page(Title) -> - [start_html(), - only_html_header(Title), - start_html_body()]. - -stop_html_page() -> - [stop_html_body(), - stop_html()]. - -only_html_header(Title) -> - only_html_header(Title,""). -only_html_header(Title,JavaScript) -> - ["\n", - "", Title, "\n", - JavaScript, - "\n"]. - start_html() -> "\n". stop_html() -> @@ -1052,22 +201,17 @@ html_header(Title,JavaScript,Body) -> Body, stop_html()]. +only_html_header(Title,JavaScript) -> + ["\n", + "", Title, "\n", + JavaScript, + "\n"]. + body(Text) -> [start_html_body(), Text, stop_html_body()]. -frameset(Args,Frames) -> - ["\n", Frames, "\n\n"]. -frame(Args) -> - ["\n"]. - -start_visible_table() -> - start_table("BORDER=\"4\" CELLPADDING=\"4\""). -start_visible_table(ColTitles) -> - [start_visible_table(), - tr([th(ColTitle) || ColTitle <- ColTitles])]. - start_table(Args) -> ["\n"]. stop_table() -> @@ -1079,8 +223,6 @@ tr(Text) -> ["\n", Text, "\n\n"]. tr(Args,Text) -> ["\n", Text, "\n\n"]. -th(Text) -> - [""]. th(Args,Text) -> ["\n"]. td(Text) -> @@ -1088,10 +230,6 @@ td(Text) -> td(Args,Text) -> [""]. -b(Text) -> - ["",Text,""]. -em(Text) -> - ["",Text,"\n"]. start_pre() -> "
".
 stop_pre() ->
@@ -1102,22 +240,10 @@ href(Link,Text) ->
     ["",Text,""].
 href(Args,Link,Text) ->
     ["",Text,""].
-img("") ->
-    "";
-img(Picture) ->
-    [""].
-form(Args,Text) ->
-    ["
\n",Text,"\n\n"]. -input(Args) -> - ["\n"]. -h1(Text) -> - ["

",Text,"

\n"]. font(Args,Text) -> ["\n",Text,"\n\n"]. p(Text) -> ["

",Text,"

\n"]. -p(Args, Text) -> - ["

",Text,"

\n"]. br() -> "
\n". @@ -1236,9 +362,9 @@ href_proc_bin(From, T, Acc, LTB) -> ")>>"], if LTB -> href("TARGET=\"expanded\"", - ["#OBSBinary?offset="++Preview++ - "&size="++Size++ - "&pos="++Md5], + ["#OBSBinary?key1="++Preview++ + "&key2="++Size++ + "&key3="++Md5], PreviewStr); true -> PreviewStr @@ -1260,310 +386,3 @@ warn([]) -> []; warn(Warning) -> font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). - -heading(Heading,HelpMarker) -> - [font("SIZE=+2",b(Heading)),?space,?space,help(HelpMarker)]. - -help(HelpMarker) -> - [href("TARGET=doc", - ["/crashdump_doc/crashdump_help.html#",HelpMarker], - "Help"), - br(),br()]. - -%%%----------------------------------------------------------------- -%%% This function pretty formats a string which contains erlang -%%% terms (e.g. the message queue). -%%% In all the following, "<" is changed to "<" and ">" is changed to ">" -pretty_format(In) -> - case catch scan(In,[],initial,[]) of - {'EXIT',_Reason} -> - %% Probably a truncated file, so the erlang term is not complete - [font("COLOR=\"#FF0000\"","(This term might be truncated)"), - href_proc_port(lists:flatten(In))]; - {[R],_,Insrt} -> - InsrtString = lists:flatten(io_lib:format("~p",[R])), - lists:flatten(replace_insrt(lists:reverse(InsrtString),Insrt,[])) - end. - -%% Finish term -scan(In,Acc,list,Insrt) when hd(In)==$] -> - {lists:reverse(Acc),tl(In),Insrt}; -scan(In,Acc,tuple,Insrt) when hd(In)==$} -> - {list_to_tuple(lists:reverse(Acc)),tl(In),Insrt}; -scan(In,Acc,atom,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} -> - {list_to_atom(lists:reverse(Acc)),In,Insrt}; -scan(In,Acc,float,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} -> - {list_to_float(lists:reverse(Acc)),In,Insrt}; -scan(In,Acc,integer,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} -> - {list_to_integer(lists:reverse(Acc)),In,Insrt}; -scan([$"|In],Acc,string,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} -> - {lists:reverse(Acc),In,Insrt}; -scan([$>|In],Acc,special,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} -> - %% pid, ref, port, fun - {lists:reverse([$;,$t,$g,$&|Acc]),In,Insrt}; -scan([$}|In],Acc,special,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} -> - %% bignum integer, e.g. #integer(2) = {2452,4324} - {lists:reverse([$}|Acc]),In,Insrt}; -scan([$,|In],Acc,Cur,Insrt) when Cur/=string,Cur/=special -> - scan(In,Acc,Cur,Insrt); - -%% In the middle of an atom -scan([$'|In],Acc,Cur,Insrt) when Cur==atom -> - %% all $' are removed. They are added again by list_to_atom, - %% so if we don't remove them we will get two of them. - scan(In,Acc,Cur,Insrt); - -%% A $. in the middle of an integer - turn to float -scan([C|T],Acc,integer,Insrt) when C==$. -> - scan(T,[C|Acc],float,Insrt); - -%% In the middle of an atom, integer, float or string -scan([$<|T],Acc,Cur,Insrt) when Cur==atom;Cur==string;Cur==special -> - scan(T,[$;,$t,$l,$&|Acc],Cur,Insrt); -scan([$>|T],Acc,Cur,Insrt) when Cur==atom;Cur==string -> - scan(T,[$;,$t,$g,$&|Acc],Cur,Insrt); -scan([C|T],Acc,Cur,Insrt) when Cur==atom;Cur==integer;Cur==float;Cur==string;Cur==special -> - scan(T,[C|Acc],Cur,Insrt); - -%% Start list -scan([$[|T],Acc,Cur,Insrt0) -> - {L,Rest,Insrt} = scan(T,[],list,Insrt0), - scan(Rest,[L|Acc],Cur,Insrt); - -%% Star tuple -scan([${|T],Acc,Cur,Insrt0) -> - {Tuple,Rest,Insrt} = scan(T,[],tuple,Insrt0), - scan(Rest,[Tuple|Acc],Cur,Insrt); - -%% Star string -scan([$"|T],Acc,Cur,Insrt0) -> - {String,Rest,Insrt} = scan(T,[],string,Insrt0), - scan(Rest,[String|Acc],Cur,Insrt); - -%% Start atom -scan([$'|T],Acc,Cur,Insrt0) -> - %% all $' are removed. They are added again by list_to_atom, - %% so if we don't remove them we will get two of them. - {Atom,Rest,Insrt} = scan(T,[],atom,Insrt0), - scan(Rest,[Atom|Acc],Cur,Insrt); -scan([C|T],Acc,Cur,Insrt0) when C>=$A,C=<$Z;C>=$a,C=<$z;C==$'-> - {Atom,Rest,Insrt} = scan(T,[C],atom,Insrt0), - scan(Rest,[Atom|Acc],Cur,Insrt); - -%% Start integer or float -scan([C|T],Acc,Cur,Insrt0) when C>=$0,C=<$9;C==$- -> - {Num,Rest,Insrt} = scan(T,[C],integer,Insrt0), % can later change to float - scan(Rest,[Num|Acc],Cur,Insrt); - -%% Start Pid/Port/Ref/Fun/Binary -scan([$<|T],Acc,Cur,Insrt0) -> - {Special,Rest,Insrt} = scan(T,[$;,$t,$l,$&],special,Insrt0), - scan(Rest,['$insrt'|Acc],Cur,[Special|Insrt]); -scan([$#|T],Acc,Cur,Insrt0) -> - {Special,Rest,Insrt} = scan(T,[$#],special,Insrt0), - scan(Rest,['$insrt'|Acc],Cur,[Special|Insrt]); - - -%% done -scan([],Acc,initial,Insrt) -> - {Acc,[],Insrt}. - - -replace_insrt("'trsni$'"++Rest,[H|T],Acc) -> % the list is reversed here! - Special = - case H of - "<<" ++ _Binary -> - H; - "<" ++ _Pid -> - href("TARGET=\"main\"",["./proc_details?pid=",H],H); - "#Port<" ++ Port -> - href("TARGET=\"main\"",["./port?port=","Port<"++Port],H); - "#" ++ _other -> - H - end, - replace_insrt(Rest,T,[Special|Acc]); -replace_insrt([H|T],Insrt,Acc) -> - replace_insrt(T,Insrt,[H|Acc]); -replace_insrt([],[],Acc) -> - Acc. - -%%%----------------------------------------------------------------- -%%% Create a page with one table by delivering chunk by chunk to -%%% inets. crashdump_viewer first calls chunk_page/5 once, then -%%% chunk/3 multiple times until all data is delivered. -chunk_page(processes,SessionId,TW,{Sorted,SharedHeap,DumpVsn},FirstChunk) -> - Columns = procs_summary_table_head(Sorted,SharedHeap,DumpVsn), - chunk_page(SessionId, "Process Information", TW, FirstChunk, - "processes", Columns, fun procs_summary_table/1); -chunk_page(ports,SessionId,TW,_,FirstChunk) -> - chunk_page(SessionId, "Port Information", TW, FirstChunk, - "ports", port_table_head(), fun ports_table/1); -chunk_page(ets_tables,SessionId,TW,Heading,FirstChunk) -> - Columns = ["Owner", - "Slot", - "Id", - "Name", - "Type", - "Buckets", - "Objects", - "Memory (bytes)"], - chunk_page(SessionId, Heading, TW, FirstChunk, - "ets_tables", Columns, fun ets_tables_table/1); -chunk_page(timers,SessionId,TW,Heading,FirstChunk) -> - chunk_page(SessionId, Heading, TW, FirstChunk, "timers", - ["Owner","Message","Time left"], fun timers_table/1); -chunk_page(loaded_mods,SessionId,TW,{CC,OC},FirstChunk) -> - TotalsInfo = p([b("Current code: "),CC," bytes",br(), - b("Old code: "),OC," bytes"]), - Columns = ["Module","Current size (bytes)","Old size (bytes)"], - chunk_page(SessionId, "Loaded Modules Information", TW, FirstChunk, - "loaded_modules", TotalsInfo,Columns, fun loaded_mods_table/1); -chunk_page(funs,SessionId, TW, _, FirstChunk) -> - Columns = ["Module", - "Uniq", - "Index", - "Address", - "Native_address", - "Refc"], - chunk_page(SessionId, "Fun Information", TW, FirstChunk, - "funs", Columns, fun funs_table/1). - -chunk_page(SessionId,Heading,TW,FirstChunk,Type,TableColumns,TableFun) -> - chunk_page(SessionId,Heading,TW,FirstChunk,Type,[],TableColumns,TableFun). -chunk_page(SessionId,Heading,TW,done,Type,_TotalsInfo,_TableColumns,_TableFun) -> - no_info_found(SessionId,Heading,TW,Type); -chunk_page(SessionId,Heading,TW,FirstChunk,Type,TotalsInfo,TableColumns,TableFun) -> - deliver_first(SessionId,[start_html_page(Heading), - heading(Heading,Type), - warn(TW), - TotalsInfo, - start_visible_table(TableColumns)]), - chunk(SessionId,FirstChunk,TableFun), - TableFun. - -no_info_found(SessionId, Heading, TW, Type) -> - Info = ["No ", Type, " were found\n"], - deliver_first(SessionId,[start_html_page(Heading), - h1(Heading), - warn(TW), - Info, - stop_html_page()]). - -chunk(SessionId, done, _TableFun) -> - deliver(SessionId,[stop_table(),stop_html_page()]); -chunk(SessionId, Items, TableFun) -> - deliver(SessionId, [lists:map(TableFun, Items), - stop_table(), %! Will produce an empty table at the end - start_visible_table()]). % of the page :( - -%%%----------------------------------------------------------------- -%%% Deliver part of a page to inets -%%% The first part, which includes the HTTP header, must always be -%%% delivered as a string (i.e. no binaries). The rest of the page is -%%% better delivered as binaries in order to avoid data copying. -deliver_first(SessionId,String) -> - mod_esi:deliver(SessionId,String). -deliver(SessionId,IoList) -> - mod_esi:deliver(SessionId,[list_to_binary(IoList)]). - - -%%%----------------------------------------------------------------- -%%% Page specific stuff for chunk pages -procs_summary_table_head(Sorted,SharedHeap,DumpVsn) -> - MemHeading = - if DumpVsn>=?r16b01_dump_vsn -> - "Memory (bytes)"; - true -> - if SharedHeap -> - "Stack"; - true -> - "Stack+heap" - end - end, - [procs_summary_table_head1("pid","Pid",Sorted), - procs_summary_table_head1("name_func","Name/Spawned as",Sorted), - procs_summary_table_head1("state","State",Sorted), - procs_summary_table_head1("reds","Reductions",Sorted), - procs_summary_table_head1("mem",MemHeading,Sorted), - procs_summary_table_head1("msg_q_len","MsgQ Length",Sorted)]. - -procs_summary_table_head1(_,Text,no_sort) -> - Text; -procs_summary_table_head1(Sorted,Text,Sorted) -> - %% Mark the sorted column (bigger and italic) - font("SIZE=\"+1\"",em(href("./sort_procs?sort="++Sorted,Text))); -procs_summary_table_head1(SortOn,Text,_Sorted) -> - href("./sort_procs?sort="++SortOn,Text). - -procs_summary_table(Proc) -> - #proc{pid=Pid,name=Name,state=State, - reds=Reds,stack_heap=Stack,memory=Memory,msg_q_len=MsgQLen}=Proc, - Mem = - case Memory of - undefined -> % assuming pre-R16B01 - case Stack of - -1 -> "unknown"; - _ -> integer_to_list(Stack) - end; - _ -> - integer_to_list(Memory) - end, - tr( - [td(href(["./proc_details?pid=",Pid],Pid)), - td(Name), - td(State), - td("ALIGN=right",integer_to_list(Reds)), - td("ALIGN=right",Mem), - td("ALIGN=right",integer_to_list(MsgQLen))]). - -port_table_head() -> - ["Id","Slot","Connected","Links","Name","Monitors","Controls"]. - -ports_table(Port) -> - #port{id=Id,slot=Slot,connected=Connected,links=Links,name=Name, - monitors=Monitors,controls=Controls}=Port, - tr( - [td(Id), - td("ALIGN=right",Slot), - td(href_proc_port(Connected)), - td(href_proc_port(Links)), - td(Name), - td(href_proc_port(Monitors)), - td(Controls)]). - -ets_tables_table(EtsTable) -> - #ets_table{pid=Pid,slot=Slot,id=Id,name=Name,type=Type, - buckets=Buckets,size=Size,memory=Memory} = EtsTable, - tr( - [td(href_proc_port(Pid)), - td(Slot), - td(Id), - td(Name), - td(Type), - td("ALIGN=right",Buckets), - td("ALIGN=right",Size), - td("ALIGN=right",Memory)]). - -timers_table(Timer) -> - #timer{pid=Pid,msg=Msg,time=Time}=Timer, - tr( - [td(href_proc_port(Pid)), - td(Msg), - td("ALIGN=right",Time)]). - -loaded_mods_table(#loaded_mod{mod=Mod,current_size=CS,old_size=OS}) -> - tr([td(href(["loaded_mod_details?mod=",http_uri:encode(Mod)],Mod)), - td("ALIGN=right",CS), - td("ALIGN=right",OS)]). - -funs_table(Fu) -> - #fu{module=Module,uniq=Uniq,index=Index,address=Address, - native_address=NativeAddress,refc=Refc}=Fu, - tr( - [td(Module), - td("ALIGN=right",Uniq), - td("ALIGN=right",Index), - td(Address), - td(NativeAddress), - td("ALIGN=right",Refc)]) -. diff --git a/lib/observer/src/crashdump_viewer_wx.erl b/lib/observer/src/crashdump_viewer_wx.erl index 17e43838d6..3464dfafae 100644 --- a/lib/observer/src/crashdump_viewer_wx.erl +++ b/lib/observer/src/crashdump_viewer_wx.erl @@ -122,7 +122,6 @@ init(File0) -> wxNotebook:connect(Notebook, command_notebook_page_changing), wxFrame:connect(Frame, close_window, [{skip, true}]), wxMenu:connect(Frame, command_menu_selected), - wxFrame:show(Frame), case load_dump(Frame,File0) of {ok,File} -> @@ -149,7 +148,7 @@ init(File0) -> ignore end. -setup(#state{frame=Frame, notebook=Notebook, main_panel=Panel}=State) -> +setup(#state{frame=Frame, notebook=Notebook}=State) -> %% Setup Menubar & Menus MenuBar = wxMenuBar:new(), @@ -190,8 +189,8 @@ setup(#state{frame=Frame, notebook=Notebook, main_panel=Panel}=State) -> %% Memory Panel IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_panel, cdv_int_tab_wx), - %% Force redraw (window needs it) - wxWindow:refresh(Panel), + %% Show the window + wxFrame:show(Frame), GenPid = wx_object:get_pid(GenPanel), GenPid ! active, -- cgit v1.2.3 From 9da4c7b4e41c325569d2576fa4c55568a1f1cfa2 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Tue, 17 Dec 2013 11:11:17 +0100 Subject: observer: renamed crashdump_viewer files and fixed makefiles --- lib/observer/src/Makefile | 46 ++- lib/observer/src/cdv_atom_cb.erl | 48 +++ lib/observer/src/cdv_atom_wx.erl | 48 --- lib/observer/src/cdv_bin_cb.erl | 82 +++++ lib/observer/src/cdv_bin_wx.erl | 82 ----- lib/observer/src/cdv_detail_win.erl | 158 ---------- lib/observer/src/cdv_detail_wx.erl | 158 ++++++++++ lib/observer/src/cdv_dist_cb.erl | 91 ++++++ lib/observer/src/cdv_dist_wx.erl | 91 ------ lib/observer/src/cdv_ets_cb.erl | 67 +++++ lib/observer/src/cdv_ets_wx.erl | 67 ----- lib/observer/src/cdv_fun_cb.erl | 58 ++++ lib/observer/src/cdv_fun_wx.erl | 58 ---- lib/observer/src/cdv_gen_cb.erl | 45 +++ lib/observer/src/cdv_gen_wx.erl | 45 --- lib/observer/src/cdv_html_page.erl | 128 -------- lib/observer/src/cdv_html_wx.erl | 128 ++++++++ lib/observer/src/cdv_info_page.erl | 128 -------- lib/observer/src/cdv_info_wx.erl | 128 ++++++++ lib/observer/src/cdv_int_tab_cb.erl | 86 ++++++ lib/observer/src/cdv_int_tab_wx.erl | 86 ------ lib/observer/src/cdv_mem_cb.erl | 84 ++++++ lib/observer/src/cdv_mem_wx.erl | 84 ------ lib/observer/src/cdv_mod_cb.erl | 102 +++++++ lib/observer/src/cdv_mod_wx.erl | 102 ------- lib/observer/src/cdv_multi_panel.erl | 188 ------------ lib/observer/src/cdv_multi_wx.erl | 188 ++++++++++++ lib/observer/src/cdv_port_cb.erl | 103 +++++++ lib/observer/src/cdv_port_wx.erl | 102 ------- lib/observer/src/cdv_proc_cb.erl | 156 ++++++++++ lib/observer/src/cdv_proc_wx.erl | 155 ---------- lib/observer/src/cdv_table_page.erl | 106 ------- lib/observer/src/cdv_table_wx.erl | 106 +++++++ lib/observer/src/cdv_term_cb.erl | 75 +++++ lib/observer/src/cdv_term_wx.erl | 75 ----- lib/observer/src/cdv_timer_cb.erl | 51 ++++ lib/observer/src/cdv_timer_wx.erl | 51 ---- lib/observer/src/cdv_virtual_list.erl | 414 -------------------------- lib/observer/src/cdv_virtual_list_wx.erl | 414 ++++++++++++++++++++++++++ lib/observer/src/cdv_wx.erl | 462 +++++++++++++++++++++++++++++ lib/observer/src/crashdump_viewer.erl | 10 +- lib/observer/src/crashdump_viewer_html.erl | 388 ------------------------ lib/observer/src/crashdump_viewer_wx.erl | 462 ----------------------------- lib/observer/src/observer.app.src | 24 +- lib/observer/src/observer_html_lib.erl | 388 ++++++++++++++++++++++++ lib/observer/src/observer_procinfo.erl | 12 +- 46 files changed, 3075 insertions(+), 3055 deletions(-) create mode 100644 lib/observer/src/cdv_atom_cb.erl delete mode 100644 lib/observer/src/cdv_atom_wx.erl create mode 100644 lib/observer/src/cdv_bin_cb.erl delete mode 100644 lib/observer/src/cdv_bin_wx.erl delete mode 100644 lib/observer/src/cdv_detail_win.erl create mode 100644 lib/observer/src/cdv_detail_wx.erl create mode 100644 lib/observer/src/cdv_dist_cb.erl delete mode 100644 lib/observer/src/cdv_dist_wx.erl create mode 100644 lib/observer/src/cdv_ets_cb.erl delete mode 100644 lib/observer/src/cdv_ets_wx.erl create mode 100644 lib/observer/src/cdv_fun_cb.erl delete mode 100644 lib/observer/src/cdv_fun_wx.erl create mode 100644 lib/observer/src/cdv_gen_cb.erl delete mode 100644 lib/observer/src/cdv_gen_wx.erl delete mode 100644 lib/observer/src/cdv_html_page.erl create mode 100644 lib/observer/src/cdv_html_wx.erl delete mode 100644 lib/observer/src/cdv_info_page.erl create mode 100644 lib/observer/src/cdv_info_wx.erl create mode 100644 lib/observer/src/cdv_int_tab_cb.erl delete mode 100644 lib/observer/src/cdv_int_tab_wx.erl create mode 100644 lib/observer/src/cdv_mem_cb.erl delete mode 100644 lib/observer/src/cdv_mem_wx.erl create mode 100644 lib/observer/src/cdv_mod_cb.erl delete mode 100644 lib/observer/src/cdv_mod_wx.erl delete mode 100644 lib/observer/src/cdv_multi_panel.erl create mode 100644 lib/observer/src/cdv_multi_wx.erl create mode 100644 lib/observer/src/cdv_port_cb.erl delete mode 100644 lib/observer/src/cdv_port_wx.erl create mode 100644 lib/observer/src/cdv_proc_cb.erl delete mode 100644 lib/observer/src/cdv_proc_wx.erl delete mode 100644 lib/observer/src/cdv_table_page.erl create mode 100644 lib/observer/src/cdv_table_wx.erl create mode 100644 lib/observer/src/cdv_term_cb.erl delete mode 100644 lib/observer/src/cdv_term_wx.erl create mode 100644 lib/observer/src/cdv_timer_cb.erl delete mode 100644 lib/observer/src/cdv_timer_wx.erl delete mode 100644 lib/observer/src/cdv_virtual_list.erl create mode 100644 lib/observer/src/cdv_virtual_list_wx.erl create mode 100644 lib/observer/src/cdv_wx.erl delete mode 100644 lib/observer/src/crashdump_viewer_html.erl delete mode 100644 lib/observer/src/crashdump_viewer_wx.erl create mode 100644 lib/observer/src/observer_html_lib.erl (limited to 'lib/observer/src') diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 0b1813db0a..fc6f51c617 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -36,36 +36,34 @@ 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_html_page \ - 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 \ - cdv_bin_wx \ - cdv_term_wx \ + cdv_atom_cb \ + cdv_bin_cb \ + cdv_detail_wx \ + cdv_dist_cb \ + cdv_ets_cb \ + cdv_fun_cb \ + cdv_gen_cb \ + cdv_html_wx \ + cdv_info_wx \ + cdv_int_tab_cb \ + cdv_mem_cb \ + cdv_mod_cb \ + cdv_multi_wx \ + cdv_port_cb \ + cdv_proc_cb \ + cdv_table_wx \ + cdv_term_cb \ + cdv_timer_cb \ + cdv_virtual_list_wx \ + cdv_wx \ etop \ etop_gui \ etop_tr \ etop_txt \ observer \ observer_app_wx \ + observer_html_lib \ observer_lib \ - observer_wx \ observer_perf_wx \ observer_pro_wx \ observer_procinfo \ @@ -74,7 +72,7 @@ MODULES= \ observer_traceoptions_wx \ observer_tv_table \ observer_tv_wx \ - observer_term_wx \ + observer_wx \ ttb \ ttb_et diff --git a/lib/observer/src/cdv_atom_cb.erl b/lib/observer/src/cdv_atom_cb.erl new file mode 100644 index 0000000000..46fce81b52 --- /dev/null +++ b/lib/observer/src/cdv_atom_cb.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_cb). + +-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_wx +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_atom_wx.erl b/lib/observer/src/cdv_atom_wx.erl deleted file mode 100644 index 372badc944..0000000000 --- a/lib/observer/src/cdv_atom_wx.erl +++ /dev/null @@ -1,48 +0,0 @@ -%% -%% %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_cb.erl b/lib/observer/src/cdv_bin_cb.erl new file mode 100644 index 0000000000..0266047ee3 --- /dev/null +++ b/lib/observer/src/cdv_bin_cb.erl @@ -0,0 +1,82 @@ +%% +%% %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_cb). + +-export([get_details/1, + detail_pages/0]). + +%% Callbacks for cdv_detail_wx +get_details({_, {T,Key}}) -> + [{Key,Term}] = ets:lookup(T,Key), + {ok,{"Expanded Binary", Term, []}}; +get_details({cdv, Id}) -> + {ok,Bin} = crashdump_viewer:expand_binary(Id), + {ok,{"Expanded Binary", Bin, []}}. + +detail_pages() -> + [{"Binary", fun init_bin_page/2}]. + +init_bin_page(Parent,Bin) -> + cdv_multi_wx:start_link( + Parent, + [{"Format \~p",cdv_html_wx,format_bin_fun("~p",Bin)}, + {"Format \~tp",cdv_html_wx,format_bin_fun("~tp",Bin)}, + {"Format \~w",cdv_html_wx,format_bin_fun("~w",Bin)}, + {"Format \~s",cdv_html_wx,format_bin_fun("~s",Bin)}, + {"Format \~ts",cdv_html_wx,format_bin_fun("~ts",Bin)}, + {"Hex",cdv_html_wx,hex_binary_fun(Bin)}, + {"Term",cdv_html_wx,binary_to_term_fun(Bin)}]). + +format_bin_fun(Format,Bin) -> + fun() -> + try io_lib:format(Format,[Bin]) of + Str -> plain_html(lists:flatten(Str)) + catch error:badarg -> + Warning = "This binary can not be formatted with " ++ Format, + observer_html_lib:warning(Warning) + end + end. + +binary_to_term_fun(Bin) -> + fun() -> + try binary_to_term(Bin) of + Term -> plain_html(io_lib:format("~p",[Term])) + catch error:badarg -> + Warning = "This binary can not be coverted to an Erlang term", + observer_html_lib:warning(Warning) + end + end. + +-define(line_break,25). +hex_binary_fun(Bin) -> + fun() -> + S = "<<" ++ format_hex(Bin,?line_break) ++ ">>", + plain_html(io_lib:format("~s",[S])) + end. + +format_hex(<>,_) -> + [integer_to_list(B1,16),integer_to_list(B2,16)]; +format_hex(<>,0) -> + [integer_to_list(B1,16),integer_to_list(B2,16),$,,$\n,$\s,$\s + | format_hex(Bin,?line_break)]; +format_hex(<>,N) -> + [integer_to_list(B1,16),integer_to_list(B2,16),$, + | format_hex(Bin,N-1)]. + +plain_html(Text) -> + observer_html_lib:plain_page(Text). diff --git a/lib/observer/src/cdv_bin_wx.erl b/lib/observer/src/cdv_bin_wx.erl deleted file mode 100644 index 6cf344f945..0000000000 --- a/lib/observer/src/cdv_bin_wx.erl +++ /dev/null @@ -1,82 +0,0 @@ -%% -%% %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]). - -%% Callbacks for cdv_detail_win -get_details({_, {T,Key}}) -> - [{Key,Term}] = ets:lookup(T,Key), - {ok,{"Expanded Binary", Term, []}}; -get_details({cdv, Id}) -> - {ok,Bin} = crashdump_viewer:expand_binary(Id), - {ok,{"Expanded Binary", Bin, []}}. - -detail_pages() -> - [{"Binary", fun init_bin_page/2}]. - -init_bin_page(Parent,Bin) -> - cdv_multi_panel:start_link( - Parent, - [{"Format \~p",cdv_html_page,format_bin_fun("~p",Bin)}, - {"Format \~tp",cdv_html_page,format_bin_fun("~tp",Bin)}, - {"Format \~w",cdv_html_page,format_bin_fun("~w",Bin)}, - {"Format \~s",cdv_html_page,format_bin_fun("~s",Bin)}, - {"Format \~ts",cdv_html_page,format_bin_fun("~ts",Bin)}, - {"Hex",cdv_html_page,hex_binary_fun(Bin)}, - {"Term",cdv_html_page, binary_to_term_fun(Bin)}]). - -format_bin_fun(Format,Bin) -> - fun() -> - try io_lib:format(Format,[Bin]) of - Str -> plain_html(lists:flatten(Str)) - catch error:badarg -> - Warning = "This binary can not be formatted with " ++ Format, - crashdump_viewer_html:warning(Warning) - end - end. - -binary_to_term_fun(Bin) -> - fun() -> - try binary_to_term(Bin) of - Term -> plain_html(io_lib:format("~p",[Term])) - catch error:badarg -> - Warning = "This binary can not be coverted to an Erlang term", - crashdump_viewer_html:warning(Warning) - end - end. - --define(line_break,25). -hex_binary_fun(Bin) -> - fun() -> - S = "<<" ++ format_hex(Bin,?line_break) ++ ">>", - plain_html(io_lib:format("~s",[S])) - end. - -format_hex(<>,_) -> - [integer_to_list(B1,16),integer_to_list(B2,16)]; -format_hex(<>,0) -> - [integer_to_list(B1,16),integer_to_list(B2,16),$,,$\n,$\s,$\s - | format_hex(Bin,?line_break)]; -format_hex(<>,N) -> - [integer_to_list(B1,16),integer_to_list(B2,16),$, - | format_hex(Bin,N-1)]. - -plain_html(Text) -> - crashdump_viewer_html:plain_page(Text). diff --git a/lib/observer/src/cdv_detail_win.erl b/lib/observer/src/cdv_detail_win.erl deleted file mode 100644 index 4445169379..0000000000 --- a/lib/observer/src/cdv_detail_win.erl +++ /dev/null @@ -1,158 +0,0 @@ -%% -%% %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_link/3]). - --export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, - handle_call/3, handle_info/2]). - --include_lib("wx/include/wx.hrl"). --include("crashdump_viewer.hrl"). --include("observer_defs.hrl"). - --record(state, {parent, - frame, - id, - pages=[] - }). - -%% Defines --define(ID_NOTEBOOK, 604). - -%% Detail view -start_link(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, []), - Sizer = wxBoxSizer:new(?wxVERTICAL), - {InfoPanel,Pages} = create_pages(Panel,Callback:detail_pages(),[Info]), - wxSizer:add(Sizer, InfoPanel, [{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), - - wxFrame:connect(Frame, close_window), - wxMenu:connect(Frame, command_menu_selected), - wxFrame:show(Frame), - {Frame, #state{parent=Parent, - id=Id, - frame=Frame, - pages=Pages - }}. - -create_pages(Panel,[{_PageTitle,Fun}],FunArgs) -> - %% Only one page - don't create notebook - Page = init_panel(Panel, Fun, FunArgs), - {Page,[Page]}; -create_pages(Panel,PageSpecs,FunArgs) -> - Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), - Pages = [init_tab(Notebook, PageTitle, Fun, FunArgs) - || {PageTitle,Fun} <- PageSpecs], - {Notebook, Pages}. - -init_tab(Notebook,Title,Fun,FunArgs) -> - Panel = init_panel(Notebook,Fun,FunArgs), - true = wxNotebook:addPage(Notebook, Panel, Title), - Panel. - -init_panel(ParentWin, Fun, FunArgs) -> - Panel = wxScrolledWindow:new(ParentWin), - wxScrolledWindow:enableScrolling(Panel,true,true), - wxScrolledWindow:setScrollbars(Panel,1,1,0,0), - Sizer = wxBoxSizer:new(?wxHORIZONTAL), - Window = apply(Fun, [Panel | FunArgs]), - wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, - {proportion, 1}, - {border, 5}]), - wxPanel:setSizer(Panel, Sizer), - 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(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}) -> - wx_object:cast(Parent,{detail_win_closed, Id}), - case Frame of - undefined -> ok; - _ -> wxFrame:destroy(Frame) - end, - ok. - -code_change(_, _, State) -> - {ok, State}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -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_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl new file mode 100644 index 0000000000..dc93507a36 --- /dev/null +++ b/lib/observer/src/cdv_detail_wx.erl @@ -0,0 +1,158 @@ +%% +%% %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_wx). + +-behaviour(wx_object). + +-export([start_link/3]). + +-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, + handle_call/3, handle_info/2]). + +-include_lib("wx/include/wx.hrl"). +-include("crashdump_viewer.hrl"). +-include("observer_defs.hrl"). + +-record(state, {parent, + frame, + id, + pages=[] + }). + +%% Defines +-define(ID_NOTEBOOK, 604). + +%% Detail view +start_link(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, []), + Sizer = wxBoxSizer:new(?wxVERTICAL), + {InfoPanel,Pages} = create_pages(Panel,Callback:detail_pages(),[Info]), + wxSizer:add(Sizer, InfoPanel, [{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), + + wxFrame:connect(Frame, close_window), + wxMenu:connect(Frame, command_menu_selected), + wxFrame:show(Frame), + {Frame, #state{parent=Parent, + id=Id, + frame=Frame, + pages=Pages + }}. + +create_pages(Panel,[{_PageTitle,Fun}],FunArgs) -> + %% Only one page - don't create notebook + Page = init_panel(Panel, Fun, FunArgs), + {Page,[Page]}; +create_pages(Panel,PageSpecs,FunArgs) -> + Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), + Pages = [init_tab(Notebook, PageTitle, Fun, FunArgs) + || {PageTitle,Fun} <- PageSpecs], + {Notebook, Pages}. + +init_tab(Notebook,Title,Fun,FunArgs) -> + Panel = init_panel(Notebook,Fun,FunArgs), + true = wxNotebook:addPage(Notebook, Panel, Title), + Panel. + +init_panel(ParentWin, Fun, FunArgs) -> + Panel = wxScrolledWindow:new(ParentWin), + wxScrolledWindow:enableScrolling(Panel,true,true), + wxScrolledWindow:setScrollbars(Panel,1,1,0,0), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + Window = apply(Fun, [Panel | FunArgs]), + wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, + {border, 5}]), + wxPanel:setSizer(Panel, Sizer), + 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(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}) -> + wx_object:cast(Parent,{detail_win_closed, Id}), + case Frame of + undefined -> ok; + _ -> wxFrame:destroy(Frame) + end, + ok. + +code_change(_, _, State) -> + {ok, State}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +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_cb.erl b/lib/observer/src/cdv_dist_cb.erl new file mode 100644 index 0000000000..3860324d6f --- /dev/null +++ b/lib/observer/src/cdv_dist_cb.erl @@ -0,0 +1,91 @@ +%% +%% %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_cb). + +-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_wx +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, 130}, + {"Controller", ?wxLIST_FORMAT_LEFT, 130}, + {"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_wx +get_details(Id) -> + case crashdump_viewer:node_info(Id) of + {ok,Info,TW} -> + Proplist = crashdump_viewer:to_proplist(record_info(fields,nod),Info), + Title = io_lib:format("~s (~s)",[Info#nod.name,Id]), + {ok,{Title,Proplist,TW}}; + {error,not_found} -> + Info = "The node you are searching for could not be found.", + {info,Info} + end. + +detail_pages() -> + [{"General Information", fun init_gen_page/2}]. + +init_gen_page(Parent, Info) -> + Fields = info_fields(), + cdv_info_wx:start_link(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_dist_wx.erl b/lib/observer/src/cdv_dist_wx.erl deleted file mode 100644 index 3910f4fac5..0000000000 --- a/lib/observer/src/cdv_dist_wx.erl +++ /dev/null @@ -1,91 +0,0 @@ -%% -%% %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, 130}, - {"Controller", ?wxLIST_FORMAT_LEFT, 130}, - {"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) -> - case crashdump_viewer:node_info(Id) of - {ok,Info,TW} -> - Proplist = crashdump_viewer:to_proplist(record_info(fields,nod),Info), - Title = io_lib:format("~s (~s)",[Info#nod.name,Id]), - {ok,{Title,Proplist,TW}}; - {error,not_found} -> - Info = "The node you are searching for could not be found.", - {info,Info} - end. - -detail_pages() -> - [{"General Information", fun init_gen_page/2}]. - -init_gen_page(Parent, Info) -> - Fields = info_fields(), - cdv_info_page:start_link(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_cb.erl b/lib/observer/src/cdv_ets_cb.erl new file mode 100644 index 0000000000..2a5c170e58 --- /dev/null +++ b/lib/observer/src/cdv_ets_cb.erl @@ -0,0 +1,67 @@ +%% +%% %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_cb). + +-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_BUCK, ?COL_OWNER+1). +-define(COL_OBJ, ?COL_BUCK+1). +-define(COL_MEM, ?COL_OBJ+1). +-define(COL_TYPE, ?COL_MEM+1). + +%% Callbacks for cdv_virtual_list_wx +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, 50}, + {"Owner", ?wxLIST_FORMAT_CENTRE, 90}, + {"Buckets", ?wxLIST_FORMAT_RIGHT, 50}, + {"Objects", ?wxLIST_FORMAT_RIGHT, 50}, + {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, + {"Type", ?wxLIST_FORMAT_LEFT, 50} + ]. + +get_info(Owner) -> + {ok,Info,TW} = crashdump_viewer:ets_tables(Owner), + {Info,TW}. + +get_detail_cols(all) -> + {[?COL_OWNER],false}; +get_detail_cols(_) -> + {[],false}. diff --git a/lib/observer/src/cdv_ets_wx.erl b/lib/observer/src/cdv_ets_wx.erl deleted file mode 100644 index ac45aa297e..0000000000 --- a/lib/observer/src/cdv_ets_wx.erl +++ /dev/null @@ -1,67 +0,0 @@ -%% -%% %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_BUCK, ?COL_OWNER+1). --define(COL_OBJ, ?COL_BUCK+1). --define(COL_MEM, ?COL_OBJ+1). --define(COL_TYPE, ?COL_MEM+1). - -%% Callbacks for cdv_virtual_list -col_to_elem(id) -> col_to_elem(?COL_ID); -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, 50}, - {"Owner", ?wxLIST_FORMAT_CENTRE, 90}, - {"Buckets", ?wxLIST_FORMAT_RIGHT, 50}, - {"Objects", ?wxLIST_FORMAT_RIGHT, 50}, - {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, - {"Type", ?wxLIST_FORMAT_LEFT, 50} - ]. - -get_info(Owner) -> - {ok,Info,TW} = crashdump_viewer:ets_tables(Owner), - {Info,TW}. - -get_detail_cols(all) -> - {[?COL_OWNER],false}; -get_detail_cols(_) -> - {[],false}. diff --git a/lib/observer/src/cdv_fun_cb.erl b/lib/observer/src/cdv_fun_cb.erl new file mode 100644 index 0000000000..689ef0e3bb --- /dev/null +++ b/lib/observer/src/cdv_fun_cb.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_cb). + +-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_wx +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, 100}, + {"Index", ?wxLIST_FORMAT_RIGHT, 50}, + {"Address", ?wxLIST_FORMAT_LEFT, 120}, + {"Native Address", ?wxLIST_FORMAT_LEFT, 120}, + {"Refc", ?wxLIST_FORMAT_RIGHT, 50}]. + +get_info(_) -> + {ok,Info,TW} = crashdump_viewer:funs(), + {Info,TW}. + +get_detail_cols(_) -> + {[?COL_MOD],false}. diff --git a/lib/observer/src/cdv_fun_wx.erl b/lib/observer/src/cdv_fun_wx.erl deleted file mode 100644 index 296abd2719..0000000000 --- a/lib/observer/src/cdv_fun_wx.erl +++ /dev/null @@ -1,58 +0,0 @@ -%% -%% %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, 100}, - {"Index", ?wxLIST_FORMAT_RIGHT, 50}, - {"Address", ?wxLIST_FORMAT_LEFT, 120}, - {"Native Address", ?wxLIST_FORMAT_LEFT, 120}, - {"Refc", ?wxLIST_FORMAT_RIGHT, 50}]. - -get_info(_) -> - {ok,Info,TW} = crashdump_viewer:funs(), - {Info,TW}. - -get_detail_cols(_) -> - {[?COL_MOD],false}. diff --git a/lib/observer/src/cdv_gen_cb.erl b/lib/observer/src/cdv_gen_cb.erl new file mode 100644 index 0000000000..6be717d76d --- /dev/null +++ b/lib/observer/src/cdv_gen_cb.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_cb). + +-export([get_info/0]). + +-include("crashdump_viewer.hrl"). + +get_info() -> + {ok,Info,TW} = crashdump_viewer:general_info(), + Fields = info_fields(), + Proplist = + crashdump_viewer:to_proplist(record_info(fields,general_info),Info), + {Fields,Proplist,TW}. + +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_gen_wx.erl b/lib/observer/src/cdv_gen_wx.erl deleted file mode 100644 index 92759aaa6d..0000000000 --- a/lib/observer/src/cdv_gen_wx.erl +++ /dev/null @@ -1,45 +0,0 @@ -%% -%% %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,TW} = crashdump_viewer:general_info(), - Fields = info_fields(), - Proplist = - crashdump_viewer:to_proplist(record_info(fields,general_info),Info), - {Fields,Proplist,TW}. - -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_html_page.erl b/lib/observer/src/cdv_html_page.erl deleted file mode 100644 index 0208d1d67a..0000000000 --- a/lib/observer/src/cdv_html_page.erl +++ /dev/null @@ -1,128 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% --module(cdv_html_page). - --behaviour(wx_object). - --export([start_link/2]). -%% wx_object callbacks --export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, - handle_event/2, handle_cast/2]). - --include_lib("wx/include/wx.hrl"). --include("observer_defs.hrl"). - -%% Records --record(state, - {panel, - expand_table, - expand_wins=[]}). - -start_link(ParentWin, Info) -> - wx_object:start_link(?MODULE, [ParentWin, Info], []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init([ParentWin, Fun]) when is_function(Fun) -> - init([ParentWin, Fun()]); -init([ParentWin, {expand,HtmlText,Tab}]) -> - HtmlWin = observer_lib:html_window(ParentWin), - wxHtmlWindow:setPage(HtmlWin,HtmlText), - {HtmlWin, #state{panel=HtmlWin,expand_table=Tab}}; -init([ParentWin, HtmlText]) -> - HtmlWin = observer_lib:html_window(ParentWin), - wxHtmlWindow:setPage(HtmlWin,HtmlText), - {HtmlWin, #state{panel=HtmlWin}}. - -%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -handle_info(active, State) -> - {noreply, State}; - -handle_info(Info, State) -> - io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_, _, State) -> - {ok, State}. - -handle_call(Msg, _From, State) -> - io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), - {reply, ok, State}. - -handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) -> - Opened = lists:keydelete(Id, 1, Opened0), - {noreply, State#state{expand_wins=Opened}}; - -handle_cast(Msg, State) -> - io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), - {noreply, State}. - -handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, - linkInfo=#wxHtmlLinkInfo{href=Target}}}, - #state{expand_table=Tab}=State) -> - NewState= - case Target of - "#Binary?" ++ BinSpec -> - [{"offset",Off},{"size",Size},{"pos",Pos}] = - httpd:parse_query(BinSpec), - Id = {cdv, {list_to_integer(Off), - list_to_integer(Size), - list_to_integer(Pos)}}, - expand(Id,cdv_bin_wx,State); - "#OBSBinary?" ++ BinSpec -> - [{"key1",Preview},{"key2",Size},{"key3",Hash}] = - httpd:parse_query(BinSpec), - Id = {obs, {Tab, {list_to_integer(Preview), - list_to_integer(Size), - list_to_integer(Hash)}}}, - expand(Id,cdv_bin_wx,State); - "#Term?" ++ TermKeys -> - [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = - httpd:parse_query(TermKeys), - Id = {cdv, {Tab,{list_to_integer(Key1), - list_to_integer(Key2), - list_to_integer(Key3)}}}, - expand(Id,cdv_term_wx,State); - _ -> - cdv_virtual_list:start_detail_win(Target), - State - end, - {noreply, NewState}; - -handle_event(Event, State) -> - io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), - {noreply, State}. - -%%%----------------------------------------------------------------- -%%% Internal -expand(Id,Callback,#state{expand_wins=Opened0}=State) -> - Opened = - case lists:keyfind(Id,1,Opened0) of - false -> - EW = cdv_detail_win:start_link(Id,State#state.panel,Callback), - wx_object:get_pid(EW) ! active, - [{Id,EW}|Opened0]; - {_,EW} -> - wxFrame:raise(EW), - Opened0 - end, - State#state{expand_wins=Opened}. diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl new file mode 100644 index 0000000000..fe77a41f59 --- /dev/null +++ b/lib/observer/src/cdv_html_wx.erl @@ -0,0 +1,128 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(cdv_html_wx). + +-behaviour(wx_object). + +-export([start_link/2]). +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_cast/2]). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +%% Records +-record(state, + {panel, + expand_table, + expand_wins=[]}). + +start_link(ParentWin, Info) -> + wx_object:start_link(?MODULE, [ParentWin, Info], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([ParentWin, Fun]) when is_function(Fun) -> + init([ParentWin, Fun()]); +init([ParentWin, {expand,HtmlText,Tab}]) -> + HtmlWin = observer_lib:html_window(ParentWin), + wxHtmlWindow:setPage(HtmlWin,HtmlText), + {HtmlWin, #state{panel=HtmlWin,expand_table=Tab}}; +init([ParentWin, HtmlText]) -> + HtmlWin = observer_lib:html_window(ParentWin), + wxHtmlWindow:setPage(HtmlWin,HtmlText), + {HtmlWin, #state{panel=HtmlWin}}. + +%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info(active, State) -> + {noreply, State}; + +handle_info(Info, State) -> + io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_, _, State) -> + {ok, State}. + +handle_call(Msg, _From, State) -> + io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) -> + Opened = lists:keydelete(Id, 1, Opened0), + {noreply, State#state{expand_wins=Opened}}; + +handle_cast(Msg, State) -> + io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), + {noreply, State}. + +handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, + linkInfo=#wxHtmlLinkInfo{href=Target}}}, + #state{expand_table=Tab}=State) -> + NewState= + case Target of + "#Binary?" ++ BinSpec -> + [{"offset",Off},{"size",Size},{"pos",Pos}] = + httpd:parse_query(BinSpec), + Id = {cdv, {list_to_integer(Off), + list_to_integer(Size), + list_to_integer(Pos)}}, + expand(Id,cdv_bin_cb,State); + "#OBSBinary?" ++ BinSpec -> + [{"key1",Preview},{"key2",Size},{"key3",Hash}] = + httpd:parse_query(BinSpec), + Id = {obs, {Tab, {list_to_integer(Preview), + list_to_integer(Size), + list_to_integer(Hash)}}}, + expand(Id,cdv_bin_cb,State); + "#Term?" ++ TermKeys -> + [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = + httpd:parse_query(TermKeys), + Id = {cdv, {Tab,{list_to_integer(Key1), + list_to_integer(Key2), + list_to_integer(Key3)}}}, + expand(Id,cdv_term_cb,State); + _ -> + cdv_virtual_list_wx:start_detail_win(Target), + State + end, + {noreply, NewState}; + +handle_event(Event, State) -> + io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), + {noreply, State}. + +%%%----------------------------------------------------------------- +%%% Internal +expand(Id,Callback,#state{expand_wins=Opened0}=State) -> + Opened = + case lists:keyfind(Id,1,Opened0) of + false -> + EW = cdv_detail_wx:start_link(Id,State#state.panel,Callback), + wx_object:get_pid(EW) ! active, + [{Id,EW}|Opened0]; + {_,EW} -> + wxFrame:raise(EW), + Opened0 + end, + State#state{expand_wins=Opened}. diff --git a/lib/observer/src/cdv_info_page.erl b/lib/observer/src/cdv_info_page.erl deleted file mode 100644 index 27eb71225d..0000000000 --- a/lib/observer/src/cdv_info_page.erl +++ /dev/null @@ -1,128 +0,0 @@ -%% -%% %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(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(new_dump, _From, #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), - {reply, ok, State#state{fpanel=NewFPanel,trunc_warn=TW}}; - -handle_call(Msg, _From, State) -> - io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), - {reply, ok, State}. - -handle_cast(Msg, State) -> - io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), - {noreply, State}. - -handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) -> - cdv_virtual_list:start_detail_win(Target), - {noreply, State}; - -handle_event(#wx{obj=Obj,event=#wxMouse{type=enter_window}},State) -> - wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), - {noreply, State}; - -handle_event(#wx{obj=Obj,event=#wxMouse{type=leave_window}},State) -> - wxTextCtrl:setForegroundColour(Obj,?wxBLUE), - {noreply, State}; - -handle_event(Event, State) -> - io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), - {noreply, State}. - -%%%----------------------------------------------------------------- -%%% 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_info_wx.erl b/lib/observer/src/cdv_info_wx.erl new file mode 100644 index 0000000000..59ce0cabb1 --- /dev/null +++ b/lib/observer/src/cdv_info_wx.erl @@ -0,0 +1,128 @@ +%% +%% %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_wx). + +-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) -> + cdv_wx:set_status(State#state.trunc_warn), + {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(new_dump, _From, #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), + {reply, ok, State#state{fpanel=NewFPanel,trunc_warn=TW}}; + +handle_call(Msg, _From, State) -> + io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast(Msg, State) -> + io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), + {noreply, State}. + +handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) -> + cdv_virtual_list_wx:start_detail_win(Target), + {noreply, State}; + +handle_event(#wx{obj=Obj,event=#wxMouse{type=enter_window}},State) -> + wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), + {noreply, State}; + +handle_event(#wx{obj=Obj,event=#wxMouse{type=leave_window}},State) -> + wxTextCtrl:setForegroundColour(Obj,?wxBLUE), + {noreply, State}; + +handle_event(Event, State) -> + io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), + {noreply, State}. + +%%%----------------------------------------------------------------- +%%% 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_cb.erl b/lib/observer/src/cdv_int_tab_cb.erl new file mode 100644 index 0000000000..31727391fe --- /dev/null +++ b/lib/observer/src/cdv_int_tab_cb.erl @@ -0,0 +1,86 @@ +%% +%% %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_cb). + +-export([get_info/0]). + +-include_lib("wx/include/wx.hrl"). +-include("crashdump_viewer.hrl"). + +get_info() -> + observer_lib:report_progress({ok,"Processing internal tables"}), + HashInfo = get_hash_info(), + observer_lib:report_progress({ok,33}), + IndexInfo = get_index_info(), + observer_lib:report_progress({ok,66}), + IntEtsInfo = get_internal_ets_info(), + observer_lib:report_progress({ok,100}), + [{"Hash Tables",cdv_table_wx,HashInfo}, + {"Index Tables",cdv_table_wx,IndexInfo}, + {"Internal ETS Tables",cdv_table_wx,IntEtsInfo}]. + +%%%----------------------------------------------------------------- +%%% 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_int_tab_wx.erl b/lib/observer/src/cdv_int_tab_wx.erl deleted file mode 100644 index b4fa6ca889..0000000000 --- a/lib/observer/src/cdv_int_tab_wx.erl +++ /dev/null @@ -1,86 +0,0 @@ -%% -%% %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() -> - observer_lib:report_progress({ok,"Processing internal tables"}), - HashInfo = get_hash_info(), - observer_lib:report_progress({ok,33}), - IndexInfo = get_index_info(), - observer_lib:report_progress({ok,66}), - IntEtsInfo = get_internal_ets_info(), - observer_lib:report_progress({ok,100}), - [{"Hash Tables",cdv_table_page,HashInfo}, - {"Index Tables",cdv_table_page,IndexInfo}, - {"Internal ETS Tables",cdv_table_page,IntEtsInfo}]. - -%%%----------------------------------------------------------------- -%%% Hash tables -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_cb.erl b/lib/observer/src/cdv_mem_cb.erl new file mode 100644 index 0000000000..2b0809df13 --- /dev/null +++ b/lib/observer/src/cdv_mem_cb.erl @@ -0,0 +1,84 @@ +%% +%% %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_cb). + +-export([get_info/0]). + +-include("crashdump_viewer.hrl"). +-include_lib("wx/include/wx.hrl"). + +get_info() -> + observer_lib:report_progress({ok,"Processing memory info"}), + MemInfo = get_mem_info(), + observer_lib:report_progress({ok,33}), + {AllocInfo,AllocTW} = get_alloc_info(), + observer_lib:report_progress({ok,66}), + AreaInfo = get_area_info(), + observer_lib:report_progress({ok,100}), + [{"Memory",cdv_info_wx,MemInfo} + | [{Title,cdv_table_wx,{Cols,Data,AllocTW}} || + {Title,Cols,Data} <- AllocInfo]] ++ + [{"Allocated Areas",cdv_table_wx,AreaInfo}]. + + +%%%----------------------------------------------------------------- +%%% 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, 180} | + [{Column, ?wxLIST_FORMAT_RIGHT, 140} || Column <- Columns]]. diff --git a/lib/observer/src/cdv_mem_wx.erl b/lib/observer/src/cdv_mem_wx.erl deleted file mode 100644 index 673795f39d..0000000000 --- a/lib/observer/src/cdv_mem_wx.erl +++ /dev/null @@ -1,84 +0,0 @@ -%% -%% %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() -> - observer_lib:report_progress({ok,"Processing memory info"}), - MemInfo = get_mem_info(), - observer_lib:report_progress({ok,33}), - {AllocInfo,AllocTW} = get_alloc_info(), - observer_lib:report_progress({ok,66}), - AreaInfo = get_area_info(), - observer_lib:report_progress({ok,100}), - [{"Memory",cdv_info_page,MemInfo} - | [{Title,cdv_table_page,{Cols,Data,AllocTW}} || - {Title,Cols,Data} <- AllocInfo]] ++ - [{"Allocated Areas",cdv_table_page,AreaInfo}]. - - -%%%----------------------------------------------------------------- -%%% 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, 180} | - [{Column, ?wxLIST_FORMAT_RIGHT, 140} || Column <- Columns]]. diff --git a/lib/observer/src/cdv_mod_cb.erl b/lib/observer/src/cdv_mod_cb.erl new file mode 100644 index 0000000000..e829ff4fca --- /dev/null +++ b/lib/observer/src/cdv_mod_cb.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_mod_cb). + +-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_wx +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_wx +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() -> + [{"General Information", fun init_gen_page/2}, + {"Current Attributes", fun init_curr_attr_page/2}, + {"Current Compilation Info", fun init_curr_comp_page/2}, + {"Old Attributes", fun init_old_attr_page/2}, + {"Old Compilation Info", fun init_old_comp_page/2}]. + +init_gen_page(Parent, Info) -> + Fields = info_fields(), + cdv_info_wx:start_link(Parent,{Fields,Info,[]}). + +init_curr_attr_page(Parent, Info) -> + init_info_page(Parent, proplists:get_value(current_attrib,Info)). + +init_curr_comp_page(Parent, Info) -> + init_info_page(Parent, proplists:get_value(current_comp_info,Info)). + +init_old_attr_page(Parent, Info) -> + init_info_page(Parent, proplists:get_value(old_attrib,Info)). + +init_old_comp_page(Parent, Info) -> + init_info_page(Parent, proplists:get_value(old_comp_info,Info)). + +init_info_page(Parent, undefined) -> + init_info_page(Parent, ""); +init_info_page(Parent, String) -> + cdv_html_wx:start_link(Parent,observer_html_lib:plain_page(String)). + +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_mod_wx.erl b/lib/observer/src/cdv_mod_wx.erl deleted file mode 100644 index 8751651fec..0000000000 --- a/lib/observer/src/cdv_mod_wx.erl +++ /dev/null @@ -1,102 +0,0 @@ -%% -%% %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() -> - [{"General Information", fun init_gen_page/2}, - {"Current Attributes", fun init_curr_attr_page/2}, - {"Current Compilation Info", fun init_curr_comp_page/2}, - {"Old Attributes", fun init_old_attr_page/2}, - {"Old Compilation Info", fun init_old_comp_page/2}]. - -init_gen_page(Parent, Info) -> - Fields = info_fields(), - cdv_info_page:start_link(Parent,{Fields,Info,[]}). - -init_curr_attr_page(Parent, Info) -> - init_info_page(Parent, proplists:get_value(current_attrib,Info)). - -init_curr_comp_page(Parent, Info) -> - init_info_page(Parent, proplists:get_value(current_comp_info,Info)). - -init_old_attr_page(Parent, Info) -> - init_info_page(Parent, proplists:get_value(old_attrib,Info)). - -init_old_comp_page(Parent, Info) -> - init_info_page(Parent, proplists:get_value(old_comp_info,Info)). - -init_info_page(Parent, undefined) -> - init_info_page(Parent, ""); -init_info_page(Parent, String) -> - cdv_html_page:start_link(Parent,crashdump_viewer_html:plain_page(String)). - -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 deleted file mode 100644 index 8218824ff2..0000000000 --- a/lib/observer/src/cdv_multi_panel.erl +++ /dev/null @@ -1,188 +0,0 @@ -%% -%% %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, Info) -> - wx_object:start_link(?MODULE, [Notebook, Info], []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init([Notebook, Callback]) when is_atom(Callback) -> - Pages = Callback:get_info(), - {MainPanel,State0} = init([Notebook, Pages]), - {MainPanel,State0#state{callback=Callback}}; -init([Notebook, Pages]) -> - MainPanel = wxPanel:new(Notebook), - Sizer = wxBoxSizer:new(?wxHORIZONTAL), - LeftMenuSizer = wxStaticBoxSizer:new(?wxVERTICAL,MainPanel, - [{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), - - State = load_dyn_page(#state{main_panel=MainPanel, - main_sizer=Sizer, - menu=LeftMenu, - menu_sizer=LeftMenuSizer, - pages=Pages, - dyn_panel=DynPanel - }), - {MainPanel, State}. - -%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -handle_info(active, State) -> - NewState = - wx:batch( - fun() -> - update_dyn_page(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(new_dump, _From, State) -> - NewState = - wx:batch( - fun() -> - update_left_menu(State) - end), - {reply, ok, NewState}; - -handle_call(Msg, _From, State) -> - io:format("~p:~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), - {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) -> - %% Freeze and thaw causes a hang (and is not needed) on 2.9 and higher - DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9], - DoFreeze andalso wxWindow:freeze(DynPanel), - Name = wxListBox:getStringSelection(Menu), - {Page,Sizer} = load_dyn_page(DynPanel,Name,Pages), - wxSizer:layout(MainSizer), - DoFreeze andalso wxWindow:thaw(DynPanel), - wx_object:get_pid(Page) ! active, - State#state{dyn_page=Page,dyn_sizer=Sizer}. - -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_multi_wx.erl b/lib/observer/src/cdv_multi_wx.erl new file mode 100644 index 0000000000..75c7f48fc2 --- /dev/null +++ b/lib/observer/src/cdv_multi_wx.erl @@ -0,0 +1,188 @@ +%% +%% %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_wx). + +-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, Info) -> + wx_object:start_link(?MODULE, [Notebook, Info], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([Notebook, Callback]) when is_atom(Callback) -> + Pages = Callback:get_info(), + {MainPanel,State0} = init([Notebook, Pages]), + {MainPanel,State0#state{callback=Callback}}; +init([Notebook, Pages]) -> + MainPanel = wxPanel:new(Notebook), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + LeftMenuSizer = wxStaticBoxSizer:new(?wxVERTICAL,MainPanel, + [{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), + + State = load_dyn_page(#state{main_panel=MainPanel, + main_sizer=Sizer, + menu=LeftMenu, + menu_sizer=LeftMenuSizer, + pages=Pages, + dyn_panel=DynPanel + }), + {MainPanel, State}. + +%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info(active, State) -> + NewState = + wx:batch( + fun() -> + update_dyn_page(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(new_dump, _From, State) -> + NewState = + wx:batch( + fun() -> + update_left_menu(State) + end), + {reply, ok, NewState}; + +handle_call(Msg, _From, State) -> + io:format("~p:~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), + {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) -> + %% Freeze and thaw causes a hang (and is not needed) on 2.9 and higher + DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9], + DoFreeze andalso wxWindow:freeze(DynPanel), + Name = wxListBox:getStringSelection(Menu), + {Page,Sizer} = load_dyn_page(DynPanel,Name,Pages), + wxSizer:layout(MainSizer), + DoFreeze andalso wxWindow:thaw(DynPanel), + wx_object:get_pid(Page) ! active, + State#state{dyn_page=Page,dyn_sizer=Sizer}. + +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_cb.erl b/lib/observer/src/cdv_port_cb.erl new file mode 100644 index 0000000000..08488d3e34 --- /dev/null +++ b/lib/observer/src/cdv_port_cb.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_port_cb). + +-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_wx +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, 100}, + {"Connected", ?wxLIST_FORMAT_LEFT, 120}, + {"Name", ?wxLIST_FORMAT_LEFT, 150}, + {"Controls", ?wxLIST_FORMAT_LEFT, 200}, + {"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_wx +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?", + Fun = fun() -> cdv_virtual_list_wx:start_detail_win(NodeId) end, + {yes_no, Info, Fun}; + {error,not_found} -> + Info = "The port you are searching for could not be found.", + {info,Info} + end. + +detail_pages() -> + [{"General Information", fun init_gen_page/2}]. + +init_gen_page(Parent, Info) -> + Fields = info_fields(), + cdv_info_wx:start_link(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", {click,connected}}, + {"Slot", slot}, + {"Controls", controls}]}, + {scroll_boxes, + [{"Links",1,{click,links}}, + {"Monitors",1,{click,monitors}}]}]. diff --git a/lib/observer/src/cdv_port_wx.erl b/lib/observer/src/cdv_port_wx.erl deleted file mode 100644 index e34198ea0b..0000000000 --- a/lib/observer/src/cdv_port_wx.erl +++ /dev/null @@ -1,102 +0,0 @@ -%% -%% %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, 100}, - {"Connected", ?wxLIST_FORMAT_LEFT, 120}, - {"Name", ?wxLIST_FORMAT_LEFT, 150}, - {"Controls", ?wxLIST_FORMAT_LEFT, 200}, - {"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() -> - [{"General Information", fun init_gen_page/2}]. - -init_gen_page(Parent, Info) -> - Fields = info_fields(), - cdv_info_page:start_link(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", {click,connected}}, - {"Slot", slot}, - {"Controls", controls}]}, - {scroll_boxes, - [{"Links",1,{click,links}}, - {"Monitors",1,{click,monitors}}]}]. diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl new file mode 100644 index 0000000000..dfc2df9c4c --- /dev/null +++ b/lib/observer/src/cdv_proc_cb.erl @@ -0,0 +1,156 @@ +%% +%% %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_cb). + +-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_wx +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, 80}, + {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, + {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50}]. + +get_info(_) -> + {ok,Info,TW} = crashdump_viewer:processes(), + {Info,TW}. + +get_detail_cols(_) -> + {[?COL_ID],true}. + +%% Callbacks for cdv_detail_wx +get_details(Id) -> + case crashdump_viewer:proc_details(Id) of + {ok,Info,TW} -> + %% The following table is used by observer_html_lib + %% for storing expanded terms and it is read by + %% cdv_html_wx when a link to an expandable term is clicked. + Tab = ets:new(cdv_expand,[set,public]), + Proplist0 = + crashdump_viewer:to_proplist(record_info(fields,proc),Info), + Proplist = [{expand_table,Tab}|Proplist0], + Title = io_lib:format("~s (~s)",[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?", + Fun = fun() -> cdv_virtual_list_wx:start_detail_win(NodeId) end, + {yes_no, Info, Fun}; + {error,not_found} -> + Info = "The process you are searching for could not be found.", + {info,Info} + end. + +detail_pages() -> + [{"General Information", fun init_gen_page/2}, + {"Messages", fun init_message_page/2}, + {"Dictionary", fun init_dict_page/2}, + {"Stack Dump", fun init_stack_page/2}, + {"ETS tables", fun init_ets_page/2}, + {"Timers", fun init_timer_page/2}]. + +init_gen_page(Parent, Info) -> + Fields = info_fields(), + cdv_info_wx:start_link(Parent,{Fields,Info,[]}). + +init_message_page(Parent, Info) -> + init_memory_page(Parent, Info, msg_q, "MsgQueue"). + +init_dict_page(Parent, Info) -> + init_memory_page(Parent, Info, dict, "Dictionary"). + +init_stack_page(Parent, Info) -> + init_memory_page(Parent, Info, stack_dump, "StackDump"). + +init_memory_page(Parent, Info0, Tag, Heading) -> + Info = proplists:get_value(Tag,Info0), + Tab = proplists:get_value(expand_table,Info0), + Html = observer_html_lib:expandable_term(Heading,Info,Tab), + cdv_html_wx:start_link(Parent,{expand,Html,Tab}). + +init_ets_page(Parent, Info) -> + Pid = proplists:get_value(pid,Info), + cdv_virtual_list_wx:start_link(Parent, cdv_ets_cb, Pid). + +init_timer_page(Parent, Info) -> + Pid = proplists:get_value(pid,Info), + cdv_virtual_list_wx:start_link(Parent, cdv_timer_cb, 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_proc_wx.erl b/lib/observer/src/cdv_proc_wx.erl deleted file mode 100644 index 6a8fa0b9be..0000000000 --- a/lib/observer/src/cdv_proc_wx.erl +++ /dev/null @@ -1,155 +0,0 @@ -%% -%% %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, 80}, - {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, - {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50}]. - -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} -> - %% The following table is used by crashdump_viewer_html - %% for storing expanded terms and it is read by - %% cdv_html_page when a link to an expandable term is clicked. - Tab = ets:new(cdv_expand,[set,public]), - Proplist0 = - crashdump_viewer:to_proplist(record_info(fields,proc),Info), - Proplist = [{expand_table,Tab}|Proplist0], - Title = io_lib:format("~s (~s)",[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() -> - [{"General Information", fun init_gen_page/2}, - {"Messages", fun init_message_page/2}, - {"Dictionary", fun init_dict_page/2}, - {"Stack Dump", fun init_stack_page/2}, - {"ETS tables", fun init_ets_page/2}, - {"Timers", fun init_timer_page/2}]. - -init_gen_page(Parent, Info) -> - Fields = info_fields(), - cdv_info_page:start_link(Parent,{Fields,Info,[]}). - -init_message_page(Parent, Info) -> - init_memory_page(Parent, Info, msg_q, "MsgQueue"). - -init_dict_page(Parent, Info) -> - init_memory_page(Parent, Info, dict, "Dictionary"). - -init_stack_page(Parent, Info) -> - init_memory_page(Parent, Info, stack_dump, "StackDump"). - -init_memory_page(Parent, Info0, Tag, Heading) -> - Info = proplists:get_value(Tag,Info0), - Tab = proplists:get_value(expand_table,Info0), - Html = crashdump_viewer_html:expandable_term(Heading,Info,Tab), - cdv_html_page:start_link(Parent,{expand,Html,Tab}). - -init_ets_page(Parent, Info) -> - Pid = proplists:get_value(pid,Info), - cdv_virtual_list:start_link(Parent, cdv_ets_wx, Pid). - -init_timer_page(Parent, Info) -> - Pid = proplists:get_value(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 deleted file mode 100644 index 71ec7686ce..0000000000 --- a/lib/observer/src/cdv_table_page.erl +++ /dev/null @@ -1,106 +0,0 @@ -%% -%% %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(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_table_wx.erl b/lib/observer/src/cdv_table_wx.erl new file mode 100644 index 0000000000..f8943db17d --- /dev/null +++ b/lib/observer/src/cdv_table_wx.erl @@ -0,0 +1,106 @@ +%% +%% %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_wx). + +-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) -> + cdv_wx:set_status(State#state.trunc_warn), + {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_term_cb.erl b/lib/observer/src/cdv_term_cb.erl new file mode 100644 index 0000000000..6426cc0803 --- /dev/null +++ b/lib/observer/src/cdv_term_cb.erl @@ -0,0 +1,75 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(cdv_term_cb). + +-export([get_details/1, + detail_pages/0]). + +%% Callbacks for cdv_detail_wx +get_details({_, {T,Key}}) -> + [{Key,Term}] = ets:lookup(T,Key), + {ok,{"Expanded Term", [Term, T], []}}. + +detail_pages() -> + [{"Term", fun init_term_page/2}]. + +init_term_page(ParentWin, [Term, Tab]) -> + Expanded = expand(Term, true), + BinSaved = expand(Term, Tab), + cdv_multi_wx:start_link( + ParentWin, + [{"Format \~p",cdv_html_wx,format_term_fun("~p",BinSaved,Tab)}, + {"Format \~tp",cdv_html_wx,format_term_fun("~tp",BinSaved,Tab)}, + {"Format \~w",cdv_html_wx,format_term_fun("~w",BinSaved,Tab)}, + {"Format \~s",cdv_html_wx,format_term_fun("~s",Expanded,Tab)}, + {"Format \~ts",cdv_html_wx,format_term_fun("~ts",Expanded,Tab)}]). + +format_term_fun(Format,Term,Tab) -> + fun() -> + try io_lib:format(Format,[Term]) of + Str -> {expand, plain_html(Str), Tab} + catch error:badarg -> + Warning = "This term can not be formatted with " ++ Format, + observer_html_lib:warning(Warning) + end + end. + +plain_html(Text) -> + observer_html_lib:plain_page(Text). + +expand(['#CDVBin',Offset,Size,Pos], true) -> + {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), + Bin; +expand(Bin, Tab) when is_binary(Bin), not is_boolean(Tab) -> + <> = Bin, + Size = byte_size(Bin), + Hash = erlang:phash2(Bin), + Key = {Preview, Size, Hash}, + ets:insert(Tab, {Key,Bin}), + ['#OBSBin',Preview,Size,Hash]; +expand([H|T], Expand) -> + case expand(T, Expand) of + ET when is_list(ET) -> + [expand(H, Expand)|ET]; + ET -> % The tail is an expanded binary - cannot append with | + [expand(H, Expand),ET] + end; +expand(Tuple, Expand) when is_tuple(Tuple) -> + list_to_tuple(expand(tuple_to_list(Tuple), Expand)); +expand(Term, _) -> + Term. diff --git a/lib/observer/src/cdv_term_wx.erl b/lib/observer/src/cdv_term_wx.erl deleted file mode 100644 index 9b83249eae..0000000000 --- a/lib/observer/src/cdv_term_wx.erl +++ /dev/null @@ -1,75 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% --module(cdv_term_wx). - --export([get_details/1, - detail_pages/0]). - -%% Callbacks for cdv_detail_win -get_details({_, {T,Key}}) -> - [{Key,Term}] = ets:lookup(T,Key), - {ok,{"Expanded Term", [Term, T], []}}. - -detail_pages() -> - [{"Term", fun init_term_page/2}]. - -init_term_page(ParentWin, [Term, Tab]) -> - Expanded = expand(Term, true), - BinSaved = expand(Term, Tab), - cdv_multi_panel:start_link( - ParentWin, - [{"Format \~p",cdv_html_page,format_term_fun("~p",BinSaved,Tab)}, - {"Format \~tp",cdv_html_page,format_term_fun("~tp",BinSaved,Tab)}, - {"Format \~w",cdv_html_page,format_term_fun("~w",BinSaved,Tab)}, - {"Format \~s",cdv_html_page,format_term_fun("~s",Expanded,Tab)}, - {"Format \~ts",cdv_html_page,format_term_fun("~ts",Expanded,Tab)}]). - -format_term_fun(Format,Term,Tab) -> - fun() -> - try io_lib:format(Format,[Term]) of - Str -> {expand, plain_html(Str), Tab} - catch error:badarg -> - Warning = "This term can not be formatted with " ++ Format, - crashdump_viewer_html:warning(Warning) - end - end. - -plain_html(Text) -> - crashdump_viewer_html:plain_page(Text). - -expand(['#CDVBin',Offset,Size,Pos], true) -> - {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), - Bin; -expand(Bin, Tab) when is_binary(Bin), not is_boolean(Tab) -> - <> = Bin, - Size = byte_size(Bin), - Hash = erlang:phash2(Bin), - Key = {Preview, Size, Hash}, - ets:insert(Tab, {Key,Bin}), - ['#OBSBin',Preview,Size,Hash]; -expand([H|T], Expand) -> - case expand(T, Expand) of - ET when is_list(ET) -> - [expand(H, Expand)|ET]; - ET -> % The tail is an expanded binary - cannot append with | - [expand(H, Expand),ET] - end; -expand(Tuple, Expand) when is_tuple(Tuple) -> - list_to_tuple(expand(tuple_to_list(Tuple), Expand)); -expand(Term, _) -> - Term. diff --git a/lib/observer/src/cdv_timer_cb.erl b/lib/observer/src/cdv_timer_cb.erl new file mode 100644 index 0000000000..9cdbfa05a9 --- /dev/null +++ b/lib/observer/src/cdv_timer_cb.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_cb). + +-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_wx +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, 110}, + {"Message", ?wxLIST_FORMAT_LEFT, 400}, + {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 80}]. + +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_timer_wx.erl b/lib/observer/src/cdv_timer_wx.erl deleted file mode 100644 index 2bd250a46c..0000000000 --- a/lib/observer/src/cdv_timer_wx.erl +++ /dev/null @@ -1,51 +0,0 @@ -%% -%% %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, 110}, - {"Message", ?wxLIST_FORMAT_LEFT, 400}, - {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 80}]. - -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 deleted file mode 100644 index 5897b9d02a..0000000000 --- a/lib/observer/src/cdv_virtual_list.erl +++ /dev/null @@ -1,414 +0,0 @@ -%% -%% %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_link(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(active, State) -> - crashdump_viewer_wx:set_status(State#state.trunc_warn), - {noreply, State}; - -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(new_dump, _From, - #state{grid=Grid,detail_wins=Opened, - holder=Holder,callback=Callback}=State) -> - lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened), - wxListCtrl:deleteAllItems(Grid), - Ref = erlang:monitor(process,Holder), - Holder ! stop, - receive {'DOWN',Ref,_,_,_} -> ok end, - {NewHolder,TW} = spawn_table_holder(Callback, all), - {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; - -handle_call(Msg, _From, State) -> - io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), - {reply, ok, State}. - -handle_cast({start_detail_win,Id}, State) -> - State2 = do_start_detail_win(Id, State), - {noreply, State2}; - -handle_cast({detail_win_closed, Id},#state{detail_wins=Opened}=State) -> - Opened2 = lists:keydelete(Id, 1, Opened), - {noreply, State#state{detail_wins=Opened2}}; - -handle_cast(Msg, State) -> - io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), - {noreply, State}. - -%%%%%%%%%%%%%%%%%%%%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, InfoList0) -> - Sort = #sort{sort_key=Callback:col_to_elem(id)}, - {_Sort, InfoList} = do_sort(Sort,InfoList0), - Info = array:from_list(InfoList), - NRows = array:size(Info), - Parent ! {holder_updated, NRows}, - table_holder(#holder{parent=Parent, - info=Info, - sort=Sort, - attrs=Attrs, - callback=Callback}). - -table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> - receive - _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}. - -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/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl new file mode 100644 index 0000000000..c5a7d9a2e5 --- /dev/null +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -0,0 +1,414 @@ +%% +%% %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_wx). + +-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_cb; + "#Port"++_ -> + cdv_port_cb; + _ -> + case catch list_to_integer(Id) of + NodeId when is_integer(NodeId) -> + cdv_dist_cb; + _ -> + cdv_mod_cb + 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_wx:start_link(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(active, State) -> + cdv_wx:set_status(State#state.trunc_warn), + {noreply, State}; + +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(new_dump, _From, + #state{grid=Grid,detail_wins=Opened, + holder=Holder,callback=Callback}=State) -> + lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened), + wxListCtrl:deleteAllItems(Grid), + Ref = erlang:monitor(process,Holder), + Holder ! stop, + receive {'DOWN',Ref,_,_,_} -> ok end, + {NewHolder,TW} = spawn_table_holder(Callback, all), + {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; + +handle_call(Msg, _From, State) -> + io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast({start_detail_win,Id}, State) -> + State2 = do_start_detail_win(Id, State), + {noreply, State2}; + +handle_cast({detail_win_closed, Id},#state{detail_wins=Opened}=State) -> + Opened2 = lists:keydelete(Id, 1, Opened), + {noreply, State#state{detail_wins=Opened2}}; + +handle_cast(Msg, State) -> + io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), + {noreply, State}. + +%%%%%%%%%%%%%%%%%%%%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, InfoList0) -> + Sort = #sort{sort_key=Callback:col_to_elem(id)}, + {_Sort, InfoList} = do_sort(Sort,InfoList0), + Info = array:from_list(InfoList), + NRows = array:size(Info), + Parent ! {holder_updated, NRows}, + table_holder(#holder{parent=Parent, + info=Info, + sort=Sort, + attrs=Attrs, + callback=Callback}). + +table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> + receive + _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}. + +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/cdv_wx.erl b/lib/observer/src/cdv_wx.erl new file mode 100644 index 0000000000..26df60b0a6 --- /dev/null +++ b/lib/observer/src/cdv_wx.erl @@ -0,0 +1,462 @@ +%% +%% %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_wx). +-compile(export_all). +-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(File0) -> + register(?SERVER, self()), + wx:new(), + + {ok,CdvServer} = crashdump_viewer:start_link(), + + catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), + Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer", + [{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), + + %% 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), + + %% 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), + + case load_dump(Frame,File0) of + {ok,File} -> + %% Set window title + T1 = "Crashdump Viewer: ", + Title = + if length(File) > 70 -> + T1 ++ filename:basename(File); + true -> + T1 ++ File + end, + wxFrame:setTitle(Frame, Title), + + setup(#state{server=CdvServer, + file=File, + frame=Frame, + status_bar=StatusBar, + notebook=Notebook, + main_panel=Panel}); + error -> + wxFrame:destroy(Frame), + wx:destroy(), + crashdump_viewer:stop(), + ignore + end. + +setup(#state{frame=Frame, notebook=Notebook}=State) -> + + %% Setup Menubar & Menus + MenuBar = wxMenuBar:new(), + DefMenus = default_menus(), + observer_lib:create_menus(DefMenus, MenuBar, default), + wxFrame:setMenuBar(Frame, MenuBar), + + %% General information Panel + GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_wx, cdv_gen_cb), + + %% Process Panel + ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list_wx, cdv_proc_cb), + + %% Port Panel + PortPanel = add_page(Notebook, ?PORT_STR, cdv_virtual_list_wx, cdv_port_cb), + + %% Table Panel + EtsPanel = add_page(Notebook, ?ETS_STR, cdv_virtual_list_wx, cdv_ets_cb), + + %% Timer Panel + TimerPanel = add_page(Notebook, ?TIMER_STR, cdv_virtual_list_wx,cdv_timer_cb), + + %% Fun Panel + FunPanel = add_page(Notebook, ?FUN_STR, cdv_virtual_list_wx, cdv_fun_cb), + + %% Atom Panel + AtomPanel = add_page(Notebook, ?ATOM_STR, cdv_virtual_list_wx, cdv_atom_cb), + + %% Distribution Panel + DistPanel = add_page(Notebook, ?DIST_STR, cdv_virtual_list_wx, cdv_dist_cb), + + %% Loaded Modules Panel + ModPanel = add_page(Notebook, ?MOD_STR, cdv_virtual_list_wx, cdv_mod_cb), + + %% Memory Panel + MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_wx, cdv_mem_cb), + + %% Memory Panel + IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_wx, cdv_int_tab_cb), + + %% Show the window + wxFrame:show(Frame), + + GenPid = wx_object:get_pid(GenPanel), + GenPid ! active, + observer_lib:destroy_progress_dialog(), + process_flag(trap_exit, true), + {Frame, State#state{menubar = MenuBar, + gen_panel = GenPanel, + pro_panel = ProPanel, + port_panel = PortPanel, + ets_panel = EtsPanel, + timer_panel = TimerPanel, + fun_panel = FunPanel, + atom_panel = AtomPanel, + dist_panel = DistPanel, + mod_panel = ModPanel, + mem_panel = MemPanel, + int_panel = IntPanel, + active_tab = GenPid + }}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%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 -> + 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) -> + NewState = + case load_dump(State#state.frame,undefined) of + {ok,File} -> + Panels = [State#state.gen_panel, + State#state.pro_panel, + State#state.port_panel, + State#state.ets_panel, + State#state.timer_panel, + State#state.fun_panel, + State#state.atom_panel, + State#state.dist_panel, + State#state.mod_panel, + State#state.mem_panel, + State#state.int_panel], + _ = [wx_object:call(Panel,new_dump) || Panel<-Panels], + wxNotebook:setSelection(State#state.notebook,0), + observer_lib:destroy_progress_dialog(), + State#state{file=File}; + error -> + State + end, + {noreply,NewState}; + +handle_event(#wx{id = ?wxID_EXIT, + event = #wxCommand{type = command_menu_selected}}, + 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), + wx:destroy(), + 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. + [{"File", [Open, About,Quit]}, {"&Help", [Help,UG,Howto]}] + end. + + +load_dump(Frame,undefined) -> + FD = wxFileDialog:new(wx:null(), + [{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]), + case wxFileDialog:showModal(FD) of + ?wxID_OK -> + Path = wxFileDialog:getPath(FD), + wxDialog:destroy(FD), + load_dump(Frame,Path); + _ -> + wxDialog:destroy(FD), + error + end; +load_dump(Frame,FileName) -> + ok = observer_lib:display_progress_dialog("Crashdump Viewer", + "Loading crashdump"), + crashdump_viewer:read_file(FileName), + case observer_lib:wait_for_progress() of + ok -> + %% Set window title + T1 = "Crashdump Viewer: ", + Title = + if length(FileName) > 70 -> + T1 ++ filename:basename(FileName); + true -> + T1 ++ FileName + end, + wxFrame:setTitle(Frame, Title), + {ok,FileName}; + error -> + error + end. + +%%%----------------------------------------------------------------- +%%% 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/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index 53e0241711..a17efbccb0 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -153,7 +153,7 @@ stop_debug() -> start() -> start(undefined). start(File) -> - crashdump_viewer_wx:start(File). + cdv_wx:start(File). stop() -> case whereis(?SERVER) of @@ -216,7 +216,7 @@ usage() -> %% External functions %%==================================================================== %%%-------------------------------------------------------------------- -%%% Start the server - called by crashdump_viewer_wx +%%% Start the server - called by cdv_wx start_link() -> case whereis(?SERVER) of undefined -> @@ -226,7 +226,7 @@ start_link() -> end. %%%----------------------------------------------------------------- -%%% Called by crashdump_viewer_wx +%%% Called by cdv_wx read_file(File) -> cast({read_file,File}). @@ -1378,7 +1378,7 @@ get_ports(File) -> lookup_and_parse_index(File,?port,ParseFun,"ports"). %% Converting port string to tuple to secure correct sorting. This is -%% converted back in cdv_port_wx:format/1. +%% converted back in cdv_port_cb:format/1. port_to_tuple("#Port<"++Port) -> [I1,I2] = string:tokens(Port,".>"), {list_to_integer(I1),list_to_integer(I2)}. @@ -1790,7 +1790,7 @@ get_atoms1(Fd,[Bin|Bins],N,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 +%% syntax in cdv_atom_cb:format/1 get_atom(<<"\'",Atom/binary>>) -> {Atom,q}; % quoted get_atom(Atom) when is_binary(Atom) -> diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl deleted file mode 100644 index 4b7caaf41e..0000000000 --- a/lib/observer/src/crashdump_viewer_html.erl +++ /dev/null @@ -1,388 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2003-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_html). - -%% -%% This module implements the HTML generation for the crashdump -%% viewer. No logic or states are kept by this module. -%% - --export([plain_page/1, - expandable_term/3, - warning/1]). - --include("crashdump_viewer.hrl"). --include("observer_defs.hrl"). - -%%%----------------------------------------------------------------- -%%% 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)))]. - -%%%----------------------------------------------------------------- -%%% Expanded memory -expandable_term(Heading,Expanded,Tab) -> - header(Heading,body(expandable_term_body(Heading,Expanded,Tab))). - -expandable_term_body(Heading,[],_Tab) -> - [case Heading of - "MsgQueue" -> "No messages were found"; - "Message Queue" -> "No messages were found"; - "StackDump" -> "No stack dump was found"; - "Dictionary" -> "No dictionary was found"; - "ProcState" -> "Information could not be retrieved," - " system messages may not be handled by this process." - end]; -expandable_term_body(Heading,Expanded,Tab) -> - Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", - [case Heading of - "MsgQueue" -> - table(Attr, - [tr( - [th("WIDTH=70%","Message"), - th("WIDTH=30%","SeqTraceToken")]) | - element(1, lists:mapfoldl(fun(Msg, Even) -> - {msgq_table(Tab, Msg, Even), - not Even} - end, - true, Expanded))]); - "Message Queue" -> - table(Attr, - [tr( - [th("WIDTH=10%","Id"), - th("WIDTH=90%","Message")]) | - element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> - {msgq_table(Tab, Msg, N, Even), - {not Even, N+1}} - end, - {true,1}, Expanded))]); - "StackDump" -> - table(Attr, - [tr( - [th("WIDTH=20%","Label"), - th("WIDTH=80%","Term")]) | - element(1, lists:mapfoldl(fun(Entry, Even) -> - {stackdump_table(Tab, Entry, Even), - not Even} - end, true, Expanded))]); - "ProcState" -> - table(Attr, - [tr( - [th("WIDTH=20%","Label"), - th("WIDTH=80%","Information")]) | - element(1, lists:mapfoldl(fun(Entry, Even) -> - {proc_state(Tab, Entry,Even), - not Even} - end, true, Expanded))]); - _ -> - table(Attr, - [tr( - [th("WIDTH=30%","Key"), - th("WIDTH=70%","Value")]) | - element(1, lists:mapfoldl(fun(Entry, Even) -> - {dict_table(Tab, Entry,Even), - not Even} - end, true, Expanded))]) - end]. - -msgq_table(Tab,{Msg0,Token0}, Even) -> - Token = case Token0 of - [] -> ""; - _ -> io_lib:fwrite("~w",[Token0]) - end, - Msg = all_or_expand(Tab,Msg0), - tr(color(Even),[td(pre(Msg)), td(Token)]). - -msgq_table(Tab,Msg0, Id, Even) -> - Msg = all_or_expand(Tab,Msg0), - tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]). - -stackdump_table(Tab,{Label0,Term0},Even) -> - Label = io_lib:format("~w",[Label0]), - Term = all_or_expand(Tab,Term0), - tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]). - -dict_table(Tab,{Key0,Value0}, Even) -> - Key = all_or_expand(Tab,Key0), - Value = all_or_expand(Tab,Value0), - tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]). - -proc_state(Tab,{Key0,Value0}, Even) -> - Key = lists:flatten(io_lib:format("~s",[Key0])), - Value = all_or_expand(Tab,Value0), - tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]). - -all_or_expand(Tab,Term) -> - Preview = io_lib:format("~P",[Term,8]), - Check = io_lib:format("~P",[Term,100]), - Exp = Preview=/=Check, - all_or_expand(Tab,Term,Preview,Exp). -all_or_expand(_Tab,_Term,Str,false) -> - href_proc_port(lists:flatten(Str)); -all_or_expand(Tab,Term,Preview,true) - when not is_binary(Term) -> - Key = {Key1,Key2,Key3} = now(), - ets:insert(Tab,{Key,Term}), - [href_proc_port(lists:flatten(Preview), false), $\n, - href("TARGET=\"expanded\"", - ["#Term?key1="++integer_to_list(Key1)++ - "&key2="++integer_to_list(Key2)++ - "&key3="++integer_to_list(Key3)], - "Click to expand above term")]; -all_or_expand(Tab,Bin,PreviewStr,true) when is_binary(Bin) -> - <> = Bin, - Size = byte_size(Bin), - Hash = erlang:phash2(Bin), - Key = {Preview, Size, Hash}, - ets:insert(Tab,{Key,Bin}), - [href_proc_port(lists:flatten(PreviewStr), false), $\n, - href("TARGET=\"expanded\"", - ["#OBSBinary?key1="++integer_to_list(Preview)++ - "&key2="++integer_to_list(Size)++ - "&key3="++integer_to_list(Hash)], - "Click to expand above term")]. - - - -color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); -color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). - -%%%----------------------------------------------------------------- -%%% Internal library -start_html() -> - "\n". -stop_html() -> - "". -start_html_body() -> - "\n". -stop_html_body() -> - "\n". - -header(Body) -> - header("","",Body). -header(Title,Body) -> - header(Title,"",Body). -header(Title,JavaScript,Body) -> - [%only_http_header(), - html_header(Title,JavaScript,Body)]. - -html_header(Title,JavaScript,Body) -> - [start_html(), - only_html_header(Title,JavaScript), - Body, - stop_html()]. - -only_html_header(Title,JavaScript) -> - ["\n", - "", Title, "\n", - JavaScript, - "\n"]. - -body(Text) -> - [start_html_body(), - Text, - stop_html_body()]. - -start_table(Args) -> - ["
", Text, "\n", Text, "\n", Text, "
\n"]. -stop_table() -> - "
\n". - -table(Args,Text) -> - [start_table(Args), Text, stop_table()]. -tr(Text) -> - ["\n", Text, "\n\n"]. -tr(Args,Text) -> - ["\n", Text, "\n\n"]. -th(Args,Text) -> - ["\n", Text, "\n\n"]. -td(Text) -> - ["", Text, ""]. -td(Args,Text) -> - ["", Text, ""]. - -start_pre() -> - "
".
-stop_pre() ->
-    "
". -pre(Text) -> - [start_pre(),Text,stop_pre()]. -href(Link,Text) -> - ["",Text,""]. -href(Args,Link,Text) -> - ["",Text,""]. -font(Args,Text) -> - ["\n",Text,"\n\n"]. -p(Text) -> - ["

",Text,"

\n"]. -br() -> - "
\n". - - -%% In all the following, "<" is changed to "<" and ">" is changed to ">" -href_proc_port(Text) -> - href_proc_port(Text,true). -href_proc_port(Text,LinkToBin) -> - href_proc_port(Text,[],LinkToBin). -href_proc_port("#Ref<"++T,Acc,LTB) -> - %% No links to refs - href_proc_port(T,["#Ref<"|Acc],LTB); -href_proc_port("#Fun<"++T,Acc,LTB) -> - %% No links to funs - href_proc_port(T,["#Fun<"|Acc],LTB); -href_proc_port("#Port<"++T,Acc,LTB) -> - {Port0,Rest} = split($>,T), - Port = "#Port<"++Port0 ++ ">", - href_proc_port(Rest,[href(Port,Port)|Acc],LTB); -href_proc_port("<<"++T,Acc,LTB) -> - %% No links to binaries - href_proc_port(T,["<<"|Acc],LTB); -href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 -> - %% Pid - {Pid0,Rest} = split($>,T), - Pid = "<" ++ Pid0 ++ ">", - href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB); -href_proc_port("['#CDVBin'"++T,Acc,LTB) -> - %% Binary written by crashdump_viewer:parse_heap_term(...) - href_proc_bin(cdv, T, Acc, LTB); -href_proc_port("['#OBSBin'"++T,Acc,LTB) -> - %% Binary written by crashdump_viewer:parse_heap_term(...) - href_proc_bin(obs, T, Acc, LTB); -href_proc_port("['#CDVPort'"++T,Acc,LTB) -> - %% Port written by crashdump_viewer:parse_term(...) - {Port0,Rest} = split($],T), - PortStr= - case string:tokens(Port0,",.|") of - [X,Y] -> - Port = "#Port<"++X++"."++Y++">", - href(Port,Port); - Ns -> - "#Port<" ++ string:join(Ns,".") ++"...>" - end, - href_proc_port(Rest,[PortStr|Acc],LTB); -href_proc_port("['#CDVPid'"++T,Acc,LTB) -> - %% Pid written by crashdump_viewer:parse_term(...) - {Pid0,Rest} = split($],T), - PidStr = - case string:tokens(Pid0,",.|") of - [X,Y,Z] -> - Pid = "<"++X++"."++Y++"."++Z++">", - href(Pid,Pid); - Ns -> - "<" ++ string:join(Ns,".") ++ "...>" - end, - href_proc_port(Rest,[PidStr|Acc],LTB); -href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)-> - %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...) - IH = lists:reverse( - lists:flatten( - "...(Incomplete Heap)")), - href_proc_port(T,IH++Acc,LTB); -href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)-> - %% A binary which is truncated! Written by - %% crashdump_viewer:parse_heap_term(...) - IH = lists:reverse( - lists:flatten( - "<<...(Truncated Binary)>>" - "")), - href_proc_port(T,IH++Acc,LTB); -href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)-> - %% A binary which could not be found in the dump! Written by - %% crashdump_viewer:parse_heap_term(...) - IH = lists:reverse( - lists:flatten( - "<<...(Nonexisting Binary)>>" - "")), - href_proc_port(T,IH++Acc,LTB); -href_proc_port("<"++T,Acc,LTB) -> - href_proc_port(T,["<"|Acc],LTB); -href_proc_port(">"++T,Acc,LTB) -> - href_proc_port(T,[">"|Acc],LTB); -href_proc_port([H|T],Acc,LTB) -> - href_proc_port(T,[H|Acc],LTB); -href_proc_port([],Acc,_) -> - lists:reverse(Acc). - -href_proc_bin(From, T, Acc, LTB) -> - {OffsetSizePos,Rest} = split($],T), - BinStr = - case string:tokens(OffsetSizePos,",.| \n") of - [Offset,Size,Pos] when From =:= cdv -> - Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, - {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), - PreviewStr = ["<<", - [integer_to_list(X)++"," || <> <= PreviewBin], - "...(", - observer_lib:to_str({bytes,Size}), - ")>>"], - if LTB -> - href("TARGET=\"expanded\"", - ["#Binary?offset="++Offset++ - "&size="++Size++ - "&pos="++Pos], - PreviewStr); - true -> - PreviewStr - end; - [Preview,Size,Md5] when From =:= obs -> - PreviewBin = <<(list_to_integer(Preview)):80>>, - PreviewStr = ["<<", - [integer_to_list(X)++"," || <> <= PreviewBin], - "...(", - observer_lib:to_str({bytes,list_to_integer(Size)}), - ")>>"], - if LTB -> - href("TARGET=\"expanded\"", - ["#OBSBinary?key1="++Preview++ - "&key2="++Size++ - "&key3="++Md5], - PreviewStr); - true -> - PreviewStr - end; - _ -> - "<< ... >>" - end, - href_proc_port(Rest,[BinStr|Acc],LTB). - -split(Char,Str) -> - split(Char,Str,[]). -split(Char,[Char|Str],Acc) -> % match Char - {lists:reverse(Acc),Str}; -split(Char,[H|T],Acc) -> - split(Char,T,[H|Acc]). - - -warn([]) -> - []; -warn(Warning) -> - font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). diff --git a/lib/observer/src/crashdump_viewer_wx.erl b/lib/observer/src/crashdump_viewer_wx.erl deleted file mode 100644 index 3464dfafae..0000000000 --- a/lib/observer/src/crashdump_viewer_wx.erl +++ /dev/null @@ -1,462 +0,0 @@ -%% -%% %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). --compile(export_all). --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(File0) -> - register(?SERVER, self()), - wx:new(), - - {ok,CdvServer} = crashdump_viewer:start_link(), - - catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), - Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer", - [{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), - - %% 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), - - %% 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), - - case load_dump(Frame,File0) of - {ok,File} -> - %% Set window title - T1 = "Crashdump Viewer: ", - Title = - if length(File) > 70 -> - T1 ++ filename:basename(File); - true -> - T1 ++ File - end, - wxFrame:setTitle(Frame, Title), - - setup(#state{server=CdvServer, - file=File, - frame=Frame, - status_bar=StatusBar, - notebook=Notebook, - main_panel=Panel}); - error -> - wxFrame:destroy(Frame), - wx:destroy(), - crashdump_viewer:stop(), - ignore - end. - -setup(#state{frame=Frame, notebook=Notebook}=State) -> - - %% Setup Menubar & Menus - MenuBar = wxMenuBar:new(), - DefMenus = default_menus(), - observer_lib:create_menus(DefMenus, MenuBar, default), - wxFrame:setMenuBar(Frame, MenuBar), - - %% General information Panel - GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx), - - %% Process Panel - ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list, cdv_proc_wx), - - %% 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), - - %% Show the window - wxFrame:show(Frame), - - GenPid = wx_object:get_pid(GenPanel), - GenPid ! active, - observer_lib:destroy_progress_dialog(), - process_flag(trap_exit, true), - {Frame, State#state{menubar = MenuBar, - gen_panel = GenPanel, - pro_panel = ProPanel, - port_panel = PortPanel, - ets_panel = EtsPanel, - timer_panel = TimerPanel, - fun_panel = FunPanel, - atom_panel = AtomPanel, - dist_panel = DistPanel, - mod_panel = ModPanel, - mem_panel = MemPanel, - int_panel = IntPanel, - active_tab = GenPid - }}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%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 -> - 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) -> - NewState = - case load_dump(State#state.frame,undefined) of - {ok,File} -> - Panels = [State#state.gen_panel, - State#state.pro_panel, - State#state.port_panel, - State#state.ets_panel, - State#state.timer_panel, - State#state.fun_panel, - State#state.atom_panel, - State#state.dist_panel, - State#state.mod_panel, - State#state.mem_panel, - State#state.int_panel], - _ = [wx_object:call(Panel,new_dump) || Panel<-Panels], - wxNotebook:setSelection(State#state.notebook,0), - observer_lib:destroy_progress_dialog(), - State#state{file=File}; - error -> - State - end, - {noreply,NewState}; - -handle_event(#wx{id = ?wxID_EXIT, - event = #wxCommand{type = command_menu_selected}}, - 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), - wx:destroy(), - 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. - [{"File", [Open, About,Quit]}, {"&Help", [Help,UG,Howto]}] - end. - - -load_dump(Frame,undefined) -> - FD = wxFileDialog:new(wx:null(), - [{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]), - case wxFileDialog:showModal(FD) of - ?wxID_OK -> - Path = wxFileDialog:getPath(FD), - wxDialog:destroy(FD), - load_dump(Frame,Path); - _ -> - wxDialog:destroy(FD), - error - end; -load_dump(Frame,FileName) -> - ok = observer_lib:display_progress_dialog("Crashdump Viewer", - "Loading crashdump"), - crashdump_viewer:read_file(FileName), - case observer_lib:wait_for_progress() of - ok -> - %% Set window title - T1 = "Crashdump Viewer: ", - Title = - if length(FileName) > 70 -> - T1 ++ filename:basename(FileName); - true -> - T1 ++ FileName - end, - wxFrame:setTitle(Frame, Title), - {ok,FileName}; - error -> - error - end. - -%%%----------------------------------------------------------------- -%%% 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.app.src b/lib/observer/src/observer.app.src index e881cb3c97..ef979681ac 100644 --- a/lib/observer/src/observer.app.src +++ b/lib/observer/src/observer.app.src @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2012. All Rights Reserved. +%% Copyright Ericsson AB 2002-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 @@ -20,13 +20,33 @@ [{description, "OBSERVER version 1"}, {vsn, "%VSN%"}, {modules, [crashdump_viewer, - crashdump_viewer_html, + cdv_atom_cb, + cdv_bin_cb, + cdv_detail_wx, + cdv_dist_cb, + cdv_ets_cb, + cdv_fun_cb, + cdv_gen_cb, + cdv_html_wx, + cdv_info_wx, + cdv_int_tab_cb, + cdv_mem_cb, + cdv_mod_cb, + cdv_multi_wx, + cdv_port_cb, + cdv_proc_cb, + cdv_table_wx, + cdv_term_cb, + cdv_timer_cb, + cdv_virtual_list_wx, + cdv_wx, etop, etop_gui, etop_tr, etop_txt, observer, observer_app_wx, + observer_html_lib, observer_lib, observer_perf_wx, observer_pro_wx, diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl new file mode 100644 index 0000000000..9f77891426 --- /dev/null +++ b/lib/observer/src/observer_html_lib.erl @@ -0,0 +1,388 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(observer_html_lib). + +%% +%% This module implements the HTML generation for the crashdump +%% viewer. No logic or states are kept by this module. +%% + +-export([plain_page/1, + expandable_term/3, + warning/1]). + +-include("crashdump_viewer.hrl"). +-include("observer_defs.hrl"). + +%%%----------------------------------------------------------------- +%%% 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)))]. + +%%%----------------------------------------------------------------- +%%% Expanded memory +expandable_term(Heading,Expanded,Tab) -> + header(Heading,body(expandable_term_body(Heading,Expanded,Tab))). + +expandable_term_body(Heading,[],_Tab) -> + [case Heading of + "MsgQueue" -> "No messages were found"; + "Message Queue" -> "No messages were found"; + "StackDump" -> "No stack dump was found"; + "Dictionary" -> "No dictionary was found"; + "ProcState" -> "Information could not be retrieved," + " system messages may not be handled by this process." + end]; +expandable_term_body(Heading,Expanded,Tab) -> + Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", + [case Heading of + "MsgQueue" -> + table(Attr, + [tr( + [th("WIDTH=70%","Message"), + th("WIDTH=30%","SeqTraceToken")]) | + element(1, lists:mapfoldl(fun(Msg, Even) -> + {msgq_table(Tab, Msg, Even), + not Even} + end, + true, Expanded))]); + "Message Queue" -> + table(Attr, + [tr( + [th("WIDTH=10%","Id"), + th("WIDTH=90%","Message")]) | + element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> + {msgq_table(Tab, Msg, N, Even), + {not Even, N+1}} + end, + {true,1}, Expanded))]); + "StackDump" -> + table(Attr, + [tr( + [th("WIDTH=20%","Label"), + th("WIDTH=80%","Term")]) | + element(1, lists:mapfoldl(fun(Entry, Even) -> + {stackdump_table(Tab, Entry, Even), + not Even} + end, true, Expanded))]); + "ProcState" -> + table(Attr, + [tr( + [th("WIDTH=20%","Label"), + th("WIDTH=80%","Information")]) | + element(1, lists:mapfoldl(fun(Entry, Even) -> + {proc_state(Tab, Entry,Even), + not Even} + end, true, Expanded))]); + _ -> + table(Attr, + [tr( + [th("WIDTH=30%","Key"), + th("WIDTH=70%","Value")]) | + element(1, lists:mapfoldl(fun(Entry, Even) -> + {dict_table(Tab, Entry,Even), + not Even} + end, true, Expanded))]) + end]. + +msgq_table(Tab,{Msg0,Token0}, Even) -> + Token = case Token0 of + [] -> ""; + _ -> io_lib:fwrite("~w",[Token0]) + end, + Msg = all_or_expand(Tab,Msg0), + tr(color(Even),[td(pre(Msg)), td(Token)]). + +msgq_table(Tab,Msg0, Id, Even) -> + Msg = all_or_expand(Tab,Msg0), + tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]). + +stackdump_table(Tab,{Label0,Term0},Even) -> + Label = io_lib:format("~w",[Label0]), + Term = all_or_expand(Tab,Term0), + tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]). + +dict_table(Tab,{Key0,Value0}, Even) -> + Key = all_or_expand(Tab,Key0), + Value = all_or_expand(Tab,Value0), + tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]). + +proc_state(Tab,{Key0,Value0}, Even) -> + Key = lists:flatten(io_lib:format("~s",[Key0])), + Value = all_or_expand(Tab,Value0), + tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]). + +all_or_expand(Tab,Term) -> + Preview = io_lib:format("~P",[Term,8]), + Check = io_lib:format("~P",[Term,100]), + Exp = Preview=/=Check, + all_or_expand(Tab,Term,Preview,Exp). +all_or_expand(_Tab,_Term,Str,false) -> + href_proc_port(lists:flatten(Str)); +all_or_expand(Tab,Term,Preview,true) + when not is_binary(Term) -> + Key = {Key1,Key2,Key3} = now(), + ets:insert(Tab,{Key,Term}), + [href_proc_port(lists:flatten(Preview), false), $\n, + href("TARGET=\"expanded\"", + ["#Term?key1="++integer_to_list(Key1)++ + "&key2="++integer_to_list(Key2)++ + "&key3="++integer_to_list(Key3)], + "Click to expand above term")]; +all_or_expand(Tab,Bin,PreviewStr,true) when is_binary(Bin) -> + <> = Bin, + Size = byte_size(Bin), + Hash = erlang:phash2(Bin), + Key = {Preview, Size, Hash}, + ets:insert(Tab,{Key,Bin}), + [href_proc_port(lists:flatten(PreviewStr), false), $\n, + href("TARGET=\"expanded\"", + ["#OBSBinary?key1="++integer_to_list(Preview)++ + "&key2="++integer_to_list(Size)++ + "&key3="++integer_to_list(Hash)], + "Click to expand above term")]. + + + +color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); +color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). + +%%%----------------------------------------------------------------- +%%% Internal library +start_html() -> + "\n". +stop_html() -> + "". +start_html_body() -> + "\n". +stop_html_body() -> + "\n". + +header(Body) -> + header("","",Body). +header(Title,Body) -> + header(Title,"",Body). +header(Title,JavaScript,Body) -> + [%only_http_header(), + html_header(Title,JavaScript,Body)]. + +html_header(Title,JavaScript,Body) -> + [start_html(), + only_html_header(Title,JavaScript), + Body, + stop_html()]. + +only_html_header(Title,JavaScript) -> + ["\n", + "", Title, "\n", + JavaScript, + "\n"]. + +body(Text) -> + [start_html_body(), + Text, + stop_html_body()]. + +start_table(Args) -> + ["\n"]. +stop_table() -> + "
\n". + +table(Args,Text) -> + [start_table(Args), Text, stop_table()]. +tr(Text) -> + ["\n", Text, "\n\n"]. +tr(Args,Text) -> + ["\n", Text, "\n\n"]. +th(Args,Text) -> + ["\n", Text, "\n\n"]. +td(Text) -> + ["", Text, ""]. +td(Args,Text) -> + ["", Text, ""]. + +start_pre() -> + "
".
+stop_pre() ->
+    "
". +pre(Text) -> + [start_pre(),Text,stop_pre()]. +href(Link,Text) -> + ["",Text,""]. +href(Args,Link,Text) -> + ["",Text,""]. +font(Args,Text) -> + ["\n",Text,"\n\n"]. +p(Text) -> + ["

",Text,"

\n"]. +br() -> + "
\n". + + +%% In all the following, "<" is changed to "<" and ">" is changed to ">" +href_proc_port(Text) -> + href_proc_port(Text,true). +href_proc_port(Text,LinkToBin) -> + href_proc_port(Text,[],LinkToBin). +href_proc_port("#Ref<"++T,Acc,LTB) -> + %% No links to refs + href_proc_port(T,["#Ref<"|Acc],LTB); +href_proc_port("#Fun<"++T,Acc,LTB) -> + %% No links to funs + href_proc_port(T,["#Fun<"|Acc],LTB); +href_proc_port("#Port<"++T,Acc,LTB) -> + {Port0,Rest} = split($>,T), + Port = "#Port<"++Port0 ++ ">", + href_proc_port(Rest,[href(Port,Port)|Acc],LTB); +href_proc_port("<<"++T,Acc,LTB) -> + %% No links to binaries + href_proc_port(T,["<<"|Acc],LTB); +href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 -> + %% Pid + {Pid0,Rest} = split($>,T), + Pid = "<" ++ Pid0 ++ ">", + href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB); +href_proc_port("['#CDVBin'"++T,Acc,LTB) -> + %% Binary written by crashdump_viewer:parse_heap_term(...) + href_proc_bin(cdv, T, Acc, LTB); +href_proc_port("['#OBSBin'"++T,Acc,LTB) -> + %% Binary written by crashdump_viewer:parse_heap_term(...) + href_proc_bin(obs, T, Acc, LTB); +href_proc_port("['#CDVPort'"++T,Acc,LTB) -> + %% Port written by crashdump_viewer:parse_term(...) + {Port0,Rest} = split($],T), + PortStr= + case string:tokens(Port0,",.|") of + [X,Y] -> + Port = "#Port<"++X++"."++Y++">", + href(Port,Port); + Ns -> + "#Port<" ++ string:join(Ns,".") ++"...>" + end, + href_proc_port(Rest,[PortStr|Acc],LTB); +href_proc_port("['#CDVPid'"++T,Acc,LTB) -> + %% Pid written by crashdump_viewer:parse_term(...) + {Pid0,Rest} = split($],T), + PidStr = + case string:tokens(Pid0,",.|") of + [X,Y,Z] -> + Pid = "<"++X++"."++Y++"."++Z++">", + href(Pid,Pid); + Ns -> + "<" ++ string:join(Ns,".") ++ "...>" + end, + href_proc_port(Rest,[PidStr|Acc],LTB); +href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)-> + %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...) + IH = lists:reverse( + lists:flatten( + "...(Incomplete Heap)")), + href_proc_port(T,IH++Acc,LTB); +href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)-> + %% A binary which is truncated! Written by + %% crashdump_viewer:parse_heap_term(...) + IH = lists:reverse( + lists:flatten( + "<<...(Truncated Binary)>>" + "")), + href_proc_port(T,IH++Acc,LTB); +href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)-> + %% A binary which could not be found in the dump! Written by + %% crashdump_viewer:parse_heap_term(...) + IH = lists:reverse( + lists:flatten( + "<<...(Nonexisting Binary)>>" + "")), + href_proc_port(T,IH++Acc,LTB); +href_proc_port("<"++T,Acc,LTB) -> + href_proc_port(T,["<"|Acc],LTB); +href_proc_port(">"++T,Acc,LTB) -> + href_proc_port(T,[">"|Acc],LTB); +href_proc_port([H|T],Acc,LTB) -> + href_proc_port(T,[H|Acc],LTB); +href_proc_port([],Acc,_) -> + lists:reverse(Acc). + +href_proc_bin(From, T, Acc, LTB) -> + {OffsetSizePos,Rest} = split($],T), + BinStr = + case string:tokens(OffsetSizePos,",.| \n") of + [Offset,Size,Pos] when From =:= cdv -> + Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, + {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), + PreviewStr = ["<<", + [integer_to_list(X)++"," || <> <= PreviewBin], + "...(", + observer_lib:to_str({bytes,Size}), + ")>>"], + if LTB -> + href("TARGET=\"expanded\"", + ["#Binary?offset="++Offset++ + "&size="++Size++ + "&pos="++Pos], + PreviewStr); + true -> + PreviewStr + end; + [Preview,Size,Md5] when From =:= obs -> + PreviewBin = <<(list_to_integer(Preview)):80>>, + PreviewStr = ["<<", + [integer_to_list(X)++"," || <> <= PreviewBin], + "...(", + observer_lib:to_str({bytes,list_to_integer(Size)}), + ")>>"], + if LTB -> + href("TARGET=\"expanded\"", + ["#OBSBinary?key1="++Preview++ + "&key2="++Size++ + "&key3="++Md5], + PreviewStr); + true -> + PreviewStr + end; + _ -> + "<< ... >>" + end, + href_proc_port(Rest,[BinStr|Acc],LTB). + +split(Char,Str) -> + split(Char,Str,[]). +split(Char,[Char|Str],Acc) -> % match Char + {lists:reverse(Acc),Str}; +split(Char,[H|T],Acc) -> + split(Char,T,[H|Acc]). + + +warn([]) -> + []; +warn(Warning) -> + font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index af2b8dda2e..988e04993c 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -126,8 +126,8 @@ handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}}, #state{frame=Frame,expand_table=T,expand_wins=Opened0}=State) -> {Type, Rest} = case Href of - "#Term?"++Keys -> {cdv_term_wx, Keys}; - "#OBSBinary?"++Keys -> {cdv_bin_wx, Keys}; + "#Term?"++Keys -> {cdv_term_cb, Keys}; + "#OBSBinary?"++Keys -> {cdv_bin_cb, Keys}; _ -> {other, Href} end, case Type of @@ -142,7 +142,7 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}}, Opened = case lists:keyfind(Id,1,Opened0) of false -> - Win = cdv_detail_win:start_link(Id,Frame,Callback), + Win = cdv_detail_wx:start_link(Id,Frame,Callback), [{Id,Win}|Opened0]; {_,Win} -> wxFrame:raise(Win), @@ -201,7 +201,7 @@ init_message_page(Parent, Pid, Table) -> [Pid, messages]) of {messages, Messages} -> - Html = crashdump_viewer_html:expandable_term("Message Queue", Messages, Table), + Html = observer_html_lib:expandable_term("Message Queue", Messages, Table), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -216,7 +216,7 @@ init_dict_page(Parent, Pid, Table) -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) of {dictionary,Dict} -> - Html = crashdump_viewer_html:expandable_term("Dictionary", Dict, Table), + Html = observer_html_lib:expandable_term("Dictionary", Dict, Table), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -273,7 +273,7 @@ init_state_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), Update = fun() -> StateInfo = fetch_state_info(Pid), - Html = crashdump_viewer_html:expandable_term("ProcState", StateInfo, Table), + Html = observer_html_lib:expandable_term("ProcState", StateInfo, Table), wxHtmlWindow:setPage(Win, Html) end, Update(), -- cgit v1.2.3 From 61115ab061a21912743c62de19ad0eda57224545 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Wed, 8 Jan 2014 14:47:11 +0100 Subject: observer: Fix progress dialog creation Should not call showModal on a wxProgressdialog it is already display (see doc) and it hangs on MacOSX until someone presses the menu. --- lib/observer/src/observer_lib.erl | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) (limited to 'lib/observer/src') diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 6288dd3a45..863ef8c2dc 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. 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 @@ -730,31 +730,19 @@ progress_loop(Title,PD,Caller) -> unlink(Caller) end. -progress_dialog(Env,Title,Str) -> - %% Spawning separat process to hold this since we use showModal. - spawn_link( - fun() -> - wx:set_env(Env), - PD = wxProgressDialog:new(Title,Str, - [{maximum,101}, - {style, - ?wxPD_APP_MODAL bor - ?wxPD_SMOOTH bor - ?wxPD_AUTO_HIDE}]), - wxProgressDialog:setMinSize(PD,{200,-1}), - ?progress_handler ! {progress_dialog,PD}, - wxProgressDialog:showModal(PD), - wxDialog:destroy(PD) - end), - receive - {progress_dialog,PD} -> - timer:sleep(300), % To allow the window to show before reporting - PD - end. +progress_dialog(_Env,Title,Str) -> + PD = wxProgressDialog:new(Title,Str, + [{maximum,101}, + {style, + ?wxPD_APP_MODAL bor + ?wxPD_SMOOTH bor + ?wxPD_AUTO_HIDE}]), + wxProgressDialog:setMinSize(PD,{200,-1}), + PD. update_progress(PD,Value) -> wxProgressDialog:update(PD,Value). update_progress_text(PD,Text) -> wxProgressDialog:update(PD,0,[{newmsg,Text}]). finish_progress(PD) -> - wxProgressDialog:endModal(PD, ?wxID_OK). + wxProgressDialog:destroy(PD). -- cgit v1.2.3 From a0d7557b1f84e71f79af5f2d32caf2ce994adb4e Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 21 Jan 2014 15:03:27 +0100 Subject: observer: cosmetic gui tweaks Mainly mac stuff --- lib/observer/src/observer_app_wx.erl | 2 +- lib/observer/src/observer_defs.hrl | 2 ++ lib/observer/src/observer_lib.erl | 11 ++++------- lib/observer/src/observer_sys_wx.erl | 15 +++++++++++---- lib/observer/src/observer_trace_wx.erl | 7 ++++--- 5 files changed, 22 insertions(+), 15 deletions(-) (limited to 'lib/observer/src') diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index a710bd0a69..a8ace10275 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -81,7 +81,7 @@ init([Notebook, Parent]) -> ]), Main = wxBoxSizer:new(?wxHORIZONTAL), Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, - {style, ?wxSP_LIVE_UPDATE}, + {style, ?SASH_STYLE}, {id, 2} ]), Apps = wxListBox:new(Splitter, 3, []), diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index a720e8c833..3adc358b95 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -46,3 +46,5 @@ -define(BG_NEW, {123,168,123}). -define(LCTRL_WDECR, 4). %% Remove some pixels in column width to avoid creating unnecessary scrollbar + +-define(SASH_STYLE, ?wxSP_LIVE_UPDATE bor ?wxSP_NOBORDER bor ?wxSP_3DSASH). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 863ef8c2dc..e0f7bf482b 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -460,7 +460,7 @@ create_box(Panel, Data) -> wxSizer:add(Line, 10, 0), % space of size 10 horisontally wxSizer:add(Line, Field, RightProportion), - {_,H,_,_} = wxTextCtrl:getTextExtent(Field,"W"), + {_,H,_,_} = wxTextCtrl:getTextExtent(Field,"Wj"), wxTextCtrl:setMinSize(Field,{0,H}), wxSizer:add(Box, Line, [{proportion,0},{flag,?wxEXPAND}]), @@ -523,8 +523,8 @@ get_max_size(Txt,[{Desc,_}|Info],MaxX,MaxY) -> end; get_max_size(Txt,[undefined|Info],MaxX,MaxY) -> get_max_size(Txt,Info,MaxX,MaxY); -get_max_size(_,[],X,Y) -> - {X+2,Y}. +get_max_size(_,[],X,_Y) -> + {X+2,-1}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% set_listctrl_col_size(LCtrl, Total) -> @@ -543,10 +543,7 @@ calc_last(LCtrl, _Total) -> scroll_size(LCtrl) -> case os:type() of {win32, nt} -> 0; - {unix, darwin} -> - %% I can't figure out is there is a visible scrollbar - %% Always make room for it - wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); + {unix, darwin} -> 0; %% Always 0 in wxWidgets-3.0 _ -> case wxWindow:hasScrollbar(LCtrl, ?wxVERTICAL) of true -> wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 8321d6eefe..f989f9cf97 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -60,10 +60,10 @@ init([Notebook, Parent]) -> wxSizer:add(TopSizer, FPanel0, [{flag, ?wxEXPAND}, {proportion, 1}]), wxSizer:add(TopSizer, FPanel1, [{flag, ?wxEXPAND}, {proportion, 1}]), BorderFlags = ?wxLEFT bor ?wxRIGHT, - MemoryInfo = create_mem_info(Panel, AllocInfo), + {MemPanel, MemoryInfo} = create_mem_info(Panel, AllocInfo), wxSizer:add(Sizer, TopSizer, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP}, {proportion, 0}, {border, 5}]), - wxSizer:add(Sizer, MemoryInfo, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, + wxSizer:add(Sizer, MemPanel, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, {proportion, 1}, {border, 5}]), wxPanel:setSizer(Panel, Sizer), Timer = observer_lib:start_timer(10), @@ -86,7 +86,9 @@ update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer, alloc=Allo update_alloc(AllocCtrl, AllocInfo), wxSizer:layout(Sizer). -create_mem_info(Panel, Fields) -> +create_mem_info(Parent, Fields) -> + Panel = wxPanel:new(Parent), + wxWindow:setBackgroundColour(Panel, {255,255,255}), Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES, Grid = wxListCtrl:new(Panel, [{style, Style}]), Li = wxListItem:new(), @@ -103,7 +105,12 @@ create_mem_info(Panel, Fields) -> lists:foldl(AddListEntry, 0, ListItems), wxListItem:destroy(Li), update_alloc(Grid, Fields), - Grid. + + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}, + {border, 5}, {proportion, 1}]), + wxWindow:setSizerAndFit(Panel, Sizer), + {Panel, Grid}. update_alloc(Grid, AllocInfo) -> Fields = alloc_info(AllocInfo, [], 0, 0, true), diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index f2a1084f85..2878842c23 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -84,7 +84,8 @@ create_window(Notebook, ParentPid) -> %% Create the window Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), - Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}]), + Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, + {style, ?SASH_STYLE}]), {NodeProcView, NodeView, ProcessView} = create_process_view(Splitter), {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter), wxSplitterWindow:setSashGravity(Splitter, 0.5), @@ -120,7 +121,7 @@ create_process_view(Parent) -> Panel = wxPanel:new(Parent), MainSz = wxBoxSizer:new(?wxHORIZONTAL), Style = ?wxLC_REPORT bor ?wxLC_HRULES, - Splitter = wxSplitterWindow:new(Panel, []), + Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]), Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]), Procs = wxListCtrl:new(Splitter, [{winid, ?PROC_WIN}, {style, Style}]), Li = wxListItem:new(), @@ -157,7 +158,7 @@ create_matchspec_view(Parent) -> Panel = wxPanel:new(Parent), MainSz = wxBoxSizer:new(?wxHORIZONTAL), Style = ?wxLC_REPORT bor ?wxLC_HRULES, - Splitter = wxSplitterWindow:new(Panel, []), + Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]), Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, {style, Style}]), Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]), Li = wxListItem:new(), -- cgit v1.2.3