├── .gitignore ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md ├── all.plot ├── benchmark.conf ├── multi.plot ├── pom.xml ├── runBenchmark.sh └── src └── main ├── java └── edu │ └── brown │ └── cs │ └── zkbenchmark │ ├── AsyncBenchmarkClient.java │ ├── BenchmarkClient.java │ ├── BenchmarkListener.java │ ├── SyncBenchmarkClient.java │ └── ZooKeeperBenchmark.java └── resources └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target/ 3 | .classpath 4 | .project 5 | # Package Files # 6 | *.jar 7 | *.war 8 | *.ear 9 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Mon Aug 27 21:50:23 EDT 2012 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | eclipse.preferences.version=1 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | org.eclipse.jdt.core.compiler.compliance=1.6 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ZooKeeper Benchmark 2 | Copyright (c) 2012, Brown University. 3 | All Rights Reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of the Brown University, nor the names 15 | of its contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZooKeeper Benchmark 2 | 3 | Authors: [Chen Liang](http://www.cs.brown.edu/~chen_liang/), [Andrew Ferguson](http://www.cs.brown.edu/~adf/), [Rodrigo Fonseca](http://www.cs.brown.edu/~rfonseca/) 4 | 5 | Please contact adf@cs.brown.edu with any questions or comments. Patches and 6 | additional features are more than welcome! 7 | 8 | ## Introduction 9 | 10 | This project provides a tool to benchmark the performance of [ZooKeeper](http://zookeeper.apache.org). 11 | It is designed to measure the per-request latency of a ZooKeeper ensemble for 12 | a predetermined length of time (e.g., sustained handling of create requests for 13 | 30 seconds). This tool can be used to build graphs such as Figure 8 in the 14 | paper ["ZooKeeper: Wait-free coordination for Internet-scale systems"](http://static.usenix.org/event/usenix10/tech/full_papers/Hunt.pdf), 15 | and differs from the [ZooKeeper smoketest](https://github.com/phunt/zk-smoketest), 16 | which submits a fixed number of operations and records the time to complete all 17 | of them. 18 | 19 | The benchmark exercises the ensemble's performance at handling znode reads, 20 | repeated writes to a single znode, znode creation, repeated writes to 21 | multiple znodes, and znode deletion. These tests can be performed with either 22 | synchronous or asynchronous operations. The benchmark connects to each server 23 | in the ZooKeeper ensemble using one thread per server. 24 | 25 | In synchronous operation, each client makes a new request upon receiving the 26 | result of the previous one. In asynchronous operation, each client thread has 27 | a globally configurable target for the number of outstanding asynchronous 28 | requests. If the number of outstanding requests falls below a configurable lower 29 | bound, then new asynchronous requests are made to return to the target level. 30 | 31 | During the benchmark, the current rate of request processing is recorded at 32 | a configurable intermediate interval to one file per operation: READ.dat, 33 | SETSINGLE.dat, CREATE.dat, SETMULTI.dat, and DELETE.dat. Additional output is 34 | recorded to the log file zk-benchmark.log, including the output of the ZooKeeper 35 | `stat` command after every test (`srst` is run before each test to reset the 36 | statistics). Some messages are also displayed on the console, all of which can 37 | be adjusted via the log4j.properties file. 38 | 39 | ## Build and Usage Instructions 40 | 41 | To compile the code, run: 42 | 43 | mvn -DZooKeeperVersion= package 44 | 45 | where `` is a ZooKeeper version such as 3.4.3, 3.5.0-pane, etc. The 46 | client code corresponding to the ZooKeeper version will be found using maven. 47 | 48 | After this, run the benchmark using a configuration file: 49 | 50 | java -cp target/lib/*:target/* edu.brown.cs.zkbenchmark.ZooKeeperBenchmark --conf benchmark.conf 51 | 52 | The configuration file provides the list of servers to contact and parameters 53 | for the benchmark; please see the included example for more details. Many 54 | configuration parameters can also be set on the command line. A `--help` option 55 | lists the possible options: 56 | 57 |
 58 | Option (* = required)           Description                            
 59 | ---------------------           -----------                            
 60 | * --conf                        configuration file (required)          
 61 | --help                          print this help statement              
 62 | --interval                      interval between rate measurements     
 63 | --lbound                        lowerbound for the number of operations
 64 | --ops                           total number of operations             
 65 | --sync                          sync or async test                     
 66 | --time                          time tests will run for (milliseconds)
 67 | 
