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