1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2011-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 20-module(observer_procinfo). 21 22-behaviour(wx_object). 23 24-export([start/3]). 25 26-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, 27 handle_call/3, handle_info/2]). 28 29-include_lib("wx/include/wx.hrl"). 30-include("observer_defs.hrl"). 31 32-define(REFRESH, 601). 33-define(SELECT_ALL, 603). 34-define(ID_NOTEBOOK, 604). 35 36-record(state, {parent, 37 frame, 38 notebook, 39 pid, 40 pages=[], 41 expand_table, 42 expand_wins=[] 43 }). 44 45-record(worker, {panel, callback}). 46 47-record(io, {rdata=""}). 48 49start(Process, ParentFrame, Parent) -> 50 wx_object:start_link(?MODULE, [Process, ParentFrame, Parent], []). 51 52%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 53 54init([Pid, ParentFrame, Parent]) -> 55 try 56 Table = ets:new(observer_expand,[set,public]), 57 Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of 58 [] -> io_lib:format("~p",[Pid]); 59 {registered_name, Registered} -> io_lib:format("~tp (~p)",[Registered, Pid]); 60 undefined -> throw(process_undefined) 61 end, 62 Scale = observer_wx:get_scale(), 63 Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title], 64 [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {Scale * 850, Scale * 600}}]), 65 MenuBar = wxMenuBar:new(), 66 create_menus(MenuBar), 67 wxFrame:setMenuBar(Frame, MenuBar), 68 69 Notebook = wxNotebook:new(Frame, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), 70 71 ProcessPage = init_panel(Notebook, "Process Information", [Pid], fun init_process_page/2), 72 MessagePage = init_panel(Notebook, "Messages", [Pid,Table], fun init_message_page/3), 73 DictPage = init_panel(Notebook, "Dictionary", [Pid,Table], fun init_dict_page/3), 74 StackPage = init_panel(Notebook, "Stack Trace", [Pid], fun init_stack_page/2), 75 StatePage = init_panel(Notebook, "State", [Pid,Table], fun init_state_page/3), 76 Ps = case gen_server:call(observer, log_status) of 77 true -> [init_panel(Notebook, "Log", [Pid,Table], fun init_log_page/3)]; 78 false -> [] 79 end, 80 81 wxFrame:connect(Frame, close_window), 82 wxMenu:connect(Frame, command_menu_selected), 83 %% wxNotebook:connect(Notebook, command_notebook_page_changed, [{skip,true}]), 84 wxFrame:show(Frame), 85 {Frame, #state{parent=Parent, 86 pid=Pid, 87 frame=Frame, 88 notebook=Notebook, 89 pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage|Ps], 90 expand_table=Table 91 }} 92 catch error:{badrpc, _} -> 93 observer_wx:return_to_localnode(ParentFrame, node(Pid)), 94 {stop, badrpc}; 95 process_undefined -> 96 observer_lib:display_info_dialog(ParentFrame,"No such alive process"), 97 {stop, normal} 98 end. 99 100init_panel(Notebook, Str, FunArgs, Fun) -> 101 Panel = wxPanel:new(Notebook), 102 Sizer = wxBoxSizer:new(?wxHORIZONTAL), 103 {Window,Callback} = apply(Fun,[Panel|FunArgs]), 104 wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), 105 wxPanel:setSizer(Panel, Sizer), 106 true = wxNotebook:addPage(Notebook, Panel, Str), 107 #worker{panel=Panel, callback=Callback}. 108 109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 110handle_event(#wx{event=#wxClose{type=close_window}}, State) -> 111 {stop, normal, State}; 112 113handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> 114 {stop, normal, State}; 115 116handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages, expand_table=T}=State) -> 117 ets:delete_all_objects(T), 118 try [(W#worker.callback)() || W <- Pages] 119 catch process_undefined -> 120 wxFrame:setTitle(Frame, io_lib:format("*DEAD* ~p",[Pid])) 121 end, 122 {noreply, State}; 123 124handle_event(#wx{obj=MoreEntry,event=#wxMouse{type=left_down},userData={more,More}}, State) -> 125 observer_lib:add_scroll_entries(MoreEntry,More), 126 {noreply, State}; 127 128handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) -> 129 observer ! {open_link, TargetPid}, 130 {noreply, State}; 131 132handle_event(#wx{obj=Obj, event=#wxMouse{type=enter_window}}, State) -> 133 wxStaticText:setForegroundColour(Obj,{0,0,100,255}), 134 {noreply, State}; 135 136handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> 137 wxStaticText:setForegroundColour(Obj,?wxBLUE), 138 {noreply, State}; 139 140handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}}, 141 #state{frame=Frame,expand_table=T,expand_wins=Opened0}=State) -> 142 {Type, Rest} = case Href of 143 "#Term?"++Keys -> {cdv_term_cb, Keys}; 144 "#OBSBinary?"++Keys -> {cdv_bin_cb, Keys}; 145 _ -> {other, Href} 146 end, 147 case Type of 148 other -> 149 observer ! {open_link, Href}, 150 {noreply, State}; 151 Callback -> 152 [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = uri_string:dissect_query(Rest), 153 Id = {obs, {T,{list_to_integer(Key1), 154 list_to_integer(Key2), 155 list_to_integer(Key3)}}}, 156 Opened = 157 case lists:keyfind(Id,1,Opened0) of 158 false -> 159 Win = cdv_detail_wx:start_link(Id,[],Frame,Callback,obs), 160 [{Id,Win}|Opened0]; 161 {_,Win} -> 162 wxFrame:raise(Win), 163 Opened0 164 end, 165 {noreply,State#state{expand_wins=Opened}} 166 end; 167 168handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) -> 169 observer ! {open_link, Info}, 170 {noreply, State}; 171 172handle_event(Event, _State) -> 173 error({unhandled_event, Event}). 174 175handle_info({get_debug_info, From}, State = #state{notebook=Notebook}) -> 176 From ! {procinfo_debug, Notebook}, 177 {noreply, State}; 178handle_info(_Info, State) -> 179 %% io:format("~p: ~p, Handle info: ~tp~n", [?MODULE, ?LINE, Info]), 180 {noreply, State}. 181 182handle_call(Call, From, _State) -> 183 error({unhandled_call, Call, From}). 184 185handle_cast({detail_win_closed,Id}, #state{expand_wins=Opened0}=State) -> 186 Opened = lists:keydelete(Id,1,Opened0), 187 {noreply,State#state{expand_wins=Opened}}; 188 189handle_cast(Cast, _State) -> 190 error({unhandled_cast, Cast}). 191 192terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame,expand_table=T}) -> 193 T=/=undefined andalso ets:delete(T), 194 Parent ! {procinfo_menu_closed, Pid}, 195 case Frame of 196 undefined -> ok; 197 _ -> wxFrame:destroy(Frame) 198 end, 199 ok. 200 201code_change(_, _, State) -> 202 {ok, State}. 203 204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 205init_process_page(Panel, Pid) -> 206 WSz = observer_wx:try_rpc(node(Pid), erlang, system_info,[wordsize]), 207 Fields0 = process_info_fields(Pid, WSz), 208 {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields0), 209 {FPanel, fun() -> 210 Fields = process_info_fields(Pid, WSz), 211 observer_lib:update_info(UpFields, Fields) 212 end}. 213 214 215init_message_page(Parent, Pid, Table) -> 216 Win = observer_lib:html_window(Parent), 217 Cs = observer_lib:colors(Parent), 218 Update = fun() -> 219 case observer_wx:try_rpc(node(Pid), erlang, process_info, 220 [Pid, messages]) 221 of 222 {messages, Messages} -> 223 Html = observer_html_lib:expandable_term("Message Queue", Messages, 224 Table, Cs), 225 wxHtmlWindow:setPage(Win, Html); 226 _ -> 227 throw(process_undefined) 228 end 229 end, 230 Update(), 231 {Win, Update}. 232 233init_dict_page(Parent, Pid, Table) -> 234 Win = observer_lib:html_window(Parent), 235 Cs = observer_lib:colors(Parent), 236 Update = fun() -> 237 case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) 238 of 239 {dictionary,Dict} -> 240 Html = observer_html_lib:expandable_term("Dictionary", Dict, Table, Cs), 241 wxHtmlWindow:setPage(Win, Html); 242 _ -> 243 throw(process_undefined) 244 end 245 end, 246 Update(), 247 {Win, Update}. 248 249init_stack_page(Parent, Pid) -> 250 LCtrl = wxListCtrl:new(Parent, [{style, ?wxLC_REPORT bor ?wxLC_HRULES}]), 251 Li = wxListItem:new(), 252 Scale = observer_wx:get_scale(), 253 wxListItem:setText(Li, "Module:Function/Arg"), 254 wxListCtrl:insertColumn(LCtrl, 0, Li), 255 wxListCtrl:setColumnWidth(LCtrl, 0, Scale * 300), 256 wxListItem:setText(Li, "File:LineNumber"), 257 wxListCtrl:insertColumn(LCtrl, 1, Li), 258 wxListCtrl:setColumnWidth(LCtrl, 1, Scale * 300), 259 wxListItem:destroy(Li), 260 Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), 261 Odd = observer_lib:mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), 262 Update = fun() -> 263 case observer_wx:try_rpc(node(Pid), erlang, process_info, 264 [Pid, current_stacktrace]) 265 of 266 {current_stacktrace,RawBt} -> 267 wxListCtrl:deleteAllItems(LCtrl), 268 wx:foldl(fun({M, F, A, Info}, Row) -> 269 _Item = wxListCtrl:insertItem(LCtrl, Row, ""), 270 ?EVEN(Row) orelse 271 wxListCtrl:setItemBackgroundColour(LCtrl, Row, Odd), 272 wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})), 273 FileLine = case Info of 274 [{file,File},{line,Line}] -> 275 io_lib:format("~ts:~w", [File,Line]); 276 _ -> 277 [] 278 end, 279 wxListCtrl:setItem(LCtrl, Row, 1, FileLine), 280 Row+1 281 end, 0, RawBt); 282 _ -> 283 throw(process_undefined) 284 end 285 end, 286 Resize = fun(#wx{event=#wxSize{size={W,_}}},Ev) -> 287 wxEvent:skip(Ev), 288 observer_lib:set_listctrl_col_size(LCtrl, W) 289 end, 290 wxListCtrl:connect(LCtrl, size, [{callback, Resize}]), 291 Update(), 292 {LCtrl, Update}. 293 294init_state_page(Parent, Pid, Table) -> 295 Win = observer_lib:html_window(Parent), 296 Cs = observer_lib:colors(Parent), 297 Update = fun() -> 298 StateInfo = fetch_state_info(Pid), 299 Html = observer_html_lib:expandable_term("ProcState", StateInfo, Table, Cs), 300 wxHtmlWindow:setPage(Win, Html) 301 end, 302 Update(), 303 {Win, Update}. 304 305fetch_state_info(Pid) -> 306 %% First, test if sys:get_status/2 have any chance to return an answer 307 case rpc:call(node(Pid), proc_lib, translate_initial_call, [Pid]) of 308 %% Not a gen process 309 {proc_lib,init_p,5} -> []; 310 %% May be a gen process 311 {M, _F, _A} -> fetch_state_info2(Pid, M); 312 _ -> throw(process_undefined) 313 end. 314 315fetch_state_info2(Pid, M) -> 316 %% Get the behavio(u)r 317 I = rpc:call(node(Pid), M, module_info, [attributes]), 318 case lists:keyfind(behaviour, 1, I) of 319 false -> case lists:keyfind(behavior, 1, I) of 320 false -> B = undefined; 321 {behavior, [B]} -> B 322 end; 323 {behaviour, [B]} -> B 324 end, 325 %% but not sure that system messages are treated by this process 326 %% so using a rpc with a small timeout in order not to lag the display 327 case rpc:call(node(Pid), sys, get_status, [Pid, 200]) 328 of 329 {status, _, {module, _}, 330 [_PDict, _SysState, _Parent, _Dbg, 331 [Header,{data, First},{data, Second}|_]]} -> 332 [{"Behaviour", B}, Header] ++ First ++ Second; 333 {status, _, {module, _}, 334 [_PDict, _SysState, _Parent, _Dbg, 335 [Header,{data, First}, OtherFormat]]} -> 336 [{"Behaviour", B}, Header] ++ First ++ [{"State",OtherFormat}]; 337 {status, _, {module, _}, 338 [_PDict, _SysState, _Parent, _Dbg, OtherFormat]} -> 339 %% Formatted status ? 340 case lists:keyfind(format_status, 1, rpc:call(node(Pid), M, module_info, [exports])) of 341 false -> Opt = {"Format", unknown}; 342 _ -> Opt = {"Format", overriden} 343 end, 344 [{"Behaviour", B}, Opt, {"State",OtherFormat}]; 345 {badrpc,{'EXIT',{timeout, _}}} -> [] 346 end. 347 348init_log_page(Parent, Pid, Table) -> 349 Win = observer_lib:html_window(Parent), 350 Cs = observer_lib:colors(Parent), 351 Update = fun() -> 352 Fd = spawn_link(fun() -> io_server() end), 353 rpc:call(node(Pid), rb, rescan, [[{start_log, Fd}]]), 354 rpc:call(node(Pid), rb, grep, [local_pid_str(Pid)]), 355 Logs = io_get_data(Fd), 356 %% Replace remote local pid notation to global notation 357 Pref = global_pid_node_pref(Pid), 358 ExpPid = re:replace(Logs,"<0\.","<" ++ Pref ++ ".",[global, {return, list}]), 359 %% Try to keep same look by removing blanks at right of rewritten PID 360 NbBlanks = length(Pref) - 1, 361 Re = "(<" ++ Pref ++ "\.[^>]{1,}>)[ ]{"++ integer_to_list(NbBlanks) ++ "}", 362 Look = re:replace(ExpPid, Re, "\\1", [global, {return, list}]), 363 Html = observer_html_lib:expandable_term("SaslLog", Look, Table, Cs), 364 wxHtmlWindow:setPage(Win, Html) 365 end, 366 Update(), 367 {Win, Update}. 368 369create_menus(MenuBar) -> 370 Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}, 371 {"View", [#create_menu{id=?REFRESH, text="Refresh\tCtrl-R"}]}], 372 observer_lib:create_menus(Menus, MenuBar, new_window). 373 374process_info_fields(Pid, WSz) -> 375 Struct = [{"Overview", 376 [{"Initial Call", initial_call}, 377 {"Current Function", current_function}, 378 {"Registered Name", registered_name}, 379 {"Status", status}, 380 {"Message Queue Len",message_queue_len}, 381 {"Group Leader", {click, group_leader}}, 382 {"Priority", priority}, 383 {"Trap Exit", trap_exit}, 384 {"Reductions", reductions}, 385 {"Binary", fun(Data) -> stringify_bins(Data) end}, 386 {"Last Calls", last_calls}, 387 {"Catch Level", catchlevel}, 388 {"Trace", trace}, 389 {"Suspending", suspending}, 390 {"Sequential Trace Token", sequential_trace_token}, 391 {"Error Handler", error_handler}]}, 392 {scroll_boxes, 393 [{"Links", {click, links}}, 394 {"Monitors", {click, filter_monitor_info()}}, 395 {"Monitored by", {click, monitored_by}}]}, 396 {"Memory and Garbage Collection", right, 397 [{"Memory", {bytes, memory}}, 398 {"Stack and Heaps", {{words,WSz}, total_heap_size}}, 399 {"Heap Size", {{words,WSz}, heap_size}}, 400 {"Stack Size", {{words,WSz}, stack_size}}, 401 {"GC Min Heap Size", {{words,WSz}, get_gc_info(min_heap_size)}}, 402 {"GC FullSweep After", get_gc_info(fullsweep_after)} 403 ]}], 404 case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]) of 405 RawInfo when is_list(RawInfo) -> 406 observer_lib:fill_info(Struct, RawInfo); 407 _ -> 408 throw(process_undefined) 409 end. 410 411item_list() -> 412 [ %% backtrace, 413 binary, 414 catchlevel, 415 current_function, 416 %% dictionary, 417 error_handler, 418 garbage_collection, 419 group_leader, 420 heap_size, 421 initial_call, 422 last_calls, 423 links, 424 memory, 425 message_queue_len, 426 %% messages, 427 monitored_by, 428 monitors, 429 priority, 430 reductions, 431 registered_name, 432 sequential_trace_token, 433 stack_size, 434 status, 435 suspending, 436 total_heap_size, 437 trace, 438 trap_exit]. 439 440get_gc_info(Arg) -> 441 fun(Data) -> 442 GC = proplists:get_value(garbage_collection, Data), 443 proplists:get_value(Arg, GC) 444 end. 445 446filter_monitor_info() -> 447 fun(Data) -> 448 Ms = proplists:get_value(monitors, Data), 449 [Id || {_Type, Id} <- Ms] % Type is process or port 450 end. 451 452stringify_bins(Data) -> 453 Bins = proplists:get_value(binary, Data), 454 [lists:flatten(io_lib:format("<< ~s, refc ~w>>", [observer_lib:to_str({bytes,Sz}),Refc])) 455 || {_Ptr, Sz, Refc} <- Bins]. 456 457local_pid_str(Pid) -> 458 %% observer can observe remote nodes 459 %% There is no function to get the local 460 %% pid from the remote pid ... 461 %% So grep will fail to find remote pid in remote local log. 462 %% i.e. <4589.42.1> will not be found, but <0.42.1> will 463 %% Let's replace first integer by zero 464 "<0" ++ re:replace(pid_to_list(Pid),"\<([0-9]{1,})","",[{return, list}]). 465 466global_pid_node_pref(Pid) -> 467 %% Global PID node prefix : X of <X.Y.Z> 468 [NodePrefix|_] = string:lexemes(pid_to_list(Pid),"<."), 469 NodePrefix. 470 471io_get_data(Pid) -> 472 Pid ! {self(), get_data_and_close}, 473 receive 474 {Pid, data, Data} -> lists:flatten(Data) 475 end. 476 477io_server() -> 478 io_server(#io{}). 479 480io_server(State) -> 481 receive 482 {io_request, From, ReplyAs, Request} -> 483 {_, Reply, NewState} = io_request(Request,State), 484 From ! {io_reply, ReplyAs, Reply}, 485 io_server(NewState); 486 {Pid, get_data_and_close} -> 487 Pid ! {self(), data, lists:reverse(State#io.rdata)}, 488 normal; 489 _Unknown -> 490 io_server(State) 491 end. 492 493io_request({put_chars, _Encoding, Chars}, State = #io{rdata=Data}) -> 494 {ok, ok, State#io{rdata=[Chars|Data]}}; 495io_request({put_chars, Encoding, Module, Function, Args}, State) -> 496 try 497 io_request({put_chars, Encoding, apply(Module, Function, Args)}, State) 498 catch _:_ -> 499 {error, {error, Function}, State} 500 end; 501io_request(_Req, State) -> 502 %% io:format("~p: Unknown req: ~tp ~n",[?LINE, _Req]), 503 {ok, {error, request}, State}. 504