1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2001-2012. All Rights Reserved.
    5: %%
    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/.
   11: %%
   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
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: -module(fprof_SUITE).
   20: 
   21: -include_lib("test_server/include/test_server.hrl").
   22: 
   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]).
   26: 
   27: %% Test suites
   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]).
   31: 
   32: %% Other exports
   33: -export([create_file_slow/2]).
   34: 
   35: 
   36: %% Debug exports
   37: -export([parse/1, verify/2]).
   38: -export([spawn_simple_test/3]).
   39: 
   40: 
   41: -define(line_trace,true).
   42: 
   43: %-define(debug,true).
   44: -ifdef(debug).
   45: -define(dbg(Str,Args), io:format(Str,Args)).
   46: -else.
   47: -define(dbg(Str,Args), ok).
   48: -endif.
   49: 
   50: 
   51: 
   52: %%%---------------------------------------------------------------------
   53: %%% Test suites
   54: %%%---------------------------------------------------------------------
   55: 
   56: 
   57: 
   58: suite() -> [{ct_hooks,[ts_install_cth]}].
   59: 
   60: all() -> 
   61:     case test_server:is_native(fprof_SUITE) of
   62: 	true -> [not_run];
   63: 	false ->
   64: 	    [stack_seq, tail_seq, create_file_slow, spawn_simple,
   65: 	     imm_tail_seq, imm_create_file_slow, imm_compile,
   66: 	     cpu_create_file_slow]
   67:     end.
   68: 
   69: groups() -> 
   70:     [].
   71: 
   72: init_per_suite(Config) ->
   73:     Config.
   74: 
   75: end_per_suite(_Config) ->
   76:     ok.
   77: 
   78: init_per_group(_GroupName, Config) ->
   79:     Config.
   80: 
   81: end_per_group(_GroupName, Config) ->
   82:     Config.
   83: 
   84: 
   85: not_run(Config) when is_list(Config) ->
   86:     {skipped, "Native code"}.
   87: 
   88: %%%---------------------------------------------------------------------
   89: 
   90: stack_seq(doc) ->
   91:     ["Tests a stack recursive variant of lists:seq/3"];
   92: stack_seq(suite) ->
   93:     [];
   94: stack_seq(Config) when is_list(Config) ->
   95:     ?line Timetrap = ?t:timetrap(?t:seconds(20)),
   96:     ?line PrivDir = ?config(priv_dir, Config),
   97:     ?line TraceFile = 
   98: 	filename:join(PrivDir, ?MODULE_STRING"_stack_seq.trace"),
   99:     ?line AnalysisFile = 
  100: 	filename:join(PrivDir, ?MODULE_STRING"_stack_seq.analysis"),
  101:     ?line Start = 1,
  102:     ?line Stop = 1000,
  103:     ?line Succ = fun (X) -> X + 1 end,
  104:     ?line ok = fprof:stop(kill),
  105:     %%
  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(),
  115:     ?line R = R0,
  116:     %%
  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()),
  121:     ?line case P of
  122: 	      [{analysis_options, _},
  123: 	       [{totals, _, Acc, _}],
  124: 	       [{Proc, _, undefined, _} | _]] ->
  125: 		  ok
  126: 	  end,
  127:     %%
  128:     ?line check_own_and_acc(TraceFile,AnalysisFile),
  129:     %%
  130:     ?line ets:delete(T),
  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])}.
  138: 
  139: %%%---------------------------------------------------------------------
  140: 
  141: tail_seq(doc) ->
  142:     ["Tests a tail recursive variant of lists:seq/3"];
  143: tail_seq(suite) ->
  144:     [];
  145: tail_seq(Config) when is_list(Config) ->
  146:     ?line Timetrap = ?t:timetrap(?t:seconds(10)),
  147:     ?line PrivDir = ?config(priv_dir, Config),
  148:     ?line TraceFile = 
  149: 	filename:join(PrivDir, ?MODULE_STRING"_tail_seq.trace"),
  150:     ?line AnalysisFile = 
  151: 	filename:join(PrivDir, ?MODULE_STRING"_tail_seq.analysis"),
  152:     ?line Start = 1,
  153:     ?line Stop = 1000,
  154:     ?line Succ = fun (X) -> X + 1 end,
  155:     ?line ok = fprof:stop(kill),
  156:     %%
  157:     ?line TS0 = erlang:now(),
  158:     ?line R = seq_r(Start, Stop, Succ),
  159:     ?line TS1 = erlang:now(),
  160:     %%
  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(),
  168:     ?line R = R1,
  169:     %%
  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()),
  174:     ?line case P of
  175: 	      [{analysis_options, _},
  176: 	       [{totals, _, Acc, _}],
  177: 	       [{Proc, _, undefined, _} | _]] ->
  178: 		  ok
  179: 	  end,
  180:     %%
  181:     ?line check_own_and_acc(TraceFile,AnalysisFile),
  182:     %%
  183:     ?line ets:delete(T),
  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])}.
  191: 
  192: %%%---------------------------------------------------------------------
  193: 
  194: %% Tests the create_file_slow benchmark.
  195: create_file_slow(Config) ->
  196:     case test_server:is_native(lists) orelse
  197: 	test_server:is_native(file) of
  198: 	true ->
  199: 	    {skip,"Native libs -- tracing does not work"};
  200: 	false ->
  201: 	    do_create_file_slow(Config)
  202:     end.
  203: 
  204: do_create_file_slow(Config) ->
  205:     ?line Timetrap = ?t:timetrap(?t:seconds(40)),
  206:     ?line PrivDir = ?config(priv_dir, Config),
  207:     ?line TraceFile = 
  208: 	filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.trace"),
  209:     ?line AnalysisFile = 
  210: 	filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.analysis"),
  211:     ?line DataFile = 
  212: 	filename:join(PrivDir, ?MODULE_STRING"_create_file_slow.data"),
  213:     ?line ok = fprof:stop(kill),
  214:     %%
  215:     ?line TS0 = erlang:now(),
  216:     ?line ok = create_file_slow(DataFile, 1024),
  217:     ?line TS1 = erlang:now(),
  218:     %%
  219:     ?line ok = file:delete(DataFile),
  220:     ?line TS2 = erlang:now(),
  221:     ?line ok = fprof:apply(?MODULE, create_file_slow, [DataFile, 1024], 
  222: 			   [{file, TraceFile}]),
  223:     ?line TS3 = erlang:now(),
  224:     ?line ok = fprof:profile(file, TraceFile),
  225:     ?line ok = fprof:analyse(),
  226:     ?line ok = fprof:analyse(dest, AnalysisFile),
  227:     ?line ok = fprof:stop(),
  228:     %%
  229:     ?line {ok, [T, P]} = parse(AnalysisFile),
  230:     ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
  231:     ?line ok = verify(T, P),
  232:     ?line Proc = pid_to_list(self()),
  233:     ?line case P of
  234: 	      [{analysis_options, _},
  235: 	       [{totals, _, Acc, _}],
  236: 	       [{Proc, _, undefined, _} | _]] ->
  237: 		  ok
  238: 	  end,
  239:     %%
  240:     ?line check_own_and_acc(TraceFile,AnalysisFile),
  241:     %%
  242:     ?line ets:delete(T),
  243:     ?line file:delete(DataFile),
  244:     ?line file:delete(TraceFile),
  245:     ?line file:delete(AnalysisFile),
  246:     ?line ?t:timetrap_cancel(Timetrap),
  247:     ?line Acc1 = ts_sub(TS1, TS0),
  248:     ?line Acc3 = ts_sub(TS3, TS2),
  249:     ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc3, Acc1]),
  250:     {comment, io_lib:format("~p times slower", [Acc3/Acc1])}.
  251: 
  252: 
  253: 
  254: %%%---------------------------------------------------------------------
  255: 
  256: spawn_simple(doc) ->
  257:     ["Tests process spawn"];
  258: spawn_simple(suite) ->
  259:     [];
  260: spawn_simple(Config) when is_list(Config) ->
  261:     ?line Timetrap = ?t:timetrap(?t:seconds(30)),
  262:     ?line PrivDir = ?config(priv_dir, Config),
  263:     ?line TraceFile = 
  264: 	filename:join(PrivDir, ?MODULE_STRING"_spawn_simple.trace"),
  265:     ?line AnalysisFile = 
  266: 	filename:join(PrivDir, ?MODULE_STRING"_spawn_simple.analysis"),
  267:     ?line Start = 1,
  268:     ?line Stop = 1000,
  269:     ?line Succ = fun (X) -> X + 1 end,
  270:     ?line ok = fprof:stop(kill),
  271:     %%
  272:     ?line TS0 = erlang:now(),
  273:     ?line {{_, R1}, {_, R2}} = spawn_simple_test(Start, Stop, Succ),
  274:     ?line TS1 = erlang:now(),
  275:     %%
  276:     ?line ok = fprof:trace(start, TraceFile),
  277:     ?line {{P1, R3}, {P2, R4}} = spawn_simple_test(Start, Stop, Succ),
  278:     ?line ok = fprof:trace(stop),
  279:     ?line TS2 = erlang:now(),
  280:     ?line ok = fprof:profile(file, TraceFile),
  281:     ?line ok = fprof:analyse(),
  282:     ?line ok = fprof:analyse(dest, AnalysisFile),
  283:     ?line ok = fprof:stop(),
  284:     ?line R1 = R3,
  285:     ?line R2 = R4,
  286:     %%
  287:     ?line {ok, [T, P]} = parse(AnalysisFile),
  288:     ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
  289:     ?line ok = verify(T, P),
  290:     ?line Proc1 = pid_to_list(P1),
  291:     ?line Proc2 = pid_to_list(P2),
  292:     ?line Proc0 = pid_to_list(self()),
  293:     ?line io:format("~p~n ~p ~p ~p~n", [P, Proc0, Proc1, Proc2]),
  294:     ?line [{analysis_options, _}, [{totals, _, Acc, _}] | Procs] = P,
  295:     ?line [[{Proc0, _, undefined, _} | _]] = 
  296: 	lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc0 -> true;
  297: 			 (_) -> false
  298: 		     end, Procs),
  299:     ?line [[{Proc1, _, undefined, _},
  300: 	    {spawned_by, Proc0},
  301: 	    {spawned_as, {erlang, apply, ["#Fun"++_, []]}},
  302: 	    {initial_calls, [{erlang, apply, 2}, 
  303: 			     {?MODULE, '-spawn_simple_test/3-fun-0-', 4}]} 
  304: 	    | _]] = 
  305: 	lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc1 -> true;
  306: 			 (_) -> false
  307: 		     end, Procs),
  308:     ?line [[{Proc2, _, undefined, _},
  309: 	    {spawned_by, Proc0},
  310: 	    {spawned_as, {erlang, apply, ["#Fun"++_, []]}},
  311: 	    {initial_calls, [{erlang, apply, 2}, 
  312: 			     {?MODULE, '-spawn_simple_test/3-fun-1-', 4}]} 
  313: 	    | _]] = 
  314: 	lists:filter(fun ([Pt | _]) when element(1, Pt) == Proc2 -> true;
  315: 			 (_) -> false
  316: 		     end, Procs),
  317:     ?line 3 = length(Procs),
  318:     ?line R1 = lists:reverse(R2),
  319:     %%
  320:     ?line check_own_and_acc(TraceFile,AnalysisFile),
  321:     %%
  322:     ?line ets:delete(T),
  323:     ?line file:delete(TraceFile),
  324:     ?line file:delete(AnalysisFile),
  325:     ?line ?t:timetrap_cancel(Timetrap),
  326:     ?line Acc1 = ts_sub(TS1, TS0),
  327:     ?line Acc2 = ts_sub(TS2, TS1),
  328:     ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc2, Acc1]),
  329:     {comment, io_lib:format("~p times slower", [Acc2/Acc1])}.
  330: 
  331: 
  332: spawn_simple_test(Start, Stop, Succ) ->
  333:     Parent = self(),
  334:     Seq = 
  335: 	spawn_link(
  336: 	  fun () ->
  337: 		  Parent ! {self(), seq(Start, Stop, Succ)}
  338: 	  end),
  339:     SeqR = 
  340: 	spawn_link(
  341: 	  fun () ->
  342: 		  Parent ! {self(), seq_r(Start, Stop, Succ)}
  343: 	  end),
  344:     receive {Seq, SeqResult} ->
  345: 	    receive {SeqR, SeqRResult} ->
  346: 		    {{Seq, SeqResult}, {SeqR, SeqRResult}}
  347: 	    end
  348:     end.
  349: 
  350: 
  351: 
  352: %%%---------------------------------------------------------------------
  353: 
  354: imm_tail_seq(doc) ->
  355:     ["Tests a tail recursive variant of lists:seq/3 ",
  356:      "with immediate trace to profile"];
  357: imm_tail_seq(suite) ->
  358:     [];
  359: imm_tail_seq(Config) when is_list(Config) ->
  360:     ?line Timetrap = ?t:timetrap(?t:seconds(10)),
  361:     ?line PrivDir = ?config(priv_dir, Config),
  362:     ?line AnalysisFile = 
  363: 	filename:join(PrivDir, ?MODULE_STRING"_imm_tail_seq.analysis"),
  364:     ?line Start = 1,
  365:     ?line Stop = 1000,
  366:     ?line Succ = fun (X) -> X + 1 end,
  367:     ?line ok = fprof:stop(kill),
  368:     ?line catch eprof:stop(),
  369:     %%
  370:     ?line TS0 = erlang:now(),
  371:     ?line R0 = seq_r(Start, Stop, Succ),
  372:     ?line TS1 = erlang:now(),
  373:     %%
  374:     ?line profiling = eprof:start_profiling([self()]),
  375:     ?line TS2 = erlang:now(),
  376:     ?line R2 = seq_r(Start, Stop, Succ),
  377:     ?line TS3 = erlang:now(),
  378:     ?line profiling_stopped = eprof:stop_profiling(),
  379:     ?line R2 = R0,
  380:     %%
  381:     ?line eprof:analyze(),
  382:     ?line stopped = eprof:stop(),
  383:     %%
  384:     ?line {ok, Tracer} = fprof:profile(start),
  385:     ?line ok = fprof:trace([start, {tracer, Tracer}]),
  386:     ?line TS4 = erlang:now(),
  387:     ?line R4 = seq_r(Start, Stop, Succ),
  388:     ?line TS5 = erlang:now(),
  389:     ?line ok = fprof:trace(stop),
  390:     ?line ok = fprof:analyse(),
  391:     ?line ok = fprof:analyse(dest, AnalysisFile),
  392:     ?line ok = fprof:stop(),
  393:     ?line R4 = R0,
  394:     %%
  395:     ?line {ok, [T, P]} = parse(AnalysisFile),
  396:     ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
  397:     ?line ok = verify(T, P),
  398:     ?line Proc = pid_to_list(self()),
  399:     ?line case P of
  400: 	      [{analysis_options, _},
  401: 	       [{totals, _, Acc, _}],
  402: 	       [{Proc, _, undefined, _} | _]] ->
  403: 		  ok
  404: 	  end,
  405:     %%
  406:     ?line ets:delete(T),
  407:     ?line file:delete(AnalysisFile),
  408:     ?line ?t:timetrap_cancel(Timetrap),
  409:     ?line Acc1 = ts_sub(TS1, TS0),
  410:     ?line Acc3 = ts_sub(TS3, TS2),
  411:     ?line Acc5 = ts_sub(TS5, TS4),
  412:     ?line io:format("~p (plain), ~p (eprof), ~p (fprof), ~p (cpu)~n", 
  413: 		    [Acc1/1000, Acc3/1000, Acc5/1000, Acc/1000]),
  414:     {comment, io_lib:format("~p/~p (fprof/eprof) times slower", 
  415: 			    [Acc5/Acc1, Acc3/Acc1])}.
  416: 
  417: %%%---------------------------------------------------------------------
  418: 
  419: imm_create_file_slow(doc) ->
  420:     ["Tests a tail recursive variant of lists:seq/3 ",
  421:      "with immediate trace to profile"];
  422: imm_create_file_slow(suite) ->
  423:     [];
  424: imm_create_file_slow(Config) when is_list(Config) ->
  425:     ?line Timetrap = ?t:timetrap(?t:seconds(60)),
  426:     ?line PrivDir = ?config(priv_dir, Config),
  427:     ?line DataFile = 
  428: 	filename:join(PrivDir, ?MODULE_STRING"_imm_create_file_slow.data"),
  429:     ?line AnalysisFile = 
  430: 	filename:join(PrivDir, ?MODULE_STRING"_imm_create_file_slow.analysis"),
  431:     ?line ok = fprof:stop(kill),
  432:     %%
  433:     ?line TS0 = erlang:now(),
  434:     ?line ok = create_file_slow(DataFile, 1024),
  435:     ?line TS1 = erlang:now(),
  436:     ?line ok = file:delete(DataFile),
  437:     %%
  438:     ?line {ok, Tracer} = fprof:profile(start),
  439:     ?line TS2 = erlang:now(),
  440:     ?line ok = fprof:apply(?MODULE, create_file_slow, [DataFile, 1024], 
  441: 			  [{tracer, Tracer}, continue]),
  442:     ?line TS3 = erlang:now(),
  443:     ?line ok = fprof:profile(stop),
  444:     ?line ok = fprof:analyse(),
  445:     ?line ok = fprof:analyse(dest, AnalysisFile),
  446:     ?line ok = fprof:stop(),
  447:     %%
  448:     ?line {ok, [T, P]} = parse(AnalysisFile),
  449:     ?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
  450:     ?line ok = verify(T, P),
  451:     ?line Proc = pid_to_list(self()),
  452:     ?line case P of
  453: 	      [{analysis_options, _},
  454: 	       [{totals, _, Acc, _}],
  455: 	       [{Proc, _, undefined, _} | _]] ->
  456: 		  ok
  457: 	  end,
  458:     %%
  459:     ?line ets:delete(T),
  460:     ?line file:delete(DataFile),
  461:     ?line file:delete(AnalysisFile),
  462:     ?line ?t:timetrap_cancel(Timetrap),
  463:     ?line Acc1 = ts_sub(TS1, TS0),
  464:     ?line Acc3 = ts_sub(TS3, TS2),
  465:     ?line io:format("ts:~w, fprof:~w, bare:~w.~n", [Acc, Acc3, Acc1]),
  466:     {comment, io_lib:format("~p times slower", [Acc3/Acc1])}.
  467: 
  468: %%%---------------------------------------------------------------------
  469: 
  470: imm_compile(doc) ->
  471:     ["Tests to compile a small source file ",
  472:      "with immediate trace to profile"];
  473: imm_compile(suite) ->
  474:     [];
  475: imm_compile(Config) when is_list(Config) ->
  476:     ?line Timetrap = ?t:timetrap(?t:minutes(20)),
  477:     ?line DataDir = ?config(data_dir, Config),
  478:     ?line SourceFile = filename:join(DataDir, "foo.erl"),
  479:     ?line PrivDir = ?config(priv_dir, Config),
  480:     ?line AnalysisFile = 
  481: 	filename:join(PrivDir, ?MODULE_STRING"_imm_compile.analysis"),
  482:     ?line ok = fprof:stop(kill),
  483:     ?line catch eprof:stop(),
  484:     %%
  485:     ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
  486:     ?line TS0 = erlang:now(),
  487:     ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
  488:     ?line TS1 = erlang:now(),
  489:     %%
  490:     ?line profiling = eprof:start_profiling([self()]),
  491:     ?line TS2 = erlang:now(),
  492:     ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
  493:     ?line TS3 = erlang:now(),
  494:     ?line profiling_stopped = eprof:stop_profiling(),
  495:     %%
  496:     ?line eprof:analyze(),
  497:     ?line stopped = eprof:stop(),
  498:     %%
  499:     ?line {ok, Tracer} = fprof:profile(start),
  500:     ?line ok = fprof:trace([start, {tracer, Tracer}]),
  501:     ?line TS4 = erlang:now(),
  502:     ?line {ok, foo, _} = compile:file(SourceFile, [binary]),
  503:     ?line TS5 = erlang:now(),
  504:     ?line ok = fprof:trace(stop),
  505:     %%
  506:     ?line io:format("Analysing...~n"),
  507:     ?line ok = fprof:analyse(dest, AnalysisFile),
  508:     ?line ok = fprof:stop(),
  509:     %%
  510:     ?line {ok, [T, P]} = parse(AnalysisFile),
  511:     ?line io:format("~p~n", [P]),
  512:     ?line Acc1 = ts_sub(TS1, TS0),
  513:     ?line Acc3 = ts_sub(TS3, TS2),
  514:     ?line Acc5 = ts_sub(TS5, TS4),
  515:     ?line io:format("Verifying...~n"),
  516:     ?line ok = verify(T, P),
  517:     ?line case P of
  518: 	      [{analysis_options, _},
  519: 	       [{totals, _, Acc, _}] | _] ->
  520: 		  ok
  521: 	  end,
  522:     %%
  523:     ?line ets:delete(T),
  524:     ?line file:delete(AnalysisFile),
  525:     ?line ?t:timetrap_cancel(Timetrap),
  526:     ?line io:format("~p (plain), ~p (eprof), ~p (fprof), ~p(cpu)~n", 
  527: 		    [Acc1/1000, Acc3/1000, Acc5/1000, Acc/1000]),
  528:     {comment, io_lib:format("~p/~p (fprof/eprof) times slower", 
  529: 			    [Acc5/Acc1, Acc3/Acc1])}.
  530: 
  531: %%%---------------------------------------------------------------------
  532: 
  533: cpu_create_file_slow(doc) ->
  534:     ["Tests the create_file_slow benchmark using cpu_time"];
  535: cpu_create_file_slow(suite) ->
  536:     [];
  537: cpu_create_file_slow(Config) when is_list(Config) ->
  538:     ?line Timetrap = ?t:timetrap(?t:seconds(40)),
  539:     ?line PrivDir = ?config(priv_dir, Config),
  540:     ?line TraceFile = 
  541: 	filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.trace"),
  542:     ?line AnalysisFile = 
  543: 	filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.analysis"),
  544:     ?line DataFile = 
  545: 	filename:join(PrivDir, ?MODULE_STRING"_cpu_create_file_slow.data"),
  546:     ?line ok = fprof:stop(kill),
  547:     %%
  548:     ?line TS0 = erlang:now(),
  549:     ?line Result = (catch fprof:apply(?MODULE, create_file_slow, 
  550: 				      [DataFile, 1024], 
  551: 				      [{file, TraceFile}, cpu_time])),
  552:     ?line TS1 = erlang:now(),
  553:     ?line TestResult = 
  554: 	case Result of 
  555: 	    ok ->
  556: 		?line ok = fprof:profile(file, TraceFile),
  557: 		?line ok = fprof:analyse(),
  558: 		?line ok = fprof:analyse(dest, AnalysisFile),
  559: 		?line ok = fprof:stop(),
  560: 		%%
  561: 		?line {ok, [T, P]} = parse(AnalysisFile),
  562: 		?line io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
  563: 		?line ok = verify(T, P),
  564: 		?line Proc = pid_to_list(self()),
  565: 		?line case P of
  566: 			  [{analysis_options, _},
  567: 			   [{totals, _, Acc, _}],
  568: 			   [{Proc, _, undefined, _} | _]] ->
  569: 			      ok
  570: 		      end,
  571: 		%%
  572: 		?line check_own_and_acc(TraceFile,AnalysisFile),
  573: 		%%
  574: 		?line ets:delete(T),
  575: 		?line file:delete(DataFile),
  576: 		?line file:delete(TraceFile),
  577: 		?line file:delete(AnalysisFile),
  578: 		?line Acc1 = ts_sub(TS1, TS0),
  579: 		?line io:format("cpu_ts:~w, fprof:~w~n", [Acc, Acc1]),
  580: 		{comment, io_lib:format("~p% cpu utilization", 
  581: 					[100*Acc/Acc1])};
  582: 	    {'EXIT', not_supported} -> 
  583: 		case {os:type(), os:version()} of
  584: 		    {{unix, sunos}, {Major, Minor, _}}
  585: 		    when Major >= 5, Minor >= 7 ->
  586: 			test_server:fail(Result);
  587: 		    _ ->
  588: 			{skipped, "not_supported"}
  589: 		end;
  590: 	    _ ->
  591: 		test_server:fail(Result)
  592: 	end,
  593:     ?line ?t:timetrap_cancel(Timetrap),
  594:     TestResult.
  595: 
  596: 
  597: 
  598: %%%---------------------------------------------------------------------
  599: %%% Functions to test
  600: %%%---------------------------------------------------------------------
  601: 
  602: 
  603: 
  604: %% Stack recursive seq
  605: seq(Stop, Stop, Succ) when is_function(Succ) ->
  606:     [Stop];
  607: seq(Start, Stop, Succ) when is_function(Succ) ->
  608:     [Start | seq(Succ(Start), Stop, Succ)].
  609: 
  610: 
  611: 
  612: %% Tail recursive seq, result list is reversed
  613: seq_r(Start, Stop, Succ) when is_function(Succ) ->
  614:     seq_r(Start, Stop, Succ, []).
  615: 
  616: seq_r(Stop, Stop, _, R) ->
  617:     [Stop | R];
  618: seq_r(Start, Stop, Succ, R) ->
  619:     seq_r(Succ(Start), Stop, Succ, [Start | R]).
  620: 
  621: 
  622: 
  623: create_file_slow(Name, N) when is_integer(N), N >= 0 ->
  624:     {ok, FD} = 
  625:         file:open(Name, [raw, write, delayed_write, binary]),
  626:     if N > 256 ->
  627:             ok = file:write(FD, 
  628:                             lists:map(fun (X) -> <<X:32/unsigned>> end,
  629:                             lists:seq(0, 255))),
  630:             ok = create_file_slow(FD, 256, N);
  631:        true ->
  632:             ok = create_file_slow(FD, 0, N)
  633:     end,
  634:     ok = file:close(FD).
  635: 
  636: create_file_slow(_FD, M, M) ->
  637:     ok;
  638: create_file_slow(FD, M, N) ->
  639:     ok = file:write(FD, <<M:32/unsigned>>),
  640:     create_file_slow(FD, M+1, N).
  641: 
  642: 
  643: 
  644: %%%---------------------------------------------------------------------
  645: %%% Profile verification functions
  646: %%%---------------------------------------------------------------------
  647: 
  648: 
  649: 
  650: verify(Tab, [{analysis_options, _},
  651: 	     [{totals, Cnt, Acc, Own} | _] | Processes]) ->
  652:     Processes_1 = 
  653: 	lists:map(
  654: 	  fun ([{Proc, Cnt_P, undefined, Own_P} | _]) ->
  655: 		  case sum_process(Tab, Proc) of
  656: 		      {Proc, Cnt_P, Acc_P, Own_P} = Clocks 
  657: 		      when Acc_P >= Own_P ->
  658: 			  Clocks;
  659: 		      Weird ->
  660: 			  throw({error, [?MODULE, ?LINE, Weird]})
  661: 		  end
  662: 	  end,
  663: 	  Processes),
  664:     case lists:foldl(
  665: 	   fun ({_, Cnt_P2, Acc_P2, Own_P2}, 
  666: 		{totals, Cnt_T, Acc_T, Own_T}) ->
  667: 		   {totals, Cnt_P2+Cnt_T, Acc_P2+Acc_T, Own_P2+Own_T}
  668: 	   end,
  669: 	   {totals, 0, 0, 0},
  670: 	   Processes_1) of
  671: 	{totals, Cnt, Acc_T, Own} when Acc_T >= Acc ->
  672: 	    ok;
  673: 	Weird ->
  674: 	    throw({error, [?MODULE, ?LINE, Weird]})
  675:     end.
  676: 	  
  677: 
  678: 
  679: sum_process(Tab, Proc) ->
  680:     ets_select_fold(
  681:       Tab, [{{{Proc, '_'}, '_'}, [], ['$_']}], 100,
  682:       fun ({{P, MFA}, {Callers, {MFA, Cnt, Acc, Own}, Called}},
  683: 	   {P, Cnt_P, Acc_P, Own_P}) when P == Proc ->
  684: 	      ok = verify_callers(Tab, Proc, MFA, Callers),
  685: 	      ok = verify_called(Tab, Proc, MFA, Called),
  686: 	      {P, Cnt+Cnt_P, Acc+Acc_P, Own+Own_P};
  687: 	  (Weird, Clocks) ->
  688: 	      throw({error, [?MODULE, ?LINE, Weird, Clocks]})
  689:       end,
  690:       {Proc, 0, 0, 0}).
  691: 
  692: verify_callers(_, _, _, []) ->
  693:     ok;
  694: verify_callers(Tab, Proc, MFA, [{Caller, Cnt, Acc, Own} | Tail]) ->
  695:     Id = {Proc, Caller},
  696:     case ets:lookup(Tab, Id) of
  697: 	[{Id, {_, {Caller, _, _, _}, Called}}] ->
  698: 	    case lists:keysearch(MFA, 1, Called) of
  699: 		{value, {MFA, Cnt, Acc, Own}} ->
  700: 		    verify_callers(Tab, Proc, MFA, Tail);
  701: 		false ->
  702: 		    throw({error, [?MODULE, ?LINE, MFA, Id]})
  703: 	    end;
  704: 	Weird ->
  705: 	    throw({error, [?MODULE, ?LINE, Weird]})
  706:     end.
  707: 	      
  708: verify_called(_, _, _, []) ->
  709:     ok;
  710: verify_called(Tab, Proc, MFA, [{Called, Cnt, Acc, Own} | Tail]) ->
  711:     Id = {Proc, Called},
  712:     case ets:lookup(Tab, Id) of
  713: 	[{Id, {Callers, {Called, _, _, _}, _}}] ->
  714: 	    case lists:keysearch(MFA, 1, Callers) of
  715: 		{value, {MFA, Cnt, Acc, Own}} ->
  716: 		    verify_called(Tab, Proc, MFA, Tail);
  717: 		false ->
  718: 		    throw({error, [?MODULE, ?LINE, MFA, Id]})
  719: 	    end;
  720: 	Weird ->
  721: 	    throw({error, [?MODULE, ?LINE, Weird]})
  722:     end.
  723: 
  724: 
  725: 
  726: %% Parse a analysis file and return an Ets table with all function entries, 
  727: %% and a list of process entries. Checks the concistency of the function
  728: %% entries when they are read.
  729: parse(Filename) ->
  730:     case file:open(Filename, [read]) of
  731: 	{ok, FD} ->
  732: 	    Result = parse_stream(FD),
  733: 	    file:close(FD),
  734: 	    Result;
  735: 	Error ->
  736: 	    Error
  737:     end.
  738: 
  739: parse_stream(FD) ->
  740:     Tab = ets:new(fprof_SUITE, []),
  741:     parse_stream(FD, Tab, [], void).
  742: 
  743: parse_stream(FD, Tab, R, Proc) ->
  744:     case catch io:read(FD, '') of
  745: 	{'EXIT', _} ->
  746: 	    {error, [?MODULE, ?LINE]};
  747: 	{ok, Term} ->
  748: 	    case parse_term(Term) of
  749: 		{ok, {analysis_options, _} = Term_1}
  750: 		when Proc == void ->
  751: 		    parse_stream(FD, Tab, [Term_1 | R], analysis_options);
  752: 		{ok, [{totals, _, _, _} | _] = Term_1}
  753: 		when Proc == analysis_options ->
  754: 		    parse_stream(FD, Tab, [Term_1 | R], totals);
  755: 		{ok, [{P, _, _, _} | _] = Term_1} ->
  756: 		    parse_stream(FD, Tab, [Term_1 | R], P);
  757: 		{ok, {_Callers, {MFA, _, _, _}, _Called} = Term_1} 
  758: 		when Proc == totals; is_list(Proc) ->
  759: 		    ets:insert(Tab, {{Proc, MFA}, Term_1}),
  760: 		    parse_stream(FD, Tab, R, Proc);
  761: 		{ok, Term_1} ->
  762: 		    {error, [?MODULE, ?LINE, Term_1]};
  763: 		E ->
  764: 		    E
  765: 	    end;
  766: 	eof ->
  767: 	    {ok, [Tab, lists:reverse(R)]};
  768: 	Error ->
  769: 	    Error
  770:     end.
  771: 
  772: parse_term({Callers, Func, Called})
  773:   when is_list(Callers), is_list(Called) ->
  774:     Callers_1 = lists:map(fun parse_clocks/1, Callers),
  775:     Func_1 = parse_clocks(Func),
  776:     Called_1 = lists:map(fun parse_clocks/1, Called),
  777:     Result = {Callers_1, Func_1, Called_1},
  778:     case chk_invariant(Result) of
  779: 	ok ->
  780: 	    {ok, Result};
  781: 	Error ->
  782: 	    Error
  783:     end;
  784: parse_term([{_, _, _, _} = Clocks | Tail]) ->
  785:     {ok, [parse_clocks(Clocks) | Tail]};
  786: parse_term(Term) ->
  787:     {ok, Term}.
  788: 
  789: parse_clocks({MFA, Cnt, undefined, Own}) ->
  790:     {MFA, Cnt, undefined, round(Own*1000)};
  791: parse_clocks({MFA, Cnt, Acc, Own}) ->
  792:     {MFA, Cnt, round(Acc*1000), round(Own*1000)};
  793: parse_clocks(Clocks) ->
  794:     Clocks.
  795: 
  796: 
  797: 
  798: chk_invariant({Callers, {MFA, Cnt, Acc, Own}, Called} = Term) ->
  799:     {_, Callers_Cnt, Callers_Acc, Callers_Own} = Callers_Sum = sum(Callers),
  800: %    {_, Called_Cnt, Called_Acc, Called_Own} = Called_Sum = sum(Called),
  801:     case {MFA, 
  802: 	  lists:keymember(suspend, 1, Callers),
  803: 	  lists:keymember(garbage_collect, 1, Callers),
  804: 	  Called} of
  805: 	{suspend, false, _, []} ->
  806: 	    ok;
  807: 	{suspend, _, _, _} = Weird ->
  808: 	    {error, [?MODULE, ?LINE, Weird, Term]};
  809: 	{garbage_collect, false, false, []} ->
  810: 	    ok;
  811: 	{garbage_collect, false, false, [{suspend, _, _, _}]} ->
  812: 	    ok;
  813: 	{garbage_collect, _, _, _} = Weird ->
  814: 	    {error, [?MODULE, ?LINE, Weird, Term]};
  815: 	{undefined, false, false, _} 
  816: 	when Callers == [], Cnt == 0, Acc == 0, Own == 0 ->
  817: 	    ok;
  818: 	{undefined, _, _, _} = Weird ->
  819: 	    {error, [?MODULE, ?LINE, Weird, Term]};
  820: 	{_, _, _, _} ->
  821: 	    case chk_self_call(Term) of
  822: 		true when Callers_Cnt /= Cnt; Callers_Acc /= Acc; 
  823: 			  Callers_Own /= Own ->
  824: 		    {error, [?MODULE, ?LINE, Callers_Sum, Term]};
  825: % 		true when Called_Acc + Own /= Acc ->
  826: % 		    io:format("WARNING: ~p:~p, ~p, ~p.~n",
  827: % 			      [?MODULE, ?LINE, Term, Called_Sum]),
  828: % 		    {error, [?MODULE, ?LINE, Term, Called_Sum]};
  829: % 		    ok;
  830: 		true ->
  831: 		    ok;
  832: 		false ->
  833: 		    {error, [?MODULE, ?LINE, Term]}
  834: 	    end
  835:     end.
  836: 
  837: ts_sub({A, B, C}, {A0, B0, C0}) ->
  838:     ((A - A0)*1000000000000 + (B - B0))*1000000 + C - C0.
  839: 
  840: sum(Funcs) ->
  841:     {sum, _Cnt, _Acc, _Own} = 
  842: 	lists:foldl(
  843: 	  fun ({_, C1, A1, O1}, {sum, C2, A2, O2}) ->
  844: 		  {sum, C1+C2, A1+A2, O1+O2}
  845: 	  end,
  846: 	  {sum, 0, 0, 0},
  847: 	  Funcs).
  848: 
  849: chk_self_call({Callers, {MFA, _Cnt, _Acc, _Own}, Called}) ->
  850:     case lists:keysearch(MFA, 1, Callers) of
  851: 	false ->
  852: 	    true;
  853: 	{value, {MFA, C, 0, O}} ->
  854: 	    case lists:keysearch(MFA, 1, Called) of
  855: 		false ->
  856: 		    false;
  857: 		{value, {MFA, C, 0, O}} ->
  858: 		    true;
  859: 		{value, _} ->
  860: 		    false
  861: 	    end;
  862: 	{value, _} ->
  863: 	    false
  864:     end.
  865: 
  866: 
  867: 
  868: %%%---------------------------------------------------------------------
  869: %%% Fairly generic support functions
  870: %%%---------------------------------------------------------------------
  871: 
  872: 
  873: ets_select_fold(Table, MatchSpec, Limit, Fun, Acc) ->
  874:     ets:safe_fixtable(Table, true),
  875:     ets_select_fold_1(ets:select(Table, MatchSpec, Limit), Fun, Acc).
  876: 
  877: ets_select_fold_1('$end_of_table', _, Acc) ->
  878:     Acc;
  879: ets_select_fold_1({Matches, Continuation}, Fun, Acc) ->
  880:     ets_select_fold_1(ets:select(Continuation), 
  881: 		      Fun, 
  882: 		      lists:foldl(Fun, Acc, Matches)).
  883: 
  884: 
  885: 
  886: % ets_select_foreach(Table, MatchSpec, Limit, Fun) ->
  887: %     ets:safe_fixtable(Table, true),
  888: %     ets_select_foreach_1(ets:select(Table, MatchSpec, Limit), Fun).
  889: 
  890: % ets_select_foreach_1('$end_of_table', _) ->
  891: %     ok;
  892: % ets_select_foreach_1({Matches, Continuation}, Fun) ->
  893: %     lists:foreach(Fun, Matches),
  894: %     ets_select_foreach_1(ets:select(Continuation), Fun).
  895: 
  896: 
  897: %%%---------------------------------------------------------------------
  898: %%% Simple smulation of fprof used for checking own and acc times for
  899: %%% each function.
  900: %%% The function 'undefined' is ignored
  901: %%%---------------------------------------------------------------------
  902: 
  903: %% check_own_and_acc_traced(TraceFile, AnalysisFile) ->
  904: %%     check_own_and_acc(TraceFile, AnalysisFile, fun handle_trace_traced/2).
  905: 
  906: check_own_and_acc(TraceFile, AnalysisFile) ->
  907:     check_own_and_acc(TraceFile, AnalysisFile, fun handle_trace/2).
  908: 
  909: check_own_and_acc(TraceFile, AnalysisFile, HandlerFun) ->
  910:     dbg:trace_client(file,TraceFile,{HandlerFun,{init,self()}}),
  911:     receive {result,Result} -> 
  912: 	    compare(Result,get_own_and_acc_from_analysis(AnalysisFile))
  913:     end.
  914: 
  915: %% handle_trace_traced(Trace, Msg) ->
  916: %%     io:format("handle_trace_traced(~p, ~p).", [Trace, Msg]),
  917: %%     handle_trace(Trace, Msg).
  918: 
  919: handle_trace(Trace,{init,Parent}) ->
  920:     ?dbg("~p",[start]),
  921:     ets:new(fprof_verify_tab,[named_table]),
  922:     handle_trace(Trace,Parent);
  923: handle_trace({trace_ts,Pid,in,MFA,TS},P) ->
  924:     ?dbg("~p",[{{in,Pid,MFA},get(Pid)}]),
  925:     case get(Pid) of
  926: 	[suspend|[suspend|_]=NewStack] ->
  927: 	    T = ts_sub(TS,get({Pid,last_ts})),
  928: 	    update_acc(Pid,NewStack,T),
  929: 	    put(Pid,NewStack);
  930: 	[suspend|NewStack] = Stack ->
  931: 	    T = ts_sub(TS,get({Pid,last_ts})),
  932: 	    update_acc(Pid,Stack,T),
  933: 	    put(Pid,NewStack);
  934: 	[] ->
  935: 	    put(Pid,[MFA]),
  936: 	    insert(Pid,MFA);
  937: 	undefined ->
  938: 	    put(first_ts,TS),
  939: 	    put(Pid,[MFA]),
  940: 	    insert(Pid,MFA)
  941:     end,
  942:     put({Pid,last_ts},TS),
  943:     P;
  944: handle_trace({trace_ts,Pid,out,_MfaOrZero,TS},P) ->
  945:     ?dbg("~p",[{{out,Pid,_MfaOrZero},get(Pid)}]),
  946:     T = ts_sub(TS,get({Pid,last_ts})),
  947:     case get(Pid) of
  948: 	[suspend|S] = Stack ->
  949: 	    update_acc(Pid,S,T),
  950: 	    put(Pid,[suspend|Stack]);
  951: 	[MFA|_] = Stack ->
  952: 	    insert(Pid,suspend),
  953: 	    update_own(Pid,MFA,T),
  954: 	    update_acc(Pid,Stack,T),
  955: 	    put(Pid,[suspend|Stack]);
  956: 	[] ->
  957: 	    insert(Pid,suspend),
  958: 	    put(Pid,[suspend])
  959:     end,
  960:     put({Pid,last_ts},TS),
  961:     P;
  962: handle_trace({trace_ts,Pid,call,MFA,{cp,Caller},TS},P) ->
  963:     ?dbg("~p",[{{call,Pid,MFA},get(Pid)}]),
  964:     T = ts_sub(TS,get({Pid,last_ts})),
  965:     case get(Pid) of
  966: 	[MFA|_] = Stack ->
  967: 	    %% recursive
  968: 	    update_own(Pid,MFA,T),
  969: 	    update_acc(Pid,Stack,T);
  970: 	[CallingMFA|_] = Stack when Caller==undefined ->
  971: 	    insert(Pid,MFA),
  972: 	    update_own(Pid,CallingMFA,T),
  973: 	    update_acc(Pid,Stack,T),
  974: 	    put(Pid,[MFA|Stack]);
  975: 	[] when Caller==undefined ->
  976: 	    insert(Pid,MFA),
  977: 	    insert(Pid,MFA),
  978: 	    put(Pid,[MFA]);
  979: 	 Stack0 ->
  980: 	    Stack = [CallingMFA|_] = insert_caller(Caller,Stack0,[]),
  981: 	    insert(Pid,MFA),
  982: 	    insert(Pid,Caller),
  983: 	    update_own(Pid,CallingMFA,T),
  984: 	    update_acc(Pid,Stack,T),
  985: 	    put(Pid,[MFA|Stack])
  986:     end,
  987:     put({Pid,last_ts},TS),
  988:     P;
  989: handle_trace({trace_ts,Pid,return_to,MFA,TS},P) ->
  990:     ?dbg("~p",[{{return_to,Pid,MFA},get(Pid)}]),
  991:     T = ts_sub(TS,get({Pid,last_ts})),
  992:     case get(Pid) of
  993: 	[MFA|_] = Stack ->
  994: 	    %% recursive
  995: 	    update_own(Pid,MFA,T),
  996: 	    update_acc(Pid,Stack,T),
  997: 	    put(Pid,Stack);
  998: 	[ReturnFromMFA,MFA|RestOfStack] = Stack ->
  999: 	    update_own(Pid,ReturnFromMFA,T),
 1000: 	    update_acc(Pid,Stack,T),
 1001: 	    put(Pid,[MFA|RestOfStack]);
 1002: 	[ReturnFromMFA|RestOfStack] = Stack ->
 1003: 	    update_own(Pid,ReturnFromMFA,T),
 1004: 	    update_acc(Pid,Stack,T),
 1005: 	    case find_return_to(MFA,RestOfStack) of
 1006: 		[] when MFA==undefined -> 
 1007: 		    put(Pid,[]);
 1008: 		[] -> 
 1009: 		    insert(Pid,MFA),
 1010: 		    put(Pid,[MFA]);
 1011: 		NewStack ->
 1012: 		    put(Pid,NewStack)
 1013: 	    end
 1014:     end,
 1015:     put({Pid,last_ts},TS),
 1016:     P;
 1017: handle_trace({trace_ts,Pid,gc_start,_,TS},P) ->
 1018:     ?dbg("~p",[{{gc_start,Pid},get(Pid)}]),
 1019:     case get(Pid) of
 1020: 	[suspend|_] = Stack ->
 1021: 	    T = ts_sub(TS,get({Pid,last_ts})),
 1022: 	    insert(Pid,garbage_collect),
 1023: 	    update_acc(Pid,Stack,T),
 1024: 	    put(Pid,[garbage_collect|Stack]);
 1025: 	[CallingMFA|_] = Stack ->
 1026: 	    T = ts_sub(TS,get({Pid,last_ts})),
 1027: 	    insert(Pid,garbage_collect),
 1028: 	    update_own(Pid,CallingMFA,T),
 1029: 	    update_acc(Pid,Stack,T),
 1030: 	    put(Pid,[garbage_collect|Stack]);
 1031: 	undefined ->
 1032: 	    put(first_ts,TS),
 1033: 	    put(Pid,[garbage_collect]),
 1034: 	    insert(Pid,garbage_collect)
 1035:     end,
 1036:     put({Pid,last_ts},TS),
 1037:     P;
 1038: handle_trace({trace_ts,Pid,gc_end,_,TS},P) ->
 1039:     ?dbg("~p",[{{gc_end,Pid},get(Pid)}]),
 1040:     T = ts_sub(TS,get({Pid,last_ts})),
 1041:     case get(Pid) of
 1042: 	[garbage_collect|RestOfStack] = Stack ->
 1043: 	    update_own(Pid,garbage_collect,T),
 1044: 	    update_acc(Pid,Stack,T),
 1045: 	    put(Pid,RestOfStack)
 1046:     end,
 1047:     put({Pid,last_ts},TS),
 1048:     P;
 1049: handle_trace({trace_ts,Pid,spawn,NewPid,{M,F,Args},TS},P) ->
 1050:     MFA = {M,F,length(Args)},
 1051:     ?dbg("~p",[{{spawn,Pid,NewPid,MFA},get(Pid)}]),
 1052:     T = ts_sub(TS,get({Pid,last_ts})),
 1053:     put({NewPid,last_ts},TS),
 1054:     put(NewPid,[suspend,MFA]),
 1055:     insert(NewPid,suspend),
 1056:     insert(NewPid,MFA),
 1057:     case get(Pid) of
 1058: 	[SpawningMFA|_] = Stack ->
 1059: 	    update_own(Pid,SpawningMFA,T),
 1060: 	    update_acc(Pid,Stack,T)
 1061:     end,
 1062:     put({Pid,last_ts},TS),
 1063:     P;
 1064: handle_trace({trace_ts,Pid,exit,_Reason,TS},P) ->
 1065:     ?dbg("~p",[{{exit,Pid,_Reason},get(Pid)}]),
 1066:     T = ts_sub(TS,get({Pid,last_ts})),
 1067:     case get(Pid) of
 1068: 	[DyingMFA|_] = Stack ->
 1069: 	    update_own(Pid,DyingMFA,T),
 1070: 	    update_acc(Pid,Stack,T),
 1071: 	    put(Pid,[]);
 1072: 	[] ->
 1073: 	    ok
 1074:     end,
 1075:     put({Pid,last_ts},TS),
 1076:     P;
 1077: handle_trace({trace_ts,_,Link,_,_},P) 
 1078:   when Link==link;
 1079:        Link==unlink;
 1080:        Link==getting_linked;
 1081:        Link==getting_unlinked ->
 1082:     P;
 1083: handle_trace(end_of_trace,P) ->
 1084:     ?dbg("~p",['end']),
 1085:     Result = ets:tab2list(fprof_verify_tab),
 1086:     {TotOwn,ProcOwns} = get_proc_owns(Result,[],0),
 1087:     TotAcc = ts_sub(get_last_ts(),get(first_ts)),
 1088:     P ! {result,[{totals,TotAcc,TotOwn}|ProcOwns]++Result},
 1089:     P;
 1090: handle_trace(Other,_P) ->
 1091:     exit({unexpected,Other}).
 1092: 
 1093: find_return_to(MFA,[MFA|_]=Stack) ->
 1094:     Stack;
 1095: find_return_to(MFA,[_|Stack]) ->
 1096:     find_return_to(MFA,Stack);
 1097: find_return_to(_MFA,[]) ->
 1098:     [].
 1099: 
 1100: insert_caller(MFA,[MFA|Rest],Result) ->
 1101:     lists:reverse(Result)++[MFA|Rest];
 1102: insert_caller(MFA,[Other|Rest],Result) ->
 1103:     insert_caller(MFA,Rest,[Other|Result]);
 1104: insert_caller(MFA,[],Result) ->
 1105:     lists:reverse([MFA|Result]).
 1106: 
 1107: insert(Pid,MFA) ->
 1108:     case ets:member(fprof_verify_tab,{Pid,MFA}) of
 1109: 	false ->
 1110: 	    ets:insert(fprof_verify_tab,{{Pid,MFA},0,0});
 1111: 	true ->
 1112: 	    ok
 1113:     end.
 1114: 
 1115: update_own(Pid,MFA,T) ->
 1116:     ets:update_counter(fprof_verify_tab,{Pid,MFA},{3,T}).
 1117: 
 1118: update_acc(Pid,[MFA|Rest],T) ->
 1119:     case lists:member(MFA,Rest) of
 1120: 	true -> 
 1121: 	    %% Only charge one time for recursive functions
 1122: 	    ok;
 1123: 	false -> 
 1124: 	    ets:update_counter(fprof_verify_tab,{Pid,MFA},{2,T})
 1125:     end,
 1126:     update_acc(Pid,Rest,T);
 1127: update_acc(_Pid,[],_T) ->
 1128:     ok.
 1129: 
 1130: 
 1131: get_last_ts() ->
 1132:     get_last_ts(get(),{0,0,0}).
 1133: get_last_ts([{{_,last_ts},TS}|Rest],Last) when TS>Last ->
 1134:     get_last_ts(Rest,TS);
 1135: get_last_ts([_|Rest],Last) ->
 1136:     get_last_ts(Rest,Last);
 1137: get_last_ts([],Last) ->
 1138:     Last.
 1139: 
 1140: get_proc_owns([{{Pid,_MFA},_Acc,Own}|Rest],Result,Sum) ->
 1141:     NewResult = 
 1142: 	case lists:keysearch(Pid,1,Result) of
 1143: 	    {value,{Pid,undefined,PidOwn}} ->
 1144: 		lists:keyreplace(Pid,1,Result,{Pid,undefined,PidOwn+Own});
 1145: 	    false ->
 1146: 		[{Pid,undefined,Own}|Result]
 1147:     end,
 1148:     get_proc_owns(Rest,NewResult,Sum+Own);
 1149: get_proc_owns([],Result,Sum) ->
 1150:     {Sum,Result}.
 1151:     
 1152: 
 1153: compare([X|Rest],FprofResult) ->
 1154:     FprofResult1 = 
 1155: 	case lists:member(X,FprofResult) of
 1156: 	    true ->
 1157: 		?dbg("~p",[X]),
 1158: 		lists:delete(X,FprofResult);
 1159: 	    false -> 
 1160: 		case lists:keysearch(element(1,X),1,FprofResult) of
 1161: 		    {value,Fprof} ->
 1162: 			put(compare_error,true),
 1163: 			io:format("Error: Different values\n"
 1164: 				  "Fprof:     ~p\n"
 1165: 				  "Simulator: ~p",[Fprof,X]),
 1166: 			lists:delete(Fprof,FprofResult);
 1167: 		    false ->
 1168: 			put(compare_error,true),
 1169: 			io:format("Error: Missing in fprof: ~p",[X]),
 1170: 			FprofResult
 1171: 		end
 1172: 	end,
 1173:     compare(Rest,FprofResult1);
 1174: compare([],Rest) ->
 1175:     case {remove_undefined(Rest,[]),get(compare_error)} of
 1176: 	{[],undefined} -> ok;
 1177: 	{Error,_} ->
 1178: 	    case Error of
 1179: 		[] -> ok;
 1180: 		_ -> io:format("\nMissing in simulator results:\n~p\n",[Error])
 1181: 	    end,
 1182: 	    ?t:fail({error,mismatch_between_simulator_and_fprof})
 1183:     end.
 1184:     
 1185: remove_undefined([{{_Pid,undefined},_,_}|Rest],Result) ->
 1186:     remove_undefined(Rest,Result);
 1187: remove_undefined([X|Rest],Result) ->
 1188:     remove_undefined(Rest,[X|Result]);
 1189: remove_undefined([],Result) ->
 1190:     Result.
 1191:     
 1192: get_own_and_acc_from_analysis(Log) ->
 1193:     case file:consult(Log) of
 1194: 	{ok,[_Options,[{totals,_,TotAcc,TotOwn}]|Rest]} ->
 1195: 	    get_own_and_acc(undefined,Rest,
 1196: 			    [{totals,m1000(TotAcc),m1000(TotOwn)}]);
 1197: 	Error ->
 1198: 	    exit({error,{cant_open,Log,Error}})
 1199:     end.
 1200: 
 1201: get_own_and_acc(_,[[{PidStr,_,Acc,Own}|_]|Rest],Result) ->
 1202:     Pid = list_to_pid(PidStr),
 1203:     get_own_and_acc(Pid,Rest,[{Pid,m1000(Acc),m1000(Own)}|Result]);
 1204: get_own_and_acc(Pid,[{_Callers,{MFA,_,Acc,Own},_Called}|Rest],Result) ->
 1205:     get_own_and_acc(Pid,Rest,[{{Pid,MFA},m1000(Acc),m1000(Own)}|Result]);
 1206: get_own_and_acc(_,[],Result) ->
 1207:     lists:reverse(Result).
 1208: 
 1209: m1000(undefined) ->
 1210:     undefined;
 1211: m1000(X) ->
 1212:     round(X*1000).
 1213: