com.couchbase.roadrunner.RoadRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.couchbase.roadrunner.RoadRunner.java

Source

/**
 * Copyright (C) 2009-2013 Couchbase, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
 * IN THE SOFTWARE.
 */

package com.couchbase.roadrunner;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.HdrHistogram.Histogram;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;

/**
 * The RoadRunner project is a load tester for your Couchbase cluster.
 *
 * With this load tester, you can specify the number for CouchbaseClients and
 * corresponding worker threads to execute a workload against the connected
 * cluster. In addition to producing raw workload, it is able to measure
 * latency and throughput, therefore giving you a tool to measure the
 * performance of your cluster and JVM environment by testing it in isolation.
 * You can use it for both performance and debugging purposes.
 *
 * By default, it connects to localhost, using the "default" bucket with no
 * password. Also, it uses one CouchbaseClient and one worker thread. You can
 * change the behavior by providing custom command line arguments. Use the
 * "-h" flag to see all available options.
 */
public final class RoadRunner {

    public static final String OPT_NODES = "nodes";
    public static final String OPT_BUCKET = "bucket";
    public static final String OPT_PASSWORD = "password";
    public static final String OPT_NUM_THREADS = "num-threads";
    public static final String OPT_NUM_CLIENTS = "num-clients";
    public static final String OPT_NUM_DOCS = "num-docs";
    public static final String OPT_RATIO = "ratio";
    public static final String OPT_WORKLOAD = "workload";
    public static final String OPT_RAMP = "ramp";
    public static final String OPT_HELP = "help";
    public static final String OPT_SAMPLING = "sampling";
    public static final String OPT_DOC_SIZE = "doc-size";
    public static final String OPT_FILENAME = "data-filename";
    /** Configure a reusable logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(RoadRunner.class.getName());

    /** Do not use a public constructor for the main class. */
    private RoadRunner() {
    }

