24
24
-behaviour(gen_server).
26
-export([start/0, stop/0, dump/0, total_analyse/0,
27
start_profiling/1, profile/2, profile/4, profile/1,
28
stop_profiling/0, analyse/0, log/1]).
29
start_profiling/1, start_profiling/2,
30
profile/1, profile/2, profile/3, profile/4, profile/5,
32
analyze/0, analyze/1, analyze/2,
30
35
%% Internal exports
39
-include_lib("stdlib/include/qlc.hrl").
41
-import(lists, [flatten/1,reverse/1,keysort/2]).
44
-record(state, {table = notable,
55
start() -> gen_server:start({local, eprof}, eprof, [], []).
56
stop() -> gen_server:call(eprof, stop, infinity).
43
n = 0, % number of total calls
44
us = 0, % sum of uS for all calls
45
p = gb_trees:empty(), % tree of {Pid, {Mfa, {Count, Us}}}
46
mfa = [] % list of {Mfa, {Count, Us}}
51
pattern = {'_','_','_'},
61
%% -------------------------------------------------------------------- %%
65
%% -------------------------------------------------------------------- %%
67
start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).
68
stop() -> gen_server:call(?MODULE, stop, infinity).
70
profile(Fun) when is_function(Fun) ->
72
profile(Rs) when is_list(Rs) ->
59
75
profile(Pids, Fun) ->
61
gen_server:call(eprof, {profile,Pids,erlang,apply,[Fun,[]]},infinity).
76
profile(Pids, Fun, {'_','_','_'}).
78
profile(Pids, Fun, Pattern) ->
79
profile(Pids, erlang, apply, [Fun,[]], Pattern).
63
81
profile(Pids, M, F, A) ->
82
profile(Pids, M, F, A, {'_','_','_'}).
84
profile(Pids, M, F, A, Pattern) ->
65
gen_server:call(eprof, {profile,Pids,M,F,A},infinity).
86
gen_server:call(?MODULE, {profile,Pids,Pattern,M,F,A},infinity).
68
gen_server:call(eprof, dump, infinity).
71
gen_server:call(eprof, analyse, infinity).
89
gen_server:call(?MODULE, dump, infinity).
94
analyze(Type) when is_atom(Type) ->
96
analyze(Opts) when is_list(Opts) ->
98
analyze(Type, Opts) when is_list(Opts) ->
99
gen_server:call(?MODULE, {analyze, Type, Opts}, infinity).
74
gen_server:call(eprof, {logfile, File}, infinity).
77
gen_server:call(eprof, total_analyse, infinity).
102
gen_server:call(?MODULE, {logfile, File}, infinity).
79
104
start_profiling(Rootset) ->
105
start_profiling(Rootset, {'_','_','_'}).
106
start_profiling(Rootset, Pattern) ->
81
gen_server:call(eprof, {profile, Rootset}, infinity).
108
gen_server:call(?MODULE, {profile, Rootset, Pattern}, infinity).
83
110
stop_profiling() ->
84
gen_server:call(eprof, stop_profiling, infinity).
111
gen_server:call(?MODULE, stop_profiling, infinity).
114
%% -------------------------------------------------------------------- %%
118
%% -------------------------------------------------------------------- %%
92
121
process_flag(trap_exit, true),
93
process_flag(priority, max),
94
put(three_one, {3,1}), %To avoid building garbage.
97
subtr({X1,Y1,Z1}, {X1,Y1,Z2}) ->
99
subtr({X1,Y1,Z1}, {X2,Y2,Z2}) ->
100
(((X1-X2) * 1000000) + Y1 - Y2) * 1000000 + Z1 - Z2.
102
update_call_statistics(Tab, Key, Time) ->
103
try ets:update_counter(Tab, Key, Time) of
104
NewTime when is_integer(NewTime) ->
105
ets:update_counter(Tab, Key, get(three_one))
108
ets:insert(Tab, {Key,Time,1})
111
update_other_statistics(Tab, Key, Time) ->
113
ets:update_counter(Tab, Key, Time)
116
ets:insert(Tab, {Key,Time,0})
119
do_messages({trace_ts,From,Op,Mfa,Time}, Tab, undefined,_PrevOp0,_PrevTime0) ->
120
PrevFunc = [From|Mfa],
122
{trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time)
126
do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, call, PrevTime0) ->
127
update_call_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)),
128
PrevFunc = case Op of
134
{trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time)
138
do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, _PrevOp0, PrevTime0) ->
139
update_other_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)),
140
PrevFunc = case Op of
146
{trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time)
153
handle_cast(_Req, S) -> {noreply, S}.
155
terminate(_Reason,_S) ->
156
call_trace_for_all(false),
161
handle_call({logfile, F}, _FromTag, Status) ->
162
case file:open(F, [write]) of
124
%% -------------------------------------------------------------------- %%
128
%% -------------------------------------------------------------------- %%
132
handle_call({analyze, _, _}, _, #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0} = Bpd } = S) when is_record(Bpd, bpd) ->
133
{reply, nothing_to_analyze, S};
135
handle_call({analyze, procs, Opts}, _, #state{ bpd = #bpd{ p = Ps, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) ->
138
{Pn, Pus} = sum_bp_total_n_us(Mfas),
139
format(Fd, "~n****** Process ~w -- ~s % of profiled time *** ~n", [Pid, s("~.2f", [100.0*divide(Pus,Tus)])]),
140
print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts),
142
end, gb_trees:to_list(Ps)),
145
handle_call({analyze, total, Opts}, _, #state{ bpd = #bpd{ mfa = Mfas, n = Tn, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) ->
146
print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts),
149
handle_call({analyze, Type, _Opts}, _, S) ->
150
{reply, {error, {undefined, Type}}, S};
154
handle_call({profile, _Rootset, _Pattern, _M,_F,_A}, _From, #state{ profiling = true } = S) ->
155
{reply, {error, already_profiling}, S};
157
handle_call({profile, Rootset, Pattern, M,F,A}, From, #state{fd = Fd } = S) ->
159
set_pattern_trace(false, S#state.pattern),
160
set_process_trace(false, S#state.rootset),
162
Pid = setup_profiling(M,F,A),
163
case set_process_trace(true, [Pid|Rootset]) of
165
set_pattern_trace(true, Pattern),
167
execute_profiling(Pid),
170
rootset = [Pid|Rootset],
177
exit(Pid, eprof_kill),
178
{reply, error, #state{ fd = Fd}}
181
handle_call({profile, _Rootset, _Pattern}, _From, #state{ profiling = true } = S) ->
182
{reply, {error, already_profiling}, S};
184
handle_call({profile, Rootset, Pattern}, From, #state{ fd = Fd } = S) ->
186
set_pattern_trace(false, S#state.pattern),
187
set_process_trace(false, S#state.rootset),
189
case set_process_trace(true, Rootset) of
192
set_pattern_trace(true, Pattern),
193
{reply, profiling, #state{
202
{reply, error, #state{ fd = Fd }}
205
handle_call(stop_profiling, _From, #state{ profiling = false } = S) ->
206
{reply, profiling_already_stopped, S};
208
handle_call(stop_profiling, _From, #state{ profiling = true } = S) ->
210
set_pattern_trace(pause, S#state.pattern),
214
set_process_trace(false, S#state.rootset),
215
set_pattern_trace(false, S#state.pattern),
217
{reply, profiling_stopped, S#state{
220
pattern = {'_','_','_'},
225
handle_call({logfile, File}, _From, #state{ fd = OldFd } = S) ->
226
case file:open(File, [write]) of
166
FdOld -> file:close(FdOld)
230
OldFd -> file:close(OldFd)
171
{reply, error, Status}
174
handle_call({profile, Rootset}, {From, _Tag}, S) ->
176
maybe_delete(S#state.table),
177
io:format("eprof: Starting profiling ..... ~n",[]),
178
ptrac(S#state.rootset, false, all()),
180
Tab = ets:new(eprof, [set, public]),
181
case ptrac(Rootset, true, all()) of
183
{reply, error, #state{}};
186
call_trace_for_all(true),
188
{reply, profiling, #state{table = Tab,
194
handle_call(stop_profiling, _FromTag, S) when S#state.profiling ->
195
ptrac(S#state.rootset, false, all()),
196
call_trace_for_all(false),
198
io:format("eprof: Stop profiling~n",[]),
199
ets:delete(S#state.table, nofunc),
200
{reply, profiling_stopped, S#state{profiling = false}};
202
handle_call(stop_profiling, _FromTag, S) ->
203
{reply, profiling_already_stopped, S};
205
handle_call({profile, Rootset, M, F, A}, FromTag, S) ->
206
io:format("eprof: Starting profiling..... ~n", []),
207
maybe_delete(S#state.table),
208
ptrac(S#state.rootset, false, all()),
210
put(replyto, FromTag),
211
Tab = ets:new(eprof, [set, public]),
212
P = spawn_link(eprof, call, [self(), M, F, A]),
213
case ptrac([P|Rootset], true, all()) of
216
call_trace_for_all(true),
218
{noreply, #state{table = Tab,
220
rootset = [P|Rootset]}};
224
{reply, error, #state{}}
227
handle_call(dump, _FromTag, S) ->
228
{reply, dump(S#state.table), S};
230
handle_call(analyse, _FromTag, S) ->
231
{reply, analyse(S), S};
233
handle_call(total_analyse, _FromTag, S) ->
234
{reply, total_analyse(S), S};
232
{reply, ok, S#state{ fd = Fd}};
237
handle_call(dump, _From, #state{ bpd = Bpd } = S) when is_record(Bpd, bpd) ->
238
{reply, gb_trees:to_list(Bpd#bpd.p), S};
236
240
handle_call(stop, _FromTag, S) ->
238
241
{stop, normal, stopped, S}.
242
handle_info({trace_ts,_From,_Op,_Func,_Time}=M, S0) when S0#state.profiling ->
243
Start = erlang:now(),
244
#state{table=Tab,pop=PrevOp0,ptime=PrevTime0,pfunc=PrevFunc0,
245
overhead=Overhead0} = S0,
246
{PrevFunc,PrevOp,PrevTime} = do_messages(M, Tab, PrevFunc0, PrevOp0, PrevTime0),
247
Overhead = Overhead0 + subtr(erlang:now(), Start),
248
S = S0#state{overhead=Overhead,pfunc=PrevFunc,pop=PrevOp,ptime=PrevTime},
251
handle_info({trace_ts, From, _, _, _}, S) when not S#state.profiling ->
252
ptrac([From], false, all()),
255
handle_info({_P, {answer, A}}, S) ->
256
ptrac(S#state.rootset, false, all()),
257
io:format("eprof: Stop profiling~n",[]),
258
{From,_Tag} = get(replyto),
243
%% -------------------------------------------------------------------- %%
247
%% -------------------------------------------------------------------- %%
249
handle_cast(_Msg, State) ->
252
%% -------------------------------------------------------------------- %%
256
%% -------------------------------------------------------------------- %%
258
handle_info({'EXIT', _, normal}, S) ->
260
handle_info({'EXIT', _, eprof_kill}, S) ->
262
handle_info({'EXIT', _, Reason}, #state{ reply = FromTag } = S) ->
264
set_process_trace(false, S#state.rootset),
265
set_pattern_trace(false, S#state.pattern),
267
gen_server:reply(FromTag, {error, Reason}),
271
pattern = {'_','_','_'}
274
% check if Pid is spawned process?
275
handle_info({_Pid, {answer, Result}}, #state{ reply = {From,_} = FromTag} = S) ->
277
set_pattern_trace(pause, S#state.pattern),
281
set_process_trace(false, S#state.rootset),
282
set_pattern_trace(false, S#state.pattern),
259
284
catch unlink(From),
260
ets:delete(S#state.table, nofunc),
261
gen_server:reply(erase(replyto), {ok, A}),
263
{noreply, S#state{profiling = false,
266
handle_info({'EXIT', P, Reason},
267
#state{profiling=true,proc=P,table=T,rootset=RootSet}) ->
269
ptrac(RootSet, false, all()),
271
io:format("eprof: Profiling failed\n",[]),
272
case erase(replyto) of
285
gen_server:reply(FromTag, {ok, Result}),
289
pattern = {'_','_','_'},
293
%% -------------------------------------------------------------------- %%
297
%% -------------------------------------------------------------------- %%
299
terminate(_Reason, #state{ fd = undefined }) ->
300
set_pattern_trace(false, {'_','_','_'}),
302
terminate(_Reason, #state{ fd = Fd }) ->
304
set_pattern_trace(false, {'_','_','_'}),
307
%% -------------------------------------------------------------------- %%
311
%% -------------------------------------------------------------------- %%
313
code_change(_OldVsn, State, _Extra) ->
317
%% -------------------------------------------------------------------- %%
321
%% -------------------------------------------------------------------- %%
323
setup_profiling(M,F,A) ->
324
spawn_link(fun() -> spin_profile(M,F,A) end).
326
spin_profile(M, F, A) ->
329
Pid ! {self(), {answer, erlang:apply(M,F,A)}}
332
execute_profiling(Pid) ->
333
Pid ! {self(), execute}.
335
set_pattern_trace(Flag, Pattern) ->
336
erlang:system_flag(multi_scheduling, block),
337
erlang:trace_pattern(on_load, Flag, [call_time]),
338
erlang:trace_pattern(Pattern, Flag, [call_time]),
339
erlang:system_flag(multi_scheduling, unblock),
342
set_process_trace(Flag, Pids) ->
343
% do we need procs for meta info?
345
set_process_trace(Flag, Pids, [call, set_on_spawn]).
346
set_process_trace(_, [], _) -> true;
347
set_process_trace(Flag, [Pid|Pids], Options) when is_pid(Pid) ->
349
erlang:trace(Pid, Flag, Options),
350
set_process_trace(Flag, Pids, Options)
355
set_process_trace(Flag, [Name|Pids], Options) when is_atom(Name) ->
356
case whereis(Name) of
276
gen_server:reply(FromTag, {error, Reason}),
280
handle_info({'EXIT',_P,_Reason}, S) ->
284
erlang:system_flag(multi_scheduling, block).
287
erlang:system_flag(multi_scheduling, unblock).
291
call(Top, M, F, A) ->
294
Top ! {self(), {answer, apply(M,F,A)}}
297
call_trace_for_all(Flag) ->
298
erlang:trace_pattern(on_load, Flag, [local]),
299
erlang:trace_pattern({'_','_','_'}, Flag, [local]).
301
ptrac([P|T], How, Flags) when is_pid(P) ->
302
case dotrace(P, How, Flags) of
304
ptrac(T, How, Flags);
311
ptrac([P|T], How, Flags) when is_atom(P) ->
313
undefined when How ->
315
undefined when not How ->
316
ptrac(T, How, Flags);
358
set_process_trace(Flag, Pids, Options);
318
ptrac([Pid|T], How, Flags)
321
ptrac([H|_],_How,_Flags) ->
322
io:format("** eprof bad process ~w~n",[H]),
325
ptrac([],_,_) -> true.
327
dotrace(P, How, What) ->
328
case (catch erlang:trace(P, How, What)) of
331
_Other when not How ->
334
io:format("** eprof: bad process: ~p,~p,~p~n", [P,How,What]),
338
all() -> [call,arity,return_to,running,timestamp,set_on_spawn].
340
total_analyse(#state{table=notable}) ->
343
#state{table = T, overhead = Overhead} = S,
344
QH = qlc:q([{{From,Mfa},Time,Count} ||
345
{[From|Mfa],Time,Count} <- ets:table(T)]),
346
Pcalls = reverse(keysort(2, replicas(qlc:eval(QH)))),
347
Time = collect_times(Pcalls),
348
format("FUNCTION~44s TIME ~n", ["CALLS"]),
349
printit(Pcalls, Time),
350
format("\nTotal time: ~.2f\n", [Time / 1000000]),
351
format("Measurement overhead: ~.2f\n", [Overhead / 1000000]).
353
analyse(#state{table=notable}) ->
356
#state{table = T, overhead = Overhead} = S,
357
Pids = ordsets:from_list(flatten(ets:match(T, {['$1'|'_'],'_', '_'}))),
358
Times = sum(ets:match(T, {'_','$1', '_'})),
359
format("FUNCTION~44s TIME ~n", ["CALLS"]),
360
do_pids(Pids, T, 0, Times),
361
format("\nTotal time: ~.2f\n", [Times / 1000000]),
362
format("Measurement overhead: ~.2f\n", [Overhead / 1000000]).
364
do_pids([Pid|Tail], T, AckTime, Total) ->
366
reverse(keysort(2, to_tups(ets:match(T, {[Pid|'$1'], '$2','$3'})))),
367
Time = collect_times(Pcalls),
368
PercentTotal = 100 * (divide(Time, Total)),
369
format("~n****** Process ~w -- ~s % of profiled time *** ~n",
370
[Pid, fpf(PercentTotal)]),
371
printit(Pcalls, Time),
372
do_pids(Tail, T, AckTime + Time, Total);
373
do_pids([], _, _, _) ->
377
printit([{{Mod,Fun,Arity}, Time, Calls} |Tail], ProcTime) ->
378
format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls),
379
fpf(100*(divide(Time,ProcTime)))]),
380
printit(Tail, ProcTime);
381
printit([{{_,{Mod,Fun,Arity}}, Time, Calls} |Tail], ProcTime) ->
382
format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls),
383
fpf(100*(divide(Time,ProcTime)))]),
384
printit(Tail, ProcTime);
385
printit([_|T], Time) ->
389
pad(flatten(io_lib:format("~w:~w/~w", [Mod,Fun, Arity])),45).
392
Strlen = length(Str),
394
Strlen > Len -> strip_tail(Str, 45);
395
true -> lists:append(Str, mklist(Len-Strlen))
398
strip_tail([_|_], 0) ->[];
399
strip_tail([H|T], I) -> [H|strip_tail(T, I-1)];
400
strip_tail([],_I) -> [].
402
fpf(F) -> strip_tail(flatten(io_lib:format("~w", [round(F)])), 5).
403
fint(Int) -> pad(flatten(io_lib:format("~w",[Int])), 10).
406
mklist(I) -> [$ |mklist(I-1)].
408
to_tups(L) -> lists:map(fun(List) -> erlang:list_to_tuple(List) end, L).
410
divide(X,Y) -> X / Y.
412
collect_times([]) -> 0;
413
collect_times([Tup|Tail]) -> element(2, Tup) + collect_times(Tail).
420
format("~p~n", [H]), format(T);
427
Fd -> io:format(Fd, F,A)
433
sum([[H]|T]) -> H + sum(T);
439
replicas([{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Tail], Result) ->
440
case search({Mod,Fun,Arity},Result) of
442
replicas(Tail, [{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Result]);
444
Result2 = del({Mod,Fun,Arity}, Result),
445
replicas(Tail, [{{Pid, {Mod,Fun,Arity}},
446
Ack+Ack2,Calls+Calls2} |Result2])
449
replicas([_|T], Ack) -> %% Whimpy
452
replicas([], Res) -> Res.
454
search(Key, [{{_,Key}, Ack, Calls}|_]) ->
456
search(Key, [_|T]) ->
458
search(_Key,[]) -> false.
460
del(Key, [{{_,Key},_Ack,_Calls}|T]) ->
462
del(Key, [H | Tail]) ->
468
{trace_ts, From, _, _, _} when is_pid(From) ->
469
ptrac([From], false, all()),
477
code_change(_OldVsn, State, _Extra) ->
360
set_process_trace(Flag, [Pid|Pids], Options)
364
collect_bpd([M || M <- [element(1, Mi) || Mi <- code:all_loaded()], M =/= ?MODULE]).
366
collect_bpd(Ms) when is_list(Ms) ->
367
collect_bpdf(collect_mfas(Ms) ++ erlang:system_info(snifs)).
372
Mfas ++ [{M, F, A} || {F, A} <- M:module_info(functions)]
375
collect_bpdf(Mfas) ->
376
collect_bpdf(Mfas, #bpd{}).
377
collect_bpdf([], Bpd) ->
379
collect_bpdf([Mfa|Mfas], #bpd{n = N, us = Us, p = Tree, mfa = Code } = Bpd) ->
380
case erlang:trace_info(Mfa, call_time) of
382
collect_bpdf(Mfas, Bpd);
383
{call_time, Data} when is_list(Data) ->
384
{CTn, CTus, CTree} = collect_bpdfp(Mfa, Tree, Data),
385
collect_bpdf(Mfas, Bpd#bpd{
389
mfa = [{Mfa, {CTn, CTus}}|Code]
391
{call_time, false} ->
392
collect_bpdf(Mfas, Bpd);
393
{call_time, _Other} ->
394
collect_bpdf(Mfas, Bpd)
397
collect_bpdfp(Mfa, Tree, Data) ->
399
({Pid, Ni, Si, Usi}, {PTno, PTuso, To}) ->
400
Time = Si * 1000000 + Usi,
401
Ti1 = case gb_trees:lookup(Pid, To) of
403
gb_trees:enter(Pid, [{Mfa, {Ni, Time}}], To);
405
gb_trees:enter(Pid, [{Mfa, {Ni, Time}}|Pmfas], To)
407
{PTno + Ni, PTuso + Time, Ti1}
408
end, {0,0, Tree}, Data).
411
sort_mfa(Bpfs, mfa) when is_list(Bpfs) ->
413
({A,_}, {B,_}) when A < B -> true;
416
sort_mfa(Bpfs, time) when is_list(Bpfs) ->
418
({_,{_,A}}, {_,{_,B}}) when A < B -> true;
421
sort_mfa(Bpfs, calls) when is_list(Bpfs) ->
423
({_,{A,_}}, {_,{B,_}}) when A < B -> true;
426
sort_mfa(Bpfs, _) when is_list(Bpfs) -> sort_mfa(Bpfs, time).
428
filter_mfa(Bpfs, Ts) when is_list(Ts) ->
429
filter_mfa(Bpfs, [], proplists:get_value(calls, Ts, 0), proplists:get_value(time, Ts, 0));
430
filter_mfa(Bpfs, _) -> Bpfs.
431
filter_mfa([], Out, _, _) -> lists:reverse(Out);
432
filter_mfa([{_, {C, T}}=Bpf|Bpfs], Out, Ct, Tt) when C >= Ct, T >= Tt -> filter_mfa(Bpfs, [Bpf|Out], Ct, Tt);
433
filter_mfa([_|Bpfs], Out, Ct, Tt) -> filter_mfa(Bpfs, Out, Ct, Tt).
435
sum_bp_total_n_us(Mfas) ->
436
lists:foldl(fun ({_, {Ci,Usi}}, {Co, Uso}) -> {Co + Ci, Uso + Usi} end, {0,0}, Mfas).
438
%% strings and format
440
string_bp_mfa(Mfas, Tus) -> string_bp_mfa(Mfas, Tus, {0,0,0,0,0}, []).
441
string_bp_mfa([], _, Ws, Strings) -> {Ws, lists:reverse(Strings)};
442
string_bp_mfa([{Mfa, {Count, Time}}|Mfas], Tus, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) ->
446
Sperc = s("~.2f", [100*divide(Time,Tus)]),
447
Stpc = s("~.2f", [divide(Time,Count)]),
449
string_bp_mfa(Mfas, Tus, {
450
erlang:max(MfaW, length(Smfa)),
451
erlang:max(CountW,length(Scount)),
452
erlang:max(PercW, length(Sperc)),
453
erlang:max(TimeW, length(Stime)),
454
erlang:max(TpCW, length(Stpc))
455
}, [[Smfa, Scount, Sperc, Stime, Stpc] | Strings]).
457
print_bp_mfa(Mfas, {_Tn, Tus}, Fd, Opts) ->
458
Fmfas = filter_mfa(sort_mfa(Mfas, proplists:get_value(sort, Opts)), proplists:get_value(filter, Opts)),
459
{{MfaW, CountW, PercW, TimeW, TpCW}, Strs} = string_bp_mfa(Fmfas, Tus),
461
erlang:max(length("FUNCTION"), MfaW),
462
erlang:max(length("CALLS"), CountW),
463
erlang:max(length(" %"), PercW),
464
erlang:max(length("TIME"), TimeW),
465
erlang:max(length("uS / CALLS"), TpCW)
467
format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]),
468
format(Fd, Ws, ["--------", "-----", "---", "----", "----------"]),
470
lists:foreach(fun (String) -> format(Fd, Ws, String) end, Strs),
473
s({M,F,A}) -> s("~w:~w/~w",[M,F,A]);
474
s(Term) -> s("~p", [Term]).
475
s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
478
format(Fd, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) ->
479
format(Fd, s("~~.~ps ~~~ps ~~~ps ~~~ps [~~~ps]~~n", [MfaW, CountW, PercW, TimeW, TpCW]), Strings);
480
format(undefined, Format, Strings) ->
481
io:format(Format, Strings),
483
format(Fd, Format, Strings) ->
484
io:format(Fd, Format, Strings),
485
io:format(Format, Strings),