diff options
Diffstat (limited to 'lib/observer/src')
41 files changed, 4793 insertions, 3495 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 42f5c19935..c120865213 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -36,15 +36,33 @@ RELSYSDIR = $(RELEASE_PATH)/lib/observer-$(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_wx \ observer_perf_wx \ observer_pro_wx \ observer_procinfo \ @@ -53,6 +71,7 @@ MODULES= \ observer_traceoptions_wx \ observer_tv_table \ observer_tv_wx \ + observer_wx \ ttb \ ttb_et @@ -72,13 +91,12 @@ PRIVDIR= ../priv WEBTOOLFILES= $(PRIVDIR)/crashdump_viewer.tool $(PRIVDIR)/erlang_observer.png BINDIR= $(PRIVDIR)/bin ifeq ($(findstring win32,$(TARGET)),win32) -WIN32_EXECUTABLES= $(BINDIR)/etop.bat $(BINDIR)/getop.bat $(BINDIR)/cdv.bat +WIN32_EXECUTABLES= $(BINDIR)/etop.bat $(BINDIR)/cdv.bat else WIN32_EXECUTABLES= endif EXECUTABLES= \ $(BINDIR)/etop \ - $(BINDIR)/getop \ $(BINDIR)/cdv \ $(WIN32_EXECUTABLES) CDVDIR= $(PRIVDIR)/crashdump_viewer 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_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl new file mode 100644 index 0000000000..d5fbceff1e --- /dev/null +++ b/lib/observer/src/cdv_bin_cb.erl @@ -0,0 +1,82 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-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 +%% 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({Type, {T,Key}}) -> + [{Key,Term}] = ets:lookup(T,Key), + {ok,{"Expanded Binary", {Type, Term}, []}}; +get_details({cdv, Id}) -> + {ok,Bin} = crashdump_viewer:expand_binary(Id), + {ok,{"Expanded Binary", {cvd, Bin}, []}}. + +detail_pages() -> + [{"Binary", fun init_bin_page/2}]. + +init_bin_page(Parent,{Type,Bin}) -> + cdv_multi_wx:start_link( + Parent, + [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin)}}, + {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin)}}, + {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin)}}, + {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin)}}, + {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin)}}, + {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin)}}, + {"Term",cdv_html_wx,{Type,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(<<B1:4,B2:4>>,_) -> + [integer_to_list(B1,16),integer_to_list(B2,16)]; +format_hex(<<B1:4,B2:4,Bin/binary>>,0) -> + [integer_to_list(B1,16),integer_to_list(B2,16),$,,$\n,$\s,$\s + | format_hex(Bin,?line_break)]; +format_hex(<<B1:4,B2:4,Bin/binary>>,N) -> + [integer_to_list(B1,16),integer_to_list(B2,16),$, + | format_hex(Bin,N-1)]. + +plain_html(Text) -> + observer_html_lib:plain_page(Text). 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_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_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_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_html_wx.erl b/lib/observer/src/cdv_html_wx.erl new file mode 100644 index 0000000000..b79c647f63 --- /dev/null +++ b/lib/observer/src/cdv_html_wx.erl @@ -0,0 +1,136 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-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 +%% 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, + app, %% which tool is the user + expand_table, + expand_wins=[]}). + +start_link(ParentWin, Info) -> + wx_object:start_link(?MODULE, [ParentWin, Info], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([ParentWin, {App, Fun}]) when is_function(Fun) -> + init([ParentWin, {App, Fun()}]); +init([ParentWin, {expand,HtmlText,Tab}]) -> + init(ParentWin, HtmlText, Tab, cdv); +init([ParentWin, {App, {expand,HtmlText,Tab}}]) -> + init(ParentWin, HtmlText, Tab, App); +init([ParentWin, {App,HtmlText}]) -> + init(ParentWin, HtmlText, undefined, App); +init([ParentWin, HtmlText]) -> + init(ParentWin, HtmlText, undefined, cdv). + +init(ParentWin, HtmlText, Tab, App) -> + HtmlWin = observer_lib:html_window(ParentWin), + wxHtmlWindow:setPage(HtmlWin,HtmlText), + {HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}. + +%%%%%%%%%%%%%%%%%%%%%%% 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, app=App}=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); + _ when App =:= obs -> + observer ! {open_link, Target}; + _ -> + 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_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_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_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_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_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_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..4451045012 --- /dev/null +++ b/lib/observer/src/cdv_term_cb.erl @@ -0,0 +1,76 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-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 +%% 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({Type, {T,Key}}) -> + [{Key,Term}] = ets:lookup(T,Key), + {ok,{"Expanded Term", {Type,[Term, T]}, []}}. + +detail_pages() -> + [{"Term", fun init_term_page/2}]. + +init_term_page(ParentWin, {Type, [Term, Tab]}) -> + Expanded = expand(Term, true), + BinSaved = expand(Term, Tab), + cdv_multi_wx:start_link( + ParentWin, + [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}}, + {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}}, + {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}}, + {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}}, + {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]). + +format_term_fun(Format,Term,Tab) -> + fun() -> + 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) -> + Size = byte_size(Bin), + PrevSize = min(Size, 10) * 8, + <<Preview:PrevSize, _/binary>> = Bin, + Hash = erlang:phash2(Bin), + Key = {Preview, Size, Hash}, + ets:insert(Tab, {Key,Bin}), + ['#OBSBin',Preview,Size,Hash]; +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_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 e7d71c581e..a17efbccb0 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -20,80 +20,53 @@ %% %% 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, - expand_memory/2]). - +-export([start/0,start/1,stop/0,script_start/0,script_start/1]). + +%% GUI API +-export([start_link/0]). +-export([read_file/1, + 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]). + +%% 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 +79,11 @@ -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 +92,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). @@ -152,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). @@ -164,8 +118,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 +151,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) -> + cdv_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 cdv_wx start_link() -> case whereis(?SERVER) of undefined -> @@ -334,119 +226,63 @@ 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 cdv_wx +read_file(File) -> + cast({read_file,File}). %%%----------------------------------------------------------------- -%%% The following functions are called when menu items are clicked. -general_info(_Env,_Input) -> +%%% The following functions are called when the different tabs are +%%% created +general_info() -> 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) -> +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}). - -%%%----------------------------------------------------------------- -%%% Called when the "Expand" link in a call stack (Last Calls) is -%%% clicked. -expand(_Env,Input) -> - call({expand,Input}). - -%%%----------------------------------------------------------------- -%%% Called when the "Expand" link in a stack dump, message queue or -%%% dictionary is clicked. -expand_memory(_Env,Input) -> - call({expand_memory,Input}). +port(Id) -> + call({port,Id}). %%%----------------------------------------------------------------- -%%% 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). +%%% Called when "<< xxx bytes>>" link is clicket to open a new window +%%% displaying the whole binary. +expand_binary(Pos) -> + call({expand_binary,Pos}). %%==================================================================== %% Server functions @@ -461,7 +297,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 +310,125 @@ 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(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, - 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), - Reply = - case truncated_warning([{?proc,Pid}]) of - [] -> - Expanded = expand_memory(File,What,Pid,B), - crashdump_viewer_html:expanded_memory(What,Expanded); - _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) - end, - {reply,Reply,State}; -handle_call({expand_binary,Input},_From,State=#state{file=File}) -> - [{"pos",Pos0}] = httpd:parse_query(Input), - Pos = list_to_integer(Pos0), + WS = parse_vsn_str(GenInfo#general_info.system_vsn,4), + 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=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) -> - 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)), + {reply,{ok,Bin},State}; +handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) -> 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,dump_vsn=DumpVsn,binaries=B})-> Reply = - case get_proc_details(File,Pid,State#state.dump_vsn) of - {ok,Proc} -> - TW = truncated_warning([{?proc,Pid}]), - crashdump_viewer_html:proc_details(Pid,Proc,TW,SH); - {other_node,Node} -> - TW = truncated_warning([?visible_node, - ?hidden_node, - ?not_connected]), - crashdump_viewer_html:nods(Node,TW); - not_found -> - crashdump_viewer_html:info_page(["Could not find process: ", - Pid],?space) + case get_proc_details(File,Pid,WS,DumpVsn,B) of + {ok,Proc,TW} -> + {ok,Proc,TW}; + Other -> + {error,Other} 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); - {other_node,Node} -> - TW = truncated_warning([?visible_node, - ?hidden_node, - ?not_connected]), - crashdump_viewer_html:nods(Node,TW); - not_found -> - crashdump_viewer_html:info_page( - ["Could not find port: ",Id],?space) + {ok,PortInfo,TW}; + Other -> + {error,Other} 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}) -> - Nods=nods(File), TW = truncated_warning([?visible_node,?hidden_node,?not_connected]), - Reply = crashdump_viewer_html:nods(Nods,TW), + Nods=nods(File), + {reply,{ok,Nods,TW},State}; +handle_call({node_info,Channel},_From,State=#state{file=File}) -> + 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,SessionId},_From,State=#state{file=File}) -> +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}) -> - TW = truncated_warning([?atoms,?num_atoms]), - atoms(SessionId,File,TW,Num), - {reply,ok,State}; + Funs = funs(File), + {reply,{ok,Funs,TW},State}; +handle_call(atoms,_From,State=#state{file=File,num_atoms=NumAtoms0}) -> + TW = truncated_warning([?atoms]), + 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 +439,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}, _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 -> + end_progress(Error), + {noreply, #state{}} + end; +handle_cast(stop,State) -> + {stop,normal,State}. + %%-------------------------------------------------------------------- %% Function: handle_info/2 @@ -791,24 +535,6 @@ 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. - - open(File) -> {ok,Fd} = file:open(File,[read,read_ahead,raw,binary]), Fd. @@ -861,6 +587,18 @@ get_chunk(Fd) -> {ok,Bin} end. +%% Read and report progress +progress_read(Fd) -> + {R,Bytes} = + case read(Fd) of + {ok,Bin} -> + {{ok,Bin},byte_size(Bin)}; + Other -> + {Other,0} + end, + update_progress(Bytes), + R. + read(Fd) -> file:read(Fd,?chunk_size). @@ -962,73 +700,30 @@ get_rest_of_line_1(Fd, <<>>, Acc) -> eof -> {eof,lists:reverse(Acc)} end. -count_rest_of_line(Fd) -> +get_lines_to_empty(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) -> - case get_chunk(Fd) of - {ok,Bin} -> - {AllOrPart,Rest,Lines} = get_n_lines_of_tag(Fd,N,Bin,[]), - {AllOrPart,N-Rest,Lines}; + {ok,Bin} -> + get_lines_to_empty(Fd,Bin,[],[]); eof -> - empty - end. -get_n_lines_of_tag(Fd,N,<<"\n=",_/binary>>=Bin,Acc) -> - put_chunk(Fd,Bin), - {all,N-1,lists:reverse(Acc)}; -get_n_lines_of_tag(Fd,0,Bin,Acc) -> - put_chunk(Fd,Bin), - {part,0,lists:reverse(Acc)}; -get_n_lines_of_tag(Fd,N,<<$\n:8,Bin/binary>>,Acc) -> - get_n_lines_of_tag(Fd,N-1,Bin,[$\n|Acc]); -get_n_lines_of_tag(Fd,N,<<$\r:8,Bin/binary>>,Acc) -> - get_n_lines_of_tag(Fd,N,Bin,Acc); -get_n_lines_of_tag(Fd,N,<<Char:8,Bin/binary>>,Acc) -> - get_n_lines_of_tag(Fd,N,Bin,[Char|Acc]); -get_n_lines_of_tag(Fd,N,<<>>,Acc) -> - case get_chunk(Fd) of - {ok,Bin} -> - get_n_lines_of_tag(Fd,N,Bin,Acc); - eof -> - case Acc of - [$\n|_] -> - {all,N,lists:reverse(Acc)}; - _ -> - {all,N-1,lists:reverse(Acc)} - end - end. - -count_rest_of_tag(Fd) -> - case get_chunk(Fd) of - {ok,Bin} -> count_rest_of_tag(Fd,Bin,0); - eof -> 0 + [] end. -count_rest_of_tag(Fd,<<"\n=",Bin/binary>>,N) -> +get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,[],Lines) -> put_chunk(Fd,Bin), - N; -count_rest_of_tag(Fd,<<$\r:8,Bin/binary>>,N) -> - count_rest_of_tag(Fd,Bin,N); -count_rest_of_tag(Fd,<<_Char:8,Bin/binary>>,N) -> - count_rest_of_tag(Fd,Bin,N+1); -count_rest_of_tag(Fd,<<>>,N) -> + lists:reverse(Lines); +get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) -> + get_lines_to_empty(Fd,Bin,[],[lists:reverse(Acc)|Lines]); +get_lines_to_empty(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) -> + get_lines_to_empty(Fd,Bin,Acc,Lines); +get_lines_to_empty(Fd,<<$\s:8,Bin/binary>>,[],Lines) -> + get_lines_to_empty(Fd,Bin,[],Lines); +get_lines_to_empty(Fd,<<Char:8,Bin/binary>>,Acc,Lines) -> + get_lines_to_empty(Fd,Bin,[Char|Acc],Lines); +get_lines_to_empty(Fd,<<>>,Acc,Lines) -> case get_chunk(Fd) of - {ok,Bin} -> count_rest_of_tag(Fd,Bin,N); - eof -> N + {ok,Bin} -> + get_lines_to_empty(Fd,Bin,Acc,Lines); + eof -> + lists:reverse(Lines,[lists:reverse(Acc)]) end. split(Str) -> @@ -1046,150 +741,32 @@ 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) -> 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 + init_progress("Reading file",Size), + case progress_read(Fd) of {ok,<<$=:8,TagAndRest/binary>>} -> {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1), case Tag of @@ -1197,41 +774,40 @@ 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), + end_progress(), check_if_truncated(), - initial_menu(), - Binaries = read_binaries(Fd), - R = crashdump_viewer_html:start_page(), + [{DumpVsn0,_}] = lookup_index(?erl_crash_dump), + DumpVsn = [list_to_integer(L) || + L<-string:tokens(DumpVsn0,".")], + Binaries = read_binaries(Fd,DumpVsn), close(Fd), - background_done({R,File,Binaries}); + {ok,Binaries,DumpVsn}; _Other -> - R = crashdump_viewer_html:error( + R = io_lib:format( "~s is not an Erlang crash dump~n", [File]), close(Fd), - background_done({R,undefined,undefined}) + {error,R} end; {ok,<<"<Erlang crash dump>",_Rest/binary>>} -> %% old version - no longer supported - R = crashdump_viewer_html:error( + R = io_lib:format( "The crashdump ~s is in the pre-R10B format, " "which is no longer supported.~n", - [File]), + [File]), close(Fd), - background_done({R,undefined,undefined}); + {error,R}; _Other -> - R = crashdump_viewer_html:error( + R = io_lib:format( "~s is not an Erlang crash dump~n", [File]), close(Fd), - background_done({R,undefined,undefined}) + {error,R} end; _other -> - R = crashdump_viewer_html:error("~s is not an Erlang crash dump~n", - [File]), - background_done({R,undefined,undefined}) + R = io_lib:format("~s is not an Erlang crash dump~n",[File]), + {error,R} end. indexify(Fd,Bin,N) -> @@ -1244,7 +820,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 +848,7 @@ tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,tag) -> tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,id) -> tag(Fd,Rest,N+1,Gat,[Char|Di],id); tag(Fd,<<>>,N,Gat,Di,Now) -> - case read(Fd) of + case progress_read(Fd) of {ok,Chunk} when is_binary(Chunk) -> tag(Fd,Chunk,N,Gat,Di,Now); eof -> @@ -1304,21 +880,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) -> @@ -1330,22 +897,18 @@ general_info(File) -> WholeLine -> WholeLine end, - 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)}; - _ -> GI0 - end, + GI = get_general_info(Fd,#general_info{created=Created}), {MemTot,MemMax} = case lookup_index(?memory) of [{_,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, @@ -1408,269 +971,210 @@ 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 - [] -> - ?space; - [{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)}. %%----------------------------------------------------------------- %% 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,"processes"). %%----------------------------------------------------------------- %% Page with one process -get_proc_details(File,Pid,DumpVsn) -> +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), - debug_dict=if_exist(?debug_proc_dictionary,Pid)} + {{Stack,MsgQ,Dict},TW} = + case truncated_warning([{?proc,Pid}]) of + [] -> + {expand_memory(Fd,Pid,DumpVsn,Binaries),[]}; + TW0 -> + {{[],[],[]},TW0} end, - Proc = get_procinfo(Fd,fun all_procinfo/4,Proc0), + 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}; _ -> - 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 - 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 -> ?space - end; - _ -> - expand + maybe_other_node(Pid) 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}) + get_procinfo(Fd,Fun,Proc#proc{init_func=IF},WS) end; + "Message queue length" -> + %% stored as integer so we can sort on it + get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))},WS); + "Reductions" -> + %% stored as integer so we can sort on it + 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))*WS},WS); + "Memory" -> + %% stored as integer so we can sort on it + get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))},WS); + {eof,_} -> + Proc; % truncated file + Other -> + Fun(Fd,Fun,Proc,WS,Other) + end. + +main_procinfo(Fd,Fun,Proc,WS,LineHead) -> + case LineHead of + "=" ++ _next_tag -> + Proc; + "arity = " ++ _ -> + %%! Temporary workaround + get_procinfo(Fd,Fun,Proc,WS); + _Other -> + skip_rest_of_line(Fd), + get_procinfo(Fd,Fun,Proc,WS) + end. +all_procinfo(Fd,Fun,Proc,WS,LineHead) -> + case LineHead of + %% - START - moved from get_procinfo - "Spawned by" -> case val(Fd) of "[]" -> - get_procinfo(Fd,Fun,Proc); + get_procinfo(Fd,Fun,Proc,WS); Parent -> - get_procinfo(Fd,Fun,Proc#proc{parent=Parent}) + get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS) end; "Started" -> - get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)}); + 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)}}); + val(Fd)}},WS); "Current call" -> get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call", - val(Fd)}}); - "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))}); - "Reductions" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))}); + val(Fd)}},WS); "Number of heap fragments" -> - get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)}); + 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)}); - Stack when Stack=:="Stack+heap"; Stack=:="Stack" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{stack_heap= - list_to_integer(val(Fd))}); + get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)},WS); "OldHeap" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap=val(Fd)}); + Bytes = list_to_integer(val(Fd))*WS, + get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS); "Heap unused" -> - get_procinfo(Fd,Fun,Proc#proc{heap_unused=val(Fd)}); + Bytes = list_to_integer(val(Fd))*WS, + get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS); "OldHeap unused" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=val(Fd)}); + 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)}); + 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)}); + get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)},WS); "Stack top" -> - get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)},WS); "Stack end" -> - get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)}); + 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)}); + 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)}); + 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)}); - "Memory" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))}); - {eof,_} -> - Proc; % truncated file - Other -> - Fun(Fd,Fun,Proc,Other) - end. - -main_procinfo(Fd,Fun,Proc,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 - get_procinfo(Fd,Fun,Proc); - _Other -> - skip_rest_of_line(Fd), - get_procinfo(Fd,Fun,Proc) - end. -all_procinfo(Fd,Fun,Proc,LineHead) -> - case LineHead of - "Message queue" -> - get_procinfo(Fd,Fun,Proc#proc{msg_q=size_or_term(Fd)}); + 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,99 +1192,77 @@ 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. -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); - "DebugDictionary" -> read_dictionary(Fd,?debug_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) -> +read_binaries(Fd,DumpVsn) -> AllBinaries = lookup_index(?binary), - read_binaries(Fd,AllBinaries, gb_trees:empty()). - -read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0) -> - 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); -read_binaries(_Fd,[],Dict) -> - 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}. @@ -1788,27 +1270,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}. @@ -1816,26 +1298,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. @@ -1843,16 +1325,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 -> @@ -1863,68 +1345,18 @@ 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. -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 +1368,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,"ports"). + +%% Converting port string to tuple to secure correct sorting. This is +%% 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)}. 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 +1428,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,"ets"). 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 +1457,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 +1494,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,"timers"). get_timerinfo_1(Fd,Timer) -> case line_head(Fd) of "Message" -> get_timerinfo_1(Fd,Timer#timer{msg=val(Fd)}); "Time left" -> - get_timerinfo_1(Fd,Timer#timer{time=val(Fd)}); + TimeLeft = list_to_integer(val(Fd) -- " ms"), + get_timerinfo_1(Fd,Timer#timer{time=TimeLeft}); "=" ++ _next_tag -> Timer; Other -> @@ -2054,6 +1513,28 @@ get_timerinfo_1(Fd,Timer) -> end. %%----------------------------------------------------------------- +%% Page with information about a node in the distribution +get_node(File,Channel) -> + Ms = ets:fun2ms( + fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel -> + {visible,Start}; + ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel -> + {hidden,Start}; + ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel -> + {not_connected,Start} + end), + + case ets:select(cdv_dump_index_table,Ms) of + [] -> + {error,not_found}; + [{Type,Pos}] -> + Fd = open(File), + NodeInfo = get_nodeinfo(Fd,Channel,Type,Pos), + close(Fd), + {ok,NodeInfo} + end. + +%%----------------------------------------------------------------- %% Page with information about the erlang distribution nods(File) -> case lookup_index(?no_distribution) of @@ -2064,28 +1545,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 +1576,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 +1617,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 +1635,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,"modules")}. get_loaded_mod_totals(Fd,{CC,OC}) -> case line_head(Fd) of @@ -2164,9 +1653,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 +1720,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,"funs"). 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 +1747,54 @@ 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} -> + init_progress("Processing atoms",NumAtoms), + 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) -> + end_progress(), + Atoms; +get_atoms1(Fd,[LastBin],N,Atoms) -> + case get_chunk(Fd) of + {ok,Bin0} -> + get_atoms(Fd,<<LastBin/binary,Bin0/binary>>,N,Atoms); + eof -> + 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_cb: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 +1817,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 +1845,7 @@ get_allocareainfo(Fd,Acc) -> AllocInfo = case split(Val) of {Alloc,[]} -> - {Key,Alloc,?space}; + {Key,Alloc,""}; {Alloc,Used} -> {Key,Alloc,Used} end, @@ -2361,7 +1861,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 +1870,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 +1892,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 +1985,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,142 +2186,120 @@ 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 %%% -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, []), @@ -2848,7 +2339,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}; @@ -2860,10 +2351,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. @@ -2901,13 +2392,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, []). @@ -2925,19 +2419,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(Offset,Size,Line0) -> + {_N,":"++Line} = get_hex(Line0), + do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), []). -get_binary(0, Line, Acc) -> +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 @@ -2947,29 +2448,15 @@ 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) -> - ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},Id},[],[true]}]). %%----------------------------------------------------------------- @@ -2979,7 +2466,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; @@ -2995,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; @@ -3010,37 +2495,133 @@ 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,Str) when is_list(File) -> + Indices = lookup_index(What), + 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 +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. + +%%%----------------------------------------------------------------- +%%% 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), - 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} + 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.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 deleted file mode 100644 index 93c1a842b5..0000000000 --- a/lib/observer/src/crashdump_viewer_html.erl +++ /dev/null @@ -1,1440 +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([welcome/0, - read_file_frame/0, - redirect/1, - start_page/0, - filename_frame/1, - menu_frame/0, - general_info/1, - pretty_info_page/2, - info_page/2, - proc_details/4, - expanded_memory/2, - 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, - chunk_page/5, - chunk/3]). - --include("crashdump_viewer.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. <INPUT TYPE=file NAME=pathj SIZE=40">) 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 = ["<META HTTP-EQUIV=\"refresh\" CONTENT=\"3; URL=./redirect\">"], - 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=<Max-> - 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 -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 = -"<SCRIPT type=\"text/javascript\"> - function popup() { - window.open(\"\",\"expanded\",'resizable=yes,scrollbars=yes') -} -</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("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)]), - - 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 -expanded_memory(Heading,Expanded) -> - header(Heading,body(expanded_memory_body(Heading,Expanded))). - -expanded_memory_body(Heading,[]) -> - [heading(Heading,"processes"), - 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" - end]; -expanded_memory_body(Heading,Expanded) -> - [heading(Heading,"processes"), - case Heading of - "MsgQueue" -> - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Message"), - th("SeqTraceToken")]) | - lists:map(fun(Msg) -> msgq_table(Msg) end, Expanded)]); - "StackDump" -> - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Label"), - th("Term")]) | - lists:map(fun(Entry) -> stackdump_table(Entry) end, Expanded)]); - _ -> - table( - "BORDER=4 CELLPADDING=4", - [tr( - [th("Key"), - th("Value")]) | - lists:map(fun(Entry) -> dict_table(Entry) end, Expanded)]) - end]. - -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]))), - tr([td(pre(Msg)), td(Token)]). - -stackdump_table({Label0,Term0}) -> - 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}) -> - 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))]). - - -%%%----------------------------------------------------------------- -%%% 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) -> - [only_http_header(), - start_html(), - only_html_header(Title), - start_html_body()]. - -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) -> - ["<HEAD>\n", - "<TITLE>", Title, "</TITLE>\n", - JavaScript, - "</HEAD>\n"]. - -start_html() -> - "<HTML>\n". -stop_html() -> - "</HTML>". -start_html_body() -> - "<BODY BGCOLOR=\"#FFFFFF\">\n". -stop_html_body() -> - "</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()]. - -body(Text) -> - [start_html_body(), - Text, - stop_html_body()]. - -frameset(Args,Frames) -> - ["<FRAMESET ",Args,">\n", Frames, "\n</FRAMESET>\n"]. -frame(Args) -> - ["<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) -> - ["<TABLE ", Args, ">\n"]. -stop_table() -> - "</TABLE>\n". - -table(Args,Text) -> - [start_table(Args), Text, stop_table()]. -tr(Text) -> - ["<TR>\n", Text, "\n</TR>\n"]. -tr(Args,Text) -> - ["<TR ", Args, ">\n", Text, "\n</TR>\n"]. -th(Text) -> - ["<TH>", Text, "</TH>"]. -th(Args,Text) -> - ["<TH ", Args, ">\n", Text, "\n</TH>\n"]. -td(Text) -> - ["<TD>", Text, "</TD>"]. -td(Args,Text) -> - ["<TD ", Args, ">", Text, "</TD>"]. - -b(Text) -> - ["<B>",Text,"</B>"]. -em(Text) -> - ["<EM>",Text,"</EM>\n"]. -start_pre() -> - "<PRE>". -stop_pre() -> - "</PRE>". -pre(Text) -> - [start_pre(),Text,stop_pre()]. -href(Link,Text) -> - ["<A HREF=\"",Link,"\">",Text,"</A>"]. -href(Args,Link,Text) -> - ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"]. -img("") -> - ""; -img(Picture) -> - ["<IMG SRC=\"", Picture, "\" BORDER=0>"]. -form(Args,Text) -> - ["<FORM ",Args,">\n",Text,"\n</FORM>\n"]. -input(Args) -> - ["<INPUT ", Args, ">\n"]. -h1(Text) -> - ["<H1>",Text,"</H1>\n"]. -font(Args,Text) -> - ["<FONT ",Args,">\n",Text,"\n</FONT>\n"]. -p(Text) -> - ["<P>",Text,"</P>\n"]. -p(Args, Text) -> - ["<P ", Args, ">",Text,"</P>\n"]. -br() -> - "<BR>\n". - - -%% 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) -> - %% No links to refs - href_proc_port(T,[$;,$t,$l,$&,$f,$e,$R,$#|Acc]); -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]); -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([$",$#,$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], - ["<< ",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]); -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([$',$#,$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(...) - IH = lists:reverse( - lists:flatten( - "<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")), - href_proc_port(T,IH++Acc); -href_proc_port([$',$#,$C,$D,$V,$T,$r,$u,$n,$c,$a,$t,$e,$d,$B,$i,$n,$a,$r,$y,$' - |T], Acc)-> - %% A binary which is truncated! Written by - %% crashdump_viewer:parse_heap_term(...) - IH = lists:reverse( - lists:flatten( - "<FONT COLOR=\"#FF0000\"><<...(Truncated Binary)>>" - "</FONT>")), - href_proc_port(T,IH++Acc); -href_proc_port([$',$#,$C,$D,$V,$N,$o,$n,$e,$x,$i,$s,$t,$i,$n,$g,$B,$i,$n,$a,$r, - $y,$'|T], Acc)-> - %% A binary which could not be found in the dump! Written by - %% crashdump_viewer:parse_heap_term(...) - IH = lists:reverse( - lists:flatten( - "<FONT COLOR=\"#FF0000\"><<...(Nonexisting Binary)>>" - "</FONT>")), - href_proc_port(T,IH++Acc); -href_proc_port([$<|T],Acc) -> - href_proc_port(T,[$;,$t,$l,$&|Acc]); -href_proc_port([$>|T],Acc) -> - href_proc_port(T,[$;,$t,$g,$&|Acc]); -href_proc_port([H|T],Acc) -> - href_proc_port(T,[H|Acc]); -href_proc_port([],Acc) -> - 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 - {lists:reverse(Acc),Str}; -split(Char,[H|T],Acc) -> - split(Char,T,[H|Acc]). - - -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/etop.erl b/lib/observer/src/etop.erl index 2610060eae..96a18cf450 100644 --- a/lib/observer/src/etop.erl +++ b/lib/observer/src/etop.erl @@ -44,9 +44,6 @@ help() -> " sort runtime | reductions | memory | msg_q~n" " What information to sort by~n" " Default: runtime (reductions if tracing=off)~n" - " output graphical | text~n" - " How to present results~n" - " Default: graphical~n" " tracing on | off etop uses the erlang trace facility, and thus~n" " no other tracing is possible on the node while~n" " etop is running, unless this option is set to~n" @@ -317,7 +314,7 @@ handle_args([_| R], C) -> handle_args([], C) -> C. -output(graphical) -> etop_gui; +output(graphical) -> exit({deprecated, "Use observer instead"}); output(text) -> etop_txt. diff --git a/lib/observer/src/etop_defs.hrl b/lib/observer/src/etop_defs.hrl index 664de61973..720fb50b5a 100644 --- a/lib/observer/src/etop_defs.hrl +++ b/lib/observer/src/etop_defs.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2009. 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 @@ -22,8 +22,8 @@ " procs~8w processes~8w code ~8w~n" " runq ~8w atom ~8w ets ~8w~n"). --record(opts, {node=node(), port = 8415, accum = false, intv = 5000, lines = 10, +-record(opts, {node=node(), port = 8415, accum = false, intv = 5000, lines = 10, width = 700, height = 340, sort = runtime, tracing = on, %% Other state information - out_mod=etop_gui, out_proc, server, host, tracer, store, + out_mod=etop_txt, out_proc, server, host, tracer, store, accum_tab, remote}). diff --git a/lib/observer/src/etop_gui.erl b/lib/observer/src/etop_gui.erl deleted file mode 100644 index 3971646abc..0000000000 --- a/lib/observer/src/etop_gui.erl +++ /dev/null @@ -1,374 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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 -%% 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(etop_gui). --compile([{nowarn_deprecated_function,{gs,config,2}}, - {nowarn_deprecated_function,{gs,create,3}}, - {nowarn_deprecated_function,{gs,create,4}}, - {nowarn_deprecated_function,{gs,destroy,1}}, - {nowarn_deprecated_function,{gs,read,2}}, - {nowarn_deprecated_function,{gs,start,0}}]). - --author('[email protected]'). - --export([init/1,stop/1]). --export([formatmfa/1,to_list/1]).% For etop_txt - --include("etop.hrl"). --include("etop_defs.hrl"). - --import(etop, [loadinfo/1, meminfo/2, getopt/2]). - -%% Heights --define(BarH, 28). % height of menubar --define(LabelH, 90). % height of label with system info --define(GridLineH, 21). % height of one line in the table (grid) - -%% Column numbers for grid - click to sort --define(TimeCol, 3). --define(RedsCol, 4). --define(MemCol, 5). --define(MsgQCol, 6). - -%% Font --define(Normal, {screen,12}). --define(Bold, {screen,bold,12}). - - -%% ----------------------------------------------------------------------------- -stop(_) -> ok. - -init(Config) -> - S = gs:start(), - Width = getopt(width, Config), - TotLines = getopt(lines,Config)+1, - - %% Max number of processes shown in window at startup is 10 - %% If less than 10 lines is specified, window size fits number of lines - WinH = if TotLines > 11 -> 11*?GridLineH + ?BarH + ?LabelH; - true -> TotLines*?GridLineH + ?BarH + ?LabelH - end, - Win = gs:create(window, S, - [{title, "Erlang Top"}, - {map, true}, %% While debugging - {configure, true}, - {width, Width}, {height, WinH}]), - Bar = gs:create(menubar, Win, []), - - FileButt = gs:create(menubutton, Bar, [{label,{text, " File "}}]), - OptionsButt = gs:create(menubutton, Bar, [{label,{text, " Options "}}]), - File = gs:create(menu, FileButt, []), - Options = gs:create(menu, OptionsButt, []), - gse:named_menuitem(refresh, File, - [{label,{text," Refresh "}}]), - gse:named_menuitem(dump, File, - [{label,{text," Dump to file "}}]), - gse:named_menuitem(exit, File, - [{label,{text," Exit "}}]), - - gse:named_menuitem(accum, Options, - [{label,{text, " Accumulate "}}, - {itemtype, check}]), - gse:named_menuitem(intv, Options, - [{label,{text, " Update Interval "}}]), - gse:named_menuitem(lines, Options, - [{label,{text, " Number of Lines "}}]), - Sort = gse:named_menuitem(sort, Options, - [{label,{text, " Sort "}}, - {itemtype,cascade}]), - SortMenu = gse:create(menu, Sort, []), - gse:named_menuitem(runtime, SortMenu, - [{label,{text, " Time "}}, - {itemtype,radio},{group,gr1}]), - gse:named_menuitem(memory, SortMenu, - [{label,{text, " Memory "}}, - {itemtype,radio},{group,gr1}]), - gse:named_menuitem(reductions, SortMenu, - [{label,{text, " Reductions "}}, - {itemtype,radio},{group,gr1}]), - gse:named_menuitem(msg_q, SortMenu, - [{label,{text, " Message Queue "}}, - {itemtype,radio},{group,gr1}]), - - SysInfo = gs:create(label,Win,[{x, 0}, {y, ?BarH},{align,sw}, - {width, Width},{height,?LabelH}]), - - {GridH,VScroll} = calc_grid_h(WinH,TotLines), - Grid = gse:grid(Win, - [{x, 0}, {y, ?BarH+?LabelH}, - {width, Width}, - {height, GridH}, - {hscroll, false}, - {vscroll, VScroll}, - {columnwidths, calc_column_w(Width)}, - {rows, {1, TotLines}}, - {font,?Normal}]), - - %% Header line - GL1 = gse:gridline(Grid, [{{text, 1}, "PID"}, - {{text, 2}, "Name or Initial Function"}, - {{text, ?TimeCol}, "Time(us)"}, - {{text, ?RedsCol}, "Reds"}, - {{text, ?MemCol}, "Memory"}, - {{text, ?MsgQCol}, "MsgQ"}, - {{text, 7}, "Current Function"}, - {bg, lightblue}, - {row, 1}, - {click, true}]), - - config_sort(GL1,getopt(sort,Config)), - Info = do_update(Grid, SysInfo, Config), - - get_event(Info, Win, Grid, GL1, SysInfo, Config). - -calc_column_w(W) -> - %% W = [2x, 3x, 1x, 1x, 1x, 1x, 3x] = 12x - RW = W-9, % just to make nice small margins on each side of grid - X = RW div 12, - [2*X, 3*X, X, X, X, X, 3*X + (RW - 12*X)]. - -config_sort(GL1,Sort) -> - gs:config(Sort,[{select,true}]), - lists:foreach(fun(S) -> - gs:config(GL1,[{{font,S},?Normal}]) - end, - [?TimeCol,?MemCol,?RedsCol,?MsgQCol]), - case Sort of - runtime -> gs:config(GL1,{{font,?TimeCol},?Bold}); - memory -> gs:config(GL1,{{font,?MemCol},?Bold}); - reductions -> gs:config(GL1,{{font,?RedsCol},?Bold}); - msg_q -> gs:config(GL1,{{font,?MsgQCol},?Bold}) - end. - -config_lines(Win,Grid,TotLines) -> - OldGridH = gs:read(Grid,height), - NewLinesH = TotLines*?GridLineH, - if NewLinesH =< OldGridH -> - gs:config(Win,[{height,NewLinesH+?BarH+?LabelH}]), - gs:config(Grid,[{rows,{1,TotLines}}, - {height,NewLinesH}, - {vscroll,false}]); - true -> - gs:config(Grid,[{rows,{1,TotLines}},{vscroll,right}]) - end. - -calc_grid_h(WinH,TotLines) -> - LeftInWin = WinH - ?BarH - ?LabelH, - TotGrid = TotLines * ?GridLineH, - if LeftInWin >= TotGrid -> - {TotGrid,false}; - true -> - {LeftInWin,right} - end. - -set_win_h(Win,OrigH,TotLines) -> - TotH = TotLines*?GridLineH + ?BarH + ?LabelH, - if TotH >= OrigH -> OrigH; - true -> gs:config(Win,[{height,TotH}]), - TotH - end. - -get_event(Info, Win, Grid, GL1, SysInfo, Config) -> - receive - {gs, Win, configure,[],[W,H,_,_]} -> - TotLines = getopt(lines,Config)+1, - %% Will not make window higher than total number of lines - RealWinH = set_win_h(Win,H,TotLines), - {GridH,VScroll} = calc_grid_h(RealWinH,TotLines), - gs:config(Grid, [{width, W}, - {columnwidths, calc_column_w(W)}, - {height,GridH}, {vscroll,VScroll}]), - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {gs, refresh, _, _, _} -> - Info1 = do_update(Grid, SysInfo, Config), - get_event(Info1, Win, Grid, GL1, SysInfo, Config); - {gs, dump, _, _, _} -> - case pop(Win,dump) of - {ok,File} -> etop:dump(File); - {error,cancel} -> ok - end, - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {gs, Win, destroy, _, _} -> - normal; - {gs, exit, _, _, _} -> - ok; - {gs, accum, _, _, _} -> - Old = getopt(accum,Config), - etop:config(accumulate,not Old), - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {gs,intv,_,_,_} -> - case pop(Win,interval) of - {ok,Intv} -> etop:config(interval,list_to_integer(Intv)); - {error,cancel} -> ok - end, - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {gs,lines,_,_,_} -> - case pop(Win,lines) of - {ok,Lines} -> etop:config(lines,list_to_integer(Lines)); - {error,cancel} -> ok - end, - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {gs,Sort,_,_,_} when Sort=:=runtime; - Sort=:=memory; - Sort=:=reductions; - Sort=:=msg_q -> - etop:config(sort,Sort), - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {gs,GL1,click,_,[Col,1,_]} -> - case Col of - ?TimeCol -> etop:config(sort, runtime); - ?MemCol -> etop:config(sort, memory); - ?RedsCol -> etop:config(sort, reductions); - ?MsgQCol -> etop:config(sort, msg_q); - _other -> ignore - end, - get_event(Info, Win, Grid, GL1, SysInfo, Config); - {config,{Key,Value},Config1} -> - case Key of - lines -> config_lines(Win,Grid,Value+1); - sort -> config_sort(GL1,Value); - accumulate -> gs:config(accum,[{select,Value}]); - _ -> ok - end, - Info1 = do_update(Grid, SysInfo, Config1), - get_event(Info1, Win, Grid, GL1, SysInfo, Config1); - {dump,Fd} -> - etop_txt:do_update(Fd,Info,Config), - get_event(Info, Win, Grid, GL1, SysInfo, Config); - Msg -> - io:format("~p got unexpected msg ~p~n", [?MODULE, Msg]), - get_event(Info, Win, Grid, GL1, SysInfo, Config) - after getopt(intv,Config) -> - Info1 = do_update(Grid, SysInfo, Config), - get_event(Info1, Win, Grid, GL1, SysInfo, Config) - end. - -do_update(Grid, SysInfo, Config) -> - Info = etop:update(Config), - Lines = makegridlines(Info#etop_info.procinfo, Grid, 2), - clear_lines(Lines, getopt(lines,Config) + 1, Grid), - makesysinfo(getopt(node,Config),Info,SysInfo), - Info. - -%clear_lines(From, To, _Grid) when From > To -> ok; -clear_lines(From, To, Grid) -> - case gs:read(Grid, {obj_at_row, From}) of - undefined -> - ok; - GridLine -> - gs:destroy(GridLine), - clear_lines(From + 1, To, Grid) - end. - -formatmfa({M, F, A}) -> - io_lib:format("~w:~w/~w",[M, F, A]); -formatmfa(Other) -> - %% E.g. when running hipe - the current_function for some - %% processes will be 'undefined' - io_lib:format("~w",[Other]). - - -makegridlines([#etop_proc_info{pid=Pid, - mem=Mem, - reds=Reds, - name=Name, - runtime=Time, - cf=MFA, - mq=MQ} - |T], Grid, Count) -> - update_gl(Grid, Count, [{{text, 1}, pid_to_list(Pid)}, - {{text, 2}, to_list(Name)}, - {{text, ?TimeCol}, - if is_integer(Time)->integer_to_list(Time); - true -> Time - end}, - {{text, ?RedsCol}, integer_to_list(Reds)}, - {{text, ?MemCol}, integer_to_list(Mem)}, - {{text, ?MsgQCol}, integer_to_list(MQ)}, - {{text, 7}, formatmfa(MFA)}, - {row, Count}, {click, false}]), - makegridlines(T, Grid, Count + 1); -makegridlines([],_Grid,Count) -> - Count. - -update_gl(Grid, Row, GL) -> - case gs:read(Grid, {obj_at_row, Row}) of - undefined -> - gse:gridline(Grid,[{row, Row}|GL]); - GridLine -> - gs:config(GridLine,GL) - end. - -to_list(Name) when is_atom(Name) -> atom_to_list(Name); -to_list({_M,_F,_A}=MFA) -> formatmfa(MFA). - - -makesysinfo(Node,Info,SysInfo) -> - {Cpu,NProcs,RQ,Clock} = loadinfo(Info), - case Info#etop_info.memi of - undefined -> - Str = "No memory information is available."; - Memi -> - [Tot,Procs,Atom,Bin,Code,Ets] = - meminfo(Memi, [total,processes,atom,binary,code,ets]), - Str = io_lib:fwrite(?SYSFORM, - [Node,Clock, - Cpu,Tot,Bin, - NProcs,Procs,Code, - RQ,Atom,Ets]) - end, - gs:config(SysInfo,[{label,{text,Str}},{font,?Normal}]). - - -pop(Win,Key) -> - Pop = gs:create(window,Win,[{title,"Config"}, - {width,160},{height,100}]), - gs:create(label,Pop,[{label,{text,txt(Key)}}, - {width,160}]), - gs:create(entry,entry,Pop,[{x,10},{y,30},{width,130}, - {keypress,true}]), - gs:create(button,ok,Pop,[{width,45},{y,60},{x,10}, - {label,{text,"Ok"}}]), - gs:create(button,cancel,Pop,[{width,60},{y,60},{x,80}, - {label,{text,"Cancel"}}]), - gs:config(Pop,{map,true}), - pop_loop(Pop). - -pop_loop(Pop) -> - receive - {gs,entry,keypress,_,['Return'|_]} -> - Str = gs:read(entry,text), - gs:destroy(Pop), - {ok,Str}; - {gs,entry,keypress,_,_} -> % all other keypresses - pop_loop(Pop); - {gs,ok,click,_,_} -> - Str = gs:read(entry,text), - gs:destroy(Pop), - {ok,Str}; - {gs,cancel,click,_,_} -> - gs:destroy(Pop), - {error,cancel}; - X -> - io:format("Got X=~w~n",[X]), - pop_loop(Pop) - end. - -txt(interval) -> "Enter new interval:"; -txt(lines) -> "Enter number of lines:"; -txt(dump) -> "Enter file name:". diff --git a/lib/observer/src/etop_tr.erl b/lib/observer/src/etop_tr.erl index dd326fe639..e6c69e4e1e 100644 --- a/lib/observer/src/etop_tr.erl +++ b/lib/observer/src/etop_tr.erl @@ -59,7 +59,7 @@ reader(Config) -> Port = getopt(port, Config), {ok, Sock} = gen_tcp:connect(Host, Port, [{active, false}]), - spawn_link(fun() -> reader_init(Sock,getopt(store,Config),nopid) end). + spawn_link(fun() -> reader_init(Sock,getopt(store,Config),[]) end). %%%%%%%%%%%%%% Socket reader %%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -73,24 +73,30 @@ reader(Sock, Store, Last) -> New = handle_data(Last, Data, Store), reader(Sock, Store, New). -handle_data(_, {_, Pid, in, _, Time}, _) -> - {Pid,Time}; -handle_data({Pid,Time1}, {_, Pid, out, _, Time2}, Store) -> - Elapsed = elapsed(Time1, Time2), - case ets:member(Store,Pid) of - true -> ets:update_counter(Store, Pid, Elapsed); - false -> ets:insert(Store,{Pid,Elapsed}) - end, - nopid; +handle_data(Last, {_, Pid, in, _, Time}, _) -> + [{Pid,Time}|Last]; +handle_data([], {_, _, out, _, _}, _Store) -> + %% ignore - there was probably just a 'drop' + []; +handle_data(Last, {_, Pid, out, _, Time2} = G, Store) -> + case lists:keytake(Pid, 1, Last) of + {_, {_, Time1}, New} -> + Elapsed = elapsed(Time1, Time2), + case ets:member(Store,Pid) of + true -> ets:update_counter(Store, Pid, Elapsed); + false -> ets:insert(Store,{Pid,Elapsed}) + end, + New; + false -> + io:format("Erlang top got garbage ~p~n", [G]), + Last + end; handle_data(_W, {drop, D}, _) -> %% Error case we are missing data here! io:format("Erlang top dropped data ~p~n", [D]), - nopid; -handle_data(nopid, {_, _, out, _, _}, _Store) -> - %% ignore - there was probably just a 'drop' - nopid; -handle_data(_, G, _) -> + []; +handle_data(Last, G, _) -> io:format("Erlang top got garbage ~p~n", [G]), - nopid. + Last. elapsed({Me1, S1, Mi1}, {Me2, S2, Mi2}) -> Me = (Me2 - Me1) * 1000000, diff --git a/lib/observer/src/etop_txt.erl b/lib/observer/src/etop_txt.erl index d0612f15b4..f048ff17ca 100644 --- a/lib/observer/src/etop_txt.erl +++ b/lib/observer/src/etop_txt.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2009. 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 @@ -27,7 +27,6 @@ -include("etop_defs.hrl"). -import(etop,[loadinfo/1,meminfo/2]). --import(etop_gui,[formatmfa/1,to_list/1]). -define(PROCFORM,"~-15w~-20s~8w~8w~8w~8w ~-20s~n"). @@ -99,3 +98,13 @@ writepinfo(Fd,[#etop_proc_info{pid=Pid, writepinfo(_Fd,[]) -> ok. + +formatmfa({M, F, A}) -> + io_lib:format("~w:~w/~w",[M, F, A]); +formatmfa(Other) -> + %% E.g. when running hipe - the current_function for some + %% processes will be 'undefined' + io_lib:format("~w",[Other]). + +to_list(Name) when is_atom(Name) -> atom_to_list(Name); +to_list({_M,_F,_A}=MFA) -> formatmfa(MFA). diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src index e881cb3c97..f14f0ee849 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,32 @@ [{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_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 72bafcc5e0..a8ace10275 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 @@ -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, []), @@ -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}) -> @@ -190,8 +195,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 +342,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_defs.hrl b/lib/observer/src/observer_defs.hrl index 586e7bbff9..3adc358b95 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,16 @@ 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 + +-define(SASH_STYLE, ?wxSP_LIVE_UPDATE bor ?wxSP_NOBORDER bor ?wxSP_3DSASH). diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl new file mode 100644 index 0000000000..c279218707 --- /dev/null +++ b/lib/observer/src/observer_html_lib.erl @@ -0,0 +1,407 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-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 +%% 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) + when not is_binary(Term) -> + 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,_Expand) + when is_binary(Bin) -> + Size = byte_size(Bin), + PrevSize = min(Size, 10) * 8, + <<Preview:PrevSize, _/binary>> = Bin, + Hash = erlang:phash2(Bin), + Key = {Preview, Size, Hash}, + ets:insert(Tab,{Key,Bin}), + Term = io_lib:format("~p", [['#OBSBin',Preview,Size,Hash]]), + href_proc_port(lists:flatten(Term), true). + +color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); +color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). + +%%%----------------------------------------------------------------- +%%% Internal library +start_html() -> + "<HTML>\n". +stop_html() -> + "</HTML>". +start_html_body() -> + "<BODY BGCOLOR=\"#FFFFFF\">\n". +stop_html_body() -> + "</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) -> + ["<HEAD>\n", + "<TITLE>", Title, "</TITLE>\n", + JavaScript, + "</HEAD>\n"]. + +body(Text) -> + [start_html_body(), + Text, + stop_html_body()]. + +start_table(Args) -> + ["<TABLE ", Args, ">\n"]. +stop_table() -> + "</TABLE>\n". + +table(Args,Text) -> + [start_table(Args), Text, stop_table()]. +tr(Text) -> + ["<TR>\n", Text, "\n</TR>\n"]. +tr(Args,Text) -> + ["<TR ", Args, ">\n", Text, "\n</TR>\n"]. +th(Args,Text) -> + ["<TH ", Args, ">\n", Text, "\n</TH>\n"]. +td(Text) -> + ["<TD>", Text, "</TD>"]. +td(Args,Text) -> + ["<TD ", Args, ">", Text, "</TD>"]. + +start_pre() -> + "<PRE>". +stop_pre() -> + "</PRE>". +pre(Text) -> + [start_pre(),Text,stop_pre()]. +href(Link,Text) -> + ["<A HREF=\"",Link,"\">",Text,"</A>"]. +href(Args,Link,Text) -> + ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"]. +font(Args,Text) -> + ["<FONT ",Args,">\n",Text,"\n</FONT>\n"]. +p(Text) -> + ["<P>",Text,"</P>\n"]. +br() -> + "<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( + "<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")), + href_proc_port(T,IH++Acc,LTB); +href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)-> + %% A binary which is truncated! Written by + %% crashdump_viewer:parse_heap_term(...) + IH = lists:reverse( + lists:flatten( + "<FONT COLOR=\"#FF0000\"><<...(Truncated Binary)>>" + "</FONT>")), + href_proc_port(T,IH++Acc,LTB); +href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)-> + %% A binary which could not be found in the dump! Written by + %% crashdump_viewer:parse_heap_term(...) + IH = lists:reverse( + lists:flatten( + "<FONT COLOR=\"#FF0000\"><<...(Nonexisting Binary)>>" + "</FONT>")), + 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,SizeStr,Pos] when From =:= cdv -> + Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, + {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), + PreviewStr = preview_string(list_to_integer(SizeStr), PreviewBin), + if LTB -> + href("TARGET=\"expanded\"", + ["#Binary?offset="++Offset++ + "&size="++SizeStr++ + "&pos="++Pos], + PreviewStr); + true -> + PreviewStr + end; + [Preview,SizeStr,Md5] when From =:= obs -> + Size = list_to_integer(SizeStr), + PrevSize = min(Size, 10) * 8, + PreviewStr = preview_string(Size, + <<(list_to_integer(Preview)):PrevSize>>), + if LTB -> + href("TARGET=\"expanded\"", + ["#OBSBinary?key1="++Preview++ + "&key2="++SizeStr++ + "&key3="++Md5], + PreviewStr); + true -> + PreviewStr + end; + _ -> + "<< ... >>" + end, + href_proc_port(Rest,[BinStr|Acc],LTB). + +preview_string(Size, PreviewBin) when Size > 10 -> + ["<<", + remove_lgt(io_lib:format("~p",[PreviewBin])), + "...(", + observer_lib:to_str({bytes,Size}), + ")", + ">>"]; +preview_string(_, PreviewBin) -> + ["<<", + remove_lgt(io_lib:format("~p",[PreviewBin])), + ">>"]. + +remove_lgt(Deep) -> + remove_lgt_1(lists:flatten(Deep)). + +remove_lgt_1([$<,$<|Rest]) -> + [$>,$>|BinStr] = lists:reverse(Rest), + replace_lgt(lists:reverse(BinStr)). + +replace_lgt([$<|R]) -> + ["<"|replace_lgt(R)]; +replace_lgt([$>|R]) -> + [">"|replace_lgt(R)]; +replace_lgt([L=[_|_]|R]) -> + [replace_lgt(L)|replace_lgt(R)]; +replace_lgt([A|R]) -> + [A|replace_lgt(R)]; +replace_lgt([]) -> []. + +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_lib.erl b/lib/observer/src/observer_lib.erl index f7712cf3da..cedaf7d2b8 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 @@ -19,17 +19,26 @@ -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, + 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, 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/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 @@ -96,11 +105,19 @@ 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. +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 +125,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 -> [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 -> [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 -> [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 -> [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 -> [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) -> @@ -146,23 +189,49 @@ 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); +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]) -> - wxStaticText:setLabel(Field, to_str(Value)), + 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; +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); +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, 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, @@ -207,27 +276,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) @@ -279,36 +344,188 @@ 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}; 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 = {_,_}}) -> + add_box(Panel, OuterBox, Cursor, Title, Proportion, Format); + ({Title, Format = {_,_}}) -> + add_box(Panel, OuterBox, Cursor, Title, 1, Format); + (undefined) -> + undefined + 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, Boxes}; + 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), - Field + 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,?SINGLE_LINE_STYLE}, + {value,"unknown"}]); + {click,Value} -> + link_entry(Panel,Value); + _ -> + Value = to_str(Value0), + 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), + + {_,H,_,_} = wxTextCtrl:getTextExtent(Field,"Wj"), + wxTextCtrl:setMinSize(Field,{0,H}), + + wxSizer:add(Box, Line, [{proportion,0},{flag,?wxEXPAND}]), + Field; + (undefined) -> + undefined 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_entry2(Panel, to_link(Link), Cursor), + wxCursor:destroy(Cursor), + TC. +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 " ++ Str), + wxWindow:setToolTip(TC, ToolTip), + TC. + +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}]), + %% 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), + 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(Txt,[undefined|Info],MaxX,MaxY) -> + get_max_size(Txt,Info,MaxX,MaxY); +get_max_size(_,[],X,_Y) -> + {X+2,-1}. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% set_listctrl_col_size(LCtrl, Total) -> wx:batch(fun() -> calc_last(LCtrl, Total) end). @@ -326,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); @@ -430,3 +644,108 @@ 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. + +%%%----------------------------------------------------------------- +%%% 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), + try progress_loop(Title,PD,Caller) + catch closed -> normal end. + +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) -> + 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) -> + try wxProgressDialog:update(PD,Value) + catch _:_ -> throw(closed) %% Port or window have died + end. +update_progress_text(PD,Text) -> + try wxProgressDialog:update(PD,0,[{newmsg,Text}]) + catch _:_ -> throw(closed) %% Port or window have died + end. +finish_progress(PD) -> + wxProgressDialog:destroy(PD). 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_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 9aaf648ea2..0be8c18893 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 @@ -66,6 +66,7 @@ -record(holder, {parent, info, + etop, sort=#sort{}, accum=[], attrs, @@ -191,20 +192,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 +232,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 +380,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}}; @@ -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_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 98d0403139..3ffa5fc77d 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.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 @@ -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,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]); @@ -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,13 +103,58 @@ 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])) 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=Href}}}, + #state{frame=Frame,expand_table=T,expand_wins=Opened0}=State) -> + {Type, Rest} = case Href of + "#Term?"++Keys -> {cdv_term_cb, Keys}; + "#OBSBinary?"++Keys -> {cdv_bin_cb, 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 = {obs, {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_wx: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}, + {noreply, State}; + handle_event(Event, _State) -> error({unhandled_event, Event}). @@ -116,10 +165,15 @@ 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}). -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; @@ -139,58 +193,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, + +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,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 = observer_html_lib:expandable_term("Message Queue", Messages, Table), + wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) end end, Update(), - {Text, Update}. + {Win, Update}. -init_dict_page(Parent, Pid) -> - Text = init_text_page(Parent), +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,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 = observer_html_lib:expandable_term("Dictionary", Dict, Table), + 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 +269,58 @@ init_stack_page(Parent, Pid) -> Update(), {LCtrl, Update}. - -init_state_page(Parent, Pid) -> - Text = init_text_page(Parent), +init_state_page(Parent, Pid, Table) -> + 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 = observer_html_lib:expandable_term("ProcState", StateInfo, Table), + 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 +334,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 +345,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 +398,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_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 31800cf12a..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), @@ -167,9 +174,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, 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(), 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 -> diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 47740581f0..ecb8e132fe 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-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 @@ -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). @@ -131,6 +132,9 @@ setup(#state{frame = Frame} = State) -> 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,12 @@ 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), SysPid = wx_object:get_pid(SysPanel), SysPid ! {active, node()}, @@ -206,6 +213,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) -> + spawn(crashdump_viewer, start, []), + {noreply, State}; + handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) -> {stop, normal, State}; @@ -340,6 +351,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]), @@ -517,9 +544,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 +557,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) -> @@ -550,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) -> |