aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
authorDan Gudmundsson <[email protected]>2012-02-28 12:24:26 +0100
committerDan Gudmundsson <[email protected]>2012-02-28 12:24:26 +0100
commitf06de6450bbbd324a0784780d892fa69908a2126 (patch)
tree991fa019e18655bb251bc27d7d21bf76ec0fab8c /lib/observer
parent1ac9351c019231b81caa9d27df0339da284247e8 (diff)
parentf413a27b73d1e0ceaf0d31fc9615208f11645108 (diff)
downloadotp-f06de6450bbbd324a0784780d892fa69908a2126.tar.gz
otp-f06de6450bbbd324a0784780d892fa69908a2126.tar.bz2
otp-f06de6450bbbd324a0784780d892fa69908a2126.zip
Merge branch 'dgud/observer/perf-mon/OTP-9891' into maint
Diffstat (limited to 'lib/observer')
-rw-r--r--lib/observer/priv/erlang_observer.pngbin4351 -> 2679 bytes
-rw-r--r--lib/observer/src/Makefile1
-rw-r--r--lib/observer/src/observer_app_wx.erl147
-rw-r--r--lib/observer/src/observer_lib.erl2
-rw-r--r--lib/observer/src/observer_perf_wx.erl575
-rw-r--r--lib/observer/src/observer_wx.erl33
6 files changed, 703 insertions, 55 deletions
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..7eb2144dee 100644
--- a/lib/observer/src/Makefile
+++ b/lib/observer/src/Makefile
@@ -45,6 +45,7 @@ MODULES= \
observer_app_wx \
observer_lib \
observer_wx \
+ observer_perf_wx \
observer_pro_wx \
observer_procinfo \
observer_sys_wx \
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
index 62046577ad..7eac2b8fab 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
}
}}.
@@ -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.
%%%%%%%%%%
@@ -249,26 +286,44 @@ handle_info(not_active, State = #state{appmon=AppMon, current=Prev}) ->
{noreply, State};
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}.
@@ -354,11 +409,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 +426,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 +482,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 +508,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 +541,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 +562,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 5260861497..3b924d46cf 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -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 "
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
new file mode 100644
index 0000000000..0de9785fb9
--- /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()]),
+ {error, 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, system, 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_wx.erl b/lib/observer/src/observer_wx.erl
index 5a593abf11..e2b256d768 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
@@ -55,6 +55,7 @@
sys_panel,
trace_panel,
app_panel,
+ perf_panel,
active_tab,
node,
nodes
@@ -129,6 +130,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 +165,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
@@ -322,8 +328,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) ->
@@ -345,6 +352,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
@@ -404,16 +412,33 @@ 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).
+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}) ->
Dialog = wxTextEntryDialog:new(Frame, "Connect to node"),
case wxDialog:showModal(Dialog) of