aboutsummaryrefslogtreecommitdiffstats
path: root/lib/mnesia/examples/bench
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mnesia/examples/bench')
-rw-r--r--lib/mnesia/examples/bench/Makefile10
-rw-r--r--lib/mnesia/examples/bench/README211
-rw-r--r--lib/mnesia/examples/bench/bench.config121
-rw-r--r--lib/mnesia/examples/bench/bench.config221
-rw-r--r--lib/mnesia/examples/bench/bench.config323
-rw-r--r--lib/mnesia/examples/bench/bench.config423
-rw-r--r--lib/mnesia/examples/bench/bench.config527
-rw-r--r--lib/mnesia/examples/bench/bench.config627
-rw-r--r--lib/mnesia/examples/bench/bench.config735
-rw-r--r--lib/mnesia/examples/bench/bench.erl327
-rw-r--r--lib/mnesia/examples/bench/bench.hrl107
-rwxr-xr-xlib/mnesia/examples/bench/bench.sh23
-rw-r--r--lib/mnesia/examples/bench/bench_generate.erl684
-rw-r--r--lib/mnesia/examples/bench/bench_populate.erl200
-rw-r--r--lib/mnesia/examples/bench/bench_trans.erl184
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.
+