Browse Source

Add basho bench rig

A simple benchmark can be run as:

    cd bench
    make test

This assumes R is installed and on PATH. The test rig will reclone
pooler into bench/deps. This can be useful to compare different
branches.
Seth Falcon 12 years ago
parent
commit
72d1012ecb

+ 3 - 0
.gitignore

@@ -5,3 +5,6 @@ doc/*.html
 /doc/stylesheet.css
 /doc/erlang.png
 /doc/edoc-info
+/bench/tests
+/bench/deps
+

+ 95 - 0
bench/Makefile

@@ -0,0 +1,95 @@
+DEPS = $(CURDIR)/deps
+
+DIALYZER_OPTS = -Wunderspecs
+
+# List dependencies that should be included in a cached dialyzer PLT file.
+# DIALYZER_DEPS = deps/app1/ebin \
+#                 deps/app2/ebin
+
+DEPS_PLT = bench.plt
+
+ERLANG_DIALYZER_APPS = asn1 \
+                       compiler \
+                       crypto \
+                       edoc \
+                       edoc \
+                       erts \
+                       eunit \
+                       eunit \
+                       gs \
+                       hipe \
+                       inets \
+                       kernel \
+                       mnesia \
+                       mnesia \
+                       observer \
+                       public_key \
+                       runtime_tools \
+                       runtime_tools \
+                       ssl \
+                       stdlib \
+                       syntax_tools \
+                       syntax_tools \
+                       tools \
+                       webtool \
+                       xmerl
+
+all: compile
+
+# Clean ebin and .eunit of this project
+clean:
+	@rebar clean skip_deps=true
+
+# Clean this project and all deps
+allclean:
+	@rebar clean
+
+compile: $(DEPS)
+	@rebar compile
+
+compile_skip:
+	@rebar compile skip_deps=true
+
+test: compile deps/basho_bench/basho_bench
+	@deps/basho_bench/basho_bench pooler.config
+	@deps/basho_bench/priv/summary.r -i tests/current
+
+deps/basho_bench/basho_bench:
+	@(cd deps/basho_bench;$(MAKE))
+
+$(DEPS):
+	@rebar get-deps
+
+# Full clean and removal of all deps. Remove deps first to avoid
+# wasted effort of cleaning deps before nuking them.
+distclean:
+	@rm -rf deps $(DEPS_PLT)
+	@rebar clean
+
+# Only include local PLT if we have deps that we are going to analyze
+ifeq ($(strip $(DIALYZER_DEPS)),)
+dialyzer: ~/.dialyzer_plt
+	@dialyzer $(DIALYZER_OPTS) -r ebin
+else
+dialyzer: ~/.dialyzer_plt $(DEPS_PLT)
+	@dialyzer $(DIALYZER_OPTS) --plts ~/.dialyzer_plt $(DEPS_PLT) -r ebin
+
+$(DEPS_PLT):
+	@dialyzer --build_plt $(DIALYZER_DEPS) --output_plt $(DEPS_PLT)
+endif
+
+~/.dialyzer_plt:
+	@echo "ERROR: Missing ~/.dialyzer_plt. Please wait while a new PLT is compiled."
+	dialyzer --build_plt --apps $(ERLANG_DIALYZER_APPS)
+	@echo "now try your build again"
+
+doc:
+	@rebar doc skip_deps=true
+
+shell:
+	erl -pa deps/*/ebin ebin
+
+tags:
+	find src deps -name "*.[he]rl" -print | etags -
+
+.PHONY: all compile eunit test dialyzer clean allclean distclean doc tags

+ 19 - 0
bench/README.md

@@ -0,0 +1,19 @@
+# bench - Pooler basho_bench Test Rig #
+
+Welcome to pooler's basho_bench test rig.
+
+## pooled_member ##
+
+Allows configurable start up delay, ability to crash on demand.
+
+## consumer ##
+
+Configurable think time, ability to crash on demand, configurable
+number of take/return ops. Fast, slow, superslow, member crash, self
+crash operations.
+
+
+
+
+
+

+ 16 - 0
bench/pooler.config

@@ -0,0 +1,16 @@
+{mode, max}.
+
+{duration, 4}.
+
+{concurrent, 5}.
+
+{driver, pooler_driver}.
+
+{key_generator, {function, pooler_driver, pool_name, []}}.
+
+{value_generator, {fixed_bin, 10000}}.
+
+% {operations, [{simple, 1}, {fast, 4}, {slow, 1}]}.
+{operations, [{simple, 1}]}.
+
+{code_paths, ["ebin", "deps/pooler/ebin"]}.

+ 14 - 0
bench/rebar.config

@@ -0,0 +1,14 @@
+%% -*- mode: erlang -*-
+%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 ft=erlang et
+
+{deps,
+ [
+  {pooler, ".*",
+   {git, "git://github.com/seth/pooler.git", {branch, "pooler-1-0"}}},
+
+  {basho_bench, ".*",
+   {git, "git://github.com/basho/basho_bench.git", {branch, "master"}}}
+ ]}.
+
+{cover_enabled, true}.

