From 338aa477321c0cd0cfe159aead7b6d616a81335b Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Thu, 5 Jan 2012 10:55:15 +0100 Subject: [observer] Started with system monitor --- lib/observer/src/Makefile | 1 + lib/observer/src/observer_perf_wx.erl | 370 ++++++++++++++++++++++++++++++++++ lib/observer/src/observer_wx.erl | 11 +- 3 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 lib/observer/src/observer_perf_wx.erl (limited to 'lib/observer') 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_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl new file mode 100644 index 0000000000..04e09f18d8 --- /dev/null +++ b/lib/observer/src/observer_perf_wx.erl @@ -0,0 +1,370 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(observer_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]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-compile(export_all). + +-record(state, + { + parent, + windows, + data = {0, queue:new()}, + panel, + paint, + appmon + }). + +-record(paint, {font, pen, 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), + %% wxWindow:setBackgroundColour(Panel, {222,222,222}), + Main = wxBoxSizer:new(?wxVERTICAL), + + CPU = wxPanel:new(Panel, [{winid, ?RQ_W}, {style,?wxFULL_REPAINT_ON_RESIZE}]), + 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,?wxFULL_REPAINT_ON_RESIZE}]), + wxWindow:setBackgroundColour(MEM, ?wxWHITE), + IO = wxPanel:new(Panel, [{winid, ?IO_W}, {style,?wxFULL_REPAINT_ON_RESIZE}]), + 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]), + % wxPanel:connect(DrawingArea, size, [{skip, true}]), + + DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + Cols = [{220, 50, 50}, {220, 50, 220}, {50, 50, 220}, + {50, 220, 220}, {50, 220, 50}, {220, 220, 50}], + Pens = [wxPen:new(Col) || Col <- Cols], + %% GC = wxGraphicsContext:create(DrawingArea), + %% _Font = wxGraphicsContext:createFont(GC, DefFont), + {Panel, #state{parent=Parent, + panel =Panel, + windows = {CPU, MEM, IO}, + paint=#paint{font= DefFont, + pen = ?wxBLACK_PEN, + 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{id=Id, event=_Sz=#wxSize{size=Size}}, +%% State=#state{}) -> +%% %% Id =:= ?DRAWAREA andalso setup_scrollbar(Size,AppWin,App), +%% {noreply, State}; + +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{id=Id, event = #wxPaint{}},_, + #state{paint=Paint, windows=Windows, data=Data}) -> + %% PaintDC must be created in a callback to work on windows. + Panel = element(Id, Windows), + DC = wxPaintDC:new(Panel), + %% Nothing is drawn until wxPaintDC is destroyed. + try draw(Id, DC, Panel, Paint, Data) + catch _:Err -> + io:format("Crash ~p ~p~n",[Err, erlang:get_stacktrace()]) + end, + 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}) -> + wxWindow:refresh(Panel), + {noreply, State#state{data = add_data(Stats, Data)}}; + +handle_info({active, Node}, State = #state{parent=Parent, appmon=Old}) -> + create_menus(Parent, []), + try + Node = node(Old), + {noreply, State} + catch _:_ -> + catch Old ! exit, + Me = self(), + Pid = spawn_link(Node, fun() -> fetch_stats(Me) end), + {noreply, State#state{appmon=Pid, data={0, queue:new()}}} + end; + +handle_info(not_active, State = #state{appmon=_Pid}) -> + %% Pid ! exit, + {noreply, State}; + +handle_info(_Event, State) -> + io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), + {noreply, State}. + +%%%%%%%%%% +terminate(_Event, #state{appmon=Pid}) -> + 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)}. + +fetch_stats(Parent) -> + receive + exit -> normal + after 1000 -> + M = Parent ! {stats, 1, + erlang:statistics(run_queues), + erlang:statistics(io), + erlang:memory()}, + %% io:format("IO ~p~n",[element(4,M)]), + fetch_stats(Parent) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent, _) -> + MenuEntries = + [{"File", + [ + ]} + ], + observer_wx:create_menus(Parent, MenuEntries). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +collect_data(?RQ_W, {N, Q}) -> + Data = [RQ || {stats, _Ver, RQ, _IO, _Mem} <- queue:to_list(Q)], + {N, lmax(Data), Data}; +collect_data(?MEM_W, {N, Q}) -> + Data = [{Mem} || {stats, _Ver, _RQ, _IO, Mem} <- queue:to_list(Q)], + {N, {bytes, 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, {bytes, lmax(Data)}, lists:reverse([First|Data])} + end. + +lmax([]) -> 0; +lmax(List) -> + lists:max([lists:max(tuple_to_list(T)) || T <- List]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +draw(Id, DC, Panel, Paint=#paint{pens=Pens}, Data) -> + {Len, Max, Hs} = collect_data(Id, Data), + Size = wxWindow:getClientSize(Panel), + {X0,Y0,WS,HS} = draw_borders(Id, DC, Size, Max, Paint), + Start = max(61-Len, 0)*WS+X0, + case Hs of + [] -> ignore; + _ -> + Draw = fun(N) -> + Lines = make_lines(Hs, Start, N, Y0, WS, HS), + wxDC:setPen(DC, element(1+(N rem size(Pens)), Pens)), + wxDC:drawLines(DC, Lines), + N+1 + end, + [Draw(I) || I <- lists:seq(1,tuple_size(hd(Hs)))] + end, + ok. + +make_lines(Ds = [Data|_], PX, N, PY, WS, HS) -> + Y = element(N,Data), + make_lines(Ds, PX, N, PY, WS, HS, Y, []). + +make_lines([D1 | Ds = [D2|Rest]], PX, N, PY, WS, HS, Y0, Acc0) -> + Y1 = element(N,D1), + Y2 = element(N,D2), + Y3 = case Rest of + [D3|_] -> element(N,D3); + [] -> Y2 + end, + Acc = make_splines(Y0,Y1,Y2,Y3,PX,PY,WS,HS,[{round(PX),PY-round(Y1*HS)}|Acc0]), + make_lines(Ds, PX+WS, N, PY, WS, HS, Y1, Acc); +make_lines([D1], PX, N, PY, _WS, HS, _Y0, Acc) -> + Y1 = element(N,D1), + [{round(PX),PY-round(Y1*HS)}|Acc]. + +make_splines(_Y0,Y1,Y2,_Y3, _PX,_PY, _WS,HS, Acc) + when (abs(Y1-Y2) * HS) < 3.0 -> + Acc; +make_splines(_Y0,_Y1,_Y2,_Y3, _PX,_PY, WS,_HS, Acc) + when WS < 3.0 -> + Acc; +make_splines(Y00,Y10,Y20,Y30,PX,PY,WS,HS,Acc) -> + Y1 = Y10*HS, + Y2 = Y20*HS, + Steps = round(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, PY, Delta*WS, Acc); + true -> + Acc + end. + +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}. + +splines(N, XD, XD0, Tan, Y1,Y2, PX, PY, WS, Acc) when N > 0 -> + Delta = XD+XD0, + Y = spline(Delta, Tan, Y1,Y2), + X = PX+WS, + %% io:format("Y1:~p Y(~p):~p Y2:~p~n",[round(Y1),round(X),round(Y),round(Y2)]), + splines(N-1, Delta, XD0, Tan, Y1, Y2, X, PY, WS, [{round(X),PY-round(Y)}|Acc]); +splines(_N, _XD, _XD0, _Tan, _Y1,_Y2, _PX, _PY, _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. + +-define(BW, 5). +-define(BH, 5). + +draw_borders(Type, DC, {W,H}, Max0, #paint{pen=Pen, font=Font}) -> + Max = calc_max(Max0), + wxDC:setPen(DC, Pen), + wxDC:setFont(DC, Font), + Str1 = observer_lib:to_str(Max), + Str2 = observer_lib:to_str(div2(Max)), + Str3 = observer_lib:to_str(0), + {TW,TH} = wxDC:getTextExtent(DC, Str1), + + 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 div 2), + GraphY1 = SecondsY - ?BH, + GraphW = max(GraphX1-GraphX0-1, 60), + GraphH = max(GraphY1-GraphY0-1, 100), + ScaleW = GraphW / 60, + ScaleH = calc_scale(GraphH, Max), + + case Type of + ?RQ_W -> wxDC:drawText(DC, "CPU History - Run queue length", {TopTextX,?BH}); + ?MEM_W -> wxDC:drawText(DC, "Memory Usage", {TopTextX,?BH}); + ?IO_W -> wxDC:drawText(DC, "IO Usage", {TopTextX,?BH}) + end, + + Align = fun(Str, Y) -> + {StrW, _} = wxDC:getTextExtent(DC, Str), + wxDC:drawText(DC, Str, {GraphX0 - StrW - ?BW, Y}) + end, + Align(Str1, MaxTextY), + Align(Str2, MaxTextY - (TW div 2) + (GraphY1 - MaxTextY) div 2), + Align(Str3, GraphY1 - (TH div 2) + 1), + + DrawSecs = fun(Secs, Pos) -> + Str = [observer_lib:to_str(Secs)|" s"], + wxDC:drawText(DC, Str, {round(GraphX0-?BH+Pos), SecondsY}), + Pos + 10*ScaleW + end, + lists:foldl(DrawSecs, 0, lists:seq(60,0, -10)), + case Type of + ?RQ_W -> wxDC:drawText(DC, "Scheduler", {?BW, BottomTextY}); + ?MEM_W -> wxDC:drawText(DC, "Memory", {?BW, BottomTextY}); + ?IO_W -> wxDC:drawText(DC, "Input Output", {?BW, BottomTextY}) + end, + + wxDC:drawLines(DC, [{GraphX0, GraphY0}, {GraphX0, GraphY1}, + {GraphX1, GraphY1}, {GraphX1, GraphY0}, {GraphX0, GraphY0}]), + {GraphX0, GraphY1, ScaleW, ScaleH}. + +div2({Type, Int}) -> {Type, Int div 2}; +div2(Int) -> Int div 2. + +calc_max(Max) when Max < 10 -> 10; +calc_max({Type, Max}) -> {Type,calc_max1(Max)}; +calc_max(Max) -> calc_max1(Max). + +calc_max1(Max) -> + case Max div 10 of + X when X < 10 -> + (X+1)*10; + X -> + 10*calc_max1(X) + end. + +calc_scale(H, {_Type, Max}) -> calc_scale(H,Max); +calc_scale(Height, Max) when Height > Max -> + Height / Max; +calc_scale(Height, Max) -> + Height / Max. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 5a593abf11..2403b984e5 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -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 @@ -404,12 +410,15 @@ 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). -- cgit v1.2.3