%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% -module(observer_procinfo). -behaviour(wx_object). -export([start/3]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, handle_call/3, handle_info/2]). -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). -define(REFRESH, 601). -define(SELECT_ALL, 603). -define(ID_NOTEBOOK, 604). -record(state, {parent, frame, pid, pages=[] }). -record(worker, {panel, callback}). start(Process, ParentFrame, Parent) -> wx_object:start(?MODULE, [Process, ParentFrame, Parent], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Pid, ParentFrame, Parent]) -> try Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of [] -> io_lib:format("~p",[Pid]); {registered_name, Registered} -> atom_to_list(Registered) end, Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title], [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {800,700}}]), MenuBar = wxMenuBar:new(), create_menus(MenuBar), wxFrame:setMenuBar(Frame, MenuBar), Notebook = wxNotebook:new(Frame, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), ProcessPage = init_panel(Notebook, "Process Information", Pid, fun init_process_page/2), MessagePage = init_panel(Notebook, "Messages", Pid, fun init_message_page/2), DictPage = init_panel(Notebook, "Dictionary", Pid, fun init_dict_page/2), StackPage = init_panel(Notebook, "Stack Trace", Pid, fun init_stack_page/2), wxFrame:connect(Frame, close_window), wxMenu:connect(Frame, command_menu_selected), %% wxNotebook:connect(Notebook, command_notebook_page_changed, [{skip,true}]), wxFrame:show(Frame), {Frame, #state{parent=Parent, pid=Pid, frame=Frame, pages=[ProcessPage,MessagePage,DictPage,StackPage] }} catch error:{badrpc, _} -> observer_wx:return_to_localnode(ParentFrame, node(Pid)), {stop, badrpc, #state{parent=Parent, pid=Pid}}; Error:Reason -> io:format("~p:~p: ~p ~p~n ~p~n", [?MODULE, ?LINE, Error, Reason, erlang:get_stacktrace()]) end. init_panel(Notebook, Str, Pid, Fun) -> Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxHORIZONTAL), {Window,Callback} = Fun(Panel, Pid), wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), wxPanel:setSizer(Panel, Sizer), true = wxNotebook:addPage(Notebook, Panel, Str), #worker{panel=Panel, callback=Callback}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_event(#wx{event=#wxClose{type=close_window}}, State) -> {stop, shutdown, State}; handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> {stop, shutdown, State}; handle_event(#wx{id=?REFRESH}, #state{pages=Pages}=State) -> [(W#worker.callback)() || W <- Pages], {noreply, State}; handle_event(Event, State) -> io:format("~p: ~p, Handle event: ~p~n", [?MODULE, ?LINE, Event]), {noreply, State}. handle_info(Info, State) -> io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. handle_call(Call, _From, State) -> io:format("~p ~p: Got call ~p~n",[?MODULE, ?LINE, Call]), {reply, ok, State}. handle_cast(Cast, State) -> io:format("~p ~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]), {noreply, State}. terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) -> Parent ! {procinfo_menu_closed, Pid}, case Frame of undefined -> ok; _ -> wxFrame:destroy(Frame) end, ok. code_change(_, _, State) -> {stop, not_yet_implemented, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_process_page(Panel, Pid) -> Fields = process_info_fields(Pid), {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields), {FPanel, fun() -> observer_lib:update_info(UpFields, process_info_fields(Pid)) end}. init_text_page(Parent) -> Style = ?wxTE_MULTILINE bor ?wxTE_RICH2 bor ?wxTE_READONLY, Text = wxTextCtrl:new(Parent, ?wxID_ANY, [{style, Style}]), Font = observer_wx:get_attrib({font, modern}), Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), true = wxTextCtrl:setDefaultStyle(Text, Attr), wxTextAttr:destroy(Attr), Text. init_message_page(Parent, Pid) -> Text = init_text_page(Parent), Format = fun(Message, Number) -> {io_lib:format("~-4.w ~p~n", [Number, Message]), Number+1} end, Update = fun() -> {messages,RawMessages} = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]), {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), Last = wxTextCtrl:getLastPosition(Text), wxTextCtrl:remove(Text, 0, Last), case Messages =:= [] of true -> wxTextCtrl:writeText(Text, "No messages"); false -> wxTextCtrl:writeText(Text, Messages) end end, Update(), {Text, Update}. init_dict_page(Parent, Pid) -> Text = init_text_page(Parent), Update = fun() -> {dictionary,RawDict} = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]), Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], Last = wxTextCtrl:getLastPosition(Text), wxTextCtrl:remove(Text, 0, Last), wxTextCtrl:writeText(Text, Dict) end, Update(), {Text, Update}. init_stack_page(Parent, Pid) -> Text = init_text_page(Parent), Format = fun({Mod, Fun, Arg, Info}) -> Str = io_lib:format("~w:~w/~w", [Mod,Fun,Arg]), case Info of [{file,File},{line,Line}] -> io_lib:format("~-45.s ~s:~w~n", [Str,File,Line]); _ -> [Str,$\n] end end, Update = fun() -> {current_stacktrace,RawBt} = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, current_stacktrace]), Last = wxTextCtrl:getLastPosition(Text), wxTextCtrl:remove(Text, 0, Last), [wxTextCtrl:writeText(Text, Format(Entry)) || Entry <- RawBt] end, Update(), {Text, Update}. create_menus(MenuBar) -> Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}, {"View", [#create_menu{id=?REFRESH, text="Refresh\tCtrl-R"}]}], observer_lib:create_menus(Menus, MenuBar, new_window). process_info_fields(Pid) -> RawInfo = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]), Struct = [{"Overview", [{"Initial Call", initial_call}, {"Current Function", current_function}, {"Registered Name", registered_name}, {"Status", status}, {"Message Queue Len",message_queue_len}, {"Priority", priority}, {"Trap Exit", trap_exit}, {"Reductions", reductions}, {"Binary", binary}, {"Last Calls", last_calls}, {"Catch Level", catchlevel}, {"Trace", trace}, {"Suspending", suspending}, {"Sequential Trace Token", sequential_trace_token}, {"Error Handler", error_handler}]}, {"Connections", [{"Group Leader", group_leader}, {"Links", links}, {"Monitors", monitors}, {"Monitored by", monitored_by}]}, {"Memory and Garbage Collection", right, [{"Memory", {bytes, memory}}, {"Stack and Heaps", {bytes, total_heap_size}}, {"Heap Size", {bytes, heap_size}}, {"Stack Size", {bytes, stack_size}}, {"GC Min Heap Size", {bytes, get_gc_info(min_heap_size)}}, {"GC FullSweep After", get_gc_info(fullsweep_after)} ]}], observer_lib:fill_info(Struct, RawInfo). item_list() -> [ %% backtrace, binary, catchlevel, current_function, %% dictionary, error_handler, garbage_collection, group_leader, heap_size, initial_call, last_calls, links, memory, message_queue_len, %% messages, monitored_by, monitors, priority, reductions, registered_name, sequential_trace_token, stack_size, status, suspending, total_heap_size, trace, trap_exit]. get_gc_info(Arg) -> fun(Data) -> GC = proplists:get_value(garbage_collection, Data), proplists:get_value(Arg, GC) end.