aboutsummaryrefslogtreecommitdiffstats
path: root/lib/mnesia/examples/bench/bench_generate.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mnesia/examples/bench/bench_generate.erl')
-rw-r--r--lib/mnesia/examples/bench/bench_generate.erl684
1 files changed, 684 insertions, 0 deletions
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.
+