+ 15 - 0
bench/src/bench.app.src

@@ -0,0 +1,15 @@
+%% -*- mode: erlang -*-
+{application, bench,
+ [
+  {description, "pooler basho_bench test rig"},
+  {vsn, "0.0.1"},
+  {registered, []},
+  {applications, [
+                  kernel,
+                  stdlib
+                 ]},
+  %% uncomment if this is an active application
+  %% {mod, { bench_app, []}},
+  {env, []}
+ ]}.
+%% vim: set filetype=erlang tabstop=2

+ 6 - 0
bench/src/bench.erl

@@ -0,0 +1,6 @@
+-module(bench).
+
+-export([hello/0]).
+
+hello() ->
+    howdy.

+ 101 - 0
bench/src/consumer.erl

@@ -0,0 +1,101 @@
+%% @doc A consumer of pool members used for perf testing pooler. The
+%% consumer has a configurable think time for how long it keeps a
+%% member checked out, how many take/return cycles it performs. Jitter
+%% is added to think time. You can also request a consumer to crash or
+%% trigger a member crash.
+-module(consumer).
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+
+-export([start_link/0,
+         run/2
+        ]).
+
+-export([
+         code_change/3,
+         handle_call/3,
+         handle_cast/2,
+         handle_info/2,
+         init/1,
+         terminate/2
+        ]).
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+start_link() ->
+    % not registered
+    gen_server:start_link(?MODULE, [], []).
+
+run(S, Config) ->
+    SelfCrash = proplists:get_value(consumer_crash, Config) =:= true,
+    MemberCrash = proplists:get_value(member_crash, Config) =:= true,
+    TakeCycles = proplists:get_value(take_cycles, Config),
+    ThinkTime = proplists:get_value(think_time, Config),
+    PoolName = proplists:get_value(pool_name, Config),
+    gen_server:call(S, {run, PoolName, SelfCrash, MemberCrash,
+                        TakeCycles, ThinkTime},
+                    ThinkTime * 3 * TakeCycles).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Definitions
+%% ------------------------------------------------------------------
+-record(state, {
+          id,
+          ping_count = 0
+         }).
+
+init([]) ->
+    Now = erlang:now(),
+    random:seed(Now),
+    {ok, #state{id = Now}}.
+
+handle_call({run, PoolName, SelfCrash, MemberCrash,
+             TakeCycles, ThinkTime}, _From, State) ->
+    CrashData = crash_data(SelfCrash, MemberCrash, TakeCycles),
+    run_cycles(ThinkTime, TakeCycles, CrashData, PoolName),
+    {reply, ok, State};
+handle_call(_Request, _From, State) ->
+    {noreply, ok, State}.
+
+run_cycles(_ThinkTime, 0, _, _) ->
+    done;
+run_cycles(_ThinkTime, CrashIdx, {CrashIdx, _}, _) ->
+    %% self crash
+    erlang:error({consumer, self_crash_requested});
+run_cycles(ThinkTime, CrashIdx, {_, CrashIdx} = CrashData, PoolName) ->
+    %% member crash request
+    M = pooler:take_member(PoolName),
+    member:crash(M),
+    run_cycles(ThinkTime, CrashIdx - 1, CrashData, PoolName);
+run_cycles(ThinkTime, Idx, CrashData, PoolName) ->
+    M = pooler:take_member(PoolName),
+    Think = ThinkTime + random:uniform(ThinkTime),
+    timer:sleep(Think),
+    pooler:return_member(PoolName, M),
+    run_cycles(ThinkTime, Idx - 1, CrashData, PoolName).
+
+%% only support a single crash type. So if self crash is requested,
+%% we'll never crash the member.
+crash_data(false, false, _) ->
+    {never, never};
+crash_data(true, _, TakeCycles) ->
+    {random:uniform(TakeCycles), never};
+crash_data(false, true, TakeCycles) ->
+    {never, random:uniform(TakeCycles)}.
+
+handle_cast(crash, _State) ->
+    erlang:error({member, requested_crash});
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+

+ 23 - 0
bench/src/consumer_sup.erl

@@ -0,0 +1,23 @@
+-module(consumer_sup).
+
+-behaviour(supervisor).
+
+-export([
+         init/1,
+         new_consumer/0,
+         start_link/0
+        ]).
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init(Args) ->
+    Worker = {consumer, {consumer, start_link, Args},
+              temporary,                        % never restart workers
+              brutal_kill, worker, [consumer]},
+    Specs = [Worker],
+    Restart = {simple_one_for_one, 1, 1},
+    {ok, {Restart, Specs}}.
+
+new_consumer() ->
+    supervisor:start_child(?MODULE, []).

+ 96 - 0
bench/src/member.erl

@@ -0,0 +1,96 @@
+%% @doc A pool member used for perf testing pooler. The member has a
+%% configurable start-up delay. You set a delay value and actual start
+%% delay will be `delay + random:uniform(delay)'. The module supports
+%% a crash function to make the member crash.
+-module(member).
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export([start_link/1,
+         ping/1,
+         ping_count/1,
+         crash/1,
+         stop/1
+        ]).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+         terminate/2, code_change/3]).
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+start_link(Config) ->
+    % not registered
+    gen_server:start_link(?MODULE, Config, []).
+
+ping(S) ->
+    gen_server:call(S, ping).
+
+ping_count(S) ->
+    gen_server:call(S, ping_count).
+
+crash(S) ->
+    gen_server:cast(S, crash),
+    sent_crash_request.
+
+stop(S) ->
+    gen_server:call(S, stop).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Definitions
+%% ------------------------------------------------------------------
+-record(state, {
+          id,
+          ping_count = 0
+         }).
+
+init(Config) ->
+    start_up_delay(Config),
+    {ok, #state{id = make_ref()}}.
+
+%% pause server init based on start_up_delay config plus jitter (of up
+%% to 2x delay)
+start_up_delay(Config) ->
+    case proplists:get_value(start_up_delay, Config) of
+        T when is_integer(T) ->
+            random:seed(erlang:now()),
+            J = random:uniform(T),
+            timer:sleep(T + J),
+            ok;
+        _ ->
+            ok
+    end.
+
+handle_call(ping, _From, #state{ping_count = C } = State) ->
+    State1 = State#state{ping_count = C + 1},
+    {reply, pong, State1};
+handle_call(ping_count, _From, #state{ping_count = C } = State) ->
+    {reply, C, State};
+handle_call(stop, _From, State) ->
+    {stop, normal, stop_ok, State};
+handle_call(_Request, _From, State) ->
+    {noreply, ok, State}.
+
+handle_cast(crash, _State) ->
+    erlang:error({member, requested_crash});
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+

+ 24 - 0
bench/src/member_sup.erl

@@ -0,0 +1,24 @@
+-module(member_sup).
+
+-behaviour(supervisor).
+
+-export([
+         init/1,
+         new_member/1,
+         start_link/0
+        ]).
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init(Args) ->
+    Worker = {member, {member, start_link, Args},
+              temporary,                        % never restart workers
+              brutal_kill, worker, [member]},
+    Specs = [Worker],
+    Restart = {simple_one_for_one, 1, 1},
+    {ok, {Restart, Specs}}.
+
+new_member(Delay) ->
+    Config = [{start_up_delay, Delay}],
+    supervisor:start_child(?MODULE, [Config]).

+ 73 - 0
bench/src/pooler_driver.erl

@@ -0,0 +1,73 @@
+%% @doc basho_bench driver for pooler testing
+-module(pooler_driver).
+
+-export([
+         new/1,
+         pool_name/1,
+         run/4
+         ]).
+
+
+-record(state, {
+          %% integer id received from new/1
+          id = 0,
+
+          %% pid of consumer worker process
+          consumer = undefined
+         }).
+          
+new(ID) ->
+    %% this is bogus, b/c called too many times.
+    init_driver(),
+    {ok, Consumer} = consumer_sup:new_consumer(),
+    {ok, #state{id = ID, consumer = Consumer}}.
+
+%% KeyGen can be a function that returns a pool name atom.
+run(simple, PoolNameFun, _ValueGen, #state{consumer = _C} = State) ->
+    PoolName = PoolNameFun(),
+    case pooler:take_member(PoolName) of
+        error_no_members ->
+            {error, error_no_members, State};
+        Pid ->
+            pooler:return_member(PoolName, Pid),
+            {ok, State}
+    end;
+run(fast, PoolNameFun, _ValueGen, #state{consumer = C} = State) ->
+    PoolName = PoolNameFun(),
+    ConsumerOpts = [{consumer_crash, false},
+                    {member_crash, false},
+                    {take_cycles, 1},
+                    {think_time, 10},
+                    {pool_name, PoolName}
+                   ],
+    consumer:run(C, ConsumerOpts),
+    {ok, State};
+run(slow, PoolNameFun, _ValueGen, #state{consumer = C} = State) ->
+    PoolName = PoolNameFun(),
+    ConsumerOpts = [{consumer_crash, false},
+                    {member_crash, false},
+                    {take_cycles, 1},
+                    {think_time, 200},
+                    {pool_name, PoolName}
+                   ],
+    consumer:run(C, ConsumerOpts),
+    {ok, State}.
+
+
+
+%% gets called as the PoolNameFun aka key_generator via basho_bench config
+pool_name(_Id) ->
+    fun() -> p1 end.
+
+init_driver() ->
+    consumer_sup:start_link(),
+    member_sup:start_link(),
+    application:start(pooler),
+    Delay = 1000,
+    PoolConfig = [{name, p1},
+                  {max_count, 5},
+                  {init_count, 2},
+                  {start_mfa,
+                   {member_sup, new_member, [Delay]}}],
+    pooler:new_pool(PoolConfig),
+    ok.