%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2011. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(old_scheduler_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
init_per_testcase/2, end_per_testcase/2]).
-export([equal/1, many_low/1, few_low/1, max/1, high/1]).
-define(default_timeout, ?t:minutes(11)).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
case catch erlang:system_info(modified_timing_level) of
Level when is_integer(Level) ->
{skipped,
"Modified timing (level " ++
integer_to_list(Level) ++
") is enabled. Testcases gets messed "
"up by modfied timing."};
_ -> [equal, many_low, few_low, max, high]
end.
groups() ->
[].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
%%-----------------------------------------------------------------------------------
%% TEST SUITE DESCRIPTION
%%
%% The test case function spawns two controlling processes: Starter and Receiver.
%% Starter spawns a number of prio A and a number of prio B test processes. Each
%% test process loops for a number of times, sends a report to the Receiver, then
%% loops again. For each report, the Receiver increases a counter that corresponds
%% to the priority of the sender. After a certain amount of time, the Receiver
%% sends the collected data to the main test process and waits for the test case
%% to terminate. From this data, it's possible to calculate the average run time
%% relationship between the prio A and B test processes.
%%
%% Note that in order to be able to run tests with high or max prio test processes,
%% the main test process and the Receiver needs to run at max prio, or they will
%% be starved by the test processes. The controlling processes must not wait for
%% messages from a normal (or low) prio process while max or high prio test processes
%% are running (which happens e.g. if an io function is called).
%%-----------------------------------------------------------------------------------
init_per_testcase(_Case, Config) ->
?line Dog = test_server:timetrap(?default_timeout),
%% main test process needs max prio
?line Prio = process_flag(priority, max),
?line MS = erlang:system_flag(multi_scheduling, block),
[{prio,Prio},{watchdog,Dog},{multi_scheduling, MS}|Config].
end_per_testcase(_Case, Config) ->
erlang:system_flag(multi_scheduling, unblock),
Dog=?config(watchdog, Config),
Prio=?config(prio, Config),
process_flag(priority, Prio),
test_server:timetrap_cancel(Dog),
ok.
ok(Config) when is_list(Config) ->
case ?config(multi_scheduling, Config) of
blocked ->
{comment,
"Multi-scheduling blocked during test. This testcase was not "
"written to work with multiple schedulers."};
_ -> ok
end.
%% Run equal number of low and normal prio processes.
equal(suite) -> [];
equal(doc) -> [];
equal(Config) when is_list(Config) ->
?line Self = self(),
%% specify number of test processes to run
Normal = {normal,500},
Low = {low,500},
%% specify time of test (in seconds)
Time = 30,
%% start controllers
?line Receiver =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, Normal, Low) end),
?line Starter =
spawn(fun() -> starter(Normal, Low, Receiver) end),
%% receive test data from Receiver
?line {NRs,NAvg,LRs,LAvg,Ratio} =
receive
{Receiver,Res} -> Res
end,
%% stop controllers and test processes
?line exit(Starter, kill),
?line exit(Receiver, kill),
io:format("Reports: ~w normal (~w/proc), ~w low (~w/proc). Ratio: ~w~n",
[NRs,NAvg,LRs,LAvg,Ratio]),
%% runtime ratio between normal and low should be ~8
if Ratio < 7.5 ; Ratio > 8.5 ->
?t:fail({bad_ratio,Ratio});
true ->
ok(Config)
end.
%% Run many low and few normal prio processes.
many_low(suite) -> [];
many_low(doc) -> [];
many_low(Config) when is_list(Config) ->
?line Self = self(),
Normal = {normal,1},
Low = {low,1000},
%% specify time of test (in seconds)
Time = 30,
?line Receiver =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, Normal, Low) end),
?line Starter =
spawn(fun() -> starter(Normal, Low, Receiver) end),
?line {NRs,NAvg,LRs,LAvg,Ratio} =
receive
{Receiver,Res} -> Res
end,
?line exit(Starter, kill),
?line exit(Receiver, kill),
io:format("Reports: ~w normal (~w/proc), ~w low (~w/proc). Ratio: ~w~n",
[NRs,NAvg,LRs,LAvg,Ratio]),
if Ratio < 7.5 ; Ratio > 8.5 ->
?t:fail({bad_ratio,Ratio});
true ->
ok(Config)
end.
%% Run few low and many normal prio processes.
few_low(suite) -> [];
few_low(doc) -> [];
few_low(Config) when is_list(Config) ->
?line Self = self(),
Normal = {normal,1000},
Low = {low,1},
%% specify time of test (in seconds)
Time = 30,
?line Receiver =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, Normal, Low) end),
?line Starter =
spawn(fun() -> starter(Normal, Low, Receiver) end),
?line {NRs,NAvg,LRs,LAvg,Ratio} =
receive
{Receiver,Res} -> Res
end,
?line exit(Starter, kill),
?line exit(Receiver, kill),
io:format("Reports: ~w normal (~w/proc), ~w low (~w/proc). Ratio: ~w~n",
[NRs,NAvg,LRs,LAvg,Ratio]),
if Ratio < 7.0 ; Ratio > 8.5 ->
?t:fail({bad_ratio,Ratio});
true ->
ok(Config)
end.
%% Run max prio processes and verify they get at least as much
%% runtime as high, normal and low.
max(suite) -> [];
max(doc) -> [];
max(Config) when is_list(Config) ->
max = process_flag(priority, max), % should already be max (init_per_tc)
?line Self = self(),
Max = {max,2},
High = {high,2},
Normal = {normal,100},
Low = {low,100},
%% specify time of test (in seconds)
Time = 30,
?line Receiver1 =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, Max, High) end),
?line Starter1 =
spawn(fun() -> starter(Max, High, Receiver1) end),
?line {M1Rs,M1Avg,HRs,HAvg,Ratio1} =
receive
{Receiver1,Res1} -> Res1
end,
?line exit(Starter1, kill),
?line exit(Receiver1, kill),
io:format("Reports: ~w max (~w/proc), ~w high (~w/proc). Ratio: ~w~n",
[M1Rs,M1Avg,HRs,HAvg,Ratio1]),
if Ratio1 < 1.0 ->
?t:fail({bad_ratio,Ratio1});
true ->
ok(Config)
end,
?line Receiver2 =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, Max, Normal) end),
?line Starter2 =
spawn(fun() -> starter(Max, Normal, Receiver2) end),
?line {M2Rs,M2Avg,NRs,NAvg,Ratio2} =
receive
{Receiver2,Res2} -> Res2
end,
?line exit(Starter2, kill),
?line exit(Receiver2, kill),
io:format("Reports: ~w max (~w/proc), ~w normal (~w/proc). Ratio: ~w~n",
[M2Rs,M2Avg,NRs,NAvg,Ratio2]),
if Ratio2 < 1.0 ->
?t:fail({bad_ratio,Ratio2});
true ->
ok
end,
?line Receiver3 =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, Max, Low) end),
?line Starter3 =
spawn(fun() -> starter(Max, Low, Receiver3) end),
?line {M3Rs,M3Avg,LRs,LAvg,Ratio3} =
receive
{Receiver3,Res3} -> Res3
end,
?line exit(Starter3, kill),
?line exit(Receiver3, kill),
io:format("Reports: ~w max (~w/proc), ~w low (~w/proc). Ratio: ~w~n",
[M3Rs,M3Avg,LRs,LAvg,Ratio3]),
if Ratio3 < 1.0 ->
?t:fail({bad_ratio,Ratio3});
true ->
ok(Config)
end.
%% Run high prio processes and verify they get at least as much
%% runtime as normal and low.
high(suite) -> [];
high(doc) -> [];
high(Config) when is_list(Config) ->
max = process_flag(priority, max), % should already be max (init_per_tc)
?line Self = self(),
High = {high,2},
Normal = {normal,100},
Low = {low,100},
%% specify time of test (in seconds)
Time = 30,
?line Receiver1 =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, High, Normal) end),
?line Starter1 =
spawn(fun() -> starter(High, Normal, Receiver1) end),
?line {H1Rs,H1Avg,NRs,NAvg,Ratio1} =
receive
{Receiver1,Res1} -> Res1
end,
?line exit(Starter1, kill),
?line exit(Receiver1, kill),
io:format("Reports: ~w high (~w/proc), ~w normal (~w/proc). Ratio: ~w~n",
[H1Rs,H1Avg,NRs,NAvg,Ratio1]),
if Ratio1 < 1.0 ->
?t:fail({bad_ratio,Ratio1});
true ->
ok
end,
?line Receiver2 =
spawn(fun() -> receiver(erlang:monotonic_time(), Time, Self, High, Low) end),
?line Starter2 =
spawn(fun() -> starter(High, Low, Receiver2) end),
?line {H2Rs,H2Avg,LRs,LAvg,Ratio2} =
receive
{Receiver2,Res2} -> Res2
end,
?line exit(Starter2, kill),
?line exit(Receiver2, kill),
io:format("Reports: ~w high (~w/proc), ~w low (~w/proc). Ratio: ~w~n",
[H2Rs,H2Avg,LRs,LAvg,Ratio2]),
if Ratio2 < 1.0 ->
?t:fail({bad_ratio,Ratio2});
true ->
ok(Config)
end.
%%-----------------------------------------------------------------------------------
%% Controller processes and help functions
%%-----------------------------------------------------------------------------------
receiver(T0, TimeSec, Main, {P1,P1N}, {P2,P2N}) ->
%% prio should be max so that mailbox doesn't overflow
process_flag(priority, max),
receiver(T0, TimeSec*1000, Main, P1,P1N,0, P2,P2N,0, 100000).
%% uncomment lines below to get life sign (debug)
receiver(T0, Time, Main, P1,P1N,P1Rs, P2,P2N,P2Rs, 0) ->
% T = erlang:convert_time_unit(erlang:monotonic_time() - T0, native, milli_seconds),
% erlang:display({round(T/1000),P1Rs,P2Rs}),
receiver(T0, Time, Main, P1,P1N,P1Rs, P2,P2N,P2Rs, 100000);
receiver(T0, Time, Main, P1,P1N,P1Rs, P2,P2N,P2Rs, C) ->
Remain = Time - erlang:convert_time_unit(erlang:monotonic_time() - T0,
native, milli_seconds), % test time remaining
Remain1 = if Remain < 0 ->
0;
true ->
Remain
end,
{P1Rs1,P2Rs1} =
receive
{_Pid,P1} -> % report from a P1 process
{P1Rs+1,P2Rs};
{_Pid,P2} -> % report from a P2 process
{P1Rs,P2Rs+1}
after Remain1 ->
{P1Rs,P2Rs}
end,
if Remain > 0 -> % keep going
receiver(T0, Time, Main, P1,P1N,P1Rs1, P2,P2N,P2Rs1, C-1);
true -> % finish
%% calculate results and send to main test process
P1Avg = P1Rs1/P1N,
P2Avg = P2Rs1/P2N,
Ratio = if P2Avg < 1.0 -> P1Avg;
true -> P1Avg/P2Avg
end,
Main ! {self(),{P1Rs1,round(P1Avg),P2Rs1,round(P2Avg),Ratio}},
flush_loop()
end.
starter({P1,P1N}, {P2,P2N}, Receiver) ->
%% start N1 processes with prio P1
start_p(P1, P1N, Receiver),
%% start N2 processes with prio P2
start_p(P2, P2N, Receiver),
erlang:display({started,P1N+P2N}),
flush_loop().
start_p(_, 0, _) ->
ok;
start_p(Prio, N, Receiver) ->
spawn_link(fun() -> p(Prio, Receiver) end),
start_p(Prio, N-1, Receiver).
p(Prio, Receiver) ->
%% set process priority
process_flag(priority, Prio),
p_loop(0, Prio, Receiver).
p_loop(100, Prio, Receiver) ->
receive after 0 -> ok end,
%% if Receiver gone, we're done
case is_process_alive(Receiver) of
false -> exit(bye);
true -> ok
end,
%% send report
Receiver ! {self(),Prio},
p_loop(0, Prio, Receiver);
p_loop(N, Prio, Receiver) ->
p_loop(N+1, Prio, Receiver).
flush_loop() ->
receive _ ->
ok
end,
flush_loop().