%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%% Stress tests of rwmutex implementation.
%%
%% Author: Rickard Green
%%
-module(mtx_SUITE).
%%-define(line_trace,true).
-include_lib("common_test/include/ct.hrl").
-export([all/0,suite/0,groups/0,
init_per_group/2,end_per_group/2, init_per_suite/1,
end_per_suite/1, init_per_testcase/2, end_per_testcase/2]).
-export([long_rwlock/1,
hammer_ets_rwlock/1,
hammer_rwlock/1,
hammer_rwlock_check/1,
hammer_tryrwlock/1,
hammer_tryrwlock_check/1,
hammer_sched_long_rwlock/1,
hammer_sched_long_rwlock_check/1,
hammer_sched_long_freqread_rwlock/1,
hammer_sched_long_freqread_rwlock_check/1,
hammer_sched_long_tryrwlock/1,
hammer_sched_long_tryrwlock_check/1,
hammer_sched_long_freqread_tryrwlock/1,
hammer_sched_long_freqread_tryrwlock_check/1,
hammer_sched_rwlock/1,
hammer_sched_rwlock_check/1,
hammer_sched_freqread_rwlock/1,
hammer_sched_freqread_rwlock_check/1,
hammer_sched_tryrwlock/1,
hammer_sched_tryrwlock_check/1,
hammer_sched_freqread_tryrwlock/1,
hammer_sched_freqread_tryrwlock_check/1]).
init_per_suite(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
Lib = filename:join([DataDir, atom_to_list(?MODULE)]),
ok = erlang:load_nif(Lib, none),
Config.
end_per_suite(Config) when is_list(Config) ->
Config.
init_per_testcase(_Case, Config) ->
Dog = ?t:timetrap(?t:minutes(15)),
[{watchdog, Dog}|Config].
end_per_testcase(_Func, Config) ->
Dog = ?config(watchdog, Config),
?t:timetrap_cancel(Dog).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[long_rwlock, hammer_rwlock_check, hammer_rwlock,
hammer_tryrwlock_check, hammer_tryrwlock,
hammer_ets_rwlock, hammer_sched_long_rwlock_check,
hammer_sched_long_rwlock,
hammer_sched_long_freqread_rwlock_check,
hammer_sched_long_freqread_rwlock,
hammer_sched_long_tryrwlock_check,
hammer_sched_long_tryrwlock,
hammer_sched_long_freqread_tryrwlock_check,
hammer_sched_long_freqread_tryrwlock,
hammer_sched_rwlock_check, hammer_sched_rwlock,
hammer_sched_freqread_rwlock_check,
hammer_sched_freqread_rwlock,
hammer_sched_tryrwlock_check, hammer_sched_tryrwlock,
hammer_sched_freqread_tryrwlock_check,
hammer_sched_freqread_tryrwlock].
groups() ->
[].
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
long_rwlock(Config) when is_list(Config) ->
statistics(runtime),
LLRes = long_rw_test(),
{_, RunTime} = statistics(runtime),
%% A very short run time is expected, since
%% threads in the test mostly wait
?t:format("RunTime=~p~n", [RunTime]),
?line true = RunTime < 100,
?line RunTimeStr = "Run-time during test was "++integer_to_list(RunTime)++" ms.",
case LLRes of
ok ->
{comment, RunTimeStr};
{comment, Comment} ->
{comment, Comment ++ " " ++ RunTimeStr}
end.
hammer_rwlock(Config) when is_list(Config) ->
hammer_rw_test(false).
hammer_rwlock_check(Config) when is_list(Config) ->
hammer_rw_test(true).
hammer_tryrwlock(Config) when is_list(Config) ->
hammer_tryrw_test(false).
hammer_tryrwlock_check(Config) when is_list(Config) ->
hammer_tryrw_test(true).
hammer_sched_rwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, false, true, 0, 0).
hammer_sched_rwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, true, true, 0, 0).
hammer_sched_freqread_rwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, false, true, 0, 0).
hammer_sched_freqread_rwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, true, true, 0, 0).
hammer_sched_tryrwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, false, false, 0, 100).
hammer_sched_tryrwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, true, false, 0, 100).
hammer_sched_freqread_tryrwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, false, false, 0, 100).
hammer_sched_freqread_tryrwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, true, false, 0, 100).
hammer_sched_long_rwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, false, true, 100, 0).
hammer_sched_long_rwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, true, true, 100, 0).
hammer_sched_long_freqread_rwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, false, true, 100, 0).
hammer_sched_long_freqread_rwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, true, true, 100, 0).
hammer_sched_long_tryrwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, false, false, 100, 100).
hammer_sched_long_tryrwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(false, true, false, 100, 100).
hammer_sched_long_freqread_tryrwlock(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, false, false, 100, 100).
hammer_sched_long_freqread_tryrwlock_check(Config) when is_list(Config) ->
hammer_sched_rwlock_test(true, true, false, 100, 100).
hammer_sched_rwlock_test(FreqRead, LockCheck, Blocking, WaitLocked, WaitUnlocked) ->
case create_rwlock(FreqRead, LockCheck) of
enotsup ->
{skipped, "Not supported."};
RWLock ->
Onln = erlang:system_info(schedulers_online),
NWPs = case Onln div 3 of
1 -> case Onln < 4 of
true -> 1;
false -> 2
end;
X -> X
end,
NRPs = Onln - NWPs,
NoLockOps = ((((50000000 div Onln)
div case {Blocking, WaitLocked} of
{false, 0} -> 1;
_ -> 10
end)
div (case WaitLocked == 0 of
true -> 1;
false -> WaitLocked*250
end))
div handicap()),
?t:format("NoLockOps=~p~n", [NoLockOps]),
Sleep = case Blocking of
true -> NoLockOps;
false -> NoLockOps div 10
end,
WPs = lists:map(
fun (Sched) ->
spawn_opt(
fun () ->
io:format("Writer on scheduler ~p.~n",
[Sched]),
Sched = erlang:system_info(scheduler_id),
receive go -> gone end,
hammer_sched_rwlock_proc(RWLock,
Blocking,
true,
WaitLocked,
WaitUnlocked,
NoLockOps,
Sleep),
Sched = erlang:system_info(scheduler_id)
end,
[link, {scheduler, Sched}])
end,
lists:seq(1, NWPs)),
RPs = lists:map(
fun (Sched) ->
spawn_opt(
fun () ->
io:format("Reader on scheduler ~p.~n",
[Sched]),
Sched = erlang:system_info(scheduler_id),
receive go -> gone end,
hammer_sched_rwlock_proc(RWLock,
Blocking,
false,
WaitLocked,
WaitUnlocked,
NoLockOps,
Sleep),
Sched = erlang:system_info(scheduler_id)
end,
[link, {scheduler, Sched}])
end,
lists:seq(NWPs + 1, NWPs + NRPs)),
Procs = WPs ++ RPs,
case {Blocking, WaitLocked} of
{_, 0} -> ok;
{false, _} -> ok;
_ -> statistics(runtime)
end,
lists:foreach(fun (P) -> P ! go end, Procs),
lists:foreach(fun (P) ->
M = erlang:monitor(process, P),
receive
{'DOWN', M, process, P, _} ->
ok
end
end,
Procs),
case {Blocking, WaitLocked} of
{_, 0} -> ok;
{false, _} -> ok;
_ ->
{_, RunTime} = statistics(runtime),
?t:format("RunTime=~p~n", [RunTime]),
?line true = RunTime < 500,
{comment,
"Run-time during test was "
++ integer_to_list(RunTime)
++ " ms."}
end
end.
hammer_sched_rwlock_proc(_RWLock,
_Blocking,
_WriteOp,
_WaitLocked,
_WaitUnlocked,
0,
_Sleep) ->
ok;
hammer_sched_rwlock_proc(RWLock,
Blocking,
WriteOp,
WaitLocked,
WaitUnlocked,
Times,
Sleep) when Times rem Sleep == 0 ->
rwlock_op(RWLock, Blocking, WriteOp, WaitLocked, WaitUnlocked),
hammer_sched_rwlock_proc(RWLock,
Blocking,
WriteOp,
WaitLocked,
WaitUnlocked,
Times - 1,
Sleep);
hammer_sched_rwlock_proc(RWLock,
Blocking,
WriteOp,
WaitLocked,
WaitUnlocked,
Times,
Sleep) ->
rwlock_op(RWLock, Blocking, WriteOp, WaitLocked, 0),
hammer_sched_rwlock_proc(RWLock,
Blocking,
WriteOp,
WaitLocked,
WaitUnlocked,
Times - 1,
Sleep).
-define(HAMMER_ETS_RWLOCK_REPEAT_TIMES, 1).
-define(HAMMER_ETS_RWLOCK_TSIZE, 500).
hammer_ets_rwlock(Config) when is_list(Config) ->
{Ops, Procs} = case handicap() of
1 -> {20000, 500};
2 -> {20000, 50};
3 -> {2000, 50};
_ -> {200, 50}
end,
?t:format("Procs=~p~nOps=~p~n", [Procs, Ops]),
lists:foreach(fun (XOpts) ->
?t:format("Running with extra opts: ~p", [XOpts]),
hammer_ets_rwlock_test(XOpts, true, 2, Ops,
Procs, false)
end,
[[],
[{read_concurrency, true}],
[{write_concurrency, true}],
[{read_concurrency, true},{write_concurrency, true}]]),
ok.
%% Aux funcs
long_rw_test() ->
exit(no_nif_implementation).
hammer_rw_test(_Arg) ->
exit(no_nif_implementation).
hammer_tryrw_test(_Arg) ->
exit(no_nif_implementation).
create_rwlock(_FreqRead, _LockCheck) ->
exit(no_nif_implementation).
rwlock_op(_RWLock, _Blocking, _WriteOp, _WaitLocked, _WaitUnlocked) ->
exit(no_nif_implementation).
hammer_ets_rwlock_put_data() ->
put(?MODULE, {"here are some", data, "to store", make_ref()}).
hammer_ets_rwlock_get_data() ->
get(?MODULE).
hammer_ets_rwlock_ops(_T, _UW, _N, _C, _SC, 0) ->
ok;
hammer_ets_rwlock_ops(T, UW, N, C, SC, Tot) when N >= ?HAMMER_ETS_RWLOCK_TSIZE ->
hammer_ets_rwlock_ops(T, UW, 0, C, SC, Tot);
hammer_ets_rwlock_ops(T, UW, N, 0, SC, Tot) ->
case UW of
true ->
true = ets:insert(T, {N, Tot, hammer_ets_rwlock_get_data()});
false ->
[{N, _, _}] = ets:lookup(T, N)
end,
hammer_ets_rwlock_ops(T, UW, N+1, SC, SC, Tot-1);
hammer_ets_rwlock_ops(T, UW, N, C, SC, Tot) ->
case UW of
false ->
true = ets:insert(T, {N, Tot, hammer_ets_rwlock_get_data()});
true ->
[{N, _, _}] = ets:lookup(T, N)
end,
hammer_ets_rwlock_ops(T, UW, N+1, C-1, SC, Tot-1).
hammer_ets_rwlock_init(T, N) when N < ?HAMMER_ETS_RWLOCK_TSIZE ->
ets:insert(T, {N, N, N}),
hammer_ets_rwlock_init(T, N+1);
hammer_ets_rwlock_init(_T, _N) ->
ok.
hammer_ets_rwlock_test(XOpts, UW, C, N, NP, SC) ->
receive after 100 -> ok end,
{TP, TM} = spawn_monitor(
fun () ->
_L = repeat_list(
fun () ->
Caller = self(),
T = fun () ->
Parent = self(),
hammer_ets_rwlock_put_data(),
T=ets:new(x, [public | XOpts]),
hammer_ets_rwlock_init(T, 0),
Ps0 = repeat_list(
fun () ->
spawn_link(
fun () ->
hammer_ets_rwlock_put_data(),
receive go -> ok end,
hammer_ets_rwlock_ops(T, UW, N, C, C, N),
Parent ! {done, self()},
receive after infinity -> ok end
end)
end,
NP - case SC of
false -> 0;
_ -> 1
end),
Ps = case SC of
false -> Ps0;
_ -> [spawn_link(fun () ->
hammer_ets_rwlock_put_data(),
receive go -> ok end,
hammer_ets_rwlock_ops(T, UW, N, SC, SC, N),
Parent ! {done, self()},
receive after infinity -> ok end
end) | Ps0]
end,
Start = now(),
lists:foreach(fun (P) -> P ! go end, Ps),
lists:foreach(fun (P) -> receive {done, P} -> ok end end, Ps),
Stop = now(),
lists:foreach(fun (P) ->
unlink(P),
exit(P, bang),
M = erlang:monitor(process, P),
receive
{'DOWN', M, process, P, _} -> ok
end
end, Ps),
Res = timer:now_diff(Stop, Start)/1000000,
Caller ! {?MODULE, self(), Res}
end,
TP = spawn_link(T),
receive
{?MODULE, TP, Res} ->
Res
end
end,
?HAMMER_ETS_RWLOCK_REPEAT_TIMES)
end),
receive
{'DOWN', TM, process, TP, _} -> ok
end.
repeat_list(Fun, N) ->
repeat_list(Fun, N, []).
repeat_list(_Fun, 0, Acc) ->
Acc;
repeat_list(Fun, N, Acc) ->
repeat_list(Fun, N-1, [Fun()|Acc]).
handicap() ->
X0 = case catch (erlang:system_info(logical_processors_available) >=
erlang:system_info(schedulers_online)) of
true -> 1;
_ -> 2
end,
case erlang:system_info(build_type) of
opt ->
X0;
ReallySlow when ReallySlow == debug;
ReallySlow == valgrind;
ReallySlow == purify ->
X0*3;
_Slow ->
X0*2
end.