diff options
Diffstat (limited to 'lib/observer')
22 files changed, 1123 insertions, 228 deletions
diff --git a/lib/observer/doc/src/Makefile b/lib/observer/doc/src/Makefile index cd9f9466ca..0f564d3299 100644 --- a/lib/observer/doc/src/Makefile +++ b/lib/observer/doc/src/Makefile @@ -133,16 +133,16 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_docs_spec: docs - $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf - $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf - $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" + $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" + $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" $(INSTALL_DATA) $(HTMLDIR)/* \ - $(RELSYSDIR)/doc/html - $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) - $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 - $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3 - $(INSTALL_DIR) $(RELEASE_PATH)/man/man6 - $(INSTALL_DATA) $(MAN6_FILES) $(RELEASE_PATH)/man/man6 + "$(RELSYSDIR)/doc/html" + $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" + $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" + $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" + $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6" + $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6" release_spec: diff --git a/lib/observer/doc/src/etop.xml b/lib/observer/doc/src/etop.xml index 78047caab3..af6bb2442b 100644 --- a/lib/observer/doc/src/etop.xml +++ b/lib/observer/doc/src/etop.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2002</year> - <year>2011</year> + <year>2012</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -114,6 +114,37 @@ Default: <c>on</c></item> </description> <funcs> <func> + <name>start() -> ok</name> + <fsummary>Start etop</fsummary> + <desc> + <p>This function starts <c>etop</c>. + Note that etop is preferably started with the etop and getop scripts</p> + </desc> + </func> + <func> + <name>start(Options) -> ok</name> + <fsummary>Start etop</fsummary> + <type> + <v>Options = [Option]</v> + <v>Option = {Key, Value}</v> + <v>Key = atom()</v> + <v>Value = term()</v> + </type> + <desc> + <p>This function starts <c>etop</c>. Use + <seealso marker="#help/0">help/0</seealso> to see a + description of the possible options.</p> + </desc> + </func> + <func> + <name>help() -> ok</name> + <fsummary>Print etop's help</fsummary> + <desc> + <p>This function prints the help of <c>etop</c> and + its options.</p> + </desc> + </func> + <func> <name>config(Key,Value) -> Result</name> <fsummary>Change tool's configuration</fsummary> <type> diff --git a/lib/observer/priv/erlang_observer.png b/lib/observer/priv/erlang_observer.png Binary files differindex 78e70461b1..01723d210b 100644 --- a/lib/observer/priv/erlang_observer.png +++ b/lib/observer/priv/erlang_observer.png diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index ca26afc11d..7135a6abd5 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2002-2011. All Rights Reserved. +# Copyright Ericsson AB 2002-2012. 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 @@ -45,6 +45,7 @@ MODULES= \ observer_app_wx \ observer_lib \ observer_wx \ + observer_perf_wx \ observer_pro_wx \ observer_procinfo \ observer_sys_wx \ @@ -128,20 +129,20 @@ docs: include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/examples - $(INSTALL_DATA) $(EXAMPLE_FILES) $(RELSYSDIR)/examples - $(INSTALL_DIR) $(RELSYSDIR)/include - $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include - $(INSTALL_DIR) $(RELSYSDIR)/ebin - $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin - $(INSTALL_DIR) $(RELSYSDIR)/priv/bin - $(INSTALL_SCRIPT) $(EXECUTABLES) $(RELSYSDIR)/priv/bin - $(INSTALL_DIR) $(RELSYSDIR)/priv/crashdump_viewer - $(INSTALL_DATA) $(WEBTOOLFILES) $(RELSYSDIR)/priv - $(INSTALL_DATA) $(GIF_FILES) $(RELSYSDIR)/priv/crashdump_viewer + $(INSTALL_DIR) "$(RELSYSDIR)/src" + $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)/src" + $(INSTALL_DATA) $(INTERNAL_HRL_FILES) "$(RELSYSDIR)/src" + $(INSTALL_DIR) "$(RELSYSDIR)/examples" + $(INSTALL_DATA) $(EXAMPLE_FILES) "$(RELSYSDIR)/examples" + $(INSTALL_DIR) "$(RELSYSDIR)/include" + $(INSTALL_DATA) $(HRL_FILES) "$(RELSYSDIR)/include" + $(INSTALL_DIR) "$(RELSYSDIR)/ebin" + $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)/ebin" + $(INSTALL_DIR) "$(RELSYSDIR)/priv/bin" + $(INSTALL_SCRIPT) $(EXECUTABLES) "$(RELSYSDIR)/priv/bin" + $(INSTALL_DIR) "$(RELSYSDIR)/priv/crashdump_viewer" + $(INSTALL_DATA) $(WEBTOOLFILES) "$(RELSYSDIR)/priv" + $(INSTALL_DATA) $(GIF_FILES) "$(RELSYSDIR)/priv/crashdump_viewer" release_docs_spec: diff --git a/lib/observer/src/crashdump_viewer_html.erl b/lib/observer/src/crashdump_viewer_html.erl index 24a80b1916..3151b83bfb 100644 --- a/lib/observer/src/crashdump_viewer_html.erl +++ b/lib/observer/src/crashdump_viewer_html.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2011. All Rights Reserved. +%% Copyright Ericsson AB 2003-2012. 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 @@ -1394,7 +1394,7 @@ timers_table(Timer) -> td("ALIGN=right",Time)]). loaded_mods_table(#loaded_mod{mod=Mod,current_size=CS,old_size=OS}) -> - tr([td(href(["loaded_mod_details?mod=",Mod],Mod)), + tr([td(href(["loaded_mod_details?mod=",http_uri:encode(Mod)],Mod)), td("ALIGN=right",CS), td("ALIGN=right",OS)]). diff --git a/lib/observer/src/etop.erl b/lib/observer/src/etop.erl index 0bf1d68534..428757e5ce 100644 --- a/lib/observer/src/etop.erl +++ b/lib/observer/src/etop.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. +%% Copyright Ericsson AB 2002-2012. 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 @@ -31,9 +31,9 @@ help() -> io:format( - "Usage of the erlang top program~n" - "Options are set as command line parameters as in -node a@host -..~n" - "or as parameter to etop:start([{node, a@host}, {...}])~n" + "Usage of the Erlang top program~n~n" + "Options are set as command line parameters as in -node my@host~n" + "or as parameters to etop:start([{node, my@host}, {...}]).~n~n" "Options are:~n" " node atom Required The erlang node to measure ~n" " port integer The used port, NOTE: due to a bug this program~n" diff --git a/lib/observer/src/etop_gui.erl b/lib/observer/src/etop_gui.erl index ff1b8078ad..f5cc0deb38 100644 --- a/lib/observer/src/etop_gui.erl +++ b/lib/observer/src/etop_gui.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. +%% Copyright Ericsson AB 2002-2012. 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 @@ -17,6 +17,13 @@ %% %CopyrightEnd% %% -module(etop_gui). +-compile([{nowarn_deprecated_function,{gs,config,2}}, + {nowarn_deprecated_function,{gs,create,3}}, + {nowarn_deprecated_function,{gs,create,4}}, + {nowarn_deprecated_function,{gs,destroy,1}}, + {nowarn_deprecated_function,{gs,read,2}}, + {nowarn_deprecated_function,{gs,start,0}}]). + -author('[email protected]'). -export([init/1,stop/1]). diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 62046577ad..380532e90c 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -27,6 +27,12 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). +%% Import drawing wrappers +-import(observer_perf_wx, [haveGC/1, + setPen/2, setFont/3, setBrush/2, + strokeLine/5, strokeLines/2, drawRoundedRectangle/6, + drawText/4, getTextExtent/2]). + -record(state, { parent, @@ -37,7 +43,8 @@ current, app, sel, - appmon + appmon, + usegc = false }). -record(paint, {font, pen, brush, sel, links}). @@ -63,6 +70,8 @@ -define(ID_TRACE_TREE_PIDS, 106). -define(ID_TRACE_TREE_NAMES, 107). +-define(wxGC, wxGraphicsContext). + start_link(Notebook, Parent) -> wx_object:start_link(?MODULE, [Notebook, Parent], []). @@ -99,22 +108,34 @@ init([Notebook, Parent]) -> wxPanel:connect(DrawingArea, left_up), wxPanel:connect(DrawingArea, left_dclick), wxPanel:connect(DrawingArea, right_down), + case os:type() of + {win32, _} -> %% Ignore erase on windows + wxPanel:connect(DrawingArea, erase_background, [{callback, fun(_,_) -> ok end}]); + _ -> ok + end, - DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + UseGC = haveGC(DrawingArea), + Font = case os:type() of + {unix,_} when UseGC -> + wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_NORMAL); + _ -> + wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT) + end, SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), + GreyBrush = wxBrush:new({230,230,240}), SelBrush = wxBrush:new(SelCol), LinkPen = wxPen:new(SelCol, [{width, 2}]), - %% GC = wxGraphicsContext:create(DrawingArea), - %% _Font = wxGraphicsContext:createFont(GC, DefFont), + process_flag(trap_exit, true), {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 + usegc = UseGC, + paint=#paint{font = Font, + pen = wxPen:new({80,80,80}, [{width, 2}]), + brush= GreyBrush, + sel = SelBrush, + links= LinkPen } }}. @@ -126,11 +147,11 @@ setup_scrollbar({CW, CH}, AppWin, #app{dim={W0,H0}}) -> H = max(H0,CH), PPC = 20, if W0 =< CW, H0 =< CH -> - wxScrolledWindow:setScrollbars(AppWin, W, H, 1, 1); + wxScrolledWindow:setScrollbars(AppWin, W, H, 0, 0); H0 =< CH -> - wxScrolledWindow:setScrollbars(AppWin, PPC, H, W div PPC+1, 1); + wxScrolledWindow:setScrollbars(AppWin, PPC, H, W div PPC+1, 0); W0 =< CW -> - wxScrolledWindow:setScrollbars(AppWin, W, PPC, 1, H div PPC+1); + wxScrolledWindow:setScrollbars(AppWin, W, PPC, 0, H div PPC+1); true -> wxScrolledWindow:setScrollbars(AppWin, PPC, PPC, W div PPC+1, H div PPC+1) end; @@ -183,7 +204,7 @@ handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}}, 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 + case observer_lib:user_term(Panel, "Enter Exit Reason", "kill") of cancel -> ok; {ok, Term} -> exit(Pid, Term); {error, Error} -> observer_lib:display_info_dialog(Error) @@ -215,12 +236,28 @@ handle_event(Event, _State) -> %%%%%%%%%% handle_sync_event(#wx{event = #wxPaint{}},_, - #state{app_w=DA, app=App, sel=Sel, paint=Paint}) -> + #state{app_w=DA, app=App, sel=Sel, paint=Paint, usegc=UseGC}) -> %% PaintDC must be created in a callback to work on windows. - DC = wxPaintDC:new(DA), - wxScrolledWindow:doPrepareDC(DA,DC), + IsWindows = element(1, os:type()) =:= win32, + %% Avoid Windows flickering hack + DC = if IsWindows -> wx:typeCast(wxBufferedPaintDC:new(DA), wxPaintDC); + true -> wxPaintDC:new(DA) + end, + IsWindows andalso wxDC:clear(DC), + GC = case UseGC of + true -> + GC0 = ?wxGC:create(DC), + %% Argh must handle scrolling when using ?wxGC + {Sx,Sy} = wxScrolledWindow:calcScrolledPosition(DA, {0,0}), + ?wxGC:translate(GC0, Sx,Sy), + GC0; + false -> + wxScrolledWindow:doPrepareDC(DA,DC), + DC + end, %% Nothing is drawn until wxPaintDC is destroyed. - draw(DC, App, Sel, Paint), + draw({UseGC, GC}, App, Sel, Paint), + UseGC andalso ?wxGC:destroy(GC), wxPaintDC:destroy(DC), ok. %%%%%%%%%% @@ -232,43 +269,60 @@ handle_cast(Event, _State) -> %%%%%%%%%% 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, + Pid = try + Node = node(Appmon), + Appmon + catch _:_ -> + {ok, P} = appmon_info:start_link(Node, self(), []), + P + 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}) -> +handle_info(not_active, State = #state{appmon=AppMon}) -> appmon_info:app_ctrl(AppMon, node(AppMon), false, []), - (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []), - {noreply, State}; - + lists:member(node(AppMon), nodes()) andalso exit(AppMon, normal), + observer_wx:set_status(""), + {noreply, State#state{appmon=undefined}}; handle_info({delivery, Pid, app_ctrl, _, Apps0}, - State = #state{appmon=Pid, apps_w=LBox}) -> + State = #state{appmon=Pid, apps_w=LBox, current=Curr0}) -> Apps = [atom_to_list(App) || {_, App, {_, _, _}} <- Apps0], wxListBox:clear(LBox), wxListBox:appendStrings(LBox, [App || App <- lists:sort(Apps)]), - {noreply, State}; - + case Apps of + [App|_] when Curr0 =:= undefined -> + Curr = list_to_atom(App), + appmon_info:app(Pid, Curr, true, []), + {noreply, State#state{current=Curr}}; + _ -> + {noreply, State} + end; 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, + State = #state{panel=Panel, appmon=Pid, current=Curr, usegc=UseGC, app_w=AppWin, paint=#paint{font=Font}}) -> - App = build_tree(AppData, {AppWin,Font}), + GC = if UseGC -> ?wxGC:create(AppWin); + true -> wxWindowDC:new(AppWin) + end, + FontW = {UseGC, GC}, + setFont(FontW, Font, {0,0,0}), + App = build_tree(AppData, FontW), + if UseGC -> ?wxGC:destroy(GC); + true -> wxWindowDC:destroy(GC) + end, setup_scrollbar(AppWin, App), wxWindow:refresh(Panel), wxWindow:layout(Panel), {noreply, State#state{app=App, sel=undefined}}; +handle_info({'EXIT', _, noconnection}, State) -> + {noreply, State}; +handle_info({'EXIT', _, normal}, State) -> + {noreply, State}; handle_info(_Event, State) -> %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), {noreply, State}. @@ -286,6 +340,7 @@ handle_mouse_click(Node = {#box{s1=#str{pid=Pid}},_}, Type, right_down -> popup_menu(Panel); _ -> ok end, + observer_wx:set_status(io_lib:format("Pid: ~p", [Pid])), wxWindow:refresh(AppWin), State#state{sel=Node}; handle_mouse_click(_, _, State = #state{sel=undefined}) -> @@ -294,6 +349,7 @@ handle_mouse_click(_, right_down, State=#state{panel=Panel}) -> popup_menu(Panel), State; handle_mouse_click(_, _, State=#state{app_w=AppWin}) -> + observer_wx:set_status(""), wxWindow:refresh(AppWin), State#state{sel=undefined}. @@ -321,10 +377,11 @@ popup_menu(Panel) -> 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"), + wxMenu:append(Menu, ?ID_PROC_MSG, "Send Msg"), + wxMenu:append(Menu, ?ID_PROC_KILL, "Kill process"), wxWindow:popupMenu(Panel, Menu), wxMenu:destroy(Menu). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% locate_node(X, _Y, [{Box=#box{x=BX}, _Chs}|_Rest]) when X < BX -> @@ -354,11 +411,11 @@ locate_box(From, []) -> {false, From}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -build_tree({Root, P2Name, Links, XLinks0}, Font) -> +build_tree({Root, P2Name, Links, XLinks0}, FontW) -> 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), + {_, Tree0} = build_tree2(Root, Lookup, Name2P, FontW), {Tree, Dim} = calc_tree_size(Tree0), Fetch = fun({From, To}, Acc) -> try {value, ToPid} = gb_trees:lookup(To, Name2P), @@ -371,18 +428,18 @@ build_tree({Root, P2Name, Links, XLinks0}, Font) -> XLinks = lists:foldl(Fetch, [], XLinks0), #app{ptree=Tree, dim=Dim, links=XLinks}. -build_tree2(Root, Tree0, N2P, Font) -> +build_tree2(Root, Tree0, N2P, FontW) -> case gb_trees:lookup(Root, Tree0) of - none -> {Tree0, {box(Root, N2P, Font), []}}; + none -> {Tree0, {box(Root, N2P, FontW), []}}; {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} = build_tree2(Child, T0, N2P, FontW), {T, [C|Acc]} end, {Tree1, []}, Children), - {Tree, {box(Root, N2P, Font), CHs}} + {Tree, {box(Root, N2P, FontW), CHs}} end. calc_tree_size(Tree) -> @@ -427,15 +484,15 @@ middle([{#box{y=Y0},_}|List], _) -> {#box{y=Y1},_} = lists:last(List), (Y0+Y1) div 2. -box(Str0, N2P, {Win,Font}) -> +box(Str0, N2P, FontW) -> 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}]), + {TW,TH} = getTextExtent(FontW, Str), 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{w=round(TW)+?BX_E, h=round(TH)+?BY_E, s1=Data}. box_to_pid(#box{s1=#str{pid=Pid}}) -> Pid. box_to_reg(#box{s1=#str{text=[$<|_], pid=Pid}}) -> Pid; @@ -453,39 +510,30 @@ 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), + 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), + setPen(DC, Pen), + %% ?wxGC:drawRectangle(DC, 2,2, _W-2,_H-2), %% DEBUG + setBrush(DC, Brush), + setFont(DC, Font, {0,0,0}), 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), + setBrush(DC, SelBrush), + 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), + 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}), + strokeLine(DC, X+W, CY, CX, CY), {CX, CY} end, draw_link(Parent, Box, DC), @@ -495,9 +543,9 @@ 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}); + strokeLine(DC, CX, CY, X, CY); false -> - wxDC:drawLines(DC, [{CX, CY}, {CX, Y}, {X,Y}]) + strokeLines(DC, [{CX, CY}, {CX, Y}, {X,Y}]) end; draw_link(_, _, _) -> ok. @@ -516,9 +564,8 @@ 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}]). + strokeLines(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}); + drawText(DC, Text, X+Sx,Y+Sy); draw_str(_, _, _, _) -> ok. diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 967baa5c7a..3b924d46cf 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -154,7 +154,9 @@ to_str(Value) when is_atom(Value) -> to_str({bytes, B}) -> KB = B div 1024, MB = KB div 1024, + GB = MB div 1024, if + GB > 10 -> integer_to_list(GB) ++ " gB"; MB > 10 -> integer_to_list(MB) ++ " mB"; KB > 0 -> integer_to_list(KB) ++ " kB"; true -> integer_to_list(B) ++ " B " @@ -339,17 +341,37 @@ user_term(Parent, Title, Default) -> ?wxID_OK -> Str = wxTextEntryDialog:getValue(Dialog), wxTextEntryDialog:destroy(Dialog), - parse_string(Str); + parse_string(ensure_last_is_dot(Str)); ?wxID_CANCEL -> - wxTextEntryDialog:destroy(Dialog) + wxTextEntryDialog:destroy(Dialog), + cancel 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 -> + Tokens = case erl_scan:string(Str) of + {ok, Ts, _} -> Ts; + {error, {_SLine, SMod, SError}, _} -> + throw(io_lib:format("~s", [SMod:format_error(SError)])) + end, + case erl_parse:parse_term(Tokens) of + {error, {_PLine, PMod, PError}} -> + throw(io_lib:format("~s", [PMod:format_error(PError)])); + Res -> Res + end + catch + throw:ErrStr -> + {error, ErrStr}; + _:_Err -> {error, ["Syntax error in: ", Str]} end. + +ensure_last_is_dot([]) -> + "."; +ensure_last_is_dot(String) -> + case lists:last(String) =:= $. of + true -> + String; + false -> + String ++ "." + end. diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl new file mode 100644 index 0000000000..abf90ac612 --- /dev/null +++ b/lib/observer/src/observer_perf_wx.erl @@ -0,0 +1,575 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. 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_perf_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]). + +%% Drawing wrappers for DC and GC areas +-export([haveGC/1, + setPen/2, setFont/3, setBrush/2, + strokeLine/5, strokeLines/2, drawRoundedRectangle/6, + drawText/4, getTextExtent/2]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-record(state, + { + offset = 0.0, + active = false, + parent, + windows, + data = {0, queue:new()}, + panel, + paint, + appmon, + usegc = false + }). + +-define(wxGC, wxGraphicsContext). + +-record(paint, {font, small, pen, pen2, pens}). + +-define(RQ_W, 1). +-define(MEM_W, 2). +-define(IO_W, 3). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + try + Panel = wxPanel:new(Notebook), + Main = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN, + CPU = wxPanel:new(Panel, [{winid, ?RQ_W}, {style,Style}]), + wxWindow:setBackgroundColour(CPU, ?wxWHITE), + wxSizer:add(Main, CPU, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, {border, 5}]), + MemIO = wxBoxSizer:new(?wxHORIZONTAL), + MEM = wxPanel:new(Panel, [{winid, ?MEM_W}, {style,Style}]), + wxWindow:setBackgroundColour(MEM, ?wxWHITE), + IO = wxPanel:new(Panel, [{winid, ?IO_W}, {style,Style}]), + wxWindow:setBackgroundColour(IO, ?wxWHITE), + wxSizer:add(MemIO, MEM, [{flag, ?wxEXPAND bor ?wxLEFT}, + {proportion, 1}, {border, 5}]), + wxSizer:add(MemIO, IO, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}, + {proportion, 1}, {border, 5}]), + wxSizer:add(Main, MemIO, [{flag, ?wxEXPAND bor ?wxDOWN}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Main), + + wxPanel:connect(CPU, paint, [callback]), + wxPanel:connect(IO, paint, [callback]), + wxPanel:connect(MEM, paint, [callback]), + case os:type() of + {win32, _} -> %% Ignore erase on windows + wxPanel:connect(CPU, erase_background, [{callback, fun(_,_) -> ok end}]), + wxPanel:connect(IO, erase_background, [{callback, fun(_,_) -> ok end}]), + wxPanel:connect(MEM, erase_background, [{callback, fun(_,_) -> ok end}]); + _ -> ok + end, + + UseGC = haveGC(Panel), + {Font, SmallFont} + = case os:type() of + {unix, _} when UseGC -> + %% Def font is really small when using Graphics contexts for some reason + %% Hardcode it + F = wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD), + SF = wxFont:new(10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), + {F, SF}; + _ -> + DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + DefSize = wxFont:getPointSize(DefFont), + DefFamily = wxFont:getFamily(DefFont), + F = wxFont:new(DefSize, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD), + SF = wxFont:new(DefSize-1, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), + {F, SF} + end, + BlackPen = wxPen:new({0,0,0}, [{width, 2}]), + Pens = [wxPen:new(Col, [{width, 2}]) || Col <- tuple_to_list(colors())], + process_flag(trap_exit, true), + {Panel, #state{parent=Parent, + panel =Panel, + windows = {CPU, MEM, IO}, + usegc=UseGC, + paint=#paint{font = Font, + small = SmallFont, + pen = ?wxGREY_PEN, + pen2 = BlackPen, + pens = list_to_tuple(Pens) + } + }} + catch _:Err -> + io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]), + {stop, Err} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, + State = #state{}) -> + {noreply, State}; + +handle_event(Event, _State) -> + error({unhandled_event, Event}). + +%%%%%%%%%% +handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, + #state{active=Active, offset=Offset, paint=Paint, + windows=Windows, data=Data, usegc=UseGC}) -> + %% PaintDC must be created in a callback to work on windows. + %% Sigh workaround bug on MacOSX (Id in paint event is always 0) + %% Panel = element(Id, Windows), + Id = if Panel =:= element(?RQ_W, Windows) -> ?RQ_W; + Panel =:= element(?MEM_W, Windows) -> ?MEM_W; + Panel =:= element(?IO_W, Windows) -> ?IO_W + end, + IsWindows = element(1, os:type()) =:= win32, + + DC = if IsWindows -> + %% Ugly hack to aviod flickering on windows, works on windows only + %% But the other platforms are doublebuffered by default + wx:typeCast(wxBufferedPaintDC:new(Panel), wxPaintDC); + true -> + wxPaintDC:new(Panel) + end, + IsWindows andalso wxDC:clear(DC), + GC = if UseGC -> ?wxGC:create(DC); + true -> DC + end, + %% Nothing is drawn until wxPaintDC is destroyed. + try + draw(Offset, Id, {UseGC, GC}, Panel, Paint, Data, Active) + catch _:Err -> + io:format("Internal error ~p ~p~n",[Err, erlang:get_stacktrace()]) + end, + UseGC andalso ?wxGC:destroy(GC), + wxPaintDC:destroy(DC), + ok. +%%%%%%%%%% +handle_call(Event, From, _State) -> + error({unhandled_call, Event, From}). + +handle_cast(Event, _State) -> + error({unhandled_cast, Event}). +%%%%%%%%%% +handle_info(Stats = {stats, 1, _, _, _}, + State = #state{panel=Panel, data=Data, active=Active}) -> + if Active -> + wxWindow:refresh(Panel), + Freq = 6, + erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq}); + true -> ignore + end, + {noreply, State#state{offset=0.0, data = add_data(Stats, Data)}}; + +handle_info({refresh, Seq, Freq}, State = #state{panel=Panel, offset=Prev}) -> + wxWindow:refresh(Panel), + Next = Seq+1, + if Seq > 1, Prev =:= 0.0 -> + %% We didn't have time to handle the refresh + {noreply, State}; + Next < Freq -> + erlang:send_after(trunc(1000 / Freq), self(), {refresh, Next, Freq}), + {noreply, State#state{offset=Seq/Freq}}; + true -> + {noreply, State#state{offset=Seq/Freq}} + end; + +handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) -> + create_menus(Parent, []), + try + Node = node(Old), + wxWindow:refresh(Panel), + {noreply, State#state{active=true}} + catch _:_ -> + catch Old ! exit, + Me = self(), + Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, 1000]), + wxWindow:refresh(Panel), + {noreply, State#state{active=true, appmon=Pid, data={0, queue:new()}}} + end; + +handle_info(not_active, State = #state{appmon=_Pid}) -> + %% Pid ! exit, + {noreply, State#state{active=false}}; + +handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) -> + {noreply, State#state{active=false, appmon=undefined}}; + +handle_info(_Event, State) -> + %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), + {noreply, State}. + +%%%%%%%%%% +terminate(_Event, #state{appmon=Pid}) -> + catch Pid ! exit, + ok. +code_change(_, _, State) -> + State. + +add_data(Stats, {N, Q}) when N > 60 -> + {N, queue:drop(queue:in(Stats, Q))}; +add_data(Stats, {N, Q}) -> + {N+1, queue:in(Stats, Q)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent, _) -> + MenuEntries = + [{"File", + [ + ]} + ], + observer_wx:create_menus(Parent, MenuEntries). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +collect_data(?RQ_W, {N, Q}) -> + case queue:to_list(Q) of + [] -> {0, 0, []}; + [_] -> {0, 0, []}; + [{stats, _Ver, Init0, _IO, _Mem}|Data0] -> + Init = lists:sort(Init0), + [_|Data=[First|_]] = lists:foldl(fun({stats, _, T0, _, _}, [Prev|Acc]) -> + TN = lists:sort(T0), + Delta = calc_delta(TN, Prev), + [TN, list_to_tuple(Delta)|Acc] + end, [Init], Data0), + {N, lmax(Data), lists:reverse([First|Data])} + end; +collect_data(?MEM_W, {N, Q}) -> + MemT = mem_types(), + Data = [list_to_tuple([Value || {Type,Value} <- MemInfo, + lists:member(Type, MemT)]) + || {stats, _Ver, _RQ, _IO, MemInfo} <- queue:to_list(Q)], + {N, lmax(Data), Data}; +collect_data(?IO_W, {N, Q}) -> + case queue:to_list(Q) of + [] -> {0, 0, []}; + [_] -> {0, 0, []}; + [{stats, _Ver, _RQ, {{_,In0}, {_,Out0}}, _Mem}|Data0] -> + [_,_|Data=[First|_]] = + lists:foldl(fun({stats, _, _, {{_,In}, {_,Out}}, _}, [PIn,Pout|Acc]) -> + [In,Out,{In-PIn,Out-Pout}|Acc] + end, [In0,Out0], Data0), + {N, lmax(Data), lists:reverse([First|Data])} + end. + +mem_types() -> + [total, processes, atom, binary, code, ets]. + +lmax([]) -> 0; +lmax(List) -> + lists:max([lists:max(tuple_to_list(T)) || T <- List]). + +calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) -> + [100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)]; +calc_delta([], []) -> []. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active) -> + %% This can be optimized a lot by collecting data once + %% and draw to memory and then blit memory and only draw new entries in new memory + %% area. Hmm now rewritten to use ?wxGC I don't now if it is feasable. + {Len, Max0, Hs} = collect_data(Id, Data), + Max = calc_max(Max0), + NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end, + Size = wxWindow:getClientSize(Panel), + {X0,Y0,WS,HS} = draw_borders(Id, NoGraphs, DC, Size, Max, Paint), + Last = 60*WS+X0-1, + Start = max(61-Len, 0)*WS+X0 - Offset*WS, + case Hs of + [] -> ignore; + [_] -> ignore; + _ -> + Draw = fun(N) -> + Lines = make_lines(Hs, Start, N, {X0,Max*HS,Last}, Y0, WS, HS), + setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)), + strokeLines(DC, Lines), + N+1 + end, + [Draw(I) || I <- lists:seq(NoGraphs, 1, -1)] + end, + case Active of + false -> + NotActive = "Service not available", + setFont(DC, Small, {0,0,0}), + drawText(DC, NotActive, X0 + 100, element(2,Size) div 2); + true -> + ignore + end, + ok. + +make_lines(Ds = [Data|_], PX, N, Clip, ZeroY, WS, HS) -> + Y = element(N,Data), + make_lines(Ds, PX, N, Clip, ZeroY, WS, HS, Y, []). + +make_lines([D1 | Ds = [D2|Rest]], PX, N, Clip={Cx,Cy, _}, ZeroY, WS, HS, Y0, Acc0) -> + Y1 = element(N,D1), + Y2 = element(N,D2), + Y3 = case Rest of + [D3|_] -> element(N,D3); + [] -> Y2 + end, + This = {max(Cx, PX),ZeroY-min(Cy,Y1*HS)}, + Acc = if (abs(Y1-Y2) * HS) < 3.0 -> [This|Acc0]; + WS < 3.0 -> [This|Acc0]; + PX < Cx -> + make_splines(Y0,Y1,Y2,Y3,PX,Clip,ZeroY,WS,HS,Acc0); + true -> + make_splines(Y0,Y1,Y2,Y3,PX,Clip,ZeroY,WS,HS,[This|Acc0]) + end, + make_lines(Ds, PX+WS, N, Clip, ZeroY, WS, HS, Y1, Acc); +make_lines([D1], _PX, N, {_,Cy,Last}, ZeroY, _WS, HS, _Y0, Acc) -> + Y1 = element(N,D1), + [{Last,ZeroY-min(Cy, Y1*HS)}|Acc]. + +make_splines(Y00,Y10,Y20,Y30,PX,Clip,ZeroY,WS,HS,Acc) -> + Y1 = Y10*HS, + Y2 = Y20*HS, + Steps = min(abs(Y1-Y2), WS), + if Steps > 2 -> + Y0 = Y00*HS, + Y3 = Y30*HS, + Tan = spline_tan(Y0,Y1,Y2,Y3), + Delta = 1/Steps, + splines(Steps-1, 0.0, Delta, Tan, Y1,Y2, PX, Clip,ZeroY, Delta*WS, Acc); + true -> + Acc + end. + +splines(N, XD, XD0, Tan, Y1,Y2, PX0, Clip={Cx,Cy,_},ZeroY, WS, Acc) when N > 0 -> + PX = PX0+WS, + Delta = XD+XD0, + if PX < Cx -> + splines(N-1, Delta, XD0, Tan, Y1, Y2, PX, Clip,ZeroY, WS, Acc); + true -> + Y = min(Cy, max(0,spline(Delta, Tan, Y1,Y2))), + splines(N-1, Delta, XD0, Tan, Y1, Y2, PX, Clip,ZeroY, WS, + [{PX, ZeroY-Y}|Acc]) + end; +splines(_N, _XD, _XD0, _Tan, _Y1,_Y2, _PX, _Clip,_ZeroY, _WS, Acc) -> Acc. + +spline(T, {M1, M2}, Y1, Y2) -> + %% Hermite Basis Funcs + T2 = T*T, T3 = T*T*T, + H1 = 2*T3-3*T2+1, + H2 = -2*T3+3*T2, + H3 = T3-2*T2+T, + H4 = T3-T2, + %% Result + M1*H3 + Y1*H1 + Y2*H2 + M2*H4. + +spline_tan(Y0, Y1, Y2, Y3) -> + S = 1.0, + C = 0.5, + %% Calc tangent values + M1 = S*C*(Y2-Y0), + M2 = S*C*(Y3-Y1), + {M1,M2}. + +-define(BW, 5). +-define(BH, 5). + +draw_borders(Type, NoGraphs, DC, {W,H}, Max, + #paint{pen=Pen, pen2=Pen2, font=Font, small=Small}) -> + {Unit, MaxUnit} = bytes(Type, Max), + Str1 = observer_lib:to_str(MaxUnit), + Str2 = observer_lib:to_str(MaxUnit div 2), + Str3 = observer_lib:to_str(0), + + setFont(DC, Font, {0,0,0}), + {TW,TH} = getTextExtent(DC, Str1), + {SpaceW, _} = getTextExtent(DC, "W"), + + GraphX0 = ?BW+TW+?BW, + GraphX1 = W-?BW*4, + TopTextX = ?BW+TW+?BW, + MaxTextY = ?BH+TH+?BH, + BottomTextY = H-?BH-TH, + SecondsY = BottomTextY - ?BH - TH, + GraphY0 = MaxTextY + (TH / 2), + GraphY1 = SecondsY - ?BH, + GraphW = GraphX1-GraphX0-1, + GraphH = GraphY1-GraphY0-1, + GraphY25 = GraphY0 + (GraphY1 - GraphY0) / 4, + GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2, + GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4, + ScaleW = GraphW / 60, + ScaleH = GraphH / Max, + + setFont(DC, Small, {0,0,0}), + Align = fun(Str, Y) -> + {StrW, _} = getTextExtent(DC, Str), + drawText(DC, Str, GraphX0 - StrW - ?BW, Y) + end, + Align(Str1, MaxTextY), + Align(Str2, GraphY50 - (TH / 2)), + Align(Str3, GraphY1 - (TH / 2) + 1), + + setPen(DC, Pen), + DrawSecs = fun(Secs, Pos) -> + Str = [observer_lib:to_str(Secs)|" s"], + X = GraphX0+Pos, + drawText(DC, Str, X-SpaceW, SecondsY), + strokeLine(DC, X, GraphY0, X, GraphY1+5), + Pos + 10*ScaleW + end, + lists:foldl(DrawSecs, 0, lists:seq(60,0, -10)), + + strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25), + strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50), + strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75), + + setPen(DC, Pen2), + strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1}, + {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1}, + {GraphX0, GraphY0-1}]), + + setFont(DC, Font, {0,0,0}), + case Type of + ?RQ_W -> drawText(DC, "Scheduler Utilization (%) ", TopTextX,?BH); + ?MEM_W -> drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH); + ?IO_W -> drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH) + end, + + Text = fun(X,Y, Str, PenId) -> + if PenId == 0 -> + setFont(DC, Font, {0,0,0}); + PenId > 0 -> + Id = 1 + ((PenId-1) rem tuple_size(colors())), + setFont(DC, Font, element(Id, colors())) + end, + drawText(DC, Str, X, Y), + {StrW, _} = getTextExtent(DC, Str), + StrW + X + SpaceW + end, + case Type of + ?RQ_W -> + TN0 = Text(?BW, BottomTextY, "Scheduler: ", 0), + lists:foldl(fun(Id, Pos0) -> + Text(Pos0, BottomTextY, integer_to_list(Id), Id) + end, TN0, lists:seq(1, NoGraphs)); + ?MEM_W -> + lists:foldl(fun(MType, {PenId, Pos0}) -> + Str = uppercase(atom_to_list(MType)), + Pos = Text(Pos0, BottomTextY, Str, PenId), + {PenId+1, Pos} + end, {1, ?BW}, mem_types()); + ?IO_W -> + TN0 = Text(?BW, BottomTextY, "Input", 1), + Text(TN0, BottomTextY, "Output", 2) + end, + {GraphX0+1, GraphY1, ScaleW, ScaleH}. + +uppercase([C|Rest]) -> + [C-$a+$A|Rest]. + +calc_max(Max) when Max < 10 -> 10; +calc_max(Max) -> calc_max1(Max). + +calc_max1(Max) -> + case Max div 10 of + X when X < 10 -> + case Max rem 10 of + 0 -> Max; + _ -> + (X+1)*10 + end; + X -> + 10*calc_max1(X) + end. + +bytes(?RQ_W, Val) -> {"", Val}; +bytes(_, B) -> + KB = B div 1024, + MB = KB div 1024, + GB = MB div 1024, + if + GB > 10 -> {"(GB)", GB}; + MB > 10 -> {"(MB)", MB}; + KB > 0 -> {"(KB)", KB}; + true -> {"(B)", B} + end. + +colors() -> + {{200, 50, 50}, {50, 200, 50}, {50, 50, 200}, + {255, 110, 0}, {50, 200, 200}, {200, 50, 200}, + {240, 200, 80}, {140, 2, 140}, + {100, 200, 240}, {100, 240, 100} + }. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% wxDC and ?wxGC wrappers + +haveGC(Win) -> + try + GC = ?wxGC:create(Win), + ?wxGC:destroy(GC), + true + catch _:_ -> false + end. + +setPen({false, DC}, Pen) -> + wxDC:setPen(DC, Pen); +setPen({true, GC}, Pen) -> + ?wxGC:setPen(GC, Pen). + +setFont({false, DC}, Font, Color) -> + wxDC:setTextForeground(DC, Color), + wxDC:setFont(DC, Font); +setFont({true, GC}, Font, Color) -> + ?wxGC:setFont(GC, Font, Color). + +setBrush({false, DC}, Brush) -> + wxDC:setBrush(DC, Brush); +setBrush({true, GC}, Brush) -> + ?wxGC:setBrush(GC, Brush). + +strokeLine({false, DC}, X0, Y0, X1, Y1) -> + wxDC:drawLine(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}); +strokeLine({true, GC}, X0, Y0, X1, Y1) -> + ?wxGC:strokeLine(GC, X0, Y0, X1, Y1). + +strokeLines({false, DC}, Lines) -> + wxDC:drawLines(DC, [{round(X), round(Y)} || {X,Y} <- Lines]); +strokeLines({true, GC}, Lines) -> + ?wxGC:strokeLines(GC, Lines). + +drawRoundedRectangle({false, DC}, X0, Y0, X1, Y1, R) -> + wxDC:drawRoundedRectangle(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}, round(R)); +drawRoundedRectangle({true, GC}, X0, Y0, X1, Y1, R) -> + ?wxGC:drawRoundedRectangle(GC, X0, Y0, X1, Y1, R). + +drawText({false, DC}, Str, X, Y) -> + wxDC:drawText(DC, Str, {round(X),round(Y)}); +drawText({true, GC}, Str, X, Y) -> + ?wxGC:drawText(GC, Str, X, Y). + +getTextExtent({false, DC}, Str) -> + wxDC:getTextExtent(DC, Str); +getTextExtent({true, GC}, Str) -> + {W,H,_,_} = ?wxGC:getTextExtent(GC, Str), + {W,H}. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 7578215ff9..ee67664539 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -191,13 +191,20 @@ dump_to_file(Parent, FileName, Holder) -> start_procinfo(undefined, _Frame, Opened) -> Opened; start_procinfo(Pid, Frame, Opened) -> - case lists:member(Pid, Opened) of - true -> - Opened; - false -> - observer_procinfo:start(Pid, Frame, self()), - [Pid | Opened] + %% This code doesn't work until we collect which windows have been + %% closed maybe it should moved to observer_wx.erl + %% and add a global menu which remembers windows. + %% case lists:keyfind(Pid, 1, Opened) of + %% false -> + case observer_procinfo:start(Pid, Frame, self()) of + {error, _} -> Opened; + PI -> [{Pid, PI} | Opened] end. + %%; + %% {_, PI} -> + %% wxFrame:raise(PI), + %% Opened + %% end. call(Holder, What) -> Ref = erlang:monitor(process, Holder), @@ -251,8 +258,7 @@ terminate(_Reason, #state{holder=Holder}) -> ok. code_change(_, _, State) -> - {stop, not_yet_implemented, State}. - + {ok, State}. handle_call(Msg, _From, State) -> io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index a4c5914c49..45218c177b 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -49,7 +49,8 @@ init([Pid, ParentFrame, Parent]) -> try Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of [] -> io_lib:format("~p",[Pid]); - {registered_name, Registered} -> atom_to_list(Registered) + {registered_name, Registered} -> io_lib:format("~p (~p)",[Registered, Pid]); + undefined -> throw(process_undefined) end, Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title], [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]), @@ -75,7 +76,10 @@ init([Pid, ParentFrame, Parent]) -> }} catch error:{badrpc, _} -> observer_wx:return_to_localnode(ParentFrame, node(Pid)), - {stop, badrpc, #state{parent=Parent, pid=Pid}} + {stop, badrpc}; + process_undefined -> + observer_lib:display_info_dialog("No such alive process"), + {stop, normal} end. init_panel(Notebook, Str, Pid, Fun) -> @@ -94,8 +98,11 @@ handle_event(#wx{event=#wxClose{type=close_window}}, State) -> handle_event(#wx{id=?wxID_CLOSE, event=#wxCommand{type=command_menu_selected}}, State) -> {stop, normal, State}; -handle_event(#wx{id=?REFRESH}, #state{pages=Pages}=State) -> - [(W#worker.callback)() || W <- Pages], +handle_event(#wx{id=?REFRESH}, #state{frame=Frame, pid=Pid, pages=Pages}=State) -> + try [(W#worker.callback)() || W <- Pages] + catch process_undefined -> + wxFrame:setTitle(Frame, io_lib:format("*DEAD* ~p",[Pid])) + end, {noreply, State}; handle_event(Event, _State) -> @@ -120,13 +127,16 @@ terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) -> ok. code_change(_, _, State) -> - {stop, not_yet_implemented, State}. + {ok, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_process_page(Panel, Pid) -> - Fields = process_info_fields(Pid), - {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields), - {FPanel, fun() -> observer_lib:update_info(UpFields, process_info_fields(Pid)) end}. + Fields0 = process_info_fields(Pid), + {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields0), + {FPanel, fun() -> + Fields = process_info_fields(Pid), + observer_lib:update_info(UpFields, Fields) + end}. init_text_page(Parent) -> Style = ?wxTE_MULTILINE bor ?wxTE_RICH2 bor ?wxTE_READONLY, @@ -144,16 +154,21 @@ init_message_page(Parent, Pid) -> Number+1} end, Update = fun() -> - {messages,RawMessages} = - observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]), - {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - case Messages =:= [] of - true -> - wxTextCtrl:writeText(Text, "No messages"); - false -> - wxTextCtrl:writeText(Text, Messages) + case observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, messages]) + of + {messages,RawMessages} -> + {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + case Messages =:= [] of + true -> + wxTextCtrl:writeText(Text, "No messages"); + false -> + wxTextCtrl:writeText(Text, Messages) + end; + _ -> + throw(process_undefined) end end, Update(), @@ -162,12 +177,16 @@ init_message_page(Parent, Pid) -> init_dict_page(Parent, Pid) -> Text = init_text_page(Parent), Update = fun() -> - {dictionary,RawDict} = - observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]), - Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - wxTextCtrl:writeText(Text, Dict) + case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) + of + {dictionary,RawDict} -> + Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + wxTextCtrl:writeText(Text, Dict); + _ -> + throw(process_undefined) + end end, Update(), {Text, Update}. @@ -183,24 +202,30 @@ init_stack_page(Parent, Pid) -> 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]), - 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) + case observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, current_stacktrace]) + of + {current_stacktrace,RawBt} -> + observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, current_stacktrace]), + 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); + _ -> + throw(process_undefined) + end end, Resize = fun(#wx{event=#wxSize{size={W,_}}},Ev) -> wxEvent:skip(Ev), @@ -216,7 +241,6 @@ create_menus(MenuBar) -> observer_lib:create_menus(Menus, MenuBar, new_window). process_info_fields(Pid) -> - RawInfo = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]), Struct = [{"Overview", [{"Initial Call", initial_call}, {"Current Function", current_function}, @@ -246,7 +270,12 @@ process_info_fields(Pid) -> {"GC Min Heap Size", {bytes, get_gc_info(min_heap_size)}}, {"GC FullSweep After", get_gc_info(fullsweep_after)} ]}], - observer_lib:fill_info(Struct, RawInfo). + case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]) of + RawInfo when is_list(RawInfo) -> + observer_lib:fill_info(Struct, RawInfo); + _ -> + throw(process_undefined) + end. item_list() -> [ %% backtrace, diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 09602bbd9e..f00a666a35 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -147,7 +147,7 @@ terminate(_Reason, _State) -> ok. code_change(_, _, State) -> - {stop, not_yet_implemented, State}. + {ok, State}. handle_call(Msg, _From, State) -> io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index d0b6a1e063..f2a1084f85 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -489,7 +489,7 @@ terminate(_Reason, #state{nodes=_Nodes}) -> ok. code_change(_, _, State) -> - {stop, not_yet_implemented, State}. + {ok, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index 6a634e06f0..e27f565abc 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -167,8 +167,10 @@ module_selector(Parent, Node) -> 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))]), + Externals = observer_wx:try_rpc(Node, Module, module_info, [exports]), + + Choices = lists:usort([{Name, Arity} || {Name, Arity} <- Externals ++ Functions, + not(erl_internal:guard_bif(Name, Arity))]), ParsedChoices = parse_function_names(Choices), case check_selector(Parent, ParsedChoices) of [] -> [{Module, '_', '_'}]; diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 31d5f3d632..8fdcbf331c 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -24,6 +24,8 @@ -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([format/1]). + -include("observer_defs.hrl"). -import(observer_lib, [to_str/1]). @@ -58,7 +60,7 @@ source, tab, attrs, - timer + timer={false, 30} }). -record(opt, @@ -265,11 +267,12 @@ handle_event(#wx{id=?ID_DELETE}, wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~s",[Str])), {noreply, State}; -handle_event(#wx{id=?wxID_CLOSE}, State) -> +handle_event(#wx{id=?wxID_CLOSE}, State = #state{frame=Frame}) -> + wxFrame:destroy(Frame), {stop, normal, State}; -handle_event(Help = #wx{id=?wxID_HELP}, State = #state{parent=Parent}) -> - Parent ! Help, +handle_event(Help = #wx{id=?wxID_HELP}, State) -> + observer ! Help, {noreply, State}; handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, @@ -374,40 +377,51 @@ 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]), +handle_event(_Event, State) -> + %io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. -handle_sync_event(Event, _Obj, _State) -> - io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), +handle_sync_event(_Event, _Obj, _State) -> + %io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), ok. -handle_call(Event, From, State) -> - io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), +handle_call(_Event, _From, State) -> + %io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), {noreply, State}. -handle_cast(Event, State) -> - io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), +handle_cast(_Event, State) -> + %io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. handle_info({no_rows, N}, State = #state{grid=Grid, status=StatusBar}) -> wxListCtrl:setItemCount(Grid, N), wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w",[N])), {noreply, State}; + handle_info({new_cols, New}, State = #state{grid=Grid, columns=Cols0}) -> Cols = add_columns(Grid, Cols0, New), {noreply, State#state{columns=Cols}}; + handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> wxListCtrl:refreshItems(Grid, Min, Max), {noreply, State}; + +handle_info(refresh_interval, State = #state{pid=Pid}) -> + Pid ! refresh, + {noreply, State}; + handle_info({error, Error}, State = #state{frame=Frame}) -> - Dlg = wxMessageDialog:new(Frame, Error), + ErrorStr = + try io_lib:format("~ts", [Error]), Error + catch _:_ -> io_lib:format("~p", [Error]) + end, + Dlg = wxMessageDialog:new(Frame, ErrorStr), wxMessageDialog:showModal(Dlg), wxMessageDialog:destroy(Dlg), {noreply, State}; -handle_info(Event, State) -> - io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), +handle_info(_Event, State) -> + %% io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, _Event]), {noreply, State}. terminate(_Event, #state{pid=Pid, attrs=Attrs}) -> @@ -442,7 +456,7 @@ get_attr(Table, Item) -> Ref = erlang:monitor(process, Table), Table ! {get_attr, self(), Item}, receive - {'DOWN', Ref, _, _, _} -> ""; + {'DOWN', Ref, _, _, _} -> wx:null(); {Table, Res} -> erlang:demonitor(Ref), Res @@ -588,19 +602,22 @@ search([Str, Row, Dir0, CaseSens], true -> []; false -> [caseless] end, - {ok, Re} = re:compile(Str, Opt), Dir = case Dir0 of true -> 1; false -> -1 end, - Res = search(Row, Dir, Re, Table), + Res = case re:compile(Str, Opt) of + {ok, Re} -> + search(Row, Dir, Re, Table); + {error, _} -> false + end, Parent ! {self(), Res}, S#holder{search=Res}. search(Row, Dir, Re, Table) -> Res = try lists:nth(Row+1, Table) of Term -> - Str = io_lib:format("~w", [Term]), + Str = format(Term), re:run(Str, Re) catch _:_ -> no_more end, @@ -613,9 +630,9 @@ search(Row, Dir, Re, Table) -> get_row(From, Row, Col, Table) -> case lists:nth(Row+1, Table) of [Object|_] when Col =:= all -> - From ! {self(), io_lib:format("~w", [Object])}; + From ! {self(), format(Object)}; [Object|_] when tuple_size(Object) >= Col -> - From ! {self(), io_lib:format("~w", [element(Col, Object)])}; + From ! {self(), format(element(Col, Object))}; _ -> From ! {self(), ""} end. @@ -724,3 +741,65 @@ key_pos(Node, ets, TabId) -> KeyPos = rpc:call(Node, ets, info, [TabId, keypos]), is_integer(KeyPos) orelse throw(node_or_table_down), KeyPos. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +format(Tuple) when is_tuple(Tuple) -> + [${ |format_tuple(Tuple, 1, tuple_size(Tuple))]; +format(List) when is_list(List) -> + format_list(List); +format(Bin) when is_binary(Bin), byte_size(Bin) > 100 -> + io_lib:format("<<#Bin:~w>>", [byte_size(Bin)]); +format(Bin) when is_binary(Bin) -> + try + true = printable_list(unicode:characters_to_list(Bin)), + io_lib:format("<<\"~ts\">>", [Bin]) + catch _:_ -> + io_lib:format("~w", [Bin]) + end; +format(Float) when is_float(Float) -> + io_lib:format("~.3g", [Float]); +format(Term) -> + io_lib:format("~w", [Term]). + +format_tuple(Tuple, I, Max) when I < Max -> + [format(element(I, Tuple)), $,|format_tuple(Tuple, I+1, Max)]; +format_tuple(Tuple, Max, Max) -> + [format(element(Max, Tuple)), $}]; +format_tuple(_Tuple, 1, 0) -> + [$}]. + +format_list([]) -> "[]"; +format_list(List) -> + case printable_list(List) of + true -> io_lib:format("\"~ts\"", [List]); + false -> [$[ | make_list(List)] + end. + +make_list([Last]) -> + [format(Last), $]]; +make_list([Head|Tail]) -> + [format(Head), $,|make_list(Tail)]. + +%% printable_list([Char]) -> bool() +%% Return true if CharList is a list of printable characters, else +%% false. + +printable_list([C|Cs]) when is_integer(C), C >= $ , C =< 255 -> + printable_list(Cs); +printable_list([$\n|Cs]) -> + printable_list(Cs); +printable_list([$\r|Cs]) -> + printable_list(Cs); +printable_list([$\t|Cs]) -> + printable_list(Cs); +printable_list([$\v|Cs]) -> + printable_list(Cs); +printable_list([$\b|Cs]) -> + printable_list(Cs); +printable_list([$\f|Cs]) -> + printable_list(Cs); +printable_list([$\e|Cs]) -> + printable_list(Cs); +printable_list([]) -> true; +printable_list(_Other) -> false. %Everything else is false diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index ad3e8c14ab..b276965f83 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -313,7 +313,7 @@ display_table_info(Parent0, Node, Source, Table) -> list_to_strings([]) -> "None"; list_to_strings([A]) -> integer_to_list(A); -list_to_strings([A,B]) -> +list_to_strings([A|B]) -> integer_to_list(A) ++ " ," ++ list_to_strings(B). handle_error(Foo) -> diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 5a593abf11..e433bea8c2 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. 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 @@ -20,7 +20,7 @@ -behaviour(wx_object). -export([start/0]). --export([create_menus/2, get_attrib/1, get_tracer/0, +-export([create_menus/2, get_attrib/1, get_tracer/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, @@ -55,9 +55,11 @@ sys_panel, trace_panel, app_panel, + perf_panel, active_tab, node, - nodes + nodes, + prev_node="" }). start() -> @@ -72,6 +74,9 @@ create_menus(Object, Menus) when is_list(Menus) -> get_attrib(What) -> wx_object:call(observer, {get_attrib, What}). +set_status(What) -> + wx_object:cast(observer, {status_bar, What}). + get_tracer() -> wx_object:call(observer, get_tracer). @@ -129,6 +134,10 @@ setup(#state{frame = Frame} = State) -> %% I postpone the creation of the other tabs so they can query/use %% the window size + %% Perf Viewer Panel + PerfPanel = observer_perf_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, PerfPanel, "Load Charts", []), + %% App Viewer Panel AppPanel = observer_app_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, AppPanel, "Applications", []), @@ -160,6 +169,7 @@ setup(#state{frame = Frame} = State) -> tv_panel = TVPanel, trace_panel = TracePanel, app_panel = AppPanel, + perf_panel = PerfPanel, active_tab = SysPid, node = node(), nodes = Nodes @@ -185,10 +195,13 @@ setup(#state{frame = Frame} = State) -> %%Callbacks handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}}, #state{active_tab=Previous, node=Node} = State) -> - Pid = get_active_pid(State), - Previous ! not_active, - Pid ! {active, Node}, - {noreply, State#state{active_tab=Pid}}; + case get_active_pid(State) of + Previous -> {noreply, State}; + Pid -> + Previous ! not_active, + Pid ! {active, Node}, + {noreply, State#state{active_tab=Pid}} + end; handle_event(#wx{event = #wxClose{}}, State) -> {stop, normal, State}; @@ -252,20 +265,21 @@ handle_event(#wx{id = ?ID_CONNECT, event = #wxCommand{type = command_menu_select handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected}}, #state{frame = Frame} = State) -> UpdState = case create_connect_dialog(ping, State) of - cancel -> State; + cancel -> State; {value, Value} when is_list(Value) -> try Node = list_to_atom(Value), case net_adm:ping(Node) of pang -> create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), - State; + State#state{prev_node=Value}; pong -> - change_node_view(Node, State) + State1 = change_node_view(Node, State), + State1#state{prev_node=Value} end catch _:_ -> create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), - State + State#state{prev_node=Value} end end, {noreply, UpdState}; @@ -282,6 +296,10 @@ handle_event(Event, State) -> Pid ! Event, {noreply, State}. +handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) -> + wxStatusBar:setStatusText(SB, Msg), + {noreply, State}; + handle_cast(_Cast, State) -> {noreply, State}. @@ -322,8 +340,9 @@ 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]), +handle_info({'EXIT', Pid, _Reason}, State) -> + io:format("Child (~s) crashed exiting: ~p ~p~n", + [pid2panel(Pid, State), Pid,_Reason]), {stop, normal, State}; handle_info(_Info, State) -> @@ -334,7 +353,7 @@ terminate(_Reason, #state{frame = Frame}) -> ok. code_change(_, _, State) -> - {stop, not_yet_implemented, State}. + {ok, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -345,6 +364,7 @@ try_rpc(Node, Mod, Func, Args) -> error_logger:error_report([{node, Node}, {call, {Mod, Func, Args}}, {reason, {badrpc, Reason}}]), + observer ! {nodedown, Node}, error({badrpc, Reason}); Res -> Res @@ -393,7 +413,9 @@ connect2(NodeName, Opts, Cookie) -> end. change_node_view(Node, State) -> - get_active_pid(State) ! {active, Node}, + Tab = get_active_pid(State), + Tab ! not_active, + Tab ! {active, Node}, StatusText = ["Observer - " | atom_to_list(Node)], wxFrame:setTitle(State#state.frame, StatusText), wxStatusBar:setStatusText(State#state.status_bar, StatusText), @@ -404,18 +426,35 @@ check_page_title(Notebook) -> wxNotebook:getPageText(Notebook, Selection). get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, - tv_panel=Tv, trace_panel=Trace, app_panel=App}) -> + tv_panel=Tv, trace_panel=Trace, app_panel=App, + perf_panel=Perf + }) -> Panel = case check_page_title(Notebook) of "Processes" -> Pro; "System" -> Sys; "Table Viewer" -> Tv; ?TRACE_STR -> Trace; + "Load Charts" -> Perf; "Applications" -> App end, wx_object:get_pid(Panel). -create_connect_dialog(ping, #state{frame = Frame}) -> - Dialog = wxTextEntryDialog:new(Frame, "Connect to node"), +pid2panel(Pid, #state{pro_panel=Pro, sys_panel=Sys, + tv_panel=Tv, trace_panel=Trace, app_panel=App, + perf_panel=Perf}) -> + case Pid of + Pro -> "Processes"; + Sys -> "System"; + Tv -> "Table Viewer" ; + Trace -> ?TRACE_STR; + Perf -> "Load Charts"; + App -> "Applications"; + _ -> "unknown" + end. + + +create_connect_dialog(ping, #state{frame = Frame, prev_node=Prev}) -> + Dialog = wxTextEntryDialog:new(Frame, "Connect to node", [{value, Prev}]), case wxDialog:showModal(Dialog) of ?wxID_OK -> Value = wxTextEntryDialog:getValue(Dialog), @@ -535,7 +574,16 @@ remove_menu_items([], _MB) -> ok. get_nodes() -> - Nodes = [node()| nodes()], + Nodes0 = case erlang:is_alive() of + false -> []; + true -> + case net_adm:names() of + {error, _} -> nodes(); + {ok, Names} -> + epmd_nodes(Names) ++ nodes() + end + end, + Nodes = lists:usort(Nodes0), {_, Menues} = lists:foldl(fun(Node, {Id, Acc}) when Id < ?LAST_NODES_MENU_ID -> {Id + 1, [#create_menu{id=Id + ?FIRST_NODES_MENU_ID, @@ -543,6 +591,10 @@ get_nodes() -> end, {1, []}, Nodes), {Nodes, lists:reverse(Menues)}. +epmd_nodes(Names) -> + [_, Host] = string:tokens(atom_to_list(node()),"@"), + [list_to_atom(Name ++ [$@|Host]) || {Name, _} <- Names]. + update_node_list(State = #state{menubar=MenuBar}) -> {Nodes, NodesMenuItems} = get_nodes(), NodeMenuId = wxMenuBar:findMenu(MenuBar, "Nodes"), diff --git a/lib/observer/test/Makefile b/lib/observer/test/Makefile index bf99f07081..9df0591da5 100644 --- a/lib/observer/test/Makefile +++ b/lib/observer/test/Makefile @@ -82,11 +82,11 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt release_tests_spec: make_emakefile - $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) observer.spec $(EMAKEFILE) \ $(COVERFILE) $(ERL_FILES) \ - $(RELSYSDIR) - @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) + "$(RELSYSDIR)" + @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) release_docs_spec: diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl index 79ece7edf5..6f882d0be9 100644 --- a/lib/observer/test/crashdump_viewer_SUITE.erl +++ b/lib/observer/test/crashdump_viewer_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2011. All Rights Reserved. +%% Copyright Ericsson AB 2003-2012. 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 @@ -318,7 +318,7 @@ browse_file(Port,File) -> %% The page where a filename can be entered title(Port,"read_file_frame","Read File"), - + %% Load a file Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file", Html = request_sync(post,{Url,[],[],"path="++File}), @@ -414,6 +414,10 @@ special(Port,File) -> _ -> ok end; + ".strangemodname" -> + AllMods = contents(Port,"loaded_modules"), + open_all_modules(Port,AllMods), + ok; %%! No longer needed - all atoms are shown on one page!! %% ".250atoms" -> %% Html1 = contents(Port,"atoms"), @@ -496,6 +500,26 @@ expand_binary_link(Html) -> expand_binary_link(T) end. +open_all_modules(Port,Modules) -> + case get_first_module(Modules) of + {Module,Rest} -> + ModuleDetails = contents(Port,"loaded_mod_details?mod=" ++ Module), + ModTitle = http_uri:decode(Module), + ModTitle = title(ModuleDetails), + open_all_modules(Port,Rest); + false -> + ok + end. + +get_first_module([]) -> + false; +get_first_module(Html) -> + case Html of + "<TD><A HREF=\"loaded_mod_details?mod=" ++ Rest -> + {string:sub_word(Rest,1,$"),Rest}; + [_H|T] -> + get_first_module(T) + end. %% next_link(Html) -> %% case Html of @@ -565,7 +589,7 @@ create_dumps(DataDir,[Rel|Rels],Acc) -> Fun = fun() -> do_create_dumps(DataDir,Rel) end, Pa = filename:dirname(code:which(?MODULE)), {SlAllocDumps,Dumps,DosDump} = - ?t:run_on_shielded_node(Fun, compat_rel(Rel) ++ "-pa " ++ Pa), + ?t:run_on_shielded_node(Fun, compat_rel(Rel) ++ "-pa \"" ++ Pa ++ "\""), create_dumps(DataDir,Rels,SlAllocDumps ++ Dumps ++ Acc ++ DosDump); create_dumps(_DataDir,[],Acc) -> Acc. @@ -590,7 +614,8 @@ do_create_dumps(DataDir,Rel) -> case Rel of current -> CD3 = dump_with_args(DataDir,Rel,"instr","+Mim true"), - {SlAllocDumps, [CD1,CD2,CD3], DosDump}; + CD4 = dump_with_strange_module_name(DataDir,Rel,"strangemodname"), + {SlAllocDumps, [CD1,CD2,CD3,CD4], DosDump}; _ -> {SlAllocDumps, [CD1,CD2], DosDump} end. @@ -600,7 +625,7 @@ do_create_dumps(DataDir,Rel) -> %% not connected node, and with monitors and links between nodes. full_dist_dump(DataDir,Rel) -> Opt = rel_opt(Rel), - Pz = "-pz " ++ filename:dirname(code:which(?MODULE)), + Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", PzOpt = [{args,Pz}], {ok,N1} = ?t:start_node(n1,peer,Opt ++ PzOpt), {ok,N2} = ?t:start_node(n2,peer,Opt ++ PzOpt), @@ -648,7 +673,22 @@ dump_with_args(DataDir,Rel,DumpName,Args) -> ?t:stop_node(n1), CD. +%% This dump is added to test OTP-10090 - regarding URL encoding of +%% module names in the module detail link. +dump_with_strange_module_name(DataDir,Rel,DumpName) -> + Opt = rel_opt(Rel), + {ok,N1} = ?t:start_node(n1,peer,Opt), + Mod = '<mod ule#with?strange%name>', + File = atom_to_list(Mod) ++ ".erl", + Forms = [{attribute,1,file,{File,1}}, + {attribute,1,module,Mod}, + {eof,4}], + {ok,Mod,Bin} = rpc:call(N1,compile,forms,[Forms,[binary]]), + {module,Mod} = rpc:call(N1,code,load_binary,[Mod,File,Bin]), + CD = dump(N1,DataDir,Rel,DumpName), + ?t:stop_node(n1), + CD. dump(Node,DataDir,Rel,DumpName) -> rpc:call(Node,erlang,halt,[DumpName]), @@ -711,6 +751,7 @@ rel_opt(Rel) -> r11b -> [{erl,[{release,"r11b_patched"}]}]; r12b -> [{erl,[{release,"r12b_patched"}]}]; r13b -> [{erl,[{release,"r13b_patched"}]}]; + r14b -> [{erl,[{release,"r14b_latest"}]}]; %naming convention changed current -> [] end. @@ -734,6 +775,6 @@ compat_rel(Rel) -> r11b -> "+R11 "; r12b -> "+R12 "; r13b -> "+R13 "; - r14b -> "+R13 "; + r14b -> "+R14 "; current -> "" end. diff --git a/lib/observer/test/etop_SUITE.erl b/lib/observer/test/etop_SUITE.erl index a0782ea809..06577f82cc 100644 --- a/lib/observer/test/etop_SUITE.erl +++ b/lib/observer/test/etop_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2011. All Rights Reserved. +%% Copyright Ericsson AB 2002-2012. 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 @@ -57,11 +57,14 @@ end_per_group(_GroupName, Config) -> Config. -text(suite) -> - []; -text(doc) -> - ["Start etop with text presentation"]; -text(Config) when is_list(Config) -> +%% Start etop with text presentation +text(_) -> + case test_server:is_native(lists) of + true -> {skip,"Native libs -- tracing does not work"}; + false -> text() + end. + +text() -> ?line {ok,Node} = ?t:start_node(node2,peer,[]), %% Must spawn this process, else the test case will never end. diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index fa104ede01..4eb10ae4e8 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 1.0 +OBSERVER_VSN = 1.1 |