aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer')
-rw-r--r--lib/observer/doc/src/Makefile18
-rw-r--r--lib/observer/doc/src/etop.xml33
-rw-r--r--lib/observer/priv/erlang_observer.pngbin4351 -> 2679 bytes
-rw-r--r--lib/observer/src/Makefile31
-rw-r--r--lib/observer/src/crashdump_viewer_html.erl4
-rw-r--r--lib/observer/src/etop.erl8
-rw-r--r--lib/observer/src/etop_gui.erl9
-rw-r--r--lib/observer/src/observer_app_wx.erl183
-rw-r--r--lib/observer/src/observer_lib.erl38
-rw-r--r--lib/observer/src/observer_perf_wx.erl575
-rw-r--r--lib/observer/src/observer_pro_wx.erl24
-rw-r--r--lib/observer/src/observer_procinfo.erl119
-rw-r--r--lib/observer/src/observer_sys_wx.erl4
-rw-r--r--lib/observer/src/observer_trace_wx.erl4
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl8
-rw-r--r--lib/observer/src/observer_tv_table.erl123
-rw-r--r--lib/observer/src/observer_tv_wx.erl4
-rw-r--r--lib/observer/src/observer_wx.erl90
-rw-r--r--lib/observer/test/Makefile6
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl53
-rw-r--r--lib/observer/test/etop_SUITE.erl15
-rw-r--r--lib/observer/vsn.mk2
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
index 78e70461b1..01723d210b 100644
--- a/lib/observer/priv/erlang_observer.png
+++ b/lib/observer/priv/erlang_observer.png
Binary files differ
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