1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2015-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_alloc_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-behaviour(wx_object). 28-include_lib("wx/include/wx.hrl"). 29-include("observer_defs.hrl"). 30 31-record(state, 32 { 33 time = #ti{}, 34 active = false, 35 parent, 36 wins, 37 mem, 38 samples, 39 max, 40 panel, 41 paint, 42 appmon, 43 async 44 }). 45 46-define(ID_REFRESH_INTERVAL, 102). 47 48-import(observer_perf_wx, 49 [make_win/4, setup_graph_drawing/1, refresh_panel/4, interval_dialog/2, 50 add_data/5, precalc/4]). 51 52start_link(Notebook, Parent, Config) -> 53 wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). 54 55init([Notebook, Parent, Config]) -> 56 try 57 TopP = wxPanel:new(Notebook), 58 Main = wxBoxSizer:new(?wxVERTICAL), 59 Panel = wxPanel:new(TopP), 60 GSzr = wxBoxSizer:new(?wxVERTICAL), 61 BorderFlags = ?wxLEFT bor ?wxRIGHT, 62 Carrier = make_win(alloc, Panel, GSzr, BorderFlags bor ?wxTOP), 63 Utilz = make_win(utilz, Panel, GSzr, BorderFlags), 64 wxWindow:setSizer(Panel, GSzr), 65 wxSizer:add(Main, Panel, [{flag, ?wxEXPAND},{proportion,2}]), 66 67 MemWin = create_mem_info(TopP), 68 wxSizer:add(Main, MemWin, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, 69 {proportion, 1}, {border, 5}]), 70 wxWindow:setSizer(TopP, Main), 71 Windows = [Carrier, Utilz], 72 PaintInfo = setup_graph_drawing(Windows), 73 {TopP, #state{parent= Parent, 74 panel = Panel, 75 wins = Windows, 76 mem = MemWin, 77 paint = PaintInfo, 78 time = setup_time(Config), 79 max = #{} 80 } 81 } 82 catch _:Err:Stacktrace -> 83 io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, Stacktrace]), 84 {stop, Err} 85 end. 86 87setup_time(Config) -> 88 Freq = maps:get(fetch, Config, 1), 89 #ti{disp=?DISP_FREQ/Freq, 90 fetch=Freq, 91 secs=maps:get(secs, Config, ?DISP_SECONDS)}. 92 93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 94handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}}, 95 #state{active=Active, panel=Panel, appmon=Old, wins=Wins0, time=#ti{fetch=F0} = Ti0} = State) -> 96 case interval_dialog(Panel, Ti0) of 97 Ti0 -> {noreply, State}; 98 #ti{fetch=F0} = Ti -> %% Same fetch interval force refresh 99 Wins = [W#win{max=undefined} || W <- Wins0], 100 {noreply, precalc(State#state{time=Ti, wins=Wins})}; 101 Ti when not Active -> 102 {noreply, State#state{time=Ti}}; 103 Ti -> %% Changed fetch interval, drop all data 104 {noreply, restart_fetcher(Old, State#state{time=Ti})} 105 end; 106handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, 107 State = #state{}) -> 108 {noreply, State}; 109 110handle_event(Event, _State) -> 111 error({unhandled_event, Event}). 112 113%%%%%%%%%% 114handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, 115 #state{active=Active, time=Ti, paint=Paint, 116 wins = Windows}) -> 117 %% Sigh workaround bug on MacOSX (Id in paint event is always 0) 118 Win = lists:keyfind(Panel, #win.panel, Windows), 119 refresh_panel(Active, Win, Ti, Paint), 120 ok. 121%%%%%%%%%% 122handle_call(get_config, _, #state{time=Ti}=State) -> 123 #ti{fetch=Fetch, secs=Range} = Ti, 124 {reply, #{fetch=>Fetch, secs=>Range}, State}; 125 126handle_call(Event, From, _State) -> 127 error({unhandled_call, Event, From}). 128 129handle_cast(Event, _State) -> 130 error({unhandled_cast, Event}). 131%%%%%%%%%% 132 133handle_info({Key, {promise_reply, {badrpc, _}}}, #state{async=Key} = State) -> 134 {noreply, State#state{active=false, appmon=undefined}}; 135 136handle_info({Key, {promise_reply, SysInfo}}, 137 #state{async=Key, samples=Data, max=Max0, 138 active=Active, wins=Wins0, time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) -> 139 Disp = trunc(Disp0), 140 Next = max(Tick - Disp, 0), 141 erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), 142 Info = alloc_info(SysInfo), 143 Max = lists:foldl(fun calc_max/2, Max0, Info), 144 {Wins, Samples} = add_data(Info, Data, Wins0, Ti, Active), 145 S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, max=Max, async=undefined}, 146 if Active -> 147 update_alloc(S0, Info, Max), 148 State = precalc(S1), 149 {noreply, State}; 150 true -> 151 {noreply, S1} 152 end; 153 154handle_info({refresh, Seq}, 155 State = #state{panel=Panel, appmon=Node, time=#ti{tick=Seq, disp=DispF}=Ti}) 156 when (Seq+1) < (DispF*1.5) -> 157 Next = Seq+1, 158 State#state.active andalso (catch wxWindow:refresh(Panel)), 159 erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), 160 if Seq =:= (trunc(DispF)-1) -> 161 Req = request_info(Node), 162 {noreply, State#state{time=Ti#ti{tick=Next}, async=Req}}; 163 true -> 164 {noreply, State#state{time=Ti#ti{tick=Next}}} 165 end; 166handle_info({refresh, _S}, #state{}=State) -> 167 {noreply, State}; 168 169handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) -> 170 create_menus(Parent, []), 171 try 172 Node = Old, 173 wxWindow:refresh(Panel), 174 {noreply, precalc(State#state{active=true})} 175 catch _:_ -> 176 {noreply, restart_fetcher(Node, State)} 177 end; 178 179handle_info(not_active, State = #state{appmon=_Pid}) -> 180 {noreply, State#state{active=false}}; 181 182handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) -> 183 {noreply, State#state{active=false, appmon=undefined}}; 184 185handle_info(_Event, State) -> 186 %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]), 187 {noreply, State}. 188 189terminate(_Event, #state{}) -> 190 ok. 191code_change(_, _, State) -> 192 State. 193 194%%%%%%%%%% 195 196request_info(Node) -> 197 ReplyTo = self(), 198 spawn(fun() -> 199 Res = rpc:call(Node, observer_backend, sys_info, []), 200 ReplyTo ! {self(), {promise_reply, Res}} 201 end). 202 203restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) -> 204 case rpc:call(Node, observer_backend, sys_info, []) of 205 {badrpc, _} -> State; 206 SysInfo -> 207 Info = alloc_info(SysInfo), 208 Max = lists:foldl(fun calc_max/2, #{}, Info), 209 {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true), 210 erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}), 211 wxWindow:refresh(Panel), 212 precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0}, 213 wins=Wins, samples=Samples, max=Max}) 214 end. 215 216precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) -> 217 Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0], 218 State#state{wins=Wins}. 219 220calc_max({Name, _, Cs}, Max0) -> 221 case maps:get(Name, Max0, 0) of 222 Value when Value < Cs -> 223 Max0#{Name=>Cs}; 224 _V -> 225 Max0 226 end. 227 228update_alloc(#state{mem=Grid}, Fields, Max) -> 229 wxWindow:freeze(Grid), 230 Last = wxListCtrl:getItemCount(Grid), 231 Update = fun({Name, BS, CS}, Row) -> 232 (Row >= Last) andalso wxListCtrl:insertItem(Grid, Row, ""), 233 MaxV = maps:get(Name, Max, CS), 234 wxListCtrl:setItem(Grid, Row, 0, observer_lib:to_str(Name)), 235 wxListCtrl:setItem(Grid, Row, 1, observer_lib:to_str(BS div 1024)), 236 wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)), 237 wxListCtrl:setItem(Grid, Row, 3, observer_lib:to_str(MaxV div 1024)), 238 Row + 1 239 end, 240 wx:foldl(Update, 0, Fields), 241 wxWindow:thaw(Grid), 242 Fields. 243 244alloc_info(SysInfo) -> 245 AllocInfo = proplists:get_value(alloc_info, SysInfo, []), 246 alloc_info(AllocInfo, [], 0, 0, true). 247 248alloc_info([{Type,Instances}|Allocators],TypeAcc,TotalBS,TotalCS,IncludeTotal) -> 249 {BS,CS,NewTotalBS,NewTotalCS,NewIncludeTotal} = 250 sum_alloc_instances(Instances,0,0,TotalBS,TotalCS), 251 alloc_info(Allocators,[{Type,BS,CS}|TypeAcc],NewTotalBS,NewTotalCS, 252 IncludeTotal andalso NewIncludeTotal); 253alloc_info([],TypeAcc,TotalBS,TotalCS,IncludeTotal) -> 254 Types = [X || X={_,BS,CS} <- TypeAcc, (BS>0 orelse CS>0)], 255 case IncludeTotal of 256 true -> 257 [{total,TotalBS,TotalCS} | lists:reverse(Types)]; 258 false -> 259 lists:reverse(Types) 260 end. 261 262sum_alloc_instances(false,BS,CS,TotalBS,TotalCS) -> 263 {BS,CS,TotalBS,TotalCS,false}; 264sum_alloc_instances([{_,_,Data}|Instances],BS,CS,TotalBS,TotalCS) -> 265 {NewBS,NewCS,NewTotalBS,NewTotalCS} = 266 sum_alloc_one_instance(Data,BS,CS,TotalBS,TotalCS), 267 sum_alloc_instances(Instances,NewBS,NewCS,NewTotalBS,NewTotalCS); 268sum_alloc_instances([],BS,CS,TotalBS,TotalCS) -> 269 {BS,CS,TotalBS,TotalCS,true}. 270 271sum_alloc_one_instance([{_,[{blocks,TypedBlocks},{carriers_size,CS,_,_}]}| 272 Rest],OldBS,OldCS,TotalBS,TotalCS) -> 273 %% OTP 23 and later. 274 BS = sum_alloc_block_list(TypedBlocks, 0), 275 sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS); 276sum_alloc_one_instance([{_,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}| 277 Rest],OldBS,OldCS,TotalBS,TotalCS) -> 278 %% OTP 22 and earlier. 279 sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS); 280sum_alloc_one_instance([_|Rest],BS,CS,TotalBS,TotalCS) -> 281 sum_alloc_one_instance(Rest,BS,CS,TotalBS,TotalCS); 282sum_alloc_one_instance([],BS,CS,TotalBS,TotalCS) -> 283 {BS,CS,TotalBS,TotalCS}. 284 285sum_alloc_block_list([{_Type, [{size, Current, _, _}]} | Rest], Acc) -> 286 %% We ignore the type since we're returning a summary of all blocks in the 287 %% carriers employed by a certain instance. 288 sum_alloc_block_list(Rest, Current + Acc); 289sum_alloc_block_list([{_Type, [{size, Current}]} | Rest], Acc) -> 290 sum_alloc_block_list(Rest, Current + Acc); 291sum_alloc_block_list([_ | Rest], Acc) -> 292 sum_alloc_block_list(Rest, Acc); 293sum_alloc_block_list([], Acc) -> 294 Acc. 295 296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 297 298create_mem_info(Parent) -> 299 Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES, 300 Grid = wxListCtrl:new(Parent, [{style, Style}]), 301 302 Li = wxListItem:new(), 303 Scale = observer_wx:get_scale(), 304 AddListEntry = fun({Name, Align, DefSize}, Col) -> 305 wxListItem:setText(Li, Name), 306 wxListItem:setAlign(Li, Align), 307 wxListCtrl:insertColumn(Grid, Col, Li), 308 wxListCtrl:setColumnWidth(Grid, Col, DefSize*Scale), 309 Col + 1 310 end, 311 ListItems = [{"Allocator Type", ?wxLIST_FORMAT_LEFT, 200}, 312 {"Block size (kB)", ?wxLIST_FORMAT_RIGHT, 150}, 313 {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}, 314 {"Max Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150} 315 ], 316 lists:foldl(AddListEntry, 0, ListItems), 317 wxListItem:destroy(Li), 318 319 Grid. 320 321create_menus(Parent, _) -> 322 View = {"View", [#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Graph Settings"}]}, 323 observer_wx:create_menus(Parent, [{"File", []}, View]). 324 325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 326