    /**
     * Initialize the RoadRunner.
     *
     * This method is responsible for parsing the passed in command line arguments
     * and also dispatch the bootstrapping of the actual workload runner.
     *
     * @param args Command line arguments to be passed in.
     */
    public static void main(final String[] args) {
        CommandLine params = null;
        try {
            params = parseCommandLine(args);
        } catch (ParseException ex) {
            LOGGER.error("Exception while parsing command line!", ex);
            System.exit(-1);
        }

        if (params.hasOption(OPT_HELP)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("roadrunner", getCommandLineOptions());
            System.exit(0);
        }

        GlobalConfig config = GlobalConfig.fromCommandLine(params);
        WorkloadDispatcher dispatcher = new WorkloadDispatcher(config);

        LOGGER.info("Running with Config: " + config.toString());

        try {
            LOGGER.debug("Initializing ClientHandlers.");
            dispatcher.init();
        } catch (Exception ex) {
            LOGGER.error("Error while initializing the ClientHandlers: ", ex);
            System.exit(-1);
        }

        Stopwatch workloadStopwatch = new Stopwatch().start();
        try {
            LOGGER.info("Running Workload.");
            dispatcher.dispatchWorkload();
        } catch (Exception ex) {
            LOGGER.error("Error while running the Workload: ", ex);
            System.exit(-1);
        }
        workloadStopwatch.stop();

        LOGGER.debug("Finished Workload.");

        LOGGER.info("==== RESULTS ====");

        dispatcher.prepareMeasures();

        long totalOps = dispatcher.getTotalOps();
        long measuredOps = dispatcher.getMeasuredOps();

        LOGGER.info("Operations: measured " + measuredOps + "ops out of total " + totalOps + "ops.");

        Map<String, List<Stopwatch>> measures = dispatcher.getMeasures();
        for (Map.Entry<String, List<Stopwatch>> entry : measures.entrySet()) {
            Histogram h = new Histogram(60 * 60 * 1000, 5);
            for (Stopwatch watch : entry.getValue()) {
                h.recordValue(watch.elapsed(TimeUnit.MICROSECONDS));
            }

            LOGGER.info("Percentile (microseconds) for \"" + entry.getKey() + "\" Workload:");
            LOGGER.info("   50%:" + (Math.round(h.getValueAtPercentile(0.5) * 100) / 100) + "   75%:"
                    + (Math.round(h.getValueAtPercentile(0.75) * 100) / 100) + "   95%:"
                    + (Math.round(h.getValueAtPercentile(0.95) * 100) / 100) + "   99%:"
                    + (Math.round(h.getValueAtPercentile(0.99) * 100) / 100));
        }

        LOGGER.info("Elapsed: " + workloadStopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms");

        List<Stopwatch> elapsedThreads = dispatcher.getThreadElapsed();
        long shortestThread = 0;
        long longestThread = 0;
        for (Stopwatch threadWatch : elapsedThreads) {
            long threadMs = threadWatch.elapsed(TimeUnit.MILLISECONDS);
            if (longestThread == 0 || threadMs > longestThread) {
                longestThread = threadMs;
            }
            if (shortestThread == 0 || threadMs < shortestThread) {
                shortestThread = threadMs;
            }
        }

        LOGGER.info("Shortest Thread: " + shortestThread + "ms");
        LOGGER.info("Longest Thread: " + longestThread + "ms");

    }

    /**
     * Parse the command line.
     *
     * @param args Command line arguments to be passed in.
     * @return The parsed command line options.
     * @throws ParseException Thrown when the command line could not be parsed.
     */
    @VisibleForTesting
    static CommandLine parseCommandLine(final String[] args) throws ParseException {
        CommandLineParser parser = new PosixParser();
        CommandLine params = parser.parse(getCommandLineOptions(), args);
        return params;
    }

    /**
     * Defines the default command line options.
     *
     * @return Supported command line options.
     */
    @VisibleForTesting
    static Options getCommandLineOptions() {
        Options options = new Options();
        options.addOption("n", OPT_NODES, true, "List of nodes to connect, separated with \",\" (default: " + "\""
                + GlobalConfig.DEFAULT_NODES + "\").");
        options.addOption("b", OPT_BUCKET, true,
                "Name of the bucket (default: \"" + GlobalConfig.DEFAULT_BUCKET + "\").");
        options.addOption("p", OPT_PASSWORD, true,
                "Password of the bucket (default: \"" + GlobalConfig.DEFAULT_PASSWORD + "\").");
        options.addOption("t", OPT_NUM_THREADS, true,
                "Number of worker threads per CouchbaseClient object (default: \""
                        + GlobalConfig.DEFAULT_NUM_THREADS + "\").");
        options.addOption("c", OPT_NUM_CLIENTS, true,
                "Number of CouchbaseClient objects (default: \"" + GlobalConfig.DEFAULT_NUM_CLIENTS + "\").");
        options.addOption("d", OPT_NUM_DOCS, true,
                "Number of documents to work with (default: \"" + GlobalConfig.DEFAULT_NUM_DOCS + "\").");
        options.addOption("R", OPT_RATIO, true,
                "Ratio - depending on workload (default: \"" + GlobalConfig.DEFAULT_RATIO + "\").");
        options.addOption("s", OPT_SAMPLING, true,
                "% Sample Rate (default \"" + GlobalConfig.DEFAULT_SAMPLING + "%\")");
        options.addOption("w", OPT_WORKLOAD, true,
                "Workload - name of the workload (default: \"" + GlobalConfig.DEFAULT_WORKLOAD + "\".");
        options.addOption("r", OPT_RAMP, true,
                "Ramp-Up time in seconds - ignored ops (default: \"" + GlobalConfig.DEFAULT_RAMP + "\".");
        options.addOption("S", OPT_DOC_SIZE, true,
                "Document Size in bytes (default \"" + GlobalConfig.DEFAULT_SIZE + "\")");
        options.addOption("h", OPT_HELP, false, "Print this help message.");
        options.addOption("f", OPT_FILENAME, true, "filename containing the data to use in the test");
        return options;
    }
}