diff options
Diffstat (limited to 'lib/mnesia/examples/bench')
-rw-r--r-- | lib/mnesia/examples/bench/Makefile | 10 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/README | 211 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config1 | 21 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config2 | 21 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config3 | 23 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config4 | 23 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config5 | 27 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config6 | 27 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.config7 | 35 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.erl | 327 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench.hrl | 107 | ||||
-rwxr-xr-x | lib/mnesia/examples/bench/bench.sh | 23 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench_generate.erl | 684 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench_populate.erl | 200 | ||||
-rw-r--r-- | lib/mnesia/examples/bench/bench_trans.erl | 184 |
15 files changed, 1923 insertions, 0 deletions
diff --git a/lib/mnesia/examples/bench/Makefile b/lib/mnesia/examples/bench/Makefile new file mode 100644 index 0000000000..55621e8cf4 --- /dev/null +++ b/lib/mnesia/examples/bench/Makefile @@ -0,0 +1,10 @@ + +all: + erl -make + +clean: + rm *.beam + +test: + ./bench.sh bench.config* + diff --git a/lib/mnesia/examples/bench/README b/lib/mnesia/examples/bench/README new file mode 100644 index 0000000000..5d31b5ba25 --- /dev/null +++ b/lib/mnesia/examples/bench/README @@ -0,0 +1,211 @@ +Author : Hakan Mattsson <[email protected]> +Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> + +This is an implementation of a real-time database benchmark +(LMC/UU-01:025), defined by Richard Trembley (LMC) and Miroslaw +Zakrzewski (LMC) . The implementation runs the benchmark on the Mnesia +DBMS which is a part of Erlang/OTP (www.erlang.org). + +The implementation is organized in the following parts: + + bench.erl - main API, startup and configuration + bench.hrl - record definitions + bench_populate.erl - create database and populate it with records + bench_trans.erl - the actual transactions to be benchmarked + bench_generate.erl - request generator, statistics computation + +Compile the files with: + + make all + +and run the benchmarks with: + + make test + +================================================================ + +The benchmark runs on a set of Erlang nodes which should reside on +one processor each. + +There are many options when running the benchmark. Benchmark +configuration parameters may either be stated in a configuration file +or as command line arguments in the Erlang shell. Erlang nodes may +either be started manually or automatically by the benchmark program. + +In its the most automated usage you only need to provide one or more +configuration files and run the + + bench.sh <ConfigFiles> + +script to start all Erlang nodes, populate the database and run the +actual benchmark for each one of the configuration files. The +benchmark results will be displayed at stdout. + +In order to be able to automatically start remote Erlang nodes, +you need to: + + - put the $ERL_TOP/bin directory in your path on all nodes + - bind IP adresses to hostnames (e.g via DNS or /etc/hosts) + - enable usage of rsh so it does not prompt for password + +If you cannot achieve this, it is possible to run the benchmark +anyway, but it requires more manual work to be done for each +execution of the benchmark. + +================================================================ + +For each configuration file given to the bench.sh script: + + - a brand new Erlang node is started + - the bench:run(['YourConfigFile']) function is invoked + - the Erlang node(s) are halted. + +Without arguments, the bench.sh simply starts an Erlang shell. +In that shell you have the ability to invoke Erlang functions, +such as bench:run/1. + +The bench:start_all/1 function analyzes the configuration, starts +all Erlang nodes necessary to perform the benchmark and starts +Mnesia on all these nodes. + +The bench:populate/1 function populates the database according +to the configuration and assumes that Mnesia is up and running +on all nodes. + +The bench:generate/1 function starts the actual benchmark +according to the configuration and assumes that Mnesia is +up and running and that the database is fully populated. +Given some arguments such as + + Args = ['YourConfigFile', {statistics_detail, debug}]. + +the invokation of + + bench:run(Args). + +is equivivalent with: + + SlaveNodes = bench:start_all(Args). + bench:populate(Args). + bench:generate(Args). + bench:stop_slave_nodes(SlaveNodes). + +In case you cannot get the automatic start of remote Erlang nodes to +work (implied by bench:start_all/1) , you may need to manually start +an Erlang node on each host (e.g. with bench.sh without arguments) and +then invoke bench:run/1 or its equivivalents on one of them. + +================================================================ + +The following configuration parameters are valid: + +generator_profile + + Selects the transaction profile of the benchmark. Must be one + of the following atoms: t1, t2, t3, t4, t5, ping, random. + Defaults to random which means that the t1 .. t5 transaction + types are randomly selected according to the benchmark spec. + The other choices means disables the random choice and selects + one particular transaction type to be run over and over again. + +generator_warmup + + Defines how long the request generators should "warm up" the + DBMS before the actual measurements are performed. The unit + is milliseconds and defaults to 2000 (2 seconds). + +generator_duration + + Defines the duration of the actual benchmark measurement activity. + The unit is milliseconds and defaults to 15000 (15 seconds). + +generator_cooldown + + Defines how long the request generators should "cool down" the + DBMS after the actual measurements has been performed. The unit + is milliseconds and defaults to 2000 (2 seconds). + +generator_nodes + + Defines which Erlang nodes that should host request generators. + The default is all connected nodes. + +n_generators_per_node + + Defines how many generator processes that should be running on + each generator node. The default is 2. + +statistics_detail + + Regulates the detail level of statistics. It must be one of the + following atoms: normal, debug and debug2. debug enables a + finer grain of statistics to be reported, but since it requires + more counters, to be updated by the generator processes it may + cause slightly worse benchmark performace figures than the brief + default case, that is normal. debug2 prints out the debug info + and formats it according to LMC's benchmark program. + +storage_type + + Defines whether the database should be kept solely in primary + memory (ram_copies), solely on disc (disc_only_copies) or + in both (disc_copies). The default is ram_copies. Currently + the other choices requires a little bit of manual preparation. + +table_nodes + + Defines which Erlang nodes that should host the tables. + +n_fragments + + Defines how many fragments each table should be divided in. + Default is 100. The fragments are evenly distributed over + all table nodes. The group table not devided in fragments. + +n_replicas + + Defines how many replicas that should be kept of each fragment. + The group table is replicated to all table nodes. + +n_subscribers + + Defines the number of subscriber records. Default 25000. + +n_subscribers + + Defines the number of subscriber records. Default 25000. + +n_groups + + Defines the number of group records. Default 5. + +n_servers + + Defines the number of server records. Default 1. + +write_lock_type + + Defines whether the transactions should use ordinary + write locks or if they utilize sticky write locks. + Must be one of the following atoms: write, sticky_write. + Default is write. + +use_binary_subscriber_key + + Defines whether the subscriber key should be represented + as a string (binary) or as an integer. Default is false. + +always_try_nearest_node + + The benchmark was initially written to test scalability + when more nodes were added to the database and when the + (fragmented) tables were distributed over all nodes. In + such a system the transactions should be evenly distributed + over all nodes. When this option is set to true it is possible + to make fair measurements of master/slave configurations, when + all transactions are performed on on one node. Default is false. + +cookie + + Defines which cookie the Erlang node should use in its + distribution protocol. Must be an atom, default is 'bench'. diff --git a/lib/mnesia/examples/bench/bench.config1 b/lib/mnesia/examples/bench/bench.config1 new file mode 100644 index 0000000000..e53ce51f63 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config1 @@ -0,0 +1,21 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb1 + ]}. +{storage_type, ram_copies}. +{n_replicas, 1}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.config2 b/lib/mnesia/examples/bench/bench.config2 new file mode 100644 index 0000000000..f2f82f01fa --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config2 @@ -0,0 +1,21 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb2 + ]}. +{storage_type, ram_copies}. +{n_replicas, 1}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.config3 b/lib/mnesia/examples/bench/bench.config3 new file mode 100644 index 0000000000..c96e4531fd --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config3 @@ -0,0 +1,23 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1, + bench@wppgpb2 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb3, + bench@wppgpb4 + ]}. +{storage_type, ram_copies}. +{n_replicas, 2}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.config4 b/lib/mnesia/examples/bench/bench.config4 new file mode 100644 index 0000000000..e7c0bf2151 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config4 @@ -0,0 +1,23 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1, + bench@wppgpb2 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb1, + bench@wppgpb2 + ]}. +{storage_type, ram_copies}. +{n_replicas, 2}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.config5 b/lib/mnesia/examples/bench/bench.config5 new file mode 100644 index 0000000000..623ec3fb73 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config5 @@ -0,0 +1,27 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1, + bench@wppgpb2, + bench@wppgpb3, + bench@wppgpb4 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb1, + bench@wppgpb2, + bench@wppgpb3, + bench@wppgpb4 + ]}. +{storage_type, ram_copies}. +{n_replicas, 2}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.config6 b/lib/mnesia/examples/bench/bench.config6 new file mode 100644 index 0000000000..f056890ff4 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config6 @@ -0,0 +1,27 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1, + bench@wppgpb2, + bench@wppgpb3, + bench@wppgpb4 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb5, + bench@wppgpb6, + bench@wppgpb7, + bench@wppgpb8 + ]}. +{storage_type, ram_copies}. +{n_replicas, 2}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.config7 b/lib/mnesia/examples/bench/bench.config7 new file mode 100644 index 0000000000..6a78570e71 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.config7 @@ -0,0 +1,35 @@ +{cookie, bench_cookie}. +{generator_profile, random}. +{statistics_detail, debug}. +{generator_warmup, 120000}. +{generator_duration, 900000}. +{generator_cooldown, 120000}. +{generator_nodes, + [bench@wppgpb1, + bench@wppgpb2, + bench@wppgpb3, + bench@wppgpb4, + bench@wppgpb5, + bench@wppgpb6, + bench@wppgpb7, + bench@wppgpb8 + ]}. +{use_binary_subscriber_key, false}. +{n_generators_per_node, 2}. +{write_lock_type, sticky_write}. +{table_nodes, + [bench@wppgpb1, + bench@wppgpb2, + bench@wppgpb3, + bench@wppgpb4, + bench@wppgpb5, + bench@wppgpb6, + bench@wppgpb7, + bench@wppgpb8 + ]}. +{storage_type, ram_copies}. +{n_replicas, 2}. +{n_fragments, 100}. +{n_subscribers, 500000}. +{n_groups, 100}. +{n_servers, 20}. diff --git a/lib/mnesia/examples/bench/bench.erl b/lib/mnesia/examples/bench/bench.erl new file mode 100644 index 0000000000..d191169296 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.erl @@ -0,0 +1,327 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% File : bench.hrl +%%% Author : Hakan Mattsson <[email protected]> +%%% Purpose : Implement the Canadian database benchmark (LMC/UU-01:025) +%%% Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(bench). +-author('[email protected]'). + +-include("bench.hrl"). + +-export([ + run/0, run/1, + + start_all/0, start_all/1, + populate/0, populate/1, + generate/0, generate/1, + + args_to_config/1, verify_config/2, + start/0, start/1, + stop_slave_nodes/1, + bind_schedulers/0 + ]). + +bind_schedulers() -> + try + %% Avoid first core and bind schedules to the remaining ones + Topo = erlang:system_info(cpu_topology), + erlang:system_flag(cpu_topology,lists:reverse(Topo)), + %% N = erlang:system_info(schedulers), + %% erlang:system_flag(schedulers_online, lists:max([N - 1, 1])), + erlang:system_flag(scheduler_bind_type, default_bind), + timer:sleep(timer:seconds(1)), % Wait for Rickard + erlang:system_info(scheduler_bindings) + catch _:_ -> + %% Ancient systems + ignore + end. + +%% Run the benchmark: +%% +%% - Start all necessary Erlang nodes +%% - Populate the database +%% - Start the traffic generators +%% - Calculate benchmark statistics +%% - Stop the temporary Erlang nodes +run() -> + FileName = "bench.config", + run([FileName]). + +run(Args) -> + C = args_to_config(Args), + SlaveNodes = start_all(C), + bench_populate:start(C), + Result = bench_generate:start(C), + stop_slave_nodes(SlaveNodes), + Result. + +%% Start Mnesia on the local node +start() -> + FileName = 'bench.config', + start([FileName]). + +start(Args) -> + C = args_to_config(Args), + erlang:set_cookie(node(), C#config.cookie), + Nodes = [node() | (((C#config.table_nodes -- C#config.generator_nodes) ++ + C#config.generator_nodes) -- [node()])], + Extra = [{extra_db_nodes, Nodes}], + ?d("Starting Mnesia on node ~p...", [node()]), + case mnesia:start(Extra) of + ok -> + Tables = mnesia:system_info(tables), + io:format(" ok.~n" , []), + ?d("Waiting for ~p tables...", [length(Tables)]), + wait(Tables); + {error, Reason} -> + io:format(" FAILED: ~p~n", [Reason]), + {error, Reason} + end. + +wait(Tables) -> + case mnesia:wait_for_tables(Tables, timer:seconds(10)) of + ok -> + io:format(" loaded.~n", []), + ok; + {timeout, More} -> + io:format(" ~p...", [length(More)]), + wait(More) + end. + +%% Populate the database +populate() -> + FileName = 'bench.config', + populate([FileName]). + +populate(Args) -> + C = args_to_config(Args), + bench_populate:start(C). + +%% Start the traffic generators +generate() -> + FileName = 'bench.config', + generate([FileName]). + +generate(Args) -> + C = args_to_config(Args), + bench_generate:start(C). + +start_all() -> + FileName = 'bench.config', + start_all([FileName]). + +start_all(Args) -> + C = args_to_config(Args), + Nodes = [node() | (((C#config.table_nodes -- C#config.generator_nodes) ++ + C#config.generator_nodes) -- [node()])], + erlang:set_cookie(node(), C#config.cookie), + ?d("Starting Erlang nodes...~n", []), + ?d("~n", []), + SlaveNodes = do_start_all(Nodes, [], C#config.cookie), + Extra = [{extra_db_nodes, Nodes}], + ?d("~n", []), + ?d("Starting Mnesia...", []), + case rpc:multicall(Nodes, mnesia, start, [Extra]) of + {Replies, []} -> + case [R || R <- Replies, R /= ok] of + [] -> + io:format(" ok~n", []), + SlaveNodes; + Bad -> + io:format(" FAILED: ~p~n", [Bad]), + exit({mnesia_start, Bad}) + end; + Bad -> + io:format(" FAILED: ~p~n", [Bad]), + exit({mnesia_start, Bad}) + end. + +do_start_all([Node | Nodes], Acc, Cookie) when is_atom(Node) -> + case string:tokens(atom_to_list(Node), [$@]) of + [Name, Host] -> + Arg = lists:concat(["-setcookie ", Cookie]), + ?d(" ~s", [left(Node)]), + case slave:start_link(Host, Name, Arg) of + {ok, Node} -> + load_modules(Node), + rpc:call(Node, ?MODULE, bind_schedulers, []), + io:format(" started~n", []), + do_start_all(Nodes, [Node | Acc], Cookie); + {error, {already_running, Node}} -> + rpc:call(Node, ?MODULE, bind_schedulers, []), + io:format(" already started~n", []), + do_start_all(Nodes, Acc, Cookie); + {error, Reason} -> + io:format(" FAILED:~p~n", [Reason]), + stop_slave_nodes(Acc), + exit({slave_start_failed, Reason}) + end; + _ -> + ?d(" ~s FAILED: " + "Not valid as node name. Must be 'name@host'.~n", + [left(Node)]), + stop_slave_nodes(Acc), + exit({bad_node_name, Node}) + end; +do_start_all([], StartedNodes, _Cookie) -> + StartedNodes. + +load_modules(Node) -> + Fun = + fun(Mod) -> + case code:get_object_code(Mod) of + {_Module, Bin, Fname} -> + rpc:call(Node, code,load_binary,[Mod,Fname,Bin]); + Other -> + Other + end + end, + lists:foreach(Fun, [bench, bench_generate, bench_populate, bench_trans]). + +stop_slave_nodes([]) -> + ok; +stop_slave_nodes(Nodes) -> + ?d("~n", []), + ?d("Stopping Erlang nodes...~n", []), + ?d("~n", []), + do_stop_slave_nodes(Nodes). + +do_stop_slave_nodes([Node | Nodes]) -> + ?d(" ~s", [left(Node)]), + Res = slave:stop(Node), + io:format(" ~p~n", [Res]), + do_stop_slave_nodes(Nodes); +do_stop_slave_nodes([]) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The configuration +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +args_to_config(C) when is_record(C, config) -> + C; +args_to_config(Args) when is_list(Args) -> + do_args_to_config(Args, []). + +do_args_to_config([{Key, Val} | Rest], Acc) when is_list(Acc) -> + do_args_to_config(Rest, Acc ++ [{Key, Val}]); +do_args_to_config([FileName | Rest], Acc) when is_list(Acc) -> + io:nl(), + ?d("Reading configuration file ~p...", [FileName]), + case file:consult(FileName) of + {ok, Config} -> + io:format(" ok~n", []), + do_args_to_config(Rest, Acc ++ Config); + {error, Reason} -> + io:format(" FAILED: ~s~n", + [[lists:flatten(file:format_error( Reason))]]), + {error, {args_to_config, FileName, Reason}} + end; +do_args_to_config([], Acc) when is_list(Acc) -> + verify_config(Acc, #config{}). + +verify_config([{Tag, Val} | T], C) -> + case Tag of + cookie when is_atom(Val) -> + verify_config(T, C#config{cookie = Val}); + generator_profile when Val == random -> + verify_config(T, C#config{generator_profile = Val}); + generator_profile when Val == t1 -> + verify_config(T, C#config{generator_profile = Val}); + generator_profile when Val == t2 -> + verify_config(T, C#config{generator_profile = Val}); + generator_profile when Val == t3 -> + verify_config(T, C#config{generator_profile = Val}); + generator_profile when Val == t4 -> + verify_config(T, C#config{generator_profile = Val}); + generator_profile when Val == t5 -> + verify_config(T, C#config{generator_profile = Val}); + generator_profile when Val == ping -> + verify_config(T, C#config{generator_profile = Val}); + generator_nodes when is_list(Val) -> + verify_config(T, C#config{generator_nodes = Val}); + n_generators_per_node when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{n_generators_per_node = Val}); + generator_warmup when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{generator_warmup = Val}); + generator_duration when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{generator_duration = Val}); + generator_cooldown when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{generator_cooldown = Val}); + statistics_detail when Val == debug -> + verify_config(T, C#config{statistics_detail = Val}); + statistics_detail when Val == debug2 -> + verify_config(T, C#config{statistics_detail = Val}); + statistics_detail when Val == normal -> + verify_config(T, C#config{statistics_detail = Val}); + table_nodes when is_list(Val) -> + verify_config(T, C#config{table_nodes = Val}); + use_binary_subscriber_key when Val == true -> + verify_config(T, C#config{use_binary_subscriber_key = Val}); + use_binary_subscriber_key when Val == false -> + verify_config(T, C#config{use_binary_subscriber_key = Val}); + storage_type when is_atom(Val) -> + verify_config(T, C#config{storage_type = Val}); + write_lock_type when Val == sticky_write -> + verify_config(T, C#config{write_lock_type = Val}); + write_lock_type when Val == write -> + verify_config(T, C#config{write_lock_type = Val}); + n_replicas when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{n_replicas = Val}); + n_fragments when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{n_fragments = Val}); + n_subscribers when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{n_subscribers = Val}); + n_groups when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{n_groups = Val}); + n_servers when is_integer(Val), Val >= 0 -> + verify_config(T, C#config{n_servers = Val}); + always_try_nearest_node when Val == true; Val == false -> + verify_config(T, C#config{always_try_nearest_node = Val}); + _ -> + ?e("Bad config value: ~p~n", [Tag, Val]), + exit({bad_config_value, {Tag, Val}}) + end; +verify_config([], C) -> + display_config(C), + C; +verify_config(Config, _) -> + ?e("Bad config: ~p~n", [Config]), + exit({bad_config, Config}). + +display_config(C) when is_record(C, config) -> + ?d("~n", []), + ?d("Actual configuration...~n", []), + ?d("~n", []), + Fields = record_info(fields, config), + [config | Values] = tuple_to_list(C), + display_config(Fields, Values). + +display_config([F | Fields], [V | Values]) -> + ?d(" ~s ~p~n", [left(F), V]), + display_config(Fields, Values); +display_config([], []) -> + ?d("~n", []), + ok. + +left(Term) -> + string:left(lists:flatten(io_lib:format("~p", [Term])), 27, $.). diff --git a/lib/mnesia/examples/bench/bench.hrl b/lib/mnesia/examples/bench/bench.hrl new file mode 100644 index 0000000000..7b0e0c1280 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.hrl @@ -0,0 +1,107 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% File : bench.hrl +%%% Author : Hakan Mattsson <[email protected]> +%%% Purpose : Define various database records +%%% Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(config, + { + generator_profile = random, + generator_warmup = timer:seconds(2), + generator_duration = timer:seconds(15), + generator_cooldown = timer:seconds(2), + generator_nodes = [node() | nodes()], + statistics_detail = debug, + n_generators_per_node = 1, + write_lock_type = sticky_write, + table_nodes = [node() | nodes()], + storage_type = ram_copies, + n_subscribers = 25000, + n_groups = 5, + n_servers = 1, + n_replicas = 1, + n_fragments = 100, + use_binary_subscriber_key = false, + always_try_nearest_node = false, + cookie = 'bench' + }). + +-record(subscriber, + { + subscriber_number, % string (10 chars) + subscriber_name, % string (32 chars) + group_id, % integer (uint32) + location, % integer (uint32) + active_sessions, % array of 32 booleans (32 bits) + changed_by, % string (25 chars) + changed_time, % string (25 chars) + suffix + }). + +-record(group, + { + group_id, % integer (uint32) + group_name, % string (32 chars) + allow_read, % array of 32 booleans (32 bits) + allow_insert, % array of 32 booleans (32 bits) + allow_delete % array of 32 booleans (32 bits) + }). + +-record(server, + { + server_key, % {ServerId, SubscriberNumberSuffix} + server_name, % string (32 chars) + no_of_read, % integer (uint32) + no_of_insert, % integer (uint32) + no_of_delete, % integer (uint32) + suffix + }). + +-record(session, + { + session_key, % {SubscriberNumber, ServerId} + session_details, % string (4000 chars) + suffix + }). + +-define(d(Format, Args), + io:format("~s" ++ Format, [string:left(lists:flatten(io_lib:format("~p(~p):", [?MODULE, ?LINE])), 30, $ ) | Args])). + +-define(e(Format, Args), + begin + ok = error_logger:format("~p(~p): " ++ Format, [?MODULE, ?LINE | Args]), + timer:sleep(1000) + end). + +-define(ERROR(M, F, A, R), + ?e("~w:~w~p\n\t ->~p\n", [M, F, A, R])). + +-define(APPLY(M, F, A), + fun() -> + case catch apply(M, F, A) of + ok -> {ok, ok}; + {atomic, R} -> {ok, R}; + {ok, R} -> {ok, R}; + {aborted, R} -> ?ERROR(M, F, A, R); + {error, R} -> ?ERROR(M, F, A, R); + R -> ?ERROR(M, F, A, R) + end + end()). diff --git a/lib/mnesia/examples/bench/bench.sh b/lib/mnesia/examples/bench/bench.sh new file mode 100755 index 0000000000..1f8b5eec52 --- /dev/null +++ b/lib/mnesia/examples/bench/bench.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Author : Hakan Mattsson <[email protected]> +# Purpose : Simplify benchmark execution +# Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> +###################################################################### + +args="-pa .. -boot start_sasl -sasl errlog_type error -sname bench" +set -x + +if [ $# -eq 0 ] ; then + + erl $args + +else + + while [ $# -gt 0 ]; do + + erl $args -s bench run $1 -s erlang halt + shift + done + +fi + diff --git a/lib/mnesia/examples/bench/bench_generate.erl b/lib/mnesia/examples/bench/bench_generate.erl new file mode 100644 index 0000000000..0fccc6c082 --- /dev/null +++ b/lib/mnesia/examples/bench/bench_generate.erl @@ -0,0 +1,684 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% File : bench_generate.hrl +%%% Author : Hakan Mattsson <[email protected]> +%%% Purpose : Start request generators and collect statistics +%%% Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(bench_generate). +-author('[email protected]'). + +-include("bench.hrl"). + +%% Public +-export([start/1]). + +%% Internal +-export([ + monitor_init/2, + generator_init/2, + worker_init/1 + ]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The traffic generator +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% ------------------------------------------------------------------- +%% Start request generators +%% ------------------------------------------------------------------- + +start(C) when is_record(C, config) -> + MonPid = spawn_link(?MODULE, monitor_init, [C, self()]), + receive + {'EXIT', MonPid, Reason} -> + exit(Reason); + {monitor_done, MonPid, Res} -> + Res + end. + +monitor_init(C, Parent) when is_record(C, config) -> + process_flag(trap_exit, true), + %% net_kernel:monitor_nodes(true), %% BUGBUG: Needed in order to re-start generators + Nodes = C#config.generator_nodes, + PerNode = C#config.n_generators_per_node, + Timer = C#config.generator_warmup, + ?d("~n", []), + ?d("Start ~p request generators each at ~p nodes...~n", + [PerNode, length(Nodes)]), + ?d("~n", []), + warmup_sticky(C), + ?d(" ~p seconds warmup...~n", [Timer div 1000]), + Alive = spawn_generators(C, Nodes, PerNode), + erlang:send_after(Timer, self(), warmup_done), + monitor_loop(C, Parent, Alive, []). + +spawn_generators(C, Nodes, PerNode) -> + [spawn_link(Node, ?MODULE, generator_init, [self(), C]) || + Node <- Nodes, + _ <- lists:seq(1, PerNode)]. + +warmup_sticky(C) -> + %% Select one node per fragment as master node + Tabs = [subscriber, session, server, suffix], + Fun = fun(S) -> + {[Node | _], _, Wlock} = nearest_node(S, transaction, C), + Stick = fun() -> [mnesia:read({T, S}, S, Wlock) || T <- Tabs] end, + Args = [transaction, Stick, [], mnesia_frag], + rpc:call(Node, mnesia, activity, Args) + end, + Suffixes = lists:seq(0, C#config.n_fragments - 1), % Assume even distrib. + lists:foreach(Fun, Suffixes). + +%% Main loop for benchmark monitor +monitor_loop(C, Parent, Alive, Deceased) -> + receive + warmup_done -> + multicall(Alive, reset_statistics), + Timer = C#config.generator_duration, + ?d(" ~p seconds actual benchmarking...~n", [Timer div 1000]), + erlang:send_after(Timer, self(), measurement_done), + monitor_loop(C, Parent, Alive, Deceased); + measurement_done -> + Stats = multicall(Alive, get_statistics), + Timer = C#config.generator_cooldown, + ?d(" ~p seconds cooldown...~n", [Timer div 1000]), + erlang:send_after(Timer, self(), {cooldown_done, Stats}), + monitor_loop(C, Parent, Alive, Deceased); + {cooldown_done, Stats} -> + multicall(Alive, stop), + display_statistics(Stats, C), + Parent ! {monitor_done, self(), ok}, + unlink(Parent), + exit(monitor_done); + {nodedown, _Node} -> + monitor_loop(C, Parent, Alive, Deceased); + {nodeup, Node} -> + NeedsBirth = [N || N <- Deceased, N == Node], + Born = spawn_generators(C, NeedsBirth, 1), + monitor_loop(C, Parent, Born ++ Alive, Deceased -- NeedsBirth); + {'EXIT', Pid, Reason} when Pid == Parent -> + exit(Reason); + {'EXIT', Pid, Reason} -> + case lists:member(Pid, Alive) of + true -> + ?d("Generator on node ~p died: ~p~n", [node(Pid), Reason]), + monitor_loop(C, Parent, Alive -- [Pid], [node(Pid) | Deceased]); + false -> + monitor_loop(C, Parent, Alive, Deceased) + end + end. + +%% Send message to a set of processes and wait for their replies +multicall(Pids, Message) -> + Send = + fun(Pid) -> + Ref = erlang:monitor(process, Pid), + Pid ! {self(), Ref, Message}, + {Pid, Ref} + end, + PidRefs = lists:map(Send, Pids), + Collect = + fun({Pid, Ref}) -> + receive + {'DOWN', Ref, process, Pid, Reason} -> + {Pid, {'EXIT', Reason}}; + {Pid, Ref, Reply} -> + erlang:demonitor(Ref), + {Pid, Reply} + end + end, + lists:map(Collect, PidRefs). + +%% Initialize a traffic generator +generator_init(Monitor, C) -> + process_flag(trap_exit, true), + Tables = mnesia:system_info(tables), + ok = mnesia:wait_for_tables(Tables, infinity), + {_Mega, Sec, Micro} = erlang:now(), + Uniq = lists:sum(binary_to_list(term_to_binary(make_ref()))), + random:seed(Uniq, Sec, Micro), + Counters = reset_counters(C, C#config.statistics_detail), + SessionTab = ets:new(bench_sessions, [public, {keypos, 1}]), + generator_loop(Monitor, C, SessionTab, Counters). + +%% Main loop for traffic generator +generator_loop(Monitor, C, SessionTab, Counters) -> + receive + {ReplyTo, Ref, get_statistics} -> + Stats = get_counters(C, Counters), + ReplyTo ! {self(), Ref, Stats}, + generator_loop(Monitor, C, SessionTab, Counters); + {ReplyTo, Ref, reset_statistics} -> + Stats = get_counters(C, Counters), + Counters2 = reset_counters(C, Counters), + ReplyTo ! {self(), Ref, Stats}, + generator_loop(Monitor, C, SessionTab, Counters2); + {_ReplyTo, _Ref, stop} -> + exit(shutdown); + {'EXIT', Pid, Reason} when Pid == Monitor -> + exit(Reason); + {'EXIT', Pid, Reason} -> + Node = node(Pid), + ?d("Worker on node ~p(~p) died: ~p~n", [Node, node(), Reason]), + Key = {worker,Node}, + case get(Key) of + undefined -> ignore; + Pid -> erase(Key); + _ -> ignore + end, + generator_loop(Monitor, C, SessionTab, Counters) + after 0 -> + {Name, {Nodes, Activity, Wlock}, Fun, CommitSessions} = + gen_trans(C, SessionTab), + Before = erlang:now(), + Res = call_worker(Nodes, Activity, Fun, Wlock, mnesia_frag), + After = erlang:now(), + Elapsed = elapsed(Before, After), + post_eval(Monitor, C, Elapsed, Res, Name, CommitSessions, SessionTab, Counters) + end. + +%% Perform a transaction on a node near the data +call_worker([Node | _], Activity, Fun, Wlock, Mod) when Node == node() -> + {Node, catch mnesia:activity(Activity, Fun, [Wlock], Mod)}; +call_worker([Node | _] = Nodes, Activity, Fun, Wlock, Mod) -> + Key = {worker,Node}, + case get(Key) of + Pid when is_pid(Pid) -> + Args = [Activity, Fun, [Wlock], Mod], + Pid ! {activity, self(), Args}, + receive + {'EXIT', Pid, Reason} -> + ?d("Worker on node ~p(~p) died: ~p~n", [Node, node(), Reason]), + erase(Key), + retry_worker(Nodes, Activity, Fun, Wlock, Mod, {'EXIT', Reason}); + {activity_result, Pid, Result} -> + case Result of + {'EXIT', {aborted, {not_local, _}}} -> + retry_worker(Nodes, Activity, Fun, Wlock, Mod, Result); + _ -> + {Node, Result} + end + end; + undefined -> + GenPid = self(), + Pid = spawn_link(Node, ?MODULE, worker_init, [GenPid]), + put(Key, Pid), + call_worker(Nodes, Activity, Fun, Wlock, Mod) + end. + +retry_worker([], _Activity, _Fun, _Wlock, _Mod, Reason) -> + {node(), Reason}; +retry_worker([BadNode | SpareNodes], Activity, Fun, Wlock, Mod, Reason) -> + Nodes = SpareNodes -- [BadNode], + case Nodes of + [] -> + {BadNode, Reason}; + [_] -> + call_worker(Nodes, Activity, Fun, write, Mod); + _ -> + call_worker(Nodes, Activity, Fun, Wlock, Mod) + end. + +worker_init(Parent) -> + Tables = mnesia:system_info(tables), + ok = mnesia:wait_for_tables(Tables, infinity), + worker_loop(Parent). + +%% Main loop for remote workers +worker_loop(Parent) -> + receive + {activity, Parent, [Activity, Fun, Extra, Mod]} -> + Result = (catch mnesia:activity(Activity, Fun, Extra, Mod)), + Parent ! {activity_result, self(), Result}, + worker_loop(Parent) + end. + + +elapsed({Before1, Before2, Before3}, {After1, After2, After3}) -> + After = After1 * 1000000000000 + After2 * 1000000 + After3, + Before = Before1 * 1000000000000 + Before2 * 1000000 + Before3, + After - Before. + +%% Lookup counters +get_counters(_C, {table, Tab}) -> + ets:match_object(Tab, '_'); +get_counters(_C, {NM, NC, NA, NB}) -> + Trans = any, + Node = somewhere, + [{{Trans, n_micros, Node}, NM}, + {{Trans, n_commits, Node}, NC}, + {{Trans, n_aborts, Node}, NA}, + {{Trans, n_branches_executed, Node}, NB}]. + +% Clear all counters +reset_counters(_C, normal) -> + {0, 0, 0, 0}; +reset_counters(C, {_, _, _, _}) -> + reset_counters(C, normal); +reset_counters(C, debug) -> + CounterTab = ets:new(bench_pending, [public, {keypos, 1}]), + reset_counters(C, {table, CounterTab}); +reset_counters(C, debug2) -> + CounterTab = ets:new(bench_pending, [public, {keypos, 1}]), + reset_counters(C, {table, CounterTab}); +reset_counters(C, {table, Tab} = Counters) -> + Names = [n_micros, n_commits, n_aborts, n_branches_executed], + Nodes = C#config.generator_nodes ++ C#config.table_nodes, + TransTypes = [t1, t2, t3, t4, t5, ping], + [ets:insert(Tab, {{Trans, Name, Node}, 0}) || Name <- Names, + Node <- Nodes, + Trans <- TransTypes], + Counters. + +%% Determine the outcome of a transaction and increment the counters +post_eval(Monitor, C, Elapsed, {Node, Res}, Name, CommitSessions, SessionTab, {table, Tab} = Counters) -> + case Res of + {do_commit, BranchExecuted, _} -> + incr(Tab, {Name, n_micros, Node}, Elapsed), + incr(Tab, {Name, n_commits, Node}, 1), + case BranchExecuted of + true -> + incr(Tab, {Name, n_branches_executed, Node}, 1), + commit_session(CommitSessions), + generator_loop(Monitor, C, SessionTab, Counters); + false -> + generator_loop(Monitor, C, SessionTab, Counters) + end; + {'EXIT', {aborted, {do_rollback, BranchExecuted, _}}} -> + incr(Tab, {Name, n_micros, Node}, Elapsed), + incr(Tab, {Name, n_aborts, Node}, 1), + case BranchExecuted of + true -> + incr(Tab, {Name, n_branches_executed, Node}, 1), + generator_loop(Monitor, C, SessionTab, Counters); + false -> + generator_loop(Monitor, C, SessionTab, Counters) + end; + _ -> + ?d("Failed(~p): ~p~n", [Node, Res]), + incr(Tab, {Name, n_micros, Node}, Elapsed), + incr(Tab, {Name, n_aborts, Node}, 1), + generator_loop(Monitor, C, SessionTab, Counters) + end; +post_eval(Monitor, C, Elapsed, {_Node, Res}, _Name, CommitSessions, SessionTab, {NM, NC, NA, NB}) -> + case Res of + {do_commit, BranchExecuted, _} -> + case BranchExecuted of + true -> + commit_session(CommitSessions), + generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC + 1, NA, NB + 1}); + false -> + generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC + 1, NA, NB}) + end; + {'EXIT', {aborted, {do_rollback, BranchExecuted, _}}} -> + case BranchExecuted of + true -> + generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC, NA + 1, NB + 1}); + false -> + generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC, NA + 1, NB}) + end; + _ -> + ?d("Failed: ~p~n", [Res]), + generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC, NA + 1, NB}) + end. + +incr(Tab, Counter, Incr) -> + ets:update_counter(Tab, Counter, Incr). + +commit_session(no_fun) -> + ignore; +commit_session(Fun) when is_function(Fun, 0) -> + Fun(). + +%% Randlomly choose a transaction type according to benchmar spec +gen_trans(C, SessionTab) when C#config.generator_profile == random -> + case random:uniform(100) of + Rand when Rand > 0, Rand =< 25 -> gen_t1(C, SessionTab); + Rand when Rand > 25, Rand =< 50 -> gen_t2(C, SessionTab); + Rand when Rand > 50, Rand =< 70 -> gen_t3(C, SessionTab); + Rand when Rand > 70, Rand =< 85 -> gen_t4(C, SessionTab); + Rand when Rand > 85, Rand =< 100 -> gen_t5(C, SessionTab) + end; +gen_trans(C, SessionTab) -> + case C#config.generator_profile of + t1 -> gen_t1(C, SessionTab); + t2 -> gen_t2(C, SessionTab); + t3 -> gen_t3(C, SessionTab); + t4 -> gen_t4(C, SessionTab); + t5 -> gen_t5(C, SessionTab); + ping -> gen_ping(C, SessionTab) + end. + +gen_t1(C, _SessionTab) -> + SubscrId = random:uniform(C#config.n_subscribers) - 1, + SubscrKey = bench_trans:number_to_key(SubscrId, C), + Location = 4711, + ChangedBy = <<4711:(8*25)>>, + ChangedTime = <<4711:(8*25)>>, + {t1, + nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:update_current_location(Wlock, SubscrKey, Location, ChangedBy, ChangedTime) end, + no_fun + }. + +gen_t2(C, _SessionTab) -> + SubscrId = random:uniform(C#config.n_subscribers) - 1, + SubscrKey = bench_trans:number_to_key(SubscrId, C), + {t2, + nearest_node(SubscrId, sync_dirty, C), + %%nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:read_current_location(Wlock, SubscrKey) end, + no_fun + }. + +gen_t3(C, SessionTab) -> + case ets:first(SessionTab) of + '$end_of_table' -> + %% This generator does not have any session, + %% try reading someone elses session details + SubscrId = random:uniform(C#config.n_subscribers) - 1, + SubscrKey = bench_trans:number_to_key(SubscrId, C), + ServerId = random:uniform(C#config.n_servers) - 1, + ServerBit = 1 bsl ServerId, + {t3, + nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:read_session_details(Wlock, SubscrKey, ServerBit, ServerId) end, + no_fun + }; + {SubscrId, SubscrKey, ServerId} -> + %% This generator do have a session, + %% read its session details + ServerBit = 1 bsl ServerId, + {t3, + nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:read_session_details(Wlock, SubscrKey, ServerBit, ServerId) end, + no_fun + } + end. + +gen_t4(C, SessionTab) -> + %% This generator may already have sessions, + %% create a new session and hope that no other + %% generator already has occupied it + SubscrId = random:uniform(C#config.n_subscribers) - 1, + SubscrKey = bench_trans:number_to_key(SubscrId, C), + ServerId = random:uniform(C#config.n_servers) - 1, + ServerBit = 1 bsl ServerId, + Details = <<4711:(8*2000)>>, + DoRollback = (random:uniform(100) =< 2), + Insert = fun() -> ets:insert(SessionTab, {{SubscrId, SubscrKey, ServerId}, self()}) end, + {t4, + nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:create_session_to_server(Wlock, SubscrKey, ServerBit, ServerId, Details, DoRollback) end, + Insert + }. + +gen_t5(C, SessionTab) -> + case ets:first(SessionTab) of + '$end_of_table' -> + %% This generator does not have any session, + %% try to delete someone elses session details + SubscrId = random:uniform(C#config.n_subscribers) - 1, + SubscrKey = bench_trans:number_to_key(SubscrId, C), + ServerId = random:uniform(C#config.n_servers) - 1, + ServerBit = 1 bsl ServerId, + DoRollback = (random:uniform(100) =< 2), + {t5, + nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:delete_session_from_server(Wlock, SubscrKey, ServerBit, ServerId, DoRollback) end, + no_fun + }; + {SubscrId, SubscrKey, ServerId} -> + %% This generator do have at least one session, + %% delete it. + ServerBit = 1 bsl ServerId, + DoRollback = (random:uniform(100) =< 2), + Delete = fun() -> ets:delete(SessionTab, {SubscrId, SubscrKey, ServerId}) end, + {t5, + nearest_node(SubscrId, transaction, C), + fun(Wlock) -> bench_trans:delete_session_from_server(Wlock, SubscrKey, ServerBit, ServerId, DoRollback) end, + Delete + } + end. + +gen_ping(C, _SessionTab) -> + SubscrId = random:uniform(C#config.n_subscribers) - 1, + {ping, + nearest_node(SubscrId, transaction, C), + fun(_Wlock) -> {do_commit, true, []} end, + no_fun + }. + +%% Select a node as near as the subscriber data as possible +nearest_node(SubscrId, Activity, C) -> + Suffix = bench_trans:number_to_suffix(SubscrId), + case mnesia_frag:table_info(t, s, {suffix, Suffix}, where_to_write) of + [] -> + {[node()], Activity, write}; + [Node] -> + {[Node], Activity, write}; + Nodes -> + Wlock = C#config.write_lock_type, + if + C#config.always_try_nearest_node; Wlock =:= write -> + case lists:member(node(), Nodes) of + true -> + {[node() | Nodes], Activity, Wlock}; + false -> + Node = pick_node(Suffix, C, Nodes), + {[Node | Nodes], Activity, Wlock} + end; + Wlock == sticky_write -> + Node = pick_node(Suffix, C, Nodes), + {[Node | Nodes], Activity, Wlock} + end + end. + +pick_node(Suffix, C, Nodes) -> + Ordered = lists:sort(Nodes), + NumberOfActive = length(Ordered), + PoolSize = length(C#config.table_nodes), + Suffix2 = + case PoolSize rem NumberOfActive of + 0 -> Suffix div (PoolSize div NumberOfActive); + _ -> Suffix + end, + N = (Suffix2 rem NumberOfActive) + 1, + lists:nth(N, Ordered). + +display_statistics(Stats, C) -> + GoodStats = [{node(GenPid), GenStats} || {GenPid, GenStats} <- Stats, + is_list(GenStats)], + FlatStats = [{Type, Name, EvalNode, GenNode, Count} || + {GenNode, GenStats} <- GoodStats, + {{Type, Name, EvalNode}, Count} <- GenStats], + TotalStats = calc_stats_per_tag(lists:keysort(2, FlatStats), 2, []), + {value, {n_aborts, 0, NA, 0, 0}} = + lists:keysearch(n_aborts, 1, TotalStats ++ [{n_aborts, 0, 0, 0, 0}]), + {value, {n_commits, NC, 0, 0, 0}} = + lists:keysearch(n_commits, 1, TotalStats ++ [{n_commits, 0, 0, 0, 0}]), + {value, {n_branches_executed, 0, 0, _NB, 0}} = + lists:keysearch(n_branches_executed, 1, TotalStats ++ [{n_branches_executed, 0, 0, 0, 0}]), + {value, {n_micros, 0, 0, 0, AccMicros}} = + lists:keysearch(n_micros, 1, TotalStats ++ [{n_micros, 0, 0, 0, 0}]), + NT = NA + NC, + NG = length(GoodStats), + NTN = length(C#config.table_nodes), + WallMicros = C#config.generator_duration * 1000 * NG, + Overhead = (catch (WallMicros - AccMicros) / WallMicros), + ?d("~n", []), + ?d("Benchmark result...~n", []), + ?d("~n", []), + ?d(" ~p transactions per second (TPS).~n", [catch ((NT * 1000000 * NG) div AccMicros)]), + ?d(" ~p TPS per table node.~n", [catch ((NT * 1000000 * NG) div (AccMicros * NTN))]), + ?d(" ~p micro seconds in average per transaction, including latency.~n", + [catch (AccMicros div NT)]), + ?d(" ~p transactions. ~f% generator overhead.~n", [NT, Overhead * 100]), + + TypeStats = calc_stats_per_tag(lists:keysort(1, FlatStats), 1, []), + EvalNodeStats = calc_stats_per_tag(lists:keysort(3, FlatStats), 3, []), + GenNodeStats = calc_stats_per_tag(lists:keysort(4, FlatStats), 4, []), + if + C#config.statistics_detail == normal -> + ignore; + true -> + ?d("~n", []), + ?d("Statistics per transaction type...~n", []), + ?d("~n", []), + display_type_stats(" ", TypeStats, NT, AccMicros), + + ?d("~n", []), + ?d("Transaction statistics per table node...~n", []), + ?d("~n", []), + display_calc_stats(" ", EvalNodeStats, NT, AccMicros), + + ?d("~n", []), + ?d("Transaction statistics per generator node...~n", []), + ?d("~n", []), + display_calc_stats(" ", GenNodeStats, NT, AccMicros) + end, + if + C#config.statistics_detail /= debug2 -> + ignore; + true -> + io:format("~n", []), + io:format("------ Test Results ------~n", []), + io:format("Length : ~p sec~n", [C#config.generator_duration div 1000]), + Host = lists:nth(2, string:tokens(atom_to_list(node()), [$@])), + io:format("Processor : ~s~n", [Host]), + io:format("Number of Proc: ~p~n", [NG]), + io:format("~n", []), + display_trans_stats(" ", TypeStats, NT, AccMicros, NG), + io:format("~n", []), + io:format(" Overall Statistics~n", []), + io:format(" Transactions: ~p~n", [NT]), + io:format(" Inner : ~p TPS~n", [catch ((NT * 1000000 * NG) div AccMicros)]), + io:format(" Outer : ~p TPS~n", [catch ((NT * 1000000 * NG) div WallMicros)]), + io:format("~n", []) + end. + + +display_calc_stats(Prefix, [{_Tag, 0, 0, 0, 0} | Rest], NT, Micros) -> + display_calc_stats(Prefix, Rest, NT, Micros); +display_calc_stats(Prefix, [{Tag, NC, NA, _NB, NM} | Rest], NT, Micros) -> + ?d("~s~s n=~s%\ttime=~s%~n", + [Prefix, left(Tag), percent(NC + NA, NT), percent(NM, Micros)]), + display_calc_stats(Prefix, Rest, NT, Micros); +display_calc_stats(_, [], _, _) -> + ok. + +display_type_stats(Prefix, [{_Tag, 0, 0, 0, 0} | Rest], NT, Micros) -> + display_type_stats(Prefix, Rest, NT, Micros); +display_type_stats(Prefix, [{Tag, NC, NA, NB, NM} | Rest], NT, Micros) -> + ?d("~s~s n=~s%\ttime=~s%\tavg micros=~p~n", + [ + Prefix, + left(Tag), + percent(NC + NA, NT), + percent(NM, Micros), + catch (NM div (NC + NA)) + ]), + case NA /= 0 of + true -> ?d("~s ~s% aborted~n", [Prefix, percent(NA, NC + NA)]); + false -> ignore + end, + case NB /= 0 of + true -> ?d("~s ~s% branches executed~n", [Prefix, percent(NB, NC + NA)]); + false -> ignore + end, + display_type_stats(Prefix, Rest, NT, Micros); +display_type_stats(_, [], _, _) -> + ok. + +left(Term) -> + string:left(lists:flatten(io_lib:format("~p", [Term])), 27, $.). + +percent(_Part, 0) -> "infinity"; +percent(Part, Total) -> io_lib:format("~8.4f", [(Part * 100) / Total]). + +calc_stats_per_tag([], _Pos, Acc) -> + lists:sort(Acc); +calc_stats_per_tag([Tuple | _] = FlatStats, Pos, Acc) when size(Tuple) == 5 -> + Tag = element(Pos, Tuple), + do_calc_stats_per_tag(FlatStats, Pos, {Tag, 0, 0, 0, 0}, Acc). + +do_calc_stats_per_tag([Tuple | Rest], Pos, {Tag, NC, NA, NB, NM}, Acc) + when element(Pos, Tuple) == Tag -> + Val = element(5, Tuple), + case element(2, Tuple) of + n_commits -> + do_calc_stats_per_tag(Rest, Pos, {Tag, NC + Val, NA, NB, NM}, Acc); + n_aborts -> + do_calc_stats_per_tag(Rest, Pos, {Tag, NC, NA + Val, NB, NM}, Acc); + n_branches_executed -> + do_calc_stats_per_tag(Rest, Pos, {Tag, NC, NA, NB + Val, NM}, Acc); + n_micros -> + do_calc_stats_per_tag(Rest, Pos, {Tag, NC, NA, NB, NM + Val}, Acc) + end; +do_calc_stats_per_tag(GenStats, Pos, CalcStats, Acc) -> + calc_stats_per_tag(GenStats, Pos, [CalcStats | Acc]). + +display_trans_stats(Prefix, [{_Tag, 0, 0, 0, 0} | Rest], NT, Micros, NG) -> + display_trans_stats(Prefix, Rest, NT, Micros, NG); +display_trans_stats(Prefix, [{Tag, NC, NA, NB, NM} | Rest], NT, Micros, NG) -> + Common = + fun(Name) -> + Sec = NM / (1000000 * NG), + io:format(" ~s: ~p (~p%) Time: ~p sec TPS = ~p~n", + [Name, + NC + NA, + round(((NC + NA) * 100) / NT), + round(Sec), + round((NC + NA) / Sec)]) + end, + Branch = + fun() -> + io:format(" Branches Executed: ~p (~p%)~n", + [NB, round((NB * 100) / (NC + NA))]) + end, + Rollback = + fun() -> + io:format(" Rollback Executed: ~p (~p%)~n", + [NA, round((NA * 100) / (NC + NA))]) + end, + case Tag of + t1 -> + Common("T1"); + t2 -> + Common("T2"); + t3 -> + Common("T3"), + Branch(); + t4 -> + Common("T4"), + Branch(), + Rollback(); + t5 -> + Common("T5"), + Branch(), + Rollback(); + _ -> + Common(io_lib:format("~p", [Tag])) + end, + display_trans_stats(Prefix, Rest, NT, Micros, NG); +display_trans_stats(_, [], _, _, _) -> + ok. + diff --git a/lib/mnesia/examples/bench/bench_populate.erl b/lib/mnesia/examples/bench/bench_populate.erl new file mode 100644 index 0000000000..f82ee210b6 --- /dev/null +++ b/lib/mnesia/examples/bench/bench_populate.erl @@ -0,0 +1,200 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% File : bench_populate.hrl +%%% Author : Hakan Mattsson <[email protected]> +%%% Purpose : Populate the database +%%% Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(bench_populate). +-author('[email protected]'). + +-include("bench.hrl"). + +%% Public +-export([start/1]). + +%% Populate the database +start(C) when is_record(C, config) -> + ?d("~n",[]), + ?d("Populate database...~n",[]), + ?d("~n",[]), + create_tables(C), + Populate = + fun() -> + populate_subscriber(write, C), + populate_group(write, C), + populate_server(write, C) + end, + mnesia:activity(sync_dirty, Populate, [], mnesia_frag). + +%% ------------------------------------------------------------------- +%% Create the tables +%% ------------------------------------------------------------------- + +create_tables(C) -> + ?d(" Delete old tables...~n",[]), + mnesia:delete_table(group), + mnesia:delete_table(subscriber), + mnesia:delete_table(session), + mnesia:delete_table(server), + mnesia:delete_table(suffix), + + ?d(" Creating ~p tables, with ~p replicas distributed over ~p nodes...~n", + [C#config.storage_type, + C#config.n_replicas, + length(C#config.table_nodes)]), + + %% Create group table + GroupDef = [{C#config.storage_type, C#config.table_nodes}, + {attributes, record_info(fields, group)}], + ?APPLY(mnesia, create_table, [group, GroupDef]), + + %% Create suffix table + FragStorage = + case C#config.storage_type of + ram_copies -> n_ram_copies; + disc_copies -> n_disc_copies; + disc_only_copies -> n_disc_only_copies + end, + FragProps = + [{FragStorage, C#config.n_replicas}, + {node_pool, C#config.table_nodes}, + {n_fragments, C#config.n_fragments}], + SuffixDef = [{frag_properties, FragProps}], + ?APPLY(mnesia, create_table, [suffix, SuffixDef]), + + %% Create subscriber table + SubscriberDef = + [{frag_properties, [{foreign_key, {suffix, #subscriber.suffix}} | FragProps]}, + {attributes, record_info(fields, subscriber)}], + ?APPLY(mnesia, create_table, [subscriber, SubscriberDef]), + + %% Create session table + SessionDef = + [{frag_properties, [{foreign_key, {suffix, #session.suffix}} | FragProps]}, + {attributes, record_info(fields, session)}], + ?APPLY(mnesia, create_table, [session, SessionDef]), + + %% Create server table + ServerDef = + [{frag_properties, [{foreign_key, {suffix, #server.suffix}} | FragProps]}, + {attributes, record_info(fields, server)}], + ?APPLY(mnesia, create_table, [server, ServerDef]). + +%% ------------------------------------------------------------------- +%% Populate the subscriber table +%% ------------------------------------------------------------------- + +populate_subscriber(Wlock, C) -> + random:seed(), + N = C#config.n_subscribers, + ?d(" Populate ~p subscribers...", [N]), + do_populate_subscriber(Wlock, N - 1, C). + +do_populate_subscriber(Wlock, Id, C) when Id >= 0 -> + Suffix = bench_trans:number_to_suffix(Id), + SubscrId = bench_trans:number_to_key(Id, C), + Name = list_to_binary([random:uniform(26) + $A - 1]), + GroupId = random:uniform(C#config.n_groups) - 1, + Subscr = #subscriber{subscriber_number = SubscrId, + subscriber_name = Name, + group_id = GroupId, + location = 0, + active_sessions = 0, + changed_by = <<"">>, + changed_time = <<"">>, + suffix = Suffix}, + ?APPLY(mnesia, write, [subscriber, Subscr, Wlock]), + do_populate_subscriber(Wlock, Id - 1, C); +do_populate_subscriber(_Wlock, _, _) -> + io:format(" totally ~p bytes~n", + [mnesia:table_info(subscriber, memory) * 4]), + ok. + +%% ------------------------------------------------------------------- +%% Populate the group table +%% ------------------------------------------------------------------- + +populate_group(Wlock, C) -> + random:seed(), + N = C#config.n_groups, + ?d(" Populate ~p groups...", [N]), + do_populate_group(Wlock, N - 1, C). + +do_populate_group(Wlock, Id, C) when Id >= 0 -> + Name = list_to_binary(["-group ", integer_to_list(Id), "-"]), + Allow = init_allow(C), + Group = #group{group_id = Id, + group_name = Name, + allow_read = Allow, + allow_insert = Allow, + allow_delete = Allow}, + ?APPLY(mnesia, write, [group, Group, Wlock]), + do_populate_group(Wlock, Id - 1, C); +do_populate_group(_Wlock, _, _) -> + io:format(" totally ~p bytes~n", + [mnesia:table_info(group, memory) * 4]), + ok. + +init_allow(C) -> + do_init_allow(0, C#config.n_servers - 1). + +do_init_allow(Allow, NS) when NS >= 0 -> + case random:uniform(100) < (90 + 1) of + true -> + ServerBit = 1 bsl NS, + do_init_allow(Allow bor ServerBit, NS - 1); + false -> + do_init_allow(Allow, NS - 1) + end; +do_init_allow(Allow, _) -> + Allow. + +%% ------------------------------------------------------------------- +%% Populate the server table +%% ------------------------------------------------------------------- + +populate_server(Wlock, C) -> + random:seed(), + N = C#config.n_servers, + ?d(" Populate ~p servers with 100 records each...", [N]), + do_populate_server(Wlock, N - 1). + +do_populate_server(Wlock, Id) when Id >= 0 -> + populate_server_suffixes(Wlock, Id, 99), + do_populate_server(Wlock, Id - 1); +do_populate_server(_Wlock, _) -> + io:format(" totally ~p bytes~n", + [mnesia:table_info(server, memory) * 4]), + ok. + +populate_server_suffixes(Wlock, Id, Suffix) when Suffix >= 0 -> + Name = list_to_binary(["-server ", integer_to_list(Id), "-"]), + Server = #server{server_key = {Id, Suffix}, + server_name = Name, + no_of_read = 0, + no_of_insert = 0, + no_of_delete = 0, + suffix = Suffix}, + ?APPLY(mnesia, write, [server, Server, Wlock]), + populate_server_suffixes(Wlock, Id, Suffix - 1); +populate_server_suffixes(_Wlock, _, _) -> + ok. + diff --git a/lib/mnesia/examples/bench/bench_trans.erl b/lib/mnesia/examples/bench/bench_trans.erl new file mode 100644 index 0000000000..945715daae --- /dev/null +++ b/lib/mnesia/examples/bench/bench_trans.erl @@ -0,0 +1,184 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% File : bench_trans.hrl +%%% Author : Hakan Mattsson <[email protected]> +%%% Purpose : Implement the transactions in Canadian database benchmark (LMC/UU-01:025) +%%% Created : 21 Jun 2001 by Hakan Mattsson <[email protected]> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(bench_trans). +-author('[email protected]'). + +-include("bench.hrl"). + +-export([ + update_current_location/5, + read_current_location/2, + read_session_details/4, + create_session_to_server/6, + delete_session_from_server/5, + number_to_suffix/1, + number_to_key/2 + ]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The transactions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% ------------------------------------------------------------------- +%% T1 +%% ------------------------------------------------------------------- + +update_current_location(Wlock, SubscrId, Location, ChangedBy, ChangedTime) -> + Suffix = number_to_suffix(SubscrId), + [Subscr] = mnesia:read({subscriber, Suffix}, SubscrId, Wlock), + Subscr2 = Subscr#subscriber{location = Location, + changed_by = ChangedBy, + changed_time = ChangedTime}, + mnesia:write(subscriber, Subscr2, Wlock), + {do_commit, false, [ok]}. + +%% ------------------------------------------------------------------- +%% T2 +%% ------------------------------------------------------------------- + +read_current_location(_Wlock, SubscrId) -> + Suffix = number_to_suffix(SubscrId), + [Subscr] = mnesia:read({subscriber, Suffix}, SubscrId, read), + + Name = Subscr#subscriber.subscriber_name, + Location = Subscr#subscriber.location, + ChangedBy = Subscr#subscriber.changed_by, + ChangedTime = Subscr#subscriber.changed_time, + {do_commit, false, [Name, Location, ChangedBy, ChangedTime]}. + +%% ------------------------------------------------------------------- +%% T3 +%% ------------------------------------------------------------------- + +read_session_details(Wlock, SubscrId, ServerBit, ServerId) -> + Suffix = number_to_suffix(SubscrId), + [Subscr] = mnesia:read({subscriber, Suffix}, SubscrId, read), + %%[Group] = mnesia:read(group, Subscr#subscriber.group_id, read), + [Group] = mnesia:dirty_read(group, Subscr#subscriber.group_id), + + IsAllowed = ((Group#group.allow_read band ServerBit) == ServerBit), + IsActive = ((Subscr#subscriber.active_sessions band ServerBit) == ServerBit), + ExecuteBranch = (IsAllowed and IsActive), + + case ExecuteBranch of + true -> + SessionKey = {SubscrId, ServerId}, + [Session] = mnesia:read({session, Suffix}, SessionKey, read), + + ServerKey = {ServerId, Suffix}, + [Server] = mnesia:read({server, Suffix}, ServerKey, Wlock), + Server2 = Server#server{no_of_read = Server#server.no_of_read + 1}, + mnesia:write(server, Server2, Wlock), + {do_commit, ExecuteBranch, [Session#session.session_details]}; + false -> + {do_commit, ExecuteBranch, []} + end. + +%% ------------------------------------------------------------------- +%% T4 +%% ------------------------------------------------------------------- + +create_session_to_server(Wlock, SubscrId, ServerBit, ServerId, Details, DoRollback) -> + Suffix = number_to_suffix(SubscrId), + [Subscr] = mnesia:read({subscriber, Suffix}, SubscrId, Wlock), + %%[Group] = mnesia:read(group, Subscr#subscriber.group_id, read), + [Group] = mnesia:dirty_read(group, Subscr#subscriber.group_id), + + IsAllowed = ((Group#group.allow_insert band ServerBit) == ServerBit), + IsInactive = ((Subscr#subscriber.active_sessions band ServerBit) == 0), + ExecuteBranch = (IsAllowed and IsInactive), + case ExecuteBranch of + true -> + SessionKey = {SubscrId, ServerId}, + Session = #session{session_key = SessionKey, + session_details = Details, + suffix = Suffix}, + mnesia:write(session, Session, Wlock), + Active = (Subscr#subscriber.active_sessions bor ServerBit), + Subscr2 = Subscr#subscriber{active_sessions = Active}, + mnesia:write(subscriber, Subscr2, Wlock), + + ServerKey = {ServerId, Suffix}, + [Server] = mnesia:read({server, Suffix}, ServerKey, Wlock), + Server2 = Server#server{no_of_insert = Server#server.no_of_insert + 1}, + mnesia:write(server, Server2, Wlock); + false -> + ignore + end, + case DoRollback of + true -> + mnesia:abort({do_rollback, ExecuteBranch, []}); + false -> + {do_commit, ExecuteBranch, []} + end. + +%% ------------------------------------------------------------------- +%% T5 +%% ------------------------------------------------------------------- + +delete_session_from_server(Wlock, SubscrId, ServerBit, ServerId, DoRollback) -> + Suffix = number_to_suffix(SubscrId), + [Subscr] = mnesia:read({subscriber, Suffix}, SubscrId, Wlock), + %%[Group] = mnesia:read(group, Subscr#subscriber.group_id, read), + [Group] = mnesia:dirty_read(group, Subscr#subscriber.group_id), + + IsAllowed = ((Group#group.allow_delete band ServerBit) == ServerBit), + IsActive = ((Subscr#subscriber.active_sessions band ServerBit) == ServerBit), + ExecuteBranch = (IsAllowed and IsActive), + case ExecuteBranch of + true -> + SessionKey = {SubscrId, ServerId}, + mnesia:delete({session, Suffix}, SessionKey, Wlock), + Active = (Subscr#subscriber.active_sessions bxor ServerBit), + Subscr2 = Subscr#subscriber{active_sessions = Active}, + mnesia:write(subscriber, Subscr2, Wlock), + + ServerKey = {ServerId, Suffix}, + [Server] = mnesia:read({server, Suffix}, ServerKey, Wlock), + Server2 = Server#server{no_of_delete = Server#server.no_of_delete + 1}, + mnesia:write(server, Server2, Wlock); + false -> + ignore + end, + case DoRollback of + true -> + mnesia:abort({do_rollback, ExecuteBranch, []}); + false -> + {do_commit, ExecuteBranch, []} + end. + +number_to_suffix(SubscrId) when is_integer(SubscrId) -> + SubscrId rem 100; +number_to_suffix(<<_:8/binary, TimesTen:8/integer, TimesOne:8/integer>>) -> + ((TimesTen - $0) * 10) + (TimesOne - $0). + +number_to_key(Id, C) when is_integer(Id) -> + case C#config.use_binary_subscriber_key of + true -> + list_to_binary(string:right(integer_to_list(Id), 10, $0)); + false -> + Id + end. + |