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.