1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19-module(cdv_wx).
20
21-behaviour(wx_object).
22
23-export([start/1]).
24-export([get_attrib/1, set_status/1, create_txt_dialog/4]).
25
26-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
27	 handle_call/3, handle_info/2, check_page_title/1]).
28
29%% Includes
30-include_lib("wx/include/wx.hrl").
31-include_lib("kernel/include/file.hrl").
32
33-include("observer_defs.hrl").
34
35%% Defines
36
37-define(SERVER, cdv_wx).
38
39-define(ID_UG, 1).
40-define(ID_HOWTO, 2).
41-define(ID_NOTEBOOK, 3).
42
43-define(GEN_STR,   "General").
44-define(PRO_STR,   "Processes").
45-define(PORT_STR,  "Ports").
46-define(ETS_STR,   "ETS Tables").
47-define(TIMER_STR, "Timers").
48-define(SCHEDULER_STR, "Schedulers").
49-define(FUN_STR,   "Funs").
50-define(ATOM_STR,  "Atoms").
51-define(DIST_STR,  "Nodes").
52-define(MOD_STR,   "Modules").
53-define(MEM_STR,   "Memory").
54-define(PERSISTENT_STR, "Persistent Terms").
55-define(INT_STR,   "Internal Tables").
56
57%% Records
58-record(state,
59	{server,
60	 file,
61	 frame,
62	 menubar,
63	 menus = [],
64	 status_bar,
65	 notebook,
66	 main_panel,
67	 gen_panel,
68	 pro_panel,
69	 port_panel,
70	 ets_panel,
71	 timer_panel,
72	 sched_panel,
73	 fun_panel,
74	 atom_panel,
75	 dist_panel,
76	 mod_panel,
77	 mem_panel,
78         persistent_panel,
79	 int_panel,
80	 active_tab
81	}).
82
83start(File) ->
84    case wx_object:start(?MODULE, File, []) of
85	Err = {error, _} -> Err;
86	_Obj -> ok
87    end.
88
89get_attrib(What) ->
90    wx_object:call(?SERVER, {get_attrib, What}).
91
92set_status(What) ->
93    wx_object:cast(?SERVER, {status_bar, What}).
94
95%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96
97init(File0) ->
98    register(?SERVER, self()),
99    wx:new(),
100
101    {ok,CdvServer} = crashdump_viewer:start_link(),
102
103    catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
104    Scale = observer_wx:get_scale(),
105    Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer",
106			[{size, {Scale*850, Scale*600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
107    IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
108    Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
109    wxFrame:setIcon(Frame, Icon),
110    wxIcon:destroy(Icon),
111
112    %% Setup panels
113    Panel = wxPanel:new(Frame, []),
114    Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
115
116    %% Setup "statusbar" to show warnings
117    StatusBar = observer_lib:create_status_bar(Panel),
118
119    %% Setup sizer create early to get it when window shows
120    MainSizer = wxBoxSizer:new(?wxVERTICAL),
121
122    wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
123    wxSizer:add(MainSizer, StatusBar, [{flag, ?wxEXPAND bor ?wxALL},
124				       {proportion, 0},
125				       {border,4}]),
126    wxPanel:setSizer(Panel, MainSizer),
127
128    wxNotebook:connect(Notebook, command_notebook_page_changing),
129    wxFrame:connect(Frame, close_window, [{skip, true}]),
130    wxMenu:connect(Frame, command_menu_selected),
131
132    case load_dump(Frame,File0) of
133	{ok,File} ->
134	    %% Set window title
135	    T1 = "Crashdump Viewer: ",
136            FileLength = string:length(File),
137	    Title =
138		if FileLength > 70 ->
139			T1 ++ filename:basename(File);
140		   true ->
141			T1 ++ File
142		end,
143	    wxFrame:setTitle(Frame, Title),
144
145	    setup(#state{server=CdvServer,
146			 file=File,
147			 frame=Frame,
148			 status_bar=StatusBar,
149			 notebook=Notebook,
150			 main_panel=Panel});
151	error ->
152	    wxFrame:destroy(Frame),
153	    wx:destroy(),
154	    crashdump_viewer:stop(),
155	    ignore
156    end.
157
158setup(#state{frame=Frame, notebook=Notebook}=State) ->
159
160    %% Setup Menubar & Menus
161    MenuBar = wxMenuBar:new(),
162    DefMenus = default_menus(),
163    observer_lib:create_menus(DefMenus, MenuBar, default),
164    wxFrame:setMenuBar(Frame, MenuBar),
165
166    %% General information Panel
167    GenPanel = add_page(Notebook, ?GEN_STR, cdv_info_wx, cdv_gen_cb),
168
169    %% Process Panel
170    ProPanel = add_page(Notebook, ?PRO_STR, cdv_virtual_list_wx, cdv_proc_cb),
171
172    %% Port Panel
173    PortPanel = add_page(Notebook, ?PORT_STR, cdv_virtual_list_wx, cdv_port_cb),
174
175    %% Table Panel
176    EtsPanel = add_page(Notebook, ?ETS_STR, cdv_virtual_list_wx, cdv_ets_cb),
177
178    %% Timer Panel
179    TimerPanel = add_page(Notebook, ?TIMER_STR, cdv_virtual_list_wx,cdv_timer_cb),
180
181    %% Scheduler Panel
182    SchedPanel = add_page(Notebook, ?SCHEDULER_STR, cdv_virtual_list_wx, cdv_sched_cb),
183
184    %% Fun Panel
185    FunPanel = add_page(Notebook, ?FUN_STR, cdv_virtual_list_wx, cdv_fun_cb),
186
187    %% Atom Panel
188    AtomPanel = add_page(Notebook, ?ATOM_STR, cdv_virtual_list_wx, cdv_atom_cb),
189
190    %% Distribution Panel
191    DistPanel = add_page(Notebook, ?DIST_STR, cdv_virtual_list_wx, cdv_dist_cb),
192
193    %% Loaded Modules Panel
194    ModPanel = add_page(Notebook, ?MOD_STR, cdv_virtual_list_wx, cdv_mod_cb),
195
196    %% Memory Panel
197    MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_wx, cdv_mem_cb),
198
199    %% Persistent Terms Panel
200    PersistentPanel = add_page(Notebook, ?PERSISTENT_STR,
201                               cdv_html_wx, cdv_persistent_cb),
202
203    %% Memory Panel
204    IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_wx, cdv_int_tab_cb),
205
206    %% Show the window
207    wxFrame:show(Frame),
208
209    GenPid = wx_object:get_pid(GenPanel),
210    GenPid ! active,
211    observer_lib:destroy_progress_dialog(),
212    process_flag(trap_exit, true),
213    {Frame, State#state{menubar = MenuBar,
214			gen_panel = GenPanel,
215			pro_panel = ProPanel,
216			port_panel = PortPanel,
217			ets_panel = EtsPanel,
218			timer_panel = TimerPanel,
219			sched_panel = SchedPanel,
220			fun_panel = FunPanel,
221			atom_panel = AtomPanel,
222			dist_panel = DistPanel,
223			mod_panel = ModPanel,
224			mem_panel = MemPanel,
225                        persistent_panel = PersistentPanel,
226			int_panel = IntPanel,
227			active_tab = GenPid
228		       }}.
229
230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
231
232%%Callbacks
233handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}},
234	     #state{active_tab=Previous} = State) ->
235    case get_active_pid(State) of
236	Previous -> {noreply, State};
237	Pid ->
238	    Pid ! active,
239	    {noreply, State#state{active_tab=Pid}}
240    end;
241
242handle_event(#wx{event = #wxClose{}}, State) ->
243    {stop, normal, State};
244
245handle_event(#wx{id = ?wxID_OPEN,
246		 event = #wxCommand{type = command_menu_selected}},
247	     State) ->
248    NewState =
249	case load_dump(State#state.frame,undefined) of
250	    {ok,File} ->
251		Panels = [State#state.gen_panel,
252			  State#state.pro_panel,
253			  State#state.port_panel,
254			  State#state.ets_panel,
255			  State#state.timer_panel,
256			  State#state.fun_panel,
257			  State#state.atom_panel,
258			  State#state.dist_panel,
259			  State#state.mod_panel,
260			  State#state.mem_panel,
261			  State#state.persistent_panel,
262			  State#state.int_panel],
263		_ = [wx_object:call(Panel,new_dump) || Panel<-Panels],
264		wxNotebook:setSelection(State#state.notebook,0),
265		observer_lib:destroy_progress_dialog(),
266		State#state{file=File};
267	    error ->
268		State
269    end,
270    {noreply,NewState};
271
272handle_event(#wx{id = ?wxID_EXIT,
273		 event = #wxCommand{type = command_menu_selected}},
274	     State) ->
275    {stop, normal, State};
276
277handle_event(#wx{id = HelpId,
278		 event = #wxCommand{type = command_menu_selected}},
279	     State) when HelpId==?wxID_HELP; HelpId==?ID_UG; HelpId==?ID_HOWTO ->
280    Help = get_help_doc(HelpId),
281    wx_misc:launchDefaultBrowser(Help) orelse
282	create_txt_dialog(State#state.frame,
283			  "Could not launch browser: ~n " ++ Help,
284			  "Error", ?wxICON_ERROR),
285    {noreply, State};
286
287handle_event(#wx{id = ?wxID_ABOUT,
288		 event = #wxCommand{type = command_menu_selected}},
289	     State = #state{frame=Frame}) ->
290    AboutString = "Display information from an erlang crash dump",
291    Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP},
292	     {caption, "About"}],
293    wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)),
294    {noreply, State};
295
296handle_event(Event, State) ->
297    Pid = get_active_pid(State),
298    Pid ! Event,
299    {noreply, State}.
300
301handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) ->
302    wxTextCtrl:clear(SB),
303    wxTextCtrl:writeText(SB, Msg),
304    {noreply, State};
305
306handle_cast(_Cast, State) ->
307    {noreply, State}.
308
309handle_call({get_attrib, Attrib}, _From, State) ->
310    {reply, get(Attrib), State};
311
312handle_call(_Msg, _From, State) ->
313    {reply, ok, State}.
314
315handle_info({'EXIT', Pid, normal}, #state{server=Pid}=State) ->
316    {stop, normal, State};
317
318handle_info({'EXIT', Pid, _Reason}, State) ->
319    io:format("Child (~s) crashed exiting:  ~p ~tp~n",
320	      [pid2panel(Pid, State), Pid,_Reason]),
321    {stop, normal, State};
322
323handle_info(_Info, State) ->
324    {noreply, State}.
325
326terminate(_Reason, #state{frame = Frame}) ->
327    wxFrame:destroy(Frame),
328    wx:destroy(),
329    crashdump_viewer:stop(),
330    ok.
331
332code_change(_, _, State) ->
333    {ok, State}.
334
335%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
336
337add_page(Notebook,Title,Callback,Extra) ->
338    Panel = Callback:start_link(Notebook, Extra),
339    wxNotebook:addPage(Notebook, Panel, Title, []),
340    Panel.
341
342create_txt_dialog(Frame, Msg, Title, Style) ->
343    MD = wxMessageDialog:new(Frame, Msg, [{style, Style}]),
344    wxMessageDialog:setTitle(MD, Title),
345    wxDialog:showModal(MD),
346    wxDialog:destroy(MD).
347
348check_page_title(Notebook) ->
349    Selection = wxNotebook:getSelection(Notebook),
350    wxNotebook:getPageText(Notebook, Selection).
351
352get_active_pid(#state{notebook=Notebook, gen_panel=Gen, pro_panel=Pro,
353		      port_panel=Ports, ets_panel=Ets, timer_panel=Timers,
354		      fun_panel=Funs, atom_panel=Atoms, dist_panel=Dist,
355		      mod_panel=Mods, mem_panel=Mem, persistent_panel=Persistent,
356                      int_panel=Int, sched_panel=Sched
357		     }) ->
358    Panel = case check_page_title(Notebook) of
359		?GEN_STR -> Gen;
360		?PRO_STR -> Pro;
361		?PORT_STR -> Ports;
362		?ETS_STR -> Ets;
363		?TIMER_STR -> Timers;
364		?SCHEDULER_STR -> Sched;
365		?FUN_STR -> Funs;
366		?ATOM_STR -> Atoms;
367		?DIST_STR -> Dist;
368		?MOD_STR -> Mods;
369		?MEM_STR -> Mem;
370		?PERSISTENT_STR -> Persistent;
371		?INT_STR -> Int
372	    end,
373    wx_object:get_pid(Panel).
374
375pid2panel(Pid, #state{gen_panel=Gen, pro_panel=Pro, port_panel=Ports,
376		      ets_panel=Ets, timer_panel=Timers, fun_panel=Funs,
377		      atom_panel=Atoms, dist_panel=Dist, mod_panel=Mods,
378		      mem_panel=Mem, persistent_panel=Persistent, int_panel=Int}) ->
379    case Pid of
380	Gen -> ?GEN_STR;
381	Pro -> ?PRO_STR;
382	Ports -> ?PORT_STR;
383	Ets -> ?ETS_STR;
384	Timers -> ?TIMER_STR;
385	Funs -> ?FUN_STR;
386	Atoms -> ?ATOM_STR;
387	Dist -> ?DIST_STR;
388	Mods -> ?MOD_STR;
389	Mem -> ?MEM_STR;
390        ?PERSISTENT_STR -> Persistent;
391	Int -> ?INT_STR;
392	_ -> "unknown"
393    end.
394
395default_menus() ->
396    Open  = #create_menu{id = ?wxID_OPEN, text = "Open new crash dump"},
397    Quit  = #create_menu{id = ?wxID_EXIT, text = "Quit"},
398    About = #create_menu{id = ?wxID_ABOUT, text = "About"},
399    Help  = #create_menu{id = ?wxID_HELP},
400    UG    = #create_menu{id = ?ID_UG, text = "Crashdump viewer user's guide"},
401    Howto = #create_menu{id = ?ID_HOWTO, text = "How to interpret crash dump"},
402    case os:type() =:= {unix, darwin} of
403	false ->
404	    FileMenu = {"File", [Open,Quit]},
405	    HelpMenu = {"Help", [About,Help,UG,Howto]},
406	    [FileMenu, HelpMenu];
407	true ->
408	    %% On Mac quit and about will be moved to the "default' place
409	    %% automagicly, so just add them to a menu that always exist.
410	    [{"File", [Open, About,Quit]}, {"&Help", [Help,UG,Howto]}]
411    end.
412
413
414load_dump(Frame,undefined) ->
415    FD  =  wxFileDialog:new(wx:null(),
416			    [{style,?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]),
417    case wxFileDialog:showModal(FD) of
418	?wxID_OK ->
419	    Path = wxFileDialog:getPath(FD),
420	    wxDialog:destroy(FD),
421	    load_dump(Frame,Path);
422	_ ->
423	    wxDialog:destroy(FD),
424	    error
425    end;
426load_dump(Frame,FileName) ->
427    case maybe_warn_filename(FileName) of
428        continue ->
429            do_load_dump(Frame,FileName);
430        stop ->
431            error
432    end.
433
434do_load_dump(Frame,FileName) ->
435    ok = observer_lib:display_progress_dialog(wx:null(),
436                                              "Crashdump Viewer",
437					      "Loading crashdump"),
438    crashdump_viewer:read_file(FileName),
439    case observer_lib:wait_for_progress() of
440	ok    ->
441	    %% Set window title
442	    T1 = "Crashdump Viewer: ",
443            FileLength = string:length(FileName),
444	    Title =
445		if FileLength > 70 ->
446			T1 ++ filename:basename(FileName);
447		   true ->
448			T1 ++ FileName
449		end,
450	    wxFrame:setTitle(Frame, Title),
451	    {ok,FileName};
452	error ->
453	    error
454    end.
455
456maybe_warn_filename(FileName) ->
457    case os:getenv("ERL_CRASH_DUMP_SECONDS")=="0" orelse
458        os:getenv("ERL_CRASH_DUMP_BYTES")=="0" of
459        true ->
460            continue;
461        false ->
462            DumpName = case os:getenv("ERL_CRASH_DUMP") of
463                           false -> filename:absname("erl_crash.dump");
464                           Name -> filename:absname(Name)
465                       end,
466            case filename:absname(FileName) of
467                DumpName ->
468                    Warning =
469                        "WARNING: the current crashdump might be overwritten "
470                        "if the crashdump_viewer node crashes.\n\n"
471                        "Renaming the file before inspecting it will "
472                        "remove the problem.\n\n"
473                        "Do you want to continue?",
474                    case observer_lib:display_yes_no_dialog(Warning) of
475                        ?wxID_YES -> continue;
476                        ?wxID_NO -> stop
477                    end;
478                _ ->
479                    continue
480            end
481    end.
482
483%%%-----------------------------------------------------------------
484%%% Find help document (HTML files)
485get_help_doc(HelpId) ->
486    Internal = get_internal_help_doc(HelpId),
487    case filelib:is_file(Internal) of
488	true -> Internal;
489	false -> get_external_help_doc(HelpId)
490    end.
491
492get_internal_help_doc(?ID_HOWTO) ->
493    filename:join(erts_doc_dir(),help_file(?ID_HOWTO));
494get_internal_help_doc(HelpId) ->
495    filename:join(observer_doc_dir(),help_file(HelpId)).
496
497get_external_help_doc(?ID_HOWTO) ->
498    filename:join("http://www.erlang.org/doc/apps/erts",help_file(?ID_HOWTO));
499get_external_help_doc(HelpId) ->
500    filename:join("http://www.erlang.org/doc/apps/observer",help_file(HelpId)).
501
502observer_doc_dir() ->
503    filename:join([code:lib_dir(observer),"doc","html"]).
504
505erts_doc_dir() ->
506    ErtsVsn = erlang:system_info(version),
507    RootDir = code:root_dir(),
508    VsnErtsDir = filename:join(RootDir,"erts-"++ErtsVsn),
509    DocDir = filename:join(["doc","html"]),
510    case filelib:is_dir(VsnErtsDir) of
511	true ->
512	    filename:join(VsnErtsDir,DocDir);
513	false ->
514	    %% So this can be run in source tree
515	    filename:join([RootDir,"erts",DocDir])
516    end.
517
518help_file(?wxID_HELP)  -> "crashdump_help.html";
519help_file(?ID_UG)    -> "crashdump_ug.html";
520help_file(?ID_HOWTO) -> "crash_dump.html".
521