1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 1997-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: 
   20: -module(statistics_SUITE).
   21: 
   22: %% Tests the statistics/1 bif.
   23: 
   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,
   26: 	 init_per_testcase/2,
   27: 	 end_per_testcase/2,
   28: 	 wall_clock_zero_diff/1, wall_clock_update/1,
   29: 	 runtime_zero_diff/1,
   30: 	 runtime_update/1, runtime_diff/1,
   31: 	 run_queue_one/1,
   32: 	 scheduler_wall_time/1,
   33: 	 reductions/1, reductions_big/1, garbage_collection/1, io/1,
   34: 	 badarg/1]).
   35: 
   36: %% Internal exports.
   37: 
   38: -export([hog/1]).
   39: 
   40: -include_lib("test_server/include/test_server.hrl").
   41: 
   42: init_per_testcase(_, Config) ->
   43:     ?line Dog = test_server:timetrap(test_server:seconds(300)),
   44:     [{watchdog, Dog}|Config].
   45: 
   46: end_per_testcase(_, Config) ->
   47:     Dog = ?config(watchdog, Config),
   48:     test_server:timetrap_cancel(Dog),
   49:     ok.
   50: 
   51: suite() -> [{ct_hooks,[ts_install_cth]}].
   52: 
   53: all() -> 
   54:     [{group, wall_clock}, {group, runtime}, reductions,
   55:      reductions_big, {group, run_queue}, scheduler_wall_time,
   56:      garbage_collection, io, badarg].
   57: 
   58: groups() -> 
   59:     [{wall_clock, [],
   60:       [wall_clock_zero_diff, wall_clock_update]},
   61:      {runtime, [],
   62:       [runtime_zero_diff, runtime_update, runtime_diff]},
   63:      {run_queue, [], [run_queue_one]}].
   64: 
   65: init_per_suite(Config) ->
   66:     Config.
   67: 
   68: end_per_suite(_Config) ->
   69:     ok.
   70: 
   71: init_per_group(_GroupName, Config) ->
   72:     Config.
   73: 
   74: end_per_group(_GroupName, Config) ->
   75:     Config.
   76: 
   77: 
   78: 
   79: %%% Testing statistics(wall_clock).
   80: 
   81: 
   82: 
   83: wall_clock_zero_diff(doc) ->
   84:     "Tests that the 'Wall clock since last call' element of the result "
   85:     "is zero when statistics(runtime) is called twice in succession.";
   86: wall_clock_zero_diff(Config) when is_list(Config) ->
   87:     wall_clock_zero_diff1(16).
   88: 
   89: wall_clock_zero_diff1(N) when N > 0 ->
   90:     ?line {Time, _} = statistics(wall_clock),
   91:     ?line case statistics(wall_clock) of
   92: 	      {Time, 0} -> ok;
   93: 	      _ -> wall_clock_zero_diff1(N-1)
   94:     end;
   95: wall_clock_zero_diff1(0) ->
   96:     ?line test_server:fail("Difference never zero.").
   97: 
   98: wall_clock_update(doc) ->
   99:     "Test that the time differences returned by two calls to "
  100:     "statistics(wall_clock) are compatible, and are within a small number "
  101:     "of ms of the amount of real time we waited for.";
  102: wall_clock_update(Config) when is_list(Config) ->
  103:     wall_clock_update1(6).
  104: 
  105: wall_clock_update1(N) when N > 0 ->
  106:     ?line {T1_wc_time, _} = statistics(wall_clock),
  107:     ?line receive after 1000 -> ok end,
  108:     ?line {T2_wc_time, Wc_Diff} = statistics(wall_clock),
  109: 
  110:     ?line Wc_Diff = T2_wc_time - T1_wc_time,
  111:     ?line test_server:format("Wall clock diff = ~p; should be  = 1000..1040~n",
  112: 			     [Wc_Diff]),
  113:     case ?t:is_debug() of
  114: 	false ->
  115: 	    ?line true = Wc_Diff =< 1040;
  116: 	true ->
  117: 	    ?line true = Wc_Diff =< 2000	%Be more tolerant in debug-compiled emulator.
  118:     end,
  119:     ?line true = Wc_Diff >= 1000,
  120:     wall_clock_update1(N-1);
  121: wall_clock_update1(0) ->
  122:     ok.
  123: 
  124: 
  125: %%% Test statistics(runtime).
  126: 
  127: 
  128: runtime_zero_diff(doc) ->
  129:     "Tests that the difference between the times returned from two consectuitive "
  130: 	"calls to statistics(runtime) is zero.";
  131: runtime_zero_diff(Config) when is_list(Config) ->
  132:     ?line runtime_zero_diff1(16).
  133: 
  134: runtime_zero_diff1(N) when N > 0 ->
  135:     ?line {T1, _} = statistics(runtime),
  136:     ?line case statistics(runtime) of
  137: 	      {T1, 0} -> ok;
  138: 	      _ -> runtime_zero_diff1(N-1)
  139: 	  end;
  140: runtime_zero_diff1(0) ->
  141:     ?line test_server:fail("statistics(runtime) never returned zero difference").
  142: 
  143: runtime_update(doc) ->
  144:     "Test that the statistics(runtime) returns a substanstially "
  145: 	"updated difference after running a process that takes all CPU "
  146: 	" power of the Erlang process for a second.";
  147: runtime_update(Config) when is_list(Config) ->
  148:     case ?t:is_cover() of
  149: 	false ->
  150: 	    ?line process_flag(priority, high),
  151: 	    do_runtime_update(10);
  152: 	true ->
  153: 	    {skip,"Cover-compiled"}
  154:     end.
  155: 
  156: do_runtime_update(0) ->
  157:     {comment,"Never close enough"};
  158: do_runtime_update(N) ->
  159:     ?line {T1,Diff0} = statistics(runtime),
  160:     ?line spawn_link(fun cpu_heavy/0),
  161:     receive after 1000 -> ok end,
  162:     ?line {T2,Diff} = statistics(runtime),
  163:     ?line true = is_integer(T1+T2+Diff0+Diff),
  164:     ?line test_server:format("T1 = ~p, T2 = ~p, Diff = ~p, T2-T1 = ~p",
  165: 			     [T1,T2,Diff,T2-T1]),
  166:     ?line if
  167: 	      T2 - T1 =:= Diff, 900 =< Diff, Diff =< 1500 -> ok;
  168: 	      true -> do_runtime_update(N-1)
  169: 	  end.
  170:     
  171: cpu_heavy() ->
  172:     cpu_heavy().
  173: 
  174: runtime_diff(doc) ->
  175:     "Test that the difference between two consecutive absolute runtimes is "
  176:     "equal to the last relative runtime. The loop runs a lot of times since "
  177:     "the bug which this test case tests for showed up only rarely.";
  178: runtime_diff(Config) when is_list(Config) ->
  179:     runtime_diff1(1000).
  180: 
  181: runtime_diff1(N) when N > 0 ->
  182:     ?line {T1_wc_time, _} = statistics(runtime),
  183:     ?line do_much(),
  184:     ?line {T2_wc_time, Wc_Diff} = statistics(runtime),
  185:     ?line Wc_Diff = T2_wc_time - T1_wc_time,
  186:     runtime_diff1(N-1);
  187: runtime_diff1(0) ->
  188:     ok.
  189: 
  190: %%% do_much(100000) takes about 760 ms on boromir.
  191: %%% do_much(1000) takes about 8 ms on boromir.
  192: 
  193: do_much() ->
  194:     do_much(1000).
  195: 
  196: do_much(0) ->
  197:     ok;
  198: do_much(N) ->
  199:     _ = 4784728478274827 * 72874284728472,
  200:     do_much(N-1).
  201: 
  202: 
  203: reductions(doc) ->
  204:     "Test that statistics(reductions) is callable, and that "
  205: 	"Total_Reductions and Reductions_Since_Last_Call make sense. "
  206: 	"(This to fail on pre-R3A version of JAM.";
  207: reductions(Config) when is_list(Config) ->
  208:     {Reductions, _} = statistics(reductions),
  209: 
  210:     %% Each loop of reductions/2 takes 4 reductions + that the garbage built
  211:     %% outside the heap in the BIF calls will bump the reductions.
  212:     %% 300 * 4 is more than CONTEXT_REDS (1000).  Thus, there will be one or
  213:     %% more context switches.
  214: 
  215:     Mask = (1 bsl erlang:system_info(wordsize)*8) - 1,
  216:     reductions(300, Reductions, Mask).
  217: 
  218: reductions(N, Previous, Mask) when N > 0 ->
  219:     ?line {Reductions, Diff} = statistics(reductions),
  220:     ?line build_some_garbage(),
  221:     ?line if Reductions > 0 -> ok end,
  222:     ?line if Diff >= 0 -> ok end,
  223:     io:format("Previous = ~p, Reductions = ~p, Diff = ~p, DiffShouldBe = ~p",
  224: 	      [Previous, Reductions, Diff, (Reductions-Previous) band Mask]),
  225:     ?line if Reductions == ((Previous+Diff) band Mask) -> reductions(N-1, Reductions, Mask) end;
  226: reductions(0, _, _) ->
  227:     ok.
  228: 
  229: build_some_garbage() ->
  230:     %% This will build garbage outside the process heap, which will cause
  231:     %% a garbage collection in the scheduler.
  232:     processes().
  233: 
  234: reductions_big(doc) ->
  235:     "Test that the number of reductions can be returned as a big number.";
  236: reductions_big(Config) when is_list(Config) ->
  237:     ?line reductions_big_loop(),
  238:     ok.
  239: 
  240: reductions_big_loop() ->
  241:     erlang:yield(),
  242:     case statistics(reductions) of
  243: 	{Red, Diff} when Red >= 16#7ffFFFF ->
  244: 	    ok = io:format("Reductions = ~w, Diff = ~w", [Red, Diff]);
  245: 	_ ->
  246: 	    reductions_big_loop()
  247:     end.
  248: 
  249: 
  250: %%% Tests of statistics(run_queue).
  251: 
  252: 
  253: run_queue_one(doc) ->
  254:     "Tests that statistics(run_queue) returns 1 if we start a "
  255:     "CPU-bound process.";
  256: run_queue_one(Config) when is_list(Config) ->
  257:     ?line MS = erlang:system_flag(multi_scheduling, block),
  258:     ?line run_queue_one_test(Config),
  259:     ?line erlang:system_flag(multi_scheduling, unblock),
  260:     case MS of
  261: 	blocked ->
  262: 	    {comment,
  263: 	     "Multi-scheduling blocked during test. This test-case "
  264: 	     "was not written to work with multiple schedulers."};
  265: 	_ -> ok
  266:     end.
  267:     
  268: 
  269: run_queue_one_test(Config) when is_list(Config) ->
  270:     ?line _Hog = spawn_link(?MODULE, hog, [self()]),
  271:     ?line receive
  272: 	      hog_started -> ok
  273: 	  end,
  274:     ?line receive after 100 -> ok end,		% Give hog a head start.
  275:     ?line case statistics(run_queue) of
  276: 	      N when N >= 1 -> ok;
  277: 	      Other -> ?line ?t:fail({unexpected,Other})
  278: 	  end,
  279:     ok.
  280: 
  281: %% CPU-bound process, going at low priority.  It will always be ready
  282: %% to run.
  283: 
  284: hog(Pid) ->
  285:     ?line process_flag(priority, low),
  286:     ?line Pid ! hog_started,
  287:     ?line Mon = erlang:monitor(process, Pid),
  288:     ?line hog_iter(0, Mon).
  289: 
  290: hog_iter(N, Mon) when N > 0 ->
  291:     receive
  292: 	{'DOWN', Mon, _, _, _} ->  ok
  293:     after 0 ->
  294: 	    ?line hog_iter(N-1, Mon)
  295:     end;
  296: hog_iter(0, Mon) ->
  297:     ?line hog_iter(10000, Mon).
  298: 
  299: %%% Tests of statistics(scheduler_wall_time).
  300: 
  301: scheduler_wall_time(doc) ->
  302:     "Tests that statistics(scheduler_wall_time) works as intended";
  303: scheduler_wall_time(Config) when is_list(Config) ->
  304:     %% Should return undefined if system_flag is not turned on yet
  305:     undefined = statistics(scheduler_wall_time),
  306:     %% Turn on statistics
  307:     false = erlang:system_flag(scheduler_wall_time, true),
  308:     try
  309: 	Schedulers = erlang:system_info(schedulers_online),
  310: 	%% Let testserver and everyone else finish their work
  311: 	timer:sleep(500),
  312: 	%% Empty load
  313: 	EmptyLoad = get_load(),
  314: 	{false, _} = {lists:any(fun(Load) -> Load > 50 end, EmptyLoad),EmptyLoad},
  315: 	MeMySelfAndI = self(),
  316: 	StartHog = fun() ->
  317: 			   Pid = spawn(?MODULE, hog, [self()]),
  318: 			   receive hog_started -> MeMySelfAndI ! go end,
  319: 			   Pid
  320: 		   end,
  321: 	P1 = StartHog(),
  322: 	%% Max on one, the other schedulers empty (hopefully)
  323: 	%% Be generous the process can jump between schedulers
  324: 	%% which is ok and we don't want the test to fail for wrong reasons
  325: 	_L1 = [S1Load|EmptyScheds1] = get_load(),
  326: 	{true,_}  = {S1Load > 50,S1Load},
  327: 	{false,_} = {lists:any(fun(Load) -> Load > 50 end, EmptyScheds1),EmptyScheds1},
  328: 	{true,_}  = {lists:sum(EmptyScheds1) < 60,EmptyScheds1},
  329: 
  330: 	%% 50% load
  331: 	HalfHogs = [StartHog() || _ <- lists:seq(1, (Schedulers-1) div 2)],
  332: 	HalfLoad = lists:sum(get_load()) div Schedulers,
  333: 	if Schedulers < 2, HalfLoad > 80 -> ok; %% Ok only one scheduler online and one hog
  334: 	   %% We want roughly 50% load
  335: 	   HalfLoad > 40, HalfLoad < 60 -> ok;
  336: 	   true -> exit({halfload, HalfLoad})
  337: 	end,
  338: 
  339: 	%% 100% load
  340: 	LastHogs = [StartHog() || _ <- lists:seq(1, Schedulers div 2)],
  341: 	FullScheds = get_load(),
  342: 	{false,_} = {lists:any(fun(Load) -> Load < 80 end, FullScheds),FullScheds},
  343: 	FullLoad = lists:sum(FullScheds) div Schedulers,
  344: 	if FullLoad > 90 -> ok;
  345: 	   true -> exit({fullload, FullLoad})
  346: 	end,
  347: 
  348: 	[exit(Pid, kill) || Pid <- [P1|HalfHogs++LastHogs]],
  349: 	AfterLoad = get_load(),
  350: 	{false,_} = {lists:any(fun(Load) -> Load > 5 end, AfterLoad),AfterLoad},
  351: 	true = erlang:system_flag(scheduler_wall_time, false)
  352:     after
  353: 	erlang:system_flag(scheduler_wall_time, false)
  354:     end.
  355: 
  356: get_load() ->
  357:     Start = erlang:statistics(scheduler_wall_time),
  358:     timer:sleep(500),
  359:     End = erlang:statistics(scheduler_wall_time),
  360:     lists:reverse(lists:sort(load_percentage(lists:sort(Start),lists:sort(End)))).
  361: 
  362: load_percentage([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) ->
  363:     [100*(WN-WP) div (TN-TP)|load_percentage(Ss, Ps)];
  364: load_percentage([], []) -> [].
  365: 
  366: 
  367: garbage_collection(doc) ->
  368:     "Tests that statistics(garbage_collection) is callable. "
  369:     "It is not clear how to test anything more.";
  370: garbage_collection(Config) when is_list(Config) ->
  371:     ?line Bin = list_to_binary(lists:duplicate(19999, 42)),
  372:     ?line case statistics(garbage_collection) of
  373: 	      {Gcs0,R,0} when is_integer(Gcs0), is_integer(R) ->
  374: 		  ?line io:format("Reclaimed: ~p", [R]),
  375: 		  ?line Gcs = garbage_collection_1(Gcs0, Bin),
  376: 		  ?line io:format("Reclaimed: ~p",
  377: 				  [element(2, statistics(garbage_collection))]),
  378: 		  {comment,integer_to_list(Gcs-Gcs0)++" GCs"}
  379: 	  end.
  380: 
  381: garbage_collection_1(Gcs0, Bin) ->
  382:     case statistics(garbage_collection) of
  383: 	{Gcs,Reclaimed,0} when Gcs >= Gcs0 ->
  384: 	    if
  385: 		Reclaimed > 16#7ffffff ->
  386: 		    Gcs;
  387: 		true ->
  388: 		    _ = binary_to_list(Bin),
  389: 		    erlang:garbage_collect(),
  390: 		    garbage_collection_1(Gcs, Bin)
  391: 	    end
  392:     end.
  393: 
  394: io(doc) ->
  395:     "Tests that statistics(io) is callable. "
  396:     "This could be improved to test something more.";
  397: io(Config) when is_list(Config) ->
  398:     ?line case statistics(io) of
  399: 	      {{input,In},{output,Out}} when is_integer(In), is_integer(Out) -> ok
  400: 	  end.
  401: 
  402: badarg(doc) ->
  403:     "Tests that some illegal arguments to statistics fails.";
  404: badarg(Config) when is_list(Config) ->
  405:     ?line case catch statistics(1) of
  406: 	      {'EXIT', {badarg, _}} -> ok
  407: 	  end,
  408:     ?line case catch statistics(bad_atom) of
  409: 	      {'EXIT', {badarg, _}} -> ok
  410: 	  end.