1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2001-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20%%% File : bench_generate.hrl 21%%% Author : Hakan Mattsson <hakan@cslab.ericsson.se> 22%%% Purpose : Start request generators and collect statistics 23%%% Created : 21 Jun 2001 by Hakan Mattsson <hakan@cslab.ericsson.se> 24%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 26-module(bench_generate). 27-author('hakan@cslab.ericsson.se'). 28 29-include("bench.hrl"). 30 31%% Public 32-export([start/1]). 33 34%% Internal 35-export([ 36 monitor_init/2, 37 generator_init/2, 38 worker_init/1 39 ]). 40 41%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 42%% The traffic generator 43%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 44 45%% ------------------------------------------------------------------- 46%% Start request generators 47%% ------------------------------------------------------------------- 48 49start(C) when is_record(C, config) -> 50 MonPid = spawn_link(?MODULE, monitor_init, [C, self()]), 51 receive 52 {'EXIT', MonPid, Reason} -> 53 exit(Reason); 54 {monitor_done, MonPid, Res} -> 55 Res 56 end. 57 58monitor_init(C, Parent) when is_record(C, config) -> 59 process_flag(trap_exit, true), 60 %% net_kernel:monitor_nodes(true), %% BUGBUG: Needed in order to re-start generators 61 Nodes = C#config.generator_nodes, 62 PerNode = C#config.n_generators_per_node, 63 Timer = C#config.generator_warmup, 64 ?d("~n", []), 65 ?d("Start ~p request generators each at ~p nodes...~n", 66 [PerNode, length(Nodes)]), 67 ?d("~n", []), 68 warmup_sticky(C), 69 ?d(" ~p seconds warmup...~n", [Timer div 1000]), 70 Alive = spawn_generators(C, Nodes, PerNode), 71 erlang:send_after(Timer, self(), warmup_done), 72 monitor_loop(C, Parent, Alive, []). 73 74spawn_generators(C, Nodes, PerNode) -> 75 [spawn_link(Node, ?MODULE, generator_init, [self(), C]) || 76 Node <- Nodes, 77 _ <- lists:seq(1, PerNode)]. 78 79warmup_sticky(C) -> 80 %% Select one node per fragment as master node 81 Tabs = [subscriber, session, server, suffix], 82 Fun = fun(S) -> 83 {[Node | _], _, Wlock} = nearest_node(S, transaction, C), 84 Stick = fun() -> [mnesia:read({T, S}, S, Wlock) || T <- Tabs] end, 85 Args = [transaction, Stick, [], mnesia_frag], 86 rpc:call(Node, mnesia, activity, Args) 87 end, 88 Suffixes = lists:seq(0, C#config.n_fragments - 1), % Assume even distrib. 89 lists:foreach(Fun, Suffixes). 90 91%% Main loop for benchmark monitor 92monitor_loop(C, Parent, Alive, Deceased) -> 93 receive 94 warmup_done -> 95 multicall(Alive, reset_statistics), 96 Timer = C#config.generator_duration, 97 ?d(" ~p seconds actual benchmarking...~n", [Timer div 1000]), 98 erlang:send_after(Timer, self(), measurement_done), 99 monitor_loop(C, Parent, Alive, Deceased); 100 measurement_done -> 101 Stats = multicall(Alive, get_statistics), 102 Timer = C#config.generator_cooldown, 103 ?d(" ~p seconds cooldown...~n", [Timer div 1000]), 104 erlang:send_after(Timer, self(), {cooldown_done, Stats}), 105 monitor_loop(C, Parent, Alive, Deceased); 106 {cooldown_done, Stats} -> 107 multicall(Alive, stop), 108 display_statistics(Stats, C), 109 Parent ! {monitor_done, self(), ok}, 110 unlink(Parent), 111 exit(monitor_done); 112 {nodedown, _Node} -> 113 monitor_loop(C, Parent, Alive, Deceased); 114 {nodeup, Node} -> 115 NeedsBirth = [N || N <- Deceased, N == Node], 116 Born = spawn_generators(C, NeedsBirth, 1), 117 monitor_loop(C, Parent, Born ++ Alive, Deceased -- NeedsBirth); 118 {'EXIT', Pid, Reason} when Pid == Parent -> 119 exit(Reason); 120 {'EXIT', Pid, Reason} -> 121 case lists:member(Pid, Alive) of 122 true -> 123 ?d("Generator on node ~p died: ~p~n", [node(Pid), Reason]), 124 monitor_loop(C, Parent, Alive -- [Pid], [node(Pid) | Deceased]); 125 false -> 126 monitor_loop(C, Parent, Alive, Deceased) 127 end 128 end. 129 130%% Send message to a set of processes and wait for their replies 131multicall(Pids, Message) -> 132 Send = 133 fun(Pid) -> 134 Ref = erlang:monitor(process, Pid), 135 Pid ! {self(), Ref, Message}, 136 {Pid, Ref} 137 end, 138 PidRefs = lists:map(Send, Pids), 139 Collect = 140 fun({Pid, Ref}) -> 141 receive 142 {'DOWN', Ref, process, Pid, Reason} -> 143 {Pid, {'EXIT', Reason}}; 144 {Pid, Ref, Reply} -> 145 erlang:demonitor(Ref), 146 {Pid, Reply} 147 end 148 end, 149 lists:map(Collect, PidRefs). 150 151%% Initialize a traffic generator 152generator_init(Monitor, C) -> 153 process_flag(trap_exit, true), 154 Tables = mnesia:system_info(tables), 155 ok = mnesia:wait_for_tables(Tables, infinity), 156 rand:seed(exsplus), 157 Counters = reset_counters(C, C#config.statistics_detail), 158 SessionTab = ets:new(bench_sessions, [public, {keypos, 1}]), 159 generator_loop(Monitor, C, SessionTab, Counters). 160 161%% Main loop for traffic generator 162generator_loop(Monitor, C, SessionTab, Counters) -> 163 receive 164 {ReplyTo, Ref, get_statistics} -> 165 Stats = get_counters(C, Counters), 166 ReplyTo ! {self(), Ref, Stats}, 167 generator_loop(Monitor, C, SessionTab, Counters); 168 {ReplyTo, Ref, reset_statistics} -> 169 Stats = get_counters(C, Counters), 170 Counters2 = reset_counters(C, Counters), 171 ReplyTo ! {self(), Ref, Stats}, 172 generator_loop(Monitor, C, SessionTab, Counters2); 173 {_ReplyTo, _Ref, stop} -> 174 exit(shutdown); 175 {'EXIT', Pid, Reason} when Pid == Monitor -> 176 exit(Reason); 177 {'EXIT', Pid, Reason} -> 178 Node = node(Pid), 179 ?d("Worker on node ~p(~p) died: ~p~n", [Node, node(), Reason]), 180 Key = {worker,Node}, 181 case get(Key) of 182 undefined -> ignore; 183 Pid -> erase(Key); 184 _ -> ignore 185 end, 186 generator_loop(Monitor, C, SessionTab, Counters) 187 after 0 -> 188 {Name, {Nodes, Activity, Wlock}, Fun, CommitSessions} = 189 gen_trans(C, SessionTab), 190 Before = erlang:monotonic_time(), 191 Res = call_worker(Nodes, Activity, Fun, Wlock, mnesia_frag), 192 After = erlang:monotonic_time(), 193 Elapsed = elapsed(Before, After), 194 post_eval(Monitor, C, Elapsed, Res, Name, CommitSessions, SessionTab, Counters) 195 end. 196 197%% Perform a transaction on a node near the data 198call_worker([Node | _], Activity, Fun, Wlock, Mod) when Node == node() -> 199 {Node, catch mnesia:activity(Activity, Fun, [Wlock], Mod)}; 200call_worker([Node | _] = Nodes, Activity, Fun, Wlock, Mod) -> 201 Key = {worker,Node}, 202 case get(Key) of 203 Pid when is_pid(Pid) -> 204 Args = [Activity, Fun, [Wlock], Mod], 205 Pid ! {activity, self(), Args}, 206 receive 207 {'EXIT', Pid, Reason} -> 208 ?d("Worker on node ~p(~p) died: ~p~n", [Node, node(), Reason]), 209 erase(Key), 210 retry_worker(Nodes, Activity, Fun, Wlock, Mod, {'EXIT', Reason}); 211 {activity_result, Pid, Result} -> 212 case Result of 213 {'EXIT', {aborted, {not_local, _}}} -> 214 retry_worker(Nodes, Activity, Fun, Wlock, Mod, Result); 215 _ -> 216 {Node, Result} 217 end 218 end; 219 undefined -> 220 GenPid = self(), 221 Pid = spawn_link(Node, ?MODULE, worker_init, [GenPid]), 222 put(Key, Pid), 223 call_worker(Nodes, Activity, Fun, Wlock, Mod) 224 end. 225 226retry_worker([], _Activity, _Fun, _Wlock, _Mod, Reason) -> 227 {node(), Reason}; 228retry_worker([BadNode | SpareNodes], Activity, Fun, Wlock, Mod, Reason) -> 229 Nodes = SpareNodes -- [BadNode], 230 case Nodes of 231 [] -> 232 {BadNode, Reason}; 233 [_] -> 234 call_worker(Nodes, Activity, Fun, write, Mod); 235 _ -> 236 call_worker(Nodes, Activity, Fun, Wlock, Mod) 237 end. 238 239worker_init(Parent) -> 240 Tables = mnesia:system_info(tables), 241 ok = mnesia:wait_for_tables(Tables, infinity), 242 worker_loop(Parent). 243 244%% Main loop for remote workers 245worker_loop(Parent) -> 246 receive 247 {activity, Parent, [Activity, Fun, Extra, Mod]} -> 248 Result = (catch mnesia:activity(Activity, Fun, Extra, Mod)), 249 Parent ! {activity_result, self(), Result}, 250 worker_loop(Parent) 251 end. 252 253 254elapsed(Before, After) -> 255 erlang:convert_time_unit(After-Before, native, micro_seconds). 256 257%% Lookup counters 258get_counters(_C, {table, Tab}) -> 259 ets:match_object(Tab, '_'); 260get_counters(_C, {NM, NC, NA, NB}) -> 261 Trans = any, 262 Node = somewhere, 263 [{{Trans, n_micros, Node}, NM}, 264 {{Trans, n_commits, Node}, NC}, 265 {{Trans, n_aborts, Node}, NA}, 266 {{Trans, n_branches_executed, Node}, NB}]. 267 268% Clear all counters 269reset_counters(_C, normal) -> 270 {0, 0, 0, 0}; 271reset_counters(C, {_, _, _, _}) -> 272 reset_counters(C, normal); 273reset_counters(C, debug) -> 274 CounterTab = ets:new(bench_pending, [public, {keypos, 1}]), 275 reset_counters(C, {table, CounterTab}); 276reset_counters(C, debug2) -> 277 CounterTab = ets:new(bench_pending, [public, {keypos, 1}]), 278 reset_counters(C, {table, CounterTab}); 279reset_counters(C, {table, Tab} = Counters) -> 280 Names = [n_micros, n_commits, n_aborts, n_branches_executed], 281 Nodes = C#config.generator_nodes ++ C#config.table_nodes, 282 TransTypes = [t1, t2, t3, t4, t5, ping], 283 [ets:insert(Tab, {{Trans, Name, Node}, 0}) || Name <- Names, 284 Node <- Nodes, 285 Trans <- TransTypes], 286 Counters. 287 288%% Determine the outcome of a transaction and increment the counters 289post_eval(Monitor, C, Elapsed, {Node, Res}, Name, CommitSessions, SessionTab, {table, Tab} = Counters) -> 290 case Res of 291 {do_commit, BranchExecuted, _} -> 292 incr(Tab, {Name, n_micros, Node}, Elapsed), 293 incr(Tab, {Name, n_commits, Node}, 1), 294 case BranchExecuted of 295 true -> 296 incr(Tab, {Name, n_branches_executed, Node}, 1), 297 commit_session(CommitSessions), 298 generator_loop(Monitor, C, SessionTab, Counters); 299 false -> 300 generator_loop(Monitor, C, SessionTab, Counters) 301 end; 302 {'EXIT', {aborted, {do_rollback, BranchExecuted, _}}} -> 303 incr(Tab, {Name, n_micros, Node}, Elapsed), 304 incr(Tab, {Name, n_aborts, Node}, 1), 305 case BranchExecuted of 306 true -> 307 incr(Tab, {Name, n_branches_executed, Node}, 1), 308 generator_loop(Monitor, C, SessionTab, Counters); 309 false -> 310 generator_loop(Monitor, C, SessionTab, Counters) 311 end; 312 _ -> 313 ?d("Failed(~p): ~p~n", [Node, Res]), 314 incr(Tab, {Name, n_micros, Node}, Elapsed), 315 incr(Tab, {Name, n_aborts, Node}, 1), 316 generator_loop(Monitor, C, SessionTab, Counters) 317 end; 318post_eval(Monitor, C, Elapsed, {_Node, Res}, _Name, CommitSessions, SessionTab, {NM, NC, NA, NB}) -> 319 case Res of 320 {do_commit, BranchExecuted, _} -> 321 case BranchExecuted of 322 true -> 323 commit_session(CommitSessions), 324 generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC + 1, NA, NB + 1}); 325 false -> 326 generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC + 1, NA, NB}) 327 end; 328 {'EXIT', {aborted, {do_rollback, BranchExecuted, _}}} -> 329 case BranchExecuted of 330 true -> 331 generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC, NA + 1, NB + 1}); 332 false -> 333 generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC, NA + 1, NB}) 334 end; 335 _ -> 336 ?d("Failed: ~p~n", [Res]), 337 generator_loop(Monitor, C, SessionTab, {NM + Elapsed, NC, NA + 1, NB}) 338 end. 339 340incr(Tab, Counter, Incr) -> 341 ets:update_counter(Tab, Counter, Incr). 342 343commit_session(no_fun) -> 344 ignore; 345commit_session(Fun) when is_function(Fun, 0) -> 346 Fun(). 347 348%% Randlomly choose a transaction type according to benchmar spec 349gen_trans(C, SessionTab) when C#config.generator_profile == random -> 350 case rand:uniform(100) of 351 Rand when Rand > 0, Rand =< 25 -> gen_t1(C, SessionTab); 352 Rand when Rand > 25, Rand =< 50 -> gen_t2(C, SessionTab); 353 Rand when Rand > 50, Rand =< 70 -> gen_t3(C, SessionTab); 354 Rand when Rand > 70, Rand =< 85 -> gen_t4(C, SessionTab); 355 Rand when Rand > 85, Rand =< 100 -> gen_t5(C, SessionTab) 356 end; 357gen_trans(C, SessionTab) -> 358 case C#config.generator_profile of 359 t1 -> gen_t1(C, SessionTab); 360 t2 -> gen_t2(C, SessionTab); 361 t3 -> gen_t3(C, SessionTab); 362 t4 -> gen_t4(C, SessionTab); 363 t5 -> gen_t5(C, SessionTab); 364 ping -> gen_ping(C, SessionTab) 365 end. 366 367gen_t1(C, _SessionTab) -> 368 SubscrId = rand:uniform(C#config.n_subscribers) - 1, 369 SubscrKey = bench_trans:number_to_key(SubscrId, C), 370 Location = 4711, 371 ChangedBy = <<4711:(8*25)>>, 372 ChangedTime = <<4711:(8*25)>>, 373 {t1, 374 nearest_node(SubscrId, transaction, C), 375 fun(Wlock) -> bench_trans:update_current_location(Wlock, SubscrKey, Location, ChangedBy, ChangedTime) end, 376 no_fun 377 }. 378 379gen_t2(C, _SessionTab) -> 380 SubscrId = rand:uniform(C#config.n_subscribers) - 1, 381 SubscrKey = bench_trans:number_to_key(SubscrId, C), 382 {t2, 383 nearest_node(SubscrId, sync_dirty, C), 384 %%nearest_node(SubscrId, transaction, C), 385 fun(Wlock) -> bench_trans:read_current_location(Wlock, SubscrKey) end, 386 no_fun 387 }. 388 389gen_t3(C, SessionTab) -> 390 case ets:first(SessionTab) of 391 '$end_of_table' -> 392 %% This generator does not have any session, 393 %% try reading someone elses session details 394 SubscrId = rand:uniform(C#config.n_subscribers) - 1, 395 SubscrKey = bench_trans:number_to_key(SubscrId, C), 396 ServerId = rand:uniform(C#config.n_servers) - 1, 397 ServerBit = 1 bsl ServerId, 398 {t3, 399 nearest_node(SubscrId, transaction, C), 400 fun(Wlock) -> bench_trans:read_session_details(Wlock, SubscrKey, ServerBit, ServerId) end, 401 no_fun 402 }; 403 {SubscrId, SubscrKey, ServerId} -> 404 %% This generator do have a session, 405 %% read its session details 406 ServerBit = 1 bsl ServerId, 407 {t3, 408 nearest_node(SubscrId, transaction, C), 409 fun(Wlock) -> bench_trans:read_session_details(Wlock, SubscrKey, ServerBit, ServerId) end, 410 no_fun 411 } 412 end. 413 414gen_t4(C, SessionTab) -> 415 %% This generator may already have sessions, 416 %% create a new session and hope that no other 417 %% generator already has occupied it 418 SubscrId = rand:uniform(C#config.n_subscribers) - 1, 419 SubscrKey = bench_trans:number_to_key(SubscrId, C), 420 ServerId = rand:uniform(C#config.n_servers) - 1, 421 ServerBit = 1 bsl ServerId, 422 Details = <<4711:(8*2000)>>, 423 DoRollback = (rand:uniform(100) =< 2), 424 Insert = fun() -> ets:insert(SessionTab, {{SubscrId, SubscrKey, ServerId}, self()}) end, 425 {t4, 426 nearest_node(SubscrId, transaction, C), 427 fun(Wlock) -> bench_trans:create_session_to_server(Wlock, SubscrKey, ServerBit, ServerId, Details, DoRollback) end, 428 Insert 429 }. 430 431gen_t5(C, SessionTab) -> 432 case ets:first(SessionTab) of 433 '$end_of_table' -> 434 %% This generator does not have any session, 435 %% try to delete someone elses session details 436 SubscrId = rand:uniform(C#config.n_subscribers) - 1, 437 SubscrKey = bench_trans:number_to_key(SubscrId, C), 438 ServerId = rand:uniform(C#config.n_servers) - 1, 439 ServerBit = 1 bsl ServerId, 440 DoRollback = (rand:uniform(100) =< 2), 441 {t5, 442 nearest_node(SubscrId, transaction, C), 443 fun(Wlock) -> bench_trans:delete_session_from_server(Wlock, SubscrKey, ServerBit, ServerId, DoRollback) end, 444 no_fun 445 }; 446 {SubscrId, SubscrKey, ServerId} -> 447 %% This generator do have at least one session, 448 %% delete it. 449 ServerBit = 1 bsl ServerId, 450 DoRollback = (rand:uniform(100) =< 2), 451 Delete = fun() -> ets:delete(SessionTab, {SubscrId, SubscrKey, ServerId}) end, 452 {t5, 453 nearest_node(SubscrId, transaction, C), 454 fun(Wlock) -> bench_trans:delete_session_from_server(Wlock, SubscrKey, ServerBit, ServerId, DoRollback) end, 455 Delete 456 } 457 end. 458 459gen_ping(C, _SessionTab) -> 460 SubscrId = rand:uniform(C#config.n_subscribers) - 1, 461 {ping, 462 nearest_node(SubscrId, transaction, C), 463 fun(_Wlock) -> {do_commit, true, []} end, 464 no_fun 465 }. 466 467%% Select a node as near as the subscriber data as possible 468nearest_node(SubscrId, Activity, C) -> 469 Suffix = bench_trans:number_to_suffix(SubscrId), 470 case mnesia_frag:table_info(t, s, {suffix, Suffix}, where_to_write) of 471 [] -> 472 {[node()], Activity, write}; 473 [Node] -> 474 {[Node], Activity, write}; 475 Nodes -> 476 Wlock = C#config.write_lock_type, 477 if 478 C#config.always_try_nearest_node; Wlock =:= write -> 479 case lists:member(node(), Nodes) of 480 true -> 481 {[node() | Nodes], Activity, Wlock}; 482 false -> 483 Node = pick_node(Suffix, C, Nodes), 484 {[Node | Nodes], Activity, Wlock} 485 end; 486 Wlock == sticky_write -> 487 Node = pick_node(Suffix, C, Nodes), 488 {[Node | Nodes], Activity, Wlock} 489 end 490 end. 491 492pick_node(Suffix, C, Nodes) -> 493 Ordered = lists:sort(Nodes), 494 NumberOfActive = length(Ordered), 495 PoolSize = length(C#config.table_nodes), 496 Suffix2 = 497 case PoolSize rem NumberOfActive of 498 0 -> Suffix div (PoolSize div NumberOfActive); 499 _ -> Suffix 500 end, 501 N = (Suffix2 rem NumberOfActive) + 1, 502 lists:nth(N, Ordered). 503 504display_statistics(Stats, C) -> 505 GoodStats = [{node(GenPid), GenStats} || {GenPid, GenStats} <- Stats, 506 is_list(GenStats)], 507 FlatStats = [{Type, Name, EvalNode, GenNode, Count} || 508 {GenNode, GenStats} <- GoodStats, 509 {{Type, Name, EvalNode}, Count} <- GenStats], 510 TotalStats = calc_stats_per_tag(lists:keysort(2, FlatStats), 2, []), 511 {value, {n_aborts, 0, NA, 0, 0}} = 512 lists:keysearch(n_aborts, 1, TotalStats ++ [{n_aborts, 0, 0, 0, 0}]), 513 {value, {n_commits, NC, 0, 0, 0}} = 514 lists:keysearch(n_commits, 1, TotalStats ++ [{n_commits, 0, 0, 0, 0}]), 515 {value, {n_branches_executed, 0, 0, _NB, 0}} = 516 lists:keysearch(n_branches_executed, 1, TotalStats ++ [{n_branches_executed, 0, 0, 0, 0}]), 517 {value, {n_micros, 0, 0, 0, AccMicros}} = 518 lists:keysearch(n_micros, 1, TotalStats ++ [{n_micros, 0, 0, 0, 0}]), 519 NT = NA + NC, 520 NG = length(GoodStats), 521 NTN = length(C#config.table_nodes), 522 WallMicros = C#config.generator_duration * 1000 * NG, 523 Overhead = (catch (WallMicros - AccMicros) / WallMicros), 524 ?d("~n", []), 525 ?d("Benchmark result...~n", []), 526 ?d("~n", []), 527 ?d(" ~p transactions per second (TPS).~n", [catch ((NT * 1000000 * NG) div AccMicros)]), 528 ?d(" ~p TPS per table node.~n", [catch ((NT * 1000000 * NG) div (AccMicros * NTN))]), 529 ?d(" ~p micro seconds in average per transaction, including latency.~n", 530 [catch (AccMicros div NT)]), 531 ?d(" ~p transactions. ~f% generator overhead.~n", [NT, Overhead * 100]), 532 533 TypeStats = calc_stats_per_tag(lists:keysort(1, FlatStats), 1, []), 534 EvalNodeStats = calc_stats_per_tag(lists:keysort(3, FlatStats), 3, []), 535 GenNodeStats = calc_stats_per_tag(lists:keysort(4, FlatStats), 4, []), 536 if 537 C#config.statistics_detail == normal -> 538 ignore; 539 true -> 540 ?d("~n", []), 541 ?d("Statistics per transaction type...~n", []), 542 ?d("~n", []), 543 display_type_stats(" ", TypeStats, NT, AccMicros), 544 545 ?d("~n", []), 546 ?d("Transaction statistics per table node...~n", []), 547 ?d("~n", []), 548 display_calc_stats(" ", EvalNodeStats, NT, AccMicros), 549 550 ?d("~n", []), 551 ?d("Transaction statistics per generator node...~n", []), 552 ?d("~n", []), 553 display_calc_stats(" ", GenNodeStats, NT, AccMicros) 554 end, 555 if 556 C#config.statistics_detail /= debug2 -> 557 ignore; 558 true -> 559 io:format("~n", []), 560 io:format("------ Test Results ------~n", []), 561 io:format("Length : ~p sec~n", [C#config.generator_duration div 1000]), 562 Host = lists:nth(2, string:tokens(atom_to_list(node()), [$@])), 563 io:format("Processor : ~s~n", [Host]), 564 io:format("Number of Proc: ~p~n", [NG]), 565 io:format("~n", []), 566 display_trans_stats(" ", TypeStats, NT, AccMicros, NG), 567 io:format("~n", []), 568 io:format(" Overall Statistics~n", []), 569 io:format(" Transactions: ~p~n", [NT]), 570 io:format(" Inner : ~p TPS~n", [catch ((NT * 1000000 * NG) div AccMicros)]), 571 io:format(" Outer : ~p TPS~n", [catch ((NT * 1000000 * NG) div WallMicros)]), 572 io:format("~n", []) 573 end. 574 575 576display_calc_stats(Prefix, [{_Tag, 0, 0, 0, 0} | Rest], NT, Micros) -> 577 display_calc_stats(Prefix, Rest, NT, Micros); 578display_calc_stats(Prefix, [{Tag, NC, NA, _NB, NM} | Rest], NT, Micros) -> 579 ?d("~s~s n=~s%\ttime=~s%~n", 580 [Prefix, left(Tag), percent(NC + NA, NT), percent(NM, Micros)]), 581 display_calc_stats(Prefix, Rest, NT, Micros); 582display_calc_stats(_, [], _, _) -> 583 ok. 584 585display_type_stats(Prefix, [{_Tag, 0, 0, 0, 0} | Rest], NT, Micros) -> 586 display_type_stats(Prefix, Rest, NT, Micros); 587display_type_stats(Prefix, [{Tag, NC, NA, NB, NM} | Rest], NT, Micros) -> 588 ?d("~s~s n=~s%\ttime=~s%\tavg micros=~p~n", 589 [ 590 Prefix, 591 left(Tag), 592 percent(NC + NA, NT), 593 percent(NM, Micros), 594 catch (NM div (NC + NA)) 595 ]), 596 case NA /= 0 of 597 true -> ?d("~s ~s% aborted~n", [Prefix, percent(NA, NC + NA)]); 598 false -> ignore 599 end, 600 case NB /= 0 of 601 true -> ?d("~s ~s% branches executed~n", [Prefix, percent(NB, NC + NA)]); 602 false -> ignore 603 end, 604 display_type_stats(Prefix, Rest, NT, Micros); 605display_type_stats(_, [], _, _) -> 606 ok. 607 608left(Term) -> 609 string:left(lists:flatten(io_lib:format("~p", [Term])), 27, $.). 610 611percent(_Part, 0) -> "infinity"; 612percent(Part, Total) -> io_lib:format("~8.4f", [(Part * 100) / Total]). 613 614calc_stats_per_tag([], _Pos, Acc) -> 615 lists:sort(Acc); 616calc_stats_per_tag([Tuple | _] = FlatStats, Pos, Acc) when size(Tuple) == 5 -> 617 Tag = element(Pos, Tuple), 618 do_calc_stats_per_tag(FlatStats, Pos, {Tag, 0, 0, 0, 0}, Acc). 619 620do_calc_stats_per_tag([Tuple | Rest], Pos, {Tag, NC, NA, NB, NM}, Acc) 621 when element(Pos, Tuple) == Tag -> 622 Val = element(5, Tuple), 623 case element(2, Tuple) of 624 n_commits -> 625 do_calc_stats_per_tag(Rest, Pos, {Tag, NC + Val, NA, NB, NM}, Acc); 626 n_aborts -> 627 do_calc_stats_per_tag(Rest, Pos, {Tag, NC, NA + Val, NB, NM}, Acc); 628 n_branches_executed -> 629 do_calc_stats_per_tag(Rest, Pos, {Tag, NC, NA, NB + Val, NM}, Acc); 630 n_micros -> 631 do_calc_stats_per_tag(Rest, Pos, {Tag, NC, NA, NB, NM + Val}, Acc) 632 end; 633do_calc_stats_per_tag(GenStats, Pos, CalcStats, Acc) -> 634 calc_stats_per_tag(GenStats, Pos, [CalcStats | Acc]). 635 636display_trans_stats(Prefix, [{_Tag, 0, 0, 0, 0} | Rest], NT, Micros, NG) -> 637 display_trans_stats(Prefix, Rest, NT, Micros, NG); 638display_trans_stats(Prefix, [{Tag, NC, NA, NB, NM} | Rest], NT, Micros, NG) -> 639 Common = 640 fun(Name) -> 641 Sec = NM / (1000000 * NG), 642 io:format(" ~s: ~p (~p%) Time: ~p sec TPS = ~p~n", 643 [Name, 644 NC + NA, 645 round(((NC + NA) * 100) / NT), 646 round(Sec), 647 round((NC + NA) / Sec)]) 648 end, 649 Branch = 650 fun() -> 651 io:format(" Branches Executed: ~p (~p%)~n", 652 [NB, round((NB * 100) / (NC + NA))]) 653 end, 654 Rollback = 655 fun() -> 656 io:format(" Rollback Executed: ~p (~p%)~n", 657 [NA, round((NA * 100) / (NC + NA))]) 658 end, 659 case Tag of 660 t1 -> 661 Common("T1"); 662 t2 -> 663 Common("T2"); 664 t3 -> 665 Common("T3"), 666 Branch(); 667 t4 -> 668 Common("T4"), 669 Branch(), 670 Rollback(); 671 t5 -> 672 Common("T5"), 673 Branch(), 674 Rollback(); 675 _ -> 676 Common(io_lib:format("~p", [Tag])) 677 end, 678 display_trans_stats(Prefix, Rest, NT, Micros, NG); 679display_trans_stats(_, [], _, _, _) -> 680 ok. 681 682