%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
-module(cdv_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(SCHEDULER_STR, "Schedulers").
-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,
sched_panel,
fun_panel,
atom_panel,
dist_panel,
mod_panel,
mem_panel,
int_panel,
active_tab
}).
start(File) ->
case wx_object:start(?MODULE, File, []) of
Err = {error, _} -> Err;
_Obj -> ok
end.
get_attrib(What) ->
wx_object:call(?SERVER, {get_attrib, What}).
set_status(What) ->
wx_object:cast(?SERVER, {status_bar, What}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init(File0) ->
register(?SERVER, self()),
wx:new(),
{ok,CdvServer} = crashdump_viewer:start_link(),
catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer",
[{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
wxFrame:setIcon(Frame, Icon),
wxIcon:destroy(Icon),
%% Setup panels
Panel = wxPanel:new(Frame, []),
Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
%% Setup "statusbar" to show warnings
StatusBar = observer_lib:create_status_bar(Panel),
%% Setup sizer create early to get it when window shows
MainSizer = wxBoxSizer:new(?wxVERTICAL),
wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
wxSizer:add(MainSizer, StatusBar, [{flag, ?wxEXPAND bor ?wxALL},
{proportion, 0},
{border,4}]),
wxPanel:setSizer(Panel, MainSizer),
wxNotebook:connect(Notebook, command_notebook_page_changing),
wxFrame:connect(Frame, close_window, [{skip, true}]),
wxMenu:connect(Frame, command_menu_selected),
case load_dump(Frame,File0) of
{ok,File} ->
%% Set window title
T1 = "Crashdump Viewer: ",
FileLength = string:length(File),
Title =
if FileLength > 70 ->
T1 ++ filename:basename(File);
true ->
T1 ++ File
end,
wxFrame:setTitle(Frame, Title),
setup(#state{server=CdvServer,
file=File,
frame=Frame,
status_bar=StatusBar,
notebook=Notebook,
main_panel=Panel});
error ->
wxFrame:destroy(Frame),
wx:destroy(),
crashdump_viewer:stop(),
ignore
end.
setup(#state{frame=Frame, notebook=Notebook}=State) ->
%% Setup Menubar & Menus
MenuBar = wxMenuBar:new(),
DefMenus = default_menus(),
observer_lib:create_menus(DefMenus, MenuBar, default),
wxFrame:setMenuBar(Frame, MenuBar),
%% General information Panel
GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_wx, cdv_gen_cb),
%% Process Panel
ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list_wx, cdv_proc_cb),
%% Port Panel
PortPanel = add_page(Notebook, ?PORT_STR, cdv_virtual_list_wx, cdv_port_cb),
%% Table Panel
EtsPanel = add_page(Notebook, ?ETS_STR, cdv_virtual_list_wx, cdv_ets_cb),
%% Timer Panel
TimerPanel = add_page(Notebook, ?TIMER_STR, cdv_virtual_list_wx,cdv_timer_cb),
%% Scheduler Panel
SchedPanel = add_page(Notebook, ?SCHEDULER_STR, cdv_virtual_list_wx, cdv_sched_cb),
%% Fun Panel
FunPanel = add_page(Notebook, ?FUN_STR, cdv_virtual_list_wx, cdv_fun_cb),
%% Atom Panel
AtomPanel = add_page(Notebook, ?ATOM_STR, cdv_virtual_list_wx, cdv_atom_cb),
%% Distribution Panel
DistPanel = add_page(Notebook, ?DIST_STR, cdv_virtual_list_wx, cdv_dist_cb),
%% Loaded Modules Panel
ModPanel = add_page(Notebook, ?MOD_STR, cdv_virtual_list_wx, cdv_mod_cb),
%% Memory Panel
MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_wx, cdv_mem_cb),
%% Memory Panel
IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_wx, cdv_int_tab_cb),
%% Show the window
wxFrame:show(Frame),
GenPid = wx_object:get_pid(GenPanel),
GenPid ! active,
observer_lib:destroy_progress_dialog(),
process_flag(trap_exit, true),
{Frame, State#state{menubar = MenuBar,
gen_panel = GenPanel,
pro_panel = ProPanel,
port_panel = PortPanel,
ets_panel = EtsPanel,
timer_panel = TimerPanel,
sched_panel = SchedPanel,
fun_panel = FunPanel,
atom_panel = AtomPanel,
dist_panel = DistPanel,
mod_panel = ModPanel,
mem_panel = MemPanel,
int_panel = IntPanel,
active_tab = GenPid
}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%Callbacks
handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}},
#state{active_tab=Previous} = State) ->
case get_active_pid(State) of
Previous -> {noreply, State};
Pid ->
Pid ! active,
{noreply, State#state{active_tab=Pid}}
end;
handle_event(#wx{event = #wxClose{}}, State) ->
{stop, normal, State};
handle_event(#wx{id = ?wxID_OPEN,
event = #wxCommand{type = command_menu_selected}},
State) ->
NewState =
case load_dump(State#state.frame,undefined) of
{ok,File} ->
Panels = [State#state.gen_panel,
State#state.pro_panel,
State#state.port_panel,
State#state.ets_panel,
State#state.timer_panel,
State#state.fun_panel,
State#state.atom_panel,
State#state.dist_panel,
State#state.mod_panel,
State#state.mem_panel,
State#state.int_panel],
_ = [wx_object:call(Panel,new_dump) || Panel<-Panels],
wxNotebook:setSelection(State#state.notebook,0),
observer_lib:destroy_progress_dialog(),
State#state{file=File};
error ->
State
end,
{noreply,NewState};
handle_event(#wx{id = ?wxID_EXIT,
event = #wxCommand{type = command_menu_selected}},
State) ->
{stop, normal, State};
handle_event(#wx{id = HelpId,
event = #wxCommand{type = command_menu_selected}},
State) when HelpId==?wxID_HELP; HelpId==?ID_UG; HelpId==?ID_HOWTO ->
Help = get_help_doc(HelpId),
wx_misc:launchDefaultBrowser(Help) orelse
create_txt_dialog(State#state.frame,
"Could not launch browser: ~n " ++ Help,
"Error", ?wxICON_ERROR),
{noreply, State};
handle_event(#wx{id = ?wxID_ABOUT,
event = #wxCommand{type = command_menu_selected}},
State = #state{frame=Frame}) ->
AboutString = "Display information from an erlang crash dump",
Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP},
{caption, "About"}],
wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)),
{noreply, State};
handle_event(Event, State) ->
Pid = get_active_pid(State),
Pid ! Event,
{noreply, State}.
handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) ->
wxTextCtrl:clear(SB),
wxTextCtrl:writeText(SB, Msg),
{noreply, State};
handle_cast(_Cast, State) ->
{noreply, State}.
handle_call({get_attrib, Attrib}, _From, State) ->
{reply, get(Attrib), State};
handle_call(_Msg, _From, State) ->
{reply, ok, State}.
handle_info({'EXIT', Pid, normal}, #state{server=Pid}=State) ->
{stop, normal, State};
handle_info({'EXIT', Pid, _Reason}, State) ->
io:format("Child (~s) crashed exiting: ~p ~tp~n",
[pid2panel(Pid, State), Pid,_Reason]),
{stop, normal, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{frame = Frame}) ->
wxFrame:destroy(Frame),
wx:destroy(),
crashdump_viewer:stop(),
ok.
code_change(_, _, State) ->
{ok, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
add_page(Notebook,Title,Callback,Extra) ->
Panel = Callback:start_link(Notebook, Extra),
wxNotebook:addPage(Notebook, Panel, Title, []),
Panel.
create_txt_dialog(Frame, Msg, Title, Style) ->
MD = wxMessageDialog:new(Frame, Msg, [{style, Style}]),
wxMessageDialog:setTitle(MD, Title),
wxDialog:showModal(MD),
wxDialog:destroy(MD).
check_page_title(Notebook) ->
Selection = wxNotebook:getSelection(Notebook),
wxNotebook:getPageText(Notebook, Selection).
get_active_pid(#state{notebook=Notebook, gen_panel=Gen, pro_panel=Pro,
port_panel=Ports, ets_panel=Ets, timer_panel=Timers,
fun_panel=Funs, atom_panel=Atoms, dist_panel=Dist,
mod_panel=Mods, mem_panel=Mem, int_panel=Int,
sched_panel=Sched
}) ->
Panel = case check_page_title(Notebook) of
?GEN_STR -> Gen;
?PRO_STR -> Pro;
?PORT_STR -> Ports;
?ETS_STR -> Ets;
?TIMER_STR -> Timers;
?SCHEDULER_STR -> Sched;
?FUN_STR -> Funs;
?ATOM_STR -> Atoms;
?DIST_STR -> Dist;
?MOD_STR -> Mods;
?MEM_STR -> Mem;
?INT_STR -> Int
end,
wx_object:get_pid(Panel).
pid2panel(Pid, #state{gen_panel=Gen, pro_panel=Pro, port_panel=Ports,
ets_panel=Ets, timer_panel=Timers, fun_panel=Funs,
atom_panel=Atoms, dist_panel=Dist, mod_panel=Mods,
mem_panel=Mem, int_panel=Int}) ->
case Pid of
Gen -> ?GEN_STR;
Pro -> ?PRO_STR;
Ports -> ?PORT_STR;
Ets -> ?ETS_STR;
Timers -> ?TIMER_STR;
Funs -> ?FUN_STR;
Atoms -> ?ATOM_STR;
Dist -> ?DIST_STR;
Mods -> ?MOD_STR;
Mem -> ?MEM_STR;
Int -> ?INT_STR;
_ -> "unknown"
end.
default_menus() ->
Open = #create_menu{id = ?wxID_OPEN, text = "Open new crash dump"},
Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"},
About = #create_menu{id = ?wxID_ABOUT, text = "About"},
Help = #create_menu{id = ?wxID_HELP},
UG = #create_menu{id = ?ID_UG, text = "Crashdump viewer user's guide"},
Howto = #create_menu{id = ?ID_HOWTO, text = "How to interpret crash dump"},
case os:type() =:= {unix, darwin} of
false ->
FileMenu = {"File", [Open,Quit]},
HelpMenu = {"Help", [About,Help,UG,Howto]},
[FileMenu, HelpMenu];
true ->
%% On Mac quit and about will be moved to the "default' place
%% automagicly, so just add them to a menu that always exist.
[{"File", [Open, About,Quit]}, {"&Help", [Help,UG,Howto]}]
end.
load_dump(Frame,undefined) ->
FD = wxFileDialog:new(wx:null(),
[{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]),
case wxFileDialog:showModal(FD) of
?wxID_OK ->
Path = wxFileDialog:getPath(FD),
wxDialog:destroy(FD),
load_dump(Frame,Path);
_ ->
wxDialog:destroy(FD),
error
end;
load_dump(Frame,FileName) ->
case maybe_warn_filename(FileName) of
continue ->
do_load_dump(Frame,FileName);
stop ->
error
end.
do_load_dump(Frame,FileName) ->
ok = observer_lib:display_progress_dialog(wx:null(),
"Crashdump Viewer",
"Loading crashdump"),
crashdump_viewer:read_file(FileName),
case observer_lib:wait_for_progress() of
ok ->
%% Set window title
T1 = "Crashdump Viewer: ",
FileLength = string:length(FileName),
Title =
if FileLength > 70 ->
T1 ++ filename:basename(FileName);
true ->
T1 ++ FileName
end,
wxFrame:setTitle(Frame, Title),
{ok,FileName};
error ->
error
end.
maybe_warn_filename(FileName) ->
case os:getenv("ERL_CRASH_DUMP_SECONDS")=="0" orelse
os:getenv("ERL_CRASH_DUMP_BYTES")=="0" of
true ->
continue;
false ->
DumpName = case os:getenv("ERL_CRASH_DUMP") of
false -> filename:absname("erl_crash.dump");
Name -> filename:absname(Name)
end,
case filename:absname(FileName) of
DumpName ->
Warning =
"WARNING: the current crashdump might be overwritten "
"if the crashdump_viewer node crashes.\n\n"
"Renaming the file before inspecting it will "
"remove the problem.\n\n"
"Do you want to continue?",
case observer_lib:display_yes_no_dialog(Warning) of
?wxID_YES -> continue;
?wxID_NO -> stop
end;
_ ->
continue
end
end.
%%%-----------------------------------------------------------------
%%% Find help document (HTML files)
get_help_doc(HelpId) ->
Internal = get_internal_help_doc(HelpId),
case filelib:is_file(Internal) of
true -> Internal;
false -> get_external_help_doc(HelpId)
end.
get_internal_help_doc(?ID_HOWTO) ->
filename:join(erts_doc_dir(),help_file(?ID_HOWTO));
get_internal_help_doc(HelpId) ->
filename:join(observer_doc_dir(),help_file(HelpId)).
get_external_help_doc(?ID_HOWTO) ->
filename:join("http://www.erlang.org/doc/apps/erts",help_file(?ID_HOWTO));
get_external_help_doc(HelpId) ->
filename:join("http://www.erlang.org/doc/apps/observer",help_file(HelpId)).
observer_doc_dir() ->
filename:join([code:lib_dir(observer),"doc","html"]).
erts_doc_dir() ->
ErtsVsn = erlang:system_info(version),
RootDir = code:root_dir(),
VsnErtsDir = filename:join(RootDir,"erts-"++ErtsVsn),
DocDir = filename:join(["doc","html"]),
case filelib:is_dir(VsnErtsDir) of
true ->
filename:join(VsnErtsDir,DocDir);
false ->
%% So this can be run in source tree
filename:join([RootDir,"erts",DocDir])
end.
help_file(?wxID_HELP) -> "crashdump_help.html";
help_file(?ID_UG) -> "crashdump_ug.html";
help_file(?ID_HOWTO) -> "crash_dump.html".