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