diff options
Diffstat (limited to 'lib/observer')
-rw-r--r-- | lib/observer/doc/src/Makefile | 2 | ||||
-rw-r--r-- | lib/observer/doc/src/observer.xml | 58 | ||||
-rw-r--r-- | lib/observer/doc/src/observer_ug.xml | 190 | ||||
-rw-r--r-- | lib/observer/doc/src/part.xml | 3 | ||||
-rw-r--r-- | lib/observer/doc/src/ref_man.xml | 3 | ||||
-rw-r--r-- | lib/observer/src/Makefile | 8 | ||||
-rw-r--r-- | lib/observer/src/observer_app_wx.erl | 524 | ||||
-rw-r--r-- | lib/observer/src/observer_defs.hrl | 17 | ||||
-rw-r--r-- | lib/observer/src/observer_lib.erl | 70 | ||||
-rw-r--r-- | lib/observer/src/observer_pro_wx.erl | 134 | ||||
-rw-r--r-- | lib/observer/src/observer_procinfo.erl | 76 | ||||
-rw-r--r-- | lib/observer/src/observer_sys_wx.erl | 68 | ||||
-rw-r--r-- | lib/observer/src/observer_trace_wx.erl | 566 | ||||
-rw-r--r-- | lib/observer/src/observer_traceoptions_wx.erl | 193 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_table.erl | 87 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_wx.erl | 218 | ||||
-rw-r--r-- | lib/observer/src/observer_wx.erl | 59 |
17 files changed, 1608 insertions, 668 deletions
diff --git a/lib/observer/doc/src/Makefile b/lib/observer/doc/src/Makefile index f82a49abbe..cd9f9466ca 100644 --- a/lib/observer/doc/src/Makefile +++ b/lib/observer/doc/src/Makefile @@ -36,6 +36,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) XML_APPLICATION_FILES = ref_man.xml XML_REF3_FILES = \ crashdump.xml \ + observer.xml \ etop.xml \ ttb.xml XML_REF6_FILES = observer_app.xml @@ -48,6 +49,7 @@ XML_PART_FILES = \ XML_CHAPTER_FILES = \ crashdump_ug.xml \ etop_ug.xml \ + observer_ug.xml \ ttb_ug.xml \ notes.xml \ notes_history.xml diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml new file mode 100644 index 0000000000..03830f2b1c --- /dev/null +++ b/lib/observer/doc/src/observer.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2011</year> + <holder>Ericsson AB, All Rights Reserved</holder> + </copyright> + <legalnotice> + 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. + + The Initial Developer of the Original Code is Ericsson AB. + </legalnotice> + + <title>Observer</title> + <prepared>Dan Gudmundsson</prepared> + <responsible></responsible> + <docno>1</docno> + <approved></approved> + <checked></checked> + <date>2011-12-08</date> + <rev>PA1</rev> + <file>observer.xml</file> + </header> + <module>observer</module> + <modulesummary>A GUI tool for observing an erlang system.</modulesummary> + <description> + <p>The observer is gui frontend containing various tools to + inspect a system. It displays system information, application + structures, process information, ets or mnesia tables and a frontend + for tracing with <seealso marker="ttb">ttb</seealso>. + </p> + + <p>See the <seealso marker="observer_ug">user's guide</seealso> + for more information about how to get started.</p> + </description> + <funcs> + <func> + <name>start() -> ok</name> + <fsummary>Start the observer gui</fsummary> + <desc> + <p>This function starts the <c>observer</c> gui. + Close the window to stop the application. + </p> + </desc> + </func> + </funcs> +</erlref> diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml new file mode 100644 index 0000000000..569d72e71e --- /dev/null +++ b/lib/observer/doc/src/observer_ug.xml @@ -0,0 +1,190 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2011</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Observer</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>observer_ug.xml</file> + </header> + + <section> + <title>Introduction</title> + <p>Observer, is a graphical tool for observing the characteristics of + erlang systems. Observer displays system information, application + supervisor trees, process information, ets or mnesia tables and contains + a frontend for erlang tracing. + </p> + </section> + + <section> + <title>General</title> + <p>Normally observer should be run from a standalone node to minimize + the impact of the system being observed. Example: + </p> + <code> + > erl -sname observer -hidden -setcookie MyCookie -run observer + </code> + <p> + Choose which node to observe via <c>Nodes</c> menu. The <c>View/Refresh + Interval</c> controls how frequent the view should be updated. + The refresh interval is set per viewer so you can + have different settings for each viewer. To minimize the system + impact only the active viewer is updated and the other + views will be updated when activated. + </p> + <note> + <p>Only R15B nodes can be observed.</p> + </note> + + <p> In general the mouse buttons behaves as expected, use left click + to select objects, right click to pop up a menu with most used + choices and double click to bring up information about the + selected object. In most viewers with several columns you can change + sort order by left clicking on column header. + </p> + </section> + + <section> + <title>Applications</title> + <p>The <c>Applications</c> view lists application information. + Select an application in the left list to display its supervisor + tree. + </p> + <p><c>Trace process</c> will add the selected process identifier + to <c>Trace Overview</c> view and the node the process resides on + will be added as well. + </p> + <p><c>Trace named process</c> will add the + registered name of the process. This can be useful when tracing on + several nodes, then processes with that name will be traced on all traced + nodes. + </p> + <p><c>Trace process tree</c> and <c>Trace named process + tree</c> will add the selected process and all processes below, + right of, it to the <c>Trace Overview</c> view. + </p> + </section> + + <section> + <title>Processes</title> + <p>The <c>Processes</c> view lists process information. + For each process the following information is presented: + </p> + <taglist> + <tag>Pid</tag> + <item>The process identifier.</item> + <tag>Reds</tag> + <item>This is the number of reductions that has been executed + on the process</item> + <tag>Memory</tag> + <item>This is the size of the process in bytes, obtained by a + call to <c>process_info(Pid,memory)</c>.</item> + <tag>MsgQ</tag> + <item>This is the length of the message queue for the process.</item> + </taglist> + <note> + <p><em>Reds</em> can be presented as accumulated values or as values since last update.</p> + </note> + <p><c>Trace Processes</c> will add the selected process identifiers to the <c>Trace Overview</c> view and the + node the processes reside on will be added as well. + <c>Trace Named Processes</c> will add the registered name of processes. This can be useful + when tracing is done on several nodes, then processes with that name will be traced on all traced nodes. + </p> + </section> + + <section> + <title>Table Viewer</title> + <p>The <c>Table Viewer</c> view lists tables. By default ets tables + are visible and unreadable, private ets, tables and tables created by the OTP + applications are not visible. Use <c>View</c> menu to view "system" + ets tables, unreadable ets tables or mnesia tables. + </p> + <p>Double click to view the content of the table. Select table and activate <c>View/Table Information</c> + menu to view table information. + </p> + <p>In the table viewer you can regexp search for objects, edit and delete objects. + </p> + </section> + + <section> + <title>Trace Overview</title> + <p>The <c>Trace Overview</c> view handles tracing. Tracing is done + by selecting which processes to be traced and how to trace + them. You can trace messages, function calls and events, where + events are process related events such as <c>spawn</c>, + <c>exit</c> and several others. + </p> + + <p>When you want to trace function calls, you also need to setup + <c>trace patterns</c>. Trace patterns selects the function calls + that will be traced. The number of traced function calls can be + further reduced with <c>match specifications</c>. Match + specifications can also be used to trigger additional information + in the trace messages. + </p> + <note><p>Trace patterns only applies to the traced processes.</p></note> + + <p> + Processes are added from the <c>Applications</c> or <c>Processes</c> views. + A special <c>new</c> identifier, meaning all processes spawned after trace start, + can be added with the <c>Add 'new' Process</c> button. + </p> + <p> + When adding processes, a window with trace options will pop up. The chosen options will + be set for the selected processes. + Process options can be changed by right clicking on a process. + </p> + <p> + Processes added by process identifiers will add the nodes these + processes resides on in the node list. Additional nodes can be added by the <c>Add + Nodes</c> button. + </p> + <p> + If function calls are traced, trace patterns must be added by <c>Add Trace Pattern</c> button. + Select a module, function(s) and a match specification. + If no functions are selected, all functions in the module will be traced. + A few basic match specifications are provided in the tool, and + you can provide your own match specifications. The syntax of match + specifications are described in the <seealso + marker="erts:match_spec">ERTS User's Guide</seealso>. To simplify + the writing of a match specification they can also be written as + <c>fun/1</c> see <seealso marker="stdlib:ms_transform">ms_transform manual page</seealso> for + further information. + </p> + + <p>Use the <c>Start trace</c> button to start the trace. + By default trace output is written to a new window, tracing is stopped when the + window is closed, or with <c>Stop Trace</c> button. + Trace output can be changed via <c>Options/Output</c> menu. + The trace settings, including match specifications, can be saved to, or loaded from, a file. + </p> + <p>More information about tracing can be found in <seealso + marker="runtime_tools:dbg">dbg</seealso> and in the chapter "Match + specifications in Erlang" in <seealso marker="erts:match_spec">ERTS User's + Guide</seealso> and the + <seealso marker="stdlib:ms_transform">ms_transform manual page</seealso>. + </p> + </section> +</chapter> diff --git a/lib/observer/doc/src/part.xml b/lib/observer/doc/src/part.xml index bd6c2b6c77..0d6aad09f2 100644 --- a/lib/observer/doc/src/part.xml +++ b/lib/observer/doc/src/part.xml @@ -4,7 +4,7 @@ <part xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>2002</year><year>2009</year> + <year>2002</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -31,6 +31,7 @@ <p>The <em>Observer</em> application contains tools for tracing and investigation of distributed systems.</p> </description> + <xi:include href="observer_ug.xml"/> <xi:include href="ttb_ug.xml"/> <xi:include href="etop_ug.xml"/> <xi:include href="crashdump_ug.xml"/> diff --git a/lib/observer/doc/src/ref_man.xml b/lib/observer/doc/src/ref_man.xml index 3d37570d2d..c33ce74141 100644 --- a/lib/observer/doc/src/ref_man.xml +++ b/lib/observer/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>2002</year><year>2009</year> + <year>2002</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -34,6 +34,7 @@ <br></br> </description> <xi:include href="observer_app.xml"/> + <xi:include href="observer.xml"/> <xi:include href="ttb.xml"/> <xi:include href="etop.xml"/> <xi:include href="crashdump.xml"/> diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 95954d8587..ca26afc11d 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -42,6 +42,7 @@ MODULES= \ etop_tr \ etop_txt \ observer \ + observer_app_wx \ observer_lib \ observer_wx \ observer_pro_wx \ @@ -57,6 +58,8 @@ MODULES= \ HRL_FILES= \ ../include/etop.hrl INTERNAL_HRL_FILES= \ + observer_tv.hrl \ + observer_defs.hrl \ crashdump_viewer.hrl \ etop_defs.hrl ERL_FILES= $(MODULES:%=%.erl) @@ -103,8 +106,7 @@ ERL_COMPILE_FLAGS += \ # ---------------------------------------------------- # Targets # ---------------------------------------------------- - -debug opt: $(TARGET_FILES) +opt debug: $(TARGET_FILES) clean: rm -f $(TARGET_FILES) @@ -116,6 +118,8 @@ $(APP_TARGET): $(APP_SRC) ../vsn.mk $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk sed -e 's;%VSN%;$(VSN);' $< > $@ +$(TARGET_FILES): $(INTERNAL_HRL_FILES) + docs: # ---------------------------------------------------- diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl new file mode 100644 index 0000000000..62046577ad --- /dev/null +++ b/lib/observer/src/observer_app_wx.erl @@ -0,0 +1,524 @@ +%% +%% %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_app_wx). + +-export([start_link/2]). + +%% 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]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-record(state, + { + parent, + panel, + apps_w, + app_w, + paint, + current, + app, + sel, + appmon + }). + +-record(paint, {font, pen, brush, sel, links}). + +-record(app, {ptree, n2p, links, dim}). +-record(box, {x,y, w,h, s1}). +-record(str, {x,y,text,pid}). + +-define(BX_E, 10). %% Empty width between text and box +-define(BX_HE, (?BX_E div 2)). +-define(BY_E, 10). %% Empty height between text and box +-define(BY_HE, (?BY_E div 2)). + +-define(BB_X, 16). %% Empty width between boxes +-define(BB_Y, 12). %% Empty height between boxes + +-define(DRAWAREA, 5). +-define(ID_PROC_INFO, 101). +-define(ID_PROC_MSG, 102). +-define(ID_PROC_KILL, 103). +-define(ID_TRACE_PID, 104). +-define(ID_TRACE_NAME, 105). +-define(ID_TRACE_TREE_PIDS, 106). +-define(ID_TRACE_TREE_NAMES, 107). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}, + {winid, 1} + ]), + Main = wxBoxSizer:new(?wxHORIZONTAL), + Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, + {style, ?wxSP_LIVE_UPDATE}, + {id, 2} + ]), + Apps = wxListBox:new(Splitter, 3, []), + %% Need extra panel and sizer to get correct size updates + %% in draw area for some reason + P2 = wxPanel:new(Splitter, [{winid, 4}]), + Extra = wxBoxSizer:new(?wxVERTICAL), + DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA}, + {style,?wxFULL_REPAINT_ON_RESIZE}]), + wxWindow:setBackgroundColour(DrawingArea, ?wxWHITE), + wxWindow:setVirtualSize(DrawingArea, 800, 800), + wxSplitterWindow:setMinimumPaneSize(Splitter,50), + wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]), + wxWindow:setSizer(P2, Extra), + wxSplitterWindow:splitVertically(Splitter, Apps, P2, [{sashPosition, 150}]), + wxWindow:setSizer(Panel, Main), + + wxSizer:add(Main, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Main), + wxListBox:connect(Apps, command_listbox_selected), + wxPanel:connect(DrawingArea, paint, [callback]), + wxPanel:connect(DrawingArea, size, [{skip, true}]), + wxPanel:connect(DrawingArea, left_up), + wxPanel:connect(DrawingArea, left_dclick), + wxPanel:connect(DrawingArea, right_down), + + DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), + SelBrush = wxBrush:new(SelCol), + LinkPen = wxPen:new(SelCol, [{width, 2}]), + %% GC = wxGraphicsContext:create(DrawingArea), + %% _Font = wxGraphicsContext:createFont(GC, DefFont), + {Panel, #state{parent=Parent, + panel =Panel, + apps_w=Apps, + app_w =DrawingArea, + paint=#paint{font= DefFont, + pen= ?wxBLACK_PEN, + brush=?wxLIGHT_GREY_BRUSH, + sel= SelBrush, + links=LinkPen + } + }}. + +setup_scrollbar(AppWin, App) -> + setup_scrollbar(wxWindow:getClientSize(AppWin), AppWin, App). + +setup_scrollbar({CW, CH}, AppWin, #app{dim={W0,H0}}) -> + W = max(W0,CW), + H = max(H0,CH), + PPC = 20, + if W0 =< CW, H0 =< CH -> + wxScrolledWindow:setScrollbars(AppWin, W, H, 1, 1); + H0 =< CH -> + wxScrolledWindow:setScrollbars(AppWin, PPC, H, W div PPC+1, 1); + W0 =< CW -> + wxScrolledWindow:setScrollbars(AppWin, W, PPC, 1, H div PPC+1); + true -> + wxScrolledWindow:setScrollbars(AppWin, PPC, PPC, W div PPC+1, H div PPC+1) + end; +setup_scrollbar(_, _, undefined) -> ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{event=#wxCommand{type=command_listbox_selected, cmdString=AppStr}}, + State = #state{appmon=AppMon, current=Prev}) -> + case AppStr of + [] -> + {noreply, State}; + _ -> + App = list_to_atom(AppStr), + (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []), + appmon_info:app(AppMon, App, true, []), + {noreply, State#state{current=App}} + end; + +handle_event(#wx{id=Id, event=_Sz=#wxSize{size=Size}}, + State=#state{app=App, app_w=AppWin}) -> + Id =:= ?DRAWAREA andalso setup_scrollbar(Size,AppWin,App), + {noreply, State}; + +handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}}, + S0=#state{app=#app{ptree=Tree}, app_w=AppWin}) -> + {X,Y} = wxScrolledWindow:calcUnscrolledPosition(AppWin, X0, Y0), + Hit = locate_node(X,Y, [Tree]), + State = handle_mouse_click(Hit, Type, S0), + {noreply, State}; + +handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, + State = #state{sel=undefined}) -> + observer_lib:display_info_dialog("Select process first"), + {noreply, State}; + +handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}}, + State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> + observer_procinfo:start(Pid, Panel, self()), + {noreply, State}; + +handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}}, + State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> + case observer_lib:user_term(Panel, "Enter message", "") of + cancel -> ok; + {ok, Term} -> Pid ! Term; + {error, Error} -> observer_lib:display_info_dialog(Error) + end, + {noreply, State}; + +handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}}, + State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> + case observer_lib:user_term(Panel, "Enter Exit Reason", "") of + cancel -> ok; + {ok, Term} -> exit(Pid, Term); + {error, Error} -> observer_lib:display_info_dialog(Error) + end, + {noreply, State}; + +%%% Trace api +handle_event(#wx{id=?ID_TRACE_PID, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel={Box,_}}) -> + observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_pid(Box)]), + {noreply, State}; +handle_event(#wx{id=?ID_TRACE_NAME, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel={Box,_}}) -> + observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_reg(Box)]), + {noreply, State}; +handle_event(#wx{id=?ID_TRACE_TREE_PIDS, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel=Sel}) -> + Get = fun(Box) -> box_to_pid(Box) end, + observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)), + {noreply, State}; +handle_event(#wx{id=?ID_TRACE_TREE_NAMES, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel=Sel}) -> + Get = fun(Box) -> box_to_reg(Box) end, + observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)), + {noreply, State}; + +handle_event(Event, _State) -> + error({unhandled_event, Event}). + +%%%%%%%%%% +handle_sync_event(#wx{event = #wxPaint{}},_, + #state{app_w=DA, app=App, sel=Sel, paint=Paint}) -> + %% PaintDC must be created in a callback to work on windows. + DC = wxPaintDC:new(DA), + wxScrolledWindow:doPrepareDC(DA,DC), + %% Nothing is drawn until wxPaintDC is destroyed. + draw(DC, App, Sel, Paint), + wxPaintDC:destroy(DC), + ok. +%%%%%%%%%% +handle_call(Event, From, _State) -> + error({unhandled_call, Event, From}). + +handle_cast(Event, _State) -> + error({unhandled_cast, Event}). +%%%%%%%%%% +handle_info({active, Node}, State = #state{parent=Parent, current=Curr, appmon=Appmon}) -> + create_menus(Parent, []), + {ok, Pid} = appmon_info:start_link(Node, self(), []), + case Appmon of + undefined -> ok; + Pid -> ok; + _ -> %% Deregister me as client (and stop appmon if last) + exit(Appmon, normal) + end, + appmon_info:app_ctrl(Pid, Node, true, []), + (Curr =/= undefined) andalso appmon_info:app(Pid, Curr, true, []), + {noreply, State#state{appmon=Pid}}; + +handle_info(not_active, State = #state{appmon=AppMon, current=Prev}) -> + appmon_info:app_ctrl(AppMon, node(AppMon), false, []), + (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []), + {noreply, State}; + +handle_info({delivery, Pid, app_ctrl, _, Apps0}, + State = #state{appmon=Pid, apps_w=LBox}) -> + Apps = [atom_to_list(App) || {_, App, {_, _, _}} <- Apps0], + wxListBox:clear(LBox), + wxListBox:appendStrings(LBox, [App || App <- lists:sort(Apps)]), + {noreply, State}; + +handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}}, + State = #state{panel=Panel}) -> + wxWindow:refresh(Panel), + {noreply, State#state{app=undefined, sel=undefined}}; + +handle_info({delivery, Pid, app, Curr, AppData}, + State = #state{panel=Panel, appmon=Pid, current=Curr, + app_w=AppWin, paint=#paint{font=Font}}) -> + App = build_tree(AppData, {AppWin,Font}), + setup_scrollbar(AppWin, App), + wxWindow:refresh(Panel), + wxWindow:layout(Panel), + {noreply, State#state{app=App, sel=undefined}}; + +handle_info(_Event, State) -> + %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), + {noreply, State}. + +%%%%%%%%%% +terminate(_Event, _State) -> + ok. +code_change(_, _, State) -> + State. + +handle_mouse_click(Node = {#box{s1=#str{pid=Pid}},_}, Type, + State=#state{app_w=AppWin,panel=Panel}) -> + case Type of + left_dclick -> observer_procinfo:start(Pid, Panel, self()); + right_down -> popup_menu(Panel); + _ -> ok + end, + wxWindow:refresh(AppWin), + State#state{sel=Node}; +handle_mouse_click(_, _, State = #state{sel=undefined}) -> + State; +handle_mouse_click(_, right_down, State=#state{panel=Panel}) -> + popup_menu(Panel), + State; +handle_mouse_click(_, _, State=#state{app_w=AppWin}) -> + wxWindow:refresh(AppWin), + State#state{sel=undefined}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent, _) -> + MenuEntries = + [{"File", + [#create_menu{id=?ID_PROC_INFO, text="Process info"}, + #create_menu{id=?ID_PROC_MSG, text="Send Msg"}, + #create_menu{id=?ID_PROC_KILL, text="Kill process"} + ]}, + {"Trace", + [#create_menu{id=?ID_TRACE_PID, text="Trace process"}, + #create_menu{id=?ID_TRACE_NAME, text="Trace named process"}, + #create_menu{id=?ID_TRACE_TREE_PIDS, text="Trace process tree"}, + #create_menu{id=?ID_TRACE_TREE_NAMES, text="Trace named process tree"} + ]}], + observer_wx:create_menus(Parent, MenuEntries). + +popup_menu(Panel) -> + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_PROC_INFO, "Process info"), + wxMenu:append(Menu, ?ID_TRACE_PID, "Trace process"), + wxMenu:append(Menu, ?ID_TRACE_NAME, "Trace named process"), + wxMenu:append(Menu, ?ID_TRACE_TREE_PIDS, "Trace process tree"), + wxMenu:append(Menu, ?ID_TRACE_TREE_NAMES, "Trace named process tree"), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +locate_node(X, _Y, [{Box=#box{x=BX}, _Chs}|_Rest]) + when X < BX -> + {left, Box}; +locate_node(X,Y, [Node={Box=#box{x=BX,y=BY,w=BW,h=BH}, _Chs}|Rest]) + when X =< (BX+BW)-> + if + Y < BY -> {above, Box}; %% Above + Y =< (BY+BH) -> Node; + true -> locate_node(X,Y,Rest) + end; +locate_node(X,Y, [{_, Chs}|Rest]) -> + case locate_node(X,Y,Chs) of + Node = {#box{},_} -> Node; + _Miss -> + locate_node(X,Y,Rest) + end; +locate_node(_, _, []) -> false. + +locate_box(From, [{Box=#box{s1=#str{pid=From}},_}|_]) -> Box; +locate_box(From, [{_,Chs}|Rest]) -> + case locate_box(From, Chs) of + Box = #box{} -> Box; + _ -> locate_box(From, Rest) + end; +locate_box(From, []) -> {false, From}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +build_tree({Root, P2Name, Links, XLinks0}, Font) -> + Fam = sofs:relation_to_family(sofs:relation(Links)), + Name2P = gb_trees:from_orddict(lists:sort([{Name,Pid} || {Pid,Name} <- P2Name])), + Lookup = gb_trees:from_orddict(sofs:to_external(Fam)), + {_, Tree0} = build_tree2(Root, Lookup, Name2P, Font), + {Tree, Dim} = calc_tree_size(Tree0), + Fetch = fun({From, To}, Acc) -> + try {value, ToPid} = gb_trees:lookup(To, Name2P), + FromPid = gb_trees:get(From, Name2P), + [{locate_box(FromPid, [Tree]),locate_box(ToPid, [Tree])}|Acc] + catch _:_ -> + Acc + end + end, + XLinks = lists:foldl(Fetch, [], XLinks0), + #app{ptree=Tree, dim=Dim, links=XLinks}. + +build_tree2(Root, Tree0, N2P, Font) -> + case gb_trees:lookup(Root, Tree0) of + none -> {Tree0, {box(Root, N2P, Font), []}}; + {value, Children} -> + Tree1 = gb_trees:delete(Root, Tree0), + {Tree, CHs} = lists:foldr(fun("port " ++_, Acc) -> + Acc; %% Skip ports + (Child,{T0, Acc}) -> + {T, C} = build_tree2(Child, T0, N2P, Font), + {T, [C|Acc]} + end, {Tree1, []}, Children), + {Tree, {box(Root, N2P, Font), CHs}} + end. + +calc_tree_size(Tree) -> + Cols = calc_col_start(Tree, [0]), + {Boxes,{W,Hs}} = calc_tree_size(Tree, Cols, ?BB_X, [?BB_Y]), + {Boxes, {W,lists:max(Hs)}}. + +calc_col_start({#box{w=W}, Chs}, [Max|Acc0]) -> + Acc = if Acc0 == [] -> [0]; true -> Acc0 end, + Depth = lists:foldl(fun(Child, MDepth) -> calc_col_start(Child, MDepth) end, + Acc, Chs), + [max(W,Max)|Depth]. + +calc_tree_size({Box=#box{w=W,h=H}, []}, _, X, [Y|Ys]) -> + {{Box#box{x=X,y=Y}, []}, {X+W+?BB_X,[Y+H+?BB_Y|Ys]}}; +calc_tree_size({Box, Children}, [Col|Cols], X, [H0|Hs0]) -> + Hs1 = calc_row_start(Children, H0, Hs0), + StartX = X+Col+?BB_X, + {Boxes, {W,Hs}} = calc_tree_sizes(Children, Cols, StartX, StartX, Hs1, []), + Y = middle(Boxes, H0), + H = Y+Box#box.h+?BB_Y, + {{Box#box{x=X,y=Y}, Boxes}, {W,[H|Hs]}}. + +calc_tree_sizes([Child|Chs], Cols, X0, W0, Hs0, Acc) -> + {Tree, {W,Hs}} = calc_tree_size(Child, Cols, X0, Hs0), + calc_tree_sizes(Chs, Cols, X0, max(W,W0), Hs, [Tree|Acc]); +calc_tree_sizes([], _, _, W,Hs, Acc) -> + {lists:reverse(Acc), {W,Hs}}. + +calc_row_start(Chs = [{#box{h=H},_}|_], Start, Hs0) -> + NChs = length(Chs), + Wanted = (H*NChs + ?BB_Y*(NChs-1)) div 2 - H div 2, + case Hs0 of + [] -> [max(?BB_Y, Start - Wanted)]; + [Next|Hs] -> + [max(Next, Start - Wanted)|Hs] + end. + +middle([], Y) -> Y; +middle([{#box{y=Y}, _}], _) -> Y; +middle([{#box{y=Y0},_}|List], _) -> + {#box{y=Y1},_} = lists:last(List), + (Y0+Y1) div 2. + +box(Str0, N2P, {Win,Font}) -> + Pid = gb_trees:get(Str0, N2P), + Str = if hd(Str0) =:= $< -> lists:append(io_lib:format("~w", [Pid])); + true -> Str0 + end, + {TW,TH, _, _} = wxWindow:getTextExtent(Win, Str, [{theFont, Font}]), + Data = #str{text=Str, x=?BX_HE, y=?BY_HE, pid=Pid}, + %% Add pid + #box{w=TW+?BX_E, h=TH+?BY_E, s1=Data}. + +box_to_pid(#box{s1=#str{pid=Pid}}) -> Pid. +box_to_reg(#box{s1=#str{text=[$<|_], pid=Pid}}) -> Pid; +box_to_reg(#box{s1=#str{text=Name}}) -> list_to_atom(Name). + +tree_map({Box, Chs}, Fun) -> + tree_map(Chs, Fun, [Fun(Box)]). +tree_map([{Box, Chs}|Rest], Fun, Acc0) -> + Acc = tree_map(Chs, Fun, [Fun(Box)|Acc0]), + tree_map(Rest, Fun, Acc); +tree_map([], _ , Acc) -> Acc. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +draw(_DC, undefined, _, _) -> + ok; +draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel, + #paint{font=Font, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) -> + %% Canvas = wxGraphicsContext:create(DC), + %% Pen = wxGraphicsContext:createPen(Canvas, ?wxBLACK_PEN), + %% wxGraphicsContext:setPen(Canvas, Pen), + %% Brush = wxGraphicsContext:createBrush(Canvas, ?wxLIGHT_GREY_BRUSH), + %% wxGraphicsContext:setBrush(Canvas, Brush), + %% Font = wxGraphicsContext:createFont(Canvas, wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT)), + %% wxGraphicsContext:setFont(Canvas, Font), + %% draw_tree(Tree, Canvas). + wxDC:setPen(DC, LPen), + [draw_xlink(Link, DC) || Link <- Links], + wxDC:setPen(DC, Pen), + %% wxDC:drawRectangle(DC, {2,2}, {W-2,H-2}), %% DEBUG + wxDC:setBrush(DC, Brush), + wxDC:setFont(DC, Font), + draw_tree(Tree, root, DC), + case Sel of + undefined -> ok; + {#box{x=X,y=Y,w=W,h=H,s1=Str1}, _} -> + wxDC:setBrush(DC, SelBrush), + wxDC:drawRoundedRectangle(DC, {X-1,Y-1}, {W+2,H+2}, 8.0), + draw_str(DC, Str1, X, Y) + end. + +draw_tree({Box=#box{x=X,y=Y,w=W,h=H,s1=Str1}, Chs}, Parent, DC) -> + %%wxGraphicsContext:drawRoundedRectangle(DC, float(X), float(Y), float(W), float(H), 8.0), + wxDC:drawRoundedRectangle(DC, {X,Y}, {W,H}, 8.0), + draw_str(DC, Str1, X, Y), + Dot = case Chs of + [] -> ok; + [{#box{x=CX0},_}|_] -> + CY = Y+(H div 2), + CX = CX0-(?BB_X div 2), + wxDC:drawLine(DC, {X+W, CY}, {CX, CY}), + {CX, CY} + end, + draw_link(Parent, Box, DC), + [draw_tree(Child, Dot, DC) || Child <- Chs]. + +draw_link({CX,CY}, #box{x=X,y=Y0,h=H}, DC) -> + Y = Y0+(H div 2), + case Y =:= CY of + true -> + wxDC:drawLine(DC, {CX, CY}, {X, CY}); + false -> + wxDC:drawLines(DC, [{CX, CY}, {CX, Y}, {X,Y}]) + end; +draw_link(_, _, _) -> ok. + +draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, y=Y1}}, DC) + when X0 =:= X1 -> + draw_xlink(X0,Y0,X1,Y1,BH,DC); +draw_xlink({#box{x=X0, y=Y0, h=BH, w=BW}, #box{x=X1, y=Y1}}, DC) + when X0 < X1 -> + draw_xlink(X0+BW,Y0,X1,Y1,BH,DC); +draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, w=BW, y=Y1}}, DC) + when X0 > X1 -> + draw_xlink(X1+BW,Y1,X0,Y0,BH,DC); +draw_xlink({_From, _To}, _DC) -> + ignore. +draw_xlink(X0, Y00, X1, Y11, BH, DC) -> + {Y0,Y1} = if Y00 < Y11 -> {Y00+BH-6, Y11+6}; + true -> {Y00+6, Y11+BH-6} + end, + wxDC:drawLines(DC, [{X0,Y0}, {X0+5,Y0}, {X1-5,Y1}, {X1,Y1}]). + +draw_str(DC, #str{x=Sx,y=Sy, text=Text}, X, Y) -> + %%wxGraphicsContext:drawText(DC, Text, float(Sx+X), float(Sy+Y)); + wxDC:drawText(DC, Text, {X+Sx,Y+Sy}); +draw_str(_, _, _, _) -> ok. diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index d83a1e2fa5..586e7bbff9 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -16,16 +16,6 @@ %% %% %CopyrightEnd% --record(trace_options, {send = false, - treceive = false, - functions = false, - events = false, - on_1st_spawn = false, - on_all_spawn = false, - on_1st_link = false, - on_all_link = false, - main_window = true}). - -record(match_spec, {name = "", term = [], str = [], @@ -37,15 +27,10 @@ arity, %integer match_spec = #match_spec{}}). --record(on_spawn, {checkbox, all_spawn, first_spawn}). - --record(on_link, {checkbox, all_link, first_link}). - --record(pid, {window, traced}). - -record(create_menu, {id, text, + help = [], type = append, check = false }). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 90c270e977..967baa5c7a 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -19,11 +19,12 @@ -module(observer_lib). -export([get_wx_parent/1, - display_info_dialog/1, + display_info_dialog/1, user_term/3, interval_dialog/4, start_timer/1, stop_timer/1, display_info/2, fill_info/2, update_info/2, to_str/1, create_menus/3, create_menu_item/3, - create_attrs/0 + create_attrs/0, + set_listctrl_col_size/2 ]). -include_lib("wx/include/wx.hrl"). @@ -195,6 +196,7 @@ to_str(No) when is_integer(No) -> to_str(Term) -> io_lib:format("~w", [Term]). +create_menus([], _MenuBar, _Type) -> ok; create_menus(Menus, MenuBar, Type) -> Add = fun({Tag, Ms}, Index) -> create_menu(Tag, Ms, Index, MenuBar, Type) @@ -239,15 +241,21 @@ create_menu(Name, MenuItems, Index, MenuBar, _Type) -> create_menu_item(#create_menu{id = ?wxID_HELP=Id}, Menu, Index) -> wxMenu:insert(Menu, Index, Id), Index+1; -create_menu_item(#create_menu{id = Id, text = Text, type = Type, check = Check}, Menu, Index) -> +create_menu_item(#create_menu{id=Id, text=Text, help=Help, type=Type, check=Check}, + Menu, Index) -> + Opts = case Help of + [] -> []; + _ -> [{help, Help}] + end, case Type of append -> - wxMenu:insert(Menu, Index, Id, [{text, Text}]); + wxMenu:insert(Menu, Index, Id, + [{text, Text}|Opts]); check -> - wxMenu:insertCheckItem(Menu, Index, Id, Text), + wxMenu:insertCheckItem(Menu, Index, Id, Text, Opts), wxMenu:check(Menu, Id, Check); radio -> - wxMenu:insertRadioItem(Menu, Index, Id, Text), + wxMenu:insertRadioItem(Menu, Index, Id, Text, Opts), wxMenu:check(Menu, Id, Check); separator -> wxMenu:insertSeparator(Menu, Index) @@ -295,3 +303,53 @@ create_box(Panel, Data) -> wxSizer:add(Box, Right), wxSizer:addSpacer(Box, 30), {Box, InfoFields}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +set_listctrl_col_size(LCtrl, Total) -> + wx:batch(fun() -> calc_last(LCtrl, Total) end). + +calc_last(LCtrl, _Total) -> + Cols = wxListCtrl:getColumnCount(LCtrl), + {Total, _} = wxWindow:getClientSize(LCtrl), + SBSize = scroll_size(LCtrl), + Last = lists:foldl(fun(I, Last) -> + Last - wxListCtrl:getColumnWidth(LCtrl, I) + end, Total-SBSize, lists:seq(0, Cols - 2)), + Size = max(150, Last), + wxListCtrl:setColumnWidth(LCtrl, Cols-1, Size). + +scroll_size(LCtrl) -> + case os:type() of + {win32, nt} -> 0; + {unix, darwin} -> + %% I can't figure out is there is a visible scrollbar + %% Always make room for it + wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); + _ -> + case wxWindow:hasScrollbar(LCtrl, ?wxVERTICAL) of + true -> wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); + false -> 0 + end + end. + + +user_term(Parent, Title, Default) -> + Dialog = wxTextEntryDialog:new(Parent, Title, [{value, Default}]), + case wxTextEntryDialog:showModal(Dialog) of + ?wxID_OK -> + Str = wxTextEntryDialog:getValue(Dialog), + wxTextEntryDialog:destroy(Dialog), + parse_string(Str); + ?wxID_CANCEL -> + wxTextEntryDialog:destroy(Dialog) + end. + +parse_string(Str) -> + try + {ok, Tokens, _} = erl_scan:string(Str), + erl_parse:parse_term(Tokens) + catch _:{badmatch, {error, {_, _, Err}}} -> + {error, ["Parse error: ", Err]}; + _Err -> + {error, ["Syntax error in: ", Str]} + end. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index cfc1c0665f..7578215ff9 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -32,22 +32,29 @@ %% Defines -define(COL_PID, 0). --define(COL_NAME, 1). --define(COL_TIME, 2). --define(COL_REDS, 3). --define(COL_MEM, 4). --define(COL_MSG, 5). --define(COL_FUN, 6). +-define(COL_NAME, ?COL_PID+1). +%%-define(COL_TIME, 2). +-define(COL_REDS, ?COL_NAME+1). +-define(COL_MEM, ?COL_REDS+1). +-define(COL_MSG, ?COL_MEM+1). +-define(COL_FUN, ?COL_MSG+1). -define(ID_KILL, 201). -define(ID_PROC, 202). -define(ID_REFRESH, 203). -define(ID_REFRESH_INTERVAL, 204). -define(ID_DUMP_TO_FILE, 205). --define(ID_TRACEMENU, 206). --define(ID_TRACE_ALL_MENU, 207). --define(ID_TRACE_NEW_MENU, 208). --define(ID_ACCUMULATE, 209). +-define(ID_TRACE_PIDS, 206). +-define(ID_TRACE_NAMES, 207). +-define(ID_TRACE_NEW, 208). +-define(ID_TRACE_ALL, 209). +-define(ID_ACCUMULATE, 210). + +-define(TRACE_PIDS_STR, "Trace selected process identifiers"). +-define(TRACE_NAMES_STR, "Trace selected processes, " + "if a process have a registered name " + "processes with same name will be traced on all nodes"). + %% Records @@ -98,12 +105,9 @@ setup(Notebook, Parent, Holder) -> wxWindow:setSizer(ProPanel, Sizer), - Popup = create_popup_menu(ProPanel), - State = #state{parent=Parent, grid=Grid, panel=ProPanel, - popup_menu=Popup, parent_notebook=Notebook, holder=Holder, timer={false, 10} @@ -124,34 +128,14 @@ create_pro_menu(Parent, Holder) -> #create_menu{id=?ID_REFRESH, text="Refresh\tCtrl-R"}, #create_menu{id=?ID_REFRESH_INTERVAL, text="Refresh Interval"}]}, {"Trace", - [#create_menu{id=?ID_TRACEMENU, text="Trace selected processes"}, - #create_menu{id=?ID_TRACE_NEW_MENU, text="Trace new processes"} + [#create_menu{id=?ID_TRACE_PIDS, text="Trace processes"}, + #create_menu{id=?ID_TRACE_NAMES, text="Trace named processes (all nodes)"}, + #create_menu{id=?ID_TRACE_NEW, text="Trace new processes"} %% , #create_menu{id=?ID_TRACE_ALL_MENU, text="Trace all processes"} ]} ], observer_wx:create_menus(Parent, MenuEntries). -create_popup_menu(ParentFrame) -> - MiniFrame = wxMiniFrame:new(ParentFrame, ?wxID_ANY, "Options", [{style, ?wxFRAME_FLOAT_ON_PARENT}]), - Panel = wxPanel:new(MiniFrame), - Sizer = wxBoxSizer:new(?wxVERTICAL), - TraceBtn = wxButton:new(Panel, ?ID_TRACEMENU, [{label, "Trace selected"}, - {style, ?wxNO_BORDER}]), - ProcBtn = wxButton:new(Panel, ?ID_PROC, [{label, "Process info"}, - {style, ?wxNO_BORDER}]), - KillBtn = wxButton:new(Panel, ?ID_KILL, [{label, "Kill process"}, - {style, ?wxNO_BORDER}]), - - wxButton:connect(TraceBtn, command_button_clicked), - wxButton:connect(ProcBtn, command_button_clicked), - wxButton:connect(KillBtn, command_button_clicked), - wxSizer:add(Sizer, TraceBtn, [{flag, ?wxEXPAND}, {proportion, 1}]), - wxSizer:add(Sizer, ProcBtn, [{flag, ?wxEXPAND}, {proportion, 1}]), - wxSizer:add(Sizer, KillBtn, [{flag, ?wxEXPAND}, {proportion, 1}]), - wxPanel:setSizer(Panel, Sizer), - wxSizer:setSizeHints(Sizer, MiniFrame), - MiniFrame. - create_list_box(Panel, Holder) -> Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_HRULES, ListCtrl = wxListCtrl:new(Panel, [{style, Style}, @@ -174,7 +158,7 @@ create_list_box(Panel, Holder) -> end, ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE, 120}, {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 200}, - {"Time", ?wxLIST_FORMAT_CENTRE, 50}, +%% {"Time", ?wxLIST_FORMAT_CENTRE, 50}, {"Reds", ?wxLIST_FORMAT_RIGHT, 100}, {"Memory", ?wxLIST_FORMAT_RIGHT, 100}, {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50}, @@ -257,10 +241,6 @@ handle_info(not_active, #state{timer=Timer0}=State) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{timer=Timer}}; -handle_info({node, Node}, #state{holder=Holder}=State) -> - Holder ! {change_node, Node}, - {noreply, State}; - handle_info(Info, State) -> io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. @@ -314,25 +294,17 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL}, Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60), {noreply, State#state{timer=Timer}}; -handle_event(#wx{id=?ID_KILL}, - #state{popup_menu=Pop,sel={[_|Ids], [ToKill|Pids]}}=State) -> - wxWindow:show(Pop, [{show, false}]), +handle_event(#wx{id=?ID_KILL}, #state{sel={[_|Ids], [ToKill|Pids]}}=State) -> exit(ToKill, kill), {noreply, State#state{sel={Ids,Pids}}}; -handle_event(#wx{id = ?ID_PROC}, - #state{panel=Panel, - popup_menu=Pop, - sel={_, [Pid|_]}, - procinfo_menu_pids=Opened}=State) -> - wxWindow:show(Pop, [{show, false}]), +handle_event(#wx{id=?ID_PROC}, + #state{panel=Panel, sel={_, [Pid|_]},procinfo_menu_pids=Opened}=State) -> Opened2 = start_procinfo(Pid, Panel, Opened), {noreply, State#state{procinfo_menu_pids=Opened2}}; -handle_event(#wx{id = ?ID_TRACEMENU}, - #state{popup_menu=Pop, sel={_, Pids}, panel=Panel}=State) -> - wxWindow:show(Pop, [{show, false}]), +handle_event(#wx{id=?ID_TRACE_PIDS}, #state{sel={_, Pids}, panel=Panel}=State) -> case Pids of [] -> observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), @@ -342,50 +314,54 @@ handle_event(#wx{id = ?ID_TRACEMENU}, {noreply, State} end; -handle_event(#wx{id=?ID_TRACE_NEW_MENU, event=#wxCommand{type=command_menu_selected}}, State) -> +handle_event(#wx{id=?ID_TRACE_NAMES}, #state{sel={SelIds,_Pids}, holder=Holder, panel=Panel}=State) -> + case SelIds of + [] -> + observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), + {noreply, State}; + _ -> + PidsOrReg = call(Holder, {get_name_or_pid, self(), SelIds}), + observer_trace_wx:add_processes(observer_wx:get_tracer(), PidsOrReg), + {noreply, State} + end; + +handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> observer_trace_wx:add_processes(observer_wx:get_tracer(), [new]), {noreply, State}; handle_event(#wx{event=#wxSize{size={W,_}}}, #state{grid=Grid}=State) -> - wx:batch(fun() -> - Cols = wxListCtrl:getColumnCount(Grid), - Last = lists:foldl(fun(I, Last) -> - Last - wxListCtrl:getColumnWidth(Grid, I) - end, W-Cols*3-?LCTRL_WDECR, lists:seq(0, Cols - 2)), - Size = max(200, Last), - %% io:format("Width ~p ~p => ~p~n",[W, Last, Size]), - wxListCtrl:setColumnWidth(Grid, Cols-1, Size) - end), + observer_lib:set_listctrl_col_size(Grid, W), {noreply, State}; handle_event(#wx{event=#wxList{type=command_list_item_right_click, itemIndex=Row}}, - #state{popup_menu=Popup, - holder=Holder}=State) -> + #state{panel=Panel, holder=Holder}=State) -> case call(Holder, {get_row, self(), Row, pid}) of {error, undefined} -> - wxWindow:show(Popup, [{show, false}]), undefined; {ok, _} -> - wxWindow:move(Popup, wx_misc:getMousePosition()), - wxWindow:show(Popup) + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_PROC, "Process info"), + wxMenu:append(Menu, ?ID_TRACE_PIDS, "Trace processes", [{help, ?TRACE_PIDS_STR}]), + wxMenu:append(Menu, ?ID_TRACE_NAMES, "Trace named processes (all nodes)", + [{help, ?TRACE_NAMES_STR}]), + wxMenu:append(Menu, ?ID_KILL, "Kill Process"), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu) end, {noreply, State}; handle_event(#wx{event=#wxList{type=command_list_item_focused, itemIndex=Row}}, - #state{grid=Grid,popup_menu=Pop,holder=Holder} = State) -> + #state{grid=Grid,holder=Holder} = State) -> case Row >= 0 of true -> - wxWindow:show(Pop, [{show, false}]), SelIds = [Row|lists:delete(Row, get_selected_items(Grid))], Pids = call(Holder, {get_pids, self(), SelIds}), - %% io:format("Focused ~p -> ~p~n",[State#state.sel, {SelIds, Pids}]), {noreply, State#state{sel={SelIds, Pids}}}; false -> - %% io:format("Focused -1~n",[]), {noreply, State} end; @@ -448,7 +424,6 @@ set_focus([Old|_], [New|_], Grid) -> wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED), wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED). - %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_table_holder(Parent, Attrs) -> @@ -486,6 +461,9 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, {get_rows_from_pids, From, Pids} -> get_rows_from_pids(From, Pids, Info), table_holder(S0); + {get_name_or_pid, From, Indices} -> + get_name_or_pid(From, Indices, Info), + table_holder(S0); {get_node, From} -> From ! {self(), Node}, @@ -562,7 +540,7 @@ get_procinfo_data(Col, Info) -> col_to_element(?COL_PID) -> #etop_proc_info.pid; col_to_element(?COL_NAME) -> #etop_proc_info.name; col_to_element(?COL_MEM) -> #etop_proc_info.mem; -col_to_element(?COL_TIME) -> #etop_proc_info.runtime; +%%col_to_element(?COL_TIME) -> #etop_proc_info.runtime; col_to_element(?COL_REDS) -> #etop_proc_info.reds; col_to_element(?COL_FUN) -> #etop_proc_info.cf; col_to_element(?COL_MSG) -> #etop_proc_info.mq. @@ -571,6 +549,14 @@ get_pids(From, Indices, ProcInfo) -> Processes = [(lists:nth(I+1, ProcInfo))#etop_proc_info.pid || I <- Indices], From ! {self(), Processes}. +get_name_or_pid(From, Indices, ProcInfo) -> + Get = fun(#etop_proc_info{name=Name}) when is_atom(Name) -> Name; + (#etop_proc_info{pid=Pid}) -> Pid + end, + Processes = [Get(lists:nth(I+1, ProcInfo)) || I <- Indices], + From ! {self(), Processes}. + + get_row(From, Row, pid, Info) -> Pid = case Row =:= -1 of true -> {error, undefined}; diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 127599a39e..a4c5914c49 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -41,7 +41,7 @@ -record(worker, {panel, callback}). start(Process, ParentFrame, Parent) -> - wx_object:start(?MODULE, [Process, ParentFrame, Parent], []). + wx_object:start_link(?MODULE, [Process, ParentFrame, Parent], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -52,7 +52,7 @@ init([Pid, ParentFrame, Parent]) -> {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}}]), + [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]), MenuBar = wxMenuBar:new(), create_menus(MenuBar), wxFrame:setMenuBar(Frame, MenuBar), @@ -75,10 +75,7 @@ init([Pid, ParentFrame, Parent]) -> }} 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()]) + {stop, badrpc, #state{parent=Parent, pid=Pid}} end. init_panel(Notebook, Str, Pid, Fun) -> @@ -92,30 +89,27 @@ init_panel(Notebook, Str, Pid, Fun) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_event(#wx{event=#wxClose{type=close_window}}, State) -> - {stop, shutdown, State}; + {stop, normal, State}; handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> - {stop, shutdown, State}; + {stop, normal, 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_event(Event, _State) -> + error({unhandled_event, Event}). -handle_info(Info, State) -> - io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), +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_call(Call, From, _State) -> + error({unhandled_call, Call, From}). -handle_cast(Cast, State) -> - io:format("~p ~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]), - {noreply, State}. +handle_cast(Cast, _State) -> + error({unhandled_cast, Cast}). terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) -> Parent ! {procinfo_menu_closed, Pid}, @@ -179,26 +173,42 @@ init_dict_page(Parent, Pid) -> {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, + LCtrl = wxListCtrl:new(Parent, [{style, ?wxLC_REPORT bor ?wxLC_HRULES}]), + Li = wxListItem:new(), + wxListItem:setText(Li, "Module:Function/Arg"), + wxListCtrl:insertColumn(LCtrl, 0, Li), + wxListCtrl:setColumnWidth(LCtrl, 0, 300), + wxListItem:setText(Li, "File:LineNumber"), + wxListCtrl:insertColumn(LCtrl, 1, Li), + wxListCtrl:setColumnWidth(LCtrl, 1, 300), + wxListItem:destroy(Li), 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] + wxListCtrl:deleteAllItems(LCtrl), + wx:foldl(fun({M, F, A, Info}, Row) -> + _Item = wxListCtrl:insertItem(LCtrl, Row, ""), + ?EVEN(Row) andalso + wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})), + FileLine = case Info of + [{file,File},{line,Line}] -> + io_lib:format("~s:~w", [File,Line]); + _ -> + [] + end, + wxListCtrl:setItem(LCtrl, Row, 1, FileLine), + Row+1 + end, 0, RawBt) end, + Resize = fun(#wx{event=#wxSize{size={W,_}}},Ev) -> + wxEvent:skip(Ev), + observer_lib:set_listctrl_col_size(LCtrl, W) + end, + wxListCtrl:connect(LCtrl, size, [{callback, Resize}]), Update(), - {Text, Update}. + {LCtrl, Update}. create_menus(MenuBar) -> Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}, diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index ddedcf3829..09602bbd9e 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -24,8 +24,6 @@ -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). --export([sys_info/0]). - -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). @@ -48,7 +46,7 @@ start_link(Notebook, Parent) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Notebook, Parent]) -> - SysInfo = sys_info(), + SysInfo = observer_backend:sys_info(), {Info, Stat} = info_fields(), Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxHORIZONTAL), @@ -73,7 +71,7 @@ create_sys_menu(Parent) -> observer_wx:create_menus(Parent, [View]). update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) -> - SysInfo = observer_wx:try_rpc(Node, ?MODULE, sys_info, []), + SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []), {Info, Stat} = info_fields(), observer_lib:update_info(Fields, observer_lib:fill_info(Info, SysInfo) ++ observer_lib:fill_info(Stat, SysInfo)), @@ -97,20 +95,20 @@ info_fields() -> ]} ], Stat = [{"Memory Usage", right, - [{"Total", total}, - {"Processes", processes}, - {"Atoms", atom}, - {"Binaries", binary}, - {"Code", code}, - {"Ets", ets} + [{"Total", {bytes, total}}, + {"Processes", {bytes, processes}}, + {"Atoms", {bytes, atom}}, + {"Binaries", {bytes, binary}}, + {"Code", {bytes, code}}, + {"Ets", {bytes, ets}} ]}, {"Statistics", right, - [{"Up time", uptime}, + [{"Up time", {time_ms, uptime}}, {"Max Processes", process_limit}, {"Processes", process_count}, {"Run Queue", run_queue}, - {"IO Input", io_input}, - {"IO Output", io_output} + {"IO Input", {bytes, io_input}}, + {"IO Output", {bytes, io_output}} ]} ], {Info, Stat}. @@ -126,16 +124,6 @@ handle_info(refresh_interval, #sys_wx_state{panel = Panel, end, {noreply, State}; -handle_info({node, Node}, #sys_wx_state{panel = Panel} = State) -> - UpdState = State#sys_wx_state{node = Node}, - try - update_syspage(UpdState), - {noreply, UpdState} - catch error:{badrpc, _} -> - observer_wx:return_to_localnode(Panel, Node), - {noreply, State} - end; - handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel, timer = Timer} = State) -> UpdState = State#sys_wx_state{node = Node}, @@ -148,7 +136,6 @@ handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel, {noreply, State} end; - handle_info(not_active, #sys_wx_state{timer = Timer} = State) -> {noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}}; @@ -188,36 +175,3 @@ handle_event(#wx{id = ?ID_REFRESH_INTERVAL, handle_event(Event, State) -> io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), {noreply, State}. - - -sys_info() -> - {{_,Input},{_,Output}} = erlang:statistics(io), - [{process_count, erlang:system_info(process_count)}, - {process_limit, erlang:system_info(process_limit)}, - {uptime, {time_ms, element(1, erlang:statistics(wall_clock))}}, - {run_queue, erlang:statistics(run_queue)}, - {io_input, {bytes, Input}}, - {io_output, {bytes, Output}}, - {logical_processors, erlang:system_info(logical_processors)}, - {logical_processors_available, erlang:system_info(logical_processors_available)}, - {logical_processors_online, erlang:system_info(logical_processors_online)}, - - {total, {bytes, erlang:memory(total)}}, - %%{processes_used, erlang:memory(processes_used)}, - {processes, {bytes, erlang:memory(processes)}}, - %%{atom_used, erlang:memory(atom_used)}, - {atom, {bytes, erlang:memory(atom)}}, - {binary, {bytes, erlang:memory(binary)}}, - {code, {bytes, erlang:memory(code)}}, - {ets, {bytes, erlang:memory(ets)}}, - - {otp_release, erlang:system_info(otp_release)}, - {version, erlang:system_info(version)}, - {system_architecture, erlang:system_info(system_architecture)}, - {kernel_poll, erlang:system_info(kernel_poll)}, - {smp_support, erlang:system_info(smp_support)}, - {threads, erlang:system_info(threads)}, - {thread_pool_size, erlang:system_info(thread_pool_size)}, - {wordsize_internal, erlang:system_info({wordsize, internal})}, - {wordsize_external, erlang:system_info({wordsize, external})} - ]. diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 0ab7db121b..d0b6a1e063 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -27,30 +27,43 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). --define(OPTIONS, 301). --define(SAVE_BUFFER, 302). --define(CLOSE, 303). --define(CLEAR, 304). -define(SAVE_TRACEOPTS, 305). -define(LOAD_TRACEOPTS, 306). -define(TOGGLE_TRACE, 307). -define(ADD_NEW, 308). -define(ADD_TP, 309). --define(PROCESSES, 350). --define(MODULES, 351). --define(FUNCTIONS, 352). --define(TRACERWIN, 353). +-define(TRACE_OUTPUT, 310). +-define(TRACE_DEFMS, 311). +-define(TRACE_DEFPS, 312). + +-define(NODES_WIN, 330). +-define(ADD_NODES, 331). +-define(REMOVE_NODES, 332). + +-define(PROC_WIN, 340). +-define(EDIT_PROCS, 341). +-define(REMOVE_PROCS, 342). + +-define(MODULES_WIN, 350). + +-define(FUNCS_WIN, 360). +-define(EDIT_FUNCS_MS, 361). +-define(REMOVE_FUNCS_MS, 362). + +-define(LOG_WIN, 370). +-define(LOG_SAVE, 321). +-define(LOG_CLEAR, 322). -record(state, {parent, panel, - p_view, - m_view, - f_view, + n_view, p_view, m_view, f_view, %% The listCtrl's + logwin, %% The latest log window nodes = [], toggle_button, tpids = [], %% #tpid def_trace_opts = [], + output = [], tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec} match_specs = []}). % [ #match_spec{} ] @@ -72,26 +85,27 @@ create_window(Notebook, ParentPid) -> Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}]), - ProcessView = create_process_view(Splitter), + {NodeProcView, NodeView, ProcessView} = create_process_view(Splitter), {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter), wxSplitterWindow:setSashGravity(Splitter, 0.5), wxSplitterWindow:setMinimumPaneSize(Splitter,50), - wxSplitterWindow:splitHorizontally(Splitter, ProcessView, MatchSpecView), + wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView), wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]), %% Buttons Buttons = wxBoxSizer:new(?wxHORIZONTAL), ToggleButton = wxToggleButton:new(Panel, ?TOGGLE_TRACE, "Start Trace", []), - wxSizer:add(Buttons, ToggleButton), - New = wxButton:new(Panel, ?ADD_NEW, [{label, "Trace New Processes"}]), - wxSizer:add(Buttons, New), - ATP = wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}]), - wxSizer:add(Buttons, ATP), - wxMenu:connect(Panel, command_togglebutton_clicked, []), - wxMenu:connect(Panel, command_button_clicked, []), - wxSizer:add(Sizer, Buttons, [{flag, ?wxALL},{border, 2}, {proportion,0}]), + wxSizer:add(Buttons, ToggleButton, [{flag, ?wxALIGN_CENTER_VERTICAL}]), + wxSizer:addSpacer(Buttons, 15), + wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NODES, [{label, "Add Nodes"}])), + wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW, [{label, "Add 'new' Process"}])), + wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}])), + wxMenu:connect(Panel, command_togglebutton_clicked, [{skip, true}]), + wxMenu:connect(Panel, command_button_clicked, [{skip, true}]), + wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN}, + {border, 5}, {proportion,0}]), wxWindow:setSizer(Panel, Sizer), {Panel, #state{parent=ParentPid, panel=Panel, - p_view=ProcessView, m_view=ModView, f_view=FuncView, + n_view=NodeView, p_view=ProcessView, m_view=ModView, f_view=FuncView, toggle_button = ToggleButton, match_specs=default_matchspecs()}}. @@ -103,36 +117,51 @@ default_matchspecs() -> [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms]. create_process_view(Parent) -> - Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, - Grid = wxListCtrl:new(Parent, [{winid, ?PROCESSES}, {style, Style}]), + Panel = wxPanel:new(Parent), + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + Style = ?wxLC_REPORT bor ?wxLC_HRULES, + Splitter = wxSplitterWindow:new(Panel, []), + Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]), + Procs = wxListCtrl:new(Splitter, [{winid, ?PROC_WIN}, {style, Style}]), Li = wxListItem:new(), - AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, "Nodes"), + wxListCtrl:insertColumn(Nodes, 0, Li), + + AddProc = fun({Name, Align, DefSize}, Col) -> wxListItem:setText(Li, Name), wxListItem:setAlign(Li, Align), - wxListCtrl:insertColumn(Grid, Col, Li), - wxListCtrl:setColumnWidth(Grid, Col, DefSize), + wxListCtrl:insertColumn(Procs, Col, Li), + wxListCtrl:setColumnWidth(Procs, Col, DefSize), Col + 1 end, ListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120}, {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], - lists:foldl(AddListEntry, 0, ListItems), + lists:foldl(AddProc, 0, ListItems), wxListItem:destroy(Li), - %% wxListCtrl:connect(Grid, command_list_item_activated), - %% wxListCtrl:connect(Grid, command_list_item_selected), - wxListCtrl:connect(Grid, size, [{skip, true}]), + wxSplitterWindow:setSashGravity(Splitter, 0.0), + wxSplitterWindow:setMinimumPaneSize(Splitter,50), + wxSplitterWindow:splitVertically(Splitter, Nodes, Procs, [{sashPosition, 155}]), + wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxListCtrl:connect(Procs, command_list_item_right_click), + wxListCtrl:connect(Nodes, command_list_item_right_click), + wxListCtrl:connect(Procs, size, [{skip, true}]), + wxListCtrl:connect(Nodes, size, [{skip, true}]), - wxWindow:setFocus(Grid), - Grid. + wxPanel:setSizer(Panel, MainSz), + wxWindow:setFocus(Procs), + {Panel, Nodes, Procs}. create_matchspec_view(Parent) -> Panel = wxPanel:new(Parent), MainSz = wxBoxSizer:new(?wxHORIZONTAL), - Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Style = ?wxLC_REPORT bor ?wxLC_HRULES, Splitter = wxSplitterWindow:new(Panel, []), - Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES}, {style, Style}]), - Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCTIONS}, {style, Style}]), + Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, {style, Style}]), + Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]), Li = wxListItem:new(), + wxListItem:setText(Li, "Modules"), wxListCtrl:insertColumn(Modules, 0, Li), wxListItem:setText(Li, "Functions"), @@ -142,49 +171,45 @@ create_matchspec_view(Parent) -> wxListCtrl:insertColumn(Funcs, 1, Li), wxListCtrl:setColumnWidth(Funcs, 1, 300), wxListItem:destroy(Li), + wxSplitterWindow:setSashGravity(Splitter, 0.0), wxSplitterWindow:setMinimumPaneSize(Splitter,50), - wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 150}]), + wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 155}]), wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), wxListCtrl:connect(Modules, size, [{skip, true}]), wxListCtrl:connect(Funcs, size, [{skip, true}]), wxListCtrl:connect(Modules, command_list_item_selected), - %% wxListCtrl:connect(Funcs, command_list_item_selected), + wxListCtrl:connect(Funcs, command_list_item_right_click), wxPanel:setSizer(Panel, MainSz), {Panel, Modules, Funcs}. create_menues(Parent) -> - Menus = [{"File", [#create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"}, - #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}] - }], + Menus = [{"File", + [#create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"}, + #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}]}, + {"Options", + [#create_menu{id = ?TRACE_OUTPUT, text = "Output"}, + #create_menu{id = ?TRACE_DEFMS, text = "Match Specifications"}, + #create_menu{id = ?TRACE_DEFPS, text = "Default Process Options"}]} + ], observer_wx:create_menus(Parent, Menus). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%Main window handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> case wx:getObjectType(Obj) =:= wxListCtrl of - true -> - wx:batch(fun() -> - Cols = wxListCtrl:getColumnCount(Obj), - Last = lists:foldl(fun(I, Last) -> - Last - wxListCtrl:getColumnWidth(Obj, I) - end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)), - Size = max(150, Last), - wxListCtrl:setColumnWidth(Obj, Cols-1, Size) - end); - false -> - ok + true -> observer_lib:set_listctrl_col_size(Obj, W); + false -> ok end, {noreply, State}; handle_event(#wx{id=?ADD_NEW}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> - case observer_traceoptions_wx:process_trace(Parent, TraceOpts) of - {ok, Opts} -> - Process = #tpid{pid=new, opts=Opts}, - {noreply, do_add_processes([Process], State#state{def_trace_opts=Opts})}; - cancel -> - {noreply, State} + try + Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), + Process = #tpid{pid=new, opts=Opts}, + {noreply, do_add_processes([Process], State#state{def_trace_opts=Opts})} + catch cancel -> {noreply, State} end; handle_event(#wx{id=?ADD_TP}, @@ -200,98 +225,101 @@ handle_event(#wx{id=?ADD_TP}, {noreply, do_add_patterns(Patterns, State)} end; -handle_event(#wx{id=?MODULES, event=#wxList{type=command_list_item_selected, itemIndex=Row}}, +handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected, itemIndex=Row}}, State = #state{tpatterns=TPs, m_view=Mview, f_view=Fview}) -> Module = list_to_atom(wxListCtrl:getItemText(Mview, Row)), update_functions_view(dict:fetch(Module, TPs), Fview), {noreply, State}; - handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, #state{panel = Panel, nodes = Nodes, tpids = TProcs, - tpatterns = TPs, - toggle_button = ToggleBtn} = State) -> - LogWin = wxFrame:new(Panel, ?TRACERWIN, "Trace Log", [{size, {750, 800}}]), - Text = wxTextCtrl:new(LogWin, ?wxID_ANY, - [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor - ?wxTE_DONTWRAP bor ?wxTE_READONLY}]), - Font = observer_wx:get_attrib({font, fixed}), - Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), - true = wxTextCtrl:setDefaultStyle(Text, Attr), - Env = wx:get_env(), - Write = fun(Trace) -> - wx:set_env(Env), - wxTextCtrl:appendText(Text, textformat(Trace)) - end, - {ok, _} = ttb:tracer(Nodes, [{file, {local,"/tmp/foo"}}, {shell, {only, Write}}]), - setup_ttb(dict:to_list(TPs), TProcs), - wxFrame:connect(LogWin, close_window, [{skip, true}]), - wxFrame:show(LogWin), - wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), - {noreply, State}; + tpatterns = TPs0, + toggle_button = ToggleBtn, + output = Opts + } = State) -> + try + TPs = dict:to_list(TPs0), + (TProcs == []) andalso throw({error, "No processes traced"}), + (Nodes == []) andalso throw({error, "No nodes traced"}), + HaveCallTrace = fun(#tpid{opts=Os}) -> lists:member(functions,Os) end, + WStr = "Call trace actived but no trace patterns used", + (TPs == []) andalso lists:any(HaveCallTrace, TProcs) andalso + observer_wx:create_txt_dialog(Panel, WStr, "Warning", ?wxICON_WARNING), + + {TTB, LogWin} = ttb_output_args(Panel, Opts), + {ok, _} = ttb:tracer(Nodes, TTB), + setup_ttb(TPs, TProcs), + wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), + {noreply, State#state{logwin=LogWin}} + catch {error, Msg} -> + observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR), + wxToggleButton:setValue(ToggleBtn, false), + {noreply, State} + end; handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, #state{toggle_button = ToggleBtn} = State) -> %%Stop tracing ttb:stop(nofetch), wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + wxToggleButton:setValue(ToggleBtn, false), + {noreply, State#state{logwin=false}}; + +handle_event(#wx{id=Id, obj=LogWin, event=Ev}, + #state{toggle_button = ToggleBtn, logwin=Latest} = State) + when Id =:= ?LOG_WIN; is_record(Ev, wxClose) -> + case LogWin of + Latest -> + %%Stop tracing + ttb:stop(nofetch), + wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + wxToggleButton:setValue(ToggleBtn, false), + {noreply, State#state{logwin=false}}; + _ -> + {noreply, State} + end; + +handle_event(#wx{id=?LOG_CLEAR, userData=TCtrl}, State) -> + wxTextCtrl:clear(TCtrl), {noreply, State}; -handle_event(#wx{id=?TRACERWIN, event=#wxClose{}}, - #state{toggle_button = ToggleBtn} = State) -> - %%Stop tracing - ttb:stop(nofetch), - wxToggleButton:setLabel(ToggleBtn, "Start Trace"), +handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) -> + Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Path = wxFileDialog:getPath(Dialog), + wxDialog:destroy(Dialog), + wxTextCtrl:saveFile(TCtrl, [{file, Path}]); + _ -> + wxDialog:destroy(Dialog), + ok + end, {noreply, State}; -%% handle_event(#wx{id = ?CLEAR, event = #wxCommand{type = command_menu_selected}}, -%% #state{text_ctrl = TxtCtrl} = State) -> -%% wxTextCtrl:clear(TxtCtrl), -%% {noreply, State}; - -%% handle_event(#wx{id = ?SAVE_BUFFER, event = #wxCommand{type = command_menu_selected}}, -%% #state{frame = Frame, text_ctrl = TxtCtrl} = State) -> -%% Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), -%% case wxFileDialog:showModal(Dialog) of -%% ?wxID_OK -> -%% Path = wxFileDialog:getPath(Dialog), -%% wxDialog:destroy(Dialog), -%% case filelib:is_file(Path) of -%% true -> -%% observer_wx:create_txt_dialog(Frame, "File already exists: " ++ Path ++ "\n", -%% "Error", ?wxICON_ERROR); -%% false -> -%% wxTextCtrl:saveFile(TxtCtrl, [{file, Path}]) -%% end; -%% _ -> -%% wxDialog:destroy(Dialog), -%% ok -%% end, -%% {noreply, State}; - -handle_event(#wx{id = ?SAVE_TRACEOPTS, - event = #wxCommand{type = command_menu_selected}}, +handle_event(#wx{id = ?SAVE_TRACEOPTS}, #state{panel = Panel, def_trace_opts = TraceOpts, match_specs = MatchSpecs, - tpatterns = TracePatterns + tpatterns = TracePatterns, + output = Output } = State) -> Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), case wxFileDialog:showModal(Dialog) of ?wxID_OK -> Path = wxFileDialog:getPath(Dialog), - write_file(Panel, Path, TraceOpts, MatchSpecs, dict:to_list(TracePatterns)); + write_file(Panel, Path, + TraceOpts, MatchSpecs, Output, + dict:to_list(TracePatterns) + ); _ -> ok end, wxDialog:destroy(Dialog), {noreply, State}; -handle_event(#wx{id = ?LOAD_TRACEOPTS, - event = #wxCommand{type = command_menu_selected}}, - #state{panel = Panel} = State) -> +handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) -> Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]), State2 = case wxFileDialog:showModal(Dialog) of ?wxID_OK -> @@ -303,27 +331,142 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS, wxDialog:destroy(Dialog), {noreply, State2}; +handle_event(#wx{id=Type, event=#wxList{type=command_list_item_right_click}}, + State = #state{panel=Panel}) -> + Menus = case Type of + ?PROC_WIN -> + [{?EDIT_PROCS, "Edit process options"}, + {?REMOVE_PROCS, "Remove processes"}]; + ?FUNCS_WIN -> + [{?EDIT_FUNCS_MS, "Edit matchspecs"}, + {?REMOVE_FUNCS_MS, "Remove trace patterns"}]; + ?NODES_WIN -> + [{?ADD_NODES, "Trace other nodes"}, + {?REMOVE_NODES, "Remove nodes"}] + end, + Menu = wxMenu:new(), + [wxMenu:append(Menu,Id,Str) || {Id,Str} <- Menus], + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu), + {noreply, State}; + +handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, p_view=Ps} = State) -> + try + [#tpid{opts=DefOpts}|_] = Selected = get_selected_items(Ps, Tpids), + Opts = observer_traceoptions_wx:process_trace(Panel, DefOpts), + Changed = [Tpid#tpid{opts=Opts} || Tpid <- Selected], + {noreply, do_add_processes(Changed, State#state{def_trace_opts=Opts})} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_PROCS}, #state{tpids=Tpids, p_view=LCtrl} = State) -> + Selected = get_selected_items(LCtrl, Tpids), + Pids = Tpids -- Selected, + update_process_view(Pids, LCtrl), + {noreply, State#state{tpids=Pids}}; + +handle_event(#wx{id=?TRACE_DEFPS}, #state{panel=Panel, def_trace_opts=PO} = State) -> + try + Opts = observer_traceoptions_wx:process_trace(Panel, PO), + {noreply, State#state{def_trace_opts=Opts}} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?TRACE_DEFMS}, #state{panel=Panel, match_specs=Ms} = State) -> + try %% Return selected MS and sends new MS's to us + observer_traceoptions_wx:select_matchspec(self(), Panel, Ms) + catch _:_ -> + cancel + end, + {noreply, State}; + +handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs, + f_view=LCtrl, m_view=Mview, + match_specs=Mss + } = State) -> + try + [Module] = get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))), + Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)), + Ms = observer_traceoptions_wx:select_matchspec(self(), Panel, Mss), + Changed = [TP#tpattern{ms=Ms} || TP <- Selected], + {noreply, do_add_patterns({Module, Changed}, State)} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_FUNCS_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_view=Mview} = State) -> + case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs0))) of + [] -> {noreply, State}; + [Module] -> + FMs0 = dict:fetch(Module, TPs0), + Selected = get_selected_items(LCtrl, FMs0), + FMs = FMs0 -- Selected, + update_functions_view(FMs, LCtrl), + TPs = case FMs of + [] -> + New = dict:erase(Module, TPs0), + update_modules_view(lists:sort(dict:fetch_keys(New)), Module, Mview), + New; + _ -> + dict:store(Module, FMs, TPs0) + end, + {noreply, State#state{tpatterns=TPs}} + end; + +handle_event(#wx{id=?TRACE_OUTPUT}, #state{panel=Panel, output=Out0} = State) -> + try + Out = observer_traceoptions_wx:output(Panel, Out0), + {noreply, State#state{output=Out}} + catch _:_ -> + {noreply, State} + end; + +handle_event(#wx{id=?ADD_NODES}, #state{panel=Panel, n_view=Nview, nodes=Ns0} = State) -> + try + Possible = [node()|nodes()] -- Ns0, + case Possible of + [] -> + Msg = "Already selected all connected nodes\n" + "Use the Nodes menu to connect to new nodes first.", + observer_wx:create_txt_dialog(Panel, Msg, "No available nodes", ?wxICON_INFORMATION), + throw(cancel); + _ -> + Ns = lists:usort(Ns0 ++ observer_traceoptions_wx:select_nodes(Panel, Possible)), + update_nodes_view(Ns, Nview), + {noreply, State#state{nodes=Ns}} + end + catch cancel -> + {noreply, State} + end; + +handle_event(#wx{id=?REMOVE_NODES}, #state{n_view=Nview, nodes=Ns0} = State) -> + Sel = get_selected_items(Nview, Ns0), + Ns = Ns0 -- Sel, + update_nodes_view(Ns, Nview), + {noreply, State#state{nodes = Ns}}; + handle_event(#wx{id=ID, event = What}, State) -> - io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, self(), ID, What]), + io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, ?LINE, ID, What]), {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_call(Msg, _From, State) -> - io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), - {reply, ok, State}. +handle_call(Msg, From, _State) -> + error({unhandled_call, Msg, From}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> - case observer_traceoptions_wx:process_trace(Parent, TraceOpts) of - {ok, Opts} -> - POpts = [#tpid{pid=Pid, opts=Opts} || Pid <- Pids], - {noreply, do_add_processes(POpts, State#state{def_trace_opts=Opts})}; - cancel -> + try + Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), + POpts = [#tpid{pid=Pid, opts=Opts} || Pid <- Pids], + S = do_add_processes(POpts, State#state{def_trace_opts=Opts}), + {noreply, S} + catch cancel -> {noreply, State} end; -handle_cast(Msg, State) -> - io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), - {noreply, State}. +handle_cast(Msg, _State) -> + error({unhandled_cast, Msg}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -342,18 +485,30 @@ handle_info(Any, State) -> {noreply, State}. terminate(_Reason, #state{nodes=_Nodes}) -> - %% case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of - %% undefined -> fine; - %% Pid -> exit(Pid, kill) - %% end, + ttb:stop(nofetch), ok. code_change(_, _, State) -> {stop, not_yet_implemented, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> + Old = case dict:find(Module, TPs0) of + {ok, Prev} -> Prev; + error -> [] + end, + case merge_patterns(NewPs, Old) of + {Old, [], []} -> + State; + {MPatterns, _New, _Changed} -> + %% if dynamicly updates update New and Changed + TPs = dict:store(Module, MPatterns, TPs0), + update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), + update_functions_view(dict:fetch(Module, TPs), Fview), + State#state{tpatterns=TPs} + end. -do_add_processes(POpts, S0=#state{p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> +do_add_processes(POpts, S0=#state{n_view=Nview, p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> case merge_pids(POpts, OldPids) of {OldPids, [], []} -> S0; @@ -363,13 +518,15 @@ do_add_processes(POpts, S0=#state{p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> Nodes = case ordsets:subtract(Ns1, Ns0) of [] -> Ns0; %% No new Nodes NewNs -> - %% Handle new nodes - %% BUGBUG add trace patterns for new nodes - ordsets:union(NewNs, Ns0) + %% if dynamicly updates add trace patterns for new nodes + All = ordsets:union(NewNs, Ns0), + update_nodes_view(All, Nview), + All end, S0#state{tpids=Pids, nodes=Nodes} end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% update_process_view(Pids, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), wx:foldl(fun(#tpid{pid=Pid, opts=Opts}, Row) -> @@ -381,20 +538,15 @@ update_process_view(Pids, LCtrl) -> Row+1 end, 0, Pids). -do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> - Old = case dict:find(Module, TPs0) of - {ok, Prev} -> Prev; - error -> [] - end, - case merge_patterns(NewPs, Old) of - {Old, [], []} -> - State; - {MPatterns, _New, _Changed} -> - TPs = dict:store(Module, MPatterns, TPs0), - update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), - update_functions_view(dict:fetch(Module, TPs), Fview), - State#state{tpatterns=TPs} - end. +update_nodes_view(Nodes, LCtrl) -> + wxListCtrl:deleteAllItems(LCtrl), + wx:foldl(fun(Node, Row) -> + _Item = wxListCtrl:insertItem(LCtrl, Row, ""), + ?EVEN(Row) andalso + wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Node)), + Row+1 + end, 0, Nodes). update_modules_view(Mods, Module, LCtrl) -> wxListCtrl:deleteAllItems(LCtrl), @@ -419,7 +571,6 @@ update_functions_view(Funcs, LCtrl) -> end, 0, Funcs). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - merge_pids([N1=#tpid{pid=new}|Ns], [N2=#tpid{pid=new}|Old]) -> {Pids, New, Changed} = merge_pids_1(Ns,Old), {[N1|Pids], New, [{N2,N2}|Changed]}; @@ -438,7 +589,6 @@ merge_pids_1(New, Old) -> merge_patterns(New, Old) -> merge(lists:sort(New), Old, #tpattern.fa, [], [], []). - merge([N|Ns], [N|Os], El, New, Ch, All) -> merge(Ns, Os, El, New, Ch, [N|All]); merge([N|Ns], [O|Os], El, New, Ch, All) @@ -456,13 +606,78 @@ merge(Ns, [], _El, New, Ch, All) -> {lists:reverse(All, Ns), Ns++New, Ch}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +ttb_output_args(Parent, Opts) -> + ToWindow = proplists:get_value(window, Opts, true), + ToShell = proplists:get_value(shell, Opts, false), + ToFile = proplists:get_value(file, Opts, false), + ToWindow orelse ToShell orelse ToFile orelse + throw({error, "No output of trace"}), + {LogWin,Text} = create_logwindow(Parent, ToWindow), + Write = output_fun(Text, ToShell), + Shell = output_shell(ToFile, Write), + FileOpts = output_file(ToFile, proplists:get_value(wrap, Opts, false), Opts), + {[{file, {local,FileOpts}}|Shell], LogWin}. + +output_shell(true, false) -> + []; %% File only +output_shell(true, Write) when is_function(Write) -> + [{shell, Write}]; +output_shell(false, Write) when is_function(Write) -> + [{shell, {only, Write}}]. + +output_fun(false, false) -> false; +output_fun(false, true) -> fun(Trace) -> io:put_chars(textformat(Trace)) end; +output_fun(Text, false) -> + Env = wx:get_env(), + fun(Trace) -> + wx:set_env(Env), + wxTextCtrl:appendText(Text, textformat(Trace)) + end; +output_fun(Text, true) -> + Env = wx:get_env(), + fun(Trace) -> + wx:set_env(Env), + IoList = textformat(Trace), + wxTextCtrl:appendText(Text, IoList), + io:put_chars(textformat(Trace)) + end. + +output_file(false, _, _Opts) -> + "ttb"; %% Will be ignored +output_file(true, false, Opts) -> + proplists:get_value(filename, Opts, "ttb"); +output_file(true, true, Opts) -> + Name = proplists:get_value(filename, Opts, "ttb"), + Size = proplists:get_value(wrap_sz, Opts, 128), + Count = proplists:get_value(wrap_c, Opts, 8), + {wrap, Name, Size*1024, Count}. + + +create_logwindow(_Parent, false) -> {false, false}; +create_logwindow(Parent, true) -> + LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750, 800}}]), + MB = wxMenuBar:new(), + File = wxMenu:new(), + wxMenu:append(File, ?LOG_CLEAR, "Clear Log\tCtrl-C"), + wxMenu:append(File, ?LOG_SAVE, "Save Log\tCtrl-S"), + wxMenu:append(File, ?wxID_CLOSE, "Close"), + wxMenuBar:append(MB, File, "File"), + wxFrame:setMenuBar(LogWin, MB), + Text = wxTextCtrl:new(LogWin, ?wxID_ANY, + [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor + ?wxTE_DONTWRAP bor ?wxTE_READONLY}]), + Font = observer_wx:get_attrib({font, fixed}), + Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), + true = wxTextCtrl:setDefaultStyle(Text, Attr), + wxFrame:connect(LogWin, close_window, [{skip, true}]), + wxFrame:connect(LogWin, command_menu_selected, [{userData, Text}]), + wxFrame:show(LogWin), + {LogWin, Text}. setup_ttb(TPs, TPids) -> _R1 = [setup_tps(FTP, []) || {_, FTP} <- TPs], _R2 = [ttb:p(Pid, dbg_flags(Flags)) || #tpid{pid=Pid, opts=Flags} <- TPids], [#tpid{pid=_Pid, opts=_Flags}|_] = TPids, - %% io:format("ttb:p(pid(\"~w\", ~w).", [Pid, Flags]), - %% io:format("TTB ~w ~w~n",[R2, R1]), ok. %% Sigh order is important @@ -511,33 +726,33 @@ format_trace(Trace, Size, TS0={_,_,MS}) -> case element(4, Trace) of {dbg,ok} -> ""; Message -> - io_lib:format("~s (~100p) << ~100p ~n", [TS,From,Message]) + io_lib:format("~s (~100p) << ~100p~n", [TS,From,Message]) end; 'send' -> Message = element(4, Trace), To = element(5, Trace), - io_lib:format("~s (~100p) ~100p ! ~100p ~n", [TS,From,To,Message]); + io_lib:format("~s (~100p) ~100p ! ~100p~n", [TS,From,To,Message]); call -> case element(4, Trace) of MFA when Size == 5 -> Message = element(5, Trace), io_lib:format("~s (~100p) call ~s (~100p) ~n", [TS,From,ffunc(MFA),Message]); MFA -> - io_lib:format("~s (~100p) call ~s ~n", [TS,From,ffunc(MFA)]) + io_lib:format("~s (~100p) call ~s~n", [TS,From,ffunc(MFA)]) end; return_from -> MFA = element(4, Trace), Ret = element(5, Trace), - io_lib:format("~s (~100p) returned from ~s -> ~100p ~n", [TS,From,ffunc(MFA),Ret]); + io_lib:format("~s (~100p) returned from ~s -> ~100p~n", [TS,From,ffunc(MFA),Ret]); return_to -> MFA = element(4, Trace), - io_lib:format("~s (~100p) returning to ~s ~n", [TS,From,ffunc(MFA)]); + io_lib:format("~s (~100p) returning to ~s~n", [TS,From,ffunc(MFA)]); spawn when Size == 5 -> Pid = element(4, Trace), MFA = element(5, Trace), - io_lib:format("~s (~100p) spawn ~100p as ~s ~n", [TS,From,Pid,ffunc(MFA)]); + io_lib:format("~s (~100p) spawn ~100p as ~s~n", [TS,From,Pid,ffunc(MFA)]); Op -> - io_lib:format("~s (~100p) ~100p ~s ~n", [TS,From,Op,ftup(Trace,4,Size)]) + io_lib:format("~s (~100p) ~100p ~s~n", [TS,From,Op,ftup(Trace,4,Size)]) end. %%% These f* functions returns non-flat strings @@ -567,7 +782,7 @@ ftup(Trace, Index, Size) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -write_file(Frame, Filename, TraceOps, MatchSpecs, TPs) -> +write_file(Frame, Filename, TraceOps, MatchSpecs, Output, TPs) -> FormatMS = fun(#match_spec{name=Id, term=T, func=F}) -> io_lib:format("[{name,\"~s\"}, {term, ~w}, {func, \"~s\"}]", [Id, T, F]) @@ -581,6 +796,7 @@ write_file(Frame, Filename, TraceOps, MatchSpecs, TPs) -> "%%%\n%%% DO NOT EDIT!\n%%%\n", [["{ms, ", FormatMS(Ms), "}.\n"] || Ms <- MatchSpecs], "{traceopts, ", io_lib:format("~w",[TraceOps]) ,"}.\n", + "{output, ", io_lib:format("~w",[Output]) ,"}.\n", [FormatTP(TP) || TP <- TPs] ], case file:write_file(Filename, list_to_binary(Str)) of @@ -602,8 +818,9 @@ read_settings(Filename, #state{match_specs=Ms0, def_trace_opts=TO0} = State) -> {ok, Terms} -> Ms = lists:usort(Ms0 ++ [parse_ms(MsList) || {ms, MsList} <- Terms]), TOs = lists:usort(TO0 ++ proplists:get_value(traceopts, Terms, [])), + Out = proplists:get_value(output, Terms, []), lists:foldl(fun parse_tp/2, - State#state{match_specs=Ms, def_trace_opts=TOs}, + State#state{match_specs=Ms, def_trace_opts=TOs, output=Out}, Terms); {error, _} -> observer_wx:create_txt_dialog(State#state.panel, "Could not load settings", @@ -626,3 +843,24 @@ parse_tp({tp, Mod, FAs}, State) -> do_add_patterns({Mod, Patterns}, State); parse_tp(_, State) -> State. + +get_selected_items(Grid, Data) -> + get_indecies(get_selected_items(Grid, -1, []), Data). +get_selected_items(Grid, Index, ItemAcc) -> + Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL}, + {state, ?wxLIST_STATE_SELECTED}]), + case Item of + -1 -> + lists:reverse(ItemAcc); + _ -> + get_selected_items(Grid, Item, [Item | ItemAcc]) + end. + +get_indecies(Items, Data) -> + get_indecies(Items, 0, Data). +get_indecies([I|Rest], I, [H|T]) -> + [H|get_indecies(Rest, I+1, T)]; +get_indecies(Rest = [_|_], I, [_|T]) -> + get_indecies(Rest, I+1, T); +get_indecies(_, _, _) -> + []. diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index bad05ec016..6a634e06f0 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -21,9 +21,8 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). --export([process_trace/2, trace_pattern/4]). - --compile(export_all). +-export([process_trace/2, trace_pattern/4, select_nodes/2, + output/2, select_matchspec/3]). process_trace(Parent, Default) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", @@ -53,19 +52,15 @@ process_trace(Parent, Default) -> check_box(LinkBox, LinkBool), enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]), enable(LinkBox, [LinkAllRadio, LinkFirstRadio]), - wxRadioButton:setValue(SpwnAllRadio, lists:member(on_spawn, Default)), - wxRadioButton:setValue(SpwnFirstRadio, lists:member(on_first_spawn, Default)), - wxRadioButton:setValue(LinkAllRadio, lists:member(on_link, Default)), - wxRadioButton:setValue(LinkFirstRadio, lists:member(on_first_link, Default)), - - wxSizer:add(LeftSz, FuncBox, []), - wxSizer:add(LeftSz, SendBox, []), - wxSizer:add(LeftSz, RecBox, []), - wxSizer:add(LeftSz, EventBox, []), + [wxRadioButton:setValue(Radio, lists:member(Opt, Default)) || + {Radio, Opt} <- [{SpwnAllRadio, on_spawn}, {SpwnFirstRadio, on_first_spawn}, + {LinkAllRadio, on_link}, {LinkFirstRadio, on_first_link}]], + + [wxSizer:add(LeftSz, CheckBox, []) || CheckBox <- [FuncBox,SendBox,RecBox,EventBox]], wxSizer:add(LeftSz, 150, -1), - wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}]), - wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}]), + wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}, {proportion,1}]), + wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}, {proportion,1}]), wxPanel:setSizer(Panel, PanelSz), wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]), Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), @@ -81,26 +76,26 @@ process_trace(Parent, Default) -> enable(LinkBox, [LinkAllRadio, LinkFirstRadio]) end}]), - Res = case wxDialog:showModal(Dialog) of - ?wxID_OK -> - All = [{SendBox, send}, {RecBox, 'receive'}, - {FuncBox, functions}, {EventBox, events}, - {{SpawnBox, SpwnAllRadio}, on_spawn}, - {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, - {{LinkBox, LinkAllRadio}, on_link}, - {{LinkBox, LinkFirstRadio}, on_first_link}], - Check = fun({Box, Radio}) -> - wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio); - (Box) -> - wxCheckBox:getValue(Box) - end, - Opts = [Id || {Tick, Id} <- All, Check(Tick)], - {ok, lists:reverse(Opts)}; - ?wxID_CANCEL -> - cancel - end, - wxDialog:destroy(Dialog), - Res. + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + All = [{SendBox, send}, {RecBox, 'receive'}, + {FuncBox, functions}, {EventBox, events}, + {{SpawnBox, SpwnAllRadio}, on_spawn}, + {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, + {{LinkBox, LinkAllRadio}, on_link}, + {{LinkBox, LinkFirstRadio}, on_first_link}], + Check = fun({Box, Radio}) -> + wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio); + (Box) -> + wxCheckBox:getValue(Box) + end, + Opts = [Id || {Tick, Id} <- All, Check(Tick)], + wxDialog:destroy(Dialog), + lists:reverse(Opts); + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> try @@ -111,6 +106,10 @@ trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> catch cancel -> cancel end. +select_nodes(Parent, Nodes) -> + Choices = [{atom_to_list(X), X} || X <- Nodes], + check_selector(Parent, Choices). + module_selector(Parent, Node) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, @@ -167,6 +166,17 @@ module_selector(Parent, Node) -> end. function_selector(Parent, Node, Module) -> + Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]), + Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, + not(erl_internal:guard_bif(Name, Arity))]), + ParsedChoices = parse_function_names(Choices), + case check_selector(Parent, ParsedChoices) of + [] -> [{Module, '_', '_'}]; + FAs -> + [{Module, F, A} || {F,A} <- FAs] + end. + +check_selector(Parent, ParsedChoices) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Functions", [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, {size, {400, 400}}]), @@ -195,10 +205,6 @@ function_selector(Parent, Node, Module) -> wxWindow:setSizer(Dialog, MainSz), wxWindow:setFocus(TxtCtrl), %% Init - Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]), - Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, - not(erl_internal:guard_bif(Name, Arity))]), - ParsedChoices = parse_function_names(Choices), filter_listbox_data("", ParsedChoices, ListBox, false), %% Setup Event handling wxTextCtrl:connect(TxtCtrl, command_text_updated, @@ -242,22 +248,19 @@ function_selector(Parent, Node, Module) -> case wxDialog:showModal(Dialog) of ?wxID_OK -> wxDialog:destroy(Dialog), - case get_checked_funcs(ListBox, []) of - [] -> [{Module, '_', '_'}]; - FAs -> - [{Module, F, A} || {F,A} <- FAs] - end; + get_checked(ListBox, []); ?wxID_CANCEL -> wxDialog:destroy(Dialog), + get_checked(ListBox, []), throw(cancel) end. -get_checked_funcs(ListBox, Acc) -> +get_checked(ListBox, Acc) -> receive {ListBox, true, FA} -> - get_checked_funcs(ListBox, [FA|lists:delete(FA,Acc)]); + get_checked(ListBox, [FA|lists:delete(FA,Acc)]); {ListBox, false, FA} -> - get_checked_funcs(ListBox, lists:delete(FA,Acc)) + get_checked(ListBox, lists:delete(FA,Acc)) after 0 -> lists:reverse(Acc) end. @@ -370,6 +373,38 @@ select_matchspec(Pid, Parent, MatchSpecs) -> throw(cancel) end. +output(Parent, Default) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + MainSz = wxBoxSizer:new(?wxVERTICAL), + PanelSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Output"}]), + + %% Output select + WinB = wxCheckBox:new(Panel, ?wxID_ANY, "Window", []), + check_box(WinB, proplists:get_value(window, Default, true)), + ShellB = wxCheckBox:new(Panel, ?wxID_ANY, "Shell", []), + check_box(ShellB, proplists:get_value(shell, Default, false)), + [wxSizer:add(PanelSz, CheckBox, []) || CheckBox <- [WinB, ShellB]], + GetFileOpts = ttb_file_options(Panel, PanelSz, Default), + %% Set sizer and show dialog + wxPanel:setSizer(Panel, PanelSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {proportion,1}, {border, 3}]), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + wxWindow:setSizerAndFit(Dialog, MainSz), + wxSizer:setSizeHints(MainSz, Dialog), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Res = [{window, wxCheckBox:getValue(WinB)}, + {shell, wxCheckBox:getValue(ShellB)} | GetFileOpts()], + wxDialog:destroy(Dialog), + Res; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. + edit_ms(TextCtrl, Label0, Parent) -> Str = ensure_last_is_dot(wxStyledTextCtrl:getText(TextCtrl)), try @@ -570,3 +605,69 @@ ensure_last_is_dot(String) -> false -> String ++ "." end. + +ttb_file_options(Panel, Sizer, Default) -> + Top = wxBoxSizer:new(?wxVERTICAL), + NameS = wxBoxSizer:new(?wxHORIZONTAL), + FileBox = wxCheckBox:new(Panel, ?wxID_ANY, "File (Using ttb file tracer)", []), + check_box(FileBox, proplists:get_value(file, Default, false)), + wxSizer:add(Sizer, FileBox), + Desc = wxStaticText:new(Panel, ?wxID_ANY, "File"), + FileName = proplists:get_value(filename, Default, "ttb"), + FileT = wxTextCtrl:new(Panel, ?wxID_ANY, [{size, {150,-1}}, {value, FileName}]), + FileB = wxButton:new(Panel, ?wxID_ANY, [{label, "Browse"}]), + wxSizer:add(NameS, Desc, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(NameS, FileT, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(NameS, FileB, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]), + + WrapB = wxCheckBox:new(Panel, ?wxID_ANY, "Wrap logs"), + WrapSz = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_sz, Default, 128), + 64, 10*1024, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]), + WrapC = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_c, Default, 8), + 2, 100, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]), + + wxSizer:add(Top, NameS, [{flag, ?wxEXPAND}]), + wxSizer:add(Top, WrapB, []), + wxSizer:add(Top, WrapSz,[{flag, ?wxEXPAND}]), + wxSizer:add(Top, WrapC, [{flag, ?wxEXPAND}]), + wxSizer:add(Sizer, Top, [{flag, ?wxEXPAND bor ?wxLEFT},{border, 20}]), + + Enable = fun(UseFile, UseWrap0) -> + UseWrap = UseFile andalso UseWrap0, + [wxWindow:enable(W, [{enable, UseFile}]) || W <- [Desc,FileT,FileB,WrapB]], + [wxWindow:enable(W, [{enable, UseWrap}]) || W <- [WrapSz, WrapC]], + check_box(WrapB, UseWrap0) + end, + Enable(proplists:get_value(file, Default, false), proplists:get_value(wrap, Default, false)), + wxPanel:connect(FileBox, command_checkbox_clicked, + [{callback, fun(_,_) -> + Enable(wxCheckBox:getValue(FileBox), + wxCheckBox:getValue(WrapB)) + end}]), + wxPanel:connect(WrapB, command_checkbox_clicked, + [{callback, fun(_,_) -> + Enable(true, wxCheckBox:getValue(WrapB)) + end}]), + wxPanel:connect(FileB, command_button_clicked, + [{callback, fun(_,_) -> get_file(FileT) end}]), + fun() -> + [{file, wxCheckBox:getValue(FileBox)}, + {filename, wxTextCtrl:getValue(FileT)}, + {wrap, wxCheckBox:getValue(WrapB)}, + {wrap_sz, wxSlider:getValue(WrapSz)}, + {wrap_c, wxSlider:getValue(WrapC)}] + end. + +get_file(Text) -> + Str = wxTextCtrl:getValue(Text), + Dialog = wxFileDialog:new(Text, + [{message, "Select a file"}, + {default_file, Str}]), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Dir = wxFileDialog:getDirectory(Dialog), + File = wxFileDialog:getFilename(Dialog), + wxTextCtrl:setValue(Text, filename:join(Dir, File)); + _ -> ok + end, + wxFileDialog:destroy(Dialog). diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 7b5cdb44b9..31d5f3d632 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -24,8 +24,6 @@ -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/3]). - -include("observer_defs.hrl"). -import(observer_lib, [to_str/1]). @@ -221,33 +219,10 @@ search_area(Parent) -> edit(Index, #state{pid=Pid, frame=Frame}) -> Str = get_row(Pid, Index, all), - Dialog = wxTextEntryDialog:new(Frame, "Edit object:", [{value, Str}]), - case wxTextEntryDialog:showModal(Dialog) of - ?wxID_OK -> - New = wxTextEntryDialog:getValue(Dialog), - wxTextEntryDialog:destroy(Dialog), - case Str =:= New of - true -> ok; - false -> - complete_edit(Index, New, Pid) - end; - ?wxID_CANCEL -> - wxTextEntryDialog:destroy(Dialog) - end. - -complete_edit(Row, New0, Pid) -> - New = case lists:reverse(New0) of - [$.|_] -> New0; - _ -> New0 ++ "." - end, - try - {ok, Tokens, _} = erl_scan:string(New), - {ok, Term} = erl_parse:parse_term(Tokens), - Pid ! {edit, Row, Term} - catch _:{badmatch, {error, {_, _, Err}}} -> - self() ! {error, ["Parse error: ", Err]}; - _Err -> - self() ! {error, ["Syntax error in: ", New]} + case observer_lib:user_term(Frame, "Edit object:", Str) of + cancel -> ok; + {ok, Term} -> Pid ! {edit, Index, Term}; + Err = {error, _} -> self() ! Err end. handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) -> @@ -260,14 +235,7 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, {noreply, State}; 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-?LCTRL_WDECR, lists:seq(0, Cols - 2)), - Size = max(?DEFAULT_COL_WIDTH, Last), - wxListCtrl:setColumnWidth(Grid, Cols-1, Size) - end), + observer_lib:set_listctrl_col_size(Grid, W), {noreply, State}; handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, @@ -537,7 +505,7 @@ table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) -> %% io:format("ignoring refresh", []), table_holder(S0); refresh -> - GetTab = rpc:call(S0#holder.node, ?MODULE, get_table, + GetTab = rpc:call(S0#holder.node, observer_backend, get_table, [self(), S0#holder.tabid, S0#holder.source]), table_holder(S0#holder{pid=GetTab}); {delete, Row} -> @@ -738,49 +706,6 @@ insert(Object, #holder{tabid=Id, source=Source, node=Node}) -> end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -get_table(Parent, Table, Module) -> - spawn(fun() -> - link(Parent), - get_table2(Parent, Table, Module) - end). - -get_table2(Parent, Table, Type) -> - Size = case Type of - ets -> ets:info(Table, size); - mnesia -> mnesia:table_info(Table, size) - end, - case Size > 0 of - false -> - Parent ! {self(), '$end_of_table'}, - normal; - true when Type =:= ets -> - Mem = ets:info(Table, memory), - Average = Mem div Size, - NoElements = max(10, 20000 div Average), - get_ets_loop(Parent, ets:match(Table, '$1', NoElements)); - true -> - Mem = mnesia:table_info(Table, memory), - Average = Mem div Size, - NoElements = max(10, 20000 div Average), - Ms = [{'$1', [], ['$1']}], - Get = fun() -> - get_mnesia_loop(Parent, mnesia:select(Table, Ms, NoElements, read)) - end, - %% Not a transaction, we don't want to grab locks when inspecting the table - mnesia:async_dirty(Get) - end. - -get_ets_loop(Parent, '$end_of_table') -> - Parent ! {self(), '$end_of_table'}; -get_ets_loop(Parent, {Match, Cont}) -> - Parent ! {self(), Match}, - get_ets_loop(Parent, ets:match(Cont)). - -get_mnesia_loop(Parent, '$end_of_table') -> - Parent ! {self(), '$end_of_table'}; -get_mnesia_loop(Parent, {Match, Cont}) -> - Parent ! {self(), Match}, - get_ets_loop(Parent, mnesia:select(Cont)). column_names(Node, Type, Table) -> case Type of diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index ded03fadb1..ad3e8c14ab 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -23,8 +23,6 @@ -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? - -behaviour(wx_object). -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). @@ -92,8 +90,7 @@ init([Notebook, Parent]) -> wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - Timer = observer_lib:start_timer(10), - {Panel, #state{grid=Grid, parent=Parent, timer=Timer}}. + {Panel, #state{grid=Grid, parent=Parent, timer={false, 10}}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> @@ -121,20 +118,18 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) ?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}}; + case get_tables2(Node, Opt) of + Error = {error, _} -> + self() ! Error, + {noreply, State}; + Tables -> + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{opt=Opt, tabs=Tabs}} + end; 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-?LCTRL_WDECR, lists:seq(0, Cols - 2)), - Size = max(200, Last), - wxListCtrl:setColumnWidth(Grid, Cols-1, Size) - end), + observer_lib:set_listctrl_col_size(Grid, W), {noreply, State}; handle_event(#wx{obj=Grid, event=#wxList{type=command_list_item_activated, @@ -169,18 +164,17 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL}, Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), {noreply, State#state{timer=Timer}}; -handle_event(Event, State) -> - io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), - {noreply, State}. +handle_event(Event, _State) -> + error({unhandled_event, Event}). handle_sync_event(_Event, _Obj, _State) -> ok. -handle_call(_Event, _From, State) -> - {noreply, State}. +handle_call(Event, From, _State) -> + error({unhandled_call, Event, From}). -handle_cast(_Event, State) -> - {noreply, State}. +handle_cast(Event, _State) -> + error({unhandled_cast, Event}). handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> Tables = get_tables(Node, Opt), @@ -200,12 +194,6 @@ handle_info(not_active, State = #state{timer = Timer0}) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{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}; @@ -240,95 +228,41 @@ create_menus(Parent, #opt{sys_hidden=Sys, unread_hidden=UnR, type=Type}) -> ]}], 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}, +get_tables(Node, Opts) -> + case get_tables2(Node, Opts) of + Error = {error, _} -> + self() ! Error, []; + Res -> + Res + end. +get_tables2(Node, #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread}) -> + Args = [Type, [{sys_hidden,Sys}, {unread_hidden,Unread}]], + case rpc:call(Node, observer_backend, get_table_list, Args) of + {badrpc, Error} -> + {error, Error}; + Error = {error, _} -> + Error; Result -> - Result + [list_to_tabrec(Tab) || Tab <- 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)). +list_to_tabrec(PL) -> + #tab{name = proplists:get_value(name, PL), + id = proplists:get_value(id, PL, ignore), + size = proplists:get_value(size, PL, 0), + memory= proplists:get_value(memory, PL, 0), %% In bytes + owner=proplists:get_value(owner, PL), + reg_name=proplists:get_value(reg_name, PL), + protection = proplists:get_value(protection, PL, public), + type=proplists:get_value(type, PL, set), + keypos=proplists:get_value(keypos, PL, 1), + heir=proplists:get_value(heir, PL, none), + compressed=proplists:get_value(compressed, PL, false), + fixed=proplists:get_value(fixed, PL, false), + %% Mnesia Info + storage =proplists:get_value(storage, PL), + index = proplists:get_value(index, PL)}. display_table_info(Parent0, Node, Source, Table) -> Parent = observer_lib:get_wx_parent(Parent0), @@ -382,59 +316,6 @@ list_to_strings([A]) -> integer_to_list(A); list_to_strings([A,B]) -> integer_to_list(A) ++ " ," ++ list_to_strings(B). -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) -> Str = io_lib:format("ERROR: ~s~n",[Foo]), observer_lib:display_info_dialog(Str). @@ -470,6 +351,3 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> end, lists:foldl(Update, 0, ProcInfo), ProcInfo. - -ignore(true, Reason) -> throw(Reason); -ignore(_,_ ) -> ok. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index f9dec1ab1b..5a593abf11 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -54,13 +54,17 @@ tv_panel, sys_panel, trace_panel, + app_panel, active_tab, node, nodes }). start() -> - wx_object:start(?MODULE, [], []). + case wx_object:start(?MODULE, [], []) of + Err = {error, _} -> Err; + _Obj -> ok + end. create_menus(Object, Menus) when is_list(Menus) -> wx_object:call(Object, {create_menus, Menus}). @@ -78,7 +82,7 @@ init(_Args) -> wx:new(), catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer", - [{size, {1000, 500}}, {style, ?wxDEFAULT_FRAME_STYLE}]), + [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]), IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"), Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), wxFrame:setIcon(Frame, Icon), @@ -125,6 +129,10 @@ setup(#state{frame = Frame} = State) -> %% I postpone the creation of the other tabs so they can query/use %% the window size + %% App Viewer Panel + AppPanel = observer_app_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, AppPanel, "Applications", []), + %% Process Panel ProPanel = observer_pro_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, ProPanel, "Processes", []), @@ -137,6 +145,7 @@ setup(#state{frame = Frame} = State) -> TracePanel = observer_trace_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), + %% Force redraw (window needs it) wxWindow:refresh(Panel), @@ -150,16 +159,24 @@ setup(#state{frame = Frame} = State) -> pro_panel = ProPanel, tv_panel = TVPanel, trace_panel = TracePanel, + app_panel = AppPanel, active_tab = SysPid, node = node(), nodes = Nodes }, %% Create resources which we don't want to duplicate - SysFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), - SysFontSize = wxFont:getPointSize(SysFont), - Modern = wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), - put({font, modern}, Modern), - put({font, fixed}, Modern), + SysFont = wxSystemSettings:getFont(?wxSYS_SYSTEM_FIXED_FONT), + %% OemFont = wxSystemSettings:getFont(?wxSYS_OEM_FIXED_FONT), + %% io:format("Sz sys ~p(~p) oem ~p(~p)~n", + %% [wxFont:getPointSize(SysFont), wxFont:isFixedWidth(SysFont), + %% wxFont:getPointSize(OemFont), wxFont:isFixedWidth(OemFont)]), + Fixed = case wxFont:isFixedWidth(SysFont) of + true -> SysFont; + false -> %% Sigh + SysFontSize = wxFont:getPointSize(SysFont), + wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL) + end, + put({font, fixed}, Fixed), UpdState. @@ -270,10 +287,13 @@ handle_cast(_Cast, State) -> handle_call({create_menus, TabMenus}, _From, State = #state{menubar=MenuBar, menus=PrevTabMenus}) -> - wx:batch(fun() -> - clean_menus(PrevTabMenus, MenuBar), - observer_lib:create_menus(TabMenus, MenuBar, plugin) - end), + if TabMenus == PrevTabMenus -> ignore; + true -> + wx:batch(fun() -> + clean_menus(PrevTabMenus, MenuBar), + observer_lib:create_menus(TabMenus, MenuBar, plugin) + end) + end, {reply, ok, State#state{menus=TabMenus}}; handle_call({get_attrib, Attrib}, _From, State) -> @@ -302,6 +322,10 @@ handle_info({nodedown, Node}, create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), {noreply, State3}; +handle_info({'EXIT', _Pid, _Reason}, State) -> + io:format("Child crashed exiting: ~p ~p~n", [_Pid,_Reason]), + {stop, normal, State}; + handle_info(_Info, State) -> {noreply, State}. @@ -368,9 +392,8 @@ connect2(NodeName, Opts, Cookie) -> {error, net_kernel, Reason} end. -change_node_view(Node, State = #state{pro_panel=Pro, sys_panel=Sys, tv_panel=Tv}) -> - lists:foreach(fun(Pid) -> wx_object:get_pid(Pid) ! {node, Node} end, - [Pro, Sys, Tv]), +change_node_view(Node, State) -> + get_active_pid(State) ! {active, Node}, StatusText = ["Observer - " | atom_to_list(Node)], wxFrame:setTitle(State#state.frame, StatusText), wxStatusBar:setStatusText(State#state.status_bar, StatusText), @@ -380,12 +403,14 @@ check_page_title(Notebook) -> Selection = wxNotebook:getSelection(Notebook), wxNotebook:getPageText(Notebook, Selection). -get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace}) -> +get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, + tv_panel=Tv, trace_panel=Trace, app_panel=App}) -> Panel = case check_page_title(Notebook) of "Processes" -> Pro; "System" -> Sys; "Table Viewer" -> Tv; - ?TRACE_STR -> Trace + ?TRACE_STR -> Trace; + "Applications" -> App end, wx_object:get_pid(Panel). @@ -468,7 +493,7 @@ default_menus(NodesMenuItems) -> [#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]} end, case os:type() =:= {unix, darwin} of - false -> + false -> FileMenu = {"File", [Quit]}, HelpMenu = {"Help", [About,Help]}, [FileMenu, NodeMenu, HelpMenu]; |