diff options
Diffstat (limited to 'lib/mnesia/examples/bench/bench_generate.erl')
-rw-r--r-- | lib/mnesia/examples/bench/bench_generate.erl | 684 |
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. + |