%% %% %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/4]). -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(CLOSE, 601). -define(REFRESH, 602). -define(SELECT_ALL, 603). -define(ID_NOTEBOOK, 604). -record(procinfo_state, {parent, frame, node, pid, module, procinfo_stc, modinfo_stc, modcode_stc, checklistbox, current_view, % proc_info::atom | module_info::atom | module_code::atom itemlist = [{backtrace, false}, {binary, false}, {catchlevel, false}, {current_function, false}, {dictionary, false}, {error_handler, true}, {garbage_collection, true}, {group_leader, true}, {heap_size, true}, {initial_call, false}, {last_calls, false}, {links, true}, {memory, false}, {message_queue_len, true}, {messages, false}, {monitored_by, false}, {monitors, false}, {priority, true}, {reductions, false}, {registered_name, false}, {sequential_trace_token, false}, {stack_size, false}, {status, false}, {suspending, false}, {total_heap_size, false}, {trace, false}, {trap_exit,true}] }). start(Node, Process, ParentFrame, Parent) -> wx_object:start(?MODULE, [Node, Process, ParentFrame, Parent], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Node, Process, ParentFrame, Parent]) -> try State = #procinfo_state{parent = Parent, node = Node, pid = Process, current_view = proc_info }, ItemList = State#procinfo_state.itemlist, Name = case observer_wx:try_rpc(Node, erlang, process_info, [Process, registered_name]) of [] -> undefined; {registered_name, M} -> M end, {initial_call, {Module, _, _}} = observer_wx:try_rpc(Node, erlang, process_info, [Process, initial_call]), {Frame, ProcStc, CheckListBox, ModInfoStc, ModCodeStc} = setup(ParentFrame, Node, Process, ItemList, Module, Name), {Frame, State#procinfo_state{frame = Frame, module = Module, procinfo_stc = ProcStc, modinfo_stc = ModInfoStc, modcode_stc = ModCodeStc, checklistbox = CheckListBox}} catch error:{badrpc, _} -> observer_wx:return_to_localnode(ParentFrame, Node), {stop, badrpc, #procinfo_state{parent = Parent, pid = Process}} end. setup(ParentFrame, Node, Pid, ItemList, Module, Name) -> Title = case Name of undefined -> atom_to_list(Node) ++ ":" ++ atom_to_list(Module); Name -> atom_to_list(Node) ++ ":" ++ atom_to_list(Name) end, Frame = wxFrame:new(ParentFrame, ?wxID_ANY, Title, [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {900,900}}]), Panel = wxPanel:new(Frame, []), MainSz = wxBoxSizer:new(?wxHORIZONTAL), Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), wxNotebook:connect(Notebook, command_notebook_page_changed), {CodePanel, CheckListBox, CodeStc} = create_procinfo_page(Notebook, Node, Pid, ItemList), {ModInfoPanel, ModInfoStc} = create_page(Notebook, Node, Module, module_info), {ModCodePanel, ModCodeStc} = create_page(Notebook, Node, Module, module_code), wxNotebook:addPage(Notebook, CodePanel, "Process information", []), wxNotebook:addPage(Notebook, ModInfoPanel, "Module information", []), wxNotebook:addPage(Notebook, ModCodePanel, "Module code", []), MenuBar = wxMenuBar:new(), create_menus(MenuBar), wxWindow:setSizer(Panel, MainSz), wxSizer:add(MainSz, Notebook, [{flag, ?wxEXPAND}, {proportion, 1}]), wxFrame:setMenuBar(Frame, MenuBar), wxFrame:show(Frame), wxFrame:connect(Frame, close_window), wxMenu:connect(Frame, command_menu_selected), {Frame, CodeStc, CheckListBox, ModInfoStc, ModCodeStc}. create_procinfo_page(Notebook, Node, Pid, ItemList) -> Panel = wxPanel:new(Notebook), MainSz = wxBoxSizer:new(?wxHORIZONTAL), CheckSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "View"}]), BtnSz = wxBoxSizer:new(?wxHORIZONTAL), Stc = create_styled_txtctrl(Panel, proc_info), Txt = get_formatted_values(Node, Pid, ItemList), set_text(Stc, Txt, text), Choices = [atom_to_list(Tag) || {Tag, _} <- ItemList], CheckListBox = wxCheckListBox:new(Panel, ?wxID_ANY, [{choices, Choices}, {style, ?wxLB_EXTENDED}, {style, ?wxLB_SORT}, {style, ?wxLB_NEEDED_SB}]), check_boxes(CheckListBox, ItemList), wxCheckListBox:connect(CheckListBox, command_checklistbox_toggled), SelAllBtn = wxButton:new(Panel, ?SELECT_ALL, [{label, "Select all"}]), DeSelAllBtn = wxButton:new(Panel, ?SELECT_ALL, [{label, "Deselect all"}]), wxButton:connect(SelAllBtn, command_button_clicked, [{userData, true}]), wxButton:connect(DeSelAllBtn, command_button_clicked, [{userData, false}]), wxWindow:setSizer(Panel, MainSz), wxSizer:add(MainSz, Stc, [{proportion, 1}, {flag, ?wxEXPAND}]), wxSizer:add(CheckSz, CheckListBox, [{proportion, 1}]), wxSizer:add(BtnSz, SelAllBtn), wxSizer:add(BtnSz, DeSelAllBtn), wxSizer:add(CheckSz, BtnSz), wxSizer:add(MainSz, CheckSz, [{flag, ?wxEXPAND}]), {Panel, CheckListBox, Stc}. create_page(Notebook, Node, Module, What) -> Panel = wxPanel:new(Notebook, []), Sizer = wxBoxSizer:new(?wxVERTICAL), Stc = create_styled_txtctrl(Panel, What), {Sort, Txt} = case What of module_info -> {text, get_formatted_modinfo(Node, Module)}; module_code -> case get_src_file(Node, Module) of {ok, File} -> {file, File}; error-> {text, "Error! Could not read sourcefile"} end end, set_text(Stc, Txt, Sort), wxWindow:setSizer(Panel, Sizer), wxSizer:add(Sizer, Stc, [{flag, ?wxEXPAND}, {proportion, 1}]), {Panel, Stc}. create_menus(MenuBar) -> Menus = [{"File", [#create_menu{id = ?CLOSE, text = "Close"}]}, {"View", [#create_menu{id = ?REFRESH, text = "Refresh"}]}], observer_lib:create_menus(Menus, MenuBar, new_window). check_boxes(CheckListBox, Bool, all) -> lists:foreach(fun(Index) -> wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]) end, lists:seq(0, wxControlWithItems:getCount(CheckListBox))). check_boxes(CheckListBox, ItemList) -> lists:foldl(fun({_, Bool}, Index) -> wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]), Index+1 end, 0, ItemList). create_styled_txtctrl(Parent, View) -> FixedFont = wxFont:new(11, ?wxFONTFAMILY_TELETYPE, ?wxFONTSTYLE_NORMAL, ?wxNORMAL,[]), Stc = wxStyledTextCtrl:new(Parent), wxStyledTextCtrl:styleClearAll(Stc), wxStyledTextCtrl:styleSetFont(Stc, ?wxSTC_STYLE_DEFAULT, FixedFont), wxStyledTextCtrl:setLexer(Stc, ?wxSTC_LEX_ERLANG), wxStyledTextCtrl:setMarginType(Stc, 2, ?wxSTC_MARGIN_NUMBER), W = wxStyledTextCtrl:textWidth(Stc, ?wxSTC_STYLE_LINENUMBER, "9"), wxStyledTextCtrl:setMarginWidth(Stc, 2, W*3), wxStyledTextCtrl:setSelectionMode(Stc, ?wxSTC_SEL_LINES), wxStyledTextCtrl:setUseHorizontalScrollBar(Stc, false), Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}}, {?wxSTC_ERLANG_COMMENT, {160,53,35}}, {?wxSTC_ERLANG_VARIABLE, {150,100,40}}, {?wxSTC_ERLANG_NUMBER, {5,5,100}}, {?wxSTC_ERLANG_KEYWORD, {130,40,172}}, {?wxSTC_ERLANG_STRING, {170,45,132}}, {?wxSTC_ERLANG_OPERATOR, {30,0,0}}, {?wxSTC_ERLANG_ATOM, {0,0,0}}, {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}}, {?wxSTC_ERLANG_CHARACTER,{236,155,172}}, {?wxSTC_ERLANG_MACRO, {40,144,170}}, {?wxSTC_ERLANG_RECORD, {40,100,20}}, {?wxSTC_ERLANG_SEPARATOR,{0,0,0}}, {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}], SetStyle = fun({Style, Color}) -> wxStyledTextCtrl:styleSetFont(Stc, Style, FixedFont), wxStyledTextCtrl:styleSetForeground(Stc, Style, Color) end, [SetStyle(Style) || Style <- Styles], KeyWords = case View of proc_info -> get_procinfo_keywords(); module_info -> get_modinfo_keywords(); module_code -> get_erl_keywords() end, wxStyledTextCtrl:setKeyWords(Stc, 0, KeyWords), Stc. get_erl_keywords() -> L = ["after","begin","case","try","cond","catch","andalso","orelse", "end","fun","if","let","of","query","receive","when","bnot","not", "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"], lists:flatten([K ++ " "|| K <- L] ++ [0]). get_procinfo_keywords() -> L = ["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"], lists:flatten([K ++ " "|| K <- L] ++ [0]). get_modinfo_keywords() -> L = ["exports", "imports", "attributes", "compile"], lists:flatten([K ++ " "|| K <- L] ++ [0]). get_formatted_values(Node, Process, ItemList) -> TagList = [Tag || {Tag, Bool} <- ItemList, Bool =:= true], Values = observer_wx:try_rpc(Node, erlang, process_info, [Process, TagList]), lists:flatten(format_value(Values, [])). format_value([], Acc) -> lists:reverse(Acc); format_value([{backtrace, Bin} | T], Acc) -> format_value(T, [io_lib:format("{backtrace,~s}~n", [binary_to_list(Bin)]) | Acc]); format_value([H|T], Acc) -> format_value(T, [io_lib:format("~p~n", [H]) | Acc]). get_formatted_modinfo(Node, Module) -> Info = observer_wx:try_rpc(Node, Module, module_info, []), lists:flatten([io_lib:format("~p~n", [I]) || I <- Info]). get_src_remote(Node, Module) -> case observer_wx:try_rpc(Node, filename, find_src, [Module]) of {error, _} -> error; {SrcFile, _} -> case observer_wx:try_rpc(Node, file, read_file_info, [SrcFile ++ ".erl"]) of {error, _} -> error; {ok, _} -> {ok, SrcFile ++ ".erl"} end end. get_src_local(Module) -> case filename:find_src(Module) of {error, _} -> error; {SrcFile, _} -> case file:read_file_info(SrcFile ++ ".erl") of {error, _} -> error; {ok, _} -> {ok, SrcFile ++ ".erl"} end end. get_src_file(Node, Module) -> case get_src_remote(Node, Module) of {ok, SrcFile} -> {ok, SrcFile}; error -> get_src_local(Module) end. set_text(Stc, Text, text) -> wxStyledTextCtrl:setReadOnly(Stc, false), wxStyledTextCtrl:setText(Stc, Text), wxStyledTextCtrl:setReadOnly(Stc, true); set_text(Stc, File, file) -> wxStyledTextCtrl:setReadOnly(Stc, false), wxStyledTextCtrl:loadFile(Stc, File), wxStyledTextCtrl:setReadOnly(Stc, true). update_procinfo_page(Stc, Node, Process, ItemList) -> Txt = get_formatted_values(Node, Process, ItemList), set_text(Stc, Txt, text). update_modinfo_page(Stc, Node, Module) -> Txt = get_formatted_modinfo(Node, Module), set_text(Stc, Txt, text). update_modcode_page(Stc, Node, Module) -> case get_src_file(Node, Module) of {ok, File} -> set_text(Stc, File, file); error -> set_text(Stc, "Error! Could not read sourcefile", text) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_event(#wx{event = #wxClose{type = close_window}}, State) -> {stop, shutdown, State}; handle_event(#wx{id = ?CLOSE, event = #wxCommand{type = command_menu_selected}}, State) -> {stop, shutdown, State}; handle_event(#wx{id = ?REFRESH, event = #wxCommand{type = command_menu_selected}}, #procinfo_state{current_view = Current, frame = Frame, node = Node, pid = Pid, procinfo_stc = Stc, itemlist = ItemList} = State) when Current =:= proc_info -> try update_procinfo_page(Stc, Node, Pid, ItemList), {noreply, State} catch error:{badrpc, _} -> observer_wx:return_to_localnode(Frame, Node), {stop, badrpc, State} end; handle_event(#wx{id = ?REFRESH, event = #wxCommand{type = command_menu_selected}}, #procinfo_state{current_view = Current, frame = Frame, node = Node, modinfo_stc = Stc, module = Module} = State) when Current =:= module_info -> try update_modinfo_page(Stc, Node, Module), {noreply, State} catch error:{badrpc, _} -> observer_wx:return_to_localnode(Frame, Node), {stop, badrpc, State} end; handle_event(#wx{id = ?REFRESH, event = #wxCommand{type = command_menu_selected}}, #procinfo_state{current_view = Current, modcode_stc = Stc, frame = Frame, node = Node, module = Module} = State) when Current =:= module_code -> try update_modcode_page(Stc, Node, Module), {noreply, State} catch error:{badrpc, _} -> observer_wx:return_to_localnode(Frame, Node), {stop, badrpc, State} end; handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK, event = #wxNotebook{type = command_notebook_page_changed}}, #procinfo_state{frame = Frame, module = Module, procinfo_stc = ProcStc, modcode_stc = CodeStc, modinfo_stc = ModInfoStc, node = Node, pid = Pid, itemlist = ItemList} = State) -> try Current = case observer_wx:check_page_title(Notebook) of "Process information" -> update_procinfo_page(ProcStc, Node, Pid, ItemList), proc_info; "Module information" -> update_modinfo_page(ModInfoStc, Node, Module), module_info; "Module code" -> update_modcode_page(CodeStc, Node, Module), module_code end, {noreply, State#procinfo_state{current_view = Current}} catch error:{badrpc, _} -> observer_wx:return_to_localnode(Frame, Node), {stop, badrpc, State} end; handle_event(#wx{event = #wxCommand{type = command_checklistbox_toggled, commandInt = Index}, obj = CheckListbox}, #procinfo_state{frame = Frame, node = Node, pid = Process, procinfo_stc = Stc, itemlist = ItemList} = State) -> try {Tag, _} = lists:nth(Index+1, ItemList), ItemList2 = case wxCheckListBox:isChecked(CheckListbox, Index) of true -> lists:keyreplace(Tag, 1, ItemList, {Tag, true}); false -> lists:keyreplace(Tag, 1, ItemList, {Tag, false}) end, Txt = get_formatted_values(Node, Process, ItemList2), set_text(Stc, Txt, text), {noreply, State#procinfo_state{itemlist = ItemList2}} catch error:{badrpc, _} -> observer_wx:return_to_localnode(Frame, Node), {stop, badrpc, State} end; handle_event(#wx{id = ?SELECT_ALL, event = #wxCommand{type = command_button_clicked}, userData = Bool}, #procinfo_state{frame = Frame, node = Node, pid = Process, itemlist = ItemList, procinfo_stc = Stc, checklistbox = CheckListBox} = State) -> try check_boxes(CheckListBox, Bool, all), ItemList2 = lists:keymap(fun(_) -> Bool end, 2, ItemList), Txt = get_formatted_values(Node, Process, ItemList2), set_text(Stc, Txt, text), {noreply, State#procinfo_state{itemlist = ItemList2}} catch error:{badrpc, _} -> observer_wx:return_to_localnode(Frame, Node), {stop, badrpc, State} end; 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, #procinfo_state{parent = Parent, pid = Pid, frame = Frame}) -> io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), Parent ! {procinfo_menu_closed, Pid}, case Frame of undefined -> ok; _ -> wxFrame:destroy(Frame) end, ok. code_change(_, _, State) -> {stop, not_yet_implemented, State}.