1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2012-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19-module(observer_perf_wx). 20 21-export([start_link/3]). 22 23%% wx_object callbacks 24-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, 25 handle_event/2, handle_sync_event/3, handle_cast/2]). 26 27%% Drawing wrappers for DC and GC areas 28-export([make_win/4, setup_graph_drawing/1, 29 refresh_panel/4, precalc/4, add_data/5, interval_dialog/2, 30 haveGC/0, make_gc/2, destroy_gc/1, 31 setPen/2, setFont/3, setBrush/2, 32 strokeLine/5, strokeLines/2, drawRoundedRectangle/6, 33 drawText/4, getTextExtent/2]). 34 35-behaviour(wx_object). 36-include_lib("wx/include/wx.hrl"). 37-include("observer_defs.hrl"). 38 39-define(ID_REFRESH_INTERVAL, 102). 40 41-define(BW, 5). 42-define(BH, 5). 43 44-record(state, 45 { 46 time = #ti{}, 47 active = false, 48 parent, 49 samples, %% Orig data store 50 wins=[], %% per window content 51 panel, 52 paint, 53 appmon 54 }). 55 56-define(wxGC, wxGraphicsContext). 57 58-record(paint, {font, small, fg, pen, pen2, pens, dot_pens, usegc = false}). 59 60start_link(Notebook, Parent, Config) -> 61 wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). 62 63init([Notebook, Parent, Config]) -> 64 try 65 Panel = wxPanel:new(Notebook), 66 Main = wxBoxSizer:new(?wxVERTICAL), 67 MemIO = wxBoxSizer:new(?wxHORIZONTAL), 68 69 CPU = make_win(runq, Panel, Main, ?wxALL), 70 MEM = make_win(memory, Panel, MemIO, ?wxLEFT), 71 IO = make_win(io, Panel, MemIO, ?wxLEFT bor ?wxRIGHT), 72 73 wxSizer:add(Main, MemIO, [{flag, ?wxEXPAND bor ?wxDOWN}, 74 {proportion, 1}, {border, 5}]), 75 wxWindow:setSizer(Panel, Main), 76 Windows = [CPU, MEM, IO], 77 PaintInfo = setup_graph_drawing(Windows), 78 79 process_flag(trap_exit, true), 80 State0 = #state{parent=Parent, 81 panel =Panel, 82 wins = Windows, 83 paint=PaintInfo, 84 samples=reset_data(), 85 time=#ti{fetch=maps:get(fetch, Config, ?FETCH_DATA), 86 secs=maps:get(secs, Config, ?DISP_SECONDS)} 87 }, 88 {Panel, State0} 89 catch _:Err:Stacktrace -> 90 io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, Stacktrace]), 91 {stop, Err} 92 end. 93 94make_win(Name, Parent, Sizer, Border) -> 95 Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN, 96 Panel = wxPanel:new(Parent, [{style,Style}]), 97 Opts = [{flag, ?wxEXPAND bor Border}, {proportion, 1}, {border, 5}], 98 wxSizer:add(Sizer, Panel, Opts), 99 #win{name=Name, panel=Panel}. 100 101setup_graph_drawing(Panels) -> 102 Do = fun(#win{panel=Panel}) -> 103 wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_PAINT), 104 wxPanel:connect(Panel, paint, [callback]) 105 end, 106 _ = [Do(Panel) || Panel <- Panels], 107 UseGC = haveGC(), 108 Version28 = ?wxMAJOR_VERSION =:= 2 andalso ?wxMINOR_VERSION =:= 8, 109 Scale = observer_wx:get_scale(), 110 {Font, SmallFont} 111 = if UseGC, Version28 -> 112 %% Def font is really small when using Graphics contexts in 2.8 113 %% Hardcode it 114 F = wxFont:new(Scale * 12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD), 115 SF = wxFont:new(Scale * 10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), 116 {F, SF}; 117 true -> 118 DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), 119 DefSize = wxFont:getPointSize(DefFont), 120 DefFamily = wxFont:getFamily(DefFont), 121 F = wxFont:new(Scale * (DefSize-1), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD), 122 SF = wxFont:new(Scale * (DefSize-2), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), 123 {F, SF} 124 end, 125 BG = wxWindow:getBackgroundColour((hd(Panels))#win.panel), 126 Fg = case observer_lib:is_darkmode(BG) of 127 false -> {0,0,0}; 128 true -> wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT) 129 end, 130 131 PenColor = case observer_lib:is_darkmode(BG) of 132 false -> {0,0,0}; 133 true -> {0,0,0} 134 end, 135 Pens = [wxPen:new(Col, [{width, Scale}, {style, ?wxSOLID}]) 136 || Col <- tuple_to_list(colors())], 137 DotPens = [wxPen:new(Col, [{width, Scale}, {style, ?wxDOT}]) 138 || Col <- tuple_to_list(colors())], 139 #paint{usegc = UseGC, 140 font = Font, 141 small = SmallFont, 142 fg = Fg, %% Text color 143 pen = wxPen:new(PenColor), 144 pen2 = wxPen:new(PenColor, [{width, Scale}]), 145 pens = list_to_tuple(Pens), 146 dot_pens = list_to_tuple(DotPens) 147 }. 148 149 150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 151handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}}, 152 #state{panel=Panel, appmon=Old, wins=Wins0, time=#ti{fetch=F0} = Ti0} = State) -> 153 case interval_dialog(Panel, Ti0) of 154 Ti0 -> {noreply, State}; 155 #ti{fetch=F0} = Ti -> %% Same fetch interval force refresh 156 Wins = [W#win{max=undefined} || W <- Wins0], 157 {noreply, precalc(State#state{time=Ti, wins=Wins})}; 158 Ti when Old =:= undefined -> 159 {noreply, State#state{time=Ti}}; 160 Ti -> %% Changed fetch interval, drop all data 161 {noreply, restart_fetcher(node(Old), State#state{time=Ti})} 162 end; 163handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, 164 State = #state{}) -> 165 {noreply, State}; 166 167handle_event(Event, _State) -> 168 error({unhandled_event, Event}). 169 170%%%%%%%%%% 171handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, 172 #state{active=Active, time=Ti, paint=Paint, wins=Windows}) -> 173 %% Sigh workaround bug on MacOSX (Id in paint event is always 0) 174 %% Panel = element(Id, Windows), 175 Win = lists:keyfind(Panel, #win.panel, Windows), 176 refresh_panel(Active, Win, Ti, Paint), 177 ok. 178 179refresh_panel(Active, #win{name=_Id, panel=Panel}=Win, Ti, #paint{usegc=UseGC}=Paint) -> 180 %% PaintDC must be created in a callback to work on windows. 181 %% Nothing is drawn until wxPaintDC is destroyed. 182 GC = make_gc(Panel, UseGC), 183 if Active -> draw_win(GC, Win, Ti, Paint); 184 true -> ignore 185 end, 186 destroy_gc(GC). 187 188%%%%%%%%%% 189handle_call(get_config, _, #state{time=Ti}=State) -> 190 #ti{fetch=Fetch, secs=Range} = Ti, 191 {reply, #{fetch=>Fetch, secs=>Range}, State}; 192 193handle_call(Event, From, _State) -> 194 error({unhandled_call, Event, From}). 195 196handle_cast(Event, _State) -> 197 error({unhandled_cast, Event}). 198%%%%%%%%%% 199handle_info({stats, 1, _, _, _} = Stats, 200 #state{panel=Panel, samples=Data, active=Active, wins=Wins0, 201 appmon=Node, time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) -> 202 if Active -> 203 Disp = trunc(Disp0), 204 Next = max(Tick - Disp, 0), 205 erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), 206 {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node), 207 State = precalc(State0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples}), 208 wxWindow:refresh(Panel), 209 {noreply, State}; 210 true -> 211 {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node), 212 Wins = [W#win{max=undefined} || W <- Wins1], 213 {noreply, State0#state{samples=Samples, wins=Wins, time=Ti#ti{tick=0}}} 214 end; 215 216handle_info({refresh, Seq}, #state{panel=Panel, time=#ti{tick=Seq, disp=DispF}=Ti} = State0) 217 when (Seq+1) < (DispF*1.5) -> 218 Next = Seq+1, 219 erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), 220 State = precalc(State0#state{time=Ti#ti{tick=Next}}), 221 catch wxWindow:refresh(Panel), 222 {noreply, State}; 223handle_info({refresh, _}, State) -> 224 {noreply, State}; 225 226handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old} = State) -> 227 create_menus(Parent, []), 228 try 229 Node = node(Old), 230 wxWindow:refresh(Panel), 231 erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}), 232 {noreply, State#state{active=true}} 233 catch _:_ -> 234 {noreply,restart_fetcher(Node, State)} 235 end; 236 237handle_info(not_active, State = #state{appmon=_Pid}) -> 238 %% Pid ! exit, 239 {noreply, State#state{active=false}}; 240 241handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) -> 242 {noreply, State#state{active=false, appmon=undefined}}; 243 244handle_info(_Event, State) -> 245 %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]), 246 {noreply, State}. 247 248%%%%%%%%%% 249terminate(_Event, #state{appmon=Pid}) -> 250 catch Pid ! exit, 251 ok. 252code_change(_, _, State) -> 253 State. 254 255restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti, wins=Wins0}=State) -> 256 catch Old ! exit, 257 Me = self(), 258 Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, round(1000/Freq)]), 259 wxWindow:refresh(Panel), 260 Wins = [W#win{state=undefined} || W <- Wins0], 261 precalc(State#state{active=true, appmon=Pid, samples=reset_data(), 262 wins=Wins, time=Ti#ti{tick=0}}). 263 264reset_data() -> 265 {0, queue:new()}. 266 267add_data(Stats, Q, Wins, Ti, Active) -> 268 add_data(Stats, Q, Wins, Ti, Active, ignore). 269 270add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active, Node) 271 when N > (Secs*Fetch+1) -> 272 {{value, Drop}, Q} = queue:out(Q0), 273 add_data_1(Wins, Stats, N, {Drop,Q}, Active, Node); 274add_data(Stats, {N, Q}, Wins, _, Active, Node) -> 275 add_data_1(Wins, Stats, N+1, {empty, Q}, Active, Node). 276 277add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active, Node) 278 when St /= undefined -> 279 try 280 {Wins, Stat} = 281 lists:mapfoldl(fun(Win0, Entry) -> 282 {Win1,Stat} = add_data_2(Win0, Last, Entry), 283 case Active of 284 true -> 285 Win = add_data_3(Win1, N, Drop, Stat, Q), 286 {Win, Stat}; 287 false -> 288 {Win1, Stat} 289 end 290 end, #{}, Wins0), 291 {Wins, {N,queue:in(Stat#{}, Q)}} 292 catch no_scheduler_change -> 293 {[Win#win{state=init_data(Id, Last), info=info(Id, Last, Node)} 294 || #win{name=Id}=Win <- Wins0], {0,queue:new()}} 295 end; 296 297add_data_1(Wins, Stats, 1, {_, Q}, _, Node) -> 298 {[Win#win{state=init_data(Id, Stats), info=info(Id, Stats, Node)} 299 || #win{name=Id}=Win <- Wins], {0,Q}}. 300 301add_data_2(#win{name=Id, state=S0}=Win, Stats, Map) -> 302 {V1, S1} = collect_data(Id, Stats, S0), 303 {Win#win{state=S1}, Map#{Id=>V1}}. 304 305add_data_3(#win{name=Id, max={{OldMax, OldEntry},_,_,_}, 306 geom=#{scale:={WS,HS}}, state={Max,_}, 307 graphs=Graphs}=Win, 308 N, Drop0, Last, Q1) 309 when N > 3 -> 310 Drop = case Drop0 of 311 #{Id:=D} -> D; 312 _ -> Drop0 313 end, 314 case {max_value(Max), Drop =:= OldEntry} of 315 {OldMax, false} -> 316 #{Id:=V4} = Last, 317 {{value, #{Id:=V3}},Q2} = queue:out_r(Q1), 318 {{value, #{Id:=V2}},Q3} = queue:out_r(Q2), 319 {{value, #{Id:=V1}},_} = queue:out_r(Q3), 320 Vals = [V1,V2,V3,V4], 321 Gs = tuple_size(V1), 322 Info = lists:zip(lists:seq(Gs, 1, -1), Graphs), 323 Lines = [add_lines(Vals, Drop, Prev, I, WS, HS) || {I, Prev} <- Info], 324 Win#win{graphs=Lines, no_samples=N}; 325 _W -> %% Max changed Trigger complete recalc 326 Win#win{max=undefined} 327 end; 328add_data_3(Win, _, _, _,_) -> 329 %% Trigger complete recalc 330 Win#win{max=undefined}. 331 332%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 333 334create_menus(Parent, _) -> 335 View = {"View", [#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Graph Settings"}]}, 336 observer_wx:create_menus(Parent, [{"File", []}, View]). 337 338interval_dialog(Parent0, #ti{fetch=Fetch0, secs=Secs0}=Ti) -> 339 Parent = observer_lib:get_wx_parent(Parent0), 340 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Load Chart Settings", 341 [{style, ?wxDEFAULT_DIALOG_STYLE bor 342 ?wxRESIZE_BORDER}]), 343 {Sl1,FetchSl} = slider(Dialog, "Sample (ms)", trunc(1000 / Fetch0), 100, 10000), 344 {Sl2, SecsSl} = slider(Dialog, "Length (min)", Secs0 div 60, 1, 10), 345 TopSizer = wxBoxSizer:new(?wxVERTICAL), 346 Flags = [{flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT}, 347 {border, 5}, {proportion, 1}], 348 wxSizer:add(TopSizer, Sl1, Flags), 349 wxSizer:add(TopSizer, Sl2, Flags), 350 wxSizer:add(TopSizer, wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), Flags), 351 wxWindow:setSizerAndFit(Dialog, TopSizer), 352 wxSizer:setSizeHints(TopSizer, Dialog), 353 Res = case wxDialog:showModal(Dialog) of 354 ?wxID_OK -> 355 Fetch = 1000 / wxSlider:getValue(FetchSl), 356 Secs = wxSlider:getValue(SecsSl) * 60, 357 Ti#ti{fetch=Fetch, secs=Secs, disp=?DISP_FREQ/Fetch}; 358 ?wxID_CANCEL -> 359 Ti 360 end, 361 wxDialog:destroy(Dialog), 362 Res. 363 364slider(Parent, Str, Value, Min, Max) -> 365 Sz = wxBoxSizer:new(?wxHORIZONTAL), 366 Center = [{flag, ?wxALIGN_CENTER_VERTICAL}], 367 wxSizer:add(Sz, wxStaticText:new(Parent, ?wxID_ANY, Str), [{proportion, 1}|Center]), 368 Opt = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}], 369 Slider = wxSlider:new(Parent, ?wxID_ANY, Value, Min, Max, Opt), 370 wxSizer:add(Sz, Slider, [{proportion, 2}|Center]), 371 case Min > 1 of 372 false -> 373 {Sz, Slider}; 374 true -> 375 CB = fun(#wx{event=Ev},_) -> step(Ev, Slider, Min) end, 376 wxSlider:connect(Slider, scroll_thumbtrack, [{callback, CB}]), 377 wxSlider:connect(Slider, scroll_changed, [{callback, CB}]), 378 {Sz, Slider} 379 end. 380 381step(_Ev = #wxScroll{commandInt=Value}, Slider, Min) -> 382 Val = Min * round(Value / Min), 383 wxSlider:setValue(Slider, Val), 384 ok. 385 386%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 387 388mk_max() -> {0, undefined}. 389max_value({Max,_}) -> Max. 390%% max_data({_,Data}) -> Data. matched in function head 391 392lmax(MState, Tuple, Tuple) when is_tuple(Tuple) -> 393 lmax(MState, tuple_to_list(Tuple), Tuple); 394lmax(MState, Values, State) -> 395 Max = max_value(MState), 396 New = lists:max([Max|Values]), 397 case New >= Max of 398 false -> MState; 399 true -> {New, State} 400 end. 401 402init_data(runq, {stats, _, T0, _, _}) -> {mk_max(),lists:sort(T0)}; 403init_data(io, {stats, _, _, {{_,In0}, {_,Out0}}, _}) -> {mk_max(), {In0,Out0}}; 404init_data(memory, _) -> {mk_max(), info(memory, undefined, undefined)}; 405init_data(alloc, _) -> {mk_max(), unused}; 406init_data(utilz, _) -> {mk_max(), unused}. 407 408info(runq, {stats, _, T0, _, _}, Node) -> 409 Dirty = get_dirty_cpu(Node), 410 {lists:seq(1, length(T0)-Dirty), Dirty}; 411info(memory, _, _) -> [total, processes, atom, binary, code, ets]; 412info(io, _, _) -> [input, output]; 413info(alloc, First, _) -> [Type || {Type, _, _} <- First]; 414info(utilz, First, _) -> [Type || {Type, _, _} <- First]; 415info(_, [], _) -> []. 416 417get_dirty_cpu(Node) -> 418 case rpc:call(node(Node), erlang, system_info, [dirty_cpu_schedulers]) of 419 {badrpc,_R} -> 0; 420 N -> N 421 end. 422 423collect_data(runq, {stats, _, T0, _, _}, {Max,S0}) -> 424 S1 = lists:sort(T0), 425 Delta = calc_delta(S1, S0), 426 Sample = list_to_tuple(Delta), 427 {Sample, {lmax(Max,Delta,Sample), S1}}; 428collect_data(io, {stats, _, _, {{_,In0}, {_,Out0}}, _}, {Max,{PIn,POut}}) -> 429 In = In0-PIn, 430 Out = Out0-POut, 431 Sample = {In, Out}, 432 {Sample, {lmax(Max, [In,Out], Sample), {In0, Out0}}}; 433collect_data(memory, {stats, _, _, _, MemInfo}, {Max, MemTypes}) -> 434 Vs = [Value || {Type,Value} <- MemInfo, lists:member(Type, MemTypes)], 435 Sample = list_to_tuple(Vs), 436 {Sample, {lmax(Max, Vs, Sample),MemTypes}}; 437collect_data(alloc, MemInfo, Max) -> 438 Vs = [Carrier || {_Type,_Block,Carrier} <- MemInfo], 439 Sample = list_to_tuple(Vs), 440 {Sample, {lmax(Max, Vs, Sample),unused}}; 441collect_data(utilz, MemInfo, Max) -> 442 Vs = [round(100*Block/Carrier) || {_Type,Block,Carrier} <- MemInfo], 443 Sample = list_to_tuple(Vs), 444 {Sample, {lmax(Max,Vs,Sample),unused}}. 445 446calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) -> 447 [100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)]; 448calc_delta([], []) -> []; 449calc_delta(_, _) -> throw(no_scheduler_change). 450 451precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) -> 452 Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0], 453 State#state{wins=Wins}. 454 455precalc(Ti, {NoSamples,Q}, Paint, #win{name=Id, panel=Panel}=Win) -> 456 Size = wxWindow:getClientSize(Panel), 457 case Win of 458 #win{max=Max, no_samples=NoSamples, size=Size} when is_tuple(Max) -> 459 Win; 460 _SomeThingChanged -> 461 Hs = [Vals || #{Id:=Vals} <- queue:to_list(Q)], 462 Max = lists:foldl(fun(Vals,Max) -> lmax(Max, Vals, Vals) end, 463 mk_max(), Hs), 464 MaxDisp = calc_max(Id, Max), 465 #{scale:={WS,HS}} = Props = window_geom(Size, MaxDisp, Ti, Panel, Paint), 466 NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end, 467 Graphs = [make_lines(Hs, I, WS, HS) || I <- lists:seq(NoGraphs, 1, -1)], 468 State = case Win#win.state of 469 undefined -> {Max, undefined}; 470 {_, St} -> {Max, St} 471 end, 472 Win#win{geom=Props, size=Size, 473 max=MaxDisp, 474 graphs=Graphs, 475 no_samples=NoSamples, 476 state=State} 477 end. 478 479window_geom({W,H}, {_, Max, _Unit, MaxUnit}, 480 #ti{secs=Secs, fetch=FetchFreq}, 481 Panel, #paint{font=Font}) -> 482 Str1 = observer_lib:to_str(MaxUnit), 483 Str2 = observer_lib:to_str(MaxUnit div 2), 484 Str3 = observer_lib:to_str(0), 485 {TW,TH,_,_} = wxWindow:getTextExtent(Panel, Str1, [{theFont, Font}]), 486 {SpaceW, _,_,_} = wxWindow:getTextExtent(Panel, "W", [{theFont, Font}]), 487 X0 = ?BW+TW+?BW, 488 X1 = W-?BW*4, 489 MaxTextY = TH+?BH, 490 BottomTextY = H-?BH-TH, 491 Y0 = MaxTextY + (TH / 2), 492 Y1 = BottomTextY - TH - ?BH, 493 494 ScaleW = (X1-X0-1)/(Secs*FetchFreq), 495 ScaleH = (Y1-Y0-1) / Max, 496 #{p0=>{X0,Y0}, p1=>{X1,Y1}, scale=>{ScaleW, ScaleH}, 497 txsz=>{TW,TH,SpaceW}, txt=>{BottomTextY, MaxTextY}, strs=>{Str1,Str2,Str3}}. 498 499 500%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 501draw_win(DC, #win{name=Name, no_samples=Samples, geom=#{scale:={WS,HS}}, 502 graphs=Graphs, max={_,Max,_,_}, info=Info}=Win, 503 #ti{tick=Tick, fetch=FetchFreq, secs=Secs, disp=DispFreq}=Ti, 504 Paint=#paint{pens=Pens, dot_pens=Dots}) when Samples >= 2, Graphs =/= [] -> 505 %% Draw graphs 506 {X0,Y0,DrawBs} = draw_borders(DC, Ti, Win, Paint), 507 Offset = Tick / DispFreq, 508 Full = case Samples > (1+Secs*FetchFreq) of 509 true -> 1; 510 false -> 2 511 end, 512 Start = X0 + (max(Secs*FetchFreq+Full-Samples, 0) - Offset)*WS, 513 Last = Secs*FetchFreq*WS+X0, 514 Dirty = case {Name, Info} of 515 {runq, {_, DCpu}} -> DCpu; 516 _ -> 0 517 end, 518 NoGraphs = length(Graphs), 519 NoCpu = NoGraphs - Dirty, 520 Draw = fun(Lines0, N) -> 521 case Dirty > 0 andalso N > NoCpu of 522 true -> setPen(DC, element(1+ ((N-NoCpu-1) rem tuple_size(Dots)), Dots)); 523 false -> setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)) 524 end, 525 Order = lists:reverse(Lines0), 526 [{_,Y}|Lines] = translate(Order, {Start, Y0}, 0, WS, {X0,Max*HS,Last}, []), 527 strokeLines(DC, [{Last,Y}|Lines]), 528 N-1 529 end, 530 lists:foldl(Draw, NoGraphs, Graphs), 531 DrawBs(), 532 ok; 533 534draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{fg=Fg, small=Small}=Paint) -> 535 %% Draw Error Msg 536 try draw_borders(DC, Ti, Win, Paint) of 537 {X0,_Y0,DrawBs} -> 538 Text = case Samples =< 1 of 539 true -> "Waiting for data"; 540 false -> "Information not available" 541 end, 542 setFont(DC, Small, Fg), 543 {_,WW} = getSize(DC), 544 drawText(DC, Text, X0 + 100, WW div 2), 545 DrawBs(), 546 ok 547 catch _:_ -> %% Early redraws fail 548 ok 549 end. 550 551translate([{X0,Y}|Rest], {Sx,Sy}=Start, N, WS, {Cx,Cy,Cw}=Clip, Acc) -> 552 X = min((N-X0)*WS+Sx,Cw), 553 Next = if X0 > 0 -> N; true -> N+1 end, 554 case X =< Cx of 555 true -> 556 translate(Rest, Start, Next, WS, Clip, [{Cx,Sy-min(Cy,Y)}]); 557 false -> 558 translate(Rest, Start, Next, WS, Clip, [{X,Sy-min(Cy,Y)}|Acc]) 559 end; 560translate([], _, _, _, _, Acc) -> 561 Acc. 562 563add_lines(Vals, Drop, OldLines, I, WS, HS) -> 564 Lines = strip(OldLines, Drop, 2), 565 New = make_lines(Vals, I, WS, HS), 566 New ++ Lines. 567 568strip([{X,_}|Rest], Drop, N) when X > 0.0001, N > 0 -> 569 strip(Rest, Drop, N); 570strip([_|Rest], Drop, N) when N > 0 -> 571 strip(Rest, Drop, N-1); 572strip(List, empty, _) -> List; 573strip(List, _, _) -> 574 lists:reverse(strip(lists:reverse(List), empty, 1)). 575 576make_lines(Ds = [Data|_], N, WS, HS) -> 577 Y = element(N,Data), 578 make_lines(Ds, N, WS, HS, Y, []). 579 580make_lines([D1 | Ds = [D2|Rest]], N, WS, HS, Y0, Acc0) -> 581 Y1 = element(N,D1), 582 Y2 = element(N,D2), 583 Y3 = case Rest of 584 [D3|_] -> element(N,D3); 585 [] -> Y2 586 end, 587 This = {0, Y1*HS}, 588 Acc = if (abs(Y1-Y2) * HS) < 3.0 -> [This|Acc0]; 589 WS < 3.0 -> [This|Acc0]; 590 true -> make_splines(Y0,Y1,Y2,Y3,WS,HS,[This|Acc0]) 591 end, 592 make_lines(Ds, N, WS, HS, Y1, Acc); 593make_lines([_D1], _N, _WS, _HS, _Y0, Acc) -> 594 Acc. 595 596make_splines(Y00,Y10,Y20,Y30,WS,HS,Acc) -> 597 Y1 = Y10*HS, 598 Y2 = Y20*HS, 599 Steps = min(abs(Y1-Y2), WS/2), 600 if Steps > 2 -> 601 Y0 = Y00*HS, 602 Y3 = Y30*HS, 603 Tan = spline_tan(Y0,Y1,Y2,Y3), 604 Delta = 1/Steps, 605 splines(Steps-1, 0.0, Delta, Tan, Y1,Y2, Acc); 606 true -> 607 Acc 608 end. 609 610splines(N, XD, XD0, Tan, Y1,Y2, Acc) when N > 0 -> 611 Delta = XD+XD0, 612 Y = max(0, spline(Delta, Tan, Y1,Y2)), 613 splines(N-1, Delta, XD0, Tan, Y1, Y2, [{1.0-Delta, Y}|Acc]); 614splines(_N, _XD, _XD0, _Tan, _Y1,_Y2, Acc) -> 615 Acc. 616 617spline(T, {M1, M2}, Y1, Y2) -> 618 %% Hermite Basis Funcs 619 T2 = T*T, T3 = T*T*T, 620 H1 = 2*T3-3*T2+1, 621 H2 = -2*T3+3*T2, 622 H3 = T3-2*T2+T, 623 H4 = T3-T2, 624 %% Result 625 M1*H3 + Y1*H1 + Y2*H2 + M2*H4. 626 627spline_tan(Y0, Y1, Y2, Y3) -> 628 S = 1.0, 629 C = 0.5, 630 %% Calc tangent values 631 M1 = S*C*(Y2-Y0), 632 M2 = S*C*(Y3-Y1), 633 {M1,M2}. 634 635draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, 636 #win{name=Type, geom=Geom, info=Info, max={_,_,Unit,_}}, 637 #paint{pen=Pen, pen2=Pen2, fg=Fg, font=Font, small=Small}) -> 638 #{p0:={GraphX0, GraphY0}, p1:={GraphX1,GraphY1}, scale:={ScaleW0,_}, 639 txsz:={TW,TH,SpaceW}, txt:={BottomTextY, MaxTextY}, strs:={Str1,Str2,Str3}} = Geom, 640 641 ScaleW = ScaleW0*FetchFreq, 642 TopTextX = ?BW*3+TW, 643 SecondsY = BottomTextY - TH, 644 645 GraphY25 = GraphY0 + (GraphY1 - GraphY0) / 4, 646 GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2, 647 GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4, 648 649 setFont(DC, Small, Fg), 650 Align = fun(Str, Y) -> 651 {StrW, _} = getTextExtent(DC, Str), 652 drawText(DC, Str, GraphX0 - StrW - ?BW, Y) 653 end, 654 Align(Str1, MaxTextY), 655 Align(Str2, GraphY50 - (TH / 2)), 656 Align(Str3, GraphY1 - (TH / 2) + 1), 657 658 setPen(DC, Pen), 659 DrawSecs = fun(Sec, {Pos, Prev}) -> 660 Str = observer_lib:to_str(Sec) ++ "s", 661 X = GraphX0+Pos, 662 strokeLine(DC, X, GraphY0, X, GraphY1+5), 663 TxtX = X-SpaceW, 664 case TxtX > Prev of 665 true -> 666 drawText(DC, Str, TxtX, SecondsY), 667 TxtW = SpaceW*length(Str), 668 {Pos + 10*ScaleW, TxtX+TxtW}; 669 false -> 670 {Pos + 10*ScaleW, Prev} 671 end 672 end, 673 lists:foldl(DrawSecs, {0, 0}, lists:seq(Secs,0, -10)), 674 675 strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25), 676 strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50), 677 strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75), 678 679 setFont(DC, Font, Fg), 680 681 Text = fun(X,Y, Str, PenId) -> 682 if PenId == 0 -> 683 setFont(DC, Font, Fg); 684 PenId > 0 -> 685 Id = 1 + ((PenId-1) rem tuple_size(colors())), 686 setFont(DC, Font, element(Id, colors())) 687 end, 688 drawText(DC, Str, X, Y), 689 {StrW, _} = getTextExtent(DC, Str), 690 StrW + X + ?BW*2 691 end, 692 693 case Type of 694 runq -> 695 {TextInfo, DirtyCpus} = Info, 696 drawText(DC, "Scheduler Utilization (%) ", TopTextX, ?BH), 697 TN0 = Text(TopTextX, BottomTextY, "Scheduler: ", 0), 698 Id = fun(Id, Pos0) -> 699 Text(Pos0, BottomTextY, integer_to_list(Id), Id) 700 end, 701 TN1 = lists:foldl(Id, TN0, TextInfo), 702 TN2 = Text(TN1, BottomTextY, "Dirty cpu: ", 0), 703 TN3 = lists:foldl(Id, TN2, lists:seq(1, DirtyCpus)), 704 _ = Text(TN3, BottomTextY, "(dotted)", 0), 705 ok; 706 memory -> 707 drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH), 708 lists:foldl(fun(MType, {PenId, Pos0}) -> 709 Str = to_string(MType), 710 Pos = Text(Pos0, BottomTextY, Str, PenId), 711 {PenId+1, Pos} 712 end, {1, TopTextX}, Info); 713 io -> 714 drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH), 715 lists:foldl(fun(MType, {PenId, Pos0}) -> 716 Str = to_string(MType), 717 Pos = Text(Pos0, BottomTextY, Str, PenId), 718 {PenId+1, Pos} 719 end, {1, TopTextX}, Info); 720 alloc -> 721 drawText(DC, "Carrier Size " ++ Unit, TopTextX,?BH); 722 utilz -> 723 drawText(DC, "Carrier Utilization (%)" ++ Unit, TopTextX,?BH), 724 lists:foldl(fun(MType, {PenId, Pos0}) -> 725 Str = to_string(MType), 726 Pos = Text(Pos0, BottomTextY, Str, PenId), 727 {PenId+1, Pos} 728 end, {1, TopTextX}, Info) 729 end, 730 DrawBorder = fun() -> 731 setPen(DC, Pen2), 732 strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1}, 733 {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1}, 734 {GraphX0, GraphY0-1}]) 735 end, 736 {GraphX0+1, GraphY1, DrawBorder}. 737 738to_string(Atom) -> 739 Name = atom_to_list(Atom), 740 case lists:reverse(Name) of 741 "colla_" ++ Rev -> 742 uppercase(lists:reverse(Rev)); 743 _ -> 744 uppercase(Name) 745 end. 746 747uppercase([C|Rest]) -> 748 [C-$a+$A|Rest]. 749 750calc_max(Type, Max) -> 751 bytes(Type, Max). 752 753bytes(runq, Max) -> 754 Upper = calc_max1(max_value(Max)), 755 {Max, Upper, "", Upper}; 756bytes(utilz, Max) -> 757 Upper = calc_max1(max_value(Max)), 758 {Max, Upper, "", Upper}; 759bytes(_, Max) -> 760 B = max_value(Max), 761 KB = B div 1024, 762 MB = KB div 1024, 763 GB = MB div 1024, 764 if 765 GB > 10 -> 766 Upper = calc_max1(GB), 767 {Max, Upper*1024*1024*1024, "(GB)", Upper}; 768 MB > 10 -> 769 Upper = calc_max1(MB), 770 {Max, Upper*1024*1024, "(MB)", Upper}; 771 KB > 0 -> 772 Upper = calc_max1(KB), 773 {Max, Upper*1024, "(KB)", Upper}; 774 true -> 775 Upper = calc_max1(B), 776 {Max, Upper, "(B)", Upper} 777 end. 778 779calc_max1(Max) when Max < 10 -> 780 10; 781calc_max1(Max) -> 782 case Max div 10 of 783 X when X < 10 -> 784 case Max rem 10 of 785 0 -> Max; 786 _ -> 787 (X+1)*10 788 end; 789 X -> 790 10*calc_max1(X) 791 end. 792 793colors() -> 794 {{240, 100, 100}, {0, 128, 0}, {25, 45, 170}, {255, 165, 0}, 795 {220, 220, 40}, {100, 240, 240},{240, 100, 240}, {160, 40, 40}, 796 {100, 100, 240}, {140, 140, 0}, {25, 200, 100}, {120, 25, 240}, 797 {255, 140, 163}, {25, 120, 120}, {120, 25, 120}, {110, 90, 60} 798 }. 799 800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 801%% wxDC and ?wxGC wrappers 802 803make_gc(Panel,UseGC) -> 804 DC = case os:type() of 805 {win32, _} -> 806 %% Ugly hack to avoid flickering on windows, works on windows only 807 %% But the other platforms are doublebuffered by default 808 DC0 = wx:typeCast(wxBufferedPaintDC:new(Panel), wxPaintDC), 809 wxDC:clear(DC0), 810 DC0; 811 _ -> 812 wxPaintDC:new(Panel) 813 end, 814 if UseGC -> {?wxGC:create(DC), DC}; 815 true -> {false, DC} 816 end. 817 818destroy_gc({GC, DC}) -> 819 (GC =/= false) andalso ?wxGC:destroy(GC), 820 case DC =/= false andalso wx:getObjectType(DC) of 821 false -> ok; 822 Type -> Type:destroy(DC) 823 end. 824 825haveGC() -> 826 try 827 wxGraphicsRenderer:getDefaultRenderer(), 828 true 829 catch _:_ -> false 830 end. 831 832getSize({_, DC}) -> 833 wxDC:getSize(DC). 834 835setPen({false, DC}, Pen) -> 836 wxDC:setPen(DC, Pen); 837setPen({GC, _}, Pen) -> 838 ?wxGC:setPen(GC, Pen). 839 840setFont({false, DC}, Font, Color) -> 841 wxDC:setTextForeground(DC, Color), 842 wxDC:setFont(DC, Font); 843setFont({GC, _}, Font, Color) -> 844 ?wxGC:setFont(GC, Font, Color). 845 846setBrush({false, DC}, Brush) -> 847 wxDC:setBrush(DC, Brush); 848setBrush({GC, _}, Brush) -> 849 ?wxGC:setBrush(GC, Brush). 850 851strokeLine({false, DC}, X0, Y0, X1, Y1) -> 852 wxDC:drawLine(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}); 853strokeLine({GC, _}, X0, Y0, X1, Y1) -> 854 ?wxGC:strokeLine(GC, X0, Y0, X1, Y1). 855 856strokeLines(_, [_]) -> ok; 857strokeLines({false, DC}, Lines) -> 858 wxDC:drawLines(DC, [{round(X), round(Y)} || {X,Y} <- Lines]); 859strokeLines({GC, _}, Lines) -> 860 ?wxGC:strokeLines(GC, Lines). 861 862drawRoundedRectangle({false, DC}, X0, Y0, X1, Y1, R) -> 863 wxDC:drawRoundedRectangle(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}, round(R)); 864drawRoundedRectangle({GC, _}, X0, Y0, X1, Y1, R) -> 865 ?wxGC:drawRoundedRectangle(GC, X0, Y0, X1, Y1, R). 866 867drawText({false, DC}, Str, X, Y) -> 868 wxDC:drawText(DC, Str, {round(X),round(Y)}); 869drawText({GC, _}, Str, X, Y) -> 870 ?wxGC:drawText(GC, Str, X, Y). 871 872getTextExtent({false, DC}, Str) -> 873 wxDC:getTextExtent(DC, Str); 874getTextExtent({GC, _}, Str) -> 875 {W,H,_,_} = ?wxGC:getTextExtent(GC, Str), 876 {W,H}. 877