You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by xe...@apache.org on 2013/12/24 03:08:42 UTC

[3/6] Improve Stress Tool patch by Benedict; reviewed by Pavel Yaskevich for CASSANDRA-6199

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/server/StressThread.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/server/StressThread.java b/tools/stress/src/org/apache/cassandra/stress/server/StressThread.java
deleted file mode 100644
index 158a09f..0000000
--- a/tools/stress/src/org/apache/cassandra/stress/server/StressThread.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.cassandra.stress.server;
-
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.StressAction;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.PrintStream;
-import java.net.Socket;
-
-public class StressThread extends Thread
-{
-    private final Socket socket;
-
-    public StressThread(Socket client)
-    {
-        this.socket = client;
-    }
-
-    public void run()
-    {
-        try
-        {
-            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
-            PrintStream out = new PrintStream(socket.getOutputStream());
-
-            StressAction action = new StressAction((Session) in.readObject(), out);
-            action.start();
-
-            while (action.isAlive())
-            {
-                try
-                {
-                    if (in.readInt() == 1)
-                    {
-                        action.stopAction();
-                        break;
-                    }
-                }
-                catch (Exception e)
-                {
-                    // continue without problem
-                }
-            }
-
-            out.close();
-            in.close();
-            socket.close();
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e.getMessage(), e);
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java b/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java
new file mode 100644
index 0000000..76c7509
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/CliOption.java
@@ -0,0 +1,58 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CliOption
+{
+    KEY("Key details such as size in bytes and value distribution", SettingsKey.helpPrinter()),
+    COL("Column details such as size and count distribution, data generator, names, comparator and if super columns should be used", SettingsColumn.helpPrinter()),
+    RATE("Thread count, rate limit or automatic mode (default is auto)", SettingsRate.helpPrinter()),
+    MODE("Thrift or CQL with options", SettingsMode.helpPrinter()),
+    SCHEMA("Replication settings, compression, compaction, etc.", SettingsSchema.helpPrinter()),
+    NODE("Nodes to connect to", SettingsNode.helpPrinter()),
+    LOG("Where to log progress to, and the interval at which to do it", SettingsLog.helpPrinter()),
+    TRANSPORT("Custom transport factories", SettingsTransport.helpPrinter()),
+    PORT("The port to connect to cassandra nodes on", SettingsPort.helpPrinter()),
+    SENDTO("-send-to", "Specify a stress server to send this command to", SettingsMisc.sendToDaemonHelpPrinter())
+    ;
+
+    private static final Map<String, CliOption> LOOKUP;
+    static
+    {
+        final Map<String, CliOption> lookup = new HashMap<>();
+        for (CliOption cmd : values())
+        {
+            lookup.put("-" + cmd.toString().toLowerCase(), cmd);
+            if (cmd.extraName != null)
+                lookup.put(cmd.extraName, cmd);
+        }
+        LOOKUP = lookup;
+    }
+
+    public static CliOption get(String command)
+    {
+        return LOOKUP.get(command.toLowerCase());
+    }
+
+    public final String extraName;
+    public final String description;
+    private final Runnable helpPrinter;
+
+    private CliOption(String description, Runnable helpPrinter)
+    {
+        this(null, description, helpPrinter);
+    }
+    private CliOption(String extraName, String description, Runnable helpPrinter)
+    {
+        this.extraName = extraName;
+        this.description = description;
+        this.helpPrinter = helpPrinter;
+    }
+
+    public void printHelp()
+    {
+        helpPrinter.run();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/Command.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/Command.java b/tools/stress/src/org/apache/cassandra/stress/settings/Command.java
new file mode 100644
index 0000000..4bd843e
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/Command.java
@@ -0,0 +1,101 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum Command
+{
+
+    READ(false,
+            SettingsCommand.helpPrinter("read"),
+            "Multiple concurrent reads - the cluster must first be populated by a write test",
+            CommandCategory.BASIC
+    ),
+    WRITE(true,
+            SettingsCommand.helpPrinter("write"),
+            "insert",
+            "Multiple concurrent writes against the cluster",
+            CommandCategory.BASIC
+    ),
+    MIXED(true,
+            SettingsCommandMixed.helpPrinter(),
+            "Interleaving of any basic commands, with configurable ratio and distribution - the cluster must first be populated by a write test",
+            CommandCategory.MIXED
+    ),
+    RANGESLICE(false,
+            SettingsCommandMulti.helpPrinter("range_slice"),
+            "Range slice queries - the cluster must first be populated by a write test",
+            CommandCategory.MULTI
+    ),
+    IRANGESLICE(false,
+            SettingsCommandMulti.helpPrinter("indexed_range_slice"),
+            "Range slice queries through a secondary index. The cluster must first be populated by a write test, with indexing enabled.",
+            CommandCategory.MULTI
+    ),
+    READMULTI(false,
+            SettingsCommandMulti.helpPrinter("readmulti"),
+            "multi_read",
+            "Multiple concurrent reads fetching multiple rows at once. The cluster must first be populated by a write test.",
+            CommandCategory.MULTI
+    ),
+    COUNTERWRITE(true,
+            SettingsCommand.helpPrinter("counteradd"),
+            "counter_add",
+            "Multiple concurrent updates of counters.",
+            CommandCategory.BASIC
+    ),
+    COUNTERREAD(false,
+            SettingsCommand.helpPrinter("counterread"),
+            "counter_get",
+            "Multiple concurrent reads of counters. The cluster must first be populated by a counterwrite test.",
+            CommandCategory.BASIC
+    ),
+
+    HELP(false, SettingsMisc.helpHelpPrinter(), "-?", "Print help for a command or option", null),
+    PRINT(false, SettingsMisc.printHelpPrinter(), "Inspect the output of a distribution definition", null),
+    LEGACY(false, Legacy.helpPrinter(), "Legacy support mode", null)
+
+    ;
+
+    private static final Map<String, Command> LOOKUP;
+    static
+    {
+        final Map<String, Command> lookup = new HashMap<>();
+        for (Command cmd : values())
+        {
+            lookup.put(cmd.toString().toLowerCase(), cmd);
+            if (cmd.extraName != null)
+                lookup.put(cmd.extraName, cmd);
+        }
+        LOOKUP = lookup;
+    }
+
+    public static Command get(String command)
+    {
+        return LOOKUP.get(command.toLowerCase());
+    }
+
+    public final boolean updates;
+    public final CommandCategory category;
+    public final String extraName;
+    public final String description;
+    public final Runnable helpPrinter;
+
+    Command(boolean updates, Runnable helpPrinter, String description, CommandCategory category)
+    {
+        this(updates, helpPrinter, null, description, category);
+    }
+    Command(boolean updates, Runnable helpPrinter, String extra, String description, CommandCategory category)
+    {
+        this.updates = updates;
+        this.category = category;
+        this.helpPrinter = helpPrinter;
+        this.extraName = extra;
+        this.description = description;
+    }
+    public void printHelp()
+    {
+        helpPrinter.run();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/CommandCategory.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/CommandCategory.java b/tools/stress/src/org/apache/cassandra/stress/settings/CommandCategory.java
new file mode 100644
index 0000000..87a13f7
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/CommandCategory.java
@@ -0,0 +1,8 @@
+package org.apache.cassandra.stress.settings;
+
+public enum CommandCategory
+{
+    BASIC,
+    MULTI,
+    MIXED
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionAPI.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionAPI.java b/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionAPI.java
new file mode 100644
index 0000000..c647f66
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionAPI.java
@@ -0,0 +1,7 @@
+package org.apache.cassandra.stress.settings;
+
+public enum ConnectionAPI
+{
+    THRIFT, THRIFT_SMART, SIMPLE_NATIVE, JAVA_DRIVER_NATIVE
+}
+

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionStyle.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionStyle.java b/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionStyle.java
new file mode 100644
index 0000000..6e77f4a
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/ConnectionStyle.java
@@ -0,0 +1,9 @@
+package org.apache.cassandra.stress.settings;
+
+public enum ConnectionStyle
+{
+    CQL,
+    CQL_PREPARED,
+    THRIFT
+}
+

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/CqlVersion.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/CqlVersion.java b/tools/stress/src/org/apache/cassandra/stress/settings/CqlVersion.java
new file mode 100644
index 0000000..853e399
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/CqlVersion.java
@@ -0,0 +1,48 @@
+package org.apache.cassandra.stress.settings;
+
+public enum CqlVersion
+{
+
+    NOCQL(null),
+    CQL2("2.0.0"),
+    CQL3("3.0.0");
+
+    public final String connectVersion;
+
+    private CqlVersion(String connectVersion)
+    {
+        this.connectVersion = connectVersion;
+    }
+
+    static CqlVersion get(String version)
+    {
+        if (version == null)
+            return NOCQL;
+        switch(version.charAt(0))
+        {
+            case '2':
+                return CQL2;
+            case '3':
+                return CQL3;
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    public boolean isCql()
+    {
+        return this != NOCQL;
+    }
+
+    public boolean isCql2()
+    {
+        return this == CQL2;
+    }
+
+    public boolean isCql3()
+    {
+        return this == CQL3;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/GroupedOptions.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/GroupedOptions.java b/tools/stress/src/org/apache/cassandra/stress/settings/GroupedOptions.java
new file mode 100644
index 0000000..fe965c9
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/GroupedOptions.java
@@ -0,0 +1,104 @@
+package org.apache.cassandra.stress.settings;
+
+import java.io.PrintStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class GroupedOptions
+{
+
+    int accepted = 0;
+
+    public boolean accept(String param)
+    {
+        for (Option option : options())
+        {
+            if (option.accept(param))
+            {
+                accepted++;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean happy()
+    {
+        for (Option option : options())
+            if (!option.happy())
+                return false;
+        return true;
+    }
+
+    public abstract List<? extends Option> options();
+
+    // hands the parameters to each of the option groups, and returns the first provided
+    // option group that is happy() after this is done, that also accepted all the parameters
+    public static <G extends GroupedOptions> G select(String[] params, G... groupings)
+    {
+        for (String param : params)
+        {
+            boolean accepted = false;
+            for (GroupedOptions grouping : groupings)
+                accepted |= grouping.accept(param);
+            if (!accepted)
+                throw new IllegalArgumentException("Invalid parameter " + param);
+        }
+        for (G grouping : groupings)
+            if (grouping.happy() && grouping.accepted == params.length)
+                return grouping;
+        return null;
+    }
+
+    // pretty prints all of the option groupings
+    public static void printOptions(PrintStream out, String command, GroupedOptions... groupings)
+    {
+        out.println();
+        boolean firstRow = true;
+        for (GroupedOptions grouping : groupings)
+        {
+            if (!firstRow)
+            {
+                out.println(" OR ");
+            }
+            firstRow = false;
+
+            StringBuilder sb = new StringBuilder("Usage: " + command);
+            for (Option option : grouping.options())
+            {
+                sb.append(" ");
+                sb.append(option.shortDisplay());
+            }
+            out.println(sb.toString());
+        }
+        out.println();
+        final Set<Option> printed = new HashSet<>();
+        for (GroupedOptions grouping : groupings)
+        {
+            for (Option option : grouping.options())
+            {
+                if (printed.add(option))
+                {
+                    if (option.longDisplay() != null)
+                    {
+                        out.println("  " + option.longDisplay());
+                        for (String row : option.multiLineDisplay())
+                            out.println("      " + row);
+                    }
+                }
+            }
+        }
+    }
+
+    public static String formatLong(String longDisplay, String description)
+    {
+        return String.format("%-40s %s", longDisplay, description);
+    }
+
+    public static String formatMultiLine(String longDisplay, String description)
+    {
+        return String.format("%-36s %s", longDisplay, description);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/Legacy.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/Legacy.java b/tools/stress/src/org/apache/cassandra/stress/settings/Legacy.java
new file mode 100644
index 0000000..6242425
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/Legacy.java
@@ -0,0 +1,369 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.stress.settings;
+
+import java.io.Serializable;
+import java.util.*;
+
+import org.apache.commons.cli.*;
+import org.apache.commons.cli.Option;
+
+public class Legacy implements Serializable
+{
+
+    // command line options
+    public static final Options availableOptions = new Options();
+
+    private static final String SSL_TRUSTSTORE = "truststore";
+    private static final String SSL_TRUSTSTORE_PW = "truststore-password";
+    private static final String SSL_PROTOCOL = "ssl-protocol";
+    private static final String SSL_ALGORITHM = "ssl-alg";
+    private static final String SSL_STORE_TYPE = "store-type";
+    private static final String SSL_CIPHER_SUITES = "ssl-ciphers";
+
+    static
+    {
+        availableOptions.addOption("h",  "help",                 false,  "Show this help message and exit");
+        availableOptions.addOption("n",  "num-keys",             true,   "Number of keys, default:1000000");
+        availableOptions.addOption("F",  "num-different-keys",   true,   "Number of different keys (if < NUM-KEYS, the same key will re-used multiple times), default:NUM-KEYS");
+        availableOptions.addOption("t",  "threadCount",              true,   "Number of threadCount to use, default:50");
+        availableOptions.addOption("c",  "columns",              true,   "Number of columns per key, default:5");
+        availableOptions.addOption("S",  "column-size",          true,   "Size of column values in bytes, default:34");
+        availableOptions.addOption("C",  "unique columns",       true,   "Max number of unique columns per key, default:50");
+        availableOptions.addOption("RC", "unique rows",          true,   "Max number of unique rows, default:50");
+        availableOptions.addOption("d",  "nodes",                true,   "Host nodes (comma separated), default:locahost");
+        availableOptions.addOption("D",  "nodesfile",            true,   "File containing host nodes (one per line)");
+        availableOptions.addOption("s",  "stdev",                true,   "Standard Deviation for gaussian read key generation, default:0.1");
+        availableOptions.addOption("r",  "random",               false,  "Use random key generator for read key generation (STDEV will have no effect), default:false");
+        availableOptions.addOption("f",  "file",                 true,   "Write output to given file");
+        availableOptions.addOption("p",  "port",                 true,   "Thrift port, default:9160");
+        availableOptions.addOption("o",  "operation",            true,   "Operation to perform (WRITE, READ, READWRITE, RANGE_SLICE, INDEXED_RANGE_SLICE, MULTI_GET, COUNTERWRITE, COUNTER_GET), default:WRITE");
+        availableOptions.addOption("u",  "supercolumns",         true,   "Number of super columns per key, default:1");
+        availableOptions.addOption("y",  "family-type",          true,   "Column Family Type (Super, Standard), default:Standard");
+        availableOptions.addOption("K",  "keep-trying",          true,   "Retry on-going operation N times (in case of failure). positive integer, default:10");
+        availableOptions.addOption("k",  "keep-going",           false,  "Ignore errors inserting or reading (when set, --keep-trying has no effect), default:false");
+        availableOptions.addOption("i",  "progress-interval",    true,   "Progress Report Interval (seconds), default:10");
+        availableOptions.addOption("g",  "keys-per-call",        true,   "Number of keys to get_range_slices or multiget per call, default:1000");
+        availableOptions.addOption("l",  "replication-factor",   true,   "Replication Factor to use when creating needed column families, default:1");
+        availableOptions.addOption("L",  "enable-cql",           false,  "Perform queries using CQL2 (Cassandra Query Language v 2.0.0)");
+        availableOptions.addOption("L3", "enable-cql3",          false,  "Perform queries using CQL3 (Cassandra Query Language v 3.0.0)");
+        availableOptions.addOption("b",  "enable-native-protocol",  false,  "Use the binary native protocol (only work along with -L3)");
+        availableOptions.addOption("P",  "use-prepared-statements", false, "Perform queries using prepared statements (only applicable to CQL).");
+        availableOptions.addOption("e",  "consistency-level",    true,   "Consistency Level to use (ONE, QUORUM, LOCAL_QUORUM, EACH_QUORUM, ALL, ANY), default:ONE");
+        availableOptions.addOption("x",  "create-index",         true,   "Type of index to create on needed column families (KEYS)");
+        availableOptions.addOption("R",  "replication-strategy", true,   "Replication strategy to use (only on insert if keyspace does not exist), default:org.apache.cassandra.locator.SimpleStrategy");
+        availableOptions.addOption("O",  "strategy-properties",  true,   "Replication strategy properties in the following format <dc_name>:<num>,<dc_name>:<num>,...");
+        availableOptions.addOption("W",  "no-replicate-on-write",false,  "Set replicate_on_write to false for counters. Only counter add with CL=ONE will work");
+        availableOptions.addOption("V",  "average-size-values",  false,  "Generate column values of average rather than specific size");
+        availableOptions.addOption("T",  "send-to",              true,   "Send this as a request to the stress daemon at specified address.");
+        availableOptions.addOption("I",  "compression",          true,   "Specify the compression to use for sstable, default:no compression");
+        availableOptions.addOption("Q",  "query-names",          true,   "Comma-separated list of column names to retrieve from each row.");
+        availableOptions.addOption("Z",  "compaction-strategy",  true,   "CompactionStrategy to use.");
+        availableOptions.addOption("U",  "comparator",           true,   "Column Comparator to use. Currently supported types are: TimeUUIDType, AsciiType, UTF8Type.");
+        availableOptions.addOption("tf", "transport-factory",    true,   "Fully-qualified TTransportFactory class name for creating a connection. Note: For Thrift over SSL, use org.apache.cassandra.stress.SSLTransportFactory.");
+        availableOptions.addOption("ns", "no-statistics",        false,  "Turn off the aggegate statistics that is normally output after completion.");
+        availableOptions.addOption("ts", SSL_TRUSTSTORE,         true, "SSL: full path to truststore");
+        availableOptions.addOption("tspw", SSL_TRUSTSTORE_PW,    true, "SSL: full path to truststore");
+        availableOptions.addOption("prtcl", SSL_PROTOCOL,        true, "SSL: connections protocol to use (default: TLS)");
+        availableOptions.addOption("alg", SSL_ALGORITHM,         true, "SSL: algorithm (default: SunX509)");
+        availableOptions.addOption("st", SSL_STORE_TYPE,         true, "SSL: type of store");
+        availableOptions.addOption("ciphers", SSL_CIPHER_SUITES, true, "SSL: comma-separated list of encryption suites to use");
+        availableOptions.addOption("th",  "throttle",            true,   "Throttle the total number of operations per second to a maximum amount.");
+    }
+
+    public static StressSettings build(String[] arguments)
+    {
+        CommandLineParser parser = new PosixParser();
+
+        final Converter r = new Converter();
+        try
+        {
+            CommandLine cmd = parser.parse(availableOptions, arguments);
+
+            if (cmd.getArgs().length > 0)
+            {
+                System.err.println("Application does not allow arbitrary arguments: " + Arrays.asList(cmd.getArgList()));
+                System.exit(1);
+            }
+
+            if (cmd.hasOption("h"))
+                printHelpMessage();
+
+            if (cmd.hasOption("C"))
+                System.out.println("Ignoring deprecated option -C");
+
+            if (cmd.hasOption("o"))
+                r.setCommand(cmd.getOptionValue("o").toLowerCase());
+            else
+                r.setCommand("insert");
+
+            if (cmd.hasOption("K"))
+                r.add("command", "tries=" + cmd.getOptionValue("K"));
+
+            if (cmd.hasOption("k"))
+            {
+                if (!cmd.hasOption("K"))
+                    r.add("command", "retry=1");
+                r.add("command", "ignore_errors");
+            }
+
+            if (cmd.hasOption("g"))
+                r.add("command", "at-once=" + cmd.getOptionValue("g"));
+
+            if (cmd.hasOption("e"))
+                r.add("command", "cl=" + cmd.getOptionValue("e"));
+
+            String numKeys;
+            if (cmd.hasOption("n"))
+                numKeys = cmd.getOptionValue("n");
+            else
+                numKeys = "1000000";
+            r.add("command", "n=" + numKeys);
+
+            String uniqueKeys;
+            if (cmd.hasOption("F"))
+                uniqueKeys = cmd.getOptionValue("F");
+            else
+                uniqueKeys = numKeys;
+
+            if (r.opts.containsKey("write") || r.opts.containsKey("counterwrite"))
+            {
+                if (!uniqueKeys.equals(numKeys))
+                    r.add("-key", "populate=1.." + uniqueKeys);
+            }
+            else if (cmd.hasOption("r"))
+            {
+                r.add("-key", "dist=uniform(1.." + uniqueKeys + ")");
+            }
+            else
+            {
+                if (!cmd.hasOption("s"))
+                    r.add("-key", "dist=gauss(1.." + uniqueKeys + ",5)");
+                else
+                    r.add("-key", String.format("dist=gauss(1..%s,%.2f)", uniqueKeys,
+                            0.5 / Float.parseFloat(cmd.getOptionValue("s"))));
+            }
+
+            String colCount;
+            if (cmd.hasOption("c"))
+                colCount = cmd.getOptionValue("c");
+            else
+                colCount = "5";
+
+            String colSize;
+            if (cmd.hasOption("S"))
+                colSize = cmd.getOptionValue("S");
+            else
+                colSize = "34";
+
+            r.add("-col", "n=fixed(" + colCount + ")");
+            if (cmd.hasOption("V"))
+            {
+                r.add("-col", "size=uniform(1.." + Integer.parseInt(colSize) * 2 + ")");
+                r.add("-col", "data=rand()");
+            }
+            else
+            {
+                r.add("-col", "size=fixed(" + colSize + ")");
+                r.add("-col", "data=repeat(1)");
+            }
+            if (cmd.hasOption("Q"))
+                r.add("-col", "names=" + cmd.getOptionValue("Q"));
+
+            if (cmd.hasOption("U"))
+                r.add("-col", "comparator=" + cmd.getOptionValue("U"));
+
+            if (cmd.hasOption("y") && cmd.getOptionValue("y").equals("Super"))
+                r.add("-col", "super=" + (cmd.hasOption("u") ? cmd.getOptionValue("u") : "1"));
+
+            if (cmd.hasOption("t"))
+                r.add("-rate", "threads=" + cmd.getOptionValue("t"));
+            else
+                r.add("-rate", "threads=50");
+
+            if (cmd.hasOption("th"))
+                r.add("-rate", "limit=" + cmd.getOptionValue("th") + "/s");
+
+            if (cmd.hasOption("f"))
+                r.add("-log", "file=" + cmd.getOptionValue("f"));
+
+            if (cmd.hasOption("p"))
+                r.add("-port", cmd.getOptionValue("p"));
+
+            if (cmd.hasOption("i"))
+                r.add("-log", "interval=" + cmd.getOptionValue("i"));
+            else
+                r.add("-log", "interval=10");
+
+            if (cmd.hasOption("x"))
+                r.add("-schema", "index=" + cmd.getOptionValue("x"));
+
+            if (cmd.hasOption("R") || cmd.hasOption("l") || cmd.hasOption("O"))
+            {
+                StringBuilder rep = new StringBuilder();
+                if (cmd.hasOption("R"))
+                    rep.append("strategy=" + cmd.getOptionValue("R"));
+                if (cmd.hasOption("l"))
+                {
+                    if (rep.length() > 0)
+                        rep.append(",");
+                    rep.append("factor=" + cmd.getOptionValue("l"));
+                }
+                if (cmd.hasOption("O"))
+                {
+                    if (rep.length() > 0)
+                        rep.append(",");
+                    rep.append(cmd.getOptionValue("O").replace(':','='));
+                }
+                r.add("-schema", "replication(" + rep + ")");
+            }
+
+            if (cmd.hasOption("L"))
+                r.add("-mode", cmd.hasOption("P") ? "prepared cql2" : "cql2");
+            else if (cmd.hasOption("L3"))
+                r.add("-mode", (cmd.hasOption("P") ? "prepared" : "") + (cmd.hasOption("b") ? "native" : "") +  "cql3");
+            else
+                r.add("-mode", "thrift");
+
+            if (cmd.hasOption("W"))
+                r.add("-schema", "no-replicate-on-write");
+
+            if (cmd.hasOption("I"))
+                r.add("-schema", "compression=" + cmd.getOptionValue("I"));
+
+            if (cmd.hasOption("d"))
+                r.add("-node", cmd.getOptionValue("d"));
+
+            if (cmd.hasOption("D"))
+                r.add("-node", "file=" + cmd.getOptionValue("D"));
+
+
+            if (cmd.hasOption("send-to"))
+                r.add("-send-to", cmd.getOptionValue("send-to"));
+
+            if (cmd.hasOption("Z"))
+                r.add("-schema", "compaction=" + cmd.getOptionValue("Z"));
+
+            if (cmd.hasOption("ns"))
+                r.add("-log", "no-summary");
+
+            if (cmd.hasOption("tf"))
+                r.add("-transport", "factory=" + cmd.getOptionValue("tf"));
+
+            // THESE DON'T SEEM TO AFFECT PROGRAM BEHAVIOUR
+//            if(cmd.hasOption(SSL_TRUSTSTORE))
+//                encOptions.truststore = cmd.getOptionValue(SSL_TRUSTSTORE);
+//
+//            if(cmd.hasOption(SSL_TRUSTSTORE_PW))
+//                encOptions.truststore_password = cmd.getOptionValue(SSL_TRUSTSTORE_PW);
+//
+//            if(cmd.hasOption(SSL_PROTOCOL))
+//                encOptions.protocol = cmd.getOptionValue(SSL_PROTOCOL);
+//
+//            if(cmd.hasOption(SSL_ALGORITHM))
+//                encOptions.algorithm = cmd.getOptionValue(SSL_ALGORITHM);
+//
+//            if(cmd.hasOption(SSL_STORE_TYPE))
+//                encOptions.store_type = cmd.getOptionValue(SSL_STORE_TYPE);
+//
+//            if(cmd.hasOption(SSL_CIPHER_SUITES))
+//                encOptions.cipher_suites = cmd.getOptionValue(SSL_CIPHER_SUITES).split(",");
+
+        }
+        catch (ParseException e)
+        {
+            printHelpMessage();
+            System.exit(1);
+        }
+
+        r.printNewCommand();
+        return r.get();
+    }
+
+    private static final class Converter
+    {
+        private Map<String, List<String>> opts = new LinkedHashMap<>();
+        List<String> command;
+        public void add(String option, String suboption)
+        {
+            if (option.equals("command"))
+            {
+                command.add(suboption);
+                return;
+            }
+            List<String> params = opts.get(option);
+            if (params == null)
+                opts.put(option, params = new ArrayList());
+            params.add(suboption);
+        }
+        StressSettings get(){
+            Map<String, String[]> clArgs = new HashMap<>();
+            for (Map.Entry<String, List<String>> e : opts.entrySet())
+                clArgs .put(e.getKey(), e.getValue().toArray(new String[0]));
+            return StressSettings.get(clArgs);
+        }
+        void setCommand(String command)
+        {
+            command = Command.get(command).toString().toLowerCase();
+            opts.put(command, this.command = new ArrayList<>());
+        }
+        void printNewCommand()
+        {
+            StringBuilder sb = new StringBuilder("stress");
+            for (Map.Entry<String, List<String>> e : opts.entrySet())
+            {
+                sb.append(" ");
+                sb.append(e.getKey());
+                for (String opt : e.getValue())
+                {
+                    sb.append(" ");
+                    sb.append(opt);
+                }
+            }
+            System.out.println("Running in legacy support mode. Translating command to: ");
+            System.out.println(sb.toString());
+        }
+    }
+
+    public static void printHelpMessage()
+    {
+        System.out.println("Usage: ./bin/cassandra-stress legacy [options]\n\nOptions:");
+        System.out.println("THIS IS A LEGACY SUPPORT MODE");
+
+        for(Object o : availableOptions.getOptions())
+        {
+            Option option = (Option) o;
+            String upperCaseName = option.getLongOpt().toUpperCase();
+            System.out.println(String.format("-%s%s, --%s%s%n\t\t%s%n", option.getOpt(), (option.hasArg()) ? " "+upperCaseName : "",
+                    option.getLongOpt(), (option.hasArg()) ? "="+upperCaseName : "", option.getDescription()));
+        }
+    }
+
+    public static Runnable helpPrinter()
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                printHelpMessage();
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/Option.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/Option.java b/tools/stress/src/org/apache/cassandra/stress/settings/Option.java
new file mode 100644
index 0000000..dfd0857
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/Option.java
@@ -0,0 +1,24 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.List;
+
+abstract class Option
+{
+
+    abstract boolean accept(String param);
+    abstract boolean happy();
+    abstract String shortDisplay();
+    abstract String longDisplay();
+    abstract List<String> multiLineDisplay();
+
+    public int hashCode()
+    {
+        return getClass().hashCode();
+    }
+
+    public boolean equals(Object that)
+    {
+        return this.getClass() == that.getClass();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/OptionDataGen.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionDataGen.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionDataGen.java
new file mode 100644
index 0000000..7f4e5ea
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionDataGen.java
@@ -0,0 +1,177 @@
+package org.apache.cassandra.stress.settings;
+
+import java.io.File;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.cassandra.stress.generatedata.*;
+
+/**
+ * For selecting a data generator
+ */
+class OptionDataGen extends Option
+{
+
+    private static final Pattern FULL = Pattern.compile("([A-Z]+)\\(([^)]+)\\)", Pattern.CASE_INSENSITIVE);
+    private static final Pattern ARGS = Pattern.compile("[^,]+");
+
+    final String prefix;
+    private DataGenFactory factory;
+    private final DataGenFactory defaultFactory;
+
+    public OptionDataGen(String prefix, String defaultSpec)
+    {
+        this.prefix = prefix;
+        this.defaultFactory = defaultSpec == null ? null : get(defaultSpec);
+    }
+
+    @Override
+    public boolean accept(String param)
+    {
+        if (!param.toLowerCase().startsWith(prefix))
+            return false;
+        factory = get(param.substring(prefix.length()));
+        return true;
+    }
+
+    private static DataGenFactory get(String spec)
+    {
+        Matcher m = FULL.matcher(spec);
+        if (!m.matches())
+            throw new IllegalArgumentException("Illegal data generator specification: " + spec);
+        String name = m.group(1);
+        Impl impl = LOOKUP.get(name.toLowerCase());
+        if (impl == null)
+            throw new IllegalArgumentException("Illegal data generator type: " + name);
+        List<String> params = new ArrayList<>();
+        m = ARGS.matcher(m.group(2));
+        while (m.find())
+            params.add(m.group());
+        return impl.getFactory(params);
+    }
+
+    public DataGenFactory get()
+    {
+        return factory != null ? factory : defaultFactory;
+    }
+
+    @Override
+    public boolean happy()
+    {
+        return factory != null || defaultFactory != null;
+    }
+
+    @Override
+    public String shortDisplay()
+    {
+        return prefix + "ALG()";
+    }
+
+    public String longDisplay()
+    {
+        return shortDisplay() + ": Specify a data generator from:";
+    }
+
+    @Override
+    public List<String> multiLineDisplay()
+    {
+        return Arrays.asList(
+                GroupedOptions.formatMultiLine("RANDOM()", "Completely random byte generation"),
+                GroupedOptions.formatMultiLine("REPEAT(<freq>)", "An MD5 hash of (opIndex % freq) combined with the column index"),
+                GroupedOptions.formatMultiLine("DICT(<file>)","Random words from a dictionary; the file should be in the format \"<freq> <word>\"")
+        );
+    }
+
+    private static final Map<String, Impl> LOOKUP;
+    static
+    {
+        final Map<String, Impl> lookup = new HashMap<>();
+        lookup.put("random", new RandomImpl());
+        lookup.put("rand", new RandomImpl());
+        lookup.put("rnd", new RandomImpl());
+        lookup.put("repeat", new RepeatImpl());
+        lookup.put("dict", new DictionaryImpl());
+        lookup.put("dictionary", new DictionaryImpl());
+        LOOKUP = lookup;
+    }
+
+    private static interface Impl
+    {
+        public DataGenFactory getFactory(List<String> params);
+    }
+
+    private static final class RandomImpl implements Impl
+    {
+        @Override
+        public DataGenFactory getFactory(List<String> params)
+        {
+            if (params.size() != 0)
+                throw new IllegalArgumentException("Invalid parameter list for random generator: " + params);
+            return new RandomFactory();
+        }
+    }
+
+    private static final class RepeatImpl implements Impl
+    {
+
+        @Override
+        public DataGenFactory getFactory(List<String> params)
+        {
+            if (params.size() != 1)
+                throw new IllegalArgumentException("Invalid parameter list for repeating generator: " + params);
+            try
+            {
+                int repeatFrequency = Integer.parseInt(params.get(0));
+                return new RepeatsFactory(repeatFrequency);
+            } catch (Exception _)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for repeating generator: " + params);
+            }
+        }
+    }
+
+    private static final class DictionaryImpl implements Impl
+    {
+
+        @Override
+        public DataGenFactory getFactory(List<String> params)
+        {
+            if (params.size() != 1)
+                throw new IllegalArgumentException("Invalid parameter list for dictionary generator: " + params);
+            try
+            {
+                final File file = new File(params.get(0));
+                return DataGenStringDictionary.getFactory(file);
+            } catch (Exception e)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for dictionary generator: " + params, e);
+            }
+        }
+    }
+
+    private static final class RandomFactory implements DataGenFactory
+    {
+        @Override
+        public DataGen get()
+        {
+            return new DataGenBytesRandom();
+        }
+    }
+
+    private static final class RepeatsFactory implements DataGenFactory
+    {
+        final int frequency;
+        private RepeatsFactory(int frequency)
+        {
+            this.frequency = frequency;
+        }
+
+        @Override
+        public DataGen get()
+        {
+            return new DataGenStringRepeats(frequency);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/OptionDistribution.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionDistribution.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionDistribution.java
new file mode 100644
index 0000000..749c797
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionDistribution.java
@@ -0,0 +1,340 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.cassandra.stress.generatedata.*;
+import org.apache.commons.math3.distribution.ExponentialDistribution;
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+import org.apache.commons.math3.distribution.WeibullDistribution;
+
+/**
+ * For selecting a mathematical distribution
+ */
+class OptionDistribution extends Option
+{
+
+    private static final Pattern FULL = Pattern.compile("([A-Z]+)\\((.+)\\)", Pattern.CASE_INSENSITIVE);
+    private static final Pattern ARGS = Pattern.compile("[^,]+");
+
+    final String prefix;
+    private String spec;
+    private final String defaultSpec;
+
+    public OptionDistribution(String prefix, String defaultSpec)
+    {
+        this.prefix = prefix;
+        this.defaultSpec = defaultSpec;
+    }
+
+    @Override
+    public boolean accept(String param)
+    {
+        if (!param.toLowerCase().startsWith(prefix))
+            return false;
+        spec = param.substring(prefix.length());
+        return true;
+    }
+
+    private static DistributionFactory get(String spec)
+    {
+        Matcher m = FULL.matcher(spec);
+        if (!m.matches())
+            throw new IllegalArgumentException("Illegal distribution specification: " + spec);
+        String name = m.group(1);
+        Impl impl = LOOKUP.get(name.toLowerCase());
+        if (impl == null)
+            throw new IllegalArgumentException("Illegal distribution type: " + name);
+        List<String> params = new ArrayList<>();
+        m = ARGS.matcher(m.group(2));
+        while (m.find())
+            params.add(m.group());
+        return impl.getFactory(params);
+    }
+
+    public DistributionFactory get()
+    {
+        return spec != null ? get(spec) : get(defaultSpec);
+    }
+
+    @Override
+    public boolean happy()
+    {
+        return spec != null || defaultSpec != null;
+    }
+
+    public String longDisplay()
+    {
+        return shortDisplay() + ": Specify a mathematical distribution";
+    }
+
+    @Override
+    public List<String> multiLineDisplay()
+    {
+        return Arrays.asList(
+                GroupedOptions.formatMultiLine("EXP(min..max)", "An exponential distribution over the range [min..max]"),
+                GroupedOptions.formatMultiLine("EXTREME(min..max,shape)", "An extreme value (Weibull) distribution over the range [min..max]"),
+                GroupedOptions.formatMultiLine("GAUSSIAN(min..max,stdvrng)", "A gaussian/normal distribution, where mean=(min+max)/2, and stdev is (mean-min)/stdvrng"),
+                GroupedOptions.formatMultiLine("GAUSSIAN(min..max,mean,stdev)", "A gaussian/normal distribution, with explicitly defined mean and stdev"),
+                GroupedOptions.formatMultiLine("UNIFORM(min..max)", "A uniform distribution over the range [min, max]"),
+                GroupedOptions.formatMultiLine("FIXED(val)", "A fixed distribution, always returning the same value"),
+                "Aliases: extr, gauss, normal, norm, weibull"
+        );
+    }
+
+    @Override
+    public String shortDisplay()
+    {
+        return prefix + "DIST(?)";
+    }
+
+    private static final Map<String, Impl> LOOKUP;
+    static
+    {
+        final Map<String, Impl> lookup = new HashMap<>();
+        lookup.put("exp", new ExponentialImpl());
+        lookup.put("extr", new ExtremeImpl());
+        lookup.put("extreme", lookup.get("extreme"));
+        lookup.put("weibull", lookup.get("weibull"));
+        lookup.put("gaussian", new GaussianImpl());
+        lookup.put("normal", lookup.get("gaussian"));
+        lookup.put("gauss", lookup.get("gaussian"));
+        lookup.put("norm", lookup.get("gaussian"));
+        lookup.put("uniform", new UniformImpl());
+        lookup.put("fixed", new FixedImpl());
+        LOOKUP = lookup;
+    }
+
+    // factory builders
+
+    private static interface Impl
+    {
+        public DistributionFactory getFactory(List<String> params);
+    }
+
+    private static final class GaussianImpl implements Impl
+    {
+
+        @Override
+        public DistributionFactory getFactory(List<String> params)
+        {
+            if (params.size() > 3 || params.size() < 1)
+                throw new IllegalArgumentException("Invalid parameter list for gaussian distribution: " + params);
+            try
+            {
+                String[] bounds = params.get(0).split("\\.\\.+");
+                final long min = Long.parseLong(bounds[0]);
+                final long max = Long.parseLong(bounds[1]);
+                final double mean, stdev;
+                if (params.size() == 3)
+                {
+                    mean = Double.parseDouble(params.get(1));
+                    stdev = Double.parseDouble(params.get(2));
+                }
+                else
+                {
+                    final double stdevsToEdge = params.size() == 1 ? 3d : Double.parseDouble(params.get(1));
+                    mean = (min + max) / 2d;
+                    stdev = ((max - min) / 2d) / stdevsToEdge;
+                }
+                return new GaussianFactory(min, max, mean, stdev);
+            } catch (Exception _)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
+            }
+        }
+    }
+
+    private static final class ExponentialImpl implements Impl
+    {
+        @Override
+        public DistributionFactory getFactory(List<String> params)
+        {
+            if (params.size() != 1)
+                throw new IllegalArgumentException("Invalid parameter list for gaussian distribution: " + params);
+            try
+            {
+                String[] bounds = params.get(0).split("\\.\\.+");
+                final long min = Long.parseLong(bounds[0]);
+                final long max = Long.parseLong(bounds[1]);
+                ExponentialDistribution findBounds = new ExponentialDistribution(1d);
+                // max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
+                // over entire range, but this results in overly skewed distribution, so take sqrt
+                final double mean = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
+                return new ExpFactory(min, max, mean);
+            } catch (Exception _)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
+            }
+        }
+    }
+
+    private static final class ExtremeImpl implements Impl
+    {
+        @Override
+        public DistributionFactory getFactory(List<String> params)
+        {
+            if (params.size() != 2)
+                throw new IllegalArgumentException("Invalid parameter list for extreme (Weibull) distribution: " + params);
+            try
+            {
+                String[] bounds = params.get(0).split("\\.\\.+");
+                final long min = Long.parseLong(bounds[0]);
+                final long max = Long.parseLong(bounds[1]);
+                final double shape = Double.parseDouble(params.get(1));
+                WeibullDistribution findBounds = new WeibullDistribution(shape, 1d);
+                // max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
+                // over entire range, but this results in overly skewed distribution, so take sqrt
+                final double scale = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
+                return new ExtremeFactory(min, max, shape, scale);
+            } catch (Exception _)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for extreme (Weibull) distribution: " + params);
+            }
+        }
+    }
+
+    private static final class UniformImpl implements Impl
+    {
+
+        @Override
+        public DistributionFactory getFactory(List<String> params)
+        {
+            if (params.size() != 1)
+                throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
+            try
+            {
+                String[] bounds = params.get(0).split("\\.\\.+");
+                final long min = Long.parseLong(bounds[0]);
+                final long max = Long.parseLong(bounds[1]);
+                return new UniformFactory(min, max);
+            } catch (Exception _)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
+            }
+        }
+    }
+
+    private static final class FixedImpl implements Impl
+    {
+
+        @Override
+        public DistributionFactory getFactory(List<String> params)
+        {
+            if (params.size() != 1)
+                throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
+            try
+            {
+                final long key = Long.parseLong(params.get(0));
+                return new FixedFactory(key);
+            } catch (Exception _)
+            {
+                throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
+            }
+        }
+    }
+
+    // factories
+
+    private static final class ExpFactory implements DistributionFactory
+    {
+        final long min, max;
+        final double mean;
+        private ExpFactory(long min, long max, double mean)
+        {
+            this.min = min;
+            this.max = max;
+            this.mean = mean;
+        }
+
+        @Override
+        public Distribution get()
+        {
+            return new DistributionOffsetApache(new ExponentialDistribution(mean), min, max);
+        }
+    }
+
+    private static final class ExtremeFactory implements DistributionFactory
+    {
+        final long min, max;
+        final double shape, scale;
+        private ExtremeFactory(long min, long max, double shape, double scale)
+        {
+            this.min = min;
+            this.max = max;
+            this.shape = shape;
+            this.scale = scale;
+        }
+
+        @Override
+        public Distribution get()
+        {
+            return new DistributionOffsetApache(new WeibullDistribution(shape, scale), min, max);
+        }
+    }
+
+    private static final class GaussianFactory implements DistributionFactory
+    {
+        final long min, max;
+        final double mean, stdev;
+        private GaussianFactory(long min, long max, double mean, double stdev)
+        {
+            this.min = min;
+            this.max = max;
+            this.stdev = stdev;
+            this.mean = mean;
+        }
+
+        @Override
+        public Distribution get()
+        {
+            return new DistributionBoundApache(new NormalDistribution(mean, stdev), min, max);
+        }
+    }
+
+    private static final class UniformFactory implements DistributionFactory
+    {
+        final long min, max;
+        private UniformFactory(long min, long max)
+        {
+            this.min = min;
+            this.max = max;
+        }
+
+        @Override
+        public Distribution get()
+        {
+            return new DistributionBoundApache(new UniformRealDistribution(min, max), min, max);
+        }
+    }
+
+    private static final class FixedFactory implements DistributionFactory
+    {
+        final long key;
+        private FixedFactory(long key)
+        {
+            this.key = key;
+        }
+
+        @Override
+        public Distribution get()
+        {
+            return new DistributionFixed(key);
+        }
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return prefix.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object that)
+    {
+        return super.equals(that) && ((OptionDistribution) that).prefix.equals(this.prefix);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/OptionMulti.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionMulti.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionMulti.java
new file mode 100644
index 0000000..9b92462
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionMulti.java
@@ -0,0 +1,107 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * For specifying multiple grouped sub-options in the form: group(arg1=,arg2,arg3) etc.
+ */
+abstract class OptionMulti extends Option
+{
+
+    private static final Pattern ARGS = Pattern.compile("([^,]+)", Pattern.CASE_INSENSITIVE);
+
+    private final class Delegate extends GroupedOptions
+    {
+        @Override
+        public List<? extends Option> options()
+        {
+            return OptionMulti.this.options();
+        }
+    }
+
+    protected abstract List<? extends Option> options();
+
+    private final String name;
+    private final Pattern pattern;
+    private final String description;
+    private final Delegate delegate = new Delegate();
+    public OptionMulti(String name, String description)
+    {
+        this.name = name;
+        pattern = Pattern.compile(name + "\\((.*)\\)", Pattern.CASE_INSENSITIVE);
+        this.description = description;
+    }
+
+    @Override
+    public boolean accept(String param)
+    {
+        Matcher m = pattern.matcher(param);
+        if (!m.matches())
+            return false;
+        m = ARGS.matcher(m.group(1));
+        int last = -1;
+        while (m.find())
+        {
+            if (m.start() != last + 1)
+                throw new IllegalArgumentException("Invalid " + name + " specification: " + param);
+            last = m.end();
+            if (!delegate.accept(m.group()))
+                throw new IllegalArgumentException("Invalid " + name + " specification: " + m.group());
+        }
+        return true;
+    }
+
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(name);
+        sb.append("(");
+        for (Option option : options())
+        {
+            sb.append(option);
+            sb.append(",");
+        }
+        sb.append(")");
+        return sb.toString();
+    }
+
+    @Override
+    public String shortDisplay()
+    {
+        return name + "(?)";
+    }
+
+    @Override
+    public String longDisplay()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(name);
+        sb.append("(");
+        for (Option opt : options())
+        {
+            sb.append(opt.shortDisplay());
+        }
+        sb.append("): ");
+        sb.append(description);
+        return sb.toString();
+    }
+
+    @Override
+    public List<String> multiLineDisplay()
+    {
+        final List<String> r = new ArrayList<>();
+        for (Option option : options())
+            r.add(option.longDisplay());
+        return r;
+    }
+
+    @Override
+    boolean happy()
+    {
+        return delegate.happy();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java
new file mode 100644
index 0000000..b145de4
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionReplication.java
@@ -0,0 +1,114 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.cassandra.locator.AbstractReplicationStrategy;
+
+/**
+ * For specifying replication options
+ */
+class OptionReplication extends Option
+{
+
+    private static final Pattern FULL = Pattern.compile("replication\\((.*)\\)", Pattern.CASE_INSENSITIVE);
+    private static final Pattern OPTION = Pattern.compile("([^,=]+)=([^,]+)", Pattern.CASE_INSENSITIVE);
+
+    private String strategy = "org.apache.cassandra.locator.SimpleStrategy";
+    private Map<String, String> options = new HashMap<>();
+
+    public String getStrategy()
+    {
+        return strategy;
+    }
+
+    public Map<String, String> getOptions()
+    {
+        if (!options.containsKey("replication_factor") && strategy.endsWith("SimpleStrategy"))
+            options.put("replication_factor", "1");
+        return options;
+    }
+
+
+    @Override
+    public boolean accept(String param)
+    {
+        Matcher m = FULL.matcher(param);
+        if (!m.matches())
+            return false;
+        String args = m.group(1);
+        m = OPTION.matcher(args);
+        int last = -1;
+        while (m.find())
+        {
+            if (m.start() != last + 1)
+                throw new IllegalArgumentException("Invalid replication specification: " + param);
+            last = m.end();
+            String key = m.group(1).toLowerCase();
+            sw: switch(key)
+            {
+                case "factor":
+                    try
+                    {
+                        Integer.parseInt(m.group(2));
+                    } catch (NumberFormatException e)
+                    {
+                        throw new IllegalArgumentException("Invalid replication factor: " + param);
+                    }
+                    options.put("replication_factor", m.group(2));
+                    break;
+                case "strategy":
+                    for (String name : new String[] { m.group(2), "org.apache.cassandra.locator." + m.group(2) })
+                    {
+                        try
+                        {
+                            Class<?> clazz = Class.forName(name);
+                            if (!AbstractReplicationStrategy.class.isAssignableFrom(clazz))
+                                throw new RuntimeException();
+                            strategy = name;
+                            break sw;
+                        } catch (Exception _)
+                        {
+                        }
+                    }
+                    throw new IllegalArgumentException("Invalid replication strategy: " + param);
+                default:
+
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean happy()
+    {
+        return true;
+    }
+
+    @Override
+    public String shortDisplay()
+    {
+        return "replication(?)";
+    }
+
+    @Override
+    public String longDisplay()
+    {
+        return "replication(factor=?,strategy=?,<option1>=?,...)";
+    }
+
+    @Override
+    public List<String> multiLineDisplay()
+    {
+        return Arrays.asList(
+                GroupedOptions.formatMultiLine("factor=?","The replication factor to use (default 1)"),
+                GroupedOptions.formatMultiLine("strategy=?","The replication strategy to use (default SimpleStrategy)"),
+                GroupedOptions.formatMultiLine("option=?","Arbitrary replication strategy options")
+        );
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/OptionSimple.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionSimple.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionSimple.java
new file mode 100644
index 0000000..01e75b5
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionSimple.java
@@ -0,0 +1,131 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * For parsing a simple (sub)option for a command/major option
+ */
+class OptionSimple extends Option
+{
+
+    final String displayPrefix;
+    final Pattern matchPrefix;
+    final String defaultValue;
+    final Pattern pattern;
+    final String description;
+    final boolean required;
+    String value;
+
+    public OptionSimple(String prefix, String valuePattern, String defaultValue, String description, boolean required)
+    {
+        this.displayPrefix = prefix;
+        this.matchPrefix = Pattern.compile(Pattern.quote(prefix), Pattern.CASE_INSENSITIVE);
+        this.pattern = Pattern.compile(valuePattern, Pattern.CASE_INSENSITIVE);
+        this.defaultValue = defaultValue;
+        this.description = description;
+        this.required = required;
+    }
+
+    public OptionSimple(String displayPrefix, Pattern matchPrefix, Pattern valuePattern, String defaultValue, String description, boolean required)
+    {
+        this.displayPrefix = displayPrefix;
+        this.matchPrefix = matchPrefix;
+        this.pattern = valuePattern;
+        this.defaultValue = defaultValue;
+        this.description = description;
+        this.required = required;
+    }
+
+    public boolean setByUser()
+    {
+        return value != null;
+    }
+
+    public boolean present()
+    {
+        return value != null || defaultValue != null;
+    }
+
+    public String value()
+    {
+        return value != null ? value : defaultValue;
+    }
+
+    public boolean accept(String param)
+    {
+        if (matchPrefix.matcher(param).lookingAt())
+        {
+            if (value != null)
+                throw new IllegalArgumentException("Suboption " + displayPrefix + " has been specified more than once");
+            String v = param.substring(displayPrefix.length());
+            if (!pattern.matcher(v).matches())
+                throw new IllegalArgumentException("Invalid option " + param + "; must match pattern " + pattern);
+            value = v;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean happy()
+    {
+        return !required || value != null;
+    }
+
+    public String shortDisplay()
+    {
+        StringBuilder sb = new StringBuilder();
+        if (!required)
+            sb.append("[");
+        sb.append(displayPrefix);
+        if (displayPrefix.endsWith("="))
+            sb.append("?");
+        if (displayPrefix.endsWith("<"))
+            sb.append("?");
+        if (displayPrefix.endsWith(">"))
+            sb.append("?");
+        if (!required)
+            sb.append("]");
+        return sb.toString();
+    }
+
+    public String longDisplay()
+    {
+        if (description.equals("") && defaultValue == null && pattern.pattern().equals(""))
+            return null;
+        StringBuilder sb = new StringBuilder();
+        sb.append(displayPrefix);
+        if (displayPrefix.endsWith("="))
+            sb.append("?");
+        if (displayPrefix.endsWith("<"))
+            sb.append("?");
+        if (displayPrefix.endsWith(">"))
+            sb.append("?");
+        if (defaultValue != null)
+        {
+            sb.append(" (default=");
+            sb.append(defaultValue);
+            sb.append(")");
+        }
+        return GroupedOptions.formatLong(sb.toString(), description);
+    }
+
+    public List<String> multiLineDisplay()
+    {
+        return Collections.emptyList();
+    }
+
+    public int hashCode()
+    {
+        return displayPrefix.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object that)
+    {
+        return that instanceof OptionSimple && ((OptionSimple) that).displayPrefix.equals(this.displayPrefix);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/SettingsColumn.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsColumn.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsColumn.java
new file mode 100644
index 0000000..b3cca10
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsColumn.java
@@ -0,0 +1,176 @@
+package org.apache.cassandra.stress.settings;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cassandra.db.marshal.*;
+import org.apache.cassandra.stress.generatedata.*;
+
+/**
+ * For parsing column options
+ */
+public class SettingsColumn implements Serializable
+{
+
+    public final int maxColumnsPerKey;
+    public final List<ByteBuffer> names;
+    public final String comparator;
+    public final boolean useTimeUUIDComparator;
+    public final int superColumns;
+    public final boolean useSuperColumns;
+    public final boolean variableColumnCount;
+
+    private final DistributionFactory sizeDistribution;
+    private final DistributionFactory countDistribution;
+    private final DataGenFactory dataGenFactory;
+
+    public SettingsColumn(GroupedOptions options)
+    {
+        this((Options) options,
+                options instanceof NameOptions ? (NameOptions) options : null,
+                options instanceof CountOptions ? (CountOptions) options : null
+        );
+    }
+
+    public SettingsColumn(Options options, NameOptions name, CountOptions count)
+    {
+        sizeDistribution = options.size.get();
+        superColumns = Integer.parseInt(options.superColumns.value());
+        dataGenFactory = options.generator.get();
+        useSuperColumns = superColumns > 0;
+        {
+            comparator = options.comparator.value();
+            AbstractType parsed = null;
+
+            try
+            {
+                parsed = TypeParser.parse(comparator);
+            }
+            catch (Exception e)
+            {
+                System.err.println(e.getMessage());
+                System.exit(1);
+            }
+
+            useTimeUUIDComparator = parsed instanceof TimeUUIDType;
+
+            if (!(parsed instanceof TimeUUIDType || parsed instanceof AsciiType || parsed instanceof UTF8Type))
+            {
+                System.err.println("Currently supported types are: TimeUUIDType, AsciiType, UTF8Type.");
+                System.exit(1);
+            }
+        }
+        if (name != null)
+        {
+            assert count == null;
+
+            AbstractType comparator;
+            try
+            {
+                comparator = TypeParser.parse(this.comparator);
+            } catch (Exception e)
+            {
+                throw new IllegalStateException(e);
+            }
+
+            final String[] names = name.name.value().split(",");
+            this.names = new ArrayList<>(names.length);
+
+            for (String columnName : names)
+                this.names.add(comparator.fromString(columnName));
+
+            final int nameCount = this.names.size();
+            countDistribution = new DistributionFactory()
+            {
+                @Override
+                public Distribution get()
+                {
+                    return new DistributionFixed(nameCount);
+                }
+            };
+        }
+        else
+        {
+            this.countDistribution = count.count.get();
+            this.names = null;
+        }
+        maxColumnsPerKey = (int) countDistribution.get().maxValue();
+        variableColumnCount = countDistribution.get().minValue() < maxColumnsPerKey;
+    }
+
+    public RowGen newRowGen()
+    {
+        return new RowGenDistributedSize(dataGenFactory.get(), countDistribution.get(), sizeDistribution.get());
+    }
+
+    // Option Declarations
+
+    private static abstract class Options extends GroupedOptions
+    {
+        final OptionSimple superColumns = new OptionSimple("super=", "[0-9]+", "0", "Number of super columns to use (no super columns used if not specified)", false);
+        final OptionSimple comparator = new OptionSimple("comparator=", "TimeUUIDType|AsciiType|UTF8Type", "AsciiType", "Column Comparator to use", false);
+        final OptionDistribution size = new OptionDistribution("size=", "FIXED(34)");
+        final OptionDataGen generator = new OptionDataGen("data=", "REPEAT(50)");
+    }
+
+    private static final class NameOptions extends Options
+    {
+        final OptionSimple name = new OptionSimple("names=", ".*", null, "Column names", true);
+
+        @Override
+        public List<? extends Option> options()
+        {
+            return Arrays.asList(name, superColumns, comparator, size, generator);
+        }
+    }
+
+    private static final class CountOptions extends Options
+    {
+        final OptionDistribution count = new OptionDistribution("n=", "FIXED(5)");
+
+        @Override
+        public List<? extends Option> options()
+        {
+            return Arrays.asList(count, superColumns, comparator, size, generator);
+        }
+    }
+
+    // CLI Utility Methods
+
+    static SettingsColumn get(Map<String, String[]> clArgs)
+    {
+        String[] params = clArgs.remove("-col");
+        if (params == null)
+            return new SettingsColumn(new CountOptions());
+
+        GroupedOptions options = GroupedOptions.select(params, new NameOptions(), new CountOptions());
+        if (options == null)
+        {
+            printHelp();
+            System.out.println("Invalid -col options provided, see output for valid options");
+            System.exit(1);
+        }
+        return new SettingsColumn(options);
+    }
+
+    static void printHelp()
+    {
+        GroupedOptions.printOptions(System.out, "-col", new NameOptions(), new CountOptions());
+    }
+
+    static Runnable helpPrinter()
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                printHelp();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommand.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommand.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommand.java
new file mode 100644
index 0000000..a996988
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommand.java
@@ -0,0 +1,159 @@
+package org.apache.cassandra.stress.settings;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cassandra.thrift.ConsistencyLevel;
+
+// Generic command settings - common to read/write/etc
+public class SettingsCommand implements Serializable
+{
+
+    public final Command type;
+    public final long count;
+    public final int tries;
+    public final boolean ignoreErrors;
+    public final ConsistencyLevel consistencyLevel;
+    public final double targetUncertainty;
+    public final int minimumUncertaintyMeasurements;
+    public final int maximumUncertaintyMeasurements;
+
+    public SettingsCommand(Command type, GroupedOptions options)
+    {
+        this(type, (Options) options,
+                options instanceof Count ? (Count) options : null,
+                options instanceof Uncertainty ? (Uncertainty) options : null
+        );
+    }
+
+    public SettingsCommand(Command type, Options options, Count count, Uncertainty uncertainty)
+    {
+        this.type = type;
+        this.tries = Math.max(1, Integer.parseInt(options.retries.value()) + 1);
+        this.ignoreErrors = options.ignoreErrors.setByUser();
+        this.consistencyLevel = ConsistencyLevel.valueOf(options.consistencyLevel.value().toUpperCase());
+        if (count != null)
+        {
+            this.count = Long.parseLong(count.count.value());
+            this.targetUncertainty = -1;
+            this.minimumUncertaintyMeasurements = -1;
+            this.maximumUncertaintyMeasurements = -1;
+        }
+        else
+        {
+            this.count = -1;
+            this.targetUncertainty = Double.parseDouble(uncertainty.uncertainty.value());
+            this.minimumUncertaintyMeasurements = Integer.parseInt(uncertainty.minMeasurements.value());
+            this.maximumUncertaintyMeasurements = Integer.parseInt(uncertainty.maxMeasurements.value());
+        }
+    }
+
+    // Option Declarations
+
+    static abstract class Options extends GroupedOptions
+    {
+        final OptionSimple retries = new OptionSimple("tries=", "[0-9]+", "9", "Number of tries to perform for each operation before failing", false);
+        final OptionSimple ignoreErrors = new OptionSimple("ignore_errors", "", null, "Do not print/log errors", false);
+        final OptionSimple consistencyLevel = new OptionSimple("cl=", "ONE|QUORUM|LOCAL_QUORUM|EACH_QUORUM|ALL|ANY", "ONE", "Consistency level to use", false);
+    }
+
+    static class Count extends Options
+    {
+
+        final OptionSimple count = new OptionSimple("n=", "[0-9]+", null, "Number of operations to perform", true);
+
+        @Override
+        public List<? extends Option> options()
+        {
+            return Arrays.asList(count, retries, ignoreErrors, consistencyLevel);
+        }
+    }
+
+    static class Uncertainty extends Options
+    {
+
+        final OptionSimple uncertainty = new OptionSimple("err<", "0\\.[0-9]+", "0.02", "Run until the standard error of the mean is below this fraction", false);
+        final OptionSimple minMeasurements = new OptionSimple("n>", "[0-9]+", "30", "Run at least this many iterations before accepting uncertainty convergence", false);
+        final OptionSimple maxMeasurements = new OptionSimple("n<", "[0-9]+", "200", "Run at most this many iterations before accepting uncertainty convergence", false);
+
+        @Override
+        public List<? extends Option> options()
+        {
+            return Arrays.asList(uncertainty, minMeasurements, maxMeasurements, retries, ignoreErrors, consistencyLevel);
+        }
+    }
+
+    // CLI Utility Methods
+
+    static SettingsCommand get(Map<String, String[]> clArgs)
+    {
+        for (Command cmd : Command.values())
+        {
+            if (cmd.category == null)
+                continue;
+            final String[] params = clArgs.remove(cmd.toString().toLowerCase());
+            if (params != null)
+            {
+                switch (cmd.category)
+                {
+                    case BASIC:
+                        return build(cmd, params);
+                    case MULTI:
+                        return SettingsCommandMulti.build(cmd, params);
+                    case MIXED:
+                        return SettingsCommandMixed.build(params);
+                }
+            }
+        }
+        return null;
+    }
+
+    static SettingsCommand build(Command type, String[] params)
+    {
+        GroupedOptions options = GroupedOptions.select(params, new Count(), new Uncertainty());
+        if (options == null)
+        {
+            printHelp(type);
+            System.out.println("Invalid " + type + " options provided, see output for valid options");
+            System.exit(1);
+        }
+        return new SettingsCommand(type, options);
+    }
+
+    static void printHelp(Command type)
+    {
+        printHelp(type.toString().toLowerCase());
+    }
+
+    static void printHelp(String type)
+    {
+        GroupedOptions.printOptions(System.out, type.toString().toLowerCase(), new Uncertainty(), new Count());
+    }
+
+    static Runnable helpPrinter(final String type)
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                printHelp(type);
+            }
+        };
+    }
+
+    static Runnable helpPrinter(final Command type)
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                printHelp(type);
+            }
+        };
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMixed.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMixed.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMixed.java
new file mode 100644
index 0000000..995e7d6
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMixed.java
@@ -0,0 +1,184 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cassandra.stress.generatedata.Distribution;
+import org.apache.cassandra.stress.generatedata.DistributionFactory;
+import org.apache.commons.math3.distribution.EnumeratedDistribution;
+import org.apache.commons.math3.util.Pair;
+
+// Settings unique to the mixed command type
+public class SettingsCommandMixed extends SettingsCommandMulti
+{
+
+    // Ratios for selecting commands - index for each Command, NaN indicates the command is not requested
+    private final List<Pair<Command, Double>> ratios;
+    private final DistributionFactory clustering;
+
+    public SettingsCommandMixed(Options options)
+    {
+        super(Command.MIXED, options.parent);
+
+        OptionSimple[] ratiosIn = options.probabilities.ratios;
+        List<Pair<Command, Double>> ratiosOut = new ArrayList<>();
+        for (int i = 0 ; i < ratiosIn.length ; i++)
+        {
+            if (ratiosIn[i] != null && ratiosIn[i].present())
+            {
+                double d = Double.parseDouble(ratiosIn[i].value());
+                if (d > 0)
+                    ratiosOut.add(new Pair<>(Command.values()[i], d));
+            }
+        }
+
+        ratios = ratiosOut;
+        clustering = options.clustering.get();
+
+        if (ratios.size() == 0)
+            throw new IllegalArgumentException("Must specify at least one command with a non-zero ratio");
+    }
+
+    public List<Command> getCommands()
+    {
+        final List<Command> r = new ArrayList<>();
+        for (Pair<Command, Double> p : ratios)
+            r.add(p.getFirst());
+        return r;
+    }
+
+    public CommandSelector selector()
+    {
+        return new CommandSelector(ratios, clustering.get());
+    }
+
+    // Class for randomly selecting the next command type
+
+    public static final class CommandSelector
+    {
+
+        final EnumeratedDistribution<Command> selector;
+        final Distribution count;
+        private Command cur;
+        private long remaining;
+
+        public CommandSelector(List<Pair<Command, Double>> ratios, Distribution count)
+        {
+            selector = new EnumeratedDistribution<>(ratios);
+            this.count = count;
+        }
+
+        public Command next()
+        {
+            while (remaining == 0)
+            {
+                remaining = count.next();
+                cur = selector.sample();
+            }
+            remaining--;
+            return cur;
+        }
+    }
+
+    // Option Declarations
+
+    static final class Probabilities extends OptionMulti
+    {
+        // entry for each in Command.values()
+        final OptionSimple[] ratios;
+        final List<OptionSimple> grouping;
+
+        public Probabilities()
+        {
+            super("ratio", "Specify the ratios for operations to perform; e.g. (reads=2,writes=1) will perform 2 reads for each write");
+            OptionSimple[] ratios = new OptionSimple[Command.values().length];
+            List<OptionSimple> grouping = new ArrayList<>();
+            for (Command command : Command.values())
+            {
+                if (command.category == null)
+                    continue;
+                String defaultValue;
+                switch (command)
+                {
+                    case MIXED:
+                        continue;
+                    case READ:
+                    case WRITE:
+                        defaultValue = "1";
+                        break;
+                    default:
+                        defaultValue = null;
+                }
+                OptionSimple ratio = new OptionSimple(command.toString().toLowerCase() +
+                        "=", "[0-9]+(\\.[0-9]+)?", defaultValue, "Performs this many " + command + " operations out of total", false);
+                ratios[command.ordinal()] = ratio;
+                grouping.add(ratio);
+            }
+            this.grouping = grouping;
+            this.ratios = ratios;
+        }
+
+        @Override
+        public List<? extends Option> options()
+        {
+            return grouping;
+        }
+    }
+
+    static final class Options extends GroupedOptions
+    {
+        final SettingsCommandMulti.Options parent;
+        protected Options(SettingsCommandMulti.Options parent)
+        {
+            this.parent = parent;
+        }
+        final OptionDistribution clustering = new OptionDistribution("clustering=", "GAUSSIAN(1..10)");
+        final Probabilities probabilities = new Probabilities();
+
+        @Override
+        public List<? extends Option> options()
+        {
+            final List<Option> options = new ArrayList<>();
+            options.add(clustering);
+            options.add(probabilities);
+            options.addAll(parent.options());
+            return options;
+        }
+
+    }
+
+    // CLI utility methods
+
+    public static SettingsCommandMixed build(String[] params)
+    {
+        GroupedOptions options = GroupedOptions.select(params,
+                new Options(new SettingsCommandMulti.Options(new Uncertainty())),
+                new Options(new SettingsCommandMulti.Options(new Count())));
+        if (options == null)
+        {
+            printHelp();
+            System.out.println("Invalid MIXED options provided, see output for valid options");
+            System.exit(1);
+        }
+        return new SettingsCommandMixed((Options) options);
+    }
+
+    public static void printHelp()
+    {
+        GroupedOptions.printOptions(System.out, "mixed",
+                new Options(new SettingsCommandMulti.Options(new Uncertainty())),
+                new Options(new SettingsCommandMulti.Options(new Count())));
+    }
+
+    public static Runnable helpPrinter()
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                printHelp();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2e1e98ad/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMulti.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMulti.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMulti.java
new file mode 100644
index 0000000..720b0c6
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandMulti.java
@@ -0,0 +1,69 @@
+package org.apache.cassandra.stress.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// Settings common to commands that operate over multiple keys at once
+public class SettingsCommandMulti extends SettingsCommand
+{
+
+    public final int keysAtOnce;
+
+    public SettingsCommandMulti(Command type, Options options)
+    {
+        super(type, options.parent);
+        this.keysAtOnce = Integer.parseInt(options.maxKeys.value());
+    }
+
+    // Option Declarations
+
+    static final class Options extends GroupedOptions
+    {
+        final GroupedOptions parent;
+        Options(GroupedOptions parent)
+        {
+            this.parent = parent;
+        }
+        final OptionSimple maxKeys = new OptionSimple("at-once=", "[0-9]+", "1000", "Number of keys per operation", false);
+
+        @Override
+        public List<? extends Option> options()
+        {
+            final List<Option> options = new ArrayList<>();
+            options.add(maxKeys);
+            options.addAll(parent.options());
+            return options;
+        }
+    }
+
+    // CLI Utility Methods
+
+    public static SettingsCommand build(Command type, String[] params)
+    {
+        GroupedOptions options = GroupedOptions.select(params, new Options(new Uncertainty()), new Options(new Count()));
+        if (options == null)
+        {
+            printHelp(type);
+            System.out.println("Invalid " + type + " options provided, see output for valid options");
+            System.exit(1);
+        }
+        return new SettingsCommandMulti(type, (Options) options);
+    }
+
+    public static void printHelp(Command type)
+    {
+        GroupedOptions.printOptions(System.out, type.toString().toLowerCase(), new Options(new Uncertainty()), new Options(new Count()));
+    }
+
+    public static Runnable helpPrinter(final Command type)
+    {
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                printHelp(type);
+            }
+        };
+    }
+}