├── README ├── rebar ├── .hgignore ├── Makefile ├── examples ├── null_test.config ├── casbench.config ├── httpraw.config ├── bitcask.config ├── riakc_pb.config ├── riakclient.config └── innostore_test.config ├── rebar.config ├── include └── basho_bench.hrl ├── docs ├── pdf-style.org ├── html-style.org └── Documentation.org ├── priv ├── common.r ├── compare.r └── summary.r ├── src ├── basho_bench_driver_null.erl ├── basho_bench_driver_dets.erl ├── basho_bench_config.erl ├── basho_bench_app.erl ├── basho_bench_sup.erl ├── basho_bench_valgen.erl ├── basho_bench_driver_innostore.erl ├── basho_bench_driver_cassandra.erl ├── basho_bench_driver_bitcask.erl ├── basho_bench_log.erl ├── basho_bench_keygen.erl ├── basho_bench_driver_riakclient.erl ├── basho_bench_driver_riakc_pb.erl ├── basho_bench.erl ├── basho_bench_stats.erl ├── basho_bench_driver_http_raw.erl └── basho_bench_worker.erl ├── ebin └── basho_bench.app └── LICENSE /README: -------------------------------------------------------------------------------- 1 | Please see docs/Documentation.org. 2 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b/basho_bench/master/rebar -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | .beam 2 | tests/.*$ 3 | basho_bench$ 4 | erl_crash.dump$ 5 | deps/.*$ 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deps 2 | 3 | all: deps 4 | ./rebar compile escriptize 5 | 6 | deps: 7 | ./rebar get-deps 8 | 9 | clean: 10 | @./rebar clean 11 | 12 | distclean: clean 13 | @rm -rf basho_bench deps 14 | 15 | results: 16 | priv/summary.r -i tests/current 17 | -------------------------------------------------------------------------------- /examples/null_test.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 5}. 4 | 5 | {concurrent, 1}. 6 | 7 | {driver, basho_bench_driver_null}. 8 | 9 | {code_paths, ["deps/stats"]}. 10 | 11 | {key_generator, {sequential_int_bin, 5000000}}. 12 | 13 | {value_generator, {fixed_bin, 10248}}. 14 | 15 | {operations, [{put, 1}]}. 16 | -------------------------------------------------------------------------------- /examples/casbench.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 1}. 4 | 5 | {concurrent, 10}. 6 | 7 | {driver, basho_bench_driver_cassandra}. 8 | 9 | {key_generator, {uniform_int_str, 5000000}}. 10 | 11 | {value_generator, {fixed_bin, 10000}}. 12 | 13 | {operations, [{get, 1}, {put, 1}]}. 14 | 15 | {code_paths, ["deps/stats", 16 | "deps/ibrowse", 17 | "deps/casbench"]}. 18 | -------------------------------------------------------------------------------- /examples/httpraw.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 1}. 4 | 5 | {concurrent, 3}. 6 | 7 | {driver, basho_bench_driver_http_raw}. 8 | 9 | %{code_paths, ["deps/stats", 10 | % "deps/ibrowse"]}. 11 | 12 | {key_generator, {external, test, hoss_seq, [10000, 10, 10, 100]}}. 13 | 14 | {value_generator, {fixed_bin, 10000}}. 15 | 16 | {http_raw_port, 8091}. 17 | 18 | {operations, [{update, 1}]}. 19 | 20 | {source_dir, "foo"}. 21 | -------------------------------------------------------------------------------- /examples/bitcask.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 10}. 4 | 5 | {concurrent, 1}. 6 | 7 | {driver, basho_bench_driver_bitcask}. 8 | 9 | {key_generator, {uniform_int_bin, 5000000}}. 10 | 11 | {value_generator, {fixed_bin, 10000}}. 12 | 13 | {operations, [{get, 1}, {put, 1}]}. 14 | 15 | {code_paths, ["deps/stats", 16 | "../../public/bitcask"]}. 17 | 18 | {bitcask_dir, "/tmp/bitcask.bench"}. 19 | 20 | {bitcask_flags, [o_sync]}. 21 | -------------------------------------------------------------------------------- /examples/riakc_pb.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 10}. 4 | 5 | {concurrent, 1}. 6 | 7 | {driver, basho_bench_driver_riakc_pb}. 8 | 9 | {code_paths, ["deps/stats", 10 | "deps/riakc", 11 | "deps/protobuffs"]}. 12 | 13 | {key_generator, {uniform_int_str, 10000}}. 14 | 15 | {value_generator, {fixed_bin, 10000}}. 16 | 17 | {riakc_pb_ips, [{127,0,0,1}]}. 18 | 19 | {riakc_pb_replies, 1}. 20 | 21 | {operations, [{get, 1}, {update, 1}]}. 22 | 23 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {stats, "3", {hg, "http://bitbucket.org/dizzyd/stats", "tip"}}, 3 | {ibrowse, "1.*", {git, "http://github.com/dizzyd/ibrowse.git", "HEAD"}}, 4 | {casbench, "0.1", {hg, "http://bitbucket.org/basho/casbench", "tip"}}, 5 | {riakc, ".*", {hg, "http://bitbucket.org/basho/riak-erlang-client", "tip"}}, 6 | {protobuffs, ".*", {hg, "http://bitbucket.org/basho/protobuffs", "tip"}} 7 | ]}. 8 | 9 | {escript_incl_apps, [stats, ibrowse, riakc, protobuffs]}. 10 | -------------------------------------------------------------------------------- /include/basho_bench.hrl: -------------------------------------------------------------------------------- 1 | 2 | 3 | -define(FAIL_MSG(Str, Args), ?ERROR(Str, Args), halt(1)). 4 | 5 | -define(CONSOLE(Str, Args), basho_bench_log:log(console, Str, Args)). 6 | 7 | -define(DEBUG(Str, Args), basho_bench_log:log(debug, Str, Args)). 8 | -define(INFO(Str, Args), basho_bench_log:log(info, Str, Args)). 9 | -define(WARN(Str, Args), basho_bench_log:log(warn, Str, Args)). 10 | -define(ERROR(Str, Args), basho_bench_log:log(error, Str, Args)). 11 | 12 | -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). 13 | 14 | -------------------------------------------------------------------------------- /examples/riakclient.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 15}. 4 | 5 | {concurrent, 5}. 6 | 7 | {driver, basho_bench_driver_riakclient}. 8 | 9 | {code_paths, ["deps/stats", 10 | "/Users/jmeredith/basho/riak/apps/riak_kv", 11 | "/Users/jmeredith/basho/riak/apps/riak_core"]}. 12 | 13 | {key_generator, {uniform_int_bin, 35000}}. 14 | 15 | {value_generator, {fixed_bin, 10000}}. 16 | 17 | {riakclient_nodes, ['riak@127.0.0.1']}. 18 | 19 | {riakclient_mynode, ['riak_bench@127.0.0.1', longnames]}. 20 | 21 | {riakclient_replies, 1}. 22 | -------------------------------------------------------------------------------- /examples/innostore_test.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | {duration, 10}. 4 | 5 | {concurrent, 1}. 6 | 7 | {driver, basho_bench_driver_innostore}. 8 | 9 | {code_paths, ["deps/stats", 10 | "../innostore"]}. 11 | 12 | {key_generator, {uniform_int_bin, 500000}}. 13 | 14 | {value_generator, {fixed_bin, 10000}}. 15 | 16 | {innostore_config, [ 17 | {data_home_dir, "/tmp/innodb"}, 18 | {log_group_home_dir, "/tmp/innodb"}, 19 | {buffer_pool_size, 1073741824}, %% 1G of Buffer 20 | {log_files_in_group, 4}, 21 | {log_file_size, 134217728} %% 128 MB log files 22 | ]}. 23 | 24 | {operations, [{put, 1}]}. 25 | -------------------------------------------------------------------------------- /docs/pdf-style.org: -------------------------------------------------------------------------------- 1 | #+LANGUAGE: en 2 | #+LATEX_HEADER: \usepackage{color} 3 | #+LATEX_HEADER: \usepackage{sectsty} 4 | #+LATEX_HEADER: \usepackage{listings} 5 | #+LATEX_HEADER: \usepackage[T1]{fontenc} 6 | #+LATEX_HEADER: \usepackage{cmbright} 7 | #+LATEX_HEADER: \usepackage[left=1in,top=1in,right=1in,bottom=1in, nohead]{geometry} 8 | #+LATEX_HEADER: \usepackage{hyperref} 9 | #+LATEX_HEADER: \setlength\parindent{0in} 10 | #+LATEX_HEADER: \setlength\parskip{0.1in} 11 | #+LATEX_HEADER:\sectionfont{\pagebreak\fontfamily{iwona}\Huge\selectfont} 12 | #+LATEX_HEADER:\subsectionfont{\fontfamily{iwona}\color[rgb]{0.18,0.41,0.56}\selectfont} 13 | #+LATEX_HEADER:\hypersetup{pdfborder={0 0 0 0}, colorlinks=true, linkcolor=[rgb]{0.80,0.44,0.02},urlcolor=[rgb]{0.80,0.44,0.02}} 14 | #+STYLE: 15 | #+OPTIONS: H:3 toc:2 num:t 16 | -------------------------------------------------------------------------------- /priv/common.r: -------------------------------------------------------------------------------- 1 | 2 | # Load a library, or attempt to install it if it's not available 3 | load_library <- function(Name) 4 | { 5 | if (!library(Name, character.only = TRUE, logical.return = TRUE)) 6 | { 7 | install.packages(Name, repos = "http://lib.stat.cmu.edu/R/CRAN") 8 | } 9 | } 10 | 11 | # Load a latency file and ensure that it is appropriately tagged 12 | load_latency_frame <- function(File) 13 | { 14 | op <- strsplit(basename(File), "_")[[1]][1] 15 | frame <- read.csv(File) 16 | frame$op = rep(op, nrow(frame)) 17 | return (frame) 18 | } 19 | 20 | # Load summary and latency information for a given directory 21 | load_benchmark <- function(Dir) 22 | { 23 | ## Load up summary data 24 | summary <- read.csv(sprintf("%s/%s", Dir, "summary.csv")) 25 | 26 | ## Get a list of latency files 27 | latencies <- lapply(list.files(path = Dir, pattern = "_latencies.csv", 28 | full.names = TRUE), 29 | load_latency_frame) 30 | latencies <- do.call('rbind', latencies) 31 | 32 | ## Convert timing information in latencies from usecs -> msecs 33 | latencies[4:10] <- latencies[4:10] / 1000 34 | 35 | return (list(summary = summary, latencies = latencies)) 36 | } 37 | 38 | load_library("getopt") 39 | load_library("grid") 40 | load_library("ggplot2") 41 | -------------------------------------------------------------------------------- /docs/html-style.org: -------------------------------------------------------------------------------- 1 | #+STYLE: 39 | -------------------------------------------------------------------------------- /src/basho_bench_driver_null.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_null). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | %% ==================================================================== 30 | %% API 31 | %% ==================================================================== 32 | 33 | new(_Id) -> 34 | {ok, undefined}. 35 | 36 | run(get, KeyGen, _ValueGen, _State) -> 37 | Key = KeyGen(), 38 | {ok, Key}; 39 | run(put, KeyGen, ValueGen, _State) -> 40 | Key = KeyGen(), 41 | ValueGen(), 42 | {ok, Key}; 43 | run(delete, KeyGen, _ValueGen, _State) -> 44 | Key = KeyGen(), 45 | {ok, Key}. 46 | 47 | -------------------------------------------------------------------------------- /priv/compare.r: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript --vanilla 2 | 3 | source("priv/common.r") 4 | 5 | # Setup parameters for the script 6 | params = matrix(c( 7 | 'width', 'w', 2, "integer", 8 | 'height', 'h', 2, "integer", 9 | 'outfile', 'o', 2, "character", 10 | 'dir1', 'i', 1, "character", 11 | 'tag1', 'j', 1, "character", 12 | 'dir2', 'k', 1, "character", 13 | 'tag2', 'l', 1, "character" 14 | ), ncol=4, byrow=TRUE) 15 | 16 | # Parse the parameters 17 | opt = getopt(params) 18 | 19 | # Initialize defaults for opt 20 | if (is.null(opt$width)) { opt$width = 1024 } 21 | if (is.null(opt$height)) { opt$height = 768 } 22 | if (is.null(opt$outfile)) { opt$outfile = "compare.png" } 23 | 24 | # Load the benchmark data for each directory 25 | b1 = load_benchmark(opt$dir1) 26 | b2 = load_benchmark(opt$dir2) 27 | 28 | # If there is no actual data available, bail 29 | if (nrow(b1$latencies) == 0) 30 | { 31 | stop("No latency information available to analyze in ", opt$indir) 32 | } 33 | 34 | if (nrow(b2$latencies) == 0) 35 | { 36 | stop("No latency information available to analyze in ", opt$indir) 37 | } 38 | 39 | png(file = opt$outfile, width = opt$width, height = opt$height) 40 | 41 | # Tag the summary frames for each benchmark so that we can distinguish 42 | # between them in the legend. 43 | b1$summary$tag <- opt$tag1 44 | b2$summary$tag <- opt$tag2 45 | 46 | # Compare the req/sec between the two datasets 47 | plot1 <- qplot(elapsed, total / window, 48 | data = b1$summary, 49 | color = tag, 50 | geom = "smooth", 51 | xlab = "Elapsed Secs", 52 | ylab = "Req/sec", 53 | main = "Throughput") + geom_smooth(data = b2$summary) 54 | 55 | 56 | grid.newpage() 57 | 58 | pushViewport(viewport(layout = grid.layout(3, 1))) 59 | 60 | vplayout <- function(x,y) viewport(layout.pos.row = x, layout.pos.col = y) 61 | 62 | print(plot1, vp = vplayout(1,1)) 63 | #print(plot2, vp = vplayout(2,1)) 64 | #print(plot3, vp = vplayout(3,1)) 65 | 66 | dev.off() 67 | 68 | -------------------------------------------------------------------------------- /src/basho_bench_driver_dets.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_dets). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | %% ==================================================================== 30 | %% API 31 | %% ==================================================================== 32 | 33 | new(_Id) -> 34 | File = basho_bench_config:get(dets_file, ?MODULE), 35 | {ok, _} = dets:open_file(?MODULE, [{file, File}, 36 | {min_no_slots, 8192}, 37 | {max_no_slots, 16777216}]), 38 | {ok, undefined}. 39 | 40 | run(get, KeyGen, _ValueGen, State) -> 41 | Key = KeyGen(), 42 | case dets:lookup(?MODULE, Key) of 43 | [] -> 44 | {ok, State}; 45 | [{Key, _}] -> 46 | {ok, State}; 47 | {error, Reason} -> 48 | {error, Reason, State} 49 | end; 50 | run(put, KeyGen, ValueGen, State) -> 51 | ok = dets:insert(?MODULE, {KeyGen(), ValueGen()}), 52 | {ok, State}; 53 | run(delete, KeyGen, _ValueGen, State) -> 54 | ok = dets:delete(?MODULE, KeyGen()), 55 | {ok, State}. 56 | 57 | -------------------------------------------------------------------------------- /src/basho_bench_config.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_config). 23 | 24 | -export([load/1, 25 | set/2, 26 | get/1, get/2]). 27 | 28 | -include("basho_bench.hrl"). 29 | 30 | %% =================================================================== 31 | %% Public API 32 | %% =================================================================== 33 | 34 | load(File) -> 35 | case file:consult(File) of 36 | {ok, Terms} -> 37 | load_config(Terms); 38 | {error, Reason} -> 39 | ?FAIL_MSG("Failed to parse config file ~s: ~p\n", [File, Reason]) 40 | end. 41 | 42 | set(Key, Value) -> 43 | ok = application:set_env(basho_bench, Key, Value). 44 | 45 | get(Key) -> 46 | case application:get_env(basho_bench, Key) of 47 | {ok, Value} -> 48 | Value; 49 | undefined -> 50 | erlang:error("Missing configuration key", [Key]) 51 | end. 52 | 53 | get(Key, Default) -> 54 | case application:get_env(basho_bench, Key) of 55 | {ok, Value} -> 56 | Value; 57 | _ -> 58 | Default 59 | end. 60 | 61 | 62 | %% =================================================================== 63 | %% Internal functions 64 | %% =================================================================== 65 | 66 | load_config([]) -> 67 | ok; 68 | load_config([{Key, Value} | Rest]) -> 69 | ?MODULE:set(Key, Value), 70 | load_config(Rest); 71 | load_config([ Other | Rest]) -> 72 | ?WARN("Ignoring non-tuple config value: ~p\n", [Other]), 73 | load_config(Rest). 74 | -------------------------------------------------------------------------------- /src/basho_bench_app.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_app). 23 | 24 | -behaviour(application). 25 | 26 | %% API 27 | -export([start/0, 28 | stop/0, 29 | is_running/0]). 30 | 31 | %% Application callbacks 32 | -export([start/2, stop/1]). 33 | 34 | 35 | %% =================================================================== 36 | %% API 37 | %%=================================================================== 38 | 39 | start() -> 40 | %% Redirect all SASL logging into a text file 41 | application:load(sasl), 42 | application:set_env(sasl, sasl_error_logger, {file, "log.sasl.txt"}), 43 | ok = application:start(sasl), 44 | 45 | %% Make sure crypto is available 46 | ok = application:start(crypto), 47 | 48 | %% Start up our application -- mark it as permanent so that the node 49 | %% will be killed if we go down 50 | application:start(basho_bench, permanent). 51 | 52 | stop() -> 53 | application:stop(basho_bench). 54 | 55 | is_running() -> 56 | application:get_env(basho_bench_app, is_running) == {ok, true}. 57 | 58 | 59 | %% =================================================================== 60 | %% Application callbacks 61 | %%=================================================================== 62 | 63 | start(_StartType, _StartArgs) -> 64 | {ok, Pid} = basho_bench_sup:start_link(), 65 | application:set_env(basho_bench_app, is_running, true), 66 | ok = basho_bench_stats:run(), 67 | ok = basho_bench_worker:run(basho_bench_sup:workers()), 68 | {ok, Pid}. 69 | 70 | 71 | stop(_State) -> 72 | ok. 73 | 74 | %% =================================================================== 75 | %% Internal functions 76 | %% =================================================================== 77 | -------------------------------------------------------------------------------- /ebin/basho_bench.app: -------------------------------------------------------------------------------- 1 | {application, basho_bench, 2 | [{description, "Riak Benchmarking Suite"}, 3 | {vsn, "0.1"}, 4 | {modules, [ 5 | basho_bench, 6 | basho_bench_app, 7 | basho_bench_config, 8 | basho_bench_driver_dets, 9 | basho_bench_driver_http_raw, 10 | basho_bench_driver_innostore, 11 | basho_bench_driver_riakc_pb, 12 | basho_bench_driver_riakclient, 13 | basho_bench_driver_cassandra, 14 | basho_bench_driver_bitcask, 15 | basho_bench_driver_null, 16 | basho_bench_log, 17 | basho_bench_keygen, 18 | basho_bench_stats, 19 | basho_bench_sup, 20 | basho_bench_worker, 21 | basho_bench_valgen 22 | ]}, 23 | {registered, [ basho_bench_sup ]}, 24 | {applications, [kernel, 25 | stdlib, 26 | sasl]}, 27 | {mod, {basho_bench_app, []}}, 28 | {env, [ 29 | %% 30 | %% Mode of load generation: 31 | %% max - Generate as many requests as possible per worker 32 | %% {rate, Rate} - Exp. distributed Mean reqs/sec 33 | %% 34 | {mode, {rate, 5}}, 35 | 36 | %% 37 | %% Default log level 38 | %% 39 | {log_level, debug}, 40 | 41 | %% 42 | %% Base test output directory 43 | %% 44 | {test_dir, "tests"}, 45 | 46 | %% 47 | %% Test duration (minutes) 48 | %% 49 | {duration, 5}, 50 | 51 | %% 52 | %% Number of concurrent workers 53 | %% 54 | {concurrent, 3}, 55 | 56 | %% 57 | %% Driver module for the current test 58 | %% 59 | {driver, basho_bench_driver_http_raw}, 60 | 61 | %% 62 | %% Operations (and associated mix). Note that 63 | %% the driver may not implement every operation. 64 | %% 65 | {operations, [{get, 4}, 66 | {put, 4}, 67 | {delete, 1}]}, 68 | 69 | %% 70 | %% Interval on which to report latencies and status (seconds) 71 | %% 72 | {report_interval, 10}, 73 | 74 | %% 75 | %% Key generators 76 | %% 77 | %% {uniform_int, N} - Choose a uniformly distributed integer between 0 and N 78 | %% 79 | {key_generator, {uniform_int, 100000}}, 80 | 81 | %% 82 | %% Value generators 83 | %% 84 | %% {fixed_bin, N} - Fixed size binary blob of N bytes 85 | %% 86 | {value_generator, {fixed_bin, 100}}, 87 | 88 | %% 89 | %% RNG Seed -- ensures consistent generation of key/value sizes (but not content!) 90 | %% 91 | {rng_seed, {42, 23, 12}} 92 | ]} 93 | ]}. 94 | -------------------------------------------------------------------------------- /src/basho_bench_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_sup). 23 | 24 | -behaviour(supervisor). 25 | 26 | %% API 27 | -export([start_link/0, 28 | workers/0, 29 | stop_child/1]). 30 | 31 | %% Supervisor callbacks 32 | -export([init/1]). 33 | 34 | -include("basho_bench.hrl"). 35 | 36 | %% Helper macro for declaring children of supervisor 37 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 38 | 39 | %% =================================================================== 40 | %% API functions 41 | %% =================================================================== 42 | 43 | start_link() -> 44 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 45 | 46 | workers() -> 47 | [Pid || {_Id, Pid, worker, [basho_bench_worker]} <- supervisor:which_children(?MODULE)]. 48 | 49 | stop_child(Id) -> 50 | ok = supervisor:terminate_child(?MODULE, Id), 51 | ok = supervisor:delete_child(?MODULE, Id). 52 | 53 | 54 | %% =================================================================== 55 | %% Supervisor callbacks 56 | %% =================================================================== 57 | 58 | init([]) -> 59 | %% Get the number concurrent workers we're expecting and generate child 60 | %% specs for each 61 | Workers = worker_specs(basho_bench_config:get(concurrent), []), 62 | {ok, {{one_for_one, 5, 10}, [?CHILD(basho_bench_log, worker), 63 | ?CHILD(basho_bench_stats, worker)] ++ Workers}}. 64 | 65 | 66 | %% =================================================================== 67 | %% Internal functions 68 | %% =================================================================== 69 | 70 | worker_specs(0, Acc) -> 71 | Acc; 72 | worker_specs(Count, Acc) -> 73 | Id = list_to_atom(lists:concat(['basho_bench_worker_', Count])), 74 | Spec = {Id, {basho_bench_worker, start_link, [Id, Count]}, 75 | permanent, 5000, worker, [basho_bench_worker]}, 76 | worker_specs(Count-1, [Spec | Acc]). 77 | -------------------------------------------------------------------------------- /priv/summary.r: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript --vanilla 2 | 3 | # Parse the --file= argument out of command line args and 4 | # determine where base directory is so that we can source 5 | # our common sub-routines 6 | arg0 <- sub("--file=(.*)", "\\1", grep("--file=", commandArgs(), value = TRUE)) 7 | dir0 <- dirname(arg0) 8 | source(file.path(dir0, "common.r")) 9 | 10 | # Setup parameters for the script 11 | params = matrix(c( 12 | 'help', 'h', 0, "logical", 13 | 'width', 'x', 2, "integer", 14 | 'height', 'y', 2, "integer", 15 | 'outfile', 'o', 2, "character", 16 | 'indir', 'i', 2, "character" 17 | ), ncol=4, byrow=TRUE) 18 | 19 | # Parse the parameters 20 | opt = getopt(params) 21 | 22 | if (!is.null(opt$help)) 23 | { 24 | cat(paste(getopt(params, command = basename(arg0), usage = TRUE))) 25 | q(status=1) 26 | } 27 | 28 | # Initialize defaults for opt 29 | if (is.null(opt$width)) { opt$width = 1024 } 30 | if (is.null(opt$height)) { opt$height = 768 } 31 | if (is.null(opt$indir)) { opt$indir = "current"} 32 | if (is.null(opt$outfile)) { opt$outfile = file.path(opt$indir, "summary.png") } 33 | 34 | # Load the benchmark data 35 | b = load_benchmark(opt$indir) 36 | 37 | # If there is no actual data available, bail 38 | if (nrow(b$latencies) == 0) 39 | { 40 | stop("No latency information available to analyze in ", opt$indir) 41 | } 42 | 43 | png(file = opt$outfile, width = opt$width, height = opt$height) 44 | 45 | # First plot req/sec from summary 46 | plot1 <- qplot(elapsed, total / window, data = b$summary, 47 | geom = "smooth", 48 | xlab = "Elapsed Secs", ylab = "Op/sec", 49 | main = "Throughput") 50 | 51 | # Setup common elements of the latency plots 52 | latency_plot <- ggplot(b$latencies, aes(x = elapsed)) + 53 | facet_grid(. ~ op) + 54 | labs(x = "Elapsed Secs", y = "Latency (ms)") 55 | 56 | # Plot 99 and 99.9th percentiles 57 | plot2 <- latency_plot + 58 | geom_smooth(aes(y = X99th, color = "X99th")) + 59 | geom_smooth(aes(y = X99_9th, color = "X99_9th")) + 60 | scale_color_hue("Percentile", 61 | breaks = c("X99th", "X99_9th"), 62 | labels = c("99th", "99.9th")) 63 | 64 | 65 | # Plot median, mean and 95th percentiles 66 | plot3 <- latency_plot + 67 | geom_smooth(aes(y = median, color = "median")) + 68 | geom_smooth(aes(y = mean, color = "mean")) + 69 | geom_smooth(aes(y = X95th, color = "X95th")) + 70 | scale_color_hue("Percentile", 71 | breaks = c("median", "mean", "X95th"), 72 | labels = c("Median", "Mean", "95th")) 73 | 74 | grid.newpage() 75 | 76 | pushViewport(viewport(layout = grid.layout(3, 1))) 77 | 78 | vplayout <- function(x,y) viewport(layout.pos.row = x, layout.pos.col = y) 79 | 80 | print(plot1, vp = vplayout(1,1)) 81 | print(plot2, vp = vplayout(2,1)) 82 | print(plot3, vp = vplayout(3,1)) 83 | 84 | dev.off() 85 | -------------------------------------------------------------------------------- /src/basho_bench_valgen.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_valgen). 23 | 24 | -export([new/2, 25 | dimension/2]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | %% ==================================================================== 30 | %% API 31 | %% ==================================================================== 32 | 33 | new({fixed_bin, Size}, _Id) -> 34 | Source = init_source(), 35 | fun() -> data_block(Source, Size) end; 36 | new({exponential_bin, MinSize, Mean}, _Id) -> 37 | Source = init_source(), 38 | fun() -> data_block(Source, MinSize + trunc(stats_rv:exponential(1 / Mean))) end; 39 | new({uniform_bin, MinSize, MaxSize}, _Id) -> 40 | Source = init_source(), 41 | Diff = MaxSize - MinSize, 42 | fun() -> data_block(Source, MinSize + random:uniform(Diff)) end; 43 | new({function, Module, Function, Args}, Id) -> 44 | case code:ensure_loaded(Module) of 45 | {module, Module} -> 46 | erlang:apply(Module, Function, [Id] ++ Args); 47 | _Error -> 48 | ?FAIL_MSG("Could not find valgen function: ~p:~p\n", [Module, Function]) 49 | end; 50 | new(Other, _Id) -> 51 | ?FAIL_MSG("Unsupported value generator requested: ~p\n", [Other]). 52 | 53 | dimension({fixed_bin, Size}, KeyDimension) -> 54 | Size * KeyDimension; 55 | dimension(_Other, _) -> 56 | 0.0. 57 | 58 | 59 | 60 | %% ==================================================================== 61 | %% Internal Functions 62 | %% ==================================================================== 63 | 64 | init_source() -> 65 | SourceSz = basho_bench_config:get(value_generator_source_size, 1048576), 66 | {SourceSz, crypto:rand_bytes(SourceSz)}. 67 | 68 | data_block({SourceSz, Source}, BlockSize) -> 69 | case SourceSz - BlockSize > 0 of 70 | true -> 71 | Offset = random:uniform(SourceSz - BlockSize), 72 | <<_:Offset/bytes, Slice:BlockSize/bytes, _Rest/binary>> = Source, 73 | Slice; 74 | false -> 75 | ?WARN("value_generator_source_size is too small; it needs a value > ~p.\n", 76 | [BlockSize]), 77 | Source 78 | end. 79 | -------------------------------------------------------------------------------- /src/basho_bench_driver_innostore.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_innostore). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | %% ==================================================================== 30 | %% API 31 | %% ==================================================================== 32 | 33 | new(_Id) -> 34 | %% Make sure innostore app is available 35 | case code:which(innostore) of 36 | non_existing -> 37 | ?FAIL_MSG("~s requires innostore to be available on code path.\n", 38 | [?MODULE]); 39 | _ -> 40 | ok 41 | end, 42 | 43 | %% Pull the innodb_config key which has all the key/value pairs for the innostore 44 | %% engine -- stuff everything into the innostore application namespace 45 | %% so that starting innostore will pull it in. 46 | application:load(innostore), 47 | InnoConfig = basho_bench_config:get(innostore_config, []), 48 | [ok = application:set_env(innostore, K, V) || {K, V} <- InnoConfig], 49 | 50 | Bucket = basho_bench_config:get(innostore_bucket, <<"test">>), 51 | {ok, Port} = innostore:connect(), 52 | case innostore:open_keystore(Bucket, Port) of 53 | {ok, Store} -> 54 | {ok, Store}; 55 | {error, Reason} -> 56 | ?FAIL_MSG("Failed to open keystore ~p: ~p\n", [Bucket, Reason]) 57 | end. 58 | 59 | 60 | run(get, KeyGen, _ValueGen, State) -> 61 | case innostore:get(KeyGen(), State) of 62 | {ok, not_found} -> 63 | {ok, State}; 64 | {ok, _Value} -> 65 | {ok, State}; 66 | {error, Reason} -> 67 | {error, Reason, State} 68 | end; 69 | run(put, KeyGen, ValueGen, State) -> 70 | case innostore:put(KeyGen(), ValueGen(), State) of 71 | ok -> 72 | {ok, State}; 73 | {error, Reason} -> 74 | {error, State, Reason} 75 | end; 76 | run(delete, KeyGen, _ValueGen, State) -> 77 | case innostore:delete(KeyGen(), State) of 78 | ok -> 79 | {ok, State}; 80 | {error, Reason} -> 81 | {error, Reason, State} 82 | end. 83 | -------------------------------------------------------------------------------- /src/basho_bench_driver_cassandra.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_cassandra). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | -include_lib("casbench/include/cassandra_thrift.hrl"). 29 | 30 | -record(state, { client, 31 | keyspace, 32 | colpath }). 33 | 34 | 35 | %% ==================================================================== 36 | %% API 37 | %% ==================================================================== 38 | 39 | new(Id) -> 40 | %% Make sure the path is setup such that we can get at riak_client 41 | case code:which(cassandra_thrift) of 42 | non_existing -> 43 | ?FAIL_MSG("~s requires cassandra_thrift module to be available on code path.\n", 44 | [?MODULE]); 45 | _ -> 46 | ok 47 | end, 48 | 49 | Hosts = basho_bench_config:get(cassandra_hosts, ["localhost"]), 50 | Port = basho_bench_config:get(cassandra_port, 9160), 51 | Keyspace = basho_bench_config:get(cassandra_keyspace, "Keyspace1"), 52 | ColPath = #columnPath { column_family = "Standard1", column = "col1" }, 53 | 54 | %% Choose the node using our ID as a modulus 55 | TargetHost = lists:nth((Id rem length(Hosts)+1), Hosts), 56 | ?INFO("Using target ~s:~p for worker ~p\n", [TargetHost, Port, Id]), 57 | 58 | case thrift_client:start_link(TargetHost, Port, cassandra_thrift) of 59 | {ok, Client} -> 60 | {ok, #state { client = Client, 61 | keyspace = Keyspace, 62 | colpath = ColPath }}; 63 | {error, Reason} -> 64 | ?FAIL_MSG("Failed to get a thrift_client for ~p: ~p\n", [TargetHost, Reason]) 65 | end. 66 | 67 | call(State, Op, Args) -> 68 | (catch thrift_client:call(State#state.client, Op, Args)). 69 | 70 | tstamp() -> 71 | {Mega, Sec, _Micro} = now(), 72 | (Mega * 1000000) + Sec. 73 | 74 | 75 | run(get, KeyGen, _ValueGen, State) -> 76 | Key = KeyGen(), 77 | case call(State, get, [State#state.keyspace, Key, State#state.colpath, 1]) of 78 | {ok, _} -> 79 | {ok, State}; 80 | {notFoundException} -> 81 | {ok, State}; 82 | {'EXIT', {timeout, _}} -> 83 | {error, timeout, State}; 84 | Error -> 85 | {error, Error, State} 86 | end; 87 | run(put, KeyGen, ValueGen, State) -> 88 | case call(State, insert, [State#state.keyspace, KeyGen(), State#state.colpath, 89 | ValueGen(), tstamp(), 1]) of 90 | {ok, ok} -> 91 | {ok, State}; 92 | {'EXIT', {timeout, _}} -> 93 | {error, timeout, State}; 94 | Error -> 95 | {error, Error, State} 96 | end. 97 | -------------------------------------------------------------------------------- /src/basho_bench_driver_bitcask.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_bitcask). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | -record(state, { file, 30 | sync_interval, 31 | last_sync }). 32 | 33 | %% ==================================================================== 34 | %% API 35 | %% ==================================================================== 36 | 37 | new(_Id) -> 38 | %% Make sure bitcask is available 39 | case code:which(bitcask) of 40 | non_existing -> 41 | ?FAIL_MSG("~s requires bitcask to be available on code path.\n", 42 | [?MODULE]); 43 | _ -> 44 | ok 45 | end, 46 | 47 | %% Get the target directory 48 | Dir = basho_bench_config:get(bitcask_dir, "."), 49 | Filename = filename:join(Dir, "test.bitcask"), 50 | 51 | %% Look for sync interval config 52 | case basho_bench_config:get(bitcask_sync_interval, infinity) of 53 | Value when is_integer(Value) -> 54 | SyncInterval = Value; 55 | infinity -> 56 | SyncInterval = infinity 57 | end, 58 | 59 | %% Get any bitcask flags 60 | Flags = basho_bench_config:get(bitcask_flags, []), 61 | case bitcask:open(Filename, [read_write] ++ Flags) of 62 | {error, Reason} -> 63 | ?FAIL_MSG("Failed to open bitcask in ~s: ~p\n", [Filename, Reason]); 64 | File -> 65 | {ok, #state { file = File, sync_interval = SyncInterval, 66 | last_sync = os:timestamp() }} 67 | end. 68 | 69 | 70 | 71 | run(get, KeyGen, _ValueGen, State) -> 72 | State1 = maybe_sync(State), 73 | case bitcask:get(State1#state.file, KeyGen()) of 74 | {ok, _Value} -> 75 | {ok, State1}; 76 | not_found -> 77 | {ok, State1}; 78 | {error, Reason} -> 79 | {error, Reason} 80 | end; 81 | run(put, KeyGen, ValueGen, State) -> 82 | State1 = maybe_sync(State), 83 | case bitcask:put(State1#state.file, KeyGen(), ValueGen()) of 84 | ok -> 85 | {ok, State1}; 86 | {error, Reason} -> 87 | {error, Reason} 88 | end. 89 | 90 | 91 | 92 | maybe_sync(#state { sync_interval = infinity } = State) -> 93 | State; 94 | maybe_sync(#state { sync_interval = SyncInterval } = State) -> 95 | Now = os:timestamp(), 96 | case timer:now_diff(Now, State#state.last_sync) / 1000000 of 97 | Value when Value >= SyncInterval -> 98 | bitcask:sync(State#state.file), 99 | State#state { last_sync = Now }; 100 | _ -> 101 | State 102 | end. 103 | -------------------------------------------------------------------------------- /src/basho_bench_log.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_log). 23 | 24 | -behaviour(gen_server). 25 | 26 | %% API 27 | -export([start_link/0, 28 | log/3]). 29 | 30 | %% gen_server callbacks 31 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 32 | terminate/2, code_change/3]). 33 | 34 | -record(state, { log_level, 35 | log_file }). 36 | 37 | %% ==================================================================== 38 | %% API 39 | %% ==================================================================== 40 | 41 | start_link() -> 42 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 43 | 44 | log(Level, Str, Args) -> 45 | case whereis(?MODULE) of 46 | undefined -> 47 | basic_log(Level, Str, Args); 48 | Pid -> 49 | gen_server:call(Pid, {log, Level, Str, Args}) 50 | end. 51 | 52 | 53 | %% ==================================================================== 54 | %% gen_server callbacks 55 | %% ==================================================================== 56 | 57 | init([]) -> 58 | LogLevel = basho_bench_config:get(log_level), 59 | {ok, LogFile} = file:open("log.txt", [raw, binary, write]), 60 | {ok, #state{ log_level = LogLevel, 61 | log_file = LogFile }}. 62 | 63 | handle_call({log, Level, Str, Args}, _From, State) -> 64 | case should_log(State#state.log_level, Level) of 65 | true -> 66 | Message = io_lib:format(log_prefix(Level) ++ Str, Args), 67 | ok = file:write(State#state.log_file, Message), 68 | ok = io:format(Message); 69 | false -> 70 | ok 71 | end, 72 | {reply, ok, State}. 73 | 74 | handle_cast(_Msg, State) -> 75 | {noreply, State}. 76 | 77 | handle_info(_Info, State) -> 78 | {noreply, State}. 79 | 80 | terminate(_Reason, _State) -> 81 | ok. 82 | 83 | code_change(_OldVsn, State, _Extra) -> 84 | {ok, State}. 85 | 86 | 87 | %% =================================================================== 88 | %% Internal functions 89 | %% =================================================================== 90 | 91 | basic_log(Level, Str, Args) -> 92 | {ok, LogLevel} = application:get_env(basho_bench, log_level), 93 | case should_log(LogLevel, Level) of 94 | true -> 95 | io:format(log_prefix(Level) ++ Str, Args); 96 | false -> 97 | ok 98 | end. 99 | 100 | should_log(_, console) -> true; 101 | should_log(debug, _) -> true; 102 | should_log(info, debug) -> false; 103 | should_log(info, _) -> true; 104 | should_log(warn, debug) -> false; 105 | should_log(warn, info) -> false; 106 | should_log(warn, _) -> true; 107 | should_log(error, error) -> true; 108 | should_log(error, _) -> false; 109 | should_log(_, _) -> false. 110 | 111 | log_prefix(console) -> ""; 112 | log_prefix(debug) -> "DEBUG:" ; 113 | log_prefix(info) -> "INFO: "; 114 | log_prefix(warn) -> "WARN: "; 115 | log_prefix(error) -> "ERROR: ". 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/basho_bench_keygen.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_keygen). 23 | 24 | -export([new/2, 25 | dimension/1]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | %% Use a fixed shape for Pareto that will yield the desired 80/20 30 | %% ratio of generated values. 31 | -define(PARETO_SHAPE, 1.5). 32 | 33 | %% ==================================================================== 34 | %% API 35 | %% ==================================================================== 36 | 37 | new({sequential_int, MaxKey}, _Id) -> 38 | Ref = make_ref(), 39 | fun() -> sequential_int_generator(Ref, MaxKey) end; 40 | new({sequential_int_bin, MaxKey}, _Id) -> 41 | Ref = make_ref(), 42 | fun() -> Key = sequential_int_generator(Ref, MaxKey), <> end; 43 | new({sequential_int_str, MaxKey}, _Id) -> 44 | Ref = make_ref(), 45 | fun() -> Key = sequential_int_generator(Ref, MaxKey), integer_to_list(Key) end; 46 | new({uniform_int_bin, MaxKey}, _Id) -> 47 | fun() -> Key = random:uniform(MaxKey), <> end; 48 | new({uniform_int_str, MaxKey}, _Id) -> 49 | fun() -> Key = random:uniform(MaxKey), integer_to_list(Key) end; 50 | new({uniform_int, MaxKey}, _Id) -> 51 | fun() -> random:uniform(MaxKey) end; 52 | new({pareto_int, MaxKey}, _Id) -> 53 | pareto(trunc(MaxKey * 0.2), ?PARETO_SHAPE); 54 | new({pareto_int_bin, MaxKey}, _Id) -> 55 | Pareto = pareto(trunc(MaxKey * 0.2), ?PARETO_SHAPE), 56 | fun() -> <<(Pareto()):32/native>> end; 57 | new({function, Module, Function, Args}, Id) -> 58 | case code:ensure_loaded(Module) of 59 | {module, Module} -> 60 | erlang:apply(Module, Function, [Id] ++ Args); 61 | _Error -> 62 | ?FAIL_MSG("Could not find keygen function: ~p:~p\n", [Module, Function]) 63 | end; 64 | new(Other, _Id) -> 65 | ?FAIL_MSG("Unsupported key generator requested: ~p\n", [Other]). 66 | 67 | 68 | dimension({sequential_int, MaxKey}) -> 69 | MaxKey; 70 | dimension({sequential_int_bin, MaxKey}) -> 71 | MaxKey; 72 | dimension({sequential_int_str, MaxKey}) -> 73 | MaxKey; 74 | dimension({uniform_int_bin, MaxKey}) -> 75 | MaxKey; 76 | dimension({uniform_int_str, MaxKey}) -> 77 | MaxKey; 78 | dimension({uniform_int, MaxKey}) -> 79 | MaxKey; 80 | dimension(Other) -> 81 | ?INFO("No dimension available for key generator: ~p\n", [Other]), 82 | undefined. 83 | 84 | 85 | 86 | 87 | %% ==================================================================== 88 | %% Internal functions 89 | %% ==================================================================== 90 | 91 | pareto(Mean, Shape) -> 92 | S1 = (-1 / Shape), 93 | S2 = Mean * (Shape - 1), 94 | fun() -> 95 | U = 1 - random:uniform(), 96 | trunc((math:pow(U, S1) - 1) * S2) 97 | end. 98 | 99 | 100 | sequential_int_generator(Ref, MaxValue) -> 101 | %% A bit of evil here. We want to generate numbers in sequence and stop 102 | %% at MaxKey. This means we need state in our anonymous function. Use the process 103 | %% dictionary to keep track of where we are. 104 | case erlang:get({sigen, Ref}) of 105 | undefined -> 106 | erlang:put({sigen, Ref}, 1), 107 | 0; 108 | MaxValue -> 109 | throw({stop, empty_keygen}); 110 | Value -> 111 | case Value rem 5000 of 112 | 0 -> 113 | ?DEBUG("sequential_int_gen: ~p (~w%)\n", [Value, trunc(100 * (Value / MaxValue))]); 114 | _ -> 115 | ok 116 | end, 117 | erlang:put({sigen, Ref}, Value+1), 118 | Value 119 | end. 120 | -------------------------------------------------------------------------------- /src/basho_bench_driver_riakclient.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_riakclient). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | -record(state, { client, 30 | bucket, 31 | replies }). 32 | 33 | %% ==================================================================== 34 | %% API 35 | %% ==================================================================== 36 | 37 | new(Id) -> 38 | %% Make sure the path is setup such that we can get at riak_client 39 | case code:which(riak_client) of 40 | non_existing -> 41 | ?FAIL_MSG("~s requires riak_client module to be available on code path.\n", 42 | [?MODULE]); 43 | _ -> 44 | ok 45 | end, 46 | 47 | Nodes = basho_bench_config:get(riakclient_nodes), 48 | Cookie = basho_bench_config:get(riakclient_cookie, 'riak'), 49 | MyNode = basho_bench_config:get(riakclient_mynode, [basho_bench, longnames]), 50 | Replies = basho_bench_config:get(riakclient_replies, 2), 51 | Bucket = basho_bench_config:get(riakclient_bucket, <<"test">>), 52 | 53 | %% Try to spin up net_kernel 54 | case net_kernel:start(MyNode) of 55 | {ok, _} -> 56 | ?INFO("Net kernel started as ~p\n", [node()]); 57 | {error, {already_started, _}} -> 58 | ok; 59 | {error, Reason} -> 60 | ?FAIL_MSG("Failed to start net_kernel for ~p: ~p\n", [?MODULE, Reason]) 61 | end, 62 | 63 | %% Initialize cookie for each of the nodes 64 | [true = erlang:set_cookie(N, Cookie) || N <- Nodes], 65 | 66 | %% Try to ping each of the nodes 67 | ping_each(Nodes), 68 | 69 | %% Choose the node using our ID as a modulus 70 | TargetNode = lists:nth((Id rem length(Nodes)+1), Nodes), 71 | ?INFO("Using target node ~p for worker ~p\n", [TargetNode, Id]), 72 | 73 | case riak:client_connect(TargetNode) of 74 | {ok, Client} -> 75 | {ok, #state { client = Client, 76 | bucket = Bucket, 77 | replies = Replies }}; 78 | {error, Reason2} -> 79 | ?FAIL_MSG("Failed get a riak:client_connect to ~p: ~p\n", [TargetNode, Reason2]) 80 | end. 81 | 82 | run(get, KeyGen, _ValueGen, State) -> 83 | Key = KeyGen(), 84 | case (State#state.client):get(State#state.bucket, Key, State#state.replies) of 85 | {ok, _} -> 86 | {ok, State}; 87 | {error, notfound} -> 88 | {ok, State}; 89 | {error, Reason} -> 90 | {error, Reason, State} 91 | end; 92 | run(put, KeyGen, ValueGen, State) -> 93 | Robj = riak_object:new(State#state.bucket, KeyGen(), ValueGen()), 94 | case (State#state.client):put(Robj, State#state.replies) of 95 | ok -> 96 | {ok, State}; 97 | {error, Reason} -> 98 | {error, Reason, State} 99 | end; 100 | run(update, KeyGen, ValueGen, State) -> 101 | Key = KeyGen(), 102 | case (State#state.client):get(State#state.bucket, Key, State#state.replies) of 103 | {ok, Robj} -> 104 | Robj2 = riak_object:update_value(Robj, ValueGen()), 105 | case (State#state.client):put(Robj2, State#state.replies) of 106 | ok -> 107 | {ok, State}; 108 | {error, Reason} -> 109 | {error, Reason, State} 110 | end; 111 | {error, notfound} -> 112 | Robj = riak_object:new(State#state.bucket, Key, ValueGen()), 113 | case (State#state.client):put(Robj, State#state.replies) of 114 | ok -> 115 | {ok, State}; 116 | {error, Reason} -> 117 | {error, Reason, State} 118 | end 119 | end; 120 | run(delete, KeyGen, _ValueGen, State) -> 121 | case (State#state.client):delete(State#state.bucket, KeyGen(), State#state.replies) of 122 | ok -> 123 | {ok, State}; 124 | {error, notfound} -> 125 | {ok, State}; 126 | {error, Reason} -> 127 | {error, Reason, State} 128 | end. 129 | 130 | 131 | %% ==================================================================== 132 | %% Internal functions 133 | %% ==================================================================== 134 | 135 | ping_each([]) -> 136 | ok; 137 | ping_each([Node | Rest]) -> 138 | case net_adm:ping(Node) of 139 | pong -> 140 | ping_each(Rest); 141 | pang -> 142 | ?FAIL_MSG("Failed to ping node ~p\n", [Node]) 143 | end. 144 | -------------------------------------------------------------------------------- /src/basho_bench_driver_riakc_pb.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench_driver_riakc_pb: Driver for riak protocol buffers client 4 | %% 5 | %% Copyright (c) 2009 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_riakc_pb). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | -record(state, { pid, 30 | bucket, 31 | r, 32 | w, 33 | dw, 34 | rw}). 35 | 36 | %% ==================================================================== 37 | %% API 38 | %% ==================================================================== 39 | 40 | new(Id) -> 41 | %% Make sure the path is setup such that we can get at riak_client 42 | case code:which(riakc_pb_socket) of 43 | non_existing -> 44 | ?FAIL_MSG("~s requires riakc_pb_socket module to be available on code path.\n", 45 | [?MODULE]); 46 | _ -> 47 | ok 48 | end, 49 | 50 | Ips = basho_bench_config:get(riakc_pb_ips, [{127,0,0,1}]), 51 | Port = basho_bench_config:get(riakc_pb_port, 8087), 52 | %% riakc_pb_replies sets defaults for R, W, DW and RW. 53 | %% Each can be overridden separately 54 | Replies = basho_bench_config:get(riakc_pb_replies, 2), 55 | R = basho_bench_config:get(riakc_pb_r, Replies), 56 | W = basho_bench_config:get(riakc_pb_w, Replies), 57 | DW = basho_bench_config:get(riakc_pb_dw, Replies), 58 | RW = basho_bench_config:get(riakc_pb_rw, Replies), 59 | Bucket = basho_bench_config:get(riakc_pb_bucket, <<"test">>), 60 | 61 | %% Choose the node using our ID as a modulus 62 | TargetIp = lists:nth((Id rem length(Ips)+1), Ips), 63 | ?INFO("Using target ip ~p for worker ~p\n", [TargetIp, Id]), 64 | 65 | case riakc_pb_socket:start_link(TargetIp, Port) of 66 | {ok, Pid} -> 67 | {ok, #state { pid = Pid, 68 | bucket = Bucket, 69 | r = R, 70 | w = W, 71 | dw = DW, 72 | rw = RW 73 | }}; 74 | {error, Reason2} -> 75 | ?FAIL_MSG("Failed to connect riakc_pb_socket to ~p port ~p: ~p\n", 76 | [TargetIp, Port, Reason2]) 77 | end. 78 | 79 | run(get, KeyGen, _ValueGen, State) -> 80 | Key = KeyGen(), 81 | case riakc_pb_socket:get(State#state.pid, State#state.bucket, Key, 82 | [{r, State#state.r}]) of 83 | {ok, _} -> 84 | {ok, State}; 85 | {error, notfound} -> 86 | {ok, State}; 87 | {error, Reason} -> 88 | {error, Reason, State} 89 | end; 90 | run(put, KeyGen, ValueGen, State) -> 91 | Robj0 = riakc_obj:new(State#state.bucket, KeyGen()), 92 | Robj = riakc_obj:update_value(Robj0, ValueGen()), 93 | case riakc_pb_socket:put(State#state.pid, Robj, [{w, State#state.w}, 94 | {dw, State#state.dw}]) of 95 | ok -> 96 | {ok, State}; 97 | {error, Reason} -> 98 | {error, Reason, State} 99 | end; 100 | run(update, KeyGen, ValueGen, State) -> 101 | Key = KeyGen(), 102 | case riakc_pb_socket:get(State#state.pid, State#state.bucket, 103 | Key, [{r, State#state.r}]) of 104 | {ok, Robj} -> 105 | Robj2 = riakc_obj:update_value(Robj, ValueGen()), 106 | case riakc_pb_socket:put(State#state.pid, Robj2, [{w, State#state.w}, 107 | {dw, State#state.dw}]) of 108 | ok -> 109 | {ok, State}; 110 | {error, Reason} -> 111 | {error, Reason, State} 112 | end; 113 | {error, notfound} -> 114 | Robj0 = riakc_obj:new(State#state.bucket, KeyGen()), 115 | Robj = riakc_obj:update_value(Robj0, ValueGen()), 116 | case riakc_pb_socket:put(State#state.pid, Robj, [{w, State#state.w}, 117 | {dw, State#state.dw}]) of 118 | ok -> 119 | {ok, State}; 120 | {error, Reason} -> 121 | {error, Reason, State} 122 | end 123 | end; 124 | run(delete, KeyGen, _ValueGen, State) -> 125 | %% Pass on rw 126 | case riakc_pb_socket:delete(State#state.pid, State#state.bucket, KeyGen(), 127 | [{rw, State#state.rw}]) of 128 | ok -> 129 | {ok, State}; 130 | {error, notfound} -> 131 | {ok, State}; 132 | {error, Reason} -> 133 | {error, Reason, State} 134 | end. 135 | 136 | 137 | %% ==================================================================== 138 | %% Internal functions 139 | %% ==================================================================== 140 | 141 | -------------------------------------------------------------------------------- /src/basho_bench.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench). 23 | 24 | -export([main/1]). 25 | 26 | -include("basho_bench.hrl"). 27 | 28 | %% ==================================================================== 29 | %% API 30 | %% ==================================================================== 31 | 32 | main([]) -> 33 | io:format("Usage: basho_bench CONFIG_FILE~n"); 34 | 35 | main([Config]) -> 36 | %% Load baseline config 37 | ok = application:load(basho_bench), 38 | 39 | %% Load the config file 40 | basho_bench_config:load(Config), 41 | 42 | %% Init code path 43 | add_code_paths(basho_bench_config:get(code_paths, [])), 44 | 45 | %% If a source directory is specified, compile and load all .erl files found 46 | %% there. 47 | case basho_bench_config:get(source_dir, []) of 48 | [] -> 49 | ok; 50 | SourceDir -> 51 | load_source_files(SourceDir) 52 | end, 53 | 54 | %% Setup working directory for this test. All logs, stats, and config 55 | %% info will be placed here 56 | {ok, Cwd} = file:get_cwd(), 57 | TestId = id(), 58 | TestDir = filename:join([Cwd, basho_bench_config:get(test_dir), TestId]), 59 | ok = filelib:ensure_dir(filename:join(TestDir, "foobar")), 60 | basho_bench_config:set(test_id, TestId), 61 | 62 | %% Create a link to the test dir for convenience 63 | TestLink = filename:join([Cwd, basho_bench_config:get(test_dir), "current"]), 64 | [] = os:cmd(?FMT("rm -f ~s; ln -sf ~s ~s", [TestLink, TestDir, TestLink])), 65 | 66 | %% Copy the config into the test dir for posterity 67 | {ok, _} = file:copy(Config, filename:join(TestDir, filename:basename(Config))), 68 | 69 | %% Set our CWD to the test dir 70 | ok = file:set_cwd(TestDir), 71 | 72 | log_dimensions(), 73 | 74 | %% Spin up the application 75 | ok = basho_bench_app:start(), 76 | 77 | %% Pull the runtime duration from the config and sleep until that's passed OR 78 | %% the supervisor process exits 79 | Mref = erlang:monitor(process, whereis(basho_bench_sup)), 80 | DurationMins = basho_bench_config:get(duration), 81 | wait_for_stop(Mref, DurationMins). 82 | 83 | 84 | 85 | 86 | 87 | %% ==================================================================== 88 | %% Internal functions 89 | %% ==================================================================== 90 | 91 | wait_for_stop(Mref, infinity) -> 92 | receive 93 | {'DOWN', Mref, _, _, Info} -> 94 | ?CONSOLE("Test stopped: ~p\n", [Info]) 95 | end; 96 | wait_for_stop(Mref, DurationMins) -> 97 | Duration = timer:minutes(DurationMins) + timer:seconds(1), 98 | receive 99 | {'DOWN', Mref, _, _, Info} -> 100 | ?CONSOLE("Test stopped: ~p\n", [Info]) 101 | 102 | after Duration -> 103 | basho_bench_app:stop(), 104 | ?CONSOLE("Test completed after ~p mins.\n", [DurationMins]) 105 | end. 106 | 107 | 108 | 109 | %% 110 | %% Construct a string suitable for use as a unique ID for this test run 111 | %% 112 | id() -> 113 | {{Y, M, D}, {H, Min, S}} = calendar:local_time(), 114 | ?FMT("~w~2..0w~2..0w_~2..0w~2..0w~2..0w", [Y, M, D, H, Min, S]). 115 | 116 | 117 | add_code_paths([]) -> 118 | ok; 119 | add_code_paths([Path | Rest]) -> 120 | Absname = filename:absname(Path), 121 | case filename:basename(Absname) of 122 | "ebin" -> 123 | true = code:add_path(Absname); 124 | _ -> 125 | true = code:add_path(filename:join(Absname, "ebin")) 126 | end, 127 | add_code_paths(Rest). 128 | 129 | 130 | %% 131 | %% Convert a number of bytes into a more user-friendly representation 132 | %% 133 | user_friendly_bytes(Size) -> 134 | lists:foldl(fun(Desc, {Sz, SzDesc}) -> 135 | case Sz > 1000 of 136 | true -> 137 | {Sz / 1024, Desc}; 138 | false -> 139 | {Sz, SzDesc} 140 | end 141 | end, 142 | {Size, bytes}, ['KB', 'MB', 'GB']). 143 | 144 | log_dimensions() -> 145 | case basho_bench_keygen:dimension(basho_bench_config:get(key_generator)) of 146 | undefined -> 147 | ok; 148 | Keyspace -> 149 | Valspace = basho_bench_valgen:dimension(basho_bench_config:get(value_generator), Keyspace), 150 | {Size, Desc} = user_friendly_bytes(Valspace), 151 | ?INFO("Est. data size: ~.2f ~s\n", [Size, Desc]) 152 | end. 153 | 154 | 155 | load_source_files(Dir) -> 156 | CompileFn = fun(F, _Acc) -> 157 | case compile:file(F, [report, binary]) of 158 | {ok, Mod, Bin} -> 159 | {module, Mod} = code:load_binary(Mod, F, Bin), 160 | ?INFO("Loaded ~p (~s)\n", [Mod, F]), 161 | ok; 162 | Error -> 163 | io:format("Failed to compile ~s: ~p\n", [F, Error]) 164 | end 165 | end, 166 | filelib:fold_files(Dir, ".*.erl", false, CompileFn, ok). 167 | -------------------------------------------------------------------------------- /src/basho_bench_stats.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_stats). 23 | 24 | -behaviour(gen_server). 25 | 26 | %% API 27 | -export([start_link/0, 28 | run/0, 29 | op_complete/3]). 30 | 31 | %% gen_server callbacks 32 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 33 | terminate/2, code_change/3]). 34 | 35 | -include("basho_bench.hrl"). 36 | 37 | -record(state, { ops, 38 | start_time, 39 | last_write_time, 40 | report_interval, 41 | errors_since_last_report = false, 42 | summary_file}). 43 | 44 | %% Tracks latencies up to 5 secs w/ 250 us resolution 45 | -define(NEW_HIST, stats_histogram:new(0, 5000000, 20000)). 46 | 47 | %% ==================================================================== 48 | %% API 49 | %% ==================================================================== 50 | 51 | start_link() -> 52 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 53 | 54 | run() -> 55 | gen_server:call(?MODULE, run). 56 | 57 | op_complete(Op, Result, ElapsedUs) -> 58 | gen_server:call(?MODULE, {op, Op, Result, ElapsedUs}). 59 | 60 | %% ==================================================================== 61 | %% gen_server callbacks 62 | %% ==================================================================== 63 | 64 | init([]) -> 65 | %% Trap exits so we have a chance to flush data 66 | process_flag(trap_exit, true), 67 | 68 | %% Initialize an ETS table to track error and crash counters 69 | ets:new(basho_bench_errors, [protected, named_table]), 70 | 71 | %% Get the list of operations we'll be using for this test 72 | Ops = [Op || {Op, _} <- basho_bench_config:get(operations)], 73 | 74 | %% Setup stats instance for each operation -- we only track latencies on 75 | %% successful operations 76 | %% 77 | %% NOTE: Store the histograms in the process dictionary to avoid painful 78 | %% copying on state updates. 79 | [erlang:put({latencies, Op}, ?NEW_HIST) || Op <- Ops], 80 | 81 | %% Setup output file handles for dumping periodic CSV of histogram results. 82 | [erlang:put({csv_file, Op}, op_csv_file(Op)) || Op <- Ops], 83 | 84 | %% Setup output file w/ counters for total requests, errors, etc. 85 | {ok, SummaryFile} = file:open("summary.csv", [raw, binary, write]), 86 | file:write(SummaryFile, <<"elapsed, window, total, successful, failed\n">>), 87 | 88 | %% Schedule next write/reset of data 89 | ReportInterval = timer:seconds(basho_bench_config:get(report_interval)), 90 | 91 | {ok, #state{ ops = Ops, 92 | report_interval = ReportInterval, 93 | summary_file = SummaryFile }}. 94 | 95 | handle_call(run, _From, State) -> 96 | %% Schedule next report 97 | Now = now(), 98 | erlang:send_after(State#state.report_interval, self(), report), 99 | {reply, ok, State#state { start_time = Now, last_write_time = Now}}; 100 | 101 | handle_call({op, Op, ok, ElapsedUs}, _From, State) -> 102 | %% Update the histogram for the op in question 103 | Hist = stats_histogram:update(ElapsedUs, erlang:get({latencies, Op})), 104 | erlang:put({latencies, Op}, Hist), 105 | {reply, ok, State}; 106 | handle_call({op, Op, {error, Reason}, _ElapsedUs}, _From, State) -> 107 | increment_error_counter(Op), 108 | increment_error_counter({Op, Reason}), 109 | {reply, ok, State#state { errors_since_last_report = true }}. 110 | 111 | handle_cast(_, State) -> 112 | {noreply, State}. 113 | 114 | handle_info(report, State) -> 115 | %% Determine how much time has elapsed (seconds) since our last report 116 | Now = now(), 117 | Elapsed = trunc(timer:now_diff(Now, State#state.start_time) / 1000000), 118 | Window = trunc(timer:now_diff(Now, State#state.last_write_time) / 1000000), 119 | 120 | %% Time to report latency data to our CSV files 121 | {Oks, Errors} = lists:foldl(fun(Op, {TotalOks, TotalErrors}) -> 122 | {Oks, Errors} = report_latency(Elapsed, Window, Op), 123 | {TotalOks + Oks, TotalErrors + Errors} 124 | end, {0,0}, State#state.ops), 125 | 126 | %% Reset latency histograms 127 | [erlang:put({latencies, Op}, ?NEW_HIST) || Op <- State#state.ops], 128 | 129 | %% Write summary 130 | file:write(State#state.summary_file, 131 | io_lib:format("~w, ~w, ~w, ~w, ~w\n", 132 | [Elapsed, 133 | Window, 134 | Oks + Errors, 135 | Oks, 136 | Errors])), 137 | 138 | %% Dump current error counts to console 139 | case (State#state.errors_since_last_report) of 140 | true -> 141 | ?INFO("Errors:~p\n", [ets:tab2list(basho_bench_errors)]); 142 | false -> 143 | ok 144 | end, 145 | 146 | %% Schedule next report 147 | erlang:send_after(State#state.report_interval, self(), report), 148 | {noreply, State#state { last_write_time = Now, errors_since_last_report = false }}. 149 | 150 | terminate(_Reason, State) -> 151 | [ok = file:close(F) || {{csv_file, _}, F} <- erlang:get()], 152 | ok = file:close(State#state.summary_file), 153 | 154 | ?CONSOLE("~p\n", [ets:tab2list(basho_bench_errors)]), 155 | ok. 156 | 157 | code_change(_OldVsn, State, _Extra) -> 158 | {ok, State}. 159 | 160 | 161 | 162 | %% ==================================================================== 163 | %% Internal functions 164 | %% ==================================================================== 165 | 166 | op_csv_file(Op) -> 167 | Fname = lists:concat([Op, "_latencies.csv"]), 168 | {ok, F} = file:open(Fname, [raw, binary, write]), 169 | ok = file:write(F, <<"elapsed, window, n, min, mean, median, 95th, 99th, 99_9th, max, errors\n">>), 170 | F. 171 | 172 | increment_error_counter(Key) -> 173 | %% Increment the counter for this specific key. We have to deal with 174 | %% missing keys, so catch the update if it fails and init as necessary 175 | case catch(ets:update_counter(basho_bench_errors, Key, 1)) of 176 | Value when is_integer(Value) -> 177 | ok; 178 | {'EXIT', _} -> 179 | true = ets:insert_new(basho_bench_errors, {Key, 1}), 180 | ok 181 | end. 182 | 183 | error_counter(Key) -> 184 | case catch(ets:lookup_element(basho_bench_errors, Key, 2)) of 185 | {'EXIT', _} -> 186 | 0; 187 | Value -> 188 | Value 189 | end. 190 | 191 | %% 192 | %% Write latency info for a given op to the appropriate CSV. Returns the 193 | %% number of successful and failed ops in this window of time. 194 | %% 195 | report_latency(Elapsed, Window, Op) -> 196 | Hist = erlang:get({latencies, Op}), 197 | Errors = error_counter(Op), 198 | case stats_histogram:observations(Hist) > 0 of 199 | true -> 200 | {Min, Mean, Max, _, _} = stats_histogram:summary_stats(Hist), 201 | Line = io_lib:format("~w, ~w, ~w, ~w, ~.1f, ~.1f, ~.1f, ~.1f, ~.1f, ~w, ~w\n", 202 | [Elapsed, 203 | Window, 204 | stats_histogram:observations(Hist), 205 | Min, 206 | Mean, 207 | stats_histogram:quantile(0.500, Hist), 208 | stats_histogram:quantile(0.950, Hist), 209 | stats_histogram:quantile(0.990, Hist), 210 | stats_histogram:quantile(0.999, Hist), 211 | Max, 212 | Errors]); 213 | false -> 214 | ?WARN("No data for op: ~p\n", [Op]), 215 | Line = io_lib:format("~w, ~w, 0, 0, 0, 0, 0, 0, 0, 0, ~w\n", 216 | [Elapsed, 217 | Window, 218 | Errors]) 219 | end, 220 | ok = file:write(erlang:get({csv_file, Op}), Line), 221 | {stats_histogram:observations(Hist), Errors}. 222 | 223 | -------------------------------------------------------------------------------- /src/basho_bench_driver_http_raw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_driver_http_raw). 23 | 24 | -export([new/1, 25 | run/4]). 26 | 27 | -include("basho_bench.hrl"). 28 | 29 | -record(url, {abspath, host, port, username, password, path, protocol}). 30 | 31 | -record(state, { client_id, % Tuple client ID for HTTP requests 32 | base_urls, % Tuple of #url -- one for each IP 33 | base_urls_index, % #url to use for next request 34 | path_params }). % Params to append on the path 35 | 36 | %% ==================================================================== 37 | %% API 38 | %% ==================================================================== 39 | 40 | new(Id) -> 41 | %% Make sure ibrowse is available 42 | case code:which(ibrowse) of 43 | non_existing -> 44 | ?FAIL_MSG("~s requires ibrowse to be installed.\n", [?MODULE]); 45 | _ -> 46 | ok 47 | end, 48 | 49 | %% Setup client ID by base-64 encoding the ID 50 | ClientId = {'X-Riak-ClientId', base64:encode(<>)}, 51 | ?DEBUG("Client ID: ~p\n", [ClientId]), 52 | 53 | application:start(ibrowse), 54 | 55 | %% The IPs, port and path we'll be testing 56 | Ips = basho_bench_config:get(http_raw_ips, ["127.0.0.1"]), 57 | Port = basho_bench_config:get(http_raw_port, 8098), 58 | Path = basho_bench_config:get(http_raw_path, "/riak/test"), 59 | Params = basho_bench_config:get(http_raw_params, ""), 60 | 61 | %% If there are multiple URLs, convert the list to a tuple so we can efficiently 62 | %% round-robin through them. 63 | case length(Ips) of 64 | 1 -> 65 | [Ip] = Ips, 66 | BaseUrls = #url { host = Ip, port = Port, path = Path }, 67 | BaseUrlsIndex = 1; 68 | _ -> 69 | BaseUrls = list_to_tuple([ #url { host = Ip, port = Port, path = Path } 70 | || Ip <- Ips]), 71 | BaseUrlsIndex = random:uniform(tuple_size(BaseUrls)) 72 | end, 73 | 74 | {ok, #state { client_id = ClientId, 75 | base_urls = BaseUrls, 76 | base_urls_index = BaseUrlsIndex, 77 | path_params = Params }}. 78 | 79 | 80 | run(get, KeyGen, _ValueGen, State) -> 81 | {NextUrl, S2} = next_url(State), 82 | case do_get(url(NextUrl, KeyGen, State#state.path_params)) of 83 | {not_found, _Url} -> 84 | {ok, S2}; 85 | {ok, _Url, _Headers} -> 86 | {ok, S2}; 87 | {error, Reason} -> 88 | {error, Reason, S2} 89 | end; 90 | run(update, KeyGen, ValueGen, State) -> 91 | {NextUrl, S2} = next_url(State), 92 | case do_get(url(NextUrl, KeyGen, State#state.path_params)) of 93 | {error, Reason} -> 94 | {error, Reason, S2}; 95 | 96 | {not_found, Url} -> 97 | case do_put(Url, [], ValueGen) of 98 | ok -> 99 | {ok, S2}; 100 | {error, Reason} -> 101 | {error, Reason, S2} 102 | end; 103 | 104 | {ok, Url, Headers} -> 105 | Vclock = lists:keyfind("X-Riak-Vclock", 1, Headers), 106 | case do_put(Url, [State#state.client_id, Vclock], ValueGen) of 107 | ok -> 108 | {ok, S2}; 109 | {error, Reason} -> 110 | {error, Reason, S2} 111 | end 112 | end; 113 | run(insert, KeyGen, ValueGen, State) -> 114 | %% Go ahead and evaluate the keygen so that we can use the 115 | %% sequential_int_gen to do a controlled # of inserts (if we desire). Note 116 | %% that the actual insert randomly generates a key (server-side), so the 117 | %% output of the keygen is ignored. 118 | KeyGen(), 119 | {NextUrl, S2} = next_url(State), 120 | case do_post(url(NextUrl, State#state.path_params), [], ValueGen) of 121 | ok -> 122 | {ok, S2}; 123 | {error, Reason} -> 124 | {error, Reason, S2} 125 | end. 126 | 127 | 128 | 129 | %% ==================================================================== 130 | %% Internal functions 131 | %% ==================================================================== 132 | 133 | next_url(State) when is_record(State#state.base_urls, url) -> 134 | {State#state.base_urls, State}; 135 | next_url(State) when State#state.base_urls_index > tuple_size(State#state.base_urls) -> 136 | { element(1, State#state.base_urls), 137 | State#state { base_urls_index = 1 } }; 138 | next_url(State) -> 139 | { element(State#state.base_urls_index, State#state.base_urls), 140 | State#state { base_urls_index = State#state.base_urls_index + 1 }}. 141 | 142 | url(BaseUrl, Params) -> 143 | BaseUrl#url { path = lists:concat([BaseUrl#url.path, Params]) }. 144 | url(BaseUrl, KeyGen, Params) -> 145 | BaseUrl#url { path = lists:concat([BaseUrl#url.path, '/', KeyGen(), Params]) }. 146 | 147 | 148 | do_get(Url) -> 149 | case send_request(Url, [], get, [], [{response_format, binary}]) of 150 | {ok, "404", _Headers, _Body} -> 151 | {not_found, Url}; 152 | {ok, "300", Headers, _Body} -> 153 | {ok, Url, Headers}; 154 | {ok, "200", Headers, _Body} -> 155 | {ok, Url, Headers}; 156 | {ok, Code, _Headers, _Body} -> 157 | {error, {http_error, Code}}; 158 | {error, Reason} -> 159 | {error, Reason} 160 | end. 161 | 162 | do_put(Url, Headers, ValueGen) -> 163 | case send_request(Url, Headers ++ [{'Content-Type', 'application/octet-stream'}], 164 | put, ValueGen(), [{response_format, binary}]) of 165 | {ok, "204", _Header, _Body} -> 166 | ok; 167 | {ok, Code, _Header, _Body} -> 168 | {error, {http_error, Code}}; 169 | {error, Reason} -> 170 | {error, Reason} 171 | end. 172 | 173 | do_post(Url, Headers, ValueGen) -> 174 | case send_request(Url, Headers ++ [{'Content-Type', 'application/octet-stream'}], 175 | post, ValueGen(), [{response_format, binary}]) of 176 | {ok, "201", _Header, _Body} -> 177 | ok; 178 | {ok, "204", _Header, _Body} -> 179 | ok; 180 | {ok, Code, _Header, _Body} -> 181 | {error, {http_error, Code}}; 182 | {error, Reason} -> 183 | {error, Reason} 184 | end. 185 | 186 | 187 | connect(Url) -> 188 | case erlang:get({ibrowse_pid, Url#url.host}) of 189 | undefined -> 190 | {ok, Pid} = ibrowse_http_client:start({Url#url.host, Url#url.port}), 191 | erlang:put({ibrowse_pid, Url#url.host}, Pid), 192 | Pid; 193 | Pid -> 194 | case is_process_alive(Pid) of 195 | true -> 196 | Pid; 197 | false -> 198 | erlang:erase({ibrowse_pid, Url#url.host}), 199 | connect(Url) 200 | end 201 | end. 202 | 203 | 204 | disconnect(Url) -> 205 | case erlang:get({ibrowse_pid, Url#url.host}) of 206 | undefined -> 207 | ok; 208 | OldPid -> 209 | catch(ibrowse_http_client:stop(OldPid)) 210 | end, 211 | erlang:erase({ibrowse_pid, Url#url.host}), 212 | ok. 213 | 214 | 215 | send_request(Url, Headers, Method, Body, Options) -> 216 | send_request(Url, Headers, Method, Body, Options, 3). 217 | 218 | send_request(_Url, _Headers, _Method, _Body, _Options, 0) -> 219 | {error, max_retries}; 220 | send_request(Url, Headers, Method, Body, Options, Count) -> 221 | Pid = connect(Url), 222 | case catch(ibrowse_http_client:send_req(Pid, Url, Headers, Method, Body, Options, 5000)) of 223 | {ok, Status, RespHeaders, RespBody} -> 224 | {ok, Status, RespHeaders, RespBody}; 225 | 226 | Error -> 227 | disconnect(Url), 228 | case should_retry(Error) of 229 | true -> 230 | send_request(Url, Headers, Method, Body, Options, Count-1); 231 | 232 | false -> 233 | normalize_error(Method, Error) 234 | end 235 | end. 236 | 237 | 238 | should_retry({error, send_failed}) -> true; 239 | should_retry({error, connection_closed}) -> true; 240 | should_retry({'EXIT', {normal, _}}) -> true; 241 | should_retry({'EXIT', {noproc, _}}) -> true; 242 | should_retry(_) -> false. 243 | 244 | normalize_error(Method, {'EXIT', {timeout, _}}) -> {error, {Method, timeout}}; 245 | normalize_error(Method, {'EXIT', Reason}) -> {error, {Method, 'EXIT', Reason}}; 246 | normalize_error(Method, {error, Reason}) -> {error, {Method, Reason}}. 247 | -------------------------------------------------------------------------------- /src/basho_bench_worker.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% basho_bench: Benchmarking Suite 4 | %% 5 | %% Copyright (c) 2009-2010 Basho Techonologies 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(basho_bench_worker). 23 | 24 | -behaviour(gen_server). 25 | 26 | %% API 27 | -export([start_link/2, 28 | run/1, 29 | stop/1]). 30 | 31 | %% gen_server callbacks 32 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 33 | terminate/2, code_change/3]). 34 | 35 | -record(state, { id, 36 | keygen, 37 | valgen, 38 | driver, 39 | driver_state, 40 | ops, 41 | ops_len, 42 | rng_seed, 43 | parent_pid, 44 | worker_pid, 45 | sup_id}). 46 | 47 | -include("basho_bench.hrl"). 48 | 49 | %% ==================================================================== 50 | %% API 51 | %% ==================================================================== 52 | 53 | start_link(SupChild, Id) -> 54 | gen_server:start_link(?MODULE, [SupChild, Id], []). 55 | 56 | run(Pids) -> 57 | [ok = gen_server:call(Pid, run) || Pid <- Pids], 58 | ok. 59 | 60 | stop(Pids) -> 61 | [ok = gen_server:call(Pid, stop) || Pid <- Pids], 62 | ok. 63 | 64 | %% ==================================================================== 65 | %% gen_server callbacks 66 | %% ==================================================================== 67 | 68 | init([SupChild, Id]) -> 69 | %% Setup RNG seed for worker sub-process to use; incorporate the ID of 70 | %% the worker to ensure consistency in load-gen 71 | %% 72 | %% NOTE: If the worker process dies, this obviously introduces some entroy 73 | %% into the equation since you'd be restarting the RNG all over. 74 | process_flag(trap_exit, true), 75 | {A1, A2, A3} = basho_bench_config:get(rng_seed), 76 | RngSeed = {A1+Id, A2+Id, A3+Id}, 77 | 78 | %% Pull all config settings from environment 79 | Driver = basho_bench_config:get(driver), 80 | Ops = ops_tuple(), 81 | 82 | %% Finally, initialize key and value generation. We pass in our ID to the 83 | %% initialization to enable (optional) key/value space partitioning 84 | KeyGen = basho_bench_keygen:new(basho_bench_config:get(key_generator), Id), 85 | ValGen = basho_bench_valgen:new(basho_bench_config:get(value_generator), Id), 86 | 87 | State = #state { id = Id, keygen = KeyGen, valgen = ValGen, 88 | driver = Driver, 89 | ops = Ops, ops_len = size(Ops), 90 | rng_seed = RngSeed, 91 | parent_pid = self(), 92 | sup_id = SupChild}, 93 | 94 | %% Use a dedicated sub-process to do the actual work. The work loop may need 95 | %% to sleep or otherwise delay in a way that would be inappropriate and/or 96 | %% inefficient for a gen_server. Furthermore, we want the loop to be as 97 | %% tight as possible for peak load generation and avoid unnecessary polling 98 | %% of the message queue. 99 | %% 100 | %% Link the worker and the sub-process to ensure that if either exits, the 101 | %% other goes with it. 102 | WorkerPid = spawn_link(fun() -> worker_init(State) end), 103 | WorkerPid ! {init_driver, self()}, 104 | receive 105 | driver_ready -> 106 | ok 107 | end, 108 | 109 | %% If the system is marked as running this is a restart; queue up the run 110 | %% message for this worker 111 | case basho_bench_app:is_running() of 112 | true -> 113 | ?WARN("Restarting crashed worker.\n", []), 114 | gen_server:cast(self(), run); 115 | false -> 116 | ok 117 | end, 118 | 119 | {ok, State#state { worker_pid = WorkerPid }}. 120 | 121 | handle_call(run, _From, State) -> 122 | State#state.worker_pid ! run, 123 | {reply, ok, State}. 124 | 125 | handle_cast(run, State) -> 126 | State#state.worker_pid ! run, 127 | {noreply, State}. 128 | 129 | handle_info({'EXIT', _Pid, Reason}, State) -> 130 | case Reason of 131 | normal -> 132 | %% Clean shutdown of the worker; spawn a process to terminate this 133 | %% process via the supervisor API and make sure it doesn't restart. 134 | spawn(fun() -> stop_worker(State#state.sup_id) end), 135 | {noreply, State}; 136 | 137 | _ -> 138 | %% Worker process exited for some other reason; stop this process 139 | %% as well so that everything gets restarted by the sup 140 | {stop, normal, State} 141 | end. 142 | 143 | terminate(_Reason, _State) -> 144 | ok. 145 | 146 | code_change(_OldVsn, State, _Extra) -> 147 | {ok, State}. 148 | 149 | 150 | 151 | %% ==================================================================== 152 | %% Internal functions 153 | %% ==================================================================== 154 | 155 | %% 156 | %% Stop a worker process via the supervisor and terminate the app 157 | %% if there are no workers remaining 158 | %% 159 | %% WARNING: Must run from a process other than the worker! 160 | %% 161 | stop_worker(SupChild) -> 162 | ok = basho_bench_sup:stop_child(SupChild), 163 | case basho_bench_sup:workers() of 164 | [] -> 165 | %% No more workers -- stop the system 166 | basho_bench_app:stop(); 167 | _ -> 168 | ok 169 | end. 170 | 171 | %% 172 | %% Expand operations list into tuple suitable for weighted, random draw 173 | %% 174 | ops_tuple() -> 175 | Ops = [lists:duplicate(Count, Op) || {Op, Count} <- basho_bench_config:get(operations)], 176 | list_to_tuple(lists:flatten(Ops)). 177 | 178 | 179 | worker_init(State) -> 180 | random:seed(State#state.rng_seed), 181 | worker_idle_loop(State). 182 | 183 | worker_idle_loop(State) -> 184 | Driver = State#state.driver, 185 | receive 186 | {init_driver, Caller} -> 187 | %% Spin up the driver implementation 188 | case catch(Driver:new(State#state.id)) of 189 | {ok, DriverState} -> 190 | Caller ! driver_ready, 191 | ok; 192 | Error -> 193 | DriverState = undefined, % Make erlc happy 194 | ?FAIL_MSG("Failed to initialize driver ~p: ~p\n", [Driver, Error]) 195 | end, 196 | worker_idle_loop(State#state { driver_state = DriverState }); 197 | run -> 198 | case basho_bench_config:get(mode) of 199 | max -> 200 | ?INFO("Starting max worker: ~p\n", [self()]), 201 | max_worker_run_loop(State); 202 | {rate, Rate} -> 203 | %% Calculate mean interarrival time in in milliseconds. A 204 | %% fixed rate worker can generate (at max) only 1k req/sec. 205 | MeanArrival = 1000 / Rate, 206 | ?INFO("Starting ~w ms/req fixed rate worker: ~p\n", [MeanArrival, self()]), 207 | rate_worker_run_loop(State, 1 / MeanArrival) 208 | end 209 | end. 210 | 211 | worker_next_op(State) -> 212 | Next = element(random:uniform(State#state.ops_len), State#state.ops), 213 | Start = now(), 214 | Result = (catch (State#state.driver):run(Next, State#state.keygen, State#state.valgen, 215 | State#state.driver_state)), 216 | ElapsedUs = timer:now_diff(now(), Start), 217 | case Result of 218 | {ok, DriverState} -> 219 | %% Success 220 | basho_bench_stats:op_complete(Next, ok, ElapsedUs), 221 | {ok, State#state { driver_state = DriverState}}; 222 | 223 | {error, Reason, DriverState} -> 224 | %% Driver encountered a recoverable error 225 | basho_bench_stats:op_complete(Next, {error, Reason}, ElapsedUs), 226 | {ok, State#state { driver_state = DriverState}}; 227 | 228 | {'EXIT', Reason} -> 229 | %% Driver crashed, generate a crash error and terminate. This will take down 230 | %% the corresponding worker which will get restarted by the appropriate supervisor. 231 | basho_bench_stats:op_complete(Next, {error, crash}, ElapsedUs), 232 | ?DEBUG("Driver ~p crashed: ~p\n", [State#state.driver, Reason]), 233 | crash; 234 | 235 | {stop, Reason} -> 236 | %% Driver (or something within it) has requested that this worker 237 | %% terminate cleanly. 238 | ?INFO("Driver ~p (~p) has requested stop: ~p\n", [State#state.driver, self(), Reason]), 239 | normal 240 | end. 241 | 242 | max_worker_run_loop(State) -> 243 | case worker_next_op(State) of 244 | {ok, State2} -> 245 | max_worker_run_loop(State2); 246 | ExitReason -> 247 | exit(ExitReason) 248 | end. 249 | 250 | rate_worker_run_loop(State, Lambda) -> 251 | %% Delay between runs using exponentially distributed delays to mimic 252 | %% queue. 253 | timer:sleep(trunc(stats_rv:exponential(Lambda))), 254 | case worker_next_op(State) of 255 | {ok, State2} -> 256 | rate_worker_run_loop(State2, Lambda); 257 | ExitReason -> 258 | exit(ExitReason) 259 | end. 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /docs/Documentation.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: html-style.org 2 | #+SETUPFILE: pdf-style.org 3 | #+TITLE: Basho Bench 0.1 Documentation 4 | #+AUTHOR: Dave Smith (code) / Rusty Klophaus (docs) 5 | #+EMAIL: dizzyd@basho.com, rusty@basho.com 6 | 7 | * Overview 8 | ** Purpose 9 | 10 | Basho Bench is a benchmarking tool created to conduct accurate and 11 | repeatable performance tests and stress tests, and produce performance graphs. 12 | 13 | Originally developed by Dave Smith (Dizzy) to benchmark Riak, Basho's 14 | key/value datastore, it exposes a pluggable driver interface and has 15 | been extended to serve as a benchmarking tool against a variety of 16 | projects. New drivers can be written in Erlang and are generally 17 | less than 200 lines of code. 18 | 19 | ** How does it work? 20 | 21 | When Basho Bench starts (basho\_bench.erl), it reads the configuration 22 | (basho\_bench\_config.erl), creates a new results directory, then sets up the 23 | test. (basho\_bench\_app.erl/basho\_bench\_sup.erl) 24 | 25 | During test setup, Basho Bench creates: 26 | 27 | + One *log process* (basho\_bench\_log.erl). During startup, this 28 | creates a new /log.txt/ file in the current results directory, to 29 | which output is logged at the specified logging level. 30 | 31 | + One *stats process* (basho\_bench\_stats.erl). This receives notifications 32 | when an operation completes, plus the elapsed time of the operation, and 33 | stores it in a histogram. At regular intervals, the histograms are dumped 34 | to /summary.csv/ as well as operation-specific latency CSVs 35 | (e.g. /put_latencies.csv/ for the 'put' operation). 36 | 37 | + N *workers*, where N is specified by the /concurrent/ configuration 38 | setting. (basho\_bench\_worker.erl). The worker process wraps a driver 39 | module, specified by the /driver/ configuration setting. The driver is 40 | randomly invoked using the distribution of operations as specified by the 41 | /operation/ configuration setting. The rate at which the driver invokes 42 | operations is governed by the /mode/ setting. 43 | 44 | Once these processes have been created and initialized, Basho Bench sends a 45 | run command to all worker processes, causing them to begin the test. Each worker 46 | is initialized with a common seed value for random number generation to ensure 47 | that the generated workload is reproducible at a later date. 48 | 49 | During the test, the workers repeatedly call /driver/:run/4, passing in the 50 | next operation to run, a keygen function, a valuegen function, and the last 51 | state of the driver. The worker process times the operation, and reports this 52 | to the stats process when the operation has completed. 53 | 54 | Finally, once the test has been run for the duration specified in the config 55 | file, all workers and stats processes are terminated and the benchmark 56 | ends. The measured latency and throughput of the test can be found in 57 | /./tests/current/. Previous results are in timestamped directories of the 58 | form /./tests/YYYYMMDD-HHMMSS/. 59 | 60 | * Installation 61 | 62 | ** Prerequisites 63 | 64 | + Erlang R13B03 - http://erlang.org/download.html 65 | + R - http://www.r-project.org/ (for graphing) 66 | 67 | ** Building from Source 68 | 69 | Basho Bench is currently available as source code only. To get the 70 | latest code, clone the basho\_bench repository: 71 | 72 | : hg clone ssh://hg@bitbucket.org/basho/basho_bench 73 | : cd basho_bench 74 | : make 75 | 76 | * Usage 77 | 78 | Run basho\_bench: 79 | 80 | : ./basho_bench myconfig.config 81 | 82 | This will generate results in /tests/current/. You will need to 83 | create a configuration file. The recommended approach is to start 84 | from a file in the /examples/ directory and modify settings using the 85 | /Configuration/ section below for reference. 86 | 87 | Note that currently you must run the basho\_bench script from the directory 88 | where it was built to ensure that the necessary dependencies are available. 89 | 90 | * Generating Benchmark Graphs 91 | 92 | The output of basho\_bench can be used to create graphs showing: 93 | 94 | + Throughput - Operations per second over the duration of the test. 95 | 96 | + Latency at 99th percentile, 99.9th percentile and max latency for 97 | the selected operations. 98 | 99 | + Median latency, mean latency, and 95th percentile latency for the 100 | selected operations. 101 | 102 | ** Prerequisites 103 | 104 | The R statistics language is needed to generate graphs. 105 | 106 | + More information: http://www.r-project.org/. 107 | 108 | + Download R: http://cran.r-project.org/mirrors.html 109 | 110 | Follow the instructions for your platform to install R. 111 | 112 | ** Generating a Graphs 113 | 114 | To generate a benchmark graph against the current results, run: 115 | 116 | : make results 117 | 118 | This will create a results file in /tests/current/summary.png/. 119 | 120 | You can also run this manually: 121 | 122 | : priv/summary.r -i tests/current 123 | 124 | * Configuration 125 | 126 | Basho Bench ships with a number of sample configuration files, 127 | available in the /examples/ directory. 128 | 129 | ** Global Config Settings 130 | *** mode 131 | 132 | The *mode* setting controls the rate at which workers invoke the /driver/:run/4 133 | function with a new operation. There are two possible values: 134 | 135 | - max :: generate as many ops per second as possible 136 | 137 | - {rate, N} :: generate N ops per second, with exponentially distributed 138 | interarrival times. 139 | 140 | Note that this setting is applied to each driver independently. For example, if 141 | /{rate, 5}/ is used with 3 concurrent workers, basho\_bench will be generating 142 | 15 (i.e. 5 * 3) operations per second. 143 | 144 | : % Run at max, ie: as quickly as possible. 145 | : {mode, max} 146 | 147 | : % Run 15 operations per second. 148 | : {mode, {rate, 15}} 149 | 150 | *** concurrent 151 | 152 | The number of concurrent worker processes. The default is 3 worker processes. 153 | 154 | : % Run 10 concurrent processes. 155 | : {concurrent, 10} 156 | 157 | *** duration 158 | 159 | The duration of the test, in minutes. The default is 5 minutes. 160 | 161 | : % Run the test for one hour. 162 | : {duration, 60} 163 | 164 | *** operations 165 | 166 | The possible operations that the driver will run, plus their 167 | "weight" or likelihood of being run. Default is =[{get,4}, 168 | {put,4}, {delete, 1}]= which means that out of every 9 operations, 169 | 'get' will be called four times, 'put' will called four times, and 170 | 'delete' will be called once, on average. 171 | 172 | : % Run 80% gets, 20% puts. 173 | : {operations, [{get, 4}, {put, 1}]}. 174 | 175 | Operations are defined on a *per-driver* basis. Not all drivers will 176 | implement the "get"/"put" operations discussed above. Consult the driver 177 | source to determine the valid operations. 178 | 179 | If a driver does not support a specified operation ("askdfput" in this 180 | example) you may see errors like: 181 | 182 | : DEBUG:Driver basho_bench_driver_null crashed: {function_clause, 183 | : [{basho_bench_driver_null,run, 184 | : [asdfput, 185 | : #Fun, 186 | : #Fun, 187 | : undefined]}, 188 | : {basho_bench_worker, 189 | : worker_next_op,1}, 190 | : {basho_bench_worker, 191 | : max_worker_run_loop,1}]} 192 | 193 | 194 | *** driver 195 | 196 | The module name of the driver that basho\_bench will use to generate load. A 197 | driver may simply invoke code in-process (such as when measuring the 198 | performance of innostore or DETS) or may open network connections and 199 | generate load on a remote system (such as when testing a Riak 200 | server/cluster). 201 | 202 | Available drivers include: 203 | 204 | + basho\_bench\_driver\_http\_raw :: Uses Riak's HTTP interface to 205 | get/put/delete data on a Riak server 206 | + basho\_bench\_driver\_riakc\_pb :: Uses Riak's Protocol Buffers interface 207 | to get/put/delete data on a Riak server 208 | + basho\_bench\_driver\_riakclient :: Uses Riak's Dist. Erlang interface to 209 | get/put/delete data on a Riak server 210 | + basho\_bench\_driver\_bitcask :: Directly invokes the Bitcask API 211 | + basho\_bench\_driver\_dets :: Directly invokes the DETS API 212 | + basho\_bench\_driver\_innostore :: Directly invokes the Innostore API 213 | 214 | On invocation of the /driver/:run/4 method, the driver may return one of the 215 | following results: 216 | 217 | + ={ok, NewState}= :: operation completed successfully 218 | + ={error, Reason, NewState}= :: operation failed but the driver can continue 219 | processing (i.e. recoverable error) 220 | + ={stop, Reason}= :: operation failed; driver can't/won't continue 221 | processing 222 | + ={'EXIT', Reason}= :: operation failed; driver crashed 223 | 224 | 225 | *** code\_paths 226 | 227 | Some drivers need additional Erlang code in order to run. Specify 228 | the paths to this code using the *code\_paths* configuration 229 | setting. 230 | 231 | As noted previously, basho\_bench /must/ be run in the directory it was 232 | built, for dependency reasons. *code\_paths* should include, minimally, a 233 | reference to "deps/stats" which is the library that basho\_bench uses for 234 | various statistical purposes. 235 | 236 | For example: 237 | 238 | : {code_paths, [ 239 | : "deps/stats", 240 | : "../riak_src/apps/riak_kv", 241 | : "../riak_src/apps/riak_core"]}. 242 | 243 | 244 | *** key\_generator 245 | 246 | The generator function to use for creating keys. Generators are defined in 247 | /basho\_bench\_keygen.erl/. Available generators include: 248 | 249 | + {sequential\_int, MaxKey} :: generates integers from 0..MaxKey in order 250 | and then stops the system. Note that each instance of this keygen is 251 | specific to a worker. 252 | 253 | + {sequential\_int\_bin, MaxKey} :: same as above, but the result from the 254 | function is a 32-bit binary encoding of the integer. 255 | 256 | + {sequential\_int\_str, MaxKey} :: same as /sequential\_int/, but the 257 | result from the function is encoded as a string. 258 | 259 | + {uniform\_int, MaxKey} :: selects an integer from uniform distribution of 260 | 0..MaxKey. I.e. all integers are equally probable. 261 | 262 | + {uniform\_int\_bin, MaxKey} :: same as above, but the result of the 263 | function is a 32-bit binary encoding of the integer. 264 | 265 | + {uniform\_int\_str, MaxKey} :: same as /uniform\_int/ , but the result 266 | from the function is encoded as a string. 267 | 268 | + {pareto\_int, MaxKey} :: selects an integer from a Pareto distribution, 269 | such that 20% of the available keys get selected 80% of the time. Note 270 | that the current implementation of this generator MAY yield values 271 | larger than MaxKey due to the mathematical properties of the Pareto 272 | distribution. 273 | 274 | + {pareto\_int\_bin, MaxKey} :: same as /pareto\_int/, but the result from 275 | the function is a 32-bit binary encoding of the integer. 276 | 277 | The default key generator is ={uniform_int, 100000}=. 278 | 279 | Examples: 280 | 281 | : % Use a randomly selected integer between 1 and 10,000 282 | : {key_generator, {uniform_int, 10000}}. 283 | 284 | : % Use a randomly selected integer between 1 and 10,000, as binary. 285 | : {key_generator, {uniform_int_bin, 10000}}. 286 | 287 | : % Use a pareto distributed integer between 1 and 10,000; values < 2000 288 | : % will be returned 80% of the time. 289 | : {key_generator, {pareto_int, 10000}}. 290 | 291 | *** value\_generator 292 | 293 | The generator function to use for creating values. Generators are defined in 294 | /basho\_bench\_valgen.erl/. Available generators include: 295 | 296 | + {fixed\_bin, Size} :: generates a random binary of Size bytes. Every 297 | binary is the same size, but varies in content. 298 | 299 | + {exponential\_bin, MinSize, Mean} :: generate a random binary which has an 300 | exponentially-distributed size. Most values will be approximately 301 | MinSize + Mean bytes in size, with a long-tail of larger values. 302 | 303 | The default value generator is ={value\_generator, {fixed\_bin, 100}}=. 304 | 305 | Examples: 306 | 307 | : % Generate a fixed size random binary of 512 bytes 308 | : {value_generator, {fixed_bin, 512}}. 309 | 310 | : % Generate a random binary whose size is exponentially distributed 311 | : % starting at 1000 bytes and a mean of 2000 bytes 312 | : {value_generator, {exponential_bin, 1000, 2000}}. 313 | 314 | *** rng\_seed 315 | 316 | The initial random seed to use. This is explicitly seeded, rather than 317 | seeded from the current time, so that a test can be run in a predictable, 318 | repeatable fashion. 319 | 320 | Default is ={rng_seed, {42, 23, 12}}=. 321 | 322 | : % Seed to {12, 34, 56} 323 | : {rng_seed, {12, 34, 56}}. 324 | 325 | *** log\_level 326 | 327 | The *log\_level* setting determines which messages Basho Bench will 328 | log to the console and to disk. 329 | 330 | Default level is *debug*. 331 | 332 | Valid levels are: 333 | 334 | + debug 335 | + info 336 | + warn 337 | + error 338 | 339 | *** report\_interval 340 | 341 | How often, in seconds, should the stats process write histogram 342 | data to disk. Default is 10 seconds. 343 | 344 | *** test\_dir 345 | 346 | The directory in which to write result data. The default is 347 | /tests/. 348 | 349 | 350 | ** basho\_bench\_driver\_riakclient Settings 351 | 352 | These configuration settings apply to the 353 | /basho\_bench\_driver\_riakclient/ driver. 354 | 355 | *** riakclient\_nodes 356 | 357 | List of Riak nodes to use for testing. 358 | 359 | : {riakclient_nodes, ['riak1@127.0.0.1', 'riak2@127.0.0.1']}. 360 | 361 | *** riakclient\_cookie 362 | 363 | The Erlang cookie to use to connect to Riak clients. Default is ='riak'=. 364 | 365 | : {riakclient_cookie, riak}. 366 | 367 | *** riakclient\_mynode 368 | 369 | The name of the local node. This is passed into 370 | =net_kernel:start/1= (http://erlang.org/doc/man/net_kernel.html). 371 | 372 | : {riakclient_mynode, ['basho_bench@127.0.0.1', longnames]}. 373 | 374 | *** riakclient\_replies 375 | 376 | This value is used for R-values during a get operation, and 377 | W-values during a put operation. 378 | 379 | : % Expect 1 reply. 380 | : {riakclient_replies, 1}. 381 | 382 | *** riakclient\_bucket 383 | 384 | The Riak bucket to use for reading and writing values. Default is =<<"test">>=. 385 | 386 | : % Use the "bench" bucket. 387 | : {riakclient_bucket, <<"bench">>}. 388 | 389 | ** basho\_bench\_driver\_dets Settings 390 | 391 | Not yet documented. 392 | 393 | ** basho\_bench\_driver\_http\_raw Settings 394 | 395 | *** http\_raw\_ips 396 | List of IP addresses to connect the workers to. Each worker makes requests to each 397 | IP in a round-robin fashion. 398 | 399 | Default is ={http_raw_ips, ["127.0.0.1"]}= 400 | 401 | % Connect to a cluster of machines in the 10.x network 402 | {http_raw_ips, ["10.0.0.1", "10.0.0.2", "10.0.0.3"]}. 403 | 404 | 405 | *** http_raw_port 406 | Select the default port to connect on for the HTTP server. 407 | 408 | Default is ={http_raw_port, 8098}.= 409 | 410 | % Connect on port 8090 411 | {http_raw_port, 8090}. 412 | 413 | *** http_raw_path 414 | Base path to use for accessing riak - usually "/riak/" 415 | 416 | Defaults is ={http_raw_path, "/riak/test"}.= 417 | 418 | % Place test data in another_bucket 419 | {http_raw_path, "/riak/another_bucket"}. 420 | 421 | *** http_raw_params 422 | Additional parameters to add to the end of the URL. This can be used to set 423 | riak r/w/dw/rw parameters as as desired. 424 | 425 | Default is ={http_raw_params, ""}.= 426 | 427 | % Set R=1, W=1 for testing a system with n_val set to 1 428 | {http_raw_params, "?r=1&w=1"}. 429 | 430 | * Custom Driver 431 | 432 | A custom driver must expose the following callbacks. 433 | 434 | : % Create the worker. 435 | : % ID is an integer. 436 | : new(ID) -> {ok, State} or {error, Reason}. 437 | 438 | : % Run an operation. 439 | : run(Op, KeyGen, ValueGen, State) -> {ok, NewState} or {error, Reason, NewState} 440 | 441 | See the existing drivers for more details. 442 | 443 | 444 | --------------------------------------------------------------------------------