Current section

Files

Jump to
ra src ra_bench.erl
Raw

src/ra_bench.erl

%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2017-2022 VMware, Inc. or its affiliates. All rights reserved.
%%
%% @hidden
-module(ra_bench).
-behaviour(ra_machine).
-compile(inline_list_funcs).
-compile(inline).
-compile({no_auto_import, [apply/3]}).
-include_lib("eunit/include/eunit.hrl").
-define(PIPE_SIZE, 500).
-define(DATA_SIZE, 256).
-export([
init/1,
apply/3,
% profile/0,
% stop_profile/0
prepare/0,
run/3,
run/2,
run/1,
run/0,
print_metrics/1
]).
init(#{}) ->
undefined.
% msg_ids are scoped per customer
% ra_indexes holds all raft indexes for enqueues currently on queue
apply(#{index := I}, {noop, _}, State) ->
case I rem 100000 of
0 ->
{State, ok, {release_cursor, I, State}};
_ ->
{State, ok}
end.
run(Name, Nodes) ->
run(Name, Nodes, ?PIPE_SIZE).
run(Name, Nodes, Pipe)
when is_atom(Name)
andalso is_list(Nodes) ->
run(#{name => Name,
seconds => 30,
target => 20000, % commands / sec
degree => 5,
pipe => Pipe,
nodes => Nodes}).
run() ->
run(#{name => noop,
seconds => 10,
target => 20000, % commands / sec
degree => 5,
nodes => [node() | nodes()]}).
print_counter(Last, Counter) ->
V = counters:get(Counter, 1),
io:format("ops/sec: ~b~n", [ V - Last]),
timer:sleep(1000),
print_counter(V, Counter).
run(Nodes) when is_list(Nodes) ->
run(#{name => noop,
seconds => 30,
target => 20000, % commands / sec
degree => 5,
nodes => Nodes});
run(#{name := Name,
seconds := Secs,
target := Target,
nodes := Nodes,
degree := Degree} = Conf) ->
Pipe = maps:get(pipe, Conf, ?PIPE_SIZE),
io:format("running ra benchmark config: ~p~n", [Conf]),
io:format("starting servers on ~w~n", [Nodes]),
{ok, ServerIds, []} = start(Name, Nodes),
{ok, _, Leader} = ra:members(hd(ServerIds)),
TotalOps = Secs * Target,
Each = max(Pipe, TotalOps div Degree),
DataSize = maps:get(data_size, Conf, ?DATA_SIZE),
Counter = counters:new(1, [write_concurrency]),
Pids = [spawn_client(self(), Leader, Each, DataSize, Pipe, Counter)
|| _ <- lists:seq(1, Degree)],
io:format("running bench mark...~n", []),
Start = erlang:system_time(millisecond),
[P ! go || P <- Pids],
CounterPrinter = spawn(fun () ->
print_counter(counters:get(Counter, 1), Counter)
end),
%% wait for each pid
Wait = ((Secs * 10000) * 4),
[begin
receive
{done, P} ->
io:format("~w is done ", [P]),
ok
after Wait ->
exit({timeout, P})
end
end || P <- Pids],
End = erlang:system_time(millisecond),
Taken = End - Start,
exit(CounterPrinter, kill),
io:format("benchmark completed: ~b ops in ~bms rate ~.2f ops/sec~n",
[TotalOps, Taken, TotalOps / (Taken / 1000)]),
BName = atom_to_binary(Name, utf8),
_ = [rpc:call(N, ?MODULE, print_metrics, [BName])
|| N <- Nodes],
_ = ra:delete_cluster(ServerIds),
%%
ok.
start(Name, Nodes) when is_atom(Name) ->
ServerIds = [{Name, N} || N <- Nodes],
Configs = [begin
rpc:call(N, ?MODULE, prepare, []),
Id = {Name, N},
UId = ra:new_uid(ra_lib:to_binary(Name)),
#{id => Id,
uid => UId,
cluster_name => Name,
metrics_key => atom_to_binary(Name, utf8),
log_init_args => #{uid => UId},
initial_members => ServerIds,
machine => {module, ?MODULE, #{}}}
end || N <- Nodes],
ra:start_cluster(default, Configs).
prepare() ->
_ = application:ensure_all_started(ra),
_ = ra_system:start_default(),
% error_logger:logfile(filename:join(ra_env:data_dir(), "log.log")),
ok.
send_n(_, _Data, 0, _Counter) -> ok;
send_n(Leader, Data, N, Counter) ->
ra:pipeline_command(Leader, {noop, Data}, make_ref(), low),
counters:add(Counter, 1, 1),
send_n(Leader, Data, N-1, Counter).
client_loop(0, 0, _Leader, _Data, _Counter) ->
ok;
client_loop(Num, Sent, _Leader, Data, Counter) ->
receive
{ra_event, Leader, {applied, Applied}} ->
N = length(Applied),
ToSend = min(Sent, N),
send_n(Leader, Data, ToSend, Counter),
client_loop(Num - N, Sent - ToSend, Leader, Data, Counter);
{ra_event, _, {rejected, {not_leader, NewLeader, _}}} ->
io:format("new leader ~w~n", [NewLeader]),
send_n(NewLeader, Data, 1, Counter),
client_loop(Num, Sent, NewLeader, Data, Counter);
{ra_event, Leader, Evt} ->
io:format("unexpected ra_event ~w~n", [Evt]),
client_loop(Num, Sent, Leader, Data, Counter)
end.
spawn_client(Parent, Leader, Num, DataSize, Pipe, Counter) ->
Data = crypto:strong_rand_bytes(DataSize),
spawn_link(
fun () ->
%% first send one 1000 noop commands
%% then top up as they are applied
receive
go ->
send_n(Leader, Data, Pipe, Counter),
ok = client_loop(Num, Num - Pipe, Leader, Data, Counter),
Parent ! {done, self()}
end
end).
print_metrics(Name) ->
io:format("Node: ~w~n", [node()]),
io:format("metrics ~p~n", [ets:lookup(ra_metrics, Name)]),
io:format("counters ~p~n", [ra_counters:overview()]).
% profile() ->
% GzFile = atom_to_list(node()) ++ ".gz",
% lg:trace([noop, ra_server, ra_server_proc, ra_snapshot, ra_machine,
% ra_log, ra_flru, ra_machine, ra_log_meta, ra_log_segment],
% lg_file_tracer,
% GzFile, #{running => false, mode => profile}),
% ok.
% stop_profile() ->
% lg:stop(),
% Base = atom_to_list(node()),
% GzFile = Base ++ ".gz.*",
% lg_callgrind:profile_many(GzFile, Base ++ ".out",#{}),
% ok.