aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
authorDan Gudmundsson <[email protected]>2015-12-07 15:03:45 +0100
committerDan Gudmundsson <[email protected]>2016-02-22 09:54:34 +0100
commitd80e6353e7b827b9fb3d9a9505948dfaa2491cd4 (patch)
tree43552df58364d2c979351fdbfe568a396cefc9a1 /lib/observer
parent942aa912cd1f17a7a98fd98f9d39a1374bbac44d (diff)
downloadotp-d80e6353e7b827b9fb3d9a9505948dfaa2491cd4.tar.gz
otp-d80e6353e7b827b9fb3d9a9505948dfaa2491cd4.tar.bz2
otp-d80e6353e7b827b9fb3d9a9505948dfaa2491cd4.zip
observer: Make it possible to change the graphs Xaxis and update freq
Diffstat (limited to 'lib/observer')
-rw-r--r--lib/observer/src/observer_perf_wx.erl179
1 files changed, 130 insertions, 49 deletions
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index ace0b62c1d..fe8cae13d1 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -35,18 +35,27 @@
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
+-define(ID_REFRESH_INTERVAL, 102).
+
+-define(DISP_FREQ, 10).
+-define(FETCH_DATA, 20). %% per 10000ms
+-define(DISP_SECONDS, 120).
+
+-record(ti, {tick=0, disp=10*?DISP_FREQ/?FETCH_DATA, fetch=?FETCH_DATA, secs=?DISP_SECONDS}).
+
-record(state,
{
- offset = 0.0,
+ time = #ti{},
active = false,
parent,
windows,
- data = {0, queue:new()},
+ data,
panel,
paint,
appmon
}).
+
-define(wxGC, wxGraphicsContext).
-record(paint, {font, small, pen, pen2, pens, usegc = false}).
@@ -83,7 +92,8 @@ init([Notebook, Parent]) ->
{Panel, #state{parent=Parent,
panel =Panel,
windows = {CPU, MEM, IO},
- paint=PaintInfo
+ paint=PaintInfo,
+ data=reset_data()
}}
catch _:Err ->
io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]),
@@ -129,7 +139,15 @@ setup_graph_drawing(Panels) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}},
+ #state{panel=Panel, appmon=Old, time=#ti{fetch=F0} = Ti0} = State) ->
+ case interval_dialog(Panel, Ti0) of
+ Ti0 -> {noreply, State};
+ #ti{fetch=F0} = Ti -> %% Same fetch interval
+ {noreply, State#state{time=Ti}};
+ Ti -> %% Changed fetch interval
+ {noreply, restart_fetcher(node(Old), State#state{time=Ti})}
+ end;
handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
State = #state{}) ->
{noreply, State};
@@ -139,7 +157,7 @@ handle_event(Event, _State) ->
%%%%%%%%%%
handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
- #state{active=Active, offset=Offset, paint=Paint,
+ #state{active=Active, time=Ti, paint=Paint,
windows=Windows, data=Data}) ->
%% Sigh workaround bug on MacOSX (Id in paint event is always 0)
%% Panel = element(Id, Windows),
@@ -147,11 +165,10 @@ handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
Panel =:= element(?MEM_W, Windows) -> memory;
Panel =:= element(?IO_W, Windows) -> io
end,
-
- refresh_panel(Panel, Id, Offset, Data, Active, Paint),
+ refresh_panel(Panel, Id, Ti, Data, Active, Paint),
ok.
-refresh_panel(Panel, Id, Offset, Data, Active, #paint{usegc=UseGC} = Paint) ->
+refresh_panel(Panel, Id, Ti, Data, Active, #paint{usegc=UseGC} = Paint) ->
%% PaintDC must be created in a callback to work on windows.
IsWindows = element(1, os:type()) =:= win32,
DC = if IsWindows ->
@@ -167,7 +184,7 @@ refresh_panel(Panel, Id, Offset, Data, Active, #paint{usegc=UseGC} = Paint) ->
end,
%% Nothing is drawn until wxPaintDC is destroyed.
try
- draw(Offset, Id, {UseGC, GC}, Panel, Paint, Data, Active)
+ draw(Ti, Id, {UseGC, GC}, Panel, Paint, Data, Active)
catch _:Err ->
io:format("Internal error ~p ~p~n",[Err, erlang:get_stacktrace()])
end,
@@ -183,40 +200,34 @@ handle_cast(Event, _State) ->
error({unhandled_cast, Event}).
%%%%%%%%%%
handle_info(Stats = {stats, 1, _, _, _},
- State = #state{panel=Panel, data=Data, active=Active}) ->
+ State = #state{panel=Panel, data=Data, active=Active,
+ time=#ti{tick=Tick, disp=Disp0}=Ti}) ->
+ Disp = trunc(Disp0),
+ Next = max(Tick - Disp, 0),
if Active ->
wxWindow:refresh(Panel),
- Freq = 6,
- erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq});
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next});
true -> ignore
end,
- {noreply, State#state{offset=0.0, data = add_data(Stats, Data)}};
+ {noreply, State#state{time=Ti#ti{tick=Next}, data = add_data(Stats, Data, Ti)}};
-handle_info({refresh, Seq, Freq}, State = #state{panel=Panel, offset=Prev}) ->
- wxWindow:refresh(Panel),
+handle_info({refresh, Seq}, State = #state{panel=Panel, time=#ti{tick=Seq, disp=DispF}=Ti})
+ when (Seq+1) < (DispF*1.5) ->
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;
+ wxWindow:refresh(Panel),
+ erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
+ {noreply, State#state{time=Ti#ti{tick=Next}}};
+handle_info({refresh, _}, State) ->
+ {noreply, State};
-handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) ->
+handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old} = State) ->
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()}}}
+ {noreply,restart_fetcher(Node, State)}
end;
handle_info(not_active, State = #state{appmon=_Pid}) ->
@@ -233,24 +244,80 @@ handle_info(_Event, State) ->
%%%%%%%%%%
terminate(_Event, #state{appmon=Pid}) ->
catch Pid ! exit,
+ cprof:pause(),
ok.
code_change(_, _, State) ->
State.
-add_data(Stats, {N, Q}) when N > 60 ->
+restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti}=State) ->
+ catch Old ! exit,
+ Me = self(),
+ Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, 10000 div Freq]),
+ wxWindow:refresh(Panel),
+ cprof:start(?MODULE, '_'),
+ State#state{active=true, appmon=Pid, data=reset_data(), time=Ti#ti{tick=0}}.
+
+reset_data() ->
+ {0, queue:new()}.
+
+add_data(Stats, {N, Q}, #ti{fetch=Fetch, secs=Secs}) when N > Secs*Fetch div 10 ->
{N, queue:drop(queue:in(Stats, Q))};
-add_data(Stats, {N, Q}) ->
+add_data(Stats, {N, Q}, _) ->
{N+1, queue:in(Stats, Q)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
create_menus(Parent, _) ->
- MenuEntries =
- [{"File",
- [
- ]}
- ],
- observer_wx:create_menus(Parent, MenuEntries).
+ View = {"View", [#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Graph Settings"}]},
+ observer_wx:create_menus(Parent, [{"File", []}, View]).
+
+interval_dialog(Parent0, #ti{fetch=Fetch0, secs=Secs0}=Ti) ->
+ Parent = observer_lib:get_wx_parent(Parent0),
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, "Load Chart Settings",
+ [{style, ?wxDEFAULT_DIALOG_STYLE bor
+ ?wxRESIZE_BORDER}]),
+ {Sl1,FetchSl} = slider(Dialog, "Fetch (ms)", 10000 div Fetch0, 100, 10000),
+ {Sl2, SecsSl} = slider(Dialog, "Length (min)", Secs0 div 60, 1, 10),
+ TopSizer = wxBoxSizer:new(?wxVERTICAL),
+ Flags = [{flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT},
+ {border, 5}, {proportion, 1}],
+ wxSizer:add(TopSizer, Sl1, Flags),
+ wxSizer:add(TopSizer, Sl2, Flags),
+ wxSizer:add(TopSizer, wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), Flags),
+ wxWindow:setSizerAndFit(Dialog, TopSizer),
+ wxSizer:setSizeHints(TopSizer, Dialog),
+ Res = case wxDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Fetch = 10000 div wxSlider:getValue(FetchSl),
+ Secs = wxSlider:getValue(SecsSl) * 60,
+ Ti#ti{fetch=Fetch, secs=Secs, disp=10*?DISP_FREQ/Fetch};
+ ?wxID_CANCEL ->
+ Ti
+ end,
+ wxDialog:destroy(Dialog),
+ Res.
+
+slider(Parent, Str, Value, Min, Max) ->
+ Sz = wxBoxSizer:new(?wxHORIZONTAL),
+ Center = [{flag, ?wxALIGN_CENTER_VERTICAL}],
+ wxSizer:add(Sz, wxStaticText:new(Parent, ?wxID_ANY, Str), [{proportion, 1}|Center]),
+ Opt = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}],
+ Slider = wxSlider:new(Parent, ?wxID_ANY, Value, Min, Max, Opt),
+ wxSizer:add(Sz, Slider, [{proportion, 2}|Center]),
+ case Min > 1 of
+ false ->
+ {Sz, Slider};
+ true ->
+ CB = fun(#wx{event=Ev},_) -> step(Ev, Slider, Min) end,
+ wxSlider:connect(Slider, scroll_thumbtrack, [{callback, CB}]),
+ wxSlider:connect(Slider, scroll_changed, [{callback, CB}]),
+ {Sz, Slider}
+ end.
+
+step(_Ev = #wxScroll{commandInt=Value}, Slider, Min) ->
+ Val = Min * round(Value / Min),
+ wxSlider:setValue(Slider, Val),
+ ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
collect_data(runq, {N, Q}) ->
@@ -322,16 +389,23 @@ calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) ->
calc_delta([], []) -> [].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active) ->
+draw(#ti{tick=Tick, fetch=FetchFreq0, secs=Secs, disp=DispFreq},
+ 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, Info} = collect_data(Id, Data),
{Max,_,_} = MaxDisp = calc_max(Id, Max0),
Size = wxWindow:getClientSize(Panel),
- {X0,Y0,WS,HS, DrawBs} = draw_borders(Id, Info, DC, Size, MaxDisp, Paint),
- Last = 60*WS+X0-1,
- Start = max(61-Len, 0)*WS+X0 - Offset*WS,
+ {X0,Y0,WS0,HS,DrawBs} = draw_borders(Id, Info, DC, Size, Secs, MaxDisp, Paint),
+ FetchFreq = FetchFreq0/10,
+ WS = WS0/FetchFreq,
+ Last = Secs*FetchFreq*WS+X0-1,
+ Offset = Tick / DispFreq,
+ Start = max(Secs*FetchFreq+1-Len, 0)*WS+X0 - Offset*WS,
+ %% Id == io andalso io:format("~p ~p~n", [Tick, DispFreq]),
Samples = length(Hs),
NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end,
case Active andalso Samples > 1 andalso NoGraphs > 0 of
@@ -426,7 +500,7 @@ spline_tan(Y0, Y1, Y2, Y3) ->
-define(BW, 5).
-define(BH, 5).
-draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit},
+draw_borders(Type, Info, DC, {W,H}, Secs, {Max, Unit, MaxUnit},
#paint{pen=Pen, pen2=Pen2, font=Font, small=Small}) ->
Str1 = observer_lib:to_str(MaxUnit),
Str2 = observer_lib:to_str(MaxUnit div 2),
@@ -449,7 +523,7 @@ draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit},
GraphY25 = GraphY0 + (GraphY1 - GraphY0) / 4,
GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2,
GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4,
- ScaleW = GraphW / 60,
+ ScaleW = GraphW / Secs,
ScaleH = GraphH / Max,
setFont(DC, Small, {0,0,0}),
@@ -462,14 +536,21 @@ draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit},
Align(Str3, GraphY1 - (TH / 2) + 1),
setPen(DC, Pen),
- DrawSecs = fun(Secs, Pos) ->
- Str = [observer_lib:to_str(Secs)|" s"],
+ DrawSecs = fun(Sec, {Pos, Prev}) ->
+ Str = observer_lib:to_str(Sec) ++ "s",
X = GraphX0+Pos,
- drawText(DC, Str, X-SpaceW, SecondsY),
strokeLine(DC, X, GraphY0, X, GraphY1+5),
- Pos + 10*ScaleW
+ TxtX = X-SpaceW,
+ case TxtX > Prev of
+ true ->
+ drawText(DC, Str, TxtX, SecondsY),
+ TxtW = SpaceW*length(Str),
+ {Pos + 10*ScaleW, TxtX+TxtW};
+ false ->
+ {Pos + 10*ScaleW, Prev}
+ end
end,
- lists:foldl(DrawSecs, 0, lists:seq(60,0, -10)),
+ lists:foldl(DrawSecs, {0, 0}, lists:seq(Secs,0, -10)),
strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25),
strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50),