68 | 69 | In addition, we have included a script `runBenchmark.sh` which launches runs 70 | of the example benchmark configuration. It requires one argument, a name for 71 | the run. A path to a configuration file can be provided as an optional second 72 | argument. Finally, if the last argument is set to `--gnuplot`, the script plots 73 | the rate output files using the included gnuplot script, all.plot. A second 74 | gnuplot script, multi.plot, can be used to compare two runs named "pre" and 75 | "post". 76 | 77 | ## Eclipse Development 78 | 79 | As a simple Maven project, our benchmark can easily be developed using Eclipse. 80 | It is necessary to first set the M2_REPO variable for your workspace (This 81 | command only needs to be executed once per workspace): 82 | 83 | mvn -Declipse.workspace= eclipse:configure-workspace 84 | 85 | Next, install the libraries and create the Eclipse project files: 86 | 87 | mvn -DZooKeeperVersion= install -DskipTests 88 | mvn -DZooKeeperVersion= eclipse:eclipse 89 | 90 | You can now import the project into Eclipse using, File > Import > Existing 91 | Projects into Workspace. 92 | 93 | If you wish to view the source or JavaDocs of the benchmark's dependencies, you 94 | add `-DdownloadSources=true` or `-DdownloadJavadocs=true` when creating the 95 | Eclipse project files in the final step. 96 | 97 | Internally, this project uses the [Netflix Curator](https://github.com/Netflix/curator) 98 | library to wrap the ZooKeeper client API. 99 | 100 | ## Notes 101 | 102 | 1. In the benchmark, node creation and deletion tests are done by creating nodes 103 | in the first test, and then deleting them in the second. Since each test runs 104 | for a fixed amount of time, there are no guarantees about the number of nodes 105 | created in the first one. If there are more delete requests than create 106 | requests, the extra delete requests will not actually deleting anything. These 107 | requests are sent to, and processed by, the ZooKeeper server, which may affect 108 | the average per-request latency reported by the test. Examining the 109 | intermediately-reported request rates will provide more accurate information. 110 | 111 | 2. Read requests are handled by ZooKeeper more quickly than write requests. If 112 | the time interval and threshold are not chosen appropriately, it could happen 113 | that when the timer awakes, all requests have already been finished. In this 114 | case, the output of the read test doesn't reflect the actual rate of read 115 | requests the ensemble could support. As with the previous concern, examining 116 | the intermediately-reported request rates will provide better insight. 117 | 118 | ## License 119 | 120 | Ths ZooKeeper benchmark is provided under the 3-clause BSD license. See the 121 | file LICENSE for more details. 122 | -------------------------------------------------------------------------------- /all.plot: -------------------------------------------------------------------------------- 1 | set xlabel "Time since start" 2 | set ylabel "CREATE ops per second" 3 | plot "CREATE.dat" with lines 4 | 5 | set size 1.0, 0.6 6 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 7 | set output "create.ps" 8 | replot 9 | set term pop 10 | 11 | set xlabel "Time since start" 12 | set ylabel "DELETE ops per second" 13 | plot "DELETE.dat" with lines 14 | 15 | set size 1.0, 0.6 16 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 17 | set output "delete.ps" 18 | replot 19 | set term pop 20 | 21 | set xlabel "Time since start" 22 | set ylabel "READ ops per second" 23 | plot "READ.dat" with lines 24 | 25 | set size 1.0, 0.6 26 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 27 | set output "read.ps" 28 | replot 29 | set term pop 30 | 31 | set xlabel "Time since start" 32 | set ylabel "SETSINGLE ops per second" 33 | plot "SETSINGLE.dat" with lines 34 | 35 | set size 1.0, 0.6 36 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 37 | set output "setsingle.ps" 38 | replot 39 | set term pop 40 | 41 | set xlabel "Time since start" 42 | set ylabel "SETMULTI ops per second" 43 | plot "SETMULTI.dat" with lines 44 | 45 | set size 1.0, 0.6 46 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 47 | set output "setmulti.ps" 48 | replot 49 | set term pop 50 | -------------------------------------------------------------------------------- /benchmark.conf: -------------------------------------------------------------------------------- 1 | # Example configuration for ZooKeeper benchmark. 2 | 3 | # Length of each test in milliseconds. 4 | # Can also be set on the command-line. 5 | totalTime=30000 6 | 7 | # Interval in milliseconds between request rate measurements. 8 | # Can also be set on the command-line. 9 | interval=200 10 | 11 | # Target number of operations to submit, will be topped-off as needed 12 | # Can also be set on the command-line. 13 | totalOperations=20000 14 | 15 | # Submit more requests when the number outstanding falls below this amount. 16 | # Can also be set on the command-line. 17 | lowerbound=8000 18 | 19 | # Whether requests should be submitted synchronously or asynchronously. 20 | # Can also be set on the command-line. 21 | sync=true 22 | 23 | # List of ZooKeeper servers to connect to. 24 | server.1=host1:2181 25 | server.2=host2:2181 26 | server.3=host3:2181 27 | server.4=host4:2181 28 | server.5=host5:2181 29 | #server.1=127.0.0.1:2181 30 | #server.2=127.0.0.1:2182 31 | #server.3=127.0.0.1:2183 32 | -------------------------------------------------------------------------------- /multi.plot: -------------------------------------------------------------------------------- 1 | set xlabel "Time since start" 2 | set ylabel "CREATE ops per second" 3 | plot "pre/CREATE.dat" title 'Pre' with lines, "post/CREATE.dat" title 'Post' with lines 4 | 5 | set size 1.0, 0.6 6 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 7 | set output "create.ps" 8 | replot 9 | set term pop 10 | 11 | set xlabel "Time since start" 12 | set ylabel "DELETE ops per second" 13 | plot "pre/DELETE.dat" title 'Pre' with lines, "post/DELETE.dat" title 'Post' with lines 14 | 15 | set size 1.0, 0.6 16 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 17 | set output "delete.ps" 18 | replot 19 | set term pop 20 | 21 | set xlabel "Time since start" 22 | set ylabel "READ ops per second" 23 | plot "pre/READ.dat" title 'Pre' with lines, "post/READ.dat" title 'Post' with lines 24 | 25 | set size 1.0, 0.6 26 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 27 | set output "read.ps" 28 | replot 29 | set term pop 30 | 31 | set xlabel "Time since start" 32 | set ylabel "SETSINGLE ops per second" 33 | plot "pre/SETSINGLE.dat" title 'Pre' with lines, "post/SETSINGLE.dat" title 'Post' with lines 34 | 35 | set size 1.0, 0.6 36 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 37 | set output "setsingle.ps" 38 | replot 39 | set term pop 40 | 41 | set xlabel "Time since start" 42 | set ylabel "SETMULTI ops per second" 43 | plot "pre/SETMULTI.dat" title 'Pre' with lines, "post/SETMULTI.dat" title 'Post' with lines 44 | 45 | set size 1.0, 0.6 46 | set terminal postscript portrait enhanced color dashed lw 1 "Helvetica" 14 47 | set output "setmulti.ps" 48 | replot 49 | set term pop 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | edu.brown.cs 6 | zkbenchmark 7 | 0.1-SNAPSHOT 8 | jar 9 | 10 | ZooKeeper Benchmark 11 | http://github.com/brownsys/zookeeper-benchmark 12 | 13 | 14 | Brown Univ. Computer Science 15 | http://systems.cs.brown.edu 16 | 17 | 18 | 19 | 20 | 21 | org.apache.zookeeper 22 | zookeeper 23 | ${ZooKeeperVersion} 24 | 25 | 26 | 27 | com.netflix.curator 28 | curator-client 29 | 1.1.14 30 | 31 | 32 | 33 | com.netflix.curator 34 | curator-framework 35 | 1.1.14 36 | 37 | 38 | 39 | jline 40 | jline 41 | 0.9.94 42 | 43 | 44 | 45 | log4j 46 | log4j 47 | 1.2.16 48 | 49 | 50 | 51 | com.google.guava 52 | guava 53 | 11.0.2 54 | 55 | 56 | 57 | org.slf4j 58 | slf4j-api 59 | 1.6.4 60 | 61 | 62 | 63 | org.slf4j 64 | slf4j-log4j12 65 | 1.6.4 66 | 67 | 68 | 69 | commons-configuration 70 | commons-configuration 71 | 1.8 72 | 73 | 74 | 75 | net.sf.jopt-simple 76 | jopt-simple 77 | 4.3 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-dependency-plugin 87 | 88 | 89 | copy 90 | prepare-package 91 | 92 | copy-dependencies 93 | 94 | 95 | 96 | ${project.build.directory}/lib 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-compiler-plugin 106 | 107 | 1.6 108 | 1.6 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /runBenchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR="`dirname $0`" 4 | 5 | RUN_NAME="$1" 6 | CONF_PATH="$2" # optional; we'll check if it's --gnuplot below 7 | USE_GNUPLOT="$3" # if set to "--gnuplot", plot the output files 8 | 9 | CONF_DEFAULT_PATH="benchmark.conf" 10 | 11 | if [ "$RUN_NAME" == "" ]; then 12 | echo "Name for benchmark run required!" 13 | exit 1 14 | fi 15 | 16 | if [ -d "$SCRIPT_DIR/$RUN_NAME" ]; then 17 | echo "Benchmark run $RUN_NAME already exists!" 18 | exit 2 19 | fi 20 | 21 | # Some ad-hoc argument parsing... 22 | 23 | if [ "$CONF_PATH" == "--gnuplot" ]; then 24 | USE_GNUPLOT="$CONF_PATH" 25 | CONF_PATH="$CONF_DEFAULT_PATH" 26 | elif [ "$CONF_PATH" == "" ]; then 27 | CONF_PATH="$CONF_DEFAULT_PATH" 28 | fi 29 | 30 | if [ ! -f "$CONF_PATH" ]; then 31 | echo "Configuration file $CONF_PATH does not exist!" 32 | exit 3 33 | else 34 | echo "Using configuration: $CONF_PATH" 35 | fi 36 | 37 | # Time to start 38 | 39 | mkdir "$SCRIPT_DIR/$RUN_NAME" 40 | 41 | # Copy the parameters to be used 42 | CONF_FILE="`basename $CONF_PATH`" 43 | cp "$CONF_PATH" "$SCRIPT_DIR/$RUN_NAME/$CONF_FILE" 44 | 45 | pushd "$SCRIPT_DIR/$RUN_NAME" 1>/dev/null 46 | 47 | # Record the git hash of the benchmark used 48 | git log -1 --format="%H" > "zookeeper-benchmark.version" 49 | 50 | # Run the benchmark 51 | java -cp ../target/lib/*:../target/* edu.brown.cs.zkbenchmark.ZooKeeperBenchmark --conf "$CONF_FILE" 2>&1 | tee "$RUN_NAME.out" 52 | 53 | # Optionally, plot some graphs 54 | if [ "`which gnuplot`" != "" ] && [ "$USE_GNUPLOT" == "--gnuplot" ]; then 55 | gnuplot ../all.plot 56 | 57 | if [ "`which ps2pdf`" != "" ]; then 58 | for i in `ls -1 *.ps`; do 59 | ps2pdf $i 60 | done 61 | fi 62 | fi 63 | 64 | popd 1>/dev/null 65 | -------------------------------------------------------------------------------- /src/main/java/edu/brown/cs/zkbenchmark/AsyncBenchmarkClient.java: -------------------------------------------------------------------------------- 1 | package edu.brown.cs.zkbenchmark; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.log4j.Logger; 6 | 7 | import com.netflix.curator.framework.api.CuratorListener; 8 | import com.netflix.curator.framework.listen.ListenerContainer; 9 | 10 | import edu.brown.cs.zkbenchmark.ZooKeeperBenchmark.TestType; 11 | 12 | public class AsyncBenchmarkClient extends BenchmarkClient { 13 | 14 | private class Monitor { } 15 | 16 | TestType _currentType = TestType.UNDEFINED; 17 | private Monitor _monitor = new Monitor(); 18 | private boolean _asyncRunning; 19 | 20 | private static final Logger LOG = Logger.getLogger(AsyncBenchmarkClient.class); 21 | 22 | 23 | public AsyncBenchmarkClient(ZooKeeperBenchmark zkBenchmark, String host, String namespace, 24 | int attempts, int id) throws IOException { 25 | super(zkBenchmark, host, namespace, attempts, id); 26 | } 27 | 28 | 29 | @Override 30 | protected void submit(int n, TestType type) { 31 | ListenerContainer listeners = (ListenerContainer)_client.getCuratorListenable(); 32 | BenchmarkListener listener = new BenchmarkListener(this); 33 | listeners.addListener(listener); 34 | _currentType = type; 35 | _asyncRunning = true; 36 | 37 | submitRequests(n, type); 38 | 39 | synchronized (_monitor) { 40 | while (_asyncRunning) { 41 | try { 42 | _monitor.wait(); 43 | } catch (InterruptedException e) { 44 | LOG.warn("AsyncClient #" + _id + " was interrupted", e); 45 | } 46 | } 47 | } 48 | 49 | listeners.removeListener(listener); 50 | } 51 | 52 | private void submitRequests(int n, TestType type) { 53 | try { 54 | submitRequestsWrapped(n, type); 55 | } catch (Exception e) { 56 | // What can you do? for some reason 57 | // com.netflix.curator.framework.api.Pathable.forPath() throws Exception 58 | 59 | //just log the error, not sure how to handle this exception correctly 60 | LOG.error("Exception while submitting requests", e); 61 | } 62 | } 63 | 64 | private void submitRequestsWrapped(int n, TestType type) throws Exception { 65 | byte data[]; 66 | 67 | for (int i = 0; i < n; i++) { 68 | double time = ((double)System.nanoTime() - _zkBenchmark.getStartTime())/1000000000.0; 69 | 70 | switch (type) { 71 | case READ: 72 | _client.getData().inBackground(new Double(time)).forPath(_path); 73 | break; 74 | 75 | case SETSINGLE: 76 | data = new String(_zkBenchmark.getData() + i).getBytes(); 77 | _client.setData().inBackground(new Double(time)).forPath( 78 | _path, data); 79 | break; 80 | 81 | case SETMULTI: 82 | data = new String(_zkBenchmark.getData() + i).getBytes(); 83 | _client.setData().inBackground(new Double(time)).forPath( 84 | _path + "/" + (_count % _highestN), data); 85 | break; 86 | 87 | case CREATE: 88 | data = new String(_zkBenchmark.getData() + i).getBytes(); 89 | _client.create().inBackground(new Double(time)).forPath( 90 | _path + "/" + _count, data); 91 | _highestN++; 92 | break; 93 | 94 | case DELETE: 95 | _client.delete().inBackground(new Double(time)).forPath(_path + "/" + _count); 96 | _highestDeleted++; 97 | 98 | if (_highestDeleted >= _highestN) { 99 | zkAdminCommand("stat"); 100 | _zkBenchmark.notifyFinished(_id); 101 | _timer.cancel(); 102 | _count++; 103 | return; 104 | } 105 | } 106 | _count++; 107 | } 108 | 109 | } 110 | 111 | @Override 112 | protected void finish() { 113 | synchronized (_monitor) { 114 | _asyncRunning = false; 115 | _monitor.notify(); 116 | } 117 | } 118 | 119 | @Override 120 | protected void resubmit(int n) { 121 | submitRequests(n, _currentType); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/edu/brown/cs/zkbenchmark/BenchmarkClient.java: -------------------------------------------------------------------------------- 1 | package edu.brown.cs.zkbenchmark; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.net.Socket; 10 | import java.net.UnknownHostException; 11 | import java.util.List; 12 | import java.util.Timer; 13 | import java.util.TimerTask; 14 | import java.util.concurrent.BrokenBarrierException; 15 | 16 | import org.apache.zookeeper.data.Stat; 17 | import org.apache.log4j.Logger; 18 | 19 | import com.netflix.curator.framework.CuratorFramework; 20 | import com.netflix.curator.framework.CuratorFrameworkFactory; 21 | import com.netflix.curator.framework.api.CuratorEvent; 22 | import com.netflix.curator.retry.RetryNTimes; 23 | 24 | import edu.brown.cs.zkbenchmark.ZooKeeperBenchmark.TestType; 25 | 26 | public abstract class BenchmarkClient implements Runnable { 27 | protected ZooKeeperBenchmark _zkBenchmark; 28 | protected String _host; // the host this client is connecting to 29 | protected CuratorFramework _client; // the actual client 30 | protected TestType _type; // current test 31 | protected int _attempts; 32 | protected String _path; 33 | protected int _id; 34 | protected int _count; 35 | protected int _countTime; 36 | protected Timer _timer; 37 | 38 | protected int _highestN; 39 | protected int _highestDeleted; 40 | 41 | protected BufferedWriter _latenciesFile; 42 | 43 | private static final Logger LOG = Logger.getLogger(BenchmarkClient.class); 44 | 45 | 46 | public BenchmarkClient(ZooKeeperBenchmark zkBenchmark, String host, String namespace, 47 | int attempts, int id) throws IOException { 48 | _zkBenchmark = zkBenchmark; 49 | _host = host; 50 | _client = CuratorFrameworkFactory.builder() 51 | .connectString(_host).namespace(namespace) 52 | .retryPolicy(new RetryNTimes(Integer.MAX_VALUE,1000)) 53 | .connectionTimeoutMs(5000).build(); 54 | _type = TestType.UNDEFINED; 55 | _attempts = attempts; 56 | _id = id; 57 | _path = "/client"+id; 58 | _timer = new Timer(); 59 | _highestN = 0; 60 | _highestDeleted = 0; 61 | } 62 | 63 | @Override 64 | public void run() { 65 | if (!_client.isStarted()) 66 | _client.start(); 67 | 68 | if (_type == TestType.CLEANING) { 69 | doCleaning(); 70 | return; 71 | } 72 | 73 | zkAdminCommand("srst"); // Reset ZK server's statistics 74 | 75 | // Wait for all clients to be ready 76 | 77 | try { 78 | _zkBenchmark.getBarrier().await(); 79 | } catch (InterruptedException e) { 80 | LOG.warn("Client #" + _id + " was interrupted while waiting on barrier", e); 81 | } catch (BrokenBarrierException e) { 82 | LOG.warn("Some other client was interrupted. Client #" + _id + " is out of sync", e); 83 | } 84 | 85 | _count = 0; 86 | _countTime = 0; 87 | 88 | // Create a directory to work in 89 | 90 | try { 91 | Stat stat = _client.checkExists().forPath(_path); 92 | if (stat == null) { 93 | _client.create().forPath(_path, _zkBenchmark.getData().getBytes()); 94 | } 95 | } catch (Exception e) { 96 | LOG.error("Error while creating working directory", e); 97 | } 98 | 99 | // Create a timer to check when we're finished. Schedule it to run 100 | // periodically in case we want to record periodic statistics 101 | 102 | int interval = _zkBenchmark.getInterval(); 103 | _timer.scheduleAtFixedRate(new FinishTimer(), interval, interval); 104 | 105 | try { 106 | _latenciesFile = new BufferedWriter(new FileWriter(new File(_id + 107 | "-" + _type + "_timings.dat"))); 108 | } catch (IOException e) { 109 | LOG.error("Error while creating output file", e); 110 | } 111 | 112 | // Submit the requests! 113 | 114 | submit(_attempts, _type); 115 | 116 | // Test is complete. Print some stats and go home. 117 | 118 | zkAdminCommand("stat"); 119 | 120 | 121 | try { 122 | if (_latenciesFile != null) 123 | _latenciesFile.close(); 124 | } catch (IOException e) { 125 | LOG.warn("Error while closing output file:", e); 126 | } 127 | 128 | LOG.info("Client #" + _id + " -- Current test complete. " + 129 | "Completed " + _count + " operations."); 130 | 131 | _zkBenchmark.notifyFinished(_id); 132 | 133 | } 134 | 135 | class FinishTimer extends TimerTask { 136 | @Override 137 | public void run() { 138 | //this can be used to measure rate of each thread 139 | //at this moment, it is not necessary 140 | _countTime++; 141 | 142 | if (_countTime == _zkBenchmark.getDeadline()) { 143 | this.cancel(); 144 | finish(); 145 | } 146 | } 147 | } 148 | 149 | void doCleaning() { 150 | try { 151 | deleteChildren(); 152 | } catch (Exception e) { 153 | LOG.error("Exception while deleting old znodes", e); 154 | } 155 | 156 | _zkBenchmark.notifyFinished(_id); 157 | } 158 | 159 | /* Delete all the child znodes created by this client */ 160 | void deleteChildren() throws Exception { 161 | List children; 162 | 163 | do { 164 | children = _client.getChildren().forPath(_path); 165 | for (String child : children) { 166 | _client.delete().inBackground().forPath(_path + "/" + child); 167 | } 168 | Thread.sleep(2000); 169 | } while (children.size() != 0); 170 | } 171 | 172 | 173 | void recordEvent(CuratorEvent event) { 174 | Double submitTime = (Double) event.getContext(); 175 | recordElapsedInterval(submitTime); 176 | } 177 | 178 | 179 | void recordElapsedInterval(Double startTime) { 180 | double endtime = ((double)System.nanoTime() - _zkBenchmark.getStartTime())/1000000000.0; 181 | 182 | try { 183 | _latenciesFile.write(startTime.toString() + " " + Double.toString(endtime) + "\n"); 184 | } catch (IOException e) { 185 | LOG.error("Exceptions while writing to file", e); 186 | } 187 | } 188 | 189 | /* Send a command directly to the ZooKeeper server */ 190 | void zkAdminCommand(String cmd) { 191 | String host = _host.split(":")[0]; 192 | int port = Integer.parseInt(_host.split(":")[1]); 193 | Socket socket = null; 194 | OutputStream os = null; 195 | InputStream is = null; 196 | byte[] b = new byte[1000]; 197 | 198 | try { 199 | socket = new Socket(host, port); 200 | os = socket.getOutputStream(); 201 | is = socket.getInputStream(); 202 | 203 | os.write(cmd.getBytes()); 204 | os.flush(); 205 | 206 | int len = is.read(b); 207 | while (len >= 0) { 208 | LOG.info("Client #" + _id + " is sending " + cmd + 209 | " command:\n" + new String(b, 0, len)); 210 | len = is.read(b); 211 | } 212 | 213 | is.close(); 214 | os.close(); 215 | socket.close(); 216 | } catch (UnknownHostException e) { 217 | LOG.error("Unknown ZooKeeper server: " + _host, e); 218 | } catch (IOException e) { 219 | LOG.error("IOException while contacting ZooKeeper server: " + _host, e); 220 | } 221 | } 222 | 223 | int getTimeCount() { 224 | return _countTime; 225 | } 226 | 227 | int getOpsCount(){ 228 | return _count; 229 | } 230 | 231 | ZooKeeperBenchmark getBenchmark() { 232 | return _zkBenchmark; 233 | } 234 | 235 | void setTest(TestType type) { 236 | _type = type; 237 | } 238 | 239 | abstract protected void submit(int n, TestType type); 240 | 241 | /** 242 | * for synchronous requests, to submit more requests only needs to increase the total 243 | * number of requests, here n can be an arbitrary number 244 | * for asynchronous requests, to submit more requests means that the client will do 245 | * both submit and wait 246 | * @param n 247 | */ 248 | abstract protected void resubmit(int n); 249 | 250 | abstract protected void finish(); 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/edu/brown/cs/zkbenchmark/BenchmarkListener.java: -------------------------------------------------------------------------------- 1 | package edu.brown.cs.zkbenchmark; 2 | 3 | import com.netflix.curator.framework.CuratorFramework; 4 | import com.netflix.curator.framework.api.CuratorEvent; 5 | import com.netflix.curator.framework.api.CuratorEventType; 6 | import com.netflix.curator.framework.api.CuratorListener; 7 | 8 | import edu.brown.cs.zkbenchmark.ZooKeeperBenchmark.TestType; 9 | 10 | class BenchmarkListener implements CuratorListener { 11 | private BenchmarkClient _client; // client listener listens for 12 | 13 | BenchmarkListener(BenchmarkClient client) { 14 | _client = client; 15 | } 16 | 17 | @Override 18 | public void eventReceived(CuratorFramework client, CuratorEvent event) { 19 | CuratorEventType type = event.getType(); 20 | 21 | // Ensure that the event is reply to current test 22 | if ((type == CuratorEventType.GET_DATA && _client.getBenchmark().getCurrentTest() == TestType.READ) || 23 | (type == CuratorEventType.SET_DATA && _client.getBenchmark().getCurrentTest() == TestType.SETMULTI) || 24 | (type == CuratorEventType.SET_DATA && _client.getBenchmark().getCurrentTest() == TestType.SETSINGLE) || 25 | (type == CuratorEventType.DELETE && _client.getBenchmark().getCurrentTest() == TestType.DELETE) || 26 | (type == CuratorEventType.CREATE && _client.getBenchmark().getCurrentTest() == TestType.CREATE)) { 27 | _client.getBenchmark().incrementFinished(); 28 | _client.recordEvent(event); 29 | _client.resubmit(1); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/edu/brown/cs/zkbenchmark/SyncBenchmarkClient.java: -------------------------------------------------------------------------------- 1 | package edu.brown.cs.zkbenchmark; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | import org.apache.zookeeper.KeeperException.NoNodeException; 7 | import org.apache.log4j.Logger; 8 | 9 | import edu.brown.cs.zkbenchmark.ZooKeeperBenchmark.TestType; 10 | 11 | public class SyncBenchmarkClient extends BenchmarkClient { 12 | 13 | AtomicInteger _totalOps; 14 | private boolean _syncfin; 15 | 16 | private static final Logger LOG = Logger.getLogger(SyncBenchmarkClient.class); 17 | 18 | 19 | public SyncBenchmarkClient(ZooKeeperBenchmark zkBenchmark, String host, String namespace, 20 | int attempts, int id) throws IOException { 21 | super(zkBenchmark, host, namespace, attempts, id); 22 | } 23 | 24 | @Override 25 | protected void submit(int n, TestType type) { 26 | try { 27 | submitWrapped(n, type); 28 | } catch (Exception e) { 29 | // What can you do? for some reason 30 | // com.netflix.curator.framework.api.Pathable.forPath() throws Exception 31 | LOG.error("Error while submitting requests", e); 32 | } 33 | } 34 | 35 | protected void submitWrapped(int n, TestType type) throws Exception { 36 | _syncfin = false; 37 | _totalOps = _zkBenchmark.getCurrentTotalOps(); 38 | byte data[]; 39 | 40 | for (int i = 0; i < _totalOps.get(); i++) { 41 | double submitTime = ((double)System.nanoTime() - _zkBenchmark.getStartTime())/1000000000.0; 42 | 43 | switch (type) { 44 | case READ: 45 | _client.getData().forPath(_path); 46 | break; 47 | 48 | case SETSINGLE: 49 | data = new String(_zkBenchmark.getData() + i).getBytes(); 50 | _client.setData().forPath(_path, data); 51 | break; 52 | 53 | case SETMULTI: 54 | try { 55 | data = new String(_zkBenchmark.getData() + i).getBytes(); 56 | _client.setData().forPath(_path + "/" + (_count % _highestN), data); 57 | } catch (NoNodeException e) { 58 | LOG.warn("No such node when setting data to mutiple nodes. " + 59 | "_path = " + _path + ", _count = " + _count + 60 | ", _highestN = " + _highestN, e); 61 | } 62 | break; 63 | 64 | case CREATE: 65 | data = new String(_zkBenchmark.getData() + i).getBytes(); 66 | _client.create().forPath(_path + "/" + _count, data); 67 | _highestN++; 68 | break; 69 | 70 | case DELETE: 71 | try { 72 | _client.delete().forPath(_path + "/" + _count); 73 | } catch (NoNodeException e) { 74 | if (LOG.isDebugEnabled()) { 75 | LOG.debug("No such node (" + _path + "/" + _count + 76 | ") when deleting nodes", e); 77 | } 78 | } 79 | } 80 | 81 | recordElapsedInterval(new Double(submitTime)); 82 | _count++; 83 | _zkBenchmark.incrementFinished(); 84 | 85 | if (_syncfin) 86 | break; 87 | } 88 | 89 | } 90 | 91 | @Override 92 | protected void finish() { 93 | _syncfin = true; 94 | } 95 | 96 | /** 97 | * in fact, n here can be arbitrary number as synchronous operations can be stopped 98 | * after finishing any operation. 99 | */ 100 | @Override 101 | protected void resubmit(int n) { 102 | _totalOps.getAndAdd(n); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/edu/brown/cs/zkbenchmark/ZooKeeperBenchmark.java: -------------------------------------------------------------------------------- 1 | package edu.brown.cs.zkbenchmark; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.LinkedList; 10 | import java.util.Timer; 11 | import java.util.TimerTask; 12 | import java.util.concurrent.BrokenBarrierException; 13 | import java.util.concurrent.CyclicBarrier; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | import joptsimple.OptionException; 17 | import joptsimple.OptionParser; 18 | import joptsimple.OptionSet; 19 | 20 | import org.apache.commons.configuration.Configuration; 21 | import org.apache.commons.configuration.ConfigurationException; 22 | import org.apache.commons.configuration.PropertiesConfiguration; 23 | import org.apache.log4j.Appender; 24 | import org.apache.log4j.FileAppender; 25 | import org.apache.log4j.Logger; 26 | 27 | public class ZooKeeperBenchmark { 28 | private int _totalOps; // total operations requested by user 29 | private AtomicInteger _currentTotalOps; // possibly increased # of ops so test last for requested time 30 | private int _lowerbound; 31 | private BenchmarkClient[] _clients; 32 | private int _interval; 33 | private HashMap _running; 34 | private AtomicInteger _finishedTotal; 35 | private int _lastfinished; 36 | private int _deadline; // in units of "_interval" 37 | private long _totalTimeSeconds; 38 | private long _lastCpuTime; 39 | private long _currentCpuTime; 40 | private long _startCpuTime; 41 | private TestType _currentTest; 42 | private String _data; 43 | private BufferedWriter _rateFile; 44 | private CyclicBarrier _barrier; 45 | private Boolean _finished; 46 | 47 | enum TestType { 48 | READ, SETSINGLE, SETMULTI, CREATE, DELETE, CLEANING, UNDEFINED 49 | } 50 | 51 | private static final Logger LOG = Logger.getLogger(ZooKeeperBenchmark.class); 52 | 53 | public ZooKeeperBenchmark(Configuration conf) throws IOException { 54 | LinkedList serverList = new LinkedList(); 55 | Iterator serverNames = conf.getKeys("server"); 56 | 57 | while (serverNames.hasNext()) { 58 | String serverName = serverNames.next(); 59 | String address = conf.getString(serverName); 60 | serverList.add(address); 61 | } 62 | 63 | if (serverList.size() == 0) { 64 | throw new IllegalArgumentException("ZooKeeper server addresses required"); 65 | } 66 | 67 | _interval = conf.getInt("interval"); 68 | _totalOps = conf.getInt("totalOperations"); 69 | _lowerbound = conf.getInt("lowerbound"); 70 | int totaltime = conf.getInt("totalTime"); 71 | _totalTimeSeconds = Math.round((double) totaltime / 1000.0); 72 | boolean sync = conf.getBoolean("sync"); 73 | 74 | _running = new HashMap(); 75 | _clients = new BenchmarkClient[serverList.size()]; 76 | _barrier = new CyclicBarrier(_clients.length+1); 77 | _deadline = totaltime / _interval; 78 | 79 | LOG.info("benchmark set with: interval: " + _interval + " total number: " + _totalOps + 80 | " threshold: " + _lowerbound + " time: " + totaltime + " sync: " + (sync?"SYNC":"ASYNC")); 81 | 82 | _data = ""; 83 | 84 | for (int i = 0; i < 20; i++) { // 100 bytes of important data 85 | _data += "!!!!!"; 86 | } 87 | 88 | int avgOps = _totalOps / serverList.size(); 89 | 90 | for (int i = 0; i < serverList.size(); i++) { 91 | if (sync) { 92 | _clients[i] = new SyncBenchmarkClient(this, serverList.get(i), "/zkTest", avgOps, i); 93 | } else { 94 | _clients[i] = new AsyncBenchmarkClient(this, serverList.get(i), "/zkTest", avgOps, i); 95 | } 96 | } 97 | 98 | } 99 | 100 | public void runBenchmark() { 101 | 102 | /* Read requests are done by zookeeper extremely 103 | * quickly compared with write requests. If the time 104 | * interval and threshold are not chosen appropriately, 105 | * it could happen that when the timer awakes, all requests 106 | * have already been finished. In this case, the output 107 | * of read test doesn't reflect the actual rate of 108 | * read requests. */ 109 | doTest(TestType.READ, "warm-up"); 110 | 111 | doTest(TestType.READ, "znode read"); // Do twice to allow for warm-up 112 | 113 | doTest(TestType.SETSINGLE, "repeated single-znode write"); 114 | 115 | doTest(TestType.CREATE, "znode create"); 116 | 117 | doTest(TestType.SETMULTI, "different znode write"); 118 | 119 | /* In the test, node creation and deletion tests are 120 | * done by creating a lot of nodes at first and then 121 | * deleting them. Since both of these two tests run 122 | * for a certain time, there is no guarantee that which 123 | * requests is more than the other. If there are more 124 | * delete requests than create requests, the extra delete 125 | * requests would end up not actually deleting anything. 126 | * Though these requests are sent and processed by 127 | * zookeeper server anyway, this could still be an issue.*/ 128 | doTest(TestType.DELETE, "znode delete"); 129 | 130 | LOG.info("Tests completed, now cleaning-up"); 131 | 132 | for (int i = 0; i < _clients.length; i++) { 133 | _clients[i].setTest(TestType.CLEANING); 134 | Thread tmp = new Thread(_clients[i]); 135 | _running.put(new Integer(i), tmp); 136 | tmp.start(); 137 | } 138 | 139 | while (!_finished) { 140 | synchronized (_running) { 141 | try { 142 | _running.wait(); 143 | } catch (InterruptedException e) { 144 | LOG.warn("Benchmark main thread was interrupted while waiting", e); 145 | } 146 | } 147 | } 148 | 149 | LOG.info("All tests are complete"); 150 | } 151 | 152 | /* This is where each individual test starts */ 153 | 154 | public void doTest(TestType test, String description) { 155 | _currentTest = test; 156 | _finishedTotal = new AtomicInteger(0); 157 | _lastfinished = 0; 158 | _currentTotalOps = new AtomicInteger(_totalOps); 159 | _finished = false; 160 | 161 | System.out.print("Running " + description + " benchmark for " + _totalTimeSeconds + " seconds... "); 162 | 163 | try { 164 | _rateFile = new BufferedWriter(new FileWriter(new File(test+".dat"))); 165 | } catch (IOException e) { 166 | LOG.error("Unable to create output file", e); 167 | } 168 | 169 | _startCpuTime = System.nanoTime(); 170 | _lastCpuTime = _startCpuTime; 171 | 172 | 173 | // Start the testing clients! 174 | 175 | for (int i = 0; i < _clients.length; i++) { 176 | _clients[i].setTest(test); 177 | Thread tmp = new Thread(_clients[i]); 178 | _running.put(new Integer(i), tmp); 179 | tmp.start(); 180 | } 181 | 182 | // Wait for clients to connect to their assigned server, and 183 | // start timer which ensures we have outstanding requests. 184 | 185 | try { 186 | _barrier.await(); 187 | } catch (BrokenBarrierException e) { 188 | LOG.warn("Some other client was interrupted; Benchmark main thread is out of sync", e); 189 | } catch (InterruptedException e) { 190 | LOG.warn("Benchmark main thread was interrupted while waiting on barrier", e); 191 | } 192 | 193 | Timer timer = new Timer(); 194 | timer.scheduleAtFixedRate(new ResubmitTimer() , _interval, _interval); 195 | 196 | // Wait for the test to finish 197 | 198 | while (!_finished) { 199 | synchronized (_running) { 200 | try { 201 | _running.wait(); 202 | } catch (InterruptedException e) { 203 | LOG.warn("Benchmark main thread was interrupted while waiting", e); 204 | } 205 | } 206 | } 207 | 208 | // Test is finished 209 | 210 | _currentTest = TestType.UNDEFINED; 211 | timer.cancel(); 212 | 213 | try { 214 | if (_rateFile != null) { 215 | _rateFile.close(); 216 | } 217 | } catch (IOException e) { 218 | LOG.warn("Error while closing output file", e); 219 | } 220 | 221 | double time = getTime(); 222 | LOG.info(test + " finished, time elapsed (sec): " + time + 223 | " operations: " + _finishedTotal.get() + " avg rate: " + 224 | _finishedTotal.get()/time); 225 | 226 | System.out.println("done"); 227 | } 228 | 229 | /* return the max time consumed by each thread */ 230 | double getTime() { 231 | double ret = 0; 232 | 233 | for (int i = 0; i < _clients.length; i++) { 234 | if (ret < _clients[i].getTimeCount()) 235 | ret = _clients[i].getTimeCount(); 236 | } 237 | 238 | return (ret * _interval)/1000.0; 239 | } 240 | 241 | // TODO(adf): currently unused. should we keep it? 242 | int getTotalOps() { 243 | /* return the total number of reqs done by all threads */ 244 | int ret = 0; 245 | for (int i = 0; i < _clients.length; i++) { 246 | ret += _clients[i].getOpsCount(); 247 | } 248 | return ret; 249 | } 250 | 251 | TestType getCurrentTest() { 252 | return _currentTest; 253 | } 254 | 255 | void incrementFinished() { 256 | _finishedTotal.incrementAndGet(); 257 | } 258 | 259 | CyclicBarrier getBarrier() { 260 | return _barrier; 261 | } 262 | 263 | String getData() { 264 | return _data; 265 | } 266 | 267 | int getDeadline() { 268 | return _deadline; 269 | } 270 | 271 | AtomicInteger getCurrentTotalOps() { 272 | return _currentTotalOps; 273 | } 274 | 275 | int getInterval() { 276 | return _interval; 277 | } 278 | 279 | long getStartTime() { 280 | return _startCpuTime; 281 | } 282 | 283 | void notifyFinished(int id) { 284 | synchronized (_running) { 285 | _running.remove(new Integer(id)); 286 | if (_running.size() == 0) { 287 | _finished = true; 288 | _running.notify(); 289 | } 290 | } 291 | } 292 | 293 | private static PropertiesConfiguration initConfiguration(String[] args) { 294 | OptionSet options = null; 295 | OptionParser parser = new OptionParser(); 296 | PropertiesConfiguration conf = null; 297 | 298 | // Setup the option parser 299 | parser.accepts("help", "print this help statement"); 300 | parser.accepts("conf", "configuration file (required)"). 301 | withRequiredArg().ofType(String.class).required(); 302 | parser.accepts("interval", "interval between rate measurements"). 303 | withRequiredArg().ofType(Integer.class); 304 | parser.accepts("ops", "total number of operations"). 305 | withRequiredArg().ofType(Integer.class); 306 | parser.accepts("lbound", 307 | "lowerbound for the number of operations"). 308 | withRequiredArg().ofType(Integer.class); 309 | parser.accepts("time", "time tests will run for (milliseconds)"). 310 | withRequiredArg().ofType(Integer.class); 311 | parser.accepts("sync", "sync or async test"). 312 | withRequiredArg().ofType(Boolean.class); 313 | 314 | // Parse and gather the arguments 315 | try { 316 | options = parser.parse(args); 317 | } catch (OptionException e) { 318 | System.out.println("\nError parsing arguments: " + e.getMessage() + "\n"); 319 | try { 320 | parser.printHelpOn(System.out); 321 | } catch (IOException e2) { 322 | LOG.error("Exception while printing help message", e2); 323 | } 324 | System.exit(-1); 325 | } 326 | 327 | Integer interval = (Integer) options.valueOf("interval"); 328 | Integer totOps = (Integer) options.valueOf("ops"); 329 | Integer lowerbound = (Integer) options.valueOf("lbound"); 330 | Integer time = (Integer) options.valueOf("time"); 331 | Boolean sync = (Boolean) options.valueOf("sync"); 332 | 333 | // Load and parse the configuration file 334 | String configFile = (String) options.valueOf("conf"); 335 | LOG.info("Loading benchmark from configuration file: " + configFile); 336 | 337 | try { 338 | conf = new PropertiesConfiguration(configFile); 339 | } catch (ConfigurationException e) { 340 | LOG.error("Failed to read configuration file: " + configFile, e); 341 | System.exit(-2); 342 | } 343 | 344 | // If there are options from command line, override the conf 345 | if (interval != null) 346 | conf.setProperty("interval", interval); 347 | if (totOps != null) 348 | conf.setProperty("totalOperations", totOps); 349 | if (lowerbound != null) 350 | conf.setProperty("lowerbound", lowerbound); 351 | if (time != null) 352 | conf.setProperty("totalTime", time); 353 | if (sync != null) 354 | conf.setProperty("sync", sync); 355 | 356 | return conf; 357 | } 358 | 359 | public static void main(String[] args) { 360 | 361 | // Parse command line and configuration file 362 | PropertiesConfiguration conf = initConfiguration(args); 363 | 364 | // Helpful info for users of our default log4j configuration 365 | Appender a = Logger.getRootLogger().getAppender("file"); 366 | if (a != null && a instanceof FileAppender) { 367 | FileAppender fa = (FileAppender) a; 368 | System.out.println("Detailed logs going to: " + fa.getFile()); 369 | } 370 | 371 | // Run the benchmark 372 | try { 373 | ZooKeeperBenchmark benchmark = new ZooKeeperBenchmark(conf); 374 | benchmark.runBenchmark(); 375 | } catch (IOException e) { 376 | LOG.error("Failed to start ZooKeeper benchmark", e); 377 | } 378 | 379 | System.exit(0); 380 | } 381 | 382 | class ResubmitTimer extends TimerTask { 383 | @Override 384 | public void run() { 385 | if (_currentTest == TestType.UNDEFINED) { 386 | return; 387 | } 388 | 389 | int finished = _finishedTotal.get(); 390 | if (finished == 0) { 391 | return; 392 | } 393 | 394 | _currentCpuTime = System.nanoTime(); 395 | 396 | if (_rateFile != null) { 397 | try { 398 | if (finished - _lastfinished > 0) { 399 | // Record the time elapsed and current rate 400 | String msg = ((double)(_currentCpuTime - _startCpuTime)/1000000000.0) + " " + 401 | ((double)(finished - _lastfinished) / 402 | ((double)(_currentCpuTime - _lastCpuTime) / 1000000000.0)); 403 | _rateFile.write(msg+"\n"); 404 | } 405 | } catch (IOException e) { 406 | LOG.error("Error when writing to output file", e); 407 | } 408 | } 409 | 410 | _lastCpuTime = _currentCpuTime; 411 | _lastfinished = finished; 412 | 413 | int numRemaining = _currentTotalOps.get() - finished; 414 | 415 | if (numRemaining <= _lowerbound) { 416 | int incr = _totalOps - numRemaining; 417 | 418 | _currentTotalOps.getAndAdd(incr); 419 | int avg = incr / _clients.length; 420 | 421 | for (int i = 0; i < _clients.length; i++) { 422 | _clients[i].resubmit(avg); 423 | } 424 | } 425 | } 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout, file 3 | 4 | # Direct most log messages to a file 5 | log4j.appender.file=org.apache.log4j.FileAppender 6 | log4j.appender.file.File=zk-benchmark.log 7 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n 9 | 10 | # Error & fatal log messages also go to stdout 11 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 12 | log4j.appender.stdout.Target=System.out 13 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 14 | log4j.appender.stdout.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n 15 | log4j.appender.stdout.Threshold=ERROR 16 | 17 | # Set some reasonable defaults 18 | #log4j.logger.edu.brown.cs.zkbenchmark=INFO 19 | log4j.logger.com.netflix.curator=ERROR 20 | log4j.logger.org.apache.zookeeper=ERROR 21 | --------------------------------------------------------------------------------