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