aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src/crashdump_viewer_wx.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer/src/crashdump_viewer_wx.erl')
-rw-r--r--lib/observer/src/crashdump_viewer_wx.erl481
1 files changed, 481 insertions, 0 deletions
diff --git a/lib/observer/src/crashdump_viewer_wx.erl b/lib/observer/src/crashdump_viewer_wx.erl
new file mode 100644
index 0000000000..aeb89b54f4
--- /dev/null
+++ b/lib/observer/src/crashdump_viewer_wx.erl
@@ -0,0 +1,481 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+-module(crashdump_viewer_wx).
+
+-behaviour(wx_object).
+
+-export([start/1]).
+-export([get_attrib/1, set_status/1, create_txt_dialog/4]).
+
+-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
+ handle_call/3, handle_info/2, check_page_title/1]).
+
+%% Includes
+-include_lib("wx/include/wx.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-include("observer_defs.hrl").
+
+%% Defines
+
+-define(SERVER, cdv_wx).
+
+-define(ID_UG, 1).
+-define(ID_HOWTO, 2).
+-define(ID_NOTEBOOK, 3).
+
+-define(GEN_STR, "General").
+-define(PRO_STR, "Processes").
+-define(PORT_STR, "Ports").
+-define(ETS_STR, "ETS Tables").
+-define(TIMER_STR, "Timers").
+-define(FUN_STR, "Funs").
+-define(ATOM_STR, "Atoms").
+-define(DIST_STR, "Nodes").
+-define(MOD_STR, "Modules").
+-define(MEM_STR, "Memory").
+-define(INT_STR, "Internal Tables").
+
+%% Records
+-record(state,
+ {server,
+ file,
+ frame,
+ menubar,
+ menus = [],
+ status_bar,
+ notebook,
+ main_panel,
+ gen_panel,
+ pro_panel,
+ port_panel,
+ ets_panel,
+ timer_panel,
+ fun_panel,
+ atom_panel,
+ dist_panel,
+ mod_panel,
+ mem_panel,
+ int_panel,
+ active_tab
+ }).
+
+start(File) ->
+ case wx_object:start(?MODULE, File, []) of
+ Err = {error, _} -> Err;
+ _Obj -> ok
+ end.
+
+get_attrib(What) ->
+ wx_object:call(?SERVER, {get_attrib, What}).
+
+set_status(What) ->
+ wx_object:cast(?SERVER, {status_bar, What}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init(File) ->
+ {ok,CdvServer} = crashdump_viewer:start_link(),
+
+ register(?SERVER, self()),
+ wx:new(),
+ catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump viewer",
+ [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
+ IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
+ Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
+ wxFrame:setIcon(Frame, Icon),
+ wxIcon:destroy(Icon),
+
+ State = #state{server=CdvServer, file = File, frame = Frame},
+ UpdState = setup(State),
+ process_flag(trap_exit, true),
+ {Frame, UpdState}.
+
+setup(#state{file = File0, frame = Frame} = State) ->
+ %% Setup Menubar & Menus
+ MenuBar = wxMenuBar:new(),
+ DefMenus = default_menus(),
+ observer_lib:create_menus(DefMenus, MenuBar, default),
+ wxFrame:setMenuBar(Frame, MenuBar),
+
+ %% Setup panels
+ Panel = wxPanel:new(Frame, []),
+ Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
+
+ %% Setup "statusbar" to show warnings
+ StatusBar = observer_lib:create_status_bar(Panel),
+
+ %% Load a crashdump
+ File = load_dump(Panel,File0),
+
+ %% Set window title
+ T1 = "Crashdump Viewer: ",
+ Title =
+ if length(File) > 70 ->
+ T1 ++ filename:basename(File);
+ true ->
+ T1 ++ File
+ end,
+ wxFrame:setTitle(Frame, Title),
+
+ %% General information Panel
+ GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_page, cdv_gen_wx),
+
+ %% Setup sizer create early to get it when window shows
+ MainSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
+ wxSizer:add(MainSizer, StatusBar, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 0},
+ {border,4}]),
+ wxPanel:setSizer(Panel, MainSizer),
+
+ wxNotebook:connect(Notebook, command_notebook_page_changing),
+ wxFrame:connect(Frame, close_window, [{skip, true}]),
+ wxMenu:connect(Frame, command_menu_selected),
+ wxFrame:show(Frame),
+
+ %% I postpone the creation of the other tabs so they can query/use
+ %% the window size
+
+ %% Process Panel
+ ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list, cdv_proc_wx),
+
+ %% Port Panel
+ PortPanel = add_page(Notebook, ?PORT_STR, cdv_virtual_list, cdv_port_wx),
+
+ %% Table Panel
+ EtsPanel = add_page(Notebook, ?ETS_STR, cdv_virtual_list, cdv_ets_wx),
+
+ %% Timer Panel
+ TimerPanel = add_page(Notebook, ?TIMER_STR, cdv_virtual_list, cdv_timer_wx),
+
+ %% Fun Panel
+ FunPanel = add_page(Notebook, ?FUN_STR, cdv_virtual_list, cdv_fun_wx),
+
+ %% Atom Panel
+ AtomPanel = add_page(Notebook, ?ATOM_STR, cdv_virtual_list, cdv_atom_wx),
+
+ %% Distribution Panel
+ DistPanel = add_page(Notebook, ?DIST_STR, cdv_virtual_list, cdv_dist_wx),
+
+ %% Loaded Modules Panel
+ ModPanel = add_page(Notebook, ?MOD_STR, cdv_virtual_list, cdv_mod_wx),
+
+ %% Memory Panel
+ MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_panel, cdv_mem_wx),
+
+ %% Memory Panel
+ IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_panel, cdv_int_tab_wx),
+
+ %% Force redraw (window needs it)
+ wxWindow:refresh(Panel),
+
+ GenPid = wx_object:get_pid(GenPanel),
+ GenPid ! active,
+ UpdState = State#state{file = File,
+ main_panel = Panel,
+ notebook = Notebook,
+ menubar = MenuBar,
+ status_bar = StatusBar,
+ gen_panel = GenPanel,
+ pro_panel = ProPanel,
+ port_panel = PortPanel,
+ ets_panel = EtsPanel,
+ timer_panel = TimerPanel,
+ fun_panel = FunPanel,
+ atom_panel = AtomPanel,
+ dist_panel = DistPanel,
+ mod_panel = ModPanel,
+ mem_panel = MemPanel,
+ int_panel = IntPanel,
+ active_tab = GenPid
+ },
+ %% Create resources which we don't want to duplicate
+ SysFont = wxSystemSettings:getFont(?wxSYS_SYSTEM_FIXED_FONT),
+ Fixed = case wxFont:isFixedWidth(SysFont) of
+ true -> SysFont;
+ false -> %% Sigh
+ SysFontSize = wxFont:getPointSize(SysFont),
+ wxFont:new(SysFontSize,
+ ?wxFONTFAMILY_MODERN,
+ ?wxFONTSTYLE_NORMAL,
+ ?wxFONTWEIGHT_NORMAL)
+ end,
+ put({font, fixed}, Fixed),
+ UpdState.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%Callbacks
+handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}},
+ #state{active_tab=Previous} = State) ->
+ case get_active_pid(State) of
+ Previous -> {noreply, State};
+ Pid ->
+ Previous ! not_active,
+ Pid ! active,
+ {noreply, State#state{active_tab=Pid}}
+ end;
+
+handle_event(#wx{event = #wxClose{}}, State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id = ?wxID_OPEN,
+ event = #wxCommand{type = command_menu_selected}},
+ State) ->
+ File = load_dump(State#state.main_panel,undefined),
+ Panels = [State#state.gen_panel,
+ State#state.pro_panel,
+ State#state.port_panel,
+ State#state.ets_panel,
+ State#state.timer_panel,
+ State#state.fun_panel,
+ State#state.atom_panel,
+ State#state.dist_panel,
+ State#state.mod_panel,
+ State#state.mem_panel,
+ State#state.int_panel],
+ _ = [wx_object:get_pid(Panel) ! new_dump || Panel<-Panels],
+ State#state.active_tab ! active,
+ {noreply, State#state{file=File}};
+
+handle_event(#wx{id = ?wxID_EXIT,
+ event = #wxCommand{type = command_menu_selected}},
+ State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id = HelpId,
+ event = #wxCommand{type = command_menu_selected}},
+ State) when HelpId==?wxID_HELP; HelpId==?ID_UG; HelpId==?ID_HOWTO ->
+ Help = get_help_doc(HelpId),
+ wx_misc:launchDefaultBrowser(Help) orelse
+ create_txt_dialog(State#state.frame,
+ "Could not launch browser: ~n " ++ Help,
+ "Error", ?wxICON_ERROR),
+ {noreply, State};
+
+handle_event(#wx{id = ?wxID_ABOUT,
+ event = #wxCommand{type = command_menu_selected}},
+ State = #state{frame=Frame}) ->
+ AboutString = "Display information from an erlang crash dump",
+ Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP},
+ {caption, "About"}],
+ wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)),
+ {noreply, State};
+
+handle_event(Event, State) ->
+ Pid = get_active_pid(State),
+ Pid ! Event,
+ {noreply, State}.
+
+handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) ->
+ wxTextCtrl:clear(SB),
+ wxTextCtrl:writeText(SB, Msg),
+ {noreply, State};
+
+handle_cast(_Cast, State) ->
+ {noreply, State}.
+
+handle_call({get_attrib, Attrib}, _From, State) ->
+ {reply, get(Attrib), State};
+
+handle_call(_Msg, _From, State) ->
+ {reply, ok, State}.
+
+handle_info({'EXIT', Pid, normal}, #state{server=Pid}=State) ->
+ {stop, normal, State};
+
+handle_info({'EXIT', Pid, _Reason}, State) ->
+ io:format("Child (~s) crashed exiting: ~p ~p~n",
+ [pid2panel(Pid, State), Pid,_Reason]),
+ {stop, normal, State};
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{frame = Frame}) ->
+ wxFrame:destroy(Frame),
+ crashdump_viewer:stop(),
+ ok.
+
+code_change(_, _, State) ->
+ {ok, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+add_page(Notebook,Title,Callback,Extra) ->
+ Panel = Callback:start_link(Notebook, Extra),
+ wxNotebook:addPage(Notebook, Panel, Title, []),
+ Panel.
+
+create_txt_dialog(Frame, Msg, Title, Style) ->
+ MD = wxMessageDialog:new(Frame, Msg, [{style, Style}]),
+ wxMessageDialog:setTitle(MD, Title),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD).
+
+check_page_title(Notebook) ->
+ Selection = wxNotebook:getSelection(Notebook),
+ wxNotebook:getPageText(Notebook, Selection).
+
+get_active_pid(#state{notebook=Notebook, gen_panel=Gen, pro_panel=Pro,
+ port_panel=Ports, ets_panel=Ets, timer_panel=Timers,
+ fun_panel=Funs, atom_panel=Atoms, dist_panel=Dist,
+ mod_panel=Mods, mem_panel=Mem, int_panel=Int
+ }) ->
+ Panel = case check_page_title(Notebook) of
+ ?GEN_STR -> Gen;
+ ?PRO_STR -> Pro;
+ ?PORT_STR -> Ports;
+ ?ETS_STR -> Ets;
+ ?TIMER_STR -> Timers;
+ ?FUN_STR -> Funs;
+ ?ATOM_STR -> Atoms;
+ ?DIST_STR -> Dist;
+ ?MOD_STR -> Mods;
+ ?MEM_STR -> Mem;
+ ?INT_STR -> Int
+ end,
+ wx_object:get_pid(Panel).
+
+pid2panel(Pid, #state{gen_panel=Gen, pro_panel=Pro, port_panel=Ports,
+ ets_panel=Ets, timer_panel=Timers, fun_panel=Funs,
+ atom_panel=Atoms, dist_panel=Dist, mod_panel=Mods,
+ mem_panel=Mem, int_panel=Int}) ->
+ case Pid of
+ Gen -> ?GEN_STR;
+ Pro -> ?PRO_STR;
+ Ports -> ?PORT_STR;
+ Ets -> ?ETS_STR;
+ Timers -> ?TIMER_STR;
+ Funs -> ?FUN_STR;
+ Atoms -> ?ATOM_STR;
+ Dist -> ?DIST_STR;
+ Mods -> ?MOD_STR;
+ Mem -> ?MEM_STR;
+ Int -> ?INT_STR;
+ _ -> "unknown"
+ end.
+
+default_menus() ->
+ Open = #create_menu{id = ?wxID_OPEN, text = "Open new crash dump"},
+ Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"},
+ About = #create_menu{id = ?wxID_ABOUT, text = "About"},
+ Help = #create_menu{id = ?wxID_HELP},
+ UG = #create_menu{id = ?ID_UG, text = "Crashdump viewer user's guide"},
+ Howto = #create_menu{id = ?ID_HOWTO, text = "How to interpret crash dump"},
+ case os:type() =:= {unix, darwin} of
+ false ->
+ FileMenu = {"File", [Open,Quit]},
+ HelpMenu = {"Help", [About,Help,UG,Howto]},
+ [FileMenu, HelpMenu];
+ true ->
+ %% On Mac quit and about will be moved to the "default' place
+ %% automagicly, so just add them to a menu that always exist.
+ %% But not to the help menu for some reason
+%! {Tag, Menus} = NodeMenu,
+%! [{Tag, Menus ++ [Quit,About]}, {"&Help", [Help]}]
+ [{"File", [Quit,About]}, {"&Help", [Help,UG,Howto]}] %?siri: does this work?
+ end.
+
+
+load_dump(Panel,undefined) ->
+ FD = wxFileDialog:new(Panel,
+ [{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]),
+ case wxFileDialog:showModal(FD) of
+ ?wxID_OK ->
+ Path = wxFileDialog:getPath(FD),
+ wxDialog:destroy(FD),
+ load_dump(Panel, Path);
+ _ ->
+ wxDialog:destroy(FD)
+ end;
+load_dump(Panel, FileName) ->
+ crashdump_viewer:read_file(FileName),
+ update_progress(Panel,false),
+ FileName.
+
+update_progress(Panel,PD) ->
+ case crashdump_viewer:get_progress() of
+ {ok, done} ->
+ wxProgressDialog:destroy(PD);
+ {ok, Percent} when is_integer(Percent) ->
+ wxProgressDialog:update(PD,Percent),
+ update_progress(Panel,PD);
+ {ok,Msg} ->
+ case PD of
+ false -> ok;
+ _ -> wxProgressDialog:destroy(PD)
+ end,
+ update_progress(Panel,new_progress(Msg));
+ {error, Reason} ->
+ wxProgressDialog:destroy(PD),
+ FailMsg = file:format_error(Reason),
+ MD = wxMessageDialog:new(Panel, FailMsg),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD)
+ end.
+
+new_progress(Msg) ->
+ wxProgressDialog:new("Crashdump viewer",Msg,
+ [{maximum,100},
+ {style,
+ ?wxPD_APP_MODAL bor
+ ?wxPD_SMOOTH bor
+ ?wxPD_AUTO_HIDE}]).
+%%%-----------------------------------------------------------------
+%%% Find help document (HTML files)
+get_help_doc(HelpId) ->
+ Internal = get_internal_help_doc(HelpId),
+ case filelib:is_file(Internal) of
+ true -> Internal;
+ false -> get_external_help_doc(HelpId)
+ end.
+
+get_internal_help_doc(?ID_HOWTO) ->
+ filename:join(erts_doc_dir(),help_file(?ID_HOWTO));
+get_internal_help_doc(HelpId) ->
+ filename:join(observer_doc_dir(),help_file(HelpId)).
+
+get_external_help_doc(?ID_HOWTO) ->
+ filename:join("http://www.erlang.org/doc/apps/erts",help_file(?ID_HOWTO));
+get_external_help_doc(HelpId) ->
+ filename:join("http://www.erlang.org/doc/apps/observer",help_file(HelpId)).
+
+observer_doc_dir() ->
+ filename:join([code:lib_dir(observer),"doc","html"]).
+
+erts_doc_dir() ->
+ ErtsVsn = erlang:system_info(version),
+ RootDir = code:root_dir(),
+ VsnErtsDir = filename:join(RootDir,"erts-"++ErtsVsn),
+ DocDir = filename:join(["doc","html"]),
+ case filelib:is_dir(VsnErtsDir) of
+ true ->
+ filename:join(VsnErtsDir,DocDir);
+ false ->
+ %% So this can be run in source tree
+ filename:join([RootDir,"erts",DocDir])
+ end.
+
+help_file(?wxID_HELP) -> "crashdump_help.html";
+help_file(?ID_UG) -> "crashdump_ug.html";
+help_file(?ID_HOWTO) -> "crash_dump.html".