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