4
%% Copyright Ericsson AB 2001-2011. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
21
-include_lib("test_server/include/test_server.hrl").
23
%% Test server framework exports
24
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
25
init_per_group/2,end_per_group/2, not_run/1]).
28
-export([stack_seq/1, tail_seq/1, create_file_slow/1, spawn_simple/1,
29
imm_tail_seq/1, imm_create_file_slow/1, imm_compile/1,
30
cpu_create_file_slow/1]).
33
-export([create_file_slow/2]).
37
-export([parse/1, verify/2]).
38
-export([spawn_simple_test/3]).
41
-define(line_trace,true).
45
-define(dbg(Str,Args), io:format(Str,Args)).
47
-define(dbg(Str,Args), ok).
52
%%%---------------------------------------------------------------------
54
%%%---------------------------------------------------------------------
58
suite() -> [{ct_hooks,[ts_install_cth]}].
61
case test_server:is_native(fprof_SUITE) of
64
[stack_seq, tail_seq, create_file_slow, spawn_simple,
65
imm_tail_seq, imm_create_file_slow, imm_compile,
72
init_per_suite(Config) ->
75
end_per_suite(_Config) ->
78
init_per_group(_GroupName, Config) ->
81
end_per_group(_GroupName, Config) ->
85
not_run(Config) when is_list(Config) ->
86
{skipped, "Native code"}.
88
%%%---------------------------------------------------------------------
91
["Tests a stack recursive variant of lists:seq/3"];
94
stack_seq(Config) when is_list(Config) ->
95
?line Timetrap = ?t:timetrap(?t:seconds(20)),
96
?line PrivDir = ?config(priv_dir, Config),
98
filename:join(PrivDir, ?MODULE_STRING"_stack_seq.trace"),
100
filename:join(PrivDir, ?MODULE_STRING"_stack_seq.analysis"),
103
?line Succ = fun (X) -> X + 1 end,
104
?line ok = fprof:stop(kill),
106
?line TS0 = erlang:now(),
107
?line R0 = fprof:apply(fun seq/3, [Start, Stop, Succ], [{file, TraceFile}]),
108
?line TS1 = erlang:now(),
109
?line R = seq(Start, Stop, Succ),
110
?line TS2 = erlang:now(),
111
?line ok = fprof:profile(file, TraceFile),
112
?line ok = fprof:analyse(),
113
?line ok = fprof:analyse(dest, AnalysisFile),
114
?line ok = fprof:stop(),
117
?line {ok, [T, P]} = parse(AnalysisFile),
118
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
119
?line ok = (catch verify(T, P)),
120
?line Proc = pid_to_list(self()),
122
[{analysis_options, _},
123
[{totals, _, Acc, _}],
124
[{Proc, _, undefined, _} | _]] ->
128
?line check_own_and_acc(TraceFile,AnalysisFile),
131
?line file:delete(TraceFile),
132
?line file:delete(AnalysisFile),
133
?line ?t:timetrap_cancel(Timetrap),
134
?line Acc1 = ts_sub(TS1, TS0),
135
?line Acc2 = ts_sub(TS2, TS1),
136
?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc1, Acc2]),
137
{comment, io_lib:format("~p times slower", [Acc1/Acc2])}.
139
%%%---------------------------------------------------------------------
142
["Tests a tail recursive variant of lists:seq/3"];
145
tail_seq(Config) when is_list(Config) ->
146
?line Timetrap = ?t:timetrap(?t:seconds(10)),
147
?line PrivDir = ?config(priv_dir, Config),
149
filename:join(PrivDir, ?MODULE_STRING"_tail_seq.trace"),
151
filename:join(PrivDir, ?MODULE_STRING"_tail_seq.analysis"),
154
?line Succ = fun (X) -> X + 1 end,
155
?line ok = fprof:stop(kill),
157
?line TS0 = erlang:now(),
158
?line R = seq_r(Start, Stop, Succ),
159
?line TS1 = erlang:now(),
161
?line R1 = fprof:apply(fun seq_r/3, [Start, Stop, Succ],
162
[{file, TraceFile}]),
163
?line TS2 = erlang:now(),
164
?line ok = fprof:profile([{file,TraceFile}]),
165
?line ok = fprof:analyse(),
166
?line ok = fprof:analyse(dest, AnalysisFile),
167
?line ok = fprof:stop(),
170
?line {ok, [T, P]} = parse(AnalysisFile),
171
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
172
?line ok = verify(T, P),
173
?line Proc = pid_to_list(self()),
175
[{analysis_options, _},
176
[{totals, _, Acc, _}],
177
[{Proc, _, undefined, _} | _]] ->
181
?line check_own_and_acc(TraceFile,AnalysisFile),
184
?line file:delete(TraceFile),
185
?line file:delete(AnalysisFile),
186
?line ?t:timetrap_cancel(Timetrap),
187
?line Acc1 = ts_sub(TS1, TS0),
188
?line Acc2 = ts_sub(TS2, TS1),
189
?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc2, Acc1]),
190
{comment, io_lib:format("~p times slower", [Acc2/Acc1])}.
192
%%%---------------------------------------------------------------------
194
create_file_slow(doc) ->
195
["Tests the create_file_slow benchmark"];
196
create_file_slow(suite) ->
198
create_file_slow(Config) when is_list(Config) ->
199
?line Timetrap = ?t:timetrap(?t:seconds(40)),
200
?line PrivDir = ?config(priv_dir, Config),
202
filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.trace"),
204
filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.analysis"),
206
filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.data"),
207
?line ok = fprof:stop(kill),
209
?line TS0 = erlang:now(),
210
?line ok = create_file_slow(DataFile, 1024),
211
?line TS1 = erlang:now(),
213
?line ok = file:delete(DataFile),
214
?line TS2 = erlang:now(),
215
?line ok = fprof:apply(?MODULE, create_file_slow, [DataFile, 1024],
216
[{file, TraceFile}]),
217
?line TS3 = erlang:now(),
218
?line ok = fprof:profile(file, TraceFile),
219
?line ok = fprof:analyse(),
220
?line ok = fprof:analyse(dest, AnalysisFile),
221
?line ok = fprof:stop(),
223
?line {ok, [T, P]} = parse(AnalysisFile),
224
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
225
?line ok = verify(T, P),
226
?line Proc = pid_to_list(self()),
228
[{analysis_options, _},
229
[{totals, _, Acc, _}],
230
[{Proc, _, undefined, _} | _]] ->
234
?line check_own_and_acc(TraceFile,AnalysisFile),
237
?line file:delete(DataFile),
238
?line file:delete(TraceFile),
239
?line file:delete(AnalysisFile),
240
?line ?t:timetrap_cancel(Timetrap),
241
?line Acc1 = ts_sub(TS1, TS0),
242
?line Acc3 = ts_sub(TS3, TS2),
243
?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc3, Acc1]),
244
{comment, io_lib:format("~p times slower", [Acc3/Acc1])}.
248
%%%---------------------------------------------------------------------
251
["Tests process spawn"];
252
spawn_simple(suite) ->
254
spawn_simple(Config) when is_list(Config) ->
255
?line Timetrap = ?t:timetrap(?t:seconds(30)),
256
?line PrivDir = ?config(priv_dir, Config),
258
filename:join(PrivDir, ?MODULE_STRING"_spawn_simple.trace"),
260
filename:join(PrivDir, ?MODULE_STRING"_spawn_simple.analysis"),
263
?line Succ = fun (X) -> X + 1 end,
264
?line ok = fprof:stop(kill),
266
?line TS0 = erlang:now(),
267
?line {{_, R1}, {_, R2}} = spawn_simple_test(Start, Stop, Succ),
268
?line TS1 = erlang:now(),
270
?line ok = fprof:trace(start, TraceFile),
271
?line {{P1, R3}, {P2, R4}} = spawn_simple_test(Start, Stop, Succ),
272
?line ok = fprof:trace(stop),
273
?line TS2 = erlang:now(),
274
?line ok = fprof:profile(file, TraceFile),
275
?line ok = fprof:analyse(),
276
?line ok = fprof:analyse(dest, AnalysisFile),
277
?line ok = fprof:stop(),
281
?line {ok, [T, P]} = parse(AnalysisFile),
282
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
283
?line ok = verify(T, P),
284
?line Proc1 = pid_to_list(P1),
285
?line Proc2 = pid_to_list(P2),
286
?line Proc0 = pid_to_list(self()),
287
?line io:format("~p~n ~p ~p ~p~n", [P, Proc0, Proc1, Proc2]),
288
?line [{analysis_options, _}, [{totals, _, Acc, _}] | Procs] = P,
289
?line [[{Proc0, _, undefined, _} | _]] =
290
lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc0 -> true;
293
?line [[{Proc1, _, undefined, _},
295
{spawned_as, {erlang, apply, ["#Fun"++_, []]}},
296
{initial_calls, [{erlang, apply, 2},
297
{?MODULE, '-spawn_simple_test/3-fun-0-', 4}]}
299
lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc1 -> true;
302
?line [[{Proc2, _, undefined, _},
304
{spawned_as, {erlang, apply, ["#Fun"++_, []]}},
305
{initial_calls, [{erlang, apply, 2},
306
{?MODULE, '-spawn_simple_test/3-fun-1-', 4}]}
308
lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc2 -> true;
311
?line 3 = length(Procs),
312
?line R1 = lists:reverse(R2),
314
?line check_own_and_acc(TraceFile,AnalysisFile),
317
?line file:delete(TraceFile),
318
?line file:delete(AnalysisFile),
319
?line ?t:timetrap_cancel(Timetrap),
320
?line Acc1 = ts_sub(TS1, TS0),
321
?line Acc2 = ts_sub(TS2, TS1),
322
?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc2, Acc1]),
323
{comment, io_lib:format("~p times slower", [Acc2/Acc1])}.
326
spawn_simple_test(Start, Stop, Succ) ->
331
Parent ! {self(), seq(Start, Stop, Succ)}
336
Parent ! {self(), seq_r(Start, Stop, Succ)}
338
receive {Seq, SeqResult} ->
339
receive {SeqR, SeqRResult} ->
340
{{Seq, SeqResult}, {SeqR, SeqRResult}}
346
%%%---------------------------------------------------------------------
349
["Tests a tail recursive variant of lists:seq/3 ",
350
"with immediate trace to profile"];
351
imm_tail_seq(suite) ->
353
imm_tail_seq(Config) when is_list(Config) ->
354
?line Timetrap = ?t:timetrap(?t:seconds(10)),
355
?line PrivDir = ?config(priv_dir, Config),
357
filename:join(PrivDir, ?MODULE_STRING"_imm_tail_seq.analysis"),
360
?line Succ = fun (X) -> X + 1 end,
361
?line ok = fprof:stop(kill),
362
?line catch eprof:stop(),
364
?line TS0 = erlang:now(),
365
?line R0 = seq_r(Start, Stop, Succ),
366
?line TS1 = erlang:now(),
368
?line profiling = eprof:start_profiling([self()]),
369
?line TS2 = erlang:now(),
370
?line R2 = seq_r(Start, Stop, Succ),
371
?line TS3 = erlang:now(),
372
?line profiling_stopped = eprof:stop_profiling(),
375
?line eprof:analyze(),
376
?line stopped = eprof:stop(),
378
?line {ok, Tracer} = fprof:profile(start),
379
?line ok = fprof:trace([start, {tracer, Tracer}]),
380
?line TS4 = erlang:now(),
381
?line R4 = seq_r(Start, Stop, Succ),
382
?line TS5 = erlang:now(),
383
?line ok = fprof:trace(stop),
384
?line ok = fprof:analyse(),
385
?line ok = fprof:analyse(dest, AnalysisFile),
386
?line ok = fprof:stop(),
389
?line {ok, [T, P]} = parse(AnalysisFile),
390
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
391
?line ok = verify(T, P),
392
?line Proc = pid_to_list(self()),
394
[{analysis_options, _},
395
[{totals, _, Acc, _}],
396
[{Proc, _, undefined, _} | _]] ->
401
?line file:delete(AnalysisFile),
402
?line ?t:timetrap_cancel(Timetrap),
403
?line Acc1 = ts_sub(TS1, TS0),
404
?line Acc3 = ts_sub(TS3, TS2),
405
?line Acc5 = ts_sub(TS5, TS4),
406
?line io:format("~p (plain), ~p (eprof), ~p (fprof), ~p (cpu)~n",
407
[Acc1/1000, Acc3/1000, Acc5/1000, Acc/1000]),
408
{comment, io_lib:format("~p/~p (fprof/eprof) times slower",
409
[Acc5/Acc1, Acc3/Acc1])}.
411
%%%---------------------------------------------------------------------
413
imm_create_file_slow(doc) ->
414
["Tests a tail recursive variant of lists:seq/3 ",
415
"with immediate trace to profile"];
416
imm_create_file_slow(suite) ->
418
imm_create_file_slow(Config) when is_list(Config) ->
419
?line Timetrap = ?t:timetrap(?t:seconds(60)),
420
?line PrivDir = ?config(priv_dir, Config),
422
filename:join(PrivDir, ?MODULE_STRING"_imm_create_file_slow.data"),
424
filename:join(PrivDir, ?MODULE_STRING"_imm_create_file_slow.analysis"),
425
?line ok = fprof:stop(kill),
427
?line TS0 = erlang:now(),
428
?line ok = create_file_slow(DataFile, 1024),
429
?line TS1 = erlang:now(),
430
?line ok = file:delete(DataFile),
432
?line {ok, Tracer} = fprof:profile(start),
433
?line TS2 = erlang:now(),
434
?line ok = fprof:apply(?MODULE, create_file_slow, [DataFile, 1024],
435
[{tracer, Tracer}, continue]),
436
?line TS3 = erlang:now(),
437
?line ok = fprof:profile(stop),
438
?line ok = fprof:analyse(),
439
?line ok = fprof:analyse(dest, AnalysisFile),
440
?line ok = fprof:stop(),
442
?line {ok, [T, P]} = parse(AnalysisFile),
443
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
444
?line ok = verify(T, P),
445
?line Proc = pid_to_list(self()),
447
[{analysis_options, _},
448
[{totals, _, Acc, _}],
449
[{Proc, _, undefined, _} | _]] ->
454
?line file:delete(DataFile),
455
?line file:delete(AnalysisFile),
456
?line ?t:timetrap_cancel(Timetrap),
457
?line Acc1 = ts_sub(TS1, TS0),
458
?line Acc3 = ts_sub(TS3, TS2),
459
?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc3, Acc1]),
460
{comment, io_lib:format("~p times slower", [Acc3/Acc1])}.
462
%%%---------------------------------------------------------------------
465
["Tests to compile a small source file ",
466
"with immediate trace to profile"];
467
imm_compile(suite) ->
469
imm_compile(Config) when is_list(Config) ->
470
?line Timetrap = ?t:timetrap(?t:minutes(20)),
471
?line DataDir = ?config(data_dir, Config),
472
?line SourceFile = filename:join(DataDir, "foo.erl"),
473
?line PrivDir = ?config(priv_dir, Config),
475
filename:join(PrivDir, ?MODULE_STRING"_imm_compile.analysis"),
476
?line ok = fprof:stop(kill),
477
?line catch eprof:stop(),
479
?line {ok, foo, _} = compile:file(SourceFile, [binary]),
480
?line TS0 = erlang:now(),
481
?line {ok, foo, _} = compile:file(SourceFile, [binary]),
482
?line TS1 = erlang:now(),
484
?line profiling = eprof:start_profiling([self()]),
485
?line TS2 = erlang:now(),
486
?line {ok, foo, _} = compile:file(SourceFile, [binary]),
487
?line TS3 = erlang:now(),
488
?line profiling_stopped = eprof:stop_profiling(),
490
?line eprof:analyze(),
491
?line stopped = eprof:stop(),
493
?line {ok, Tracer} = fprof:profile(start),
494
?line ok = fprof:trace([start, {tracer, Tracer}]),
495
?line TS4 = erlang:now(),
496
?line {ok, foo, _} = compile:file(SourceFile, [binary]),
497
?line TS5 = erlang:now(),
498
?line ok = fprof:trace(stop),
500
?line io:format("Analysing...~n"),
501
?line ok = fprof:analyse(dest, AnalysisFile),
502
?line ok = fprof:stop(),
504
?line {ok, [T, P]} = parse(AnalysisFile),
505
?line io:format("~p~n", [P]),
506
?line Acc1 = ts_sub(TS1, TS0),
507
?line Acc3 = ts_sub(TS3, TS2),
508
?line Acc5 = ts_sub(TS5, TS4),
509
?line io:format("Verifying...~n"),
510
?line ok = verify(T, P),
512
[{analysis_options, _},
513
[{totals, _, Acc, _}] | _] ->
518
?line file:delete(AnalysisFile),
519
?line ?t:timetrap_cancel(Timetrap),
520
?line io:format("~p (plain), ~p (eprof), ~p (fprof), ~p(cpu)~n",
521
[Acc1/1000, Acc3/1000, Acc5/1000, Acc/1000]),
522
{comment, io_lib:format("~p/~p (fprof/eprof) times slower",
523
[Acc5/Acc1, Acc3/Acc1])}.
525
%%%---------------------------------------------------------------------
527
cpu_create_file_slow(doc) ->
528
["Tests the create_file_slow benchmark using cpu_time"];
529
cpu_create_file_slow(suite) ->
531
cpu_create_file_slow(Config) when is_list(Config) ->
532
?line Timetrap = ?t:timetrap(?t:seconds(40)),
533
?line PrivDir = ?config(priv_dir, Config),
535
filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.trace"),
537
filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.analysis"),
539
filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.data"),
540
?line ok = fprof:stop(kill),
542
?line TS0 = erlang:now(),
543
?line Result = (catch fprof:apply(?MODULE, create_file_slow,
545
[{file, TraceFile}, cpu_time])),
546
?line TS1 = erlang:now(),
550
?line ok = fprof:profile(file, TraceFile),
551
?line ok = fprof:analyse(),
552
?line ok = fprof:analyse(dest, AnalysisFile),
553
?line ok = fprof:stop(),
555
?line {ok, [T, P]} = parse(AnalysisFile),
556
?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
557
?line ok = verify(T, P),
558
?line Proc = pid_to_list(self()),
560
[{analysis_options, _},
561
[{totals, _, Acc, _}],
562
[{Proc, _, undefined, _} | _]] ->
566
?line check_own_and_acc(TraceFile,AnalysisFile),
569
?line file:delete(DataFile),
570
?line file:delete(TraceFile),
571
?line file:delete(AnalysisFile),
572
?line Acc1 = ts_sub(TS1, TS0),
573
?line io:format("cpu_ts:~w, fprof:~w~n", [Acc, Acc1]),
574
{comment, io_lib:format("~p% cpu utilization",
576
{'EXIT', not_supported} ->
577
case {os:type(), os:version()} of
578
{{unix, sunos}, {Major, Minor, _}}
579
when Major >= 5, Minor >= 7 ->
580
test_server:fail(Result);
582
{skipped, "not_supported"}
585
test_server:fail(Result)
587
?line ?t:timetrap_cancel(Timetrap),
592
%%%---------------------------------------------------------------------
593
%%% Functions to test
594
%%%---------------------------------------------------------------------
598
%% Stack recursive seq
599
seq(Stop, Stop, Succ) when is_function(Succ) ->
601
seq(Start, Stop, Succ) when is_function(Succ) ->
602
[Start | seq(Succ(Start), Stop, Succ)].
606
%% Tail recursive seq, result list is reversed
607
seq_r(Start, Stop, Succ) when is_function(Succ) ->
608
seq_r(Start, Stop, Succ, []).
610
seq_r(Stop, Stop, _, R) ->
612
seq_r(Start, Stop, Succ, R) ->
613
seq_r(Succ(Start), Stop, Succ, [Start | R]).
617
create_file_slow(Name, N) when is_integer(N), N >= 0 ->
619
file:open(Name, [raw, write, delayed_write, binary]),
622
lists:map(fun (X) -> <<X:32/unsigned>> end,
624
ok = create_file_slow(FD, 256, N);
626
ok = create_file_slow(FD, 0, N)
630
create_file_slow(_FD, M, M) ->
632
create_file_slow(FD, M, N) ->
633
ok = file:write(FD, <<M:32/unsigned>>),
634
create_file_slow(FD, M+1, N).
638
%%%---------------------------------------------------------------------
639
%%% Profile verification functions
640
%%%---------------------------------------------------------------------
644
verify(Tab, [{analysis_options, _},
645
[{totals, Cnt, Acc, Own} | _] | Processes]) ->
648
fun ([{Proc, Cnt_P, undefined, Own_P} | _]) ->
649
case sum_process(Tab, Proc) of
650
{Proc, Cnt_P, Acc_P, Own_P} = Clocks
651
when Acc_P >= Own_P ->
654
throw({error, [?MODULE, ?LINE, Weird]})
659
fun ({_, Cnt_P2, Acc_P2, Own_P2},
660
{totals, Cnt_T, Acc_T, Own_T}) ->
661
{totals, Cnt_P2+Cnt_T, Acc_P2+Acc_T, Own_P2+Own_T}
665
{totals, Cnt, Acc_T, Own} when Acc_T >= Acc ->
668
throw({error, [?MODULE, ?LINE, Weird]})
673
sum_process(Tab, Proc) ->
675
Tab, [{{{Proc, '_'}, '_'}, [], ['$_']}], 100,
676
fun ({{P, MFA}, {Callers, {MFA, Cnt, Acc, Own}, Called}},
677
{P, Cnt_P, Acc_P, Own_P}) when P == Proc ->
678
ok = verify_callers(Tab, Proc, MFA, Callers),
679
ok = verify_called(Tab, Proc, MFA, Called),
680
{P, Cnt+Cnt_P, Acc+Acc_P, Own+Own_P};
682
throw({error, [?MODULE, ?LINE, Weird, Clocks]})
686
verify_callers(_, _, _, []) ->
688
verify_callers(Tab, Proc, MFA, [{Caller, Cnt, Acc, Own} | Tail]) ->
690
case ets:lookup(Tab, Id) of
691
[{Id, {_, {Caller, _, _, _}, Called}}] ->
692
case lists:keysearch(MFA, 1, Called) of
693
{value, {MFA, Cnt, Acc, Own}} ->
694
verify_callers(Tab, Proc, MFA, Tail);
696
throw({error, [?MODULE, ?LINE, MFA, Id]})
699
throw({error, [?MODULE, ?LINE, Weird]})
702
verify_called(_, _, _, []) ->
704
verify_called(Tab, Proc, MFA, [{Called, Cnt, Acc, Own} | Tail]) ->
706
case ets:lookup(Tab, Id) of
707
[{Id, {Callers, {Called, _, _, _}, _}}] ->
708
case lists:keysearch(MFA, 1, Callers) of
709
{value, {MFA, Cnt, Acc, Own}} ->
710
verify_called(Tab, Proc, MFA, Tail);
712
throw({error, [?MODULE, ?LINE, MFA, Id]})
715
throw({error, [?MODULE, ?LINE, Weird]})
720
%% Parse a analysis file and return an Ets table with all function entries,
721
%% and a list of process entries. Checks the concistency of the function
722
%% entries when they are read.
724
case file:open(Filename, [read]) of
726
Result = parse_stream(FD),
734
Tab = ets:new(fprof_SUITE, []),
735
parse_stream(FD, Tab, [], void).
737
parse_stream(FD, Tab, R, Proc) ->
738
case catch io:read(FD, '') of
740
{error, [?MODULE, ?LINE]};
742
case parse_term(Term) of
743
{ok, {analysis_options, _} = Term_1}
745
parse_stream(FD, Tab, [Term_1 | R], analysis_options);
746
{ok, [{totals, _, _, _} | _] = Term_1}
747
when Proc == analysis_options ->
748
parse_stream(FD, Tab, [Term_1 | R], totals);
749
{ok, [{P, _, _, _} | _] = Term_1} ->
750
parse_stream(FD, Tab, [Term_1 | R], P);
751
{ok, {_Callers, {MFA, _, _, _}, _Called} = Term_1}
752
when Proc == totals; is_list(Proc) ->
753
ets:insert(Tab, {{Proc, MFA}, Term_1}),
754
parse_stream(FD, Tab, R, Proc);
756
{error, [?MODULE, ?LINE, Term_1]};
761
{ok, [Tab, lists:reverse(R)]};
766
parse_term({Callers, Func, Called})
767
when is_list(Callers), is_list(Called) ->
768
Callers_1 = lists:map(fun parse_clocks/1, Callers),
769
Func_1 = parse_clocks(Func),
770
Called_1 = lists:map(fun parse_clocks/1, Called),
771
Result = {Callers_1, Func_1, Called_1},
772
case chk_invariant(Result) of
778
parse_term([{_, _, _, _} = Clocks | Tail]) ->
779
{ok, [parse_clocks(Clocks) | Tail]};
783
parse_clocks({MFA, Cnt, undefined, Own}) ->
784
{MFA, Cnt, undefined, round(Own*1000)};
785
parse_clocks({MFA, Cnt, Acc, Own}) ->
786
{MFA, Cnt, round(Acc*1000), round(Own*1000)};
787
parse_clocks(Clocks) ->
792
chk_invariant({Callers, {MFA, Cnt, Acc, Own}, Called} = Term) ->
793
{_, Callers_Cnt, Callers_Acc, Callers_Own} = Callers_Sum = sum(Callers),
794
% {_, Called_Cnt, Called_Acc, Called_Own} = Called_Sum = sum(Called),
796
lists:keymember(suspend, 1, Callers),
797
lists:keymember(garbage_collect, 1, Callers),
799
{suspend, false, _, []} ->
801
{suspend, _, _, _} = Weird ->
802
{error, [?MODULE, ?LINE, Weird, Term]};
803
{garbage_collect, false, false, []} ->
805
{garbage_collect, false, false, [{suspend, _, _, _}]} ->
807
{garbage_collect, _, _, _} = Weird ->
808
{error, [?MODULE, ?LINE, Weird, Term]};
809
{undefined, false, false, _}
810
when Callers == [], Cnt == 0, Acc == 0, Own == 0 ->
812
{undefined, _, _, _} = Weird ->
813
{error, [?MODULE, ?LINE, Weird, Term]};
815
case chk_self_call(Term) of
816
true when Callers_Cnt /= Cnt; Callers_Acc /= Acc;
817
Callers_Own /= Own ->
818
{error, [?MODULE, ?LINE, Callers_Sum, Term]};
819
% true when Called_Acc + Own /= Acc ->
820
% io:format("WARNING: ~p:~p, ~p, ~p.~n",
821
% [?MODULE, ?LINE, Term, Called_Sum]),
822
% {error, [?MODULE, ?LINE, Term, Called_Sum]};
827
{error, [?MODULE, ?LINE, Term]}
831
ts_sub({A, B, C}, {A0, B0, C0}) ->
832
((A - A0)*1000000000000 + (B - B0))*1000000 + C - C0.
835
{sum, _Cnt, _Acc, _Own} =
837
fun ({_, C1, A1, O1}, {sum, C2, A2, O2}) ->
838
{sum, C1+C2, A1+A2, O1+O2}
843
chk_self_call({Callers, {MFA, _Cnt, _Acc, _Own}, Called}) ->
844
case lists:keysearch(MFA, 1, Callers) of
847
{value, {MFA, C, 0, O}} ->
848
case lists:keysearch(MFA, 1, Called) of
851
{value, {MFA, C, 0, O}} ->
862
%%%---------------------------------------------------------------------
863
%%% Fairly generic support functions
864
%%%---------------------------------------------------------------------
867
ets_select_fold(Table, MatchSpec, Limit, Fun, Acc) ->
868
ets:safe_fixtable(Table, true),
869
ets_select_fold_1(ets:select(Table, MatchSpec, Limit), Fun, Acc).
871
ets_select_fold_1('$end_of_table', _, Acc) ->
873
ets_select_fold_1({Matches, Continuation}, Fun, Acc) ->
874
ets_select_fold_1(ets:select(Continuation),
876
lists:foldl(Fun, Acc, Matches)).
880
% ets_select_foreach(Table, MatchSpec, Limit, Fun) ->
881
% ets:safe_fixtable(Table, true),
882
% ets_select_foreach_1(ets:select(Table, MatchSpec, Limit), Fun).
884
% ets_select_foreach_1('$end_of_table', _) ->
886
% ets_select_foreach_1({Matches, Continuation}, Fun) ->
887
% lists:foreach(Fun, Matches),
888
% ets_select_foreach_1(ets:select(Continuation), Fun).
891
%%%---------------------------------------------------------------------
892
%%% Simple smulation of fprof used for checking own and acc times for
894
%%% The function 'undefined' is ignored
895
%%%---------------------------------------------------------------------
897
%% check_own_and_acc_traced(TraceFile, AnalysisFile) ->
898
%% check_own_and_acc(TraceFile, AnalysisFile, fun handle_trace_traced/2).
900
check_own_and_acc(TraceFile, AnalysisFile) ->
901
check_own_and_acc(TraceFile, AnalysisFile, fun handle_trace/2).
903
check_own_and_acc(TraceFile, AnalysisFile, HandlerFun) ->
904
dbg:trace_client(file,TraceFile,{HandlerFun,{init,self()}}),
905
receive {result,Result} ->
906
compare(Result,get_own_and_acc_from_analysis(AnalysisFile))
909
%% handle_trace_traced(Trace, Msg) ->
910
%% io:format("handle_trace_traced(~p, ~p).", [Trace, Msg]),
911
%% handle_trace(Trace, Msg).
913
handle_trace(Trace,{init,Parent}) ->
915
ets:new(fprof_verify_tab,[named_table]),
916
handle_trace(Trace,Parent);
917
handle_trace({trace_ts,Pid,in,MFA,TS},P) ->
918
?dbg("~p",[{{in,Pid,MFA},get(Pid)}]),
920
[suspend|[suspend|_]=NewStack] ->
921
T = ts_sub(TS,get({Pid,last_ts})),
922
update_acc(Pid,NewStack,T),
924
[suspend|NewStack] = Stack ->
925
T = ts_sub(TS,get({Pid,last_ts})),
926
update_acc(Pid,Stack,T),
936
put({Pid,last_ts},TS),
938
handle_trace({trace_ts,Pid,out,_MfaOrZero,TS},P) ->
939
?dbg("~p",[{{out,Pid,_MfaOrZero},get(Pid)}]),
940
T = ts_sub(TS,get({Pid,last_ts})),
942
[suspend|S] = Stack ->
944
put(Pid,[suspend|Stack]);
947
update_own(Pid,MFA,T),
948
update_acc(Pid,Stack,T),
949
put(Pid,[suspend|Stack]);
954
put({Pid,last_ts},TS),
956
handle_trace({trace_ts,Pid,call,MFA,{cp,Caller},TS},P) ->
957
?dbg("~p",[{{call,Pid,MFA},get(Pid)}]),
958
T = ts_sub(TS,get({Pid,last_ts})),
962
update_own(Pid,MFA,T),
963
update_acc(Pid,Stack,T);
964
[CallingMFA|_] = Stack when Caller==undefined ->
966
update_own(Pid,CallingMFA,T),
967
update_acc(Pid,Stack,T),
968
put(Pid,[MFA|Stack]);
969
[] when Caller==undefined ->
974
Stack = [CallingMFA|_] = insert_caller(Caller,Stack0,[]),
977
update_own(Pid,CallingMFA,T),
978
update_acc(Pid,Stack,T),
981
put({Pid,last_ts},TS),
983
handle_trace({trace_ts,Pid,return_to,MFA,TS},P) ->
984
?dbg("~p",[{{return_to,Pid,MFA},get(Pid)}]),
985
T = ts_sub(TS,get({Pid,last_ts})),
989
update_own(Pid,MFA,T),
990
update_acc(Pid,Stack,T),
992
[ReturnFromMFA,MFA|RestOfStack] = Stack ->
993
update_own(Pid,ReturnFromMFA,T),
994
update_acc(Pid,Stack,T),
995
put(Pid,[MFA|RestOfStack]);
996
[ReturnFromMFA|RestOfStack] = Stack ->
997
update_own(Pid,ReturnFromMFA,T),
998
update_acc(Pid,Stack,T),
999
case find_return_to(MFA,RestOfStack) of
1000
[] when MFA==undefined ->
1009
put({Pid,last_ts},TS),
1011
handle_trace({trace_ts,Pid,gc_start,_,TS},P) ->
1012
?dbg("~p",[{{gc_start,Pid},get(Pid)}]),
1014
[suspend|_] = Stack ->
1015
T = ts_sub(TS,get({Pid,last_ts})),
1016
insert(Pid,garbage_collect),
1017
update_acc(Pid,Stack,T),
1018
put(Pid,[garbage_collect|Stack]);
1019
[CallingMFA|_] = Stack ->
1020
T = ts_sub(TS,get({Pid,last_ts})),
1021
insert(Pid,garbage_collect),
1022
update_own(Pid,CallingMFA,T),
1023
update_acc(Pid,Stack,T),
1024
put(Pid,[garbage_collect|Stack]);
1027
put(Pid,[garbage_collect]),
1028
insert(Pid,garbage_collect)
1030
put({Pid,last_ts},TS),
1032
handle_trace({trace_ts,Pid,gc_end,_,TS},P) ->
1033
?dbg("~p",[{{gc_end,Pid},get(Pid)}]),
1034
T = ts_sub(TS,get({Pid,last_ts})),
1036
[garbage_collect|RestOfStack] = Stack ->
1037
update_own(Pid,garbage_collect,T),
1038
update_acc(Pid,Stack,T),
1039
put(Pid,RestOfStack)
1041
put({Pid,last_ts},TS),
1043
handle_trace({trace_ts,Pid,spawn,NewPid,{M,F,Args},TS},P) ->
1044
MFA = {M,F,length(Args)},
1045
?dbg("~p",[{{spawn,Pid,NewPid,MFA},get(Pid)}]),
1046
T = ts_sub(TS,get({Pid,last_ts})),
1047
put({NewPid,last_ts},TS),
1048
put(NewPid,[suspend,MFA]),
1049
insert(NewPid,suspend),
1052
[SpawningMFA|_] = Stack ->
1053
update_own(Pid,SpawningMFA,T),
1054
update_acc(Pid,Stack,T)
1056
put({Pid,last_ts},TS),
1058
handle_trace({trace_ts,Pid,exit,_Reason,TS},P) ->
1059
?dbg("~p",[{{exit,Pid,_Reason},get(Pid)}]),
1060
T = ts_sub(TS,get({Pid,last_ts})),
1062
[DyingMFA|_] = Stack ->
1063
update_own(Pid,DyingMFA,T),
1064
update_acc(Pid,Stack,T),
1069
put({Pid,last_ts},TS),
1071
handle_trace({trace_ts,_,Link,_,_},P)
1074
Link==getting_linked;
1075
Link==getting_unlinked ->
1077
handle_trace(end_of_trace,P) ->
1079
Result = ets:tab2list(fprof_verify_tab),
1080
{TotOwn,ProcOwns} = get_proc_owns(Result,[],0),
1081
TotAcc = ts_sub(get_last_ts(),get(first_ts)),
1082
P ! {result,[{totals,TotAcc,TotOwn}|ProcOwns]++Result},
1084
handle_trace(Other,_P) ->
1085
exit({unexpected,Other}).
1087
find_return_to(MFA,[MFA|_]=Stack) ->
1089
find_return_to(MFA,[_|Stack]) ->
1090
find_return_to(MFA,Stack);
1091
find_return_to(_MFA,[]) ->
1094
insert_caller(MFA,[MFA|Rest],Result) ->
1095
lists:reverse(Result)++[MFA|Rest];
1096
insert_caller(MFA,[Other|Rest],Result) ->
1097
insert_caller(MFA,Rest,[Other|Result]);
1098
insert_caller(MFA,[],Result) ->
1099
lists:reverse([MFA|Result]).
1102
case ets:member(fprof_verify_tab,{Pid,MFA}) of
1104
ets:insert(fprof_verify_tab,{{Pid,MFA},0,0});
1109
update_own(Pid,MFA,T) ->
1110
ets:update_counter(fprof_verify_tab,{Pid,MFA},{3,T}).
1112
update_acc(Pid,[MFA|Rest],T) ->
1113
case lists:member(MFA,Rest) of
1115
%% Only charge one time for recursive functions
1118
ets:update_counter(fprof_verify_tab,{Pid,MFA},{2,T})
1120
update_acc(Pid,Rest,T);
1121
update_acc(_Pid,[],_T) ->
1126
get_last_ts(get(),{0,0,0}).
1127
get_last_ts([{{_,last_ts},TS}|Rest],Last) when TS>Last ->
1128
get_last_ts(Rest,TS);
1129
get_last_ts([_|Rest],Last) ->
1130
get_last_ts(Rest,Last);
1131
get_last_ts([],Last) ->
1134
get_proc_owns([{{Pid,_MFA},_Acc,Own}|Rest],Result,Sum) ->
1136
case lists:keysearch(Pid,1,Result) of
1137
{value,{Pid,undefined,PidOwn}} ->
1138
lists:keyreplace(Pid,1,Result,{Pid,undefined,PidOwn+Own});
1140
[{Pid,undefined,Own}|Result]
1142
get_proc_owns(Rest,NewResult,Sum+Own);
1143
get_proc_owns([],Result,Sum) ->
1147
compare([X|Rest],FprofResult) ->
1149
case lists:member(X,FprofResult) of
1152
lists:delete(X,FprofResult);
1154
case lists:keysearch(element(1,X),1,FprofResult) of
1156
put(compare_error,true),
1157
io:format("Error: Different values\n"
1159
"Simulator: ~p",[Fprof,X]),
1160
lists:delete(Fprof,FprofResult);
1162
put(compare_error,true),
1163
io:format("Error: Missing in fprof: ~p",[X]),
1167
compare(Rest,FprofResult1);
1169
case {remove_undefined(Rest,[]),get(compare_error)} of
1170
{[],undefined} -> ok;
1174
_ -> io:format("\nMissing in simulator results:\n~p\n",[Error])
1176
?t:fail({error,mismatch_between_simulator_and_fprof})
1179
remove_undefined([{{_Pid,undefined},_,_}|Rest],Result) ->
1180
remove_undefined(Rest,Result);
1181
remove_undefined([X|Rest],Result) ->
1182
remove_undefined(Rest,[X|Result]);
1183
remove_undefined([],Result) ->
1186
get_own_and_acc_from_analysis(Log) ->
1187
case file:consult(Log) of
1188
{ok,[_Options,[{totals,_,TotAcc,TotOwn}]|Rest]} ->
1189
get_own_and_acc(undefined,Rest,
1190
[{totals,m1000(TotAcc),m1000(TotOwn)}]);
1192
exit({error,{cant_open,Log,Error}})
1195
get_own_and_acc(_,[[{PidStr,_,Acc,Own}|_]|Rest],Result) ->
1196
Pid = list_to_pid(PidStr),
1197
get_own_and_acc(Pid,Rest,[{Pid,m1000(Acc),m1000(Own)}|Result]);
1198
get_own_and_acc(Pid,[{_Callers,{MFA,_,Acc,Own},_Called}|Rest],Result) ->
1199
get_own_and_acc(Pid,Rest,[{{Pid,MFA},m1000(Acc),m1000(Own)}|Result]);
1200
get_own_and_acc(_,[],Result) ->
1201
lists:reverse(Result).