%% %% %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_tv_wx). -export([start_link/2, display_table_info/4]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_sync_event/3, handle_cast/2]). -export([get_table_list/1]). %% RPC called move to runtime tools? -export([get_wx_parent/1, interval_dialog/5]). -import(observer_pro_wx, [to_str/1]). -behaviour(wx_object). -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). -include("observer_tv.hrl"). -define(GRID, 500). -define(ID_REFRESH, 401). -define(ID_REFRESH_INTERVAL, 402). -define(ID_ETS, 403). -define(ID_MNESIA, 404). -define(ID_UNREADABLE, 405). -define(ID_SYSTEM_TABLES, 406). -define(ID_TABLE_INFO, 407). -record(opt, {type=ets, sys_hidden=true, unread_hidden=true, sort_key=2, sort_incr=true }). -record(state, { parent, grid, node=node(), opt=#opt{}, selected, tabs, refr_timer=false, refr_intv=30 }). start_link(Notebook, Parent) -> wx_object:start_link(?MODULE, [Notebook, Parent], []). init([Notebook, Parent]) -> Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxVERTICAL), Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), wxWindow:setSizer(Panel, Sizer), Li = wxListItem:new(), AddListEntry = fun({Name, Align, DefSize}, Col) -> wxListItem:setText(Li, Name), wxListItem:setAlign(Li, Align), wxListCtrl:insertColumn(Grid, Col, Li), wxListCtrl:setColumnWidth(Grid, Col, DefSize), Col + 1 end, ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT, 200}, {"Table Id", ?wxLIST_FORMAT_RIGHT, 100}, {"Objects", ?wxLIST_FORMAT_RIGHT, 100}, {"Size (kB)", ?wxLIST_FORMAT_RIGHT, 100}, {"Owner Pid", ?wxLIST_FORMAT_CENTER, 150}, {"Owner Name", ?wxLIST_FORMAT_LEFT, 200} ], lists:foldl(AddListEntry, 0, ListItems), wxListItem:destroy(Li), wxListCtrl:connect(Grid, command_list_item_activated), wxListCtrl:connect(Grid, command_list_item_selected), wxListCtrl:connect(Grid, command_list_col_click), wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), {Panel, #state{grid=Grid, parent=Parent}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), {noreply, State#state{tabs=Tabs}}; handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, State = #state{node=Node, grid=Grid, opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> Opt = case Col+2 of Key -> Opt0#opt{sort_incr=not Bool}; NewKey -> Opt0#opt{sort_key=NewKey} end, Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), wxWindow:setFocus(Grid), {noreply, State#state{opt=Opt, tabs=Tabs}}; handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) when Id >= ?ID_ETS, Id =< ?ID_SYSTEM_TABLES -> Opt = case Id of ?ID_ETS -> Opt0#opt{type=ets}; ?ID_MNESIA -> Opt0#opt{type=mnesia}; ?ID_UNREADABLE -> Opt0#opt{unread_hidden= not Opt0#opt.unread_hidden}; ?ID_SYSTEM_TABLES -> Opt0#opt{sys_hidden= not Opt0#opt.sys_hidden} end, Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), wxWindow:setFocus(Grid), {noreply, State#state{opt=Opt, tabs=Tabs}}; handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> wx:batch(fun() -> Cols = wxListCtrl:getColumnCount(Grid), Last = lists:foldl(fun(I, Last) -> Last - wxListCtrl:getColumnWidth(Grid, I) end, W-2, lists:seq(0, Cols - 2)), Size = max(200, Last), wxListCtrl:setColumnWidth(Grid, Cols-1, Size) end), {noreply, State}; handle_event(#wx{obj=Grid, event=#wxList{type=command_list_item_activated, itemIndex=Index}}, State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs}) -> Table = lists:nth(Index+1, Tabs), case Table#tab.protection of private -> self() ! {error, "Table has 'private' protection and can not be read"}; _ -> observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]) end, {noreply, State}; handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, State) -> {noreply, State#state{selected=Index}}; handle_event(#wx{id=?ID_TABLE_INFO}, State = #state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) -> case Sel of undefined -> {noreply, State}; R when is_integer(R) -> Table = lists:nth(Sel+1, Tabs), Parent = get_wx_parent(Grid), display_table_info(Parent, Node, Type, Table), {noreply, State} end; handle_event(#wx{id=?ID_REFRESH_INTERVAL}, State = #state{grid=Grid, refr_timer=Timer0, refr_intv=Intv0}) -> Parent = get_wx_parent(Grid), case interval_dialog(Parent, Timer0 /= false, Intv0, 10, 5*60) of cancel -> {noreply, State}; {true, Intv} -> case Timer0 of false -> ok; _ -> timer:cancel(Timer0) end, {ok, Timer} = timer:send_interval(Intv * 1000, refresh_interval), {noreply, State#state{refr_timer=Timer, refr_intv=Intv}}; {false, _} -> case Timer0 of false -> ok; _ -> timer:cancel(Timer0) end, {noreply, State#state{refr_timer=false}} end; handle_event(Event, State) -> io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. handle_sync_event(Event, _Obj, _State) -> io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), ok. handle_call(Event, From, State) -> io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), {noreply, State}. handle_cast(Event, State) -> io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> io:format("refresh interval ~p~n", [time()]), Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), {noreply, State#state{tabs=Tabs}}; handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, refr_timer = Refr, refr_intv=Intv}) -> Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), wxWindow:setFocus(Grid), create_menus(Parent, Opt), Timer = case Refr of true -> {ok, Ref} = timer:send_interval(Intv*1000, refresh_interval), Ref; false -> false end, {noreply, State#state{node=Node, tabs=Tabs, refr_timer=Timer}}; handle_info(not_active, State = #state{refr_timer = Timer0}) -> Timer = case Timer0 of false -> false; true -> true; Timer0 -> timer:cancel(Timer0), true end, {noreply, State#state{refr_timer=Timer}}; handle_info({node, Node}, State = #state{grid=Grid, opt=Opt}) -> Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), wxWindow:setFocus(Grid), {noreply, State#state{node=Node, tabs=Tabs}}; handle_info({error, Error}, State) -> handle_error(Error), {noreply, State}; handle_info(Event, State) -> io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. terminate(Event, _State) -> io:format("~p:~p, terminate ~p\n", [?MODULE, ?LINE, Event]), ok. code_change(_, _, State) -> State. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% create_menus(Parent, #opt{sys_hidden=Sys, unread_hidden=UnR, type=Type}) -> MenuEntries = [{"View", [#create_menu{id = ?ID_TABLE_INFO, text = "Table information\tCtrl-I"}, separator, #create_menu{id = ?ID_ETS, text = "&Ets Tables", type=radio, check=Type==ets}, #create_menu{id = ?ID_MNESIA, text = "&Mnesia Tables", type=radio, check=Type==mnesia}, separator, #create_menu{id = ?ID_UNREADABLE, text = "View &Unreadable Tables", type=check, check=not UnR}, #create_menu{id = ?ID_SYSTEM_TABLES, text = "View &System Tables", type=check, check=not Sys}, separator, #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} ]}], observer_wx:create_menus(Parent, MenuEntries). get_tables(Node, Opt) -> case rpc:call(Node, ?MODULE, get_table_list, [Opt]) of {badrpc, Error} -> self() ! {error, Error}, []; {error, Error} -> self() ! {error, Error}, []; Result -> Result end. get_table_list(#opt{type=ets, unread_hidden=HideUnread, sys_hidden=HideSys}) -> Info = fun(Id, Acc) -> try TabId = case ets:info(Id, named_table) of true -> ignore; false -> Id end, Name = ets:info(Id, name), Protection = ets:info(Id, protection), ignore(HideUnread andalso Protection == private, unreadable), Owner = ets:info(Id, owner), RegName = case catch process_info(Owner, registered_name) of [] -> ignore; {registered_name, ProcName} -> ProcName end, ignore(HideSys andalso ordsets:is_element(RegName, sys_processes()), system_tab), ignore(HideSys andalso ordsets:is_element(Name, sys_tables()), system_tab), ignore((RegName == mnesia_monitor) andalso Name /= schema andalso is_atom((catch mnesia:table_info(Name, where_to_read))), mnesia_tab), Memory = ets:info(Id, memory) * erlang:system_info(wordsize), Tab = #tab{name = Name, id = TabId, protection = Protection, owner = Owner, size = ets:info(Id, size), reg_name = RegName, type = ets:info(Id, type), keypos = ets:info(Id, keypos), heir = ets:info(Id, heir), memory = Memory, compressed = ets:info(Id, compressed), fixed = ets:info(Id, fixed) }, [Tab|Acc] catch _:_What -> %% io:format("Skipped ~p: ~p ~n",[Id, _What]), Acc end end, lists:foldl(Info, [], ets:all()); get_table_list(#opt{type=mnesia, sys_hidden=HideSys}) -> Owner = ets:info(schema, owner), Owner /= undefined orelse throw({error, "Mnesia is not running on: " ++ atom_to_list(node())}), {registered_name, RegName} = process_info(Owner, registered_name), Info = fun(Id, Acc) -> try Name = Id, ignore(HideSys andalso ordsets:is_element(Name, mnesia_tables()), system_tab), ignore(Name =:= schema, mnesia_tab), Storage = mnesia:table_info(Id, storage_type), Tab0 = #tab{name = Name, owner = Owner, size = mnesia:table_info(Id, size), reg_name = RegName, type = mnesia:table_info(Id, type), keypos = 2, memory = mnesia:table_info(Id, memory) * erlang:system_info(wordsize), storage = Storage, index = mnesia:table_info(Id, index) }, Tab = if Storage == disc_only_copies -> Tab0#tab{fixed = element(2, dets:info(Id, safe_fixed)) /= []}; (Storage == ram_copies) orelse (Storage == disc_copies) -> Tab0#tab{fixed = ets:info(Id, fixed), compressed = ets:info(Id, compressed)}; true -> Tab0 end, [Tab|Acc] catch _:_What -> %% io:format("Skipped ~p: ~p ~n",[Id, _What]), Acc end end, lists:foldl(Info, [], mnesia:system_info(tables)). display_table_info(Parent, Node, Source, Table) -> Title = "Table Info: " ++ atom_to_list(Table#tab.name), Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, [{style, ?wxCAPTION bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), IdInfo = {"Identification and Owner", [{"Name", Table#tab.name}, {"Id", case Table#tab.id of ignore -> Table#tab.name; Id -> Id end}, {"Named table", Table#tab.id == ignore}, {"Owner", Table#tab.owner}, {"Owner Name", case Table#tab.reg_name of ignore -> "-"; Id -> Id end}, {"Heir", Table#tab.heir}, {"Node", Node}]}, MnesiaSettings = case Source of ets -> []; mnesia -> [{"Local storage type", case Table#tab.storage of unknown -> "Not available"; ST -> ST end}, {"Index positions", list_to_strings(Table#tab.index)}] end, Settings = {"Settings", [{"Source", Source}, {"Key Position", Table#tab.keypos}, {"Table Type", Table#tab.type}, {"Protection Mode", Table#tab.protection}, {"Fixed", Table#tab.fixed} | MnesiaSettings ]}, Memory = {"Memory Usage", [{"Number of objects", Table#tab.size}, {"Memory allocated", integer_to_list(Table#tab.memory div 1024) ++ "kB"}, {"Compressed", Table#tab.compressed}]}, Sizer = display_info_wx(Frame, [IdInfo, Settings, Memory]), wxSizer:setSizeHints(Sizer, Frame), wxFrame:center(Frame), wxFrame:show(Frame). list_to_strings([]) -> "None"; list_to_strings([A]) -> integer_to_list(A); list_to_strings([A,B]) -> integer_to_list(A) ++ " ," ++ list_to_strings(B). get_wx_parent(Window) -> Parent = wxWindow:getParent(Window), case wx:is_null(Parent) of true -> Window; false -> get_wx_parent(Parent) end. display_info_wx(Frame, Info) -> Panel = wxPanel:new(Frame), wxWindow:setBackgroundColour(Panel, {255,255,255}), Sizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:addSpacer(Sizer, 5), Add = fun(BoxInfo) -> Box = create_box(Panel, BoxInfo), wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]) end, [Add(I) || I <- Info], wxSizer:addSpacer(Sizer, 5), wxWindow:setSizerAndFit(Panel, Sizer), Sizer. create_box(Panel, {Title, Info}) -> Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]), Left = wxBoxSizer:new(?wxVERTICAL), Right = wxBoxSizer:new(?wxVERTICAL), Expand = [{flag, ?wxEXPAND}], AddRow = fun({Desc, Value}) -> wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand), wxSizer:add(Right, wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)), Expand) end, [AddRow(Entry) || Entry <- Info], wxSizer:add(Box, Left), wxSizer:addSpacer(Box, 10), wxSizer:add(Box, Right), wxSizer:addSpacer(Box, 30), Box. sys_tables() -> [ac_tab, asn1, cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table, cell_id, cell_pos, clist, cover_internal_data_table, cover_collected_remote_data_table, cover_binary_code_table, code, code_names, cookies, corba_policy, corba_policy_associations, dets, dets_owners, dets_registry, disk_log_names, disk_log_pids, eprof, erl_atom_cache, erl_epmd_nodes, etop_accum_tab, etop_tr, ets_coverage_data, file_io_servers, gs_mapping, gs_names, gstk_db, gstk_grid_cellid, gstk_grid_cellpos, gstk_grid_id, httpd, id, ign_req_index, ign_requests, index, inet_cache, inet_db, inet_hosts, 'InitialReferences', int_db, interpreter_includedirs_macros, ir_WstringDef, lmcounter, locks, % mnesia_decision, mnesia_gvar, mnesia_stats, % mnesia_transient_decision, pg2_table, queue, schema, shell_records, snmp_agent_table, snmp_local_db2, snmp_mib_data, snmp_note_store, snmp_symbolic_ets, tkFun, tkLink, tkPriv, ttb, ttb_history_table, udp_fds, udp_pids ]. sys_processes() -> [auth, code_server, global_name_server, inet_db, mnesia_recover, net_kernel, timer_server, wxe_master]. mnesia_tables() -> [ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef, ir_Contained, ir_Container, ir_EnumDef, ir_ExceptionDef, ir_IDLType, ir_IRObject, ir_InterfaceDef, ir_ModuleDef, ir_ORB, ir_OperationDef, ir_PrimitiveDef, ir_Repository, ir_SequenceDef, ir_StringDef, ir_StructDef, ir_TypedefDef, ir_UnionDef, logTable, logTransferTable, mesh_meas, mesh_type, mnesia_clist, orber_CosNaming, orber_objkeys, user ]. handle_error(Foo) -> try Str = io_lib:format("ERROR: ~s~n",[Foo]), display_info(Str) catch _:_ -> display_info(io_lib:format("ERROR: ~p~n",[Foo])) end, ok. display_info(Str) -> Dlg = wxMessageDialog:new(wx:null(), Str), wxMessageDialog:showModal(Dlg), wxMessageDialog:destroy(Dlg), ok. interval_dialog(Parent, Enabled, Value, Min, Max) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Update Interval", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), Panel = wxPanel:new(Dialog), Check = wxCheckBox:new(Panel, ?wxID_ANY, "Periodical refresh"), wxCheckBox:setValue(Check, Enabled), Style = ?wxSL_HORIZONTAL bor ?wxSL_AUTOTICKS bor ?wxSL_LABELS, Slider = wxSlider:new(Panel, ?wxID_ANY, Value, Min, Max, [{style, Style}, {size, {200, -1}}]), wxWindow:enable(Slider, [{enable, Enabled}]), InnerSizer = wxBoxSizer:new(?wxVERTICAL), Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}], wxSizer:add(InnerSizer, Check, Flags), wxSizer:add(InnerSizer, Slider, Flags), wxPanel:setSizer(Panel, InnerSizer), TopSizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(TopSizer, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), wxSizer:add(TopSizer, Buttons, [{flag, ?wxEXPAND}]), wxWindow:setSizerAndFit(Dialog, TopSizer), wxSizer:setSizeHints(TopSizer, Dialog), wxCheckBox:connect(Check, command_checkbox_clicked, [{callback, fun(#wx{event=#wxCommand{commandInt=Enable0}},_) -> Enable = Enable0 > 0, wxWindow:enable(Slider, [{enable, Enable}]) end}]), Res = case wxDialog:showModal(Dialog) of ?wxID_OK -> {wxCheckBox:isChecked(Check), wxSlider:getValue(Slider)}; ?wxID_CANCEL -> cancel end, wxDialog:destroy(Dialog), Res. update_grid(Grid, Opt, Tables) -> wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end). update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> wxListCtrl:deleteAllItems(Grid), Update = fun(#tab{name = Name, id = Id, owner = Owner, size = Size, memory = Memory, protection = Protection, reg_name = RegName}, Row) -> _Item = wxListCtrl:insertItem(Grid, Row, ""), if (Row rem 2) =:= 0 -> wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255}); true -> ignore end, if Protection == private -> wxListCtrl:setItemTextColour(Grid, Row, {200,130,50}); true -> ignore end, lists:foreach(fun({_, ignore}) -> ignore; ({Col, Val}) -> wxListCtrl:setItem(Grid, Row, Col, to_str(Val)) end, [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, {4,Owner}, {5,RegName}]), %% wxListCtrl:setItemData(Grid, Item, Item), Row + 1 end, ProcInfo = case Dir of false -> lists:reverse(lists:keysort(Sort, Tables)); true -> lists:keysort(Sort, Tables) end, lists:foldl(Update, 0, ProcInfo), ProcInfo. ignore(true, Reason) -> throw(Reason); ignore(_,_ ) -> ok.