1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2011-2021. 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(observer_wx). 20 21-behaviour(wx_object). 22 23-export([start/0, stop/0]). 24-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/0, 25 get_scale/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). 26 27-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, 28 handle_call/3, handle_info/2, check_page_title/1]). 29 30%% Includes 31-include_lib("wx/include/wx.hrl"). 32 33-include("observer_defs.hrl"). 34 35%% Defines 36 37-define(ID_PING, 1). 38-define(ID_CONNECT, 2). 39-define(ID_NOTEBOOK, 3). 40-define(ID_CDV, 4). 41-define(ID_LOGVIEW, 5). 42 43-define(FIRST_NODES_MENU_ID, 1000). 44-define(LAST_NODES_MENU_ID, 2000). 45 46-define(TRACE_STR, "Trace Overview"). 47-define(ALLOC_STR, "Memory Allocators"). 48 49%% Records 50-record(state, 51 {frame, 52 menubar, 53 menus = [], 54 status_bar, 55 notebook, 56 main_panel, 57 panels, 58 active_tab, 59 node, 60 nodes, 61 prev_node="", 62 log = false, 63 reply_to=false, 64 config 65 }). 66 67start() -> 68 case wx_object:start(?MODULE, [], []) of 69 Err = {error, _} -> Err; 70 _Obj -> ok 71 end. 72 73stop() -> 74 wx_object:call(observer, stop). 75 76create_menus(Object, Menus) when is_list(Menus) -> 77 wx_object:call(Object, {create_menus, Menus}). 78 79get_attrib(What) -> 80 wx_object:call(observer, {get_attrib, What}). 81 82set_status(What) -> 83 wx_object:cast(observer, {status_bar, What}). 84 85get_tracer() -> 86 wx_object:call(observer, get_tracer). 87 88get_active_node() -> 89 wx_object:call(observer, get_active_node). 90 91get_menubar() -> 92 wx_object:call(observer, get_menubar). 93 94get_scale() -> 95 ScaleStr = os:getenv("OBSERVER_SCALE", "1"), 96 try list_to_integer(ScaleStr) of 97 Scale when Scale < 1 -> 1; 98 Scale -> Scale 99 catch _:_ -> 100 1 101 end. 102 103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 104 105init(_Args) -> 106 %% put(debug, true), 107 register(observer, self()), 108 wx:new(), 109 catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), 110 Scale = get_scale(), 111 Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer", 112 [{size, {Scale * 850, Scale * 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]), 113 IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"), 114 Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), 115 wxFrame:setIcon(Frame, Icon), 116 wxIcon:destroy(Icon), 117 118 State = #state{frame = Frame}, 119 UpdState = setup(State), 120 net_kernel:monitor_nodes(true), 121 process_flag(trap_exit, true), 122 {Frame, UpdState}. 123 124setup(#state{frame = Frame} = State) -> 125 %% Setup Menubar & Menus 126 Config = load_config(), 127 Cnf = fun(Who) -> 128 proplists:get_value(Who, Config, #{}) 129 end, 130 MenuBar = wxMenuBar:new(), 131 132 {Nodes, NodeMenus} = get_nodes(), 133 DefMenus = default_menus(NodeMenus), 134 observer_lib:create_menus(DefMenus, MenuBar, default), 135 136 wxFrame:setMenuBar(Frame, MenuBar), 137 138 %% Setup panels 139 Panel = wxPanel:new(Frame, []), 140 Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), 141 142 %% System Panel 143 SysPanel = observer_sys_wx:start_link(Notebook, self(), Cnf(sys_panel)), 144 wxNotebook:addPage(Notebook, SysPanel, "System", []), 145 146 %% Setup sizer create early to get it when window shows 147 MainSizer = wxBoxSizer:new(?wxVERTICAL), 148 149 wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), 150 wxPanel:setSizer(Panel, MainSizer), 151 152 StatusBar = wxStatusBar:new(Frame), 153 wxFrame:setStatusBar(Frame, StatusBar), 154 wxFrame:setTitle(Frame, atom_to_list(node())), 155 wxStatusBar:setStatusText(StatusBar, atom_to_list(node())), 156 157 wxNotebook:connect(Notebook, command_notebook_page_changed, 158 [{skip, true}, {id, ?ID_NOTEBOOK}]), 159 wxFrame:connect(Frame, close_window, []), 160 wxMenu:connect(Frame, command_menu_selected), 161 wxFrame:show(Frame), 162 163 %% Freeze and thaw is buggy currently 164 DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9] 165 orelse element(1, os:type()) =:= win32, 166 DoFreeze andalso wxWindow:freeze(Panel), 167 %% I postpone the creation of the other tabs so they can query/use 168 %% the window size 169 170 %% Perf Viewer Panel 171 PerfPanel = observer_perf_wx:start_link(Notebook, self(), Cnf(perf_panel)), 172 wxNotebook:addPage(Notebook, PerfPanel, "Load Charts", []), 173 174 %% Memory Allocator Viewer Panel 175 AllcPanel = observer_alloc_wx:start_link(Notebook, self(), Cnf(allc_panel)), 176 wxNotebook:addPage(Notebook, AllcPanel, ?ALLOC_STR, []), 177 178 %% App Viewer Panel 179 AppPanel = observer_app_wx:start_link(Notebook, self(), Cnf(app_panel)), 180 wxNotebook:addPage(Notebook, AppPanel, "Applications", []), 181 182 %% Process Panel 183 ProPanel = observer_pro_wx:start_link(Notebook, self(), Cnf(pro_panel)), 184 wxNotebook:addPage(Notebook, ProPanel, "Processes", []), 185 186 %% Port Panel 187 PortPanel = observer_port_wx:start_link(Notebook, self(), Cnf(port_panel)), 188 wxNotebook:addPage(Notebook, PortPanel, "Ports", []), 189 190 %% Socket Panel 191 SockPanel = observer_sock_wx:start_link(Notebook, 192 self(), 193 Cnf(sock_panel)), 194 wxNotebook:addPage(Notebook, SockPanel, "Sockets", []), 195 196 %% Table Viewer Panel 197 TVPanel = observer_tv_wx:start_link(Notebook, self(), Cnf(tv_panel)), 198 wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []), 199 200 %% Trace Viewer Panel 201 TracePanel = observer_trace_wx:start_link(Notebook, self(), Cnf(trace_panel)), 202 wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), 203 204 %% Force redraw (windows needs it) 205 wxWindow:refresh(Panel), 206 DoFreeze andalso wxWindow:thaw(Panel), 207 208 wxFrame:raise(Frame), 209 wxFrame:setFocus(Frame), 210 211 SysPid = wx_object:get_pid(SysPanel), 212 SysPid ! {active, node()}, 213 Panels = 214 [{sys_panel, SysPanel, "System"}, %% In order 215 {perf_panel, PerfPanel, "Load Charts"}, 216 {allc_panel, AllcPanel, ?ALLOC_STR}, 217 {app_panel, AppPanel, "Applications"}, 218 {pro_panel, ProPanel, "Processes"}, 219 {port_panel, PortPanel, "Ports"}, 220 %% if (SockPanel =:= undefined) -> []; 221 %% true -> 222 %% [{sock_panel, SockPanel, "Sockets"}] 223 %% end ++ 224 {sock_panel, SockPanel, "Sockets"}, 225 {tv_panel, TVPanel, "Table Viewer"}, 226 {trace_panel, TracePanel, ?TRACE_STR}], 227 228 UpdState = State#state{main_panel = Panel, 229 notebook = Notebook, 230 menubar = MenuBar, 231 status_bar = StatusBar, 232 active_tab = SysPid, 233 panels = Panels, 234 node = node(), 235 nodes = Nodes 236 }, 237 %% Create resources which we don't want to duplicate 238 SysFont = wxSystemSettings:getFont(?wxSYS_OEM_FIXED_FONT), 239 %% OemFont = wxSystemSettings:getFont(?wxSYS_OEM_FIXED_FONT), 240 %% io:format("Sz sys ~p(~p) oem ~p(~p)~n", 241 %% [wxFont:getPointSize(SysFont), wxFont:isFixedWidth(SysFont), 242 %% wxFont:getPointSize(OemFont), wxFont:isFixedWidth(OemFont)]), 243 Fixed = case wxFont:isFixedWidth(SysFont) of 244 true -> SysFont; 245 false -> %% Sigh 246 SysFontSize = wxFont:getPointSize(SysFont), 247 wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL) 248 end, 249 put({font, fixed}, Fixed), 250 UpdState. 251 252 253%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 254 255%%Callbacks 256handle_event(#wx{event=#wxBookCtrl{type=command_notebook_page_changed, nSel=Next}}, 257 #state{active_tab=Previous, node=Node, panels=Panels, status_bar=SB} = State) -> 258 {_, Obj, _} = lists:nth(Next+1, Panels), 259 case wx_object:get_pid(Obj) of 260 Previous -> 261 {noreply, State}; 262 Pid -> 263 wxStatusBar:setStatusText(SB, ""), 264 Previous ! not_active, 265 Pid ! {active, Node}, 266 {noreply, State#state{active_tab=Pid}} 267 end; 268 269handle_event(#wx{id = ?ID_CDV, event = #wxCommand{type = command_menu_selected}}, State) -> 270 spawn(crashdump_viewer, start, []), 271 {noreply, State}; 272 273handle_event(#wx{event = #wxClose{}}, State) -> 274 stop_servers(State), 275 {noreply, State}; 276 277handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) -> 278 stop_servers(State), 279 {noreply, State}; 280 281handle_event(#wx{id = ?wxID_HELP, event = #wxCommand{type = command_menu_selected}}, State) -> 282 External = "http://www.erlang.org/doc/apps/observer/index.html", 283 Internal = filename:join([code:lib_dir(observer),"doc", "html", "index.html"]), 284 Help = case filelib:is_file(Internal) of 285 true -> Internal; 286 false -> External 287 end, 288 wx_misc:launchDefaultBrowser(Help) orelse 289 create_txt_dialog(State#state.frame, "Could not launch browser: ~n " ++ Help, 290 "Error", ?wxICON_ERROR), 291 {noreply, State}; 292 293handle_event(#wx{id = ?wxID_ABOUT, event = #wxCommand{type = command_menu_selected}}, 294 State = #state{frame=Frame}) -> 295 AboutString = "Observe an erlang system\n" 296 "Authors: Olle Mattson & Magnus Eriksson & Dan Gudmundsson", 297 Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP}, 298 {caption, "About"}], 299 wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)), 300 {noreply, State}; 301 302 303handle_event(#wx{id = ?ID_CONNECT, event = #wxCommand{type = command_menu_selected}}, 304 #state{frame = Frame} = State) -> 305 UpdState = case create_connect_dialog(connect, State) of 306 cancel -> 307 State; 308 {value, [], _, _} -> 309 create_txt_dialog(Frame, "Node must have a name", 310 "Error", ?wxICON_ERROR), 311 State; 312 {value, NodeName, LongOrShort, Cookie} -> %Shortname, 313 try 314 case connect(list_to_atom(NodeName), LongOrShort, list_to_atom(Cookie)) of 315 {ok, set_cookie} -> 316 change_node_view(node(), State); 317 {error, set_cookie} -> 318 create_txt_dialog(Frame, "Could not set cookie", 319 "Error", ?wxICON_ERROR), 320 State; 321 {error, net_kernel, _Reason} -> 322 create_txt_dialog(Frame, "Could not enable node", 323 "Error", ?wxICON_ERROR), 324 State 325 end 326 catch _:_ -> 327 create_txt_dialog(Frame, "Could not enable node", 328 "Error", ?wxICON_ERROR), 329 State 330 end 331 end, 332 {noreply, UpdState}; 333 334handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected}}, 335 #state{frame = Frame} = State) -> 336 UpdState = case create_connect_dialog(ping, State) of 337 cancel -> State; 338 {value, Value} when is_list(Value) -> 339 try 340 Node = list_to_atom(Value), 341 case net_adm:ping(Node) of 342 pang -> 343 create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), 344 State#state{prev_node=Value}; 345 pong -> 346 State1 = change_node_view(Node, State), 347 State1#state{prev_node=Value} 348 end 349 catch _:_ -> 350 create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), 351 State#state{prev_node=Value} 352 end 353 end, 354 {noreply, UpdState}; 355 356handle_event(#wx{id = ?ID_LOGVIEW, event = #wxCommand{type = command_menu_selected}}, 357 #state{frame = Frame, log = PrevLog, node = Node} = State) -> 358 try 359 ok = ensure_sasl_started(Node), 360 ok = ensure_mf_h_handler_used(Node), 361 ok = ensure_rb_mode(Node, PrevLog), 362 case PrevLog of 363 false -> 364 rpc:block_call(Node, rb, start, []), 365 set_status("Observer - " ++ atom_to_list(Node) ++ " (rb_server started)"), 366 {noreply, State#state{log=true}}; 367 true -> 368 rpc:block_call(Node, rb, stop, []), 369 set_status("Observer - " ++ atom_to_list(Node) ++ " (rb_server stopped)"), 370 {noreply, State#state{log=false}} 371 end 372 catch 373 throw:Reason -> 374 create_txt_dialog(Frame, Reason, "Log view status", ?wxICON_ERROR), 375 {noreply, State} 376 end; 377 378handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}}, 379 #state{nodes= Ns , node = PrevNode, log = PrevLog} = State) 380 when Id > ?FIRST_NODES_MENU_ID, Id < ?LAST_NODES_MENU_ID -> 381 Node = lists:nth(Id - ?FIRST_NODES_MENU_ID, Ns), 382 %% Close rb_server only if another node than current one selected 383 LState = case PrevLog of 384 true -> case Node == PrevNode of 385 false -> rpc:block_call(PrevNode, rb, stop, []), 386 State#state{log=false} ; 387 true -> State 388 end; 389 false -> State 390 end, 391 {noreply, change_node_view(Node, LState)}; 392 393handle_event(Event, #state{active_tab=Pid} = State) -> 394 Pid ! Event, 395 {noreply, State}. 396 397handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) -> 398 wxStatusBar:setStatusText(SB, Msg), 399 {noreply, State}; 400 401handle_cast(_Cast, State) -> 402 {noreply, State}. 403 404handle_call({create_menus, TabMenus}, _From, 405 State = #state{menubar=MenuBar, menus=PrevTabMenus}) -> 406 if TabMenus == PrevTabMenus -> ignore; 407 true -> 408 wx:batch(fun() -> 409 clean_menus(PrevTabMenus, MenuBar), 410 observer_lib:create_menus(TabMenus, MenuBar, plugin) 411 end) 412 end, 413 {reply, ok, State#state{menus=TabMenus}}; 414 415handle_call({get_attrib, Attrib}, _From, State) -> 416 {reply, get(Attrib), State}; 417 418handle_call(get_tracer, _From, State=#state{panels=Panels}) -> 419 {_, TraceP, _} = lists:keyfind(trace_panel, 1, Panels), 420 {reply, TraceP, State}; 421 422handle_call(get_active_node, _From, State=#state{node=Node}) -> 423 {reply, Node, State}; 424 425handle_call(get_menubar, _From, State=#state{menubar=MenuBar}) -> 426 {reply, MenuBar, State}; 427 428handle_call(stop, From, State) -> 429 stop_servers(State), 430 {noreply, State#state{reply_to=From}}; 431 432handle_call(log_status, _From, State) -> 433 {reply, State#state.log, State}; 434 435handle_call(_Msg, _From, State) -> 436 {reply, ok, State}. 437 438handle_info({nodeup, _Node}, State) -> 439 State2 = update_node_list(State), 440 {noreply, State2}; 441 442handle_info({nodedown, Node}, 443 #state{frame = Frame} = State) -> 444 State2 = case Node =:= State#state.node of 445 true -> 446 change_node_view(node(), State); 447 false -> 448 State 449 end, 450 State3 = update_node_list(State2), 451 Msg = ["Node down: " | atom_to_list(Node)], 452 create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), 453 {noreply, State3}; 454 455handle_info({open_link, Id0}, State = #state{panels=Panels,frame=Frame}) -> 456 Id = case Id0 of 457 [_|_] -> try list_to_pid(Id0) catch _:_ -> Id0 end; 458 _ -> Id0 459 end, 460 %% Forward to process tab 461 case Id of 462 Pid when is_pid(Pid) -> 463 {pro_panel, ProcViewer, _} = lists:keyfind(pro_panel, 1, Panels), 464 wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid}; 465 "#Port" ++ _ = Port -> 466 {port_panel, PortViewer, _} = lists:keyfind(port_panel, 1, Panels), 467 wx_object:get_pid(PortViewer) ! {portinfo_open, Port}; 468 _ -> 469 Msg = io_lib:format("Information about ~p is not available or implemented",[Id]), 470 Info = wxMessageDialog:new(Frame, Msg), 471 wxMessageDialog:showModal(Info), 472 wxMessageDialog:destroy(Info) 473 end, 474 {noreply, State}; 475 476handle_info({get_debug_info, From}, State = #state{notebook=Notebook, active_tab=Pid}) -> 477 From ! {observer_debug, wx:get_env(), Notebook, Pid}, 478 {noreply, State}; 479 480handle_info({'EXIT', Pid, Reason}, State) -> 481 case Reason of 482 normal -> 483 {noreply, State}; 484 _ -> 485 io:format("Observer: Child (~s) crashed exiting: ~p ~tp~n", 486 [pid2panel(Pid, State), Pid, Reason]), 487 {stop, normal, State} 488 end; 489 490handle_info({stop, Me}, State) when Me =:= self() -> 491 {stop, normal, State}; 492 493handle_info(_Info, State) -> 494 {noreply, State}. 495 496stop_servers(#state{node=Node, log=LogOn, panels=Panels} = _State) -> 497 LogOn andalso rpc:block_call(Node, rb, stop, []), 498 Me = self(), 499 save_config(Panels), 500 Stop = fun() -> 501 try 502 _ = [wx_object:stop(Panel) || {_, Panel, _} <- Panels], 503 ok 504 catch _:_ -> ok 505 end, 506 Me ! {stop, Me} 507 end, 508 spawn(Stop). 509 510terminate(_Reason, #state{frame = Frame, reply_to=From}) -> 511 wxFrame:destroy(Frame), 512 wx:destroy(), 513 case From of 514 false -> ignore; 515 _ -> gen_server:reply(From, ok) 516 end, 517 ok. 518 519load_config() -> 520 case file:consult(config_file()) of 521 {ok, Config} -> Config; 522 _ -> [] 523 end. 524 525save_config(Panels) -> 526 Configs = [{Name, wx_object:call(Panel, get_config)} || {Name, Panel, _} <- Panels], 527 File = config_file(), 528 case filelib:ensure_dir(File) of 529 ok -> 530 Format = [io_lib:format("~tp.~n",[Conf]) || Conf <- Configs], 531 _ = file:write_file(File, Format); 532 _ -> 533 ignore 534 end. 535 536config_file() -> 537 Dir = filename:basedir(user_config, "erl_observer"), 538 filename:join(Dir, "config.txt"). 539 540code_change(_, _, State) -> 541 {ok, State}. 542 543%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 544 545try_rpc(Node, Mod, Func, Args) -> 546 case 547 rpc:call(Node, Mod, Func, Args) of 548 {badrpc, Reason} -> 549 error_logger:error_report([{node, Node}, 550 {call, {Mod, Func, Args}}, 551 {reason, {badrpc, Reason}}]), 552 observer ! {nodedown, Node}, 553 error({badrpc, Reason}); 554 Res -> 555 Res 556 end. 557 558return_to_localnode(Frame, Node) -> 559 case node() =/= Node of 560 true -> 561 create_txt_dialog(Frame, "Error occured on remote node", 562 "Error", ?wxICON_ERROR), 563 disconnect_node(Node); 564 false -> 565 ok 566 end. 567 568create_txt_dialog(Frame, Msg, Title, Style) -> 569 MD = wxMessageDialog:new(Frame, Msg, [{style, Style}, {caption,Title}]), 570 wxDialog:showModal(MD), 571 wxDialog:destroy(MD). 572 573connect(NodeName, 0, Cookie) -> 574 connect2(NodeName, shortnames, Cookie); 575connect(NodeName, 1, Cookie) -> 576 connect2(NodeName, longnames, Cookie). 577 578connect2(NodeName, Opts, Cookie) -> 579 case net_adm:names() of 580 {ok, _} -> %% Epmd is running 581 ok; 582 {error, address} -> 583 Epmd = os:find_executable("epmd"), 584 os:cmd(Epmd) 585 end, 586 case net_kernel:start([NodeName, Opts]) of 587 {ok, _} -> 588 case is_alive() of 589 true -> 590 erlang:set_cookie(node(), Cookie), 591 {ok, set_cookie}; 592 false -> 593 {error, set_cookie} 594 end; 595 {error, Reason} -> 596 {error, net_kernel, Reason} 597 end. 598 599change_node_view(Node, #state{active_tab=Tab} = State) -> 600 Tab ! not_active, 601 Tab ! {active, Node}, 602 StatusText = ["Observer - " | atom_to_list(Node)], 603 wxFrame:setTitle(State#state.frame, StatusText), 604 wxStatusBar:setStatusText(State#state.status_bar, StatusText), 605 State#state{node = Node}. 606 607check_page_title(Notebook) -> 608 Selection = wxNotebook:getSelection(Notebook), 609 wxNotebook:getPageText(Notebook, Selection). 610 611pid2panel(Pid, #state{panels=Panels}) -> 612 PanelPids = [{Name, wx_object:get_pid(Obj)} || {Name, Obj, _} <- Panels], 613 case lists:keyfind(Pid, 2, PanelPids) of 614 false -> "unknown"; 615 {Name,_} -> Name 616 end. 617 618create_connect_dialog(ping, #state{frame = Frame, prev_node=Prev}) -> 619 Dialog = wxTextEntryDialog:new(Frame, "Connect to node", [{value, Prev}]), 620 case wxDialog:showModal(Dialog) of 621 ?wxID_OK -> 622 Value = wxTextEntryDialog:getValue(Dialog), 623 wxDialog:destroy(Dialog), 624 {value, Value}; 625 ?wxID_CANCEL -> 626 wxDialog:destroy(Dialog), 627 cancel 628 end; 629create_connect_dialog(connect, #state{frame = Frame}) -> 630 Dialog = wxDialog:new(Frame, ?wxID_ANY, "Distribute node", 631 [{style, ?wxDEFAULT_FRAME_STYLE bor ?wxRESIZE_BORDER}]), 632 633 VSizer = wxBoxSizer:new(?wxVERTICAL), 634 635 Choices = ["Short name", "Long name"], 636 RadioBox = wxRadioBox:new(Dialog, 1, "", ?wxDefaultPosition, ?wxDefaultSize, 637 Choices, [{majorDim, 2}, {style, ?wxHORIZONTAL}]), 638 639 NameText = wxStaticText:new(Dialog, ?wxID_ANY, "Node name: "), 640 NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {300,-1}}]), 641 wxTextCtrl:setValue(NameCtrl, "observer"), 642 CookieText = wxStaticText:new(Dialog, ?wxID_ANY, "Secret cookie: "), 643 CookieCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY,[{style, ?wxTE_PASSWORD}]), 644 645 BtnSizer = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 646 Dir = ?wxLEFT bor ?wxRIGHT bor ?wxDOWN, 647 Flags = [{flag, ?wxEXPAND bor Dir bor ?wxALIGN_CENTER_VERTICAL}, {border, 5}], 648 wxSizer:add(VSizer, RadioBox, Flags), 649 wxSizer:addSpacer(VSizer, 10), 650 wxSizer:add(VSizer, NameText, [{flag, ?wxLEFT}, {border, 5}]), 651 wxSizer:add(VSizer, NameCtrl, Flags), 652 wxSizer:addSpacer(VSizer, 10), 653 wxSizer:add(VSizer, CookieText, [{flag, ?wxLEFT}, {border, 5}]), 654 wxSizer:add(VSizer, CookieCtrl, Flags), 655 wxSizer:addSpacer(VSizer, 10), 656 wxSizer:add(VSizer, BtnSizer, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL},{border, 5}]), 657 658 wxWindow:setSizerAndFit(Dialog, VSizer), 659 wxSizer:setSizeHints(VSizer, Dialog), 660 {ok,[[HomeDir]]} = init:get_argument(home), 661 CookiePath = filename:join(HomeDir, ".erlang.cookie"), 662 DefaultCookie = case filelib:is_file(CookiePath) of 663 true -> 664 {ok, Bin} = file:read_file(CookiePath), 665 binary_to_list(Bin); 666 false -> 667 "" 668 end, 669 wxTextCtrl:setValue(CookieCtrl, DefaultCookie), 670 case wxDialog:showModal(Dialog) of 671 ?wxID_OK -> 672 NameValue = wxTextCtrl:getValue(NameCtrl), 673 NameLngthValue = wxRadioBox:getSelection(RadioBox), 674 CookieValue = wxTextCtrl:getValue(CookieCtrl), 675 wxDialog:destroy(Dialog), 676 {value, NameValue, NameLngthValue, CookieValue}; 677 ?wxID_CANCEL -> 678 wxDialog:destroy(Dialog), 679 cancel 680 end. 681 682default_menus(NodesMenuItems) -> 683 CDV = #create_menu{id = ?ID_CDV, text = "Examine Crashdump"}, 684 Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"}, 685 About = #create_menu{id = ?wxID_ABOUT, text = "About"}, 686 Help = #create_menu{id = ?wxID_HELP}, 687 FileMenu = {"File", [CDV, Quit]}, 688 NodeMenu = case erlang:is_alive() of 689 true -> {"Nodes", NodesMenuItems ++ 690 [#create_menu{id = ?ID_PING, text = "Connect Node"}]}; 691 false -> {"Nodes", NodesMenuItems ++ 692 [#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]} 693 end, 694 LogMenu = {"Log", [#create_menu{id = ?ID_LOGVIEW, text = "Toggle log view"}]}, 695 case os:type() =:= {unix, darwin} of 696 false -> 697 FileMenu = {"File", [CDV, Quit]}, 698 HelpMenu = {"Help", [About,Help]}, 699 [FileMenu, NodeMenu, LogMenu, HelpMenu]; 700 true -> 701 %% On Mac quit and about will be moved to the "default' place 702 %% automagicly, so just add them to a menu that always exist. 703 %% But not to the help menu for some reason 704 705 {Tag, Menus} = FileMenu, 706 [{Tag, Menus ++ [Quit,About]}, NodeMenu, LogMenu, {"&Help", [Help]}] 707 end. 708 709clean_menus(Menus, MenuBar) -> 710 remove_menu_items(Menus, MenuBar). 711 712remove_menu_items([{MenuStr = "File", Menus}|Rest], MenuBar) -> 713 case wxMenuBar:findMenu(MenuBar, MenuStr) of 714 ?wxNOT_FOUND -> 715 remove_menu_items(Rest, MenuBar); 716 MenuId -> 717 Menu = wxMenuBar:getMenu(MenuBar, MenuId), 718 Items = [wxMenu:findItem(Menu, Tag) || #create_menu{text=Tag} <- Menus], 719 [wxMenu:delete(Menu, MItem) || MItem <- Items], 720 remove_menu_items(Rest, MenuBar) 721 end; 722remove_menu_items([{"Nodes", _}|_], _MB) -> 723 ok; 724remove_menu_items([{Tag, _Menus}|Rest], MenuBar) -> 725 case wxMenuBar:findMenu(MenuBar, Tag) of 726 ?wxNOT_FOUND -> 727 remove_menu_items(Rest, MenuBar); 728 MenuId -> 729 Menu = wxMenuBar:getMenu(MenuBar, MenuId), 730 wxMenuBar:remove(MenuBar, MenuId), 731 Items = wxMenu:getMenuItems(Menu), 732 [wxMenu:'Destroy'(Menu, Item) || Item <- Items], 733 wxMenu:destroy(Menu), 734 remove_menu_items(Rest, MenuBar) 735 end; 736remove_menu_items([], _MB) -> 737 ok. 738 739get_nodes() -> 740 Nodes0 = case erlang:is_alive() of 741 false -> []; 742 true -> 743 case net_adm:names() of 744 {error, _} -> nodes(); 745 {ok, Names} -> 746 epmd_nodes(Names) ++ nodes() 747 end 748 end, 749 Nodes = lists:usort(Nodes0), 750 {_, Menues} = 751 lists:foldl(fun(Node, {Id, Acc}) when Id < ?LAST_NODES_MENU_ID -> 752 {Id + 1, [#create_menu{id=Id + ?FIRST_NODES_MENU_ID, 753 text=atom_to_list(Node)} | Acc]} 754 end, {1, []}, Nodes), 755 {Nodes, lists:reverse(Menues)}. 756 757epmd_nodes(Names) -> 758 [_, Host] = string:lexemes(atom_to_list(node()),"@"), 759 [list_to_atom(Name ++ [$@|Host]) || {Name, _} <- Names]. 760 761update_node_list(State = #state{menubar=MenuBar}) -> 762 {Nodes, NodesMenuItems} = get_nodes(), 763 NodeMenu = case wxMenuBar:findMenu(MenuBar, "Nodes") of 764 ?wxNOT_FOUND -> 765 Menu = wxMenu:new(), 766 wxMenuBar:append(MenuBar, Menu, "Nodes"), 767 Menu; 768 NodeMenuId -> 769 Menu = wxMenuBar:getMenu(MenuBar, NodeMenuId), 770 wx:foreach(fun(Item) -> wxMenu:'Destroy'(Menu, Item) end, 771 wxMenu:getMenuItems(Menu)), 772 Menu 773 end, 774 775 Index = wx:foldl(fun(Record, Index) -> 776 observer_lib:create_menu_item(Record, NodeMenu, Index) 777 end, 0, NodesMenuItems), 778 779 Dist = case erlang:is_alive() of 780 true -> #create_menu{id = ?ID_PING, text = "Connect node"}; 781 false -> #create_menu{id = ?ID_CONNECT, text = "Enable distribution"} 782 end, 783 observer_lib:create_menu_item(Dist, NodeMenu, Index), 784 State#state{nodes = Nodes}. 785 786ensure_sasl_started(Node) -> 787 %% is sasl started ? 788 Apps = rpc:block_call(Node, application, which_applications, []), 789 case lists:keyfind(sasl, 1, Apps) of 790 false -> throw("Error: sasl application not started."), 791 error; 792 {sasl, _, _} -> ok 793 end. 794 795ensure_mf_h_handler_used(Node) -> 796 %% is log_mf_h used ? 797 Handlers = 798 case rpc:block_call(Node, gen_event, which_handlers, [error_logger]) of 799 {badrpc,{'EXIT',noproc}} -> []; % OTP-21+ and no event handler exists 800 Hs -> Hs 801 end, 802 case lists:any(fun(L)-> L == log_mf_h end, Handlers) of 803 false -> throw("Error: log_mf_h handler not used in sasl."), 804 error; 805 true -> ok 806 end. 807 808ensure_rb_mode(Node, PrevLog) -> 809 ok = ensure_rb_module_loaded(Node), 810 ok = is_rb_compatible(Node), 811 ok = is_rb_server_running(Node, PrevLog), 812 ok. 813 814 815ensure_rb_module_loaded(Node) -> 816 %% Need to ensure that module is loaded in order to detect exported 817 %% functions on interactive nodes 818 case rpc:block_call(Node, code, ensure_loaded, [rb]) of 819 {badrpc, Reason} -> 820 throw("Error: badrpc - " ++ io_lib:format("~tp",[Reason])); 821 {error, Reason} -> 822 throw("Error: rb module load error - " ++ io_lib:format("~tp",[Reason])); 823 {module,rb} -> 824 ok 825 end. 826 827is_rb_compatible(Node) -> 828 %% Simply test that rb:log_list/0 is exported 829 case rpc:block_call(Node, erlang, function_exported, [rb, log_list, 0]) of 830 false -> throw("Error: Node's Erlang release must be at least R16B02."); 831 true -> ok 832 end. 833 834is_rb_server_running(Node, LogState) -> 835 %% If already started, somebody else may use it. 836 %% We cannot use it too, as far log file would be overriden. Not fair. 837 case rpc:block_call(Node, erlang, whereis, [rb_server]) of 838 Pid when is_pid(Pid), (LogState == false) -> 839 throw("Error: rb_server is already started and maybe used by someone."); 840 Pid when is_pid(Pid) -> 841 ok; 842 undefined -> 843 ok 844 end. 845 846 847%% d(F) -> 848%% d(F, []). 849 850%% d(F, A) -> 851%% d(get(debug), F, A). 852 853%% d(true, F, A) -> 854%% io:format("[owx] " ++ F ++ "~n", A); 855%% d(_, _, _) -> 856%% ok. 857 858 859