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 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