diff --git a/build.xml b/build.xml
index 1ffb908346..347323f3dd 100644
--- a/build.xml
+++ b/build.xml
@@ -37,6 +37,7 @@
+
@@ -343,6 +344,7 @@
+
@@ -453,6 +455,7 @@
+
@@ -703,6 +706,9 @@
+
+
+
diff --git a/lib/commons-math3-3.2.jar b/lib/commons-math3-3.2.jar
new file mode 100644
index 0000000000..f8b7db295b
Binary files /dev/null and b/lib/commons-math3-3.2.jar differ
diff --git a/tools/bin/cassandra-stressd b/tools/bin/cassandra-stressd
index 8d337e5411..9110c5d957 100755
--- a/tools/bin/cassandra-stressd
+++ b/tools/bin/cassandra-stressd
@@ -17,23 +17,25 @@
# limitations under the License.
DESC="Cassandra Stress Test Daemon"
+if [ "x$CASSANDRA_INCLUDE" = "x" ]; then
+ for include in "`dirname $0`/cassandra.in.sh" \
+ "$HOME/.cassandra.in.sh" \
+ /usr/share/cassandra/cassandra.in.sh \
+ /usr/local/share/cassandra/cassandra.in.sh \
+ /opt/cassandra/cassandra.in.sh; do
+ if [ -r $include ]; then
+ . $include
+ break
+ fi
+ done
+elif [ -r $CASSANDRA_INCLUDE ]; then
+ . $CASSANDRA_INCLUDE
+fi
-if [ "x$CLASSPATH" = "x" ]; then
-
- # execute from the build dir.
- if [ -d `dirname $0`/../../build/classes ]; then
- for directory in `dirname $0`/../../build/classes/*; do
- CLASSPATH=$CLASSPATH:$directory
- done
- else
- if [ -f `dirname $0`/../lib/stress.jar ]; then
- CLASSPATH=`dirname $0`/../lib/stress.jar
- fi
- fi
-
- for jar in `dirname $0`/../../lib/*.jar; do
- CLASSPATH=$CLASSPATH:$jar
- done
+if [ -x $JAVA_HOME/bin/java ]; then
+ JAVA=$JAVA_HOME/bin/java
+else
+ JAVA=`which java`
fi
if [ -x $JAVA_HOME/bin/java ]; then
diff --git a/tools/lib/cassandra-driver-core-2.0.0-rc2-SNAPSHOT-jar-with-dependencies.jar b/tools/lib/cassandra-driver-core-2.0.0-rc2-SNAPSHOT-jar-with-dependencies.jar
new file mode 100644
index 0000000000..1f4dafddfb
Binary files /dev/null and b/tools/lib/cassandra-driver-core-2.0.0-rc2-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/tools/lib/cassandra-driver-core-2.0.0-rc2-SNAPSHOT.jar b/tools/lib/cassandra-driver-core-2.0.0-rc2-SNAPSHOT.jar
new file mode 100644
index 0000000000..c0d4242725
Binary files /dev/null and b/tools/lib/cassandra-driver-core-2.0.0-rc2-SNAPSHOT.jar differ
diff --git a/tools/stress/src/org/apache/cassandra/stress/Operation.java b/tools/stress/src/org/apache/cassandra/stress/Operation.java
new file mode 100644
index 0000000000..fa7a4536d7
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/Operation.java
@@ -0,0 +1,204 @@
+/**
+ * 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;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cassandra.stress.generatedata.KeyGen;
+import org.apache.cassandra.stress.generatedata.RowGen;
+import org.apache.cassandra.stress.settings.Command;
+import org.apache.cassandra.stress.settings.CqlVersion;
+import org.apache.cassandra.stress.settings.SettingsCommandMixed;
+import org.apache.cassandra.stress.settings.StressSettings;
+import org.apache.cassandra.stress.util.JavaDriverClient;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.stress.util.Timer;
+import org.apache.cassandra.thrift.ColumnParent;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.transport.SimpleClient;
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+public abstract class Operation
+{
+ public final long index;
+ protected final State state;
+
+ public Operation(State state, long idx)
+ {
+ index = idx;
+ this.state = state;
+ }
+
+ public static interface RunOp
+ {
+ public boolean run() throws Exception;
+ public String key();
+ public int keyCount();
+ }
+
+ // one per thread!
+ public static final class State
+ {
+
+ public final StressSettings settings;
+ public final Timer timer;
+ public final Command type;
+ public final KeyGen keyGen;
+ public final RowGen rowGen;
+ public final List columnParents;
+ public final StressMetrics metrics;
+ public final SettingsCommandMixed.CommandSelector readWriteSelector;
+ private Object cqlCache;
+
+ public State(Command type, StressSettings settings, StressMetrics metrics)
+ {
+ this.type = type;
+ this.timer = metrics.getTiming().newTimer();
+ if (type == Command.MIXED)
+ readWriteSelector = ((SettingsCommandMixed) settings.command).selector();
+ else
+ readWriteSelector = null;
+ this.settings = settings;
+ this.keyGen = settings.keys.newKeyGen();
+ this.rowGen = settings.columns.newRowGen();
+ this.metrics = metrics;
+ if (!settings.columns.useSuperColumns)
+ columnParents = Collections.singletonList(new ColumnParent(settings.schema.columnFamily));
+ else
+ {
+ ColumnParent[] cp = new ColumnParent[settings.columns.superColumns];
+ for (int i = 0 ; i < cp.length ; i++)
+ cp[i] = new ColumnParent("Super1").setSuper_column(ByteBufferUtil.bytes("S" + i));
+ columnParents = Arrays.asList(cp);
+ }
+ }
+ public boolean isCql3()
+ {
+ return settings.mode.cqlVersion == CqlVersion.CQL3;
+ }
+ public boolean isCql2()
+ {
+ return settings.mode.cqlVersion == CqlVersion.CQL2;
+ }
+ public Object getCqlCache()
+ {
+ return cqlCache;
+ }
+ public void storeCqlCache(Object val)
+ {
+ cqlCache = val;
+ }
+ }
+
+ protected ByteBuffer getKey()
+ {
+ return state.keyGen.getKeys(1, index).get(0);
+ }
+
+ protected List getKeys(int count)
+ {
+ return state.keyGen.getKeys(count, index);
+ }
+
+ protected List generateColumnValues()
+ {
+ return state.rowGen.generate(index);
+ }
+
+ /**
+ * Run operation
+ * @param client Cassandra Thrift client connection
+ * @throws IOException on any I/O error.
+ */
+ public abstract void run(ThriftClient client) throws IOException;
+
+ public void run(SimpleClient client) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void run(JavaDriverClient client) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void timeWithRetry(RunOp run) throws IOException
+ {
+ state.timer.start();
+
+ boolean success = false;
+ String exceptionMessage = null;
+
+ for (int t = 0; t < state.settings.command.tries; t++)
+ {
+ if (success)
+ break;
+
+ try
+ {
+ success = run.run();
+ }
+ catch (Exception e)
+ {
+ System.err.println(e);
+ exceptionMessage = getExceptionMessage(e);
+ success = false;
+ }
+ }
+
+ state.timer.stop(run.keyCount());
+
+ if (!success)
+ {
+ error(String.format("Operation [%d] retried %d times - error executing for key %s %s%n",
+ index,
+ state.settings.command.tries,
+ run.key(),
+ (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
+ }
+
+ }
+
+ protected String getExceptionMessage(Exception e)
+ {
+ String className = e.getClass().getSimpleName();
+ String message = (e instanceof InvalidRequestException) ? ((InvalidRequestException) e).getWhy() : e.getMessage();
+ return (message == null) ? "(" + className + ")" : String.format("(%s): %s", className, message);
+ }
+
+ protected void error(String message) throws IOException
+ {
+ if (!state.settings.command.ignoreErrors)
+ throw new IOException(message);
+ else
+ System.err.println(message);
+ }
+
+ public static ByteBuffer getColumnNameBytes(int i)
+ {
+ return ByteBufferUtil.bytes("C" + i);
+ }
+
+ public static String getColumnName(int i)
+ {
+ return "C" + i;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/Session.java b/tools/stress/src/org/apache/cassandra/stress/Session.java
deleted file mode 100644
index 8d138f59f1..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/Session.java
+++ /dev/null
@@ -1,841 +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;
-
-import java.io.*;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.commons.cli.*;
-import org.apache.commons.lang3.StringUtils;
-
-import com.yammer.metrics.Metrics;
-
-import org.apache.cassandra.auth.IAuthenticator;
-import org.apache.cassandra.cli.transport.FramedTransportFactory;
-import org.apache.cassandra.config.CFMetaData;
-import org.apache.cassandra.config.EncryptionOptions;
-import org.apache.cassandra.config.EncryptionOptions.ClientEncryptionOptions;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.db.marshal.*;
-import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.exceptions.SyntaxException;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.thrift.*;
-import org.apache.cassandra.transport.SimpleClient;
-import org.apache.cassandra.utils.ByteBufferUtil;
-import org.apache.thrift.protocol.TBinaryProtocol;
-import org.apache.thrift.transport.TSocket;
-import org.apache.thrift.transport.TTransport;
-import org.apache.thrift.transport.TTransportFactory;
-
-public class Session implements Serializable
-{
- // command line options
- public static final Options availableOptions = new Options();
-
- public static final String KEYSPACE_NAME = "Keyspace1";
- public static final String DEFAULT_COMPARATOR = "AsciiType";
- public static final String DEFAULT_VALIDATOR = "BytesType";
-
- private static InetAddress localInetAddress;
-
- public final AtomicInteger operations = new AtomicInteger();
- public final AtomicInteger keys = new AtomicInteger();
- public final com.yammer.metrics.core.Timer latency = Metrics.newTimer(Session.class, "latency");
-
- 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("N", "skip-keys", true, "Fraction of keys to skip initially, default:0");
- availableOptions.addOption("t", "threads", true, "Number of threads to use, default:50");
- availableOptions.addOption("c", "cells", true, "Number of cells per key, default:5");
- availableOptions.addOption("S", "column-size", true, "Size of column values in bytes, default:34");
- availableOptions.addOption("C", "cardinality", true, "Number of unique values stored in cells, 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 Factor, default:0.1");
- availableOptions.addOption("r", "random", false, "Use random key generator (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 (INSERT, READ, RANGE_SLICE, INDEXED_RANGE_SLICE, MULTI_GET, COUNTER_ADD, COUNTER_GET), default:INSERT");
- 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 :,:,...");
- 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, "Cell 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.");
- availableOptions.addOption("un", "username", true, "Username for authentication.");
- availableOptions.addOption("pw", "password", true, "Password for authentication.");
- }
-
- private int numKeys = 1000 * 1000;
- private int numDifferentKeys = numKeys;
- private float skipKeys = 0;
- private int threads = 50;
- private int columns = 5;
- private int columnSize = 34;
- private int cardinality = 50;
- public String[] nodes = new String[] { "127.0.0.1" };
- private boolean random = false;
- private int retryTimes = 10;
- public int port = 9160;
- private int superColumns = 1;
- private String compression = null;
- private String compactionStrategy = null;
- private String username = null;
- private String password = null;
-
- private int progressInterval = 10;
- private int keysPerCall = 1000;
- private boolean replicateOnWrite = true;
- private boolean ignoreErrors = false;
- private boolean enable_cql = false;
- private boolean use_prepared = false;
- private boolean trace = false;
- private boolean captureStatistics = true;
- public boolean use_native_protocol = false;
- private double maxOpsPerSecond = Double.MAX_VALUE;
-
- private final String outFileName;
-
- private IndexType indexType = null;
- private Stress.Operations operation = Stress.Operations.INSERT;
- private ColumnFamilyType columnFamilyType = ColumnFamilyType.Standard;
- private ConsistencyLevel consistencyLevel = ConsistencyLevel.ONE;
- private String replicationStrategy = "org.apache.cassandra.locator.SimpleStrategy";
- private Map replicationStrategyOptions = new HashMap();
-
- // if we know exactly column names that we want to read (set by -Q option)
- public final List columnNames;
-
- public String cqlVersion;
-
- public final boolean averageSizeValues;
-
- // required by Gaussian distribution.
- protected int mean;
- protected float sigma;
-
- public final InetAddress sendToDaemon;
- public final String comparator;
- public final boolean timeUUIDComparator;
- public double traceProbability = 0.0;
- public EncryptionOptions encOptions = new ClientEncryptionOptions();
- public TTransportFactory transportFactory = new FramedTransportFactory();
-
- public Session(String[] arguments) throws IllegalArgumentException, SyntaxException
- {
- float STDev = 0.1f;
- CommandLineParser parser = new PosixParser();
-
- try
- {
- CommandLine cmd = parser.parse(availableOptions, arguments);
-
- if (cmd.getArgs().length > 0)
- {
- System.err.println("Application does not allow arbitrary arguments: " + StringUtils.join(cmd.getArgList(), ", "));
- System.exit(1);
- }
-
- if (cmd.hasOption("h"))
- throw new IllegalArgumentException("help");
-
- if (cmd.hasOption("n"))
- numKeys = Integer.parseInt(cmd.getOptionValue("n"));
-
- if (cmd.hasOption("F"))
- numDifferentKeys = Integer.parseInt(cmd.getOptionValue("F"));
- else
- numDifferentKeys = numKeys;
-
- if (cmd.hasOption("N"))
- skipKeys = Float.parseFloat(cmd.getOptionValue("N"));
-
- if (cmd.hasOption("t"))
- threads = Integer.parseInt(cmd.getOptionValue("t"));
-
- if (cmd.hasOption("c"))
- columns = Integer.parseInt(cmd.getOptionValue("c"));
-
- if (cmd.hasOption("S"))
- columnSize = Integer.parseInt(cmd.getOptionValue("S"));
-
- if (cmd.hasOption("C"))
- cardinality = Integer.parseInt(cmd.getOptionValue("C"));
-
- if (cmd.hasOption("d"))
- nodes = cmd.getOptionValue("d").split(",");
-
- if (cmd.hasOption("D"))
- {
- try
- {
- String node;
- List tmpNodes = new ArrayList();
- BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(cmd.getOptionValue("D"))));
- try
- {
- while ((node = in.readLine()) != null)
- {
- if (node.length() > 0)
- tmpNodes.add(node);
- }
- nodes = tmpNodes.toArray(new String[tmpNodes.size()]);
- }
- finally
- {
- in.close();
- }
- }
- catch(IOException ioe)
- {
- throw new RuntimeException(ioe);
- }
- }
-
- if (cmd.hasOption("s"))
- STDev = Float.parseFloat(cmd.getOptionValue("s"));
-
- if (cmd.hasOption("r"))
- random = true;
-
- outFileName = (cmd.hasOption("f")) ? cmd.getOptionValue("f") : null;
-
- if (cmd.hasOption("p"))
- port = Integer.parseInt(cmd.getOptionValue("p"));
-
- if (cmd.hasOption("o"))
- operation = Stress.Operations.valueOf(cmd.getOptionValue("o").toUpperCase());
-
- if (cmd.hasOption("u"))
- superColumns = Integer.parseInt(cmd.getOptionValue("u"));
-
- if (cmd.hasOption("y"))
- columnFamilyType = ColumnFamilyType.valueOf(cmd.getOptionValue("y"));
-
- if (cmd.hasOption("K"))
- {
- retryTimes = Integer.valueOf(cmd.getOptionValue("K"));
-
- if (retryTimes <= 0)
- {
- throw new RuntimeException("--keep-trying option value should be > 0");
- }
- }
-
- if (cmd.hasOption("k"))
- {
- retryTimes = 1;
- ignoreErrors = true;
- }
-
-
- if (cmd.hasOption("i"))
- progressInterval = Integer.parseInt(cmd.getOptionValue("i"));
-
- if (cmd.hasOption("g"))
- keysPerCall = Integer.parseInt(cmd.getOptionValue("g"));
-
- if (cmd.hasOption("th"))
- maxOpsPerSecond = Double.parseDouble(cmd.getOptionValue("th"));
-
- if (cmd.hasOption("e"))
- consistencyLevel = ConsistencyLevel.valueOf(cmd.getOptionValue("e").toUpperCase());
-
- if (cmd.hasOption("x"))
- indexType = IndexType.valueOf(cmd.getOptionValue("x").toUpperCase());
-
- if (cmd.hasOption("R"))
- replicationStrategy = cmd.getOptionValue("R");
-
- if (cmd.hasOption("l"))
- replicationStrategyOptions.put("replication_factor", String.valueOf(Integer.parseInt(cmd.getOptionValue("l"))));
- else if (replicationStrategy.endsWith("SimpleStrategy"))
- replicationStrategyOptions.put("replication_factor", "1");
-
- if (cmd.hasOption("L"))
- {
- enable_cql = true;
- cqlVersion = "2.0.0";
- }
-
- if (cmd.hasOption("L3"))
- {
- enable_cql = true;
- cqlVersion = "3.0.0";
- }
-
- if (cmd.hasOption("b"))
- {
- if (!(enable_cql && cqlVersion.startsWith("3")))
- throw new IllegalArgumentException("Cannot use binary protocol without -L3");
- use_native_protocol = true;
- }
-
- if (cmd.hasOption("P"))
- {
- if (!enable_cql)
- {
- System.err.println("-P/--use-prepared-statements is only applicable with CQL (-L/--enable-cql)");
- System.exit(-1);
- }
- use_prepared = true;
- }
-
- if (cmd.hasOption("O"))
- {
- String[] pairs = StringUtils.split(cmd.getOptionValue("O"), ',');
-
- for (String pair : pairs)
- {
- String[] keyAndValue = StringUtils.split(pair, ':');
-
- if (keyAndValue.length != 2)
- throw new RuntimeException("Invalid --strategy-properties value.");
-
- replicationStrategyOptions.put(keyAndValue[0], keyAndValue[1]);
- }
- }
-
- if (cmd.hasOption("W"))
- replicateOnWrite = false;
-
- if (cmd.hasOption("I"))
- compression = cmd.getOptionValue("I");
-
- averageSizeValues = cmd.hasOption("V");
-
- try
- {
- sendToDaemon = cmd.hasOption("send-to")
- ? InetAddress.getByName(cmd.getOptionValue("send-to"))
- : null;
- }
- catch (UnknownHostException e)
- {
- throw new RuntimeException(e);
- }
-
- if (cmd.hasOption("Q"))
- {
- AbstractType comparator = TypeParser.parse(DEFAULT_COMPARATOR);
-
- String[] names = StringUtils.split(cmd.getOptionValue("Q"), ",");
- columnNames = new ArrayList(names.length);
-
- for (String columnName : names)
- columnNames.add(comparator.fromString(columnName));
- }
- else
- {
- columnNames = null;
- }
-
- if (cmd.hasOption("Z"))
- {
- compactionStrategy = cmd.getOptionValue("Z");
-
- try
- {
- // validate compaction strategy class
- CFMetaData.createCompactionStrategy(compactionStrategy);
- }
- catch (ConfigurationException e)
- {
- System.err.println(e.getMessage());
- System.exit(1);
- }
- }
-
- if (cmd.hasOption("U"))
- {
- AbstractType parsed = null;
-
- try
- {
- parsed = TypeParser.parse(cmd.getOptionValue("U"));
- }
- catch (ConfigurationException e)
- {
- System.err.println(e.getMessage());
- System.exit(1);
- }
-
- comparator = cmd.getOptionValue("U");
- timeUUIDComparator = 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);
- }
- }
- else
- {
- comparator = null;
- timeUUIDComparator = false;
- }
-
- if (cmd.hasOption("ns"))
- {
- captureStatistics = false;
- }
-
- 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(",");
-
- if (cmd.hasOption("tf"))
- transportFactory = validateAndSetTransportFactory(cmd.getOptionValue("tf"));
-
- if (cmd.hasOption("un"))
- username = cmd.getOptionValue("un");
-
- if (cmd.hasOption("pw"))
- password = cmd.getOptionValue("pw");
- }
- catch (ParseException e)
- {
- throw new IllegalArgumentException(e.getMessage(), e);
- }
- catch (ConfigurationException e)
- {
- throw new IllegalStateException(e.getMessage(), e);
- }
-
- mean = numDifferentKeys / 2;
- sigma = numDifferentKeys * STDev;
- }
-
- private TTransportFactory validateAndSetTransportFactory(String transportFactory)
- {
- try
- {
- Class factory = Class.forName(transportFactory);
-
- if(!TTransportFactory.class.isAssignableFrom(factory))
- throw new IllegalArgumentException(String.format("transport factory '%s' " +
- "not derived from TTransportFactory", transportFactory));
-
- return (TTransportFactory) factory.newInstance();
- }
- catch (Exception e)
- {
- throw new IllegalArgumentException(String.format("Cannot create a transport factory '%s'.", transportFactory), e);
- }
- }
-
- public int getCardinality()
- {
- return cardinality;
- }
-
- public int getColumnSize()
- {
- return columnSize;
- }
-
- public int getColumnsPerKey()
- {
- return columns;
- }
-
- public ColumnFamilyType getColumnFamilyType()
- {
- return columnFamilyType;
- }
-
- public int getNumKeys()
- {
- return numKeys;
- }
-
- public int getNumDifferentKeys()
- {
- return numDifferentKeys;
- }
-
- public int getThreads()
- {
- return threads;
- }
-
- public double getMaxOpsPerSecond()
- {
- return maxOpsPerSecond;
- }
-
- public float getSkipKeys()
- {
- return skipKeys;
- }
-
- public int getSuperColumns()
- {
- return superColumns;
- }
-
- public int getKeysPerThread()
- {
- return numKeys / threads;
- }
-
- public int getTotalKeysLength()
- {
- return Integer.toString(numDifferentKeys).length();
- }
-
- public ConsistencyLevel getConsistencyLevel()
- {
- return consistencyLevel;
- }
-
- public int getRetryTimes()
- {
- return retryTimes;
- }
-
- public boolean ignoreErrors()
- {
- return ignoreErrors;
- }
-
- public Stress.Operations getOperation()
- {
- return operation;
- }
-
- public PrintStream getOutputStream()
- {
- try
- {
- return (outFileName == null) ? System.out : new PrintStream(new FileOutputStream(outFileName));
- }
- catch (FileNotFoundException e)
- {
- throw new RuntimeException(e.getMessage(), e);
- }
- }
-
- public int getProgressInterval()
- {
- return progressInterval;
- }
-
- public boolean useRandomGenerator()
- {
- return random;
- }
-
- public int getKeysPerCall()
- {
- return keysPerCall;
- }
-
- // required by Gaussian distribution
- public int getMean()
- {
- return mean;
- }
-
- // required by Gaussian distribution
- public float getSigma()
- {
- return sigma;
- }
-
- public boolean isCQL()
- {
- return enable_cql;
- }
-
- public boolean usePreparedStatements()
- {
- return use_prepared;
- }
-
- public boolean outputStatistics()
- {
- return captureStatistics;
- }
-
- /**
- * Create Keyspace with Standard and Super/Counter column families
- */
- public void createKeySpaces()
- {
- KsDef keyspace = new KsDef();
- String defaultComparator = comparator == null ? DEFAULT_COMPARATOR : comparator;
-
- // column family for standard columns
- CfDef standardCfDef = new CfDef(KEYSPACE_NAME, "Standard1");
- Map compressionOptions = new HashMap();
- if (compression != null)
- compressionOptions.put("sstable_compression", compression);
-
- standardCfDef.setComparator_type(defaultComparator)
- .setDefault_validation_class(DEFAULT_VALIDATOR)
- .setCompression_options(compressionOptions);
-
- if (!timeUUIDComparator)
- {
- for (int i = 0; i < getColumnsPerKey(); i++)
- {
- standardCfDef.addToColumn_metadata(new ColumnDef(ByteBufferUtil.bytes("C" + i), "BytesType"));
- }
- }
-
- if (indexType != null)
- {
- ColumnDef standardColumn = new ColumnDef(ByteBufferUtil.bytes("C1"), "BytesType");
- standardColumn.setIndex_type(indexType).setIndex_name("Idx1");
- standardCfDef.setColumn_metadata(Arrays.asList(standardColumn));
- }
-
- // column family with super columns
- CfDef superCfDef = new CfDef(KEYSPACE_NAME, "Super1").setColumn_type("Super");
- superCfDef.setComparator_type(DEFAULT_COMPARATOR)
- .setSubcomparator_type(defaultComparator)
- .setDefault_validation_class(DEFAULT_VALIDATOR)
- .setCompression_options(compressionOptions);
-
- // column family for standard counters
- CfDef counterCfDef = new CfDef(KEYSPACE_NAME, "Counter1").setComparator_type(defaultComparator)
- .setComparator_type(defaultComparator)
- .setDefault_validation_class("CounterColumnType")
- .setReplicate_on_write(replicateOnWrite)
- .setCompression_options(compressionOptions);
-
- // column family with counter super columns
- CfDef counterSuperCfDef = new CfDef(KEYSPACE_NAME, "SuperCounter1").setComparator_type(defaultComparator)
- .setDefault_validation_class("CounterColumnType")
- .setReplicate_on_write(replicateOnWrite)
- .setColumn_type("Super")
- .setCompression_options(compressionOptions);
-
- keyspace.setName(KEYSPACE_NAME);
- keyspace.setStrategy_class(replicationStrategy);
-
- if (!replicationStrategyOptions.isEmpty())
- {
- keyspace.setStrategy_options(replicationStrategyOptions);
- }
-
- if (compactionStrategy != null)
- {
- standardCfDef.setCompaction_strategy(compactionStrategy);
- superCfDef.setCompaction_strategy(compactionStrategy);
- counterCfDef.setCompaction_strategy(compactionStrategy);
- counterSuperCfDef.setCompaction_strategy(compactionStrategy);
- }
-
- keyspace.setCf_defs(new ArrayList(Arrays.asList(standardCfDef, superCfDef, counterCfDef, counterSuperCfDef)));
-
- CassandraClient client = getClient(false);
-
- try
- {
- client.system_add_keyspace(keyspace);
-
- /* CQL3 counter cf */
- client.set_cql_version("3.0.0"); // just to create counter cf for cql3
-
- client.set_keyspace(KEYSPACE_NAME);
- client.execute_cql3_query(createCounterCFStatementForCQL3(), Compression.NONE, ConsistencyLevel.ONE);
-
- if (enable_cql)
- client.set_cql_version(cqlVersion);
- /* end */
-
- System.out.println(String.format("Created keyspaces. Sleeping %ss for propagation.", nodes.length));
- Thread.sleep(nodes.length * 1000); // seconds
- }
- catch (InvalidRequestException e)
- {
- System.err.println("Unable to create stress keyspace: " + e.getWhy());
- }
- catch (Exception e)
- {
- System.err.println(e.getMessage());
- }
- }
-
- /**
- * Thrift client connection with Keyspace1 set.
- * @return cassandra client connection
- */
- public CassandraClient getClient()
- {
- return getClient(true);
- }
-
- /**
- * Thrift client connection
- * @param setKeyspace - should we set keyspace for client or not
- * @return cassandra client connection
- */
- public CassandraClient getClient(boolean setKeyspace)
- {
- // random node selection for fake load balancing
- String currentNode = nodes[Stress.randomizer.nextInt(nodes.length)];
-
- TSocket socket = new TSocket(currentNode, port);
- TTransport transport = transportFactory.getTransport(socket);
- CassandraClient client = new CassandraClient(new TBinaryProtocol(transport));
-
- try
- {
- if (!transport.isOpen())
- transport.open();
-
- if (enable_cql)
- client.set_cql_version(cqlVersion);
-
- if (setKeyspace)
- client.set_keyspace("Keyspace1");
-
- if (username != null && password != null)
- {
- Map credentials = new HashMap();
- credentials.put(IAuthenticator.USERNAME_KEY, username);
- credentials.put(IAuthenticator.PASSWORD_KEY, password);
- AuthenticationRequest authenticationRequest = new AuthenticationRequest(credentials);
- client.login(authenticationRequest);
- }
- }
- catch (AuthenticationException e)
- {
- throw new RuntimeException(e.getWhy());
- }
- catch (AuthorizationException e)
- {
- throw new RuntimeException(e.getWhy());
- }
- catch (InvalidRequestException e)
- {
- throw new RuntimeException(e.getWhy());
- }
- catch (Exception e)
- {
- throw new RuntimeException(e.getMessage());
- }
-
- return client;
- }
-
- public SimpleClient getNativeClient()
- {
- try
- {
- String currentNode = nodes[Stress.randomizer.nextInt(nodes.length)];
- SimpleClient client = new SimpleClient(currentNode, 9042);
- client.connect(false);
- client.execute("USE \"Keyspace1\";", org.apache.cassandra.db.ConsistencyLevel.ONE);
- return client;
- }
- catch (Exception e)
- {
- throw new RuntimeException(e.getMessage());
- }
- }
-
- public static InetAddress getLocalAddress()
- {
- if (localInetAddress == null)
- {
- try
- {
- localInetAddress = InetAddress.getLocalHost();
- }
- catch (UnknownHostException e)
- {
- throw new RuntimeException(e);
- }
- }
-
- return localInetAddress;
- }
-
- private ByteBuffer createCounterCFStatementForCQL3()
- {
- StringBuilder counter3 = new StringBuilder("CREATE TABLE \"Counter3\" (KEY blob PRIMARY KEY, ");
-
- for (int i = 0; i < getColumnsPerKey(); i++)
- {
- counter3.append("c").append(i).append(" counter");
- if (i != getColumnsPerKey() - 1)
- counter3.append(", ");
- }
- counter3.append(");");
-
- return ByteBufferUtil.bytes(counter3.toString());
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/Stress.java b/tools/stress/src/org/apache/cassandra/stress/Stress.java
index 738a1c043c..38af4f6f18 100644
--- a/tools/stress/src/org/apache/cassandra/stress/Stress.java
+++ b/tools/stress/src/org/apache/cassandra/stress/Stress.java
@@ -17,48 +17,65 @@
*/
package org.apache.cassandra.stress;
-import org.apache.commons.cli.Option;
-
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
-import java.util.Random;
+
+import org.apache.cassandra.stress.settings.StressSettings;
public final class Stress
{
- public static enum Operations
- {
- INSERT, READ, RANGE_SLICE, INDEXED_RANGE_SLICE, MULTI_GET, COUNTER_ADD, COUNTER_GET
- }
- public static Session session;
- public static Random randomizer = new Random();
+ /**
+ * Known issues:
+ * - uncertainty/stderr assumes op-rates are normally distributed. Due to GC (and possibly latency stepping from
+ * different media, though the variance of request ratio across media should be normally distributed), they are not.
+ * Should attempt to account for pauses in stderr calculation, possibly by assuming these pauses are a separate
+ * normally distributed occurrence
+ * - Under very mixed work loads, the uncertainty calculations and op/s reporting really don't mean much. Should
+ * consider breaking op/s down per workload, or should have a lower-bound on inspection interval based on clustering
+ * of operations and thread count.
+ *
+ *
+ * Future improvements:
+ * - Configurable connection compression
+ * - Java driver support
+ * - Per column data generators
+ * - Automatic column/schema detection if provided with a CF
+ * - target rate produces a very steady work rate, and if we want to simulate a real op rate for an
+ * application we should have some variation in the actual op rate within any time-slice.
+ * - auto rate should vary the thread count based on performance improvement, potentially starting on a very low
+ * thread count with a high error rate / low count to get some basic numbers
+ */
+
private static volatile boolean stopped = false;
public static void main(String[] arguments) throws Exception
{
+ final StressSettings settings;
try
{
- session = new Session(arguments);
+ settings = StressSettings.parse(arguments);
}
catch (IllegalArgumentException e)
{
printHelpMessage();
+ e.printStackTrace();
return;
}
- PrintStream outStream = session.getOutputStream();
+ PrintStream logout = settings.log.getOutput();
- if (session.sendToDaemon != null)
+ if (settings.sendToDaemon != null)
{
- Socket socket = new Socket(session.sendToDaemon, 2159);
+ Socket socket = new Socket(settings.sendToDaemon, 2159);
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
BufferedReader inp = new BufferedReader(new InputStreamReader(socket.getInputStream()));
Runtime.getRuntime().addShutdownHook(new ShutDown(socket, out));
- out.writeObject(session);
+ out.writeObject(settings);
String line;
@@ -72,7 +89,7 @@ public final class Stress
break;
}
- outStream.println(line);
+ logout.println(line);
}
}
catch (SocketException e)
@@ -88,10 +105,8 @@ public final class Stress
}
else
{
- StressAction stressAction = new StressAction(session, outStream);
- stressAction.start();
- stressAction.join();
- System.exit(stressAction.getReturnCode());
+ StressAction stressAction = new StressAction(settings, logout);
+ stressAction.run();
}
}
@@ -100,15 +115,7 @@ public final class Stress
*/
public static void printHelpMessage()
{
- System.out.println("Usage: ./bin/cassandra-stress [options]\n\nOptions:");
-
- for(Object o : Session.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()));
- }
+ StressSettings.printHelp();
}
private static class ShutDown extends Thread
diff --git a/tools/stress/src/org/apache/cassandra/stress/StressAction.java b/tools/stress/src/org/apache/cassandra/stress/StressAction.java
index 7098d0bab6..0312093e69 100644
--- a/tools/stress/src/org/apache/cassandra/stress/StressAction.java
+++ b/tools/stress/src/org/apache/cassandra/stress/StressAction.java
@@ -17,322 +17,527 @@
*/
package org.apache.cassandra.stress;
+import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintStream;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.SynchronousQueue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
-import com.google.common.util.concurrent.Uninterruptibles;
import com.google.common.util.concurrent.RateLimiter;
-import com.yammer.metrics.stats.Snapshot;
+import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.cassandra.stress.operations.*;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
+import org.apache.cassandra.stress.settings.*;
+import org.apache.cassandra.stress.util.JavaDriverClient;
+import org.apache.cassandra.stress.util.ThriftClient;
import org.apache.cassandra.transport.SimpleClient;
-public class StressAction extends Thread
+public class StressAction implements Runnable
{
- /**
- * Producer-Consumer model: 1 producer, N consumers
- */
- private final BlockingQueue operations = new SynchronousQueue(true);
- private final Session client;
+ private final StressSettings settings;
private final PrintStream output;
- private volatile boolean stop = false;
-
- public static final int SUCCESS = 0;
- public static final int FAILURE = 1;
-
- private volatile int returnCode = -1;
-
- public StressAction(Session session, PrintStream out)
+ public StressAction(StressSettings settings, PrintStream out)
{
- client = session;
+ this.settings = settings;
output = out;
}
public void run()
{
- Snapshot latency;
- long oldLatency;
- int epoch, total, oldTotal, keyCount, oldKeyCount;
-
// creating keyspace and column families
- if (client.getOperation() == Stress.Operations.INSERT || client.getOperation() == Stress.Operations.COUNTER_ADD)
- client.createKeySpaces();
+ settings.maybeCreateKeyspaces();
- int threadCount = client.getThreads();
- Consumer[] consumers = new Consumer[threadCount];
+ warmup(settings.command.type, settings.command);
- output.println("total,interval_op_rate,interval_key_rate,latency,95th,99.9th,elapsed_time");
+ output.println("Sleeping 2s...");
+ Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
- int itemsPerThread = client.getKeysPerThread();
- int modulo = client.getNumKeys() % threadCount;
- RateLimiter rateLimiter = RateLimiter.create(client.getMaxOpsPerSecond());
+ boolean success;
+ if (settings.rate.auto)
+ success = runAuto();
+ else
+ success = null != run(settings.command.type, settings.rate.threadCount, settings.command.count, output);
- // creating required type of the threads for the test
- for (int i = 0; i < threadCount; i++) {
- if (i == threadCount - 1)
- itemsPerThread += modulo; // last one is going to handle N + modulo items
+ if (success)
+ output.println("END");
+ else
+ output.println("FAILURE");
- consumers[i] = new Consumer(itemsPerThread, rateLimiter);
+ settings.disconnect();
+ }
+
+ // type provided separately to support recursive call for mixed command with each command type it is performing
+ private void warmup(Command type, SettingsCommand command)
+ {
+ // warmup - do 50k iterations; by default hotspot compiles methods after 10k invocations
+ PrintStream warmupOutput = new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { } } );
+ int iterations;
+ switch (type.category)
+ {
+ case BASIC:
+ iterations = 50000;
+ break;
+ case MIXED:
+ for (Command subtype : ((SettingsCommandMixed) command).getCommands())
+ warmup(subtype, command);
+ return;
+ case MULTI:
+ int keysAtOnce = ((SettingsCommandMulti) command).keysAtOnce;
+ iterations = Math.min(50000, (int) Math.ceil(500000d / keysAtOnce));
+ break;
+ default:
+ throw new IllegalStateException();
}
+ output.println(String.format("Warming up %s with %d iterations...", type, iterations));
+ run(type, 20, iterations, warmupOutput);
+ }
- Producer producer = new Producer();
- producer.start();
+ // TODO : permit varying more than just thread count
+ // TODO : vary thread count based on percentage improvement of previous increment, not by fixed amounts
+ private boolean runAuto()
+ {
+ int prevThreadCount = -1;
+ int threadCount = settings.rate.minAutoThreads;
+ List results = new ArrayList<>();
+ List runIds = new ArrayList<>();
+ do
+ {
+ output.println(String.format("Running with %d threadCount", threadCount));
- // starting worker threads
+ StressMetrics result = run(settings.command.type, threadCount, settings.command.count, output);
+ if (result == null)
+ return false;
+ results.add(result);
+
+ if (prevThreadCount > 0)
+ System.out.println(String.format("Improvement over %d threadCount: %.0f%%",
+ prevThreadCount, 100 * averageImprovement(results, 1)));
+
+ runIds.add(threadCount + " threadCount");
+ prevThreadCount = threadCount;
+ if (threadCount < 16)
+ threadCount *= 2;
+ else
+ threadCount *= 1.5;
+
+ if (!results.isEmpty() && threadCount > settings.rate.maxAutoThreads)
+ break;
+
+ if (settings.command.type.updates)
+ {
+ // pause an arbitrary period of time to let the commit log flush, etc. shouldn't make much difference
+ // as we only increase load, never decrease it
+ output.println("Sleeping for 15s");
+ try
+ {
+ Thread.sleep(15 * 1000);
+ } catch (InterruptedException e)
+ {
+ return false;
+ }
+ }
+ // run until we have not improved throughput significantly for previous three runs
+ } while (hasAverageImprovement(results, 3, 0) && hasAverageImprovement(results, 5, settings.command.targetUncertainty));
+
+ // summarise all results
+ StressMetrics.summarise(runIds, results, output);
+ return true;
+ }
+
+ private boolean hasAverageImprovement(List results, int count, double minImprovement)
+ {
+ if (results.size() < count + 1)
+ return true;
+ return averageImprovement(results, count) >= minImprovement;
+ }
+
+ private double averageImprovement(List results, int count)
+ {
+ double improvement = 0;
+ for (int i = results.size() - count ; i < results.size() ; i++)
+ {
+ double prev = results.get(i - 1).getTiming().getHistory().realOpRate();
+ double cur = results.get(i).getTiming().getHistory().realOpRate();
+ improvement += (cur - prev) / prev;
+ }
+ return improvement / count;
+ }
+
+ private StressMetrics run(Command type, int threadCount, long opCount, PrintStream output)
+ {
+
+ output.println(String.format("Running %s with %d threads %s",
+ type.toString(),
+ threadCount,
+ opCount > 0 ? " for " + opCount + " iterations" : "until stderr of mean < " + settings.command.targetUncertainty));
+ final WorkQueue workQueue;
+ if (opCount < 0)
+ workQueue = new ContinuousWorkQueue(50);
+ else
+ workQueue = FixedWorkQueue.build(opCount);
+
+ RateLimiter rateLimiter = null;
+ // TODO : move this to a new queue wrapper that gates progress based on a poisson (or configurable) distribution
+ if (settings.rate.opRateTargetPerSecond > 0)
+ rateLimiter = RateLimiter.create(settings.rate.opRateTargetPerSecond);
+
+ final StressMetrics metrics = new StressMetrics(output, settings.log.intervalMillis);
+
+ final CountDownLatch done = new CountDownLatch(threadCount);
+ final Consumer[] consumers = new Consumer[threadCount];
+ for (int i = 0; i < threadCount; i++)
+ consumers[i] = new Consumer(type, done, workQueue, metrics, rateLimiter);
+
+ // starting worker threadCount
for (int i = 0; i < threadCount; i++)
consumers[i].start();
- // initialization of the values
- boolean terminate = false;
- epoch = total = keyCount = 0;
+ metrics.start();
- int interval = client.getProgressInterval();
- int epochIntervals = client.getProgressInterval() * 10;
- long testStartTime = System.nanoTime();
-
- StressStatistics stats = new StressStatistics(client, output);
-
- while (!terminate)
+ if (opCount <= 0)
{
- if (stop)
+ try
{
- producer.stopProducer();
-
- for (Consumer consumer : consumers)
- consumer.stopConsume();
-
- break;
- }
-
- Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
-
- int alive = 0;
- for (Thread thread : consumers)
- if (thread.isAlive()) alive++;
-
- if (alive == 0)
- terminate = true;
-
- epoch++;
-
- if (terminate || epoch > epochIntervals)
- {
- epoch = 0;
-
- oldTotal = total;
- oldKeyCount = keyCount;
-
- total = client.operations.get();
- keyCount = client.keys.get();
- latency = client.latency.getSnapshot();
-
- int opDelta = total - oldTotal;
- int keyDelta = keyCount - oldKeyCount;
-
- long currentTimeInSeconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - testStartTime);
-
- output.println(String.format("%d,%d,%d,%.1f,%.1f,%.1f,%d",
- total,
- opDelta / interval,
- keyDelta / interval,
- latency.getMedian(), latency.get95thPercentile(), latency.get999thPercentile(),
- currentTimeInSeconds));
-
- if (client.outputStatistics()) {
- stats.addIntervalStats(total,
- opDelta / interval,
- keyDelta / interval,
- latency,
- currentTimeInSeconds);
- }
- }
+ metrics.waitUntilConverges(settings.command.targetUncertainty,
+ settings.command.minimumUncertaintyMeasurements,
+ settings.command.maximumUncertaintyMeasurements);
+ } catch (InterruptedException e) { }
+ workQueue.stop();
}
- // if any consumer failed, set the return code to failure.
- returnCode = SUCCESS;
- if (producer.isAlive())
+ try
{
- producer.interrupt(); // if producer is still alive it means that we had errors in the consumers
- returnCode = FAILURE;
- }
+ done.await();
+ metrics.stop();
+ } catch (InterruptedException e) {}
+
+ if (metrics.wasCancelled())
+ return null;
+
+ metrics.summarise();
+
+ boolean success = true;
for (Consumer consumer : consumers)
- if (consumer.getReturnCode() == FAILURE)
- returnCode = FAILURE;
+ success &= consumer.success;
- if (returnCode == SUCCESS) {
- if (client.outputStatistics())
- stats.printStats();
- // marking an end of the output to the client
- output.println("END");
- } else {
- output.println("FAILURE");
- }
+ if (!success)
+ return null;
+ return metrics;
}
- public int getReturnCode()
- {
- return returnCode;
- }
-
- /**
- * Produces exactly N items (awaits each to be consumed)
- */
- private class Producer extends Thread
- {
- private volatile boolean stop = false;
-
- public void run()
- {
- for (int i = 0; i < client.getNumKeys(); i++)
- {
- if (stop)
- break;
-
- try
- {
- operations.put(createOperation(i % client.getNumDifferentKeys()));
- }
- catch (InterruptedException e)
- {
- if (e.getMessage() != null)
- System.err.println("Producer error - " + e.getMessage());
- return;
- }
- }
- }
-
- public void stopProducer()
- {
- stop = true;
- }
- }
-
- /**
- * Each consumes exactly N items from queue
- */
private class Consumer extends Thread
{
- private final int items;
- private final RateLimiter rateLimiter;
- private volatile boolean stop = false;
- private volatile int returnCode = StressAction.SUCCESS;
- public Consumer(int toConsume, RateLimiter rateLimiter)
+ private final Operation.State state;
+ private final RateLimiter rateLimiter;
+ private volatile boolean success = true;
+ private final WorkQueue workQueue;
+ private final CountDownLatch done;
+
+ public Consumer(Command type, CountDownLatch done, WorkQueue workQueue, StressMetrics metrics, RateLimiter rateLimiter)
{
- items = toConsume;
+ this.done = done;
this.rateLimiter = rateLimiter;
+ this.workQueue = workQueue;
+ this.state = new Operation.State(type, settings, metrics);
}
public void run()
{
- if (client.use_native_protocol)
+
+ try
{
- SimpleClient connection = client.getNativeClient();
- for (int i = 0; i < items; i++)
+ SimpleClient sclient = null;
+ ThriftClient tclient = null;
+ JavaDriverClient jclient = null;
+
+ switch (settings.mode.api)
{
- if (stop)
+ case JAVA_DRIVER_NATIVE:
+ jclient = settings.getJavaDriverClient();
break;
+ case SIMPLE_NATIVE:
+ sclient = settings.getSimpleNativeClient();
+ break;
+ case THRIFT:
+ tclient = settings.getThriftClient();
+ break;
+ case THRIFT_SMART:
+ tclient = settings.getSmartThriftClient();
+ break;
+ default:
+ throw new IllegalStateException();
+ }
- try
+ Work work;
+ while ( null != (work = workQueue.poll()) )
+ {
+
+ if (rateLimiter != null)
+ rateLimiter.acquire(work.count);
+
+ for (int i = 0 ; i < work.count ; i++)
{
- rateLimiter.acquire();
- operations.take().run(connection); // running job
- }
- catch (Exception e)
- {
- if (output == null)
+ try
{
- System.err.println(e.getMessage());
- returnCode = StressAction.FAILURE;
- System.exit(-1);
- }
+ Operation op = createOperation(state, i + work.offset);
+ switch (settings.mode.api)
+ {
+ case JAVA_DRIVER_NATIVE:
+ op.run(jclient);
+ break;
+ case SIMPLE_NATIVE:
+ op.run(sclient);
+ break;
+ default:
+ op.run(tclient);
+ }
+ } catch (Exception e)
+ {
+ if (output == null)
+ {
+ System.err.println(e.getMessage());
+ success = false;
+ System.exit(-1);
+ }
- output.println(e.getMessage());
- returnCode = StressAction.FAILURE;
- break;
+ e.printStackTrace(output);
+ success = false;
+ workQueue.stop();
+ state.metrics.cancel();
+ return;
+ }
}
}
+
}
- else
+ finally
{
- CassandraClient connection = client.getClient();
-
- for (int i = 0; i < items; i++)
- {
- if (stop)
- break;
-
- try
- {
- rateLimiter.acquire();
- operations.take().run(connection); // running job
- }
- catch (Exception e)
- {
- if (output == null)
- {
- System.err.println(e.getMessage());
- returnCode = StressAction.FAILURE;
- System.exit(-1);
- }
-
- output.println(e.getMessage());
- returnCode = StressAction.FAILURE;
- break;
- }
- }
+ done.countDown();
+ state.timer.close();
}
+
}
- public void stopConsume()
+ }
+
+ private interface WorkQueue
+ {
+ // null indicates consumer should terminate
+ Work poll();
+
+ // signal all consumers to terminate
+ void stop();
+ }
+
+ private static final class Work
+ {
+ // index of operations
+ final long offset;
+
+ // how many operations to perform
+ final int count;
+
+ public Work(long offset, int count)
+ {
+ this.offset = offset;
+ this.count = count;
+ }
+ }
+
+ private static final class FixedWorkQueue implements WorkQueue
+ {
+
+ final ArrayBlockingQueue work;
+ volatile boolean stop = false;
+
+ public FixedWorkQueue(ArrayBlockingQueue work)
+ {
+ this.work = work;
+ }
+
+ @Override
+ public Work poll()
+ {
+ if (stop)
+ return null;
+ return work.poll();
+ }
+
+ @Override
+ public void stop()
{
stop = true;
}
- public int getReturnCode()
+ static FixedWorkQueue build(long operations)
{
- return returnCode;
+ // target splitting into around 50-500k items, with a minimum size of 20
+ if (operations > Integer.MAX_VALUE * (1L << 19))
+ throw new IllegalStateException("Cannot currently support more than approx 2^50 operations for one stress run. This is a LOT.");
+ int batchSize = (int) (operations / (1 << 19));
+ if (batchSize < 20)
+ batchSize = 20;
+ ArrayBlockingQueue work = new ArrayBlockingQueue(
+ (int) ((operations / batchSize)
+ + (operations % batchSize == 0 ? 0 : 1))
+ );
+ long offset = 0;
+ while (offset < operations)
+ {
+ work.add(new Work(offset, (int) Math.min(batchSize, operations - offset)));
+ offset += batchSize;
+ }
+ return new FixedWorkQueue(work);
}
+
}
- private Operation createOperation(int index)
+ private static final class ContinuousWorkQueue implements WorkQueue
{
- switch (client.getOperation())
+
+ final AtomicLong offset = new AtomicLong();
+ final int batchSize;
+ volatile boolean stop = false;
+
+ private ContinuousWorkQueue(int batchSize)
+ {
+ this.batchSize = batchSize;
+ }
+
+ @Override
+ public Work poll()
+ {
+ if (stop)
+ return null;
+ return new Work(nextOffset(), batchSize);
+ }
+
+ private long nextOffset()
+ {
+ final int inc = batchSize;
+ while (true)
+ {
+ final long cur = offset.get();
+ if (offset.compareAndSet(cur, cur + inc))
+ return cur;
+ }
+ }
+
+ @Override
+ public void stop()
+ {
+ stop = true;
+ }
+
+ }
+
+ private Operation createOperation(Operation.State state, long index)
+ {
+ return createOperation(state.type, state, index);
+ }
+ private Operation createOperation(Command type, Operation.State state, long index)
+ {
+ switch (type)
{
case READ:
- return client.isCQL() ? new CqlReader(client, index) : new Reader(client, index);
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftReader(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlReader(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
- case COUNTER_GET:
- return client.isCQL() ? new CqlCounterGetter(client, index) : new CounterGetter(client, index);
- case INSERT:
- return client.isCQL() ? new CqlInserter(client, index) : new Inserter(client, index);
+ case COUNTERREAD:
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftCounterGetter(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlCounterGetter(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
- case COUNTER_ADD:
- return client.isCQL() ? new CqlCounterAdder(client, index) : new CounterAdder(client, index);
+ case WRITE:
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftInserter(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlInserter(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
- case RANGE_SLICE:
- return client.isCQL() ? new CqlRangeSlicer(client, index) : new RangeSlicer(client, index);
+ case COUNTERWRITE:
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftCounterAdder(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlCounterAdder(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
- case INDEXED_RANGE_SLICE:
- return client.isCQL() ? new CqlIndexedRangeSlicer(client, index) : new IndexedRangeSlicer(client, index);
+ case RANGESLICE:
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftRangeSlicer(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlRangeSlicer(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
+
+ case IRANGESLICE:
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftIndexedRangeSlicer(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlIndexedRangeSlicer(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
+
+ case READMULTI:
+ switch(state.settings.mode.style)
+ {
+ case THRIFT:
+ return new ThriftMultiGetter(state, index);
+ case CQL:
+ case CQL_PREPARED:
+ return new CqlMultiGetter(state, index);
+ default:
+ throw new UnsupportedOperationException();
+ }
+
+ case MIXED:
+ return createOperation(state.readWriteSelector.next(), state, index);
- case MULTI_GET:
- return client.isCQL() ? new CqlMultiGetter(client, index) : new MultiGetter(client, index);
}
throw new UnsupportedOperationException();
}
- public void stopAction()
- {
- stop = true;
- }
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/StressMetrics.java b/tools/stress/src/org/apache/cassandra/stress/StressMetrics.java
new file mode 100644
index 0000000000..b9f1a472cd
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/StressMetrics.java
@@ -0,0 +1,178 @@
+package org.apache.cassandra.stress;
+
+import java.io.PrintStream;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadFactory;
+
+import org.apache.cassandra.concurrent.NamedThreadFactory;
+import org.apache.cassandra.stress.util.Timing;
+import org.apache.cassandra.stress.util.TimingInterval;
+import org.apache.cassandra.stress.util.Uncertainty;
+import org.apache.commons.lang3.time.DurationFormatUtils;
+
+public class StressMetrics
+{
+
+ private static final ThreadFactory tf = new NamedThreadFactory("StressMetrics");
+
+ private final PrintStream output;
+ private final Thread thread;
+ private volatile boolean stop = false;
+ private volatile boolean cancelled = false;
+ private final Uncertainty opRateUncertainty = new Uncertainty();
+ private final CountDownLatch stopped = new CountDownLatch(1);
+ private final Timing timing = new Timing();
+
+ public StressMetrics(PrintStream output, final long logIntervalMillis)
+ {
+ this.output = output;
+ printHeader("", output);
+ thread = tf.newThread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ timing.start();
+ try {
+
+ while (!stop)
+ {
+ try
+ {
+ long sleep = timing.getHistory().endMillis() + logIntervalMillis - System.currentTimeMillis();
+ if (sleep < logIntervalMillis >>> 3)
+ // if had a major hiccup, sleep full interval
+ Thread.sleep(logIntervalMillis);
+ else
+ Thread.sleep(sleep);
+ update();
+ } catch (InterruptedException e)
+ {
+ break;
+ }
+ }
+
+ update();
+ }
+ catch (InterruptedException e)
+ {}
+ catch (Exception e)
+ {
+ cancel();
+ e.printStackTrace(StressMetrics.this.output);
+ }
+ finally
+ {
+ stopped.countDown();
+ }
+ }
+ });
+ }
+
+ public void start()
+ {
+ thread.start();
+ }
+
+ public void waitUntilConverges(double targetUncertainty, int minMeasurements, int maxMeasurements) throws InterruptedException
+ {
+ opRateUncertainty.await(targetUncertainty, minMeasurements, maxMeasurements);
+ }
+
+ public void cancel()
+ {
+ cancelled = true;
+ stop = true;
+ thread.interrupt();
+ opRateUncertainty.wakeAll();
+ }
+
+ public void stop() throws InterruptedException
+ {
+ stop = true;
+ thread.interrupt();
+ stopped.await();
+ }
+
+ private void update() throws InterruptedException
+ {
+ TimingInterval interval = timing.snapInterval();
+ printRow("", interval, timing.getHistory(), opRateUncertainty, output);
+ opRateUncertainty.update(interval.adjustedOpRate());
+ }
+
+
+ // PRINT FORMATTING
+
+ public static final String HEADFORMAT = "%-10s,%8s,%8s,%8s,%8s,%8s,%8s,%8s,%8s,%8s,%7s,%9s";
+ public static final String ROWFORMAT = "%-10d,%8.0f,%8.0f,%8.0f,%8.1f,%8.1f,%8.1f,%8.1f,%8.1f,%8.1f,%7.1f,%9.5f";
+
+ private static void printHeader(String prefix, PrintStream output)
+ {
+ output.println(prefix + String.format(HEADFORMAT, "ops","op/s", "adj op/s","key/s","mean","med",".95",".99",".999","max","time","stderr"));
+ }
+
+ private static void printRow(String prefix, TimingInterval interval, TimingInterval total, Uncertainty opRateUncertainty, PrintStream output)
+ {
+ output.println(prefix + String.format(ROWFORMAT,
+ total.operationCount,
+ interval.realOpRate(),
+ interval.adjustedOpRate(),
+ interval.keyRate(),
+ interval.meanLatency(),
+ interval.medianLatency(),
+ interval.rankLatency(0.95f),
+ interval.rankLatency(0.99f),
+ interval.rankLatency(0.999f),
+ interval.maxLatency(),
+ total.runTime() / 1000f,
+ opRateUncertainty.getUncertainty()));
+ }
+
+ public void summarise()
+ {
+ output.println("\n");
+ output.println("Results:");
+ TimingInterval history = timing.getHistory();
+ output.println(String.format("real op rate : %.0f", history.realOpRate()));
+ output.println(String.format("adjusted op rate : %.0f", history.adjustedOpRate()));
+ output.println(String.format("adjusted op rate stderr : %.0f", opRateUncertainty.getUncertainty()));
+ output.println(String.format("key rate : %.0f", history.keyRate()));
+ output.println(String.format("latency mean : %.1f", history.meanLatency()));
+ output.println(String.format("latency median : %.1f", history.medianLatency()));
+ output.println(String.format("latency 95th percentile : %.1f", history.rankLatency(.95f)));
+ output.println(String.format("latency 99th percentile : %.1f", history.rankLatency(0.99f)));
+ output.println(String.format("latency 99.9th percentile : %.1f", history.rankLatency(0.999f)));
+ output.println(String.format("latency max : %.1f", history.maxLatency()));
+ output.println("Total operation time : " + DurationFormatUtils.formatDuration(
+ history.runTime(), "HH:mm:ss", true));
+ }
+
+ public static final void summarise(List ids, List summarise, PrintStream out)
+ {
+ int idLen = 0;
+ for (String id : ids)
+ idLen = Math.max(id.length(), idLen);
+ String formatstr = "%" + idLen + "s, ";
+ printHeader(String.format(formatstr, "id"), out);
+ for (int i = 0 ; i < ids.size() ; i++)
+ printRow(String.format(formatstr, ids.get(i)),
+ summarise.get(i).timing.getHistory(),
+ summarise.get(i).timing.getHistory(),
+ summarise.get(i).opRateUncertainty,
+ out
+ );
+ }
+
+ public Timing getTiming()
+ {
+ return timing;
+ }
+
+ public boolean wasCancelled()
+ {
+ return cancelled;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/StressServer.java b/tools/stress/src/org/apache/cassandra/stress/StressServer.java
index 6600dfd444..3c9e2a669c 100644
--- a/tools/stress/src/org/apache/cassandra/stress/StressServer.java
+++ b/tools/stress/src/org/apache/cassandra/stress/StressServer.java
@@ -1,27 +1,30 @@
/**
- * 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.
- */
+* 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;
import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.PrintStream;
import java.net.InetAddress;
import java.net.ServerSocket;
+import java.net.Socket;
-import org.apache.cassandra.stress.server.StressThread;
+import org.apache.cassandra.stress.settings.StressSettings;
import org.apache.commons.cli.*;
public class StressServer
@@ -68,4 +71,57 @@ public class StressServer
for (;;)
new StressThread(serverSocket.accept()).start();
}
+
+ public static 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((StressSettings) in.readObject(), out);
+ Thread actionThread = new Thread(action);
+ actionThread.start();
+
+ while (actionThread.isAlive())
+ {
+ try
+ {
+ if (in.readInt() == 1)
+ {
+ actionThread.interrupt();
+ 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();
+ }
+ }
+
+ }
+
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/StressStatistics.java b/tools/stress/src/org/apache/cassandra/stress/StressStatistics.java
deleted file mode 100644
index b739c8e457..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/StressStatistics.java
+++ /dev/null
@@ -1,126 +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;
-
-import java.io.PrintStream;
-import org.apache.commons.lang3.time.DurationFormatUtils;
-
-import com.yammer.metrics.stats.Snapshot;
-
-
-/**
- * Gathers and aggregates statistics for an operation
- */
-public class StressStatistics
-{
-
- private Session client;
- private PrintStream output;
-
- private long durationInSeconds;
- /** The sum of the interval_op_rate values collected by tallyAverages */
- private int tallyOpRateSum;
- /** The number of interval_op_rate values collected by tallyAverages */
- private int tallyOpRateCount;
- /** The sum of the interval_key_rate values collected by tallyAverages */
- private int tallyKeyRateSum;
- /** The number of interval_key_rate values collected by tallyAverages */
- private int tallyKeyRateCount;
-
- /** The sum of the latency values collected by tallyAverages */
- private double tallyLatencySum;
- /** The number of latency values collected by tallyAverages */
- private int tallyLatencyCount;
- /** The sum of the 95%tile latency values collected by tallyAverages */
- private double tally95thLatencySum;
- /** The number of 95%tile latency values collected by tallyAverages */
- private int tally95thLatencyCount;
- /** The sum of the 99.9%tile latency values collected by tallyAverages */
- private double tally999thLatencySum;
- /** The number of 99.9%tile latency values collected by tallyAverages */
- private int tally999thLatencyCount;
-
-
- public StressStatistics(Session client, PrintStream out)
- {
- this.client = client;
- this.output = out;
-
- tallyOpRateSum = 0;
- tallyOpRateCount = 0;
- }
-
- /**
- * Collect statistics per-interval
- */
- public void addIntervalStats(int totalOperations, int intervalOpRate,
- int intervalKeyRate, Snapshot latency,
- long currentTimeInSeconds)
- {
- this.tallyAverages(totalOperations, intervalKeyRate, intervalKeyRate,
- latency, currentTimeInSeconds);
- }
-
- /**
- * Collect interval_op_rate and interval_key_rate averages
- */
- private void tallyAverages(int totalOperations, int intervalOpRate,
- int intervalKeyRate, Snapshot latency,
- long currentTimeInSeconds)
- {
- //Skip the first and last 10% of values.
- //The middle values of the operation are the ones worthwhile
- //to collect and average:
- if (totalOperations > (0.10 * client.getNumKeys()) &&
- totalOperations < (0.90 * client.getNumKeys())) {
- tallyOpRateSum += intervalOpRate;
- tallyOpRateCount += 1;
- tallyKeyRateSum += intervalKeyRate;
- tallyKeyRateCount += 1;
- tallyLatencySum += latency.getMedian();
- tallyLatencyCount += 1;
- tally95thLatencySum += latency.get95thPercentile();
- tally95thLatencyCount += 1;
- tally999thLatencySum += latency.get999thPercentile();
- tally999thLatencyCount += 1;
- }
- durationInSeconds = currentTimeInSeconds;
- }
-
- public void printStats()
- {
- output.println("\n");
- if (tallyOpRateCount > 0) {
- output.println("Averages from the middle 80% of values:");
- output.println(String.format("interval_op_rate : %d",
- (tallyOpRateSum / tallyOpRateCount)));
- output.println(String.format("interval_key_rate : %d",
- (tallyKeyRateSum / tallyKeyRateCount)));
- output.println(String.format("latency median : %.1f",
- (tallyLatencySum / tallyLatencyCount)));
- output.println(String.format("latency 95th percentile : %.1f",
- (tally95thLatencySum / tally95thLatencyCount)));
- output.println(String.format("latency 99.9th percentile : %.1f",
- (tally999thLatencySum / tally999thLatencyCount)));
- }
- output.println("Total operation time : " + DurationFormatUtils.formatDuration(
- durationInSeconds*1000, "HH:mm:ss", true));
- }
-
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGen.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGen.java
new file mode 100644
index 0000000000..4c22005936
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGen.java
@@ -0,0 +1,18 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public abstract class DataGen
+{
+
+ public abstract void generate(ByteBuffer fill, long offset);
+ public abstract boolean isDeterministic();
+
+ public void generate(List fills, long offset)
+ {
+ for (ByteBuffer fill : fills)
+ generate(fill, offset++);
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenBytesRandom.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenBytesRandom.java
new file mode 100644
index 0000000000..3906f93c3b
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenBytesRandom.java
@@ -0,0 +1,24 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+public class DataGenBytesRandom extends DataGen
+{
+
+ private final Random rnd = new Random();
+
+ @Override
+ public void generate(ByteBuffer fill, long offset)
+ {
+ fill.clear();
+ rnd.nextBytes(fill.array());
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return false;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenFactory.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenFactory.java
new file mode 100644
index 0000000000..c5738cca0f
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenFactory.java
@@ -0,0 +1,9 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.io.Serializable;
+
+public interface DataGenFactory extends Serializable
+{
+ DataGen get();
+}
+
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHex.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHex.java
new file mode 100644
index 0000000000..50d49dd506
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHex.java
@@ -0,0 +1,39 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+
+public abstract class DataGenHex extends DataGen
+{
+
+ abstract long next(long operationIndex);
+
+ @Override
+ public final void generate(ByteBuffer fill, long operationIndex)
+ {
+ fill.clear();
+ fillKeyStringBytes(next(operationIndex), fill.array());
+ }
+
+ public static void fillKeyStringBytes(long key, byte[] fill)
+ {
+ int ub = fill.length - 1;
+ int offset = 0;
+ while (key != 0)
+ {
+ int digit = ((int) key) & 15;
+ key >>>= 4;
+ fill[ub - offset++] = digit(digit);
+ }
+ while (offset < fill.length)
+ fill[ub - offset++] = '0';
+ }
+
+ // needs to be UTF-8, but for these chars there is no difference
+ private static byte digit(int num)
+ {
+ if (num < 10)
+ return (byte)('0' + num);
+ return (byte)('A' + (num - 10));
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHexFromDistribution.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHexFromDistribution.java
new file mode 100644
index 0000000000..3391fced59
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHexFromDistribution.java
@@ -0,0 +1,45 @@
+package org.apache.cassandra.stress.generatedata;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+
+public class DataGenHexFromDistribution extends DataGenHex
+{
+
+ final Distribution distribution;
+
+ public DataGenHexFromDistribution(Distribution distribution)
+ {
+ this.distribution = distribution;
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return false;
+ }
+
+ @Override
+ long next(long operationIndex)
+ {
+ return distribution.next();
+ }
+
+ public static DataGenHex buildGaussian(long minKey, long maxKey, double stdevsToLimit)
+ {
+ double midRange = (maxKey + minKey) / 2d;
+ double halfRange = (maxKey - minKey) / 2d;
+ return new DataGenHexFromDistribution(new DistributionBoundApache(new NormalDistribution(midRange, halfRange / stdevsToLimit), minKey, maxKey));
+ }
+
+ public static DataGenHex buildGaussian(long minKey, long maxKey, double mean, double stdev)
+ {
+ return new DataGenHexFromDistribution(new DistributionBoundApache(new NormalDistribution(mean, stdev), minKey, maxKey));
+ }
+
+ public static DataGenHex buildUniform(long minKey, long maxKey)
+ {
+ return new DataGenHexFromDistribution(new DistributionBoundApache(new UniformRealDistribution(minKey, maxKey), minKey, maxKey));
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHexFromOpIndex.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHexFromOpIndex.java
new file mode 100644
index 0000000000..5d499d56a0
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenHexFromOpIndex.java
@@ -0,0 +1,27 @@
+package org.apache.cassandra.stress.generatedata;
+
+public class DataGenHexFromOpIndex extends DataGenHex
+{
+
+ final long minKey;
+ final long maxKey;
+
+ public DataGenHexFromOpIndex(long minKey, long maxKey)
+ {
+ this.minKey = minKey;
+ this.maxKey = maxKey;
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return true;
+ }
+
+ @Override
+ long next(long operationIndex)
+ {
+ long range = maxKey + 1 - minKey;
+ return Math.abs((operationIndex % range) + minKey);
+ }
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenStringDictionary.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenStringDictionary.java
new file mode 100644
index 0000000000..68c80348d7
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenStringDictionary.java
@@ -0,0 +1,84 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.distribution.EnumeratedDistribution;
+import org.apache.commons.math3.util.Pair;
+
+import static com.google.common.base.Charsets.UTF_8;
+
+public class DataGenStringDictionary extends DataGen
+{
+
+ private final byte space = ' ';
+ private final EnumeratedDistribution words;
+
+ public DataGenStringDictionary(EnumeratedDistribution wordDistribution)
+ {
+ words = wordDistribution;
+ }
+
+ @Override
+ public void generate(ByteBuffer fill, long index)
+ {
+ fill(fill, 0);
+ }
+
+ @Override
+ public void generate(List fills, long index)
+ {
+ for (int i = 0 ; i < fills.size() ; i++)
+ fill(fills.get(0), i);
+ }
+
+ private void fill(ByteBuffer fill, int column)
+ {
+ fill.clear();
+ byte[] trg = fill.array();
+ int i = 0;
+ while (i < trg.length)
+ {
+ if (i > 0)
+ trg[i++] = space;
+ byte[] src = words.sample();
+ System.arraycopy(src, 0, trg, i, Math.min(src.length, trg.length - i));
+ i += src.length;
+ }
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return true;
+ }
+
+ public static DataGenFactory getFactory(File file) throws IOException
+ {
+ final List> words = new ArrayList<>();
+ final BufferedReader reader = new BufferedReader(new FileReader(file));
+ String line;
+ while ( null != (line = reader.readLine()) )
+ {
+ String[] pair = line.split(" +");
+ if (pair.length != 2)
+ throw new IllegalArgumentException("Invalid record in dictionary: \"" + line + "\"");
+ words.add(new Pair<>(pair[1].getBytes(UTF_8), Double.parseDouble(pair[0])));
+ }
+ final EnumeratedDistribution dist = new EnumeratedDistribution(words);
+ return new DataGenFactory()
+ {
+ @Override
+ public DataGen get()
+ {
+ return new DataGenStringDictionary(dist);
+ }
+ };
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenStringRepeats.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenStringRepeats.java
new file mode 100644
index 0000000000..47091f7273
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DataGenStringRepeats.java
@@ -0,0 +1,69 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.cassandra.utils.FBUtilities;
+
+import static com.google.common.base.Charsets.UTF_8;
+
+public class DataGenStringRepeats extends DataGen
+{
+
+ private static final ConcurrentHashMap> CACHE_LOOKUP = new ConcurrentHashMap<>();
+
+ private final ConcurrentHashMap cache;
+ private final int repeatFrequency;
+ public DataGenStringRepeats(int repeatFrequency)
+ {
+ if (!CACHE_LOOKUP.containsKey(repeatFrequency))
+ CACHE_LOOKUP.putIfAbsent(repeatFrequency, new ConcurrentHashMap());
+ cache = CACHE_LOOKUP.get(repeatFrequency);
+ this.repeatFrequency = repeatFrequency;
+ }
+
+ @Override
+ public void generate(ByteBuffer fill, long index)
+ {
+ fill(fill, index, 0);
+ }
+
+ @Override
+ public void generate(List fills, long index)
+ {
+ for (int i = 0 ; i < fills.size() ; i++)
+ {
+ fill(fills.get(i), index, i);
+ }
+ }
+
+ private void fill(ByteBuffer fill, long index, int column)
+ {
+ fill.clear();
+ byte[] trg = fill.array();
+ byte[] src = getData(index, column);
+ for (int j = 0 ; j < trg.length ; j += src.length)
+ System.arraycopy(src, 0, trg, j, Math.min(src.length, trg.length - j));
+ }
+
+ private byte[] getData(long index, int column)
+ {
+ final long key = (column * repeatFrequency) + (index % repeatFrequency);
+ byte[] r = cache.get(key);
+ if (r != null)
+ return r;
+ MessageDigest md = FBUtilities.threadLocalMD5Digest();
+ r = md.digest(Long.toString(key).getBytes(UTF_8));
+ cache.putIfAbsent(key, r);
+ return r;
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return true;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/Distribution.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/Distribution.java
new file mode 100644
index 0000000000..5236eab2c9
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/Distribution.java
@@ -0,0 +1,19 @@
+package org.apache.cassandra.stress.generatedata;
+
+public abstract class Distribution
+{
+
+ public abstract long next();
+ public abstract long inverseCumProb(double cumProb);
+
+ public long maxValue()
+ {
+ return inverseCumProb(1d);
+ }
+
+ public long minValue()
+ {
+ return inverseCumProb(0d);
+ }
+
+}
\ No newline at end of file
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionBoundApache.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionBoundApache.java
new file mode 100644
index 0000000000..9f59dbd269
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionBoundApache.java
@@ -0,0 +1,42 @@
+package org.apache.cassandra.stress.generatedata;
+
+import org.apache.commons.math3.distribution.AbstractRealDistribution;
+
+public class DistributionBoundApache extends Distribution
+{
+
+ final AbstractRealDistribution delegate;
+ final long min, max;
+
+ public DistributionBoundApache(AbstractRealDistribution delegate, long min, long max)
+ {
+ this.delegate = delegate;
+ this.min = min;
+ this.max = max;
+ }
+
+ @Override
+ public long next()
+ {
+ return bound(min, max, delegate.sample());
+ }
+
+ @Override
+ public long inverseCumProb(double cumProb)
+ {
+ return bound(min, max, delegate.inverseCumulativeProbability(cumProb));
+ }
+
+ private static long bound(long min, long max, double val)
+ {
+ long r = (long) val;
+ if ((r >= min) & (r <= max))
+ return r;
+ if (r < min)
+ return min;
+ if (r > max)
+ return max;
+ throw new IllegalStateException();
+ }
+
+}
\ No newline at end of file
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionFactory.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionFactory.java
new file mode 100644
index 0000000000..ac2b7ba6c5
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionFactory.java
@@ -0,0 +1,10 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.io.Serializable;
+
+public interface DistributionFactory extends Serializable
+{
+
+ Distribution get();
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionFixed.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionFixed.java
new file mode 100644
index 0000000000..6873b1c750
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionFixed.java
@@ -0,0 +1,25 @@
+package org.apache.cassandra.stress.generatedata;
+
+public class DistributionFixed extends Distribution
+{
+
+ final long key;
+
+ public DistributionFixed(long key)
+ {
+ this.key = key;
+ }
+
+ @Override
+ public long next()
+ {
+ return key;
+ }
+
+ @Override
+ public long inverseCumProb(double cumProb)
+ {
+ return key;
+ }
+
+}
\ No newline at end of file
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionOffsetApache.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionOffsetApache.java
new file mode 100644
index 0000000000..c7a5acafc9
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionOffsetApache.java
@@ -0,0 +1,40 @@
+package org.apache.cassandra.stress.generatedata;
+
+import org.apache.commons.math3.distribution.AbstractRealDistribution;
+
+public class DistributionOffsetApache extends Distribution
+{
+
+ final AbstractRealDistribution delegate;
+ final long min, delta;
+
+ public DistributionOffsetApache(AbstractRealDistribution delegate, long min, long max)
+ {
+ this.delegate = delegate;
+ this.min = min;
+ this.delta = max - min;
+ }
+
+ @Override
+ public long next()
+ {
+ return offset(min, delta, delegate.sample());
+ }
+
+ @Override
+ public long inverseCumProb(double cumProb)
+ {
+ return offset(min, delta, delegate.inverseCumulativeProbability(cumProb));
+ }
+
+ private long offset(long min, long delta, double val)
+ {
+ long r = (long) val;
+ if (r < 0)
+ r = 0;
+ if (r > delta)
+ r = delta;
+ return min + r;
+ }
+
+}
\ No newline at end of file
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionSeqBatch.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionSeqBatch.java
new file mode 100644
index 0000000000..a1a51bba43
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/DistributionSeqBatch.java
@@ -0,0 +1,47 @@
+package org.apache.cassandra.stress.generatedata;
+
+public class DistributionSeqBatch extends DataGenHex
+{
+
+ final Distribution delegate;
+ final int batchSize;
+ final long maxKey;
+
+ private int batchIndex;
+ private long batchKey;
+
+ // object must be published safely if passed between threadCount, due to batchIndex not being volatile. various
+ // hacks possible, but not ideal. don't want to use volatile as object intended for single threaded use.
+ public DistributionSeqBatch(int batchSize, long maxKey, Distribution delegate)
+ {
+ this.batchIndex = batchSize;
+ this.batchSize = batchSize;
+ this.maxKey = maxKey;
+ this.delegate = delegate;
+ }
+
+ @Override
+ long next(long operationIndex)
+ {
+ if (batchIndex >= batchSize)
+ {
+ batchKey = delegate.next();
+ batchIndex = 0;
+ }
+ long r = batchKey + batchIndex++;
+ if (r > maxKey)
+ {
+ batchKey = delegate.next();
+ batchIndex = 1;
+ r = batchKey;
+ }
+ return r;
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return false;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/KeyGen.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/KeyGen.java
new file mode 100644
index 0000000000..cdd6d39029
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/KeyGen.java
@@ -0,0 +1,33 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class KeyGen
+{
+
+ final DataGen dataGen;
+ final int keySize;
+ final List keyBuffers = new ArrayList<>();
+
+ public KeyGen(DataGen dataGen, int keySize)
+ {
+ this.dataGen = dataGen;
+ this.keySize = keySize;
+ }
+
+ public List getKeys(int n, long index)
+ {
+ while (keyBuffers.size() < n)
+ keyBuffers.add(ByteBuffer.wrap(new byte[keySize]));
+ dataGen.generate(keyBuffers, index);
+ return keyBuffers;
+ }
+
+ public boolean isDeterministic()
+ {
+ return dataGen.isDeterministic();
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/RowGen.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/RowGen.java
new file mode 100644
index 0000000000..869fbc77d6
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/RowGen.java
@@ -0,0 +1,31 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Generates a row of data, by constructing one byte buffers per column according to some algorithm
+ * and delegating the work of populating the values of those byte buffers to the provided data generator
+ */
+public abstract class RowGen
+{
+
+ final DataGen dataGen;
+ protected RowGen(DataGen dataGenerator)
+ {
+ this.dataGen = dataGenerator;
+ }
+
+ public List generate(long operationIndex)
+ {
+ List fill = getColumns(operationIndex);
+ dataGen.generate(fill, operationIndex);
+ return fill;
+ }
+
+ // these byte[] may be re-used
+ abstract List getColumns(long operationIndex);
+
+ abstract public boolean isDeterministic();
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/generatedata/RowGenDistributedSize.java b/tools/stress/src/org/apache/cassandra/stress/generatedata/RowGenDistributedSize.java
new file mode 100644
index 0000000000..b68ab3ca29
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/generatedata/RowGenDistributedSize.java
@@ -0,0 +1,84 @@
+package org.apache.cassandra.stress.generatedata;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class RowGenDistributedSize extends RowGen
+{
+
+ // TODO - make configurable
+ static final int MAX_SINGLE_CACHE_SIZE = 16 * 1024;
+
+ final Distribution countDistribution;
+ final Distribution sizeDistribution;
+
+ final TreeMap cache = new TreeMap<>();
+
+ // array re-used for returning columns
+ final ByteBuffer[] ret;
+ final int[] sizes;
+
+ public RowGenDistributedSize(DataGen dataGenerator, Distribution countDistribution, Distribution sizeDistribution)
+ {
+ super(dataGenerator);
+ this.countDistribution = countDistribution;
+ this.sizeDistribution = sizeDistribution;
+ ret = new ByteBuffer[(int) countDistribution.maxValue()];
+ sizes = new int[ret.length];
+ }
+
+ ByteBuffer getBuffer(int size)
+ {
+ if (size >= MAX_SINGLE_CACHE_SIZE)
+ return ByteBuffer.allocate(size);
+ Map.Entry found = cache.ceilingEntry(size);
+ if (found == null)
+ {
+ // remove the next entry down, and replace it with a cache of this size
+ Integer del = cache.lowerKey(size);
+ if (del != null)
+ cache.remove(del);
+ return ByteBuffer.allocate(size);
+ }
+ ByteBuffer r = found.getValue();
+ cache.remove(found.getKey());
+ return r;
+ }
+
+ @Override
+ List getColumns(long operationIndex)
+ {
+ int i = 0;
+ int count = (int) countDistribution.next();
+ while (i < count)
+ {
+ int columnSize = (int) sizeDistribution.next();
+ sizes[i] = columnSize;
+ ret[i] = getBuffer(columnSize);
+ i++;
+ }
+ while (i < ret.length && ret[i] != null)
+ ret[i] = null;
+ i = 0;
+ while (i < count)
+ {
+ ByteBuffer b = ret[i];
+ cache.put(b.capacity(), b);
+ b.position(b.capacity() - sizes[i]);
+ ret[i] = b.slice();
+ b.position(0);
+ i++;
+ }
+ return Arrays.asList(ret).subList(0, count);
+ }
+
+ @Override
+ public boolean isDeterministic()
+ {
+ return false;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CQLOperation.java b/tools/stress/src/org/apache/cassandra/stress/operations/CQLOperation.java
deleted file mode 100644
index 54737a4c17..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CQLOperation.java
+++ /dev/null
@@ -1,96 +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.operations;
-
-import java.nio.ByteBuffer;
-import java.io.IOException;
-import java.util.List;
-
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.SimpleClient;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
-import org.apache.cassandra.thrift.ThriftConversion;
-
-public abstract class CQLOperation extends Operation
-{
- public CQLOperation(Session client, int idx)
- {
- super(client, idx);
- }
-
- protected abstract void run(CQLQueryExecutor executor) throws IOException;
-
- protected abstract boolean validateThriftResult(CqlResult result);
-
- protected abstract boolean validateNativeResult(ResultMessage result);
-
- public void run(final CassandraClient client) throws IOException
- {
- run(new CQLQueryExecutor()
- {
- public boolean execute(String cqlQuery, List queryParams) throws Exception
- {
- CqlResult result = null;
- if (session.usePreparedStatements())
- {
- Integer stmntId = getPreparedStatement(client, cqlQuery);
- if (session.cqlVersion.startsWith("3"))
- result = client.execute_prepared_cql3_query(stmntId, queryParamsAsByteBuffer(queryParams), session.getConsistencyLevel());
- else
- result = client.execute_prepared_cql_query(stmntId, queryParamsAsByteBuffer(queryParams));
- }
- else
- {
- String formattedQuery = formatCqlQuery(cqlQuery, queryParams);
- if (session.cqlVersion.startsWith("3"))
- result = client.execute_cql3_query(ByteBuffer.wrap(formattedQuery.getBytes()), Compression.NONE, session.getConsistencyLevel());
- else
- result = client.execute_cql_query(ByteBuffer.wrap(formattedQuery.getBytes()), Compression.NONE);
- }
- return validateThriftResult(result);
- }
- });
- }
-
- public void run(final SimpleClient client) throws IOException
- {
- run(new CQLQueryExecutor()
- {
- public boolean execute(String cqlQuery, List queryParams) throws Exception
- {
- ResultMessage result = null;
- if (session.usePreparedStatements())
- {
- byte[] stmntId = getPreparedStatement(client, cqlQuery);
- result = client.executePrepared(stmntId, queryParamsAsByteBuffer(queryParams), ThriftConversion.fromThrift(session.getConsistencyLevel()));
- }
- else
- {
- String formattedQuery = formatCqlQuery(cqlQuery, queryParams);
- result = client.execute(formattedQuery, ThriftConversion.fromThrift(session.getConsistencyLevel()));
- }
- return validateNativeResult(result);
- }
- });
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CounterAdder.java b/tools/stress/src/org/apache/cassandra/stress/operations/CounterAdder.java
deleted file mode 100644
index ab6ae9ded8..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CounterAdder.java
+++ /dev/null
@@ -1,141 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.thrift.*;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class CounterAdder extends Operation
-{
- public CounterAdder(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- List columns = new ArrayList();
- List superColumns = new ArrayList();
-
- // format used for keys
- String format = "%0" + session.getTotalKeysLength() + "d";
-
- for (int i = 0; i < session.getColumnsPerKey(); i++)
- {
- String columnName = ("C" + Integer.toString(i));
-
- columns.add(new CounterColumn(ByteBufferUtil.bytes(columnName), 1L));
- }
-
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- {
- // supers = [SuperColumn('S' + str(j), columns) for j in xrange(supers_per_key)]
- for (int i = 0; i < session.getSuperColumns(); i++)
- {
- String superColumnName = "S" + Integer.toString(i);
- superColumns.add(new CounterSuperColumn(ByteBuffer.wrap(superColumnName.getBytes()), columns));
- }
- }
-
- String rawKey = String.format(format, index);
- Map>> record = new HashMap>>();
-
- record.put(ByteBufferUtil.bytes(rawKey), session.getColumnFamilyType() == ColumnFamilyType.Super
- ? getSuperColumnsMutationMap(superColumns)
- : getColumnsMutationMap(columns));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- client.batch_mutate(record, session.getConsistencyLevel());
- success = true;
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error incrementing key %s %s%n",
- index,
- session.getRetryTimes(),
- rawKey,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
- }
-
- private Map> getSuperColumnsMutationMap(List superColumns)
- {
- List mutations = new ArrayList();
- Map> mutationMap = new HashMap>();
-
- for (CounterSuperColumn s : superColumns)
- {
- ColumnOrSuperColumn cosc = new ColumnOrSuperColumn().setCounter_super_column(s);
- mutations.add(new Mutation().setColumn_or_supercolumn(cosc));
- }
-
- mutationMap.put("SuperCounter1", mutations);
-
- return mutationMap;
- }
-
- private Map> getColumnsMutationMap(List columns)
- {
- List mutations = new ArrayList();
- Map> mutationMap = new HashMap>();
-
- for (CounterColumn c : columns)
- {
- ColumnOrSuperColumn cosc = new ColumnOrSuperColumn().setCounter_column(c);
- mutations.add(new Mutation().setColumn_or_supercolumn(cosc));
- }
-
- mutationMap.put("Counter1", mutations);
-
- return mutationMap;
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CounterGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/CounterGetter.java
deleted file mode 100644
index 56ef2434c8..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CounterGetter.java
+++ /dev/null
@@ -1,152 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.thrift.*;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-public class CounterGetter extends Operation
-{
- public CounterGetter(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- SliceRange sliceRange = new SliceRange();
-
- // start/finish
- sliceRange.setStart(new byte[] {}).setFinish(new byte[] {});
-
- // reversed/count
- sliceRange.setReversed(false).setCount(session.getColumnsPerKey());
-
- // initialize SlicePredicate with existing SliceRange
- SlicePredicate predicate = new SlicePredicate().setSlice_range(sliceRange);
-
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- {
- runSuperCounterGetter(predicate, client);
- }
- else
- {
- runCounterGetter(predicate, client);
- }
- }
-
- private void runSuperCounterGetter(SlicePredicate predicate, Cassandra.Client client) throws IOException
- {
- byte[] rawKey = generateKey();
- ByteBuffer key = ByteBuffer.wrap(rawKey);
-
- for (int j = 0; j < session.getSuperColumns(); j++)
- {
- String superColumn = 'S' + Integer.toString(j);
- ColumnParent parent = new ColumnParent("SuperCounter1").setSuper_column(superColumn.getBytes());
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- List counters;
- counters = client.get_slice(key, parent, predicate, session.getConsistencyLevel());
- success = (counters.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error reading counter key %s %s%n",
- index,
- session.getRetryTimes(),
- new String(rawKey),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
- }
- }
-
- private void runCounterGetter(SlicePredicate predicate, Cassandra.Client client) throws IOException
- {
- ColumnParent parent = new ColumnParent("Counter1");
-
- byte[] key = generateKey();
- ByteBuffer keyBuffer = ByteBuffer.wrap(key);
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- List counters;
- counters = client.get_slice(keyBuffer, parent, predicate, session.getConsistencyLevel());
- success = (counters.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error reading counter key %s %s%n",
- index,
- session.getRetryTimes(),
- new String(key),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterAdder.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterAdder.java
index 31e8371dd5..8e1f137534 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterAdder.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterAdder.java
@@ -21,102 +21,50 @@ package org.apache.cassandra.stress.operations;
*/
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-public class CqlCounterAdder extends CQLOperation
+public class CqlCounterAdder extends CqlOperation
{
- private static String cqlQuery = null;
-
- public CqlCounterAdder(Session client, int idx)
+ public CqlCounterAdder(State state, long idx)
{
- super(client, idx);
+ super(state, idx);
}
- protected void run(CQLQueryExecutor executor) throws IOException
+ @Override
+ protected String buildQuery()
{
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- throw new RuntimeException("Super columns are not implemented for CQL");
+ String counterCF = state.isCql2() ? "Counter1" : "Counter3";
- if (cqlQuery == null)
+ StringBuilder query = new StringBuilder("UPDATE ").append(wrapInQuotesIfRequired(counterCF));
+
+ if (state.isCql2())
+ query.append(" USING CONSISTENCY ").append(state.settings.command.consistencyLevel);
+
+ query.append(" SET ");
+
+ // TODO : increment distribution subset of columns
+ for (int i = 0; i < state.settings.columns.maxColumnsPerKey; i++)
{
- String counterCF = session.cqlVersion.startsWith("2") ? "Counter1" : "Counter3";
+ if (i > 0)
+ query.append(",");
- StringBuilder query = new StringBuilder("UPDATE ").append(wrapInQuotesIfRequired(counterCF));
-
- if (session.cqlVersion.startsWith("2"))
- query.append(" USING CONSISTENCY ").append(session.getConsistencyLevel());
-
- query.append(" SET ");
-
- for (int i = 0; i < session.getColumnsPerKey(); i++)
- {
- if (i > 0)
- query.append(",");
-
- query.append('C').append(i).append("=C").append(i).append("+1");
- }
- query.append(" WHERE KEY=?");
- cqlQuery = query.toString();
+ query.append('C').append(i).append("=C").append(i).append("+1");
}
-
- String key = String.format("%0" + session.getTotalKeysLength() + "d", index);
- List queryParams = Collections.singletonList(getUnQuotedCqlBlob(key, session.cqlVersion.startsWith("3")));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- success = executor.execute(cqlQuery, queryParams);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error incrementing key %s %s%n",
- index,
- session.getRetryTimes(),
- key,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
+ query.append(" WHERE KEY=?");
+ return query.toString();
}
- protected boolean validateThriftResult(CqlResult result)
+ @Override
+ protected List getQueryParameters(byte[] key)
{
- return true;
+ return Collections.singletonList(ByteBuffer.wrap(key));
}
- protected boolean validateNativeResult(ResultMessage result)
+ @Override
+ protected CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
{
- return true;
+ return new CqlRunOpAlwaysSucceed(client, query, queryId, params, keyid, key, 1);
}
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterGetter.java
index a4d037a97e..0a0b05b565 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterGetter.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlCounterGetter.java
@@ -21,100 +21,48 @@ package org.apache.cassandra.stress.operations;
*/
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
-import org.apache.cassandra.thrift.CqlResultType;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-public class CqlCounterGetter extends CQLOperation
+public class CqlCounterGetter extends CqlOperation
{
- private static String cqlQuery = null;
- public CqlCounterGetter(Session client, int idx)
+ public CqlCounterGetter(State state, long idx)
{
- super(client, idx);
+ super(state, idx);
}
- protected void run(CQLQueryExecutor executor) throws IOException
+ @Override
+ protected List getQueryParameters(byte[] key)
{
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- throw new RuntimeException("Super columns are not implemented for CQL");
-
- if (cqlQuery == null)
- {
- StringBuilder query = new StringBuilder("SELECT ");
-
- if (session.cqlVersion.startsWith("2"))
- query.append("FIRST ").append(session.getColumnsPerKey()).append(" ''..''");
- else
- query.append("*");
-
- String counterCF = session.cqlVersion.startsWith("2") ? "Counter1" : "Counter3";
-
- query.append(" FROM ").append(wrapInQuotesIfRequired(counterCF));
-
- if (session.cqlVersion.startsWith("2"))
- query.append(" USING CONSISTENCY ").append(session.getConsistencyLevel().toString());
-
- cqlQuery = query.append(" WHERE KEY=?").toString();
- }
-
- byte[] key = generateKey();
- List queryParams = Collections.singletonList(getUnQuotedCqlBlob(key, session.cqlVersion.startsWith("3")));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- success = executor.execute(cqlQuery, queryParams);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error reading counter key %s %s%n",
- index,
- session.getRetryTimes(),
- new String(key),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
+ return Collections.singletonList(ByteBuffer.wrap(key));
}
- protected boolean validateThriftResult(CqlResult result)
+ @Override
+ protected String buildQuery()
{
- return result.rows.get(0).columns.size() != 0;
+ StringBuilder query = new StringBuilder("SELECT ");
+
+ if (state.isCql2())
+ query.append("FIRST ").append(state.settings.columns.maxColumnsPerKey).append(" ''..''");
+ else
+ query.append("*");
+
+ String counterCF = state.isCql2() ? "Counter1" : "Counter3";
+
+ query.append(" FROM ").append(wrapInQuotesIfRequired(counterCF));
+
+ if (state.isCql2())
+ query.append(" USING CONSISTENCY ").append(state.settings.command.consistencyLevel);
+
+ return query.append(" WHERE KEY=?").toString();
}
- protected boolean validateNativeResult(ResultMessage result)
+ @Override
+ protected CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
{
- return result instanceof ResultMessage.Rows && ((ResultMessage.Rows)result).result.size() != 0;
+ return new CqlRunOpTestNonEmpty(client, query, queryId, params, keyid, key);
}
+
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlIndexedRangeSlicer.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlIndexedRangeSlicer.java
index bf416cc952..748bf30ee6 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlIndexedRangeSlicer.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlIndexedRangeSlicer.java
@@ -1,179 +1,123 @@
package org.apache.cassandra.stress.operations;
/*
- *
- * 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.
- *
- */
+*
+* 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.
+*
+*/
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.util.Collections;
+import java.util.Arrays;
import java.util.List;
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.cql3.ResultSet;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
-import org.apache.cassandra.thrift.CqlRow;
-import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.stress.settings.SettingsCommandMulti;
+import org.apache.cassandra.utils.FBUtilities;
-public class CqlIndexedRangeSlicer extends CQLOperation
+public class CqlIndexedRangeSlicer extends CqlOperation
{
- private static List values = null;
- private static String cqlQuery = null;
- private int lastQueryResultSize;
- private int lastMaxKey;
+ volatile boolean acceptNoResults = false;
- public CqlIndexedRangeSlicer(Session client, int idx)
+ public CqlIndexedRangeSlicer(State state, long idx)
{
- super(client, idx);
+ super(state, idx);
}
- protected void run(CQLQueryExecutor executor) throws IOException
+ @Override
+ protected List getQueryParameters(byte[] key)
{
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- throw new RuntimeException("Super columns are not implemented for CQL");
+ throw new UnsupportedOperationException();
+ }
- if (values == null)
- values = generateValues();
+ @Override
+ protected String buildQuery()
+ {
+ StringBuilder query = new StringBuilder("SELECT ");
- if (cqlQuery == null)
+ if (state.isCql2())
+ query.append(state.settings.columns.maxColumnsPerKey).append(" ''..''");
+ else
+ query.append("*");
+
+ query.append(" FROM Standard1");
+
+ if (state.isCql2())
+ query.append(" USING CONSISTENCY ").append(state.settings.command.consistencyLevel);
+
+ final String columnName = getColumnName(1);
+ query.append(" WHERE ").append(columnName).append("=?")
+ .append(" AND KEY > ? LIMIT ").append(((SettingsCommandMulti)state.settings.command).keysAtOnce);
+ return query.toString();
+ }
+
+ @Override
+ protected void run(CqlOperation.ClientWrapper client) throws IOException
+ {
+ acceptNoResults = false;
+ final List columns = generateColumnValues();
+ final ByteBuffer value = columns.get(1); // only C1 column is indexed
+ byte[] minKey = new byte[0];
+ int rowCount;
+ do
{
- StringBuilder query = new StringBuilder("SELECT ");
+ List params = Arrays.asList(value, ByteBuffer.wrap(minKey));
+ CqlRunOp op = run(client, params, value, new String(value.array()));
+ byte[][] keys = op.result;
+ rowCount = keys.length;
+ minKey = getNextMinKey(minKey, keys);
+ acceptNoResults = true;
+ } while (rowCount > 0);
+ }
- if (session.cqlVersion.startsWith("2"))
- query.append(session.getColumnsPerKey()).append(" ''..''");
- else
- query.append("*");
+ private final class IndexedRangeSliceRunOp extends CqlRunOpFetchKeys
+ {
- query.append(" FROM Standard1");
-
- if (session.cqlVersion.startsWith("2"))
- query.append(" USING CONSISTENCY ").append(session.getConsistencyLevel());
-
- query.append(" WHERE C1=").append(getUnQuotedCqlBlob(values.get(1).array(), session.cqlVersion.startsWith("3")))
- .append(" AND KEY > ? LIMIT ").append(session.getKeysPerCall());
-
- cqlQuery = query.toString();
+ protected IndexedRangeSliceRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
+ {
+ super(client, query, queryId, params, keyid, key);
}
- String format = "%0" + session.getTotalKeysLength() + "d";
- String startOffset = String.format(format, 0);
-
- int expectedPerValue = session.getNumKeys() / values.size(), received = 0;
-
- while (received < expectedPerValue)
+ @Override
+ public boolean validate(byte[][] result)
{
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
- String formattedQuery = null;
- List queryParms = Collections.singletonList(getUnQuotedCqlBlob(startOffset, session.cqlVersion.startsWith("3")));
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- success = executor.execute(cqlQuery, queryParms);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error executing indexed range query with offset %s %s%n",
- index,
- session.getRetryTimes(),
- startOffset,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- received += lastQueryResultSize;
-
- // convert max key found back to an integer, and increment it
- startOffset = String.format(format, (1 + lastMaxKey));
-
- session.operations.getAndIncrement();
- session.keys.getAndAdd(lastQueryResultSize);
- context.stop();
+ return acceptNoResults || result.length > 0;
}
}
- /**
- * Get maximum key from CqlRow list
- * @param rows list of the CqlRow objects
- * @return maximum key value of the list
- */
- private int getMaxKey(List rows)
+ @Override
+ protected CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
{
- int maxKey = ByteBufferUtil.toInt(rows.get(0).key);
-
- for (CqlRow row : rows)
- {
- int currentKey = ByteBufferUtil.toInt(row.key);
- if (currentKey > maxKey)
- maxKey = currentKey;
- }
-
- return maxKey;
+ return new IndexedRangeSliceRunOp(client, query, queryId, params, keyid, key);
}
- private int getMaxKey(ResultSet rs)
+ private static byte[] getNextMinKey(byte[] cur, byte[][] keys)
{
- int maxKey = ByteBufferUtil.toInt(rs.rows.get(0).get(0));
+ // find max
+ for (byte[] key : keys)
+ if (FBUtilities.compareUnsigned(cur, key) < 0)
+ cur = key;
- for (List row : rs.rows)
- {
- int currentKey = ByteBufferUtil.toInt(row.get(0));
- if (currentKey > maxKey)
- maxKey = currentKey;
- }
-
- return maxKey;
+ // increment
+ for (int i = 0 ; i < cur.length ; i++)
+ if (++cur[i] != 0)
+ break;
+ return cur;
}
- protected boolean validateThriftResult(CqlResult result)
- {
- lastQueryResultSize = result.rows.size();
- lastMaxKey = getMaxKey(result.rows);
- return lastQueryResultSize != 0;
- }
-
- protected boolean validateNativeResult(ResultMessage result)
- {
- assert result instanceof ResultMessage.Rows;
- lastQueryResultSize = ((ResultMessage.Rows)result).result.size();
- lastMaxKey = getMaxKey(((ResultMessage.Rows)result).result);
- return lastQueryResultSize != 0;
- }
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlInserter.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlInserter.java
index d593e572ea..6b1577ca66 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlInserter.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlInserter.java
@@ -21,126 +21,66 @@ package org.apache.cassandra.stress.operations;
*/
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.SimpleClient;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
import org.apache.cassandra.utils.UUIDGen;
-public class CqlInserter extends CQLOperation
+public class CqlInserter extends CqlOperation
{
- private static List values;
- private static String cqlQuery = null;
- public CqlInserter(Session client, int idx)
+ public CqlInserter(State state, long idx)
{
- super(client, idx);
+ super(state, idx);
}
- protected void run(CQLQueryExecutor executor) throws IOException
+ @Override
+ protected String buildQuery()
{
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- throw new RuntimeException("Super columns are not implemented for CQL");
+ StringBuilder query = new StringBuilder("UPDATE ").append(wrapInQuotesIfRequired(state.settings.schema.columnFamily));
- if (values == null)
- values = generateValues();
+ if (state.isCql2())
+ query.append(" USING CONSISTENCY ").append(state.settings.command.consistencyLevel);
- // Construct a query string once.
- if (cqlQuery == null)
+ query.append(" SET ");
+
+ for (int i = 0 ; i < state.settings.columns.maxColumnsPerKey; i++)
{
- StringBuilder query = new StringBuilder("UPDATE ").append(wrapInQuotesIfRequired("Standard1"));
+ if (i > 0)
+ query.append(',');
- if (session.cqlVersion.startsWith("2"))
- query.append(" USING CONSISTENCY ").append(session.getConsistencyLevel().toString());
-
- query.append(" SET ");
-
- for (int i = 0; i < session.getColumnsPerKey(); i++)
+ if (state.settings.columns.useTimeUUIDComparator)
{
- if (i > 0)
- query.append(',');
+ if (state.isCql3())
+ throw new UnsupportedOperationException("Cannot use UUIDs in column names with CQL3");
- if (session.timeUUIDComparator)
- {
- if (session.cqlVersion.startsWith("3"))
- throw new UnsupportedOperationException("Cannot use UUIDs in column names with CQL3");
-
- query.append(wrapInQuotesIfRequired(UUIDGen.getTimeUUID().toString()))
- .append(" = ?");
- }
- else
- {
- query.append(wrapInQuotesIfRequired("C" + i)).append(" = ?");
- }
+ query.append(wrapInQuotesIfRequired(UUIDGen.getTimeUUID().toString()))
+ .append(" = ?");
}
-
- query.append(" WHERE KEY=?");
- cqlQuery = query.toString();
- }
-
- List queryParms = new ArrayList();
- for (int i = 0; i < session.getColumnsPerKey(); i++)
- {
- // Cell value
- queryParms.add(getUnQuotedCqlBlob(values.get(i % values.size()).array(), session.cqlVersion.startsWith("3")));
- }
-
- String key = String.format("%0" + session.getTotalKeysLength() + "d", index);
- queryParms.add(getUnQuotedCqlBlob(key, session.cqlVersion.startsWith("3")));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
+ else
{
- success = executor.execute(cqlQuery, queryParms);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
+ query.append(wrapInQuotesIfRequired("C" + i)).append(" = ?");
}
}
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error inserting key %s %s%n with query %s",
- index,
- session.getRetryTimes(),
- key,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")",
- cqlQuery));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
+ query.append(" WHERE KEY=?");
+ return query.toString();
}
- protected boolean validateThriftResult(CqlResult result)
+ @Override
+ protected List getQueryParameters(byte[] key)
{
- return true;
+ final ArrayList queryParams = new ArrayList<>();
+ final List values = generateColumnValues();
+ queryParams.addAll(values);
+ queryParams.add(ByteBuffer.wrap(key));
+ return queryParams;
}
- protected boolean validateNativeResult(ResultMessage result)
+ @Override
+ protected CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
{
- return true;
+ return new CqlRunOpAlwaysSucceed(client, query, queryId, params, keyid, key, 1);
}
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlMultiGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlMultiGetter.java
index ec645d4baf..80a7118aaf 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlMultiGetter.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlMultiGetter.java
@@ -23,25 +23,20 @@ package org.apache.cassandra.stress.operations;
import java.io.IOException;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.SimpleClient;
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.util.ThriftClient;
public class CqlMultiGetter extends Operation
{
- public CqlMultiGetter(Session client, int idx)
- {
- super(client, idx);
- }
-
- public void run(CassandraClient client) throws IOException
+ public CqlMultiGetter(State state, long idx)
{
+ super(state, idx);
throw new RuntimeException("Multiget is not implemented for CQL");
}
- public void run(SimpleClient client) throws IOException
+ @Override
+ public void run(ThriftClient client) throws IOException
{
- throw new RuntimeException("Multiget is not implemented for CQL");
}
+
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlOperation.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlOperation.java
new file mode 100644
index 0000000000..1f734be62d
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlOperation.java
@@ -0,0 +1,566 @@
+/*
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import com.google.common.base.Function;
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.settings.ConnectionStyle;
+import org.apache.cassandra.stress.settings.CqlVersion;
+import org.apache.cassandra.stress.util.JavaDriverClient;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.Compression;
+import org.apache.cassandra.thrift.CqlResult;
+import org.apache.cassandra.thrift.ThriftConversion;
+import org.apache.cassandra.transport.SimpleClient;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.thrift.TException;
+
+public abstract class CqlOperation extends Operation
+{
+
+ protected abstract List getQueryParameters(byte[] key);
+ protected abstract String buildQuery();
+ protected abstract CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String id, ByteBuffer key);
+
+ public CqlOperation(State state, long idx)
+ {
+ super(state, idx);
+ if (state.settings.columns.useSuperColumns)
+ throw new IllegalStateException("Super columns are not implemented for CQL");
+ if (state.settings.columns.variableColumnCount)
+ throw new IllegalStateException("Variable column counts are not implemented for CQL");
+ }
+
+ protected CqlRunOp run(final ClientWrapper client, final List queryParams, final ByteBuffer key, final String keyid) throws IOException
+ {
+ final CqlRunOp op;
+ if (state.settings.mode.style == ConnectionStyle.CQL_PREPARED)
+ {
+ final Object id;
+ Object idobj = state.getCqlCache();
+ if (idobj == null)
+ {
+ try
+ {
+ id = client.createPreparedStatement(buildQuery());
+ } catch (TException e)
+ {
+ throw new RuntimeException(e);
+ }
+ state.storeCqlCache(id);
+ }
+ else
+ id = idobj;
+
+ op = buildRunOp(client, null, id, queryParams, keyid, key);
+ }
+ else
+ {
+ final String query;
+ Object qobj = state.getCqlCache();
+ if (qobj == null)
+ state.storeCqlCache(query = buildQuery());
+ else
+ query = qobj.toString();
+
+ op = buildRunOp(client, query, null, queryParams, keyid, key);
+ }
+
+ timeWithRetry(op);
+ return op;
+ }
+
+ protected void run(final ClientWrapper client) throws IOException
+ {
+ final byte[] key = getKey().array();
+ final List queryParams = getQueryParameters(key);
+ run(client, queryParams, ByteBuffer.wrap(key), new String(key));
+ }
+
+ // Classes to process Cql results
+
+ // Always succeeds so long as the query executes without error; provides a keyCount to increment on instantiation
+ protected final class CqlRunOpAlwaysSucceed extends CqlRunOp
+ {
+
+ final int keyCount;
+
+ protected CqlRunOpAlwaysSucceed(ClientWrapper client, String query, Object queryId, List params, String id, ByteBuffer key, int keyCount)
+ {
+ super(client, query, queryId, RowCountHandler.INSTANCE, params, id, key);
+ this.keyCount = keyCount;
+ }
+
+ @Override
+ public boolean validate(Integer result)
+ {
+ return true;
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return keyCount;
+ }
+ }
+
+ // Succeeds so long as the result set is nonempty, and the query executes without error
+ protected final class CqlRunOpTestNonEmpty extends CqlRunOp
+ {
+
+ protected CqlRunOpTestNonEmpty(ClientWrapper client, String query, Object queryId, List params, String id, ByteBuffer key)
+ {
+ super(client, query, queryId, RowCountHandler.INSTANCE, params, id, key);
+ }
+
+ @Override
+ public boolean validate(Integer result)
+ {
+ return true;
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return result;
+ }
+ }
+
+ // Requires a custom validate() method, but fetches and stores the keys from the result set for further processing
+ protected abstract class CqlRunOpFetchKeys extends CqlRunOp
+ {
+
+ protected CqlRunOpFetchKeys(ClientWrapper client, String query, Object queryId, List params, String id, ByteBuffer key)
+ {
+ super(client, query, queryId, KeysHandler.INSTANCE, params, id, key);
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return result.length;
+ }
+
+ }
+
+ // Cql
+ protected abstract class CqlRunOp implements RunOp
+ {
+
+ final ClientWrapper client;
+ final String query;
+ final Object queryId;
+ final List params;
+ final String id;
+ final ByteBuffer key;
+ final ResultHandler handler;
+ V result;
+
+ private CqlRunOp(ClientWrapper client, String query, Object queryId, ResultHandler handler, List params, String id, ByteBuffer key)
+ {
+ this.client = client;
+ this.query = query;
+ this.queryId = queryId;
+ this.handler = handler;
+ this.params = params;
+ this.id = id;
+ this.key = key;
+ }
+
+ @Override
+ public boolean run() throws Exception
+ {
+ return queryId != null
+ ? validate(result = client.execute(queryId, key, params, handler))
+ : validate(result = client.execute(query, key, params, handler));
+ }
+
+ @Override
+ public String key()
+ {
+ return id;
+ }
+
+ public abstract boolean validate(V result);
+
+ }
+
+
+ /// LOTS OF WRAPPING/UNWRAPPING NONSENSE
+
+
+ @Override
+ public void run(final ThriftClient client) throws IOException
+ {
+ run(wrap(client));
+ }
+
+ @Override
+ public void run(SimpleClient client) throws IOException
+ {
+ run(wrap(client));
+ }
+
+ @Override
+ public void run(JavaDriverClient client) throws IOException
+ {
+ run(wrap(client));
+ }
+
+ public ClientWrapper wrap(ThriftClient client)
+ {
+ return state.isCql3()
+ ? new Cql3CassandraClientWrapper(client)
+ : new Cql2CassandraClientWrapper(client);
+
+ }
+
+ public ClientWrapper wrap(JavaDriverClient client)
+ {
+ return new JavaDriverWrapper(client);
+ }
+
+ public ClientWrapper wrap(SimpleClient client)
+ {
+ return new SimpleClientWrapper(client);
+ }
+
+ protected interface ClientWrapper
+ {
+ Object createPreparedStatement(String cqlQuery) throws TException;
+ V execute(Object preparedStatementId, ByteBuffer key, List queryParams, ResultHandler handler) throws TException;
+ V execute(String query, ByteBuffer key, List queryParams, ResultHandler handler) throws TException;
+ }
+
+ private final class JavaDriverWrapper implements ClientWrapper
+ {
+ final JavaDriverClient client;
+ private JavaDriverWrapper(JavaDriverClient client)
+ {
+ this.client = client;
+ }
+
+ @Override
+ public V execute(String query, ByteBuffer key, List queryParams, ResultHandler handler)
+ {
+ String formattedQuery = formatCqlQuery(query, queryParams, state.isCql3());
+ return handler.javaDriverHandler().apply(client.execute(formattedQuery, ThriftConversion.fromThrift(state.settings.command.consistencyLevel)));
+ }
+
+ @Override
+ public V execute(Object preparedStatementId, ByteBuffer key, List queryParams, ResultHandler handler)
+ {
+ return handler.javaDriverHandler().apply(
+ client.executePrepared(
+ (PreparedStatement) preparedStatementId,
+ queryParams,
+ ThriftConversion.fromThrift(state.settings.command.consistencyLevel)));
+ }
+
+ @Override
+ public Object createPreparedStatement(String cqlQuery)
+ {
+ return client.prepare(cqlQuery);
+ }
+ }
+
+ private final class SimpleClientWrapper implements ClientWrapper
+ {
+ final SimpleClient client;
+ private SimpleClientWrapper(SimpleClient client)
+ {
+ this.client = client;
+ }
+
+ @Override
+ public V execute(String query, ByteBuffer key, List queryParams, ResultHandler handler)
+ {
+ String formattedQuery = formatCqlQuery(query, queryParams, state.isCql3());
+ return handler.thriftHandler().apply(client.execute(formattedQuery, ThriftConversion.fromThrift(state.settings.command.consistencyLevel)));
+ }
+
+ @Override
+ public V execute(Object preparedStatementId, ByteBuffer key, List queryParams, ResultHandler handler)
+ {
+ return handler.thriftHandler().apply(
+ client.executePrepared(
+ (byte[]) preparedStatementId,
+ queryParams,
+ ThriftConversion.fromThrift(state.settings.command.consistencyLevel)));
+ }
+
+ @Override
+ public Object createPreparedStatement(String cqlQuery)
+ {
+ return client.prepare(cqlQuery).statementId.bytes;
+ }
+ }
+
+ // client wrapper for Cql3
+ private final class Cql3CassandraClientWrapper implements ClientWrapper
+ {
+ final ThriftClient client;
+ private Cql3CassandraClientWrapper(ThriftClient client)
+ {
+ this.client = client;
+ }
+
+ @Override
+ public V execute(String query, ByteBuffer key, List queryParams, ResultHandler handler) throws TException
+ {
+ String formattedQuery = formatCqlQuery(query, queryParams, true);
+ return handler.simpleNativeHandler().apply(
+ client.execute_cql3_query(query, key, Compression.NONE, state.settings.command.consistencyLevel)
+ );
+ }
+
+ @Override
+ public V execute(Object preparedStatementId, ByteBuffer key, List queryParams, ResultHandler handler) throws TException
+ {
+ Integer id = (Integer) preparedStatementId;
+ return handler.simpleNativeHandler().apply(
+ client.execute_prepared_cql3_query(id, key, queryParams, state.settings.command.consistencyLevel)
+ );
+ }
+
+ @Override
+ public Object createPreparedStatement(String cqlQuery) throws TException
+ {
+ return client.prepare_cql3_query(cqlQuery, Compression.NONE);
+ }
+ }
+
+ // client wrapper for Cql2
+ private final class Cql2CassandraClientWrapper implements ClientWrapper
+ {
+ final ThriftClient client;
+ private Cql2CassandraClientWrapper(ThriftClient client)
+ {
+ this.client = client;
+ }
+
+ @Override
+ public V execute(String query, ByteBuffer key, List queryParams, ResultHandler handler) throws TException
+ {
+ String formattedQuery = formatCqlQuery(query, queryParams, false);
+ return handler.simpleNativeHandler().apply(
+ client.execute_cql_query(formattedQuery, key, Compression.NONE)
+ );
+ }
+
+ @Override
+ public V execute(Object preparedStatementId, ByteBuffer key, List queryParams, ResultHandler handler) throws TException
+ {
+ Integer id = (Integer) preparedStatementId;
+ return handler.simpleNativeHandler().apply(
+ client.execute_prepared_cql_query(id, key, queryParams)
+ );
+ }
+
+ @Override
+ public Object createPreparedStatement(String cqlQuery) throws TException
+ {
+ return client.prepare_cql_query(cqlQuery, Compression.NONE);
+ }
+ }
+
+ // interface for building functions to standardise results from each client
+ protected static interface ResultHandler
+ {
+ Function javaDriverHandler();
+ Function thriftHandler();
+ Function simpleNativeHandler();
+ }
+
+ protected static class RowCountHandler implements ResultHandler
+ {
+ static final RowCountHandler INSTANCE = new RowCountHandler();
+
+ @Override
+ public Function javaDriverHandler()
+ {
+ return new Function()
+ {
+ @Override
+ public Integer apply(ResultSet rows)
+ {
+ if (rows == null)
+ return 0;
+ return rows.all().size();
+ }
+ };
+ }
+
+ @Override
+ public Function thriftHandler()
+ {
+ return new Function()
+ {
+ @Override
+ public Integer apply(ResultMessage result)
+ {
+ return result instanceof ResultMessage.Rows ? ((ResultMessage.Rows) result).result.size() : 0;
+ }
+ };
+ }
+
+ @Override
+ public Function simpleNativeHandler()
+ {
+ return new Function()
+ {
+
+ @Override
+ public Integer apply(CqlResult result)
+ {
+ switch (result.getType())
+ {
+ case ROWS:
+ return result.getRows().size();
+ default:
+ return 1;
+ }
+ }
+ };
+ }
+
+ }
+
+ // Processes results from each client into an array of all key bytes returned
+ protected static final class KeysHandler implements ResultHandler
+ {
+ static final KeysHandler INSTANCE = new KeysHandler();
+
+ @Override
+ public Function javaDriverHandler()
+ {
+ return new Function()
+ {
+
+ @Override
+ public byte[][] apply(ResultSet result)
+ {
+
+ if (result == null)
+ return new byte[0][];
+ List rows = result.all();
+ byte[][] r = new byte[rows.size()][];
+ for (int i = 0 ; i < r.length ; i++)
+ r[i] = rows.get(i).getBytes(0).array();
+ return r;
+ }
+ };
+ }
+
+ @Override
+ public Function thriftHandler()
+ {
+ return new Function()
+ {
+
+ @Override
+ public byte[][] apply(ResultMessage result)
+ {
+ if (result instanceof ResultMessage.Rows)
+ {
+ ResultMessage.Rows rows = ((ResultMessage.Rows) result);
+ byte[][] r = new byte[rows.result.size()][];
+ for (int i = 0 ; i < r.length ; i++)
+ r[i] = rows.result.rows.get(i).get(0).array();
+ return r;
+ }
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public Function simpleNativeHandler()
+ {
+ return new Function()
+ {
+
+ @Override
+ public byte[][] apply(CqlResult result)
+ {
+ byte[][] r = new byte[result.getRows().size()][];
+ for (int i = 0 ; i < r.length ; i++)
+ r[i] = result.getRows().get(i).getKey();
+ return r;
+ }
+ };
+ }
+
+ }
+
+ private static String getUnQuotedCqlBlob(ByteBuffer term, boolean isCQL3)
+ {
+ return isCQL3
+ ? "0x" + ByteBufferUtil.bytesToHex(term)
+ : ByteBufferUtil.bytesToHex(term);
+ }
+
+ /**
+ * Constructs a CQL query string by replacing instances of the character
+ * '?', with the corresponding parameter.
+ *
+ * @param query base query string to format
+ * @param parms sequence of string query parameters
+ * @return formatted CQL query string
+ */
+ private static String formatCqlQuery(String query, List parms, boolean isCql3)
+ {
+ int marker, position = 0;
+ StringBuilder result = new StringBuilder();
+
+ if (-1 == (marker = query.indexOf('?')) || parms.size() == 0)
+ return query;
+
+ for (ByteBuffer parm : parms)
+ {
+ result.append(query.substring(position, marker));
+ result.append(getUnQuotedCqlBlob(parm, isCql3));
+
+ position = marker + 1;
+ if (-1 == (marker = query.indexOf('?', position + 1)))
+ break;
+ }
+
+ if (position < query.length())
+ result.append(query.substring(position));
+
+ return result.toString();
+ }
+
+ protected String wrapInQuotesIfRequired(String string)
+ {
+ return state.settings.mode.cqlVersion == CqlVersion.CQL3
+ ? "\"" + string + "\""
+ : string;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlRangeSlicer.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlRangeSlicer.java
index c01767bc56..467e7549df 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlRangeSlicer.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlRangeSlicer.java
@@ -21,98 +21,39 @@ package org.apache.cassandra.stress.operations;
*/
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
-import org.apache.cassandra.transport.SimpleClient;
-
-public class CqlRangeSlicer extends CQLOperation
+public class CqlRangeSlicer extends CqlOperation
{
- private static String cqlQuery = null;
- private int lastRowCount;
-
- public CqlRangeSlicer(Session client, int idx)
+ public CqlRangeSlicer(State state, long idx)
{
- super(client, idx);
+ super(state, idx);
}
- protected void run(CQLQueryExecutor executor) throws IOException
+ @Override
+ protected List getQueryParameters(byte[] key)
{
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- throw new RuntimeException("Super columns are not implemented for CQL");
-
- if (cqlQuery == null)
- {
- StringBuilder query = new StringBuilder("SELECT FIRST ").append(session.getColumnsPerKey())
- .append(" ''..'' FROM Standard1");
-
- if (session.cqlVersion.startsWith("2"))
- query.append(" USING CONSISTENCY ").append(session.getConsistencyLevel().toString());
-
- cqlQuery = query.append(" WHERE KEY > ?").toString();
- }
-
- String key = String.format("%0" + session.getTotalKeysLength() + "d", index);
- List queryParams = Collections.singletonList(getUnQuotedCqlBlob(key, session.cqlVersion.startsWith("3")));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- success = executor.execute(cqlQuery, queryParams);
- }
- catch (Exception e)
- {
- System.err.println(e);
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error executing range slice with offset %s %s%n",
- index,
- session.getRetryTimes(),
- key,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndAdd(lastRowCount);
- context.stop();
+ return Collections.singletonList(ByteBuffer.wrap(key));
}
- protected boolean validateThriftResult(CqlResult result)
+ @Override
+ protected String buildQuery()
{
- lastRowCount = result.rows.size();
- return lastRowCount != 0;
+ StringBuilder query = new StringBuilder("SELECT FIRST ").append(state.settings.columns.maxColumnsPerKey)
+ .append(" ''..'' FROM ").append(state.settings.schema.columnFamily);
+
+ if (state.isCql2())
+ query.append(" USING CONSISTENCY ").append(state.settings.command.consistencyLevel);
+
+ return query.append(" WHERE KEY > ?").toString();
}
- protected boolean validateNativeResult(ResultMessage result)
+ @Override
+ protected CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
{
- assert result instanceof ResultMessage.Rows;
- lastRowCount = ((ResultMessage.Rows)result).result.size();
- return lastRowCount != 0;
+ return new CqlRunOpTestNonEmpty(client, query, queryId, params, keyid, key);
}
+
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/CqlReader.java b/tools/stress/src/org/apache/cassandra/stress/operations/CqlReader.java
index 70273c1d1b..051fd18858 100644
--- a/tools/stress/src/org/apache/cassandra/stress/operations/CqlReader.java
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/CqlReader.java
@@ -21,116 +21,67 @@ package org.apache.cassandra.stress.operations;
*/
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.transport.SimpleClient;
-import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.thrift.Compression;
-import org.apache.cassandra.thrift.CqlResult;
-import org.apache.cassandra.thrift.ThriftConversion;
-
-public class CqlReader extends CQLOperation
+public class CqlReader extends CqlOperation
{
- private static String cqlQuery = null;
- public CqlReader(Session client, int idx)
+ public CqlReader(State state, long idx)
{
- super(client, idx);
+ super(state, idx);
}
- protected void run(CQLQueryExecutor executor) throws IOException
+ @Override
+ protected String buildQuery()
{
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- throw new RuntimeException("Super columns are not implemented for CQL");
+ StringBuilder query = new StringBuilder("SELECT ");
- if (cqlQuery == null)
+ if (state.settings.columns.names == null)
{
- StringBuilder query = new StringBuilder("SELECT ");
-
- if (session.columnNames == null)
- {
- if (session.cqlVersion.startsWith("2"))
- query.append("FIRST ").append(session.getColumnsPerKey()).append(" ''..''");
- else
- query.append("*");
- }
+ if (state.isCql2())
+ query.append("FIRST ").append(state.settings.columns.maxColumnsPerKey).append(" ''..''");
else
- {
- for (int i = 0; i < session.columnNames.size(); i++)
- {
- if (i > 0) query.append(",");
- query.append('?');
- }
- }
-
- query.append(" FROM ").append(wrapInQuotesIfRequired("Standard1"));
-
- if (session.cqlVersion.startsWith("2"))
- query.append(" USING CONSISTENCY ").append(session.getConsistencyLevel().toString());
- query.append(" WHERE KEY=?");
-
- cqlQuery = query.toString();
+ query.append("*");
}
-
- List queryParams = new ArrayList();
- if (session.columnNames != null)
- for (int i = 0; i < session.columnNames.size(); i++)
- queryParams.add(getUnQuotedCqlBlob(session.columnNames.get(i).array(), session.cqlVersion.startsWith("3")));
-
- byte[] key = generateKey();
- queryParams.add(getUnQuotedCqlBlob(key, session.cqlVersion.startsWith("3")));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
+ else
{
- if (success)
- break;
-
- try
+ for (int i = 0; i < state.settings.columns.names.size() ; i++)
{
- success = executor.execute(cqlQuery, queryParams);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
+ if (i > 0)
+ query.append(",");
+ query.append('?');
}
}
- if (!success)
+ query.append(" FROM ").append(wrapInQuotesIfRequired(state.settings.schema.columnFamily));
+
+ if (state.isCql2())
+ query.append(" USING CONSISTENCY ").append(state.settings.command.consistencyLevel);
+ query.append(" WHERE KEY=?");
+ return query.toString();
+ }
+
+ @Override
+ protected List getQueryParameters(byte[] key)
+ {
+ if (state.settings.columns.names != null)
{
- error(String.format("Operation [%d] retried %d times - error reading key %s %s%n with query %s",
- index,
- session.getRetryTimes(),
- new String(key),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")",
- cqlQuery));
+ final List queryParams = new ArrayList<>();
+ for (ByteBuffer name : state.settings.columns.names)
+ queryParams.add(name);
+ queryParams.add(ByteBuffer.wrap(key));
+ return queryParams;
}
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
+ return Collections.singletonList(ByteBuffer.wrap(key));
}
- protected boolean validateThriftResult(CqlResult result)
+ @Override
+ protected CqlRunOp buildRunOp(ClientWrapper client, String query, Object queryId, List params, String keyid, ByteBuffer key)
{
- return result.rows.get(0).columns.size() != 0;
+ return new CqlRunOpTestNonEmpty(client, query, queryId, params, keyid, key);
}
- protected boolean validateNativeResult(ResultMessage result)
- {
- return result instanceof ResultMessage.Rows && ((ResultMessage.Rows)result).result.size() != 0;
- }
}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/IndexedRangeSlicer.java b/tools/stress/src/org/apache/cassandra/stress/operations/IndexedRangeSlicer.java
deleted file mode 100644
index b7c72a27e7..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/IndexedRangeSlicer.java
+++ /dev/null
@@ -1,135 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.thrift.*;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
-
-public class IndexedRangeSlicer extends Operation
-{
- private static List values = null;
-
- public IndexedRangeSlicer(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- if (values == null)
- values = generateValues();
-
- String format = "%0" + session.getTotalKeysLength() + "d";
- SlicePredicate predicate = new SlicePredicate().setSlice_range(new SliceRange(ByteBufferUtil.EMPTY_BYTE_BUFFER,
- ByteBufferUtil.EMPTY_BYTE_BUFFER,
- false, session.getColumnsPerKey()));
-
- ColumnParent parent = new ColumnParent("Standard1");
- int expectedPerValue = session.getNumKeys() / values.size();
-
- ByteBuffer columnName = ByteBufferUtil.bytes("C1");
-
- int received = 0;
-
- String startOffset = String.format(format, 0);
- ByteBuffer value = values.get(1); // only C1 column is indexed
-
- IndexExpression expression = new IndexExpression(columnName, IndexOperator.EQ, value);
-
- while (received < expectedPerValue)
- {
- IndexClause clause = new IndexClause(Arrays.asList(expression),
- ByteBufferUtil.bytes(startOffset),
- session.getKeysPerCall());
-
- List results = null;
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- results = client.get_indexed_slices(parent, clause, predicate, session.getConsistencyLevel());
- success = (results.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error on calling get_indexed_slices for offset %s %s%n",
- index,
- session.getRetryTimes(),
- startOffset,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- received += results.size();
-
- // convert max key found back to an integer, and increment it
- startOffset = String.format(format, (1 + getMaxKey(results)));
-
- session.operations.getAndIncrement();
- session.keys.getAndAdd(results.size());
- context.stop();
- }
- }
-
- /**
- * Get maximum key from keySlice list
- * @param keySlices list of the KeySlice objects
- * @return maximum key value of the list
- */
- private int getMaxKey(List keySlices)
- {
- byte[] firstKey = keySlices.get(0).getKey();
- int maxKey = ByteBufferUtil.toInt(ByteBuffer.wrap(firstKey));
-
- for (KeySlice k : keySlices)
- {
- int currentKey = ByteBufferUtil.toInt(ByteBuffer.wrap(k.getKey()));
-
- if (currentKey > maxKey)
- {
- maxKey = currentKey;
- }
- }
-
- return maxKey;
- }
-
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/Inserter.java b/tools/stress/src/org/apache/cassandra/stress/operations/Inserter.java
deleted file mode 100644
index cbf6b984a3..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/Inserter.java
+++ /dev/null
@@ -1,135 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.thrift.*;
-import org.apache.cassandra.utils.ByteBufferUtil;
-import org.apache.cassandra.utils.FBUtilities;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.*;
-
-public class Inserter extends Operation
-{
- private static List values;
-
- public Inserter(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- if (values == null)
- values = generateValues();
-
- List columns = new ArrayList(session.getColumnsPerKey());
- List superColumns = null;
-
- // format used for keys
- String format = "%0" + session.getTotalKeysLength() + "d";
-
- for (int i = 0; i < session.getColumnsPerKey(); i++)
- {
- columns.add(new Column(columnName(i, session.timeUUIDComparator))
- .setValue(values.get(i % values.size()))
- .setTimestamp(FBUtilities.timestampMicros()));
- }
-
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- {
- superColumns = new ArrayList();
- // supers = [SuperColumn('S' + str(j), columns) for j in xrange(supers_per_key)]
- for (int i = 0; i < session.getSuperColumns(); i++)
- {
- String superColumnName = "S" + Integer.toString(i);
- superColumns.add(new SuperColumn(ByteBufferUtil.bytes(superColumnName), columns));
- }
- }
-
- String rawKey = String.format(format, index);
- Map> row = session.getColumnFamilyType() == ColumnFamilyType.Super
- ? getSuperColumnsMutationMap(superColumns)
- : getColumnsMutationMap(columns);
- Map>> record = Collections.singletonMap(ByteBufferUtil.bytes(rawKey), row);
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- client.batch_mutate(record, session.getConsistencyLevel());
- success = true;
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error inserting key %s %s%n",
- index,
- session.getRetryTimes(),
- rawKey,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
- }
-
- private Map> getSuperColumnsMutationMap(List superColumns)
- {
- List mutations = new ArrayList(superColumns.size());
- for (SuperColumn s : superColumns)
- {
- ColumnOrSuperColumn superColumn = new ColumnOrSuperColumn().setSuper_column(s);
- mutations.add(new Mutation().setColumn_or_supercolumn(superColumn));
- }
-
- return Collections.singletonMap("Super1", mutations);
- }
-
- private Map> getColumnsMutationMap(List columns)
- {
- List mutations = new ArrayList(columns.size());
- for (Column c : columns)
- {
- ColumnOrSuperColumn column = new ColumnOrSuperColumn().setColumn(c);
- mutations.add(new Mutation().setColumn_or_supercolumn(column));
- }
-
- return Collections.singletonMap("Standard1", mutations);
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/MultiGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/MultiGetter.java
deleted file mode 100644
index 12a39fb3c9..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/MultiGetter.java
+++ /dev/null
@@ -1,152 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.thrift.*;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-
-public class MultiGetter extends Operation
-{
- public MultiGetter(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- SlicePredicate predicate = new SlicePredicate().setSlice_range(new SliceRange(ByteBufferUtil.EMPTY_BYTE_BUFFER,
- ByteBufferUtil.EMPTY_BYTE_BUFFER,
- false, session.getColumnsPerKey()));
-
- int offset = index * session.getKeysPerThread();
- Map> results;
-
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- {
- List keys = generateKeys(offset, offset + session.getKeysPerCall());
-
- for (int j = 0; j < session.getSuperColumns(); j++)
- {
- ColumnParent parent = new ColumnParent("Super1").setSuper_column(ByteBufferUtil.bytes("S" + j));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- results = client.multiget_slice(keys, parent, predicate, session.getConsistencyLevel());
- success = (results.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error on calling multiget_slice for keys %s %s%n",
- index,
- session.getRetryTimes(),
- keys,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndAdd(keys.size());
- context.stop();
-
- offset += session.getKeysPerCall();
- }
- }
- else
- {
- ColumnParent parent = new ColumnParent("Standard1");
-
- List keys = generateKeys(offset, offset + session.getKeysPerCall());
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- results = client.multiget_slice(keys, parent, predicate, session.getConsistencyLevel());
- success = (results.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error on calling multiget_slice for keys %s %s%n",
- index,
- session.getRetryTimes(),
- keys,
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndAdd(keys.size());
- context.stop();
-
- offset += session.getKeysPerCall();
- }
- }
-
- private List generateKeys(int start, int limit)
- {
- List keys = new ArrayList();
-
- for (int i = start; i < limit; i++)
- {
- keys.add(ByteBuffer.wrap(generateKey()));
- }
-
- return keys;
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/RangeSlicer.java b/tools/stress/src/org/apache/cassandra/stress/operations/RangeSlicer.java
deleted file mode 100644
index f9ba1154d6..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/RangeSlicer.java
+++ /dev/null
@@ -1,144 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.thrift.*;
-import org.apache.cassandra.utils.ByteBufferUtil;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
-public class RangeSlicer extends Operation
-{
-
- public RangeSlicer(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- String format = "%0" + session.getTotalKeysLength() + "d";
-
- // initial values
- int count = session.getColumnsPerKey();
-
- SlicePredicate predicate = new SlicePredicate().setSlice_range(new SliceRange(ByteBufferUtil.EMPTY_BYTE_BUFFER,
- ByteBufferUtil.EMPTY_BYTE_BUFFER,
- false,
- count));
-
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- {
- ByteBuffer start = ByteBufferUtil.bytes(String.format(format, index));
-
- List slices = new ArrayList();
- KeyRange range = new KeyRange(count).setStart_key(start).setEnd_key(ByteBufferUtil.EMPTY_BYTE_BUFFER);
-
- for (int i = 0; i < session.getSuperColumns(); i++)
- {
- String superColumnName = "S" + Integer.toString(i);
- ColumnParent parent = new ColumnParent("Super1").setSuper_column(ByteBufferUtil.bytes(superColumnName));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- try
- {
- slices = client.get_range_slices(parent, predicate, range, session.getConsistencyLevel());
- success = (slices.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error on calling get_range_slices for range offset %s %s%n",
- index,
- session.getRetryTimes(),
- ByteBufferUtil.string(start),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- context.stop();
- }
-
- session.keys.getAndAdd(slices.size());
- }
- else
- {
- ColumnParent parent = new ColumnParent("Standard1");
-
- ByteBuffer start = ByteBufferUtil.bytes(String.format(format, index));
-
- List slices = new ArrayList();
- KeyRange range = new KeyRange(count).setStart_key(start).setEnd_key(ByteBufferUtil.EMPTY_BYTE_BUFFER);
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- slices = client.get_range_slices(parent, predicate, range, session.getConsistencyLevel());
- success = (slices.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error on calling get_indexed_slices for range offset %s %s%n",
- index,
- session.getRetryTimes(),
- ByteBufferUtil.string(start),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndAdd(slices.size());
- context.stop();
- }
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/Reader.java b/tools/stress/src/org/apache/cassandra/stress/operations/Reader.java
deleted file mode 100644
index 72d09b404c..0000000000
--- a/tools/stress/src/org/apache/cassandra/stress/operations/Reader.java
+++ /dev/null
@@ -1,159 +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.operations;
-
-import com.yammer.metrics.core.TimerContext;
-import org.apache.cassandra.stress.Session;
-import org.apache.cassandra.stress.util.CassandraClient;
-import org.apache.cassandra.stress.util.Operation;
-import org.apache.cassandra.db.ColumnFamilyType;
-import org.apache.cassandra.thrift.*;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.List;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class Reader extends Operation
-{
- public Reader(Session client, int index)
- {
- super(client, index);
- }
-
- public void run(CassandraClient client) throws IOException
- {
- // initialize SlicePredicate with existing SliceRange
- SlicePredicate predicate = new SlicePredicate();
-
- if (session.columnNames == null)
- predicate.setSlice_range(getSliceRange());
- else // see CASSANDRA-3064 about why this is useful
- predicate.setColumn_names(session.columnNames);
-
- if (session.getColumnFamilyType() == ColumnFamilyType.Super)
- {
- runSuperColumnReader(predicate, client);
- }
- else
- {
- runColumnReader(predicate, client);
- }
- }
-
- private void runSuperColumnReader(SlicePredicate predicate, Cassandra.Client client) throws IOException
- {
- byte[] rawKey = generateKey();
- ByteBuffer key = ByteBuffer.wrap(rawKey);
-
- for (int j = 0; j < session.getSuperColumns(); j++)
- {
- String superColumn = 'S' + Integer.toString(j);
- ColumnParent parent = new ColumnParent("Super1").setSuper_column(superColumn.getBytes(UTF_8));
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- List columns;
- columns = client.get_slice(key, parent, predicate, session.getConsistencyLevel());
- success = (columns.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error reading key %s %s%n",
- index,
- session.getRetryTimes(),
- new String(rawKey),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
- }
- }
-
- private void runColumnReader(SlicePredicate predicate, Cassandra.Client client) throws IOException
- {
- ColumnParent parent = new ColumnParent("Standard1");
-
- byte[] key = generateKey();
- ByteBuffer keyBuffer = ByteBuffer.wrap(key);
-
- TimerContext context = session.latency.time();
-
- boolean success = false;
- String exceptionMessage = null;
-
- for (int t = 0; t < session.getRetryTimes(); t++)
- {
- if (success)
- break;
-
- try
- {
- List columns;
- columns = client.get_slice(keyBuffer, parent, predicate, session.getConsistencyLevel());
- success = (columns.size() != 0);
- }
- catch (Exception e)
- {
- exceptionMessage = getExceptionMessage(e);
- success = false;
- }
- }
-
- if (!success)
- {
- error(String.format("Operation [%d] retried %d times - error reading key %s %s%n",
- index,
- session.getRetryTimes(),
- new String(key),
- (exceptionMessage == null) ? "" : "(" + exceptionMessage + ")"));
- }
-
- session.operations.getAndIncrement();
- session.keys.getAndIncrement();
- context.stop();
- }
-
- private SliceRange getSliceRange()
- {
- return new SliceRange()
- .setStart(new byte[] {})
- .setFinish(new byte[] {})
- .setReversed(false)
- .setCount(session.getColumnsPerKey());
- }
-}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftCounterAdder.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftCounterAdder.java
new file mode 100644
index 0000000000..b1657b2231
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftCounterAdder.java
@@ -0,0 +1,95 @@
+/**
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.*;
+
+public class ThriftCounterAdder extends Operation
+{
+ public ThriftCounterAdder(State state, long index)
+ {
+ super(state, index);
+ if (state.settings.columns.variableColumnCount)
+ throw new IllegalStateException("Variable column counts not supported for counters");
+ }
+
+ public void run(final ThriftClient client) throws IOException
+ {
+ List columns = new ArrayList();
+ for (int i = 0; i < state.settings.columns.maxColumnsPerKey; i++)
+ columns.add(new CounterColumn(getColumnNameBytes(i), 1L));
+
+ Map> row;
+ if (state.settings.columns.useSuperColumns)
+ {
+ List mutations = new ArrayList<>();
+ for (ColumnParent parent : state.columnParents)
+ {
+ CounterSuperColumn csc = new CounterSuperColumn(ByteBuffer.wrap(parent.getSuper_column()), columns);
+ ColumnOrSuperColumn cosc = new ColumnOrSuperColumn().setCounter_super_column(csc);
+ mutations.add(new Mutation().setColumn_or_supercolumn(cosc));
+ }
+ row = Collections.singletonMap("SuperCounter1", mutations);
+ }
+ else
+ {
+ List mutations = new ArrayList<>(columns.size());
+ for (CounterColumn c : columns)
+ {
+ ColumnOrSuperColumn cosc = new ColumnOrSuperColumn().setCounter_column(c);
+ mutations.add(new Mutation().setColumn_or_supercolumn(cosc));
+ }
+ row = Collections.singletonMap("Counter1", mutations);
+ }
+
+ final ByteBuffer key = getKey();
+ final Map>> record = Collections.singletonMap(key, row);
+
+ timeWithRetry(new RunOp()
+ {
+ @Override
+ public boolean run() throws Exception
+ {
+ client.batch_mutate(record, state.settings.command.consistencyLevel);
+ return true;
+ }
+
+ @Override
+ public String key()
+ {
+ return new String(key.array());
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return 1;
+ }
+ });
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftCounterGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftCounterGetter.java
new file mode 100644
index 0000000000..8567edd01d
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftCounterGetter.java
@@ -0,0 +1,75 @@
+/**
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.ColumnParent;
+import org.apache.cassandra.thrift.SlicePredicate;
+import org.apache.cassandra.thrift.SliceRange;
+
+public class ThriftCounterGetter extends Operation
+{
+ public ThriftCounterGetter(State state, long index)
+ {
+ super(state, index);
+ if (state.settings.columns.variableColumnCount)
+ throw new IllegalStateException("Variable column counts not supported for counters");
+ }
+
+ public void run(final ThriftClient client) throws IOException
+ {
+ SliceRange sliceRange = new SliceRange();
+ // start/finish
+ sliceRange.setStart(new byte[] {}).setFinish(new byte[] {});
+ // reversed/count
+ sliceRange.setReversed(false).setCount(state.settings.columns.maxColumnsPerKey);
+ // initialize SlicePredicate with existing SliceRange
+ final SlicePredicate predicate = new SlicePredicate().setSlice_range(sliceRange);
+
+ final ByteBuffer key = getKey();
+ for (final ColumnParent parent : state.columnParents)
+ {
+
+ timeWithRetry(new RunOp()
+ {
+ @Override
+ public boolean run() throws Exception
+ {
+ return client.get_slice(key, parent, predicate, state.settings.command.consistencyLevel).size() != 0;
+ }
+
+ @Override
+ public String key()
+ {
+ return new String(key.array());
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return 1;
+ }
+ });
+ }
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftIndexedRangeSlicer.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftIndexedRangeSlicer.java
new file mode 100644
index 0000000000..c6b1b03074
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftIndexedRangeSlicer.java
@@ -0,0 +1,115 @@
+/**
+* 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.settings.SettingsCommandMulti;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.*;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.FBUtilities;
+
+public class ThriftIndexedRangeSlicer extends Operation
+{
+ public ThriftIndexedRangeSlicer(State state, long index)
+ {
+ super(state, index);
+ if (!state.rowGen.isDeterministic() || !state.keyGen.isDeterministic())
+ throw new IllegalStateException("Only run with a isDeterministic row/key generator");
+ if (state.settings.columns.useSuperColumns || state.columnParents.size() != 1)
+ throw new IllegalStateException("Does not support super columns");
+ if (state.settings.columns.useTimeUUIDComparator)
+ throw new IllegalStateException("Does not support TimeUUID column names");
+ }
+
+ public void run(final ThriftClient client) throws IOException
+ {
+
+ final SlicePredicate predicate = new SlicePredicate()
+ .setSlice_range(new SliceRange(ByteBufferUtil.EMPTY_BYTE_BUFFER,
+ ByteBufferUtil.EMPTY_BYTE_BUFFER,
+ false, state.settings.columns.maxColumnsPerKey));
+ final List columns = generateColumnValues();
+ final ColumnParent parent = state.columnParents.get(0);
+
+ final ByteBuffer columnName = getColumnNameBytes(1);
+ final ByteBuffer value = columns.get(1); // only C1 column is indexed
+
+ IndexExpression expression = new IndexExpression(columnName, IndexOperator.EQ, value);
+ byte[] minKey = new byte[0];
+ final List[] results = new List[1];
+ do
+ {
+
+ final boolean first = minKey.length == 0;
+ final IndexClause clause = new IndexClause(Arrays.asList(expression),
+ ByteBuffer.wrap(minKey),
+ ((SettingsCommandMulti) state.settings.command).keysAtOnce);
+
+ timeWithRetry(new RunOp()
+ {
+ @Override
+ public boolean run() throws Exception
+ {
+ results[0] = client.get_indexed_slices(parent, clause, predicate, state.settings.command.consistencyLevel);
+ return !first || results[0].size() > 0;
+ }
+
+ @Override
+ public String key()
+ {
+ return new String(value.array());
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return results[0].size();
+ }
+ });
+
+ minKey = getNextMinKey(minKey, results[0]);
+
+ } while (results[0].size() > 0);
+ }
+
+ /**
+ * Get maximum key from keySlice list
+ * @param slices list of the KeySlice objects
+ * @return maximum key value of the list
+ */
+ private static byte[] getNextMinKey(byte[] cur, List slices)
+ {
+ // find max
+ for (KeySlice slice : slices)
+ if (FBUtilities.compareUnsigned(cur, slice.getKey()) < 0)
+ cur = slice.getKey();
+
+ // increment
+ for (int i = 0 ; i < cur.length ; i++)
+ if (++cur[i] != 0)
+ break;
+ return cur;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftInserter.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftInserter.java
new file mode 100644
index 0000000000..c5f8051980
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftInserter.java
@@ -0,0 +1,117 @@
+/**
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cassandra.db.marshal.TimeUUIDType;
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.*;
+import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.UUIDGen;
+
+public final class ThriftInserter extends Operation
+{
+
+ public ThriftInserter(State state, long index)
+ {
+ super(state, index);
+ }
+
+ public void run(final ThriftClient client) throws IOException
+ {
+ final ByteBuffer key = getKey();
+ final List columns = generateColumns();
+
+ Map> row;
+ if (!state.settings.columns.useSuperColumns)
+ {
+ List mutations = new ArrayList<>(columns.size());
+ for (Column c : columns)
+ {
+ ColumnOrSuperColumn column = new ColumnOrSuperColumn().setColumn(c);
+ mutations.add(new Mutation().setColumn_or_supercolumn(column));
+ }
+ row = Collections.singletonMap(state.settings.schema.columnFamily, mutations);
+ }
+ else
+ {
+ List mutations = new ArrayList<>(state.columnParents.size());
+ for (ColumnParent parent : state.columnParents)
+ {
+ final SuperColumn s = new SuperColumn(parent.bufferForSuper_column(), columns);
+ final ColumnOrSuperColumn cosc = new ColumnOrSuperColumn().setSuper_column(s);
+ mutations.add(new Mutation().setColumn_or_supercolumn(cosc));
+ }
+ row = Collections.singletonMap("Super1", mutations);
+ }
+
+ final Map>> record = Collections.singletonMap(key, row);
+
+ timeWithRetry(new RunOp()
+ {
+ @Override
+ public boolean run() throws Exception
+ {
+ client.batch_mutate(record, state.settings.command.consistencyLevel);
+ return true;
+ }
+
+ @Override
+ public String key()
+ {
+ return new String(key.array());
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return 1;
+ }
+ });
+ }
+
+ protected List generateColumns()
+ {
+ final List values = generateColumnValues();
+ final List columns = new ArrayList<>(values.size());
+
+ if (state.settings.columns.useTimeUUIDComparator)
+ for (int i = 0 ; i < values.size() ; i++)
+ new Column(TimeUUIDType.instance.decompose(UUIDGen.getTimeUUID()));
+ else
+ // TODO : consider randomly allocating column names in case where have fewer than max columns
+ // but need to think about implications for indexes / indexed range slicer / other knock on effects
+ for (int i = 0 ; i < values.size() ; i++)
+ columns.add(new Column(getColumnNameBytes(i)));
+
+ for (int i = 0 ; i < values.size() ; i++)
+ columns.get(i)
+ .setValue(values.get(i))
+ .setTimestamp(FBUtilities.timestampMicros());
+
+ return columns;
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftMultiGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftMultiGetter.java
new file mode 100644
index 0000000000..01c7325000
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftMultiGetter.java
@@ -0,0 +1,81 @@
+/**
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.settings.SettingsCommandMulti;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.ColumnParent;
+import org.apache.cassandra.thrift.SlicePredicate;
+import org.apache.cassandra.thrift.SliceRange;
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+
+public final class ThriftMultiGetter extends Operation
+{
+
+ public ThriftMultiGetter(State state, long index)
+ {
+ super(state, index);
+ }
+
+ public void run(final ThriftClient client) throws IOException
+ {
+
+ final SlicePredicate predicate = new SlicePredicate().setSlice_range(
+ new SliceRange(
+ ByteBufferUtil.EMPTY_BYTE_BUFFER,
+ ByteBufferUtil.EMPTY_BYTE_BUFFER,
+ false,
+ state.settings.columns.maxColumnsPerKey
+ )
+ );
+
+ final List keys = getKeys(((SettingsCommandMulti) state.settings.command).keysAtOnce);
+
+ for (final ColumnParent parent : state.columnParents)
+ {
+ timeWithRetry(new RunOp()
+ {
+ int count;
+ @Override
+ public boolean run() throws Exception
+ {
+ return (count = client.multiget_slice(keys, parent, predicate, state.settings.command.consistencyLevel).size()) != 0;
+ }
+
+ @Override
+ public String key()
+ {
+ return keys.toString();
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return count;
+ }
+ });
+ }
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftRangeSlicer.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftRangeSlicer.java
new file mode 100644
index 0000000000..ce6c8cd4ca
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftRangeSlicer.java
@@ -0,0 +1,86 @@
+/**
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.settings.SettingsCommandMulti;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.ColumnParent;
+import org.apache.cassandra.thrift.KeyRange;
+import org.apache.cassandra.thrift.SlicePredicate;
+import org.apache.cassandra.thrift.SliceRange;
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+public final class ThriftRangeSlicer extends Operation
+{
+
+ public ThriftRangeSlicer(State state, long index)
+ {
+ super(state, index);
+ }
+
+ @Override
+ public void run(final ThriftClient client) throws IOException
+ {
+ final SlicePredicate predicate = new SlicePredicate()
+ .setSlice_range(
+ new SliceRange(
+ ByteBufferUtil.EMPTY_BYTE_BUFFER,
+ ByteBufferUtil.EMPTY_BYTE_BUFFER,
+ false,
+ state.settings.columns.maxColumnsPerKey
+ )
+ );
+
+ final ByteBuffer start = getKey();
+ final KeyRange range =
+ new KeyRange(state.settings.columns.maxColumnsPerKey)
+ .setStart_key(start)
+ .setEnd_key(ByteBufferUtil.EMPTY_BYTE_BUFFER)
+ .setCount(((SettingsCommandMulti)state.settings.command).keysAtOnce);
+
+ for (final ColumnParent parent : state.columnParents)
+ {
+ timeWithRetry(new RunOp()
+ {
+ private int count = 0;
+ @Override
+ public boolean run() throws Exception
+ {
+ return (count = client.get_range_slices(parent, predicate, range, state.settings.command.consistencyLevel).size()) != 0;
+ }
+
+ @Override
+ public String key()
+ {
+ return new String(range.bufferForStart_key().array());
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return count;
+ }
+ });
+ }
+ }
+
+}
diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/ThriftReader.java b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftReader.java
new file mode 100644
index 0000000000..a8605e8ea9
--- /dev/null
+++ b/tools/stress/src/org/apache/cassandra/stress/operations/ThriftReader.java
@@ -0,0 +1,76 @@
+/**
+ * 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.operations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.cassandra.stress.Operation;
+import org.apache.cassandra.stress.util.ThriftClient;
+import org.apache.cassandra.thrift.ColumnParent;
+import org.apache.cassandra.thrift.SlicePredicate;
+import org.apache.cassandra.thrift.SliceRange;
+
+public final class ThriftReader extends Operation
+{
+
+ public ThriftReader(State state, long index)
+ {
+ super(state, index);
+ }
+
+ public void run(final ThriftClient client) throws IOException
+ {
+ final SlicePredicate predicate = new SlicePredicate();
+ if (state.settings.columns.names == null)
+ predicate.setSlice_range(new SliceRange()
+ .setStart(new byte[] {})
+ .setFinish(new byte[] {})
+ .setReversed(false)
+ .setCount(state.settings.columns.maxColumnsPerKey)
+ );
+ else // see CASSANDRA-3064 about why this is useful
+ predicate.setColumn_names(state.settings.columns.names);
+
+ final ByteBuffer key = getKey();
+ for (final ColumnParent parent : state.columnParents)
+ {
+ timeWithRetry(new RunOp()
+ {
+ @Override
+ public boolean run() throws Exception
+ {
+ return client.get_slice(key, parent, predicate, state.settings.command.consistencyLevel).size() != 0;
+ }
+
+ @Override
+ public String key()
+ {
+ return new String(key.array());
+ }
+
+ @Override
+ public int keyCount()
+ {
+ return 1;
+ }
+ });
+ }
+ }
+
+}
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 158a09f976..0000000000
--- 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();
- }
- }
-
-}
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 0000000000..76c7509b35
--- /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 LOOKUP;
+ static
+ {
+ final Map 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();
+ }
+
+}
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 0000000000..4bd843ead8
--- /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 LOOKUP;
+ static
+ {
+ final Map 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();
+ }
+
+}
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 0000000000..87a13f7487
--- /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
+}
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 0000000000..c647f664a2
--- /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
+}
+
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 0000000000..6e77f4a2e7
--- /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
+}
+
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 0000000000..853e399795
--- /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;
+ }
+
+}
+
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 0000000000..fe965c910b
--- /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 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