1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2016. 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%%
20
21%%
22%%----------------------------------------------------------------------
23%% megaco_profile: Utility module used for megaco profiling
24%%----------------------------------------------------------------------
25
26-module(megaco_profile).
27
28-export([profile/2, prepare/2, analyse/1,
29         fprof_to_calltree/1, fprof_to_calltree/2, fprof_to_calltree/3]).
30
31%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
32
33%% Execute Fun and profile it with fprof.
34profile(Slogan, Fun) when is_function(Fun, 0) ->
35    Pids = [self()],
36    {ok, TraceFile} = prepare(Slogan, Pids),
37    Res = (catch Fun()),
38    {ok, _DestFile} = analyse(Slogan),
39    ok = file:delete(TraceFile),
40    {ok, _TreeFile} = fprof_to_calltree(Slogan),
41    Res.
42
43%% Prepare for tracing
44prepare(Slogan, Pids) ->
45    TraceFile = lists:concat(["profile_", Slogan, "-fprof.trace"]),
46    {ok, _Pid} = fprof:start(),
47    erlang:garbage_collect(),
48    TraceOpts = [start,
49		 {cpu_time, false},
50		 {procs, Pids},
51		 {file, TraceFile}
52		],
53    ok = fprof:trace(TraceOpts),
54    {ok, TraceFile}.
55
56%% Stop tracing and analyse it
57analyse(Slogan) ->
58    fprof:trace(stop),
59    TraceFile = lists:concat(["profile_", Slogan, "-fprof.trace"]),
60    DestFile = lists:concat(["profile_", Slogan, ".fprof"]),
61    try
62        case fprof:profile([{file, TraceFile}]) of
63            ok ->
64                ok = fprof:analyse([{dest, DestFile}, {totals, false}]),
65                {ok, DestFile};
66            {error, Reason} ->
67                {error, Reason}
68        end
69    after
70         fprof:stop()
71    end.
72
73fprof_to_calltree(Slogan) ->
74    fprof_to_calltree(Slogan, 0).
75
76fprof_to_calltree(Slogan, MinPercent) ->
77    DestFile = lists:concat(["profile_", Slogan, ".fprof"]),
78    TreeFile = lists:concat(["profile_", Slogan, ".calltree"]),
79    fprof_to_calltree(DestFile, TreeFile, MinPercent).
80
81%% Create a calltree from an fprof file
82fprof_to_calltree(FromFile, ToFile, MinPercent) ->
83    ReplyTo = self(),
84    Ref = make_ref(),
85    spawn_link(fun() ->
86                       ReplyTo ! {Ref, do_fprof_to_calltree(FromFile, ToFile, MinPercent)}
87               end),
88    wait_for_reply(Ref).
89
90wait_for_reply(Ref) ->
91    receive
92        {Ref, Res} ->
93            Res;
94        {'EXIT', normal} ->
95            wait_for_reply(Ref);
96        {'EXIT', Reason} ->
97            exit(Reason)
98    end.
99
100do_fprof_to_calltree(FromFile, ToFile, MinPercent) ->
101    {ok, Fd} = file:open(ToFile, [write, raw]),
102
103    {ok, ConsultedFromFile} = file:consult(FromFile),
104    [_AnalysisOpts, [_Totals] | Terms] = ConsultedFromFile,
105    Processes = split_processes(Terms, [], []),
106    Indent = "",
107    Summary = collapse_processes(Processes),
108    {_Label, _Cnt, Acc, _Own, _Roots, Details} = Summary,
109    %% log(Fd, Label, Indent, Acc, {Label, Cnt, Acc, Own}, [], 0),
110    gen_calltree(Fd, Indent, Acc, Summary, MinPercent),
111    Delim = io_lib:format("\n~80..=c\n\n", [$=]),
112    Write =
113        fun(P) ->
114                file:write(Fd, Delim),
115                gen_calltree(Fd, Indent, Acc, P, MinPercent)
116        end,
117    lists:foreach(Write, Processes),
118    file:write(Fd, Delim),
119    gen_details(Fd, Acc, Details),
120    file:close(Fd),
121    {ok, ToFile}.
122
123%% Split all fprof terms into a list of processes
124split_processes([H | T], ProcAcc, TotalAcc) ->
125    if
126	is_tuple(H) ->
127	    split_processes(T, [H | ProcAcc], TotalAcc);
128	is_list(H), ProcAcc =:= [] ->
129	    split_processes(T, [H], TotalAcc);
130	is_list(H) ->
131            ProcAcc2 = rearrange_process(lists:reverse(ProcAcc)),
132	    split_processes(T, [H], [ProcAcc2 | TotalAcc])
133    end;
134split_processes([], [], TotalAcc) ->
135    lists:reverse(lists:keysort(3, TotalAcc));
136split_processes([], ProcAcc, TotalAcc) ->
137    ProcAcc2 = rearrange_process(lists:reverse(ProcAcc)),
138    lists:reverse(lists:keysort(3, [ProcAcc2 | TotalAcc])).
139
140%% Rearrange the raw process list into a more useful format
141rearrange_process([[{Label, _Cnt, _Acc, _Own} | _ ] | Details]) ->
142    do_rearrange_process(Details, Details, Label, [], []).
143
144do_rearrange_process([{CalledBy, Current, _Calls} | T], Orig, Label, Roots, Undefs) ->
145    case  [{undefined, Cnt, safe_max(Acc, Own), Own} ||
146              {undefined, Cnt, Acc, Own} <- CalledBy] of
147        [] ->
148            do_rearrange_process(T, Orig, Label, Roots, Undefs);
149        NewUndefs ->
150            do_rearrange_process(T, Orig, Label, [Current | Roots], NewUndefs ++ Undefs)
151    end;
152do_rearrange_process([], Details, Label, Roots, Undefs) ->
153    [{undefined, Cnt, Acc, Own}] = collapse_calls(Undefs, []),
154    Details2 = sort_details(3, Details),
155    {Label, Cnt, Acc, Own, lists:reverse(lists:keysort(3, Roots)), Details2}.
156
157%% Compute a summary of the rearranged process info
158collapse_processes(Processes) ->
159    Headers = lists:map(fun({_L, C, A, O, _R, _D}) -> {"SUMMARY", C, A, O} end,
160                        Processes),
161    [{Label, Cnt, Acc, Own}] = collapse_calls(Headers, []),
162    Details = lists:flatmap(fun({_L, _C, _A, _O, _R, D}) -> D end, Processes),
163    Details2 = do_collapse_processes(sort_details(1, Details), []),
164    Roots = lists:flatmap(fun({_L, _C, _A, _O, R, _D}) -> R end, Processes),
165    RootMFAs = lists:usort([MFA || {MFA, _, _, _} <- Roots]),
166    Roots2 = [R || RootMFA <- RootMFAs,
167                   {_, {MFA, _, _, _} = R, _} <- Details2,
168                   MFA =:= RootMFA],
169    Roots3 = collapse_calls(Roots2, []),
170    {Label, Cnt, Acc, Own, Roots3, Details2}.
171
172do_collapse_processes([{CalledBy1, {MFA, Cnt1, Acc1, Own1}, Calls1} | T1],
173                      [{CalledBy2, {MFA, Cnt2, Acc2, Own2}, Calls2} | T2]) ->
174    Cnt       = Cnt1 + Cnt2,
175    Acc       = Acc1 + Acc2,
176    Own       = Own1 + Own2,
177    Current   = {MFA, Cnt, Acc, Own},
178    CalledBy0 = CalledBy1 ++ CalledBy2,
179    Calls0    = Calls1 ++ Calls2,
180    CalledBy  = collapse_calls(lists:keysort(3, CalledBy0), []),
181    Calls     = collapse_calls(lists:keysort(3, Calls0), []),
182    do_collapse_processes(T1, [{CalledBy, Current, Calls} | T2]);
183do_collapse_processes([{CalledBy, Current, Calls} | T1],
184                      T2) ->
185    do_collapse_processes(T1, [{CalledBy, Current, Calls} | T2]);
186do_collapse_processes([],
187                      T2) ->
188    sort_details(3, T2).
189
190%% Reverse sort on acc field
191sort_details(Pos, Details) ->
192    Pivot = fun({_CalledBy1, Current1, _Calls1},
193                {_CalledBy2, Current2, _Calls2}) ->
194                    element(Pos, Current1) =< element(Pos, Current2)
195            end,
196    lists:reverse(lists:sort(Pivot, Details)).
197
198%% Compute a summary from a list of call tuples
199collapse_calls([{MFA, Cnt1, Acc1, Own1} | T1],
200               [{MFA, Cnt2, Acc2, Own2} | T2]) ->
201    Cnt = Cnt1 + Cnt2,
202    Acc = safe_sum(Acc1, Acc2),
203    Own = Own1 + Own2,
204    collapse_calls(T1, [{MFA, Cnt, Acc, Own} | T2]);
205collapse_calls([{MFA, Cnt, Acc, Own} | T1],
206               T2) ->
207    collapse_calls(T1, [{MFA, Cnt, Acc, Own} | T2]);
208collapse_calls([],
209              T2) ->
210    lists:reverse(lists:keysort(3, T2)).
211
212safe_sum(Int1, Int2) ->
213    if
214        Int1 =:= undefined -> Int2;
215        Int2 =:= undefined -> Int1;
216        true               -> Int1 + Int2
217    end.
218
219safe_max(Int1, Int2) ->
220    if
221        Int1 =:= undefined ->
222            io:format("111\n", []),
223            Int2;
224        Int2 =:= undefined ->
225            io:format("222\n", []),
226            Int1;
227        Int2 > Int1        -> Int2;
228        true               -> Int1
229    end.
230
231%% Compute a calltree and write it to file
232gen_calltree(Fd, Indent, TotalAcc, {Label, Cnt, Acc, Own, Roots, Details}, MinPercent) ->
233    Header =  {Label, Cnt, Acc, Own},
234    MetaLabel = "Process",
235    Diff = length(Label) - length(MetaLabel),
236    IoList = io_lib:format("~s~s Lvl  Pct        Cnt        Acc        Own Calls   => MFA\n",
237                           [MetaLabel, lists:duplicate(Diff, $\ )]),
238    file:write(Fd, IoList),
239    log(Fd, Label, Indent, TotalAcc, Header, Roots, MinPercent),
240    NewIndent = "  " ++ Indent,
241    Fun = fun({MFA, _C, _A, _O}) ->
242                  [put_detail(Label, D) || D <- Details],
243                  gen_calls(Fd, Label, NewIndent, TotalAcc, MFA, MinPercent)
244          end,
245    lists:foreach(Fun, Roots).
246
247gen_calls(Fd, Label, Indent, TotalAcc, MFA, MinPercent) ->
248    case get_detail(Label, MFA) of
249        {read, {_CalledBy,  Current, _Calls}} ->
250            log(Fd, Label, Indent, TotalAcc, Current, -1, MinPercent);
251        {unread, {_CalledBy, Current, Calls}} ->
252            log(Fd, Label, Indent, TotalAcc, Current, Calls, MinPercent),
253            NewIndent = "  " ++ Indent,
254            Fun = fun({NextMFA, _, _, _}) ->
255                          gen_calls(Fd, Label, NewIndent, TotalAcc,
256                                    NextMFA, MinPercent)
257                  end,
258            lists:foreach(Fun, Calls)
259    end.
260
261put_detail(Label, {_, {MFA, _, _, _}, _} = Detail) ->
262    put({Label, MFA}, {unread, Detail}).
263
264get_detail(Label, MFA) ->
265    Val = get({Label, MFA}),
266    case Val of
267        {unread, Detail} ->
268            put({Label, MFA}, {read, Detail}),
269            Val;
270        {read, _Detail} ->
271            Val
272    end.
273
274gen_details(Fd, Total, Details) ->
275    IoList = io_lib:format("Pct         Cnt        Acc        Own    MFA\n", []),
276    file:write(Fd, IoList),
277    do_gen_details(Fd, Total, Details).
278
279do_gen_details(Fd, Total, [{_CalledBy, {MFA, Cnt, Acc, Own}, _Calls} | Details]) ->
280    MFAStr = io_lib:format("~p", [MFA]),
281    {_, Percent} = calc_percent(Acc, Own, Total),
282    IoList = io_lib:format("~3.. B% ~10.3B ~10.3f ~10.3f => ~s\n",
283                           [Percent, Cnt, Acc, Own, MFAStr]),
284    file:write(Fd, IoList),
285    do_gen_details(Fd, Total, Details);
286do_gen_details(_Fd, _Total, []) ->
287    ok.
288
289log(Fd, Label, Indent, Acc, Current, Calls, MinPercent) when is_list(Calls) ->
290    log(Fd, Label, Indent, Acc, Current, length(Calls), MinPercent);
291log(Fd, Label, Indent, Total, {MFA, Cnt, Acc, Own}, N, MinPercent) ->
292    {Max, Percent} = calc_percent(Acc, Own, Total),
293    if
294        Percent >= MinPercent ->
295            do_log(Fd, Label, Indent, Percent, MFA, Cnt, Max, Own, N);
296        true ->
297            ok
298    end.
299
300do_log(Fd, Label, Indent, Percent, MFA, Cnt, Acc, Own, N) ->
301    MFAStr = io_lib:format("~p", [MFA]),
302    CallsStr = io_lib:format(" ~5.. s ", [lists:concat([N])]),
303    IoList = io_lib:format("~s ~3.. B "
304                           "~s~3.. B% ~10.. B ~10.. B ~10.. B ~s => ~s\n",
305                           [Label, length(Indent) div 2,
306                            Indent, Percent, Cnt,
307                            round(Acc), round(Own), CallsStr, MFAStr]),
308    file:write(Fd, IoList).
309
310calc_percent(Acc, Own, Total) ->
311    Max = safe_max(Acc, Own),
312    {Max, round((Max * 100) / Total)}.
313