mirror of https://github.com/apache/cassandra
Improve Stress Tool
patch by Benedict; reviewed by Pavel Yaskevich for CASSANDRA-6199
This commit is contained in:
parent
34235ad7b5
commit
2e1e98ad04
|
|
@ -37,6 +37,7 @@
|
|||
<property name="build.src.resources" value="${basedir}/src/resources"/>
|
||||
<property name="build.src.gen-java" value="${basedir}/src/gen-java"/>
|
||||
<property name="build.lib" value="${basedir}/lib"/>
|
||||
<property name="build.tools.lib" value="${basedir}/tools/lib"/>
|
||||
<property name="build.dir" value="${basedir}/build"/>
|
||||
<property name="build.dir.lib" value="${basedir}/build/lib"/>
|
||||
<property name="build.test.dir" value="${build.dir}/test"/>
|
||||
|
|
@ -343,6 +344,7 @@
|
|||
<dependency groupId="commons-cli" artifactId="commons-cli" version="1.1"/>
|
||||
<dependency groupId="commons-codec" artifactId="commons-codec" version="1.2"/>
|
||||
<dependency groupId="org.apache.commons" artifactId="commons-lang3" version="3.1"/>
|
||||
<dependency groupId="org.apache.commons" artifactId="commons-math3" version="3.2"/>
|
||||
<dependency groupId="com.googlecode.concurrentlinkedhashmap" artifactId="concurrentlinkedhashmap-lru" version="1.3"/>
|
||||
<dependency groupId="org.antlr" artifactId="antlr" version="3.2"/>
|
||||
<dependency groupId="org.slf4j" artifactId="slf4j-api" version="1.7.2"/>
|
||||
|
|
@ -453,6 +455,7 @@
|
|||
<dependency groupId="commons-cli" artifactId="commons-cli"/>
|
||||
<dependency groupId="commons-codec" artifactId="commons-codec"/>
|
||||
<dependency groupId="org.apache.commons" artifactId="commons-lang3"/>
|
||||
<dependency groupId="org.apache.commons" artifactId="commons-math3"/>
|
||||
<dependency groupId="com.googlecode.concurrentlinkedhashmap" artifactId="concurrentlinkedhashmap-lru"/>
|
||||
<dependency groupId="org.antlr" artifactId="antlr"/>
|
||||
<dependency groupId="org.slf4j" artifactId="slf4j-api"/>
|
||||
|
|
@ -703,6 +706,9 @@
|
|||
<fileset dir="${build.lib}">
|
||||
<include name="**/*.jar" />
|
||||
</fileset>
|
||||
<fileset dir="${build.tools.lib}">
|
||||
<include name="**/*.jar" />
|
||||
</fileset>
|
||||
</path>
|
||||
</classpath>
|
||||
</javac>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -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<ColumnParent> 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<ByteBuffer> getKeys(int count)
|
||||
{
|
||||
return state.keyGen.getKeys(count, index);
|
||||
}
|
||||
|
||||
protected List<ByteBuffer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <dc_name>:<num>,<dc_name>:<num>,...");
|
||||
availableOptions.addOption("W", "no-replicate-on-write",false, "Set replicate_on_write to false for counters. Only counter add with CL=ONE will work");
|
||||
availableOptions.addOption("V", "average-size-values", false, "Generate column values of average rather than specific size");
|
||||
availableOptions.addOption("T", "send-to", true, "Send this as a request to the stress daemon at specified address.");
|
||||
availableOptions.addOption("I", "compression", true, "Specify the compression to use for sstable, default:no compression");
|
||||
availableOptions.addOption("Q", "query-names", true, "Comma-separated list of column names to retrieve from each row.");
|
||||
availableOptions.addOption("Z", "compaction-strategy", true, "CompactionStrategy to use.");
|
||||
availableOptions.addOption("U", "comparator", true, "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<String, String> replicationStrategyOptions = new HashMap<String, String>();
|
||||
|
||||
// if we know exactly column names that we want to read (set by -Q option)
|
||||
public final List<ByteBuffer> 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<String> tmpNodes = new ArrayList<String>();
|
||||
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<ByteBuffer>(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<String, String> compressionOptions = new HashMap<String, String>();
|
||||
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<CfDef>(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<String, String> credentials = new HashMap<String, String>();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Operation> operations = new SynchronousQueue<Operation>(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<StressMetrics> results = new ArrayList<>();
|
||||
List<String> 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<StressMetrics> results, int count, double minImprovement)
|
||||
{
|
||||
if (results.size() < count + 1)
|
||||
return true;
|
||||
return averageImprovement(results, count) >= minImprovement;
|
||||
}
|
||||
|
||||
private double averageImprovement(List<StressMetrics> 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> work;
|
||||
volatile boolean stop = false;
|
||||
|
||||
public FixedWorkQueue(ArrayBlockingQueue<Work> 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> work = new ArrayBlockingQueue<Work>(
|
||||
(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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> ids, List<StressMetrics> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ByteBuffer> fills, long offset)
|
||||
{
|
||||
for (ByteBuffer fill : fills)
|
||||
generate(fill, offset++);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.apache.cassandra.stress.generatedata;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface DataGenFactory extends Serializable
|
||||
{
|
||||
DataGen get();
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<byte[]> words;
|
||||
|
||||
public DataGenStringDictionary(EnumeratedDistribution<byte[]> wordDistribution)
|
||||
{
|
||||
words = wordDistribution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(ByteBuffer fill, long index)
|
||||
{
|
||||
fill(fill, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(List<ByteBuffer> 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<Pair<byte[], Double>> 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<byte[]> dist = new EnumeratedDistribution<byte[]>(words);
|
||||
return new DataGenFactory()
|
||||
{
|
||||
@Override
|
||||
public DataGen get()
|
||||
{
|
||||
return new DataGenStringDictionary(dist);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Integer, ConcurrentHashMap<Long, byte[]>> CACHE_LOOKUP = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<Long, byte[]> cache;
|
||||
private final int repeatFrequency;
|
||||
public DataGenStringRepeats(int repeatFrequency)
|
||||
{
|
||||
if (!CACHE_LOOKUP.containsKey(repeatFrequency))
|
||||
CACHE_LOOKUP.putIfAbsent(repeatFrequency, new ConcurrentHashMap<Long, byte[]>());
|
||||
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<ByteBuffer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package org.apache.cassandra.stress.generatedata;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface DistributionFactory extends Serializable
|
||||
{
|
||||
|
||||
Distribution get();
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ByteBuffer> keyBuffers = new ArrayList<>();
|
||||
|
||||
public KeyGen(DataGen dataGen, int keySize)
|
||||
{
|
||||
this.dataGen = dataGen;
|
||||
this.keySize = keySize;
|
||||
}
|
||||
|
||||
public List<ByteBuffer> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ByteBuffer> generate(long operationIndex)
|
||||
{
|
||||
List<ByteBuffer> fill = getColumns(operationIndex);
|
||||
dataGen.generate(fill, operationIndex);
|
||||
return fill;
|
||||
}
|
||||
|
||||
// these byte[] may be re-used
|
||||
abstract List<ByteBuffer> getColumns(long operationIndex);
|
||||
|
||||
abstract public boolean isDeterministic();
|
||||
|
||||
}
|
||||
|
|
@ -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<Integer, ByteBuffer> 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<Integer, ByteBuffer> 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<ByteBuffer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String> 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<String> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CounterColumn> columns = new ArrayList<CounterColumn>();
|
||||
List<CounterSuperColumn> superColumns = new ArrayList<CounterSuperColumn>();
|
||||
|
||||
// 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<ByteBuffer, Map<String, List<Mutation>>> record = new HashMap<ByteBuffer, Map<String, List<Mutation>>>();
|
||||
|
||||
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<String, List<Mutation>> getSuperColumnsMutationMap(List<CounterSuperColumn> superColumns)
|
||||
{
|
||||
List<Mutation> mutations = new ArrayList<Mutation>();
|
||||
Map<String, List<Mutation>> mutationMap = new HashMap<String, List<Mutation>>();
|
||||
|
||||
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<String, List<Mutation>> getColumnsMutationMap(List<CounterColumn> columns)
|
||||
{
|
||||
List<Mutation> mutations = new ArrayList<Mutation>();
|
||||
Map<String, List<Mutation>> mutationMap = new HashMap<String, List<Mutation>>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ColumnOrSuperColumn> 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<ColumnOrSuperColumn> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Integer>
|
||||
{
|
||||
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<String> 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<ByteBuffer> 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<ByteBuffer> params, String keyid, ByteBuffer key)
|
||||
{
|
||||
return true;
|
||||
return new CqlRunOpAlwaysSucceed(client, query, queryId, params, keyid, key, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Integer>
|
||||
{
|
||||
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<ByteBuffer> 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<String> 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<ByteBuffer> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<byte[][]>
|
||||
{
|
||||
private static List<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> params = Arrays.asList(value, ByteBuffer.wrap(minKey));
|
||||
CqlRunOp<byte[][]> 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<ByteBuffer> 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<String> 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<CqlRow> rows)
|
||||
@Override
|
||||
protected CqlRunOp<byte[][]> buildRunOp(ClientWrapper client, String query, Object queryId, List<ByteBuffer> 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<ByteBuffer> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Integer>
|
||||
{
|
||||
private static List<ByteBuffer> 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<String> queryParms = new ArrayList<String>();
|
||||
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<ByteBuffer> getQueryParameters(byte[] key)
|
||||
{
|
||||
return true;
|
||||
final ArrayList<ByteBuffer> queryParams = new ArrayList<>();
|
||||
final List<ByteBuffer> 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<ByteBuffer> params, String keyid, ByteBuffer key)
|
||||
{
|
||||
return true;
|
||||
return new CqlRunOpAlwaysSucceed(client, query, queryId, params, keyid, key, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<V> extends Operation
|
||||
{
|
||||
|
||||
protected abstract List<ByteBuffer> getQueryParameters(byte[] key);
|
||||
protected abstract String buildQuery();
|
||||
protected abstract CqlRunOp<V> buildRunOp(ClientWrapper client, String query, Object queryId, List<ByteBuffer> 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<V> run(final ClientWrapper client, final List<ByteBuffer> queryParams, final ByteBuffer key, final String keyid) throws IOException
|
||||
{
|
||||
final CqlRunOp<V> 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<ByteBuffer> 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<Integer>
|
||||
{
|
||||
|
||||
final int keyCount;
|
||||
|
||||
protected CqlRunOpAlwaysSucceed(ClientWrapper client, String query, Object queryId, List<ByteBuffer> 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<Integer>
|
||||
{
|
||||
|
||||
protected CqlRunOpTestNonEmpty(ClientWrapper client, String query, Object queryId, List<ByteBuffer> 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<byte[][]>
|
||||
{
|
||||
|
||||
protected CqlRunOpFetchKeys(ClientWrapper client, String query, Object queryId, List<ByteBuffer> 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<V> implements RunOp
|
||||
{
|
||||
|
||||
final ClientWrapper client;
|
||||
final String query;
|
||||
final Object queryId;
|
||||
final List<ByteBuffer> params;
|
||||
final String id;
|
||||
final ByteBuffer key;
|
||||
final ResultHandler<V> handler;
|
||||
V result;
|
||||
|
||||
private CqlRunOp(ClientWrapper client, String query, Object queryId, ResultHandler<V> handler, List<ByteBuffer> 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> V execute(Object preparedStatementId, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> handler) throws TException;
|
||||
<V> V execute(String query, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> handler) throws TException;
|
||||
}
|
||||
|
||||
private final class JavaDriverWrapper implements ClientWrapper
|
||||
{
|
||||
final JavaDriverClient client;
|
||||
private JavaDriverWrapper(JavaDriverClient client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> V execute(String query, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> handler)
|
||||
{
|
||||
String formattedQuery = formatCqlQuery(query, queryParams, state.isCql3());
|
||||
return handler.javaDriverHandler().apply(client.execute(formattedQuery, ThriftConversion.fromThrift(state.settings.command.consistencyLevel)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> V execute(Object preparedStatementId, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> 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> V execute(String query, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> handler)
|
||||
{
|
||||
String formattedQuery = formatCqlQuery(query, queryParams, state.isCql3());
|
||||
return handler.thriftHandler().apply(client.execute(formattedQuery, ThriftConversion.fromThrift(state.settings.command.consistencyLevel)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> V execute(Object preparedStatementId, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> 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> V execute(String query, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> 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> V execute(Object preparedStatementId, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> 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> V execute(String query, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> handler) throws TException
|
||||
{
|
||||
String formattedQuery = formatCqlQuery(query, queryParams, false);
|
||||
return handler.simpleNativeHandler().apply(
|
||||
client.execute_cql_query(formattedQuery, key, Compression.NONE)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> V execute(Object preparedStatementId, ByteBuffer key, List<ByteBuffer> queryParams, ResultHandler<V> 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<V>
|
||||
{
|
||||
Function<ResultSet, V> javaDriverHandler();
|
||||
Function<ResultMessage, V> thriftHandler();
|
||||
Function<CqlResult, V> simpleNativeHandler();
|
||||
}
|
||||
|
||||
protected static class RowCountHandler implements ResultHandler<Integer>
|
||||
{
|
||||
static final RowCountHandler INSTANCE = new RowCountHandler();
|
||||
|
||||
@Override
|
||||
public Function<ResultSet, Integer> javaDriverHandler()
|
||||
{
|
||||
return new Function<ResultSet, Integer>()
|
||||
{
|
||||
@Override
|
||||
public Integer apply(ResultSet rows)
|
||||
{
|
||||
if (rows == null)
|
||||
return 0;
|
||||
return rows.all().size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<ResultMessage, Integer> thriftHandler()
|
||||
{
|
||||
return new Function<ResultMessage, Integer>()
|
||||
{
|
||||
@Override
|
||||
public Integer apply(ResultMessage result)
|
||||
{
|
||||
return result instanceof ResultMessage.Rows ? ((ResultMessage.Rows) result).result.size() : 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<CqlResult, Integer> simpleNativeHandler()
|
||||
{
|
||||
return new Function<CqlResult, Integer>()
|
||||
{
|
||||
|
||||
@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<byte[][]>
|
||||
{
|
||||
static final KeysHandler INSTANCE = new KeysHandler();
|
||||
|
||||
@Override
|
||||
public Function<ResultSet, byte[][]> javaDriverHandler()
|
||||
{
|
||||
return new Function<ResultSet, byte[][]>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public byte[][] apply(ResultSet result)
|
||||
{
|
||||
|
||||
if (result == null)
|
||||
return new byte[0][];
|
||||
List<Row> 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<ResultMessage, byte[][]> thriftHandler()
|
||||
{
|
||||
return new Function<ResultMessage, byte[][]>()
|
||||
{
|
||||
|
||||
@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<CqlResult, byte[][]> simpleNativeHandler()
|
||||
{
|
||||
return new Function<CqlResult, byte[][]>()
|
||||
{
|
||||
|
||||
@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<ByteBuffer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Integer>
|
||||
{
|
||||
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<ByteBuffer> 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<String> 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<ByteBuffer> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Integer>
|
||||
{
|
||||
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<String> queryParams = new ArrayList<String>();
|
||||
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<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ByteBuffer> 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<KeySlice> 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<KeySlice> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ByteBuffer> values;
|
||||
|
||||
public Inserter(Session client, int index)
|
||||
{
|
||||
super(client, index);
|
||||
}
|
||||
|
||||
public void run(CassandraClient client) throws IOException
|
||||
{
|
||||
if (values == null)
|
||||
values = generateValues();
|
||||
|
||||
List<Column> columns = new ArrayList<Column>(session.getColumnsPerKey());
|
||||
List<SuperColumn> 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<SuperColumn>();
|
||||
// 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<String, List<Mutation>> row = session.getColumnFamilyType() == ColumnFamilyType.Super
|
||||
? getSuperColumnsMutationMap(superColumns)
|
||||
: getColumnsMutationMap(columns);
|
||||
Map<ByteBuffer, Map<String, List<Mutation>>> 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<String, List<Mutation>> getSuperColumnsMutationMap(List<SuperColumn> superColumns)
|
||||
{
|
||||
List<Mutation> mutations = new ArrayList<Mutation>(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<String, List<Mutation>> getColumnsMutationMap(List<Column> columns)
|
||||
{
|
||||
List<Mutation> mutations = new ArrayList<Mutation>(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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ByteBuffer,List<ColumnOrSuperColumn>> results;
|
||||
|
||||
if (session.getColumnFamilyType() == ColumnFamilyType.Super)
|
||||
{
|
||||
List<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> generateKeys(int start, int limit)
|
||||
{
|
||||
List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
|
||||
|
||||
for (int i = start; i < limit; i++)
|
||||
{
|
||||
keys.add(ByteBuffer.wrap(generateKey()));
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<KeySlice> slices = new ArrayList<KeySlice>();
|
||||
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<KeySlice> slices = new ArrayList<KeySlice>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ColumnOrSuperColumn> 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<ColumnOrSuperColumn> 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CounterColumn> columns = new ArrayList<CounterColumn>();
|
||||
for (int i = 0; i < state.settings.columns.maxColumnsPerKey; i++)
|
||||
columns.add(new CounterColumn(getColumnNameBytes(i), 1L));
|
||||
|
||||
Map<String, List<Mutation>> row;
|
||||
if (state.settings.columns.useSuperColumns)
|
||||
{
|
||||
List<Mutation> 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<Mutation> 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<ByteBuffer, Map<String, List<Mutation>>> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ByteBuffer> 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<KeySlice>[] 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<KeySlice> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Column> columns = generateColumns();
|
||||
|
||||
Map<String, List<Mutation>> row;
|
||||
if (!state.settings.columns.useSuperColumns)
|
||||
{
|
||||
List<Mutation> 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<Mutation> 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<ByteBuffer, Map<String, List<Mutation>>> 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<Column> generateColumns()
|
||||
{
|
||||
final List<ByteBuffer> values = generateColumnValues();
|
||||
final List<Column> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ByteBuffer> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum CliOption
|
||||
{
|
||||
KEY("Key details such as size in bytes and value distribution", SettingsKey.helpPrinter()),
|
||||
COL("Column details such as size and count distribution, data generator, names, comparator and if super columns should be used", SettingsColumn.helpPrinter()),
|
||||
RATE("Thread count, rate limit or automatic mode (default is auto)", SettingsRate.helpPrinter()),
|
||||
MODE("Thrift or CQL with options", SettingsMode.helpPrinter()),
|
||||
SCHEMA("Replication settings, compression, compaction, etc.", SettingsSchema.helpPrinter()),
|
||||
NODE("Nodes to connect to", SettingsNode.helpPrinter()),
|
||||
LOG("Where to log progress to, and the interval at which to do it", SettingsLog.helpPrinter()),
|
||||
TRANSPORT("Custom transport factories", SettingsTransport.helpPrinter()),
|
||||
PORT("The port to connect to cassandra nodes on", SettingsPort.helpPrinter()),
|
||||
SENDTO("-send-to", "Specify a stress server to send this command to", SettingsMisc.sendToDaemonHelpPrinter())
|
||||
;
|
||||
|
||||
private static final Map<String, CliOption> LOOKUP;
|
||||
static
|
||||
{
|
||||
final Map<String, CliOption> lookup = new HashMap<>();
|
||||
for (CliOption cmd : values())
|
||||
{
|
||||
lookup.put("-" + cmd.toString().toLowerCase(), cmd);
|
||||
if (cmd.extraName != null)
|
||||
lookup.put(cmd.extraName, cmd);
|
||||
}
|
||||
LOOKUP = lookup;
|
||||
}
|
||||
|
||||
public static CliOption get(String command)
|
||||
{
|
||||
return LOOKUP.get(command.toLowerCase());
|
||||
}
|
||||
|
||||
public final String extraName;
|
||||
public final String description;
|
||||
private final Runnable helpPrinter;
|
||||
|
||||
private CliOption(String description, Runnable helpPrinter)
|
||||
{
|
||||
this(null, description, helpPrinter);
|
||||
}
|
||||
private CliOption(String extraName, String description, Runnable helpPrinter)
|
||||
{
|
||||
this.extraName = extraName;
|
||||
this.description = description;
|
||||
this.helpPrinter = helpPrinter;
|
||||
}
|
||||
|
||||
public void printHelp()
|
||||
{
|
||||
helpPrinter.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum Command
|
||||
{
|
||||
|
||||
READ(false,
|
||||
SettingsCommand.helpPrinter("read"),
|
||||
"Multiple concurrent reads - the cluster must first be populated by a write test",
|
||||
CommandCategory.BASIC
|
||||
),
|
||||
WRITE(true,
|
||||
SettingsCommand.helpPrinter("write"),
|
||||
"insert",
|
||||
"Multiple concurrent writes against the cluster",
|
||||
CommandCategory.BASIC
|
||||
),
|
||||
MIXED(true,
|
||||
SettingsCommandMixed.helpPrinter(),
|
||||
"Interleaving of any basic commands, with configurable ratio and distribution - the cluster must first be populated by a write test",
|
||||
CommandCategory.MIXED
|
||||
),
|
||||
RANGESLICE(false,
|
||||
SettingsCommandMulti.helpPrinter("range_slice"),
|
||||
"Range slice queries - the cluster must first be populated by a write test",
|
||||
CommandCategory.MULTI
|
||||
),
|
||||
IRANGESLICE(false,
|
||||
SettingsCommandMulti.helpPrinter("indexed_range_slice"),
|
||||
"Range slice queries through a secondary index. The cluster must first be populated by a write test, with indexing enabled.",
|
||||
CommandCategory.MULTI
|
||||
),
|
||||
READMULTI(false,
|
||||
SettingsCommandMulti.helpPrinter("readmulti"),
|
||||
"multi_read",
|
||||
"Multiple concurrent reads fetching multiple rows at once. The cluster must first be populated by a write test.",
|
||||
CommandCategory.MULTI
|
||||
),
|
||||
COUNTERWRITE(true,
|
||||
SettingsCommand.helpPrinter("counteradd"),
|
||||
"counter_add",
|
||||
"Multiple concurrent updates of counters.",
|
||||
CommandCategory.BASIC
|
||||
),
|
||||
COUNTERREAD(false,
|
||||
SettingsCommand.helpPrinter("counterread"),
|
||||
"counter_get",
|
||||
"Multiple concurrent reads of counters. The cluster must first be populated by a counterwrite test.",
|
||||
CommandCategory.BASIC
|
||||
),
|
||||
|
||||
HELP(false, SettingsMisc.helpHelpPrinter(), "-?", "Print help for a command or option", null),
|
||||
PRINT(false, SettingsMisc.printHelpPrinter(), "Inspect the output of a distribution definition", null),
|
||||
LEGACY(false, Legacy.helpPrinter(), "Legacy support mode", null)
|
||||
|
||||
;
|
||||
|
||||
private static final Map<String, Command> LOOKUP;
|
||||
static
|
||||
{
|
||||
final Map<String, Command> lookup = new HashMap<>();
|
||||
for (Command cmd : values())
|
||||
{
|
||||
lookup.put(cmd.toString().toLowerCase(), cmd);
|
||||
if (cmd.extraName != null)
|
||||
lookup.put(cmd.extraName, cmd);
|
||||
}
|
||||
LOOKUP = lookup;
|
||||
}
|
||||
|
||||
public static Command get(String command)
|
||||
{
|
||||
return LOOKUP.get(command.toLowerCase());
|
||||
}
|
||||
|
||||
public final boolean updates;
|
||||
public final CommandCategory category;
|
||||
public final String extraName;
|
||||
public final String description;
|
||||
public final Runnable helpPrinter;
|
||||
|
||||
Command(boolean updates, Runnable helpPrinter, String description, CommandCategory category)
|
||||
{
|
||||
this(updates, helpPrinter, null, description, category);
|
||||
}
|
||||
Command(boolean updates, Runnable helpPrinter, String extra, String description, CommandCategory category)
|
||||
{
|
||||
this.updates = updates;
|
||||
this.category = category;
|
||||
this.helpPrinter = helpPrinter;
|
||||
this.extraName = extra;
|
||||
this.description = description;
|
||||
}
|
||||
public void printHelp()
|
||||
{
|
||||
helpPrinter.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
public enum CommandCategory
|
||||
{
|
||||
BASIC,
|
||||
MULTI,
|
||||
MIXED
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
public enum ConnectionAPI
|
||||
{
|
||||
THRIFT, THRIFT_SMART, SIMPLE_NATIVE, JAVA_DRIVER_NATIVE
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
public enum ConnectionStyle
|
||||
{
|
||||
CQL,
|
||||
CQL_PREPARED,
|
||||
THRIFT
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class GroupedOptions
|
||||
{
|
||||
|
||||
int accepted = 0;
|
||||
|
||||
public boolean accept(String param)
|
||||
{
|
||||
for (Option option : options())
|
||||
{
|
||||
if (option.accept(param))
|
||||
{
|
||||
accepted++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean happy()
|
||||
{
|
||||
for (Option option : options())
|
||||
if (!option.happy())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract List<? extends Option> options();
|
||||
|
||||
// hands the parameters to each of the option groups, and returns the first provided
|
||||
// option group that is happy() after this is done, that also accepted all the parameters
|
||||
public static <G extends GroupedOptions> G select(String[] params, G... groupings)
|
||||
{
|
||||
for (String param : params)
|
||||
{
|
||||
boolean accepted = false;
|
||||
for (GroupedOptions grouping : groupings)
|
||||
accepted |= grouping.accept(param);
|
||||
if (!accepted)
|
||||
throw new IllegalArgumentException("Invalid parameter " + param);
|
||||
}
|
||||
for (G grouping : groupings)
|
||||
if (grouping.happy() && grouping.accepted == params.length)
|
||||
return grouping;
|
||||
return null;
|
||||
}
|
||||
|
||||
// pretty prints all of the option groupings
|
||||
public static void printOptions(PrintStream out, String command, GroupedOptions... groupings)
|
||||
{
|
||||
out.println();
|
||||
boolean firstRow = true;
|
||||
for (GroupedOptions grouping : groupings)
|
||||
{
|
||||
if (!firstRow)
|
||||
{
|
||||
out.println(" OR ");
|
||||
}
|
||||
firstRow = false;
|
||||
|
||||
StringBuilder sb = new StringBuilder("Usage: " + command);
|
||||
for (Option option : grouping.options())
|
||||
{
|
||||
sb.append(" ");
|
||||
sb.append(option.shortDisplay());
|
||||
}
|
||||
out.println(sb.toString());
|
||||
}
|
||||
out.println();
|
||||
final Set<Option> printed = new HashSet<>();
|
||||
for (GroupedOptions grouping : groupings)
|
||||
{
|
||||
for (Option option : grouping.options())
|
||||
{
|
||||
if (printed.add(option))
|
||||
{
|
||||
if (option.longDisplay() != null)
|
||||
{
|
||||
out.println(" " + option.longDisplay());
|
||||
for (String row : option.multiLineDisplay())
|
||||
out.println(" " + row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatLong(String longDisplay, String description)
|
||||
{
|
||||
return String.format("%-40s %s", longDisplay, description);
|
||||
}
|
||||
|
||||
public static String formatMultiLine(String longDisplay, String description)
|
||||
{
|
||||
return String.format("%-36s %s", longDisplay, description);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.cli.*;
|
||||
import org.apache.commons.cli.Option;
|
||||
|
||||
public class Legacy implements Serializable
|
||||
{
|
||||
|
||||
// command line options
|
||||
public static final Options availableOptions = new Options();
|
||||
|
||||
private static final String SSL_TRUSTSTORE = "truststore";
|
||||
private static final String SSL_TRUSTSTORE_PW = "truststore-password";
|
||||
private static final String SSL_PROTOCOL = "ssl-protocol";
|
||||
private static final String SSL_ALGORITHM = "ssl-alg";
|
||||
private static final String SSL_STORE_TYPE = "store-type";
|
||||
private static final String SSL_CIPHER_SUITES = "ssl-ciphers";
|
||||
|
||||
static
|
||||
{
|
||||
availableOptions.addOption("h", "help", false, "Show this help message and exit");
|
||||
availableOptions.addOption("n", "num-keys", true, "Number of keys, default:1000000");
|
||||
availableOptions.addOption("F", "num-different-keys", true, "Number of different keys (if < NUM-KEYS, the same key will re-used multiple times), default:NUM-KEYS");
|
||||
availableOptions.addOption("t", "threadCount", true, "Number of threadCount to use, default:50");
|
||||
availableOptions.addOption("c", "columns", true, "Number of columns per key, default:5");
|
||||
availableOptions.addOption("S", "column-size", true, "Size of column values in bytes, default:34");
|
||||
availableOptions.addOption("C", "unique columns", true, "Max number of unique columns per key, default:50");
|
||||
availableOptions.addOption("RC", "unique rows", true, "Max number of unique rows, default:50");
|
||||
availableOptions.addOption("d", "nodes", true, "Host nodes (comma separated), default:locahost");
|
||||
availableOptions.addOption("D", "nodesfile", true, "File containing host nodes (one per line)");
|
||||
availableOptions.addOption("s", "stdev", true, "Standard Deviation for gaussian read key generation, default:0.1");
|
||||
availableOptions.addOption("r", "random", false, "Use random key generator for read key generation (STDEV will have no effect), default:false");
|
||||
availableOptions.addOption("f", "file", true, "Write output to given file");
|
||||
availableOptions.addOption("p", "port", true, "Thrift port, default:9160");
|
||||
availableOptions.addOption("o", "operation", true, "Operation to perform (WRITE, READ, READWRITE, RANGE_SLICE, INDEXED_RANGE_SLICE, MULTI_GET, COUNTERWRITE, COUNTER_GET), default:WRITE");
|
||||
availableOptions.addOption("u", "supercolumns", true, "Number of super columns per key, default:1");
|
||||
availableOptions.addOption("y", "family-type", true, "Column Family Type (Super, Standard), default:Standard");
|
||||
availableOptions.addOption("K", "keep-trying", true, "Retry on-going operation N times (in case of failure). positive integer, default:10");
|
||||
availableOptions.addOption("k", "keep-going", false, "Ignore errors inserting or reading (when set, --keep-trying has no effect), default:false");
|
||||
availableOptions.addOption("i", "progress-interval", true, "Progress Report Interval (seconds), default:10");
|
||||
availableOptions.addOption("g", "keys-per-call", true, "Number of keys to get_range_slices or multiget per call, default:1000");
|
||||
availableOptions.addOption("l", "replication-factor", true, "Replication Factor to use when creating needed column families, default:1");
|
||||
availableOptions.addOption("L", "enable-cql", false, "Perform queries using CQL2 (Cassandra Query Language v 2.0.0)");
|
||||
availableOptions.addOption("L3", "enable-cql3", false, "Perform queries using CQL3 (Cassandra Query Language v 3.0.0)");
|
||||
availableOptions.addOption("b", "enable-native-protocol", false, "Use the binary native protocol (only work along with -L3)");
|
||||
availableOptions.addOption("P", "use-prepared-statements", false, "Perform queries using prepared statements (only applicable to CQL).");
|
||||
availableOptions.addOption("e", "consistency-level", true, "Consistency Level to use (ONE, QUORUM, LOCAL_QUORUM, EACH_QUORUM, ALL, ANY), default:ONE");
|
||||
availableOptions.addOption("x", "create-index", true, "Type of index to create on needed column families (KEYS)");
|
||||
availableOptions.addOption("R", "replication-strategy", true, "Replication strategy to use (only on insert if keyspace does not exist), default:org.apache.cassandra.locator.SimpleStrategy");
|
||||
availableOptions.addOption("O", "strategy-properties", true, "Replication strategy properties in the following format <dc_name>:<num>,<dc_name>:<num>,...");
|
||||
availableOptions.addOption("W", "no-replicate-on-write",false, "Set replicate_on_write to false for counters. Only counter add with CL=ONE will work");
|
||||
availableOptions.addOption("V", "average-size-values", false, "Generate column values of average rather than specific size");
|
||||
availableOptions.addOption("T", "send-to", true, "Send this as a request to the stress daemon at specified address.");
|
||||
availableOptions.addOption("I", "compression", true, "Specify the compression to use for sstable, default:no compression");
|
||||
availableOptions.addOption("Q", "query-names", true, "Comma-separated list of column names to retrieve from each row.");
|
||||
availableOptions.addOption("Z", "compaction-strategy", true, "CompactionStrategy to use.");
|
||||
availableOptions.addOption("U", "comparator", true, "Column Comparator to use. Currently supported types are: TimeUUIDType, AsciiType, UTF8Type.");
|
||||
availableOptions.addOption("tf", "transport-factory", true, "Fully-qualified TTransportFactory class name for creating a connection. Note: For Thrift over SSL, use org.apache.cassandra.stress.SSLTransportFactory.");
|
||||
availableOptions.addOption("ns", "no-statistics", false, "Turn off the aggegate statistics that is normally output after completion.");
|
||||
availableOptions.addOption("ts", SSL_TRUSTSTORE, true, "SSL: full path to truststore");
|
||||
availableOptions.addOption("tspw", SSL_TRUSTSTORE_PW, true, "SSL: full path to truststore");
|
||||
availableOptions.addOption("prtcl", SSL_PROTOCOL, true, "SSL: connections protocol to use (default: TLS)");
|
||||
availableOptions.addOption("alg", SSL_ALGORITHM, true, "SSL: algorithm (default: SunX509)");
|
||||
availableOptions.addOption("st", SSL_STORE_TYPE, true, "SSL: type of store");
|
||||
availableOptions.addOption("ciphers", SSL_CIPHER_SUITES, true, "SSL: comma-separated list of encryption suites to use");
|
||||
availableOptions.addOption("th", "throttle", true, "Throttle the total number of operations per second to a maximum amount.");
|
||||
}
|
||||
|
||||
public static StressSettings build(String[] arguments)
|
||||
{
|
||||
CommandLineParser parser = new PosixParser();
|
||||
|
||||
final Converter r = new Converter();
|
||||
try
|
||||
{
|
||||
CommandLine cmd = parser.parse(availableOptions, arguments);
|
||||
|
||||
if (cmd.getArgs().length > 0)
|
||||
{
|
||||
System.err.println("Application does not allow arbitrary arguments: " + Arrays.asList(cmd.getArgList()));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (cmd.hasOption("h"))
|
||||
printHelpMessage();
|
||||
|
||||
if (cmd.hasOption("C"))
|
||||
System.out.println("Ignoring deprecated option -C");
|
||||
|
||||
if (cmd.hasOption("o"))
|
||||
r.setCommand(cmd.getOptionValue("o").toLowerCase());
|
||||
else
|
||||
r.setCommand("insert");
|
||||
|
||||
if (cmd.hasOption("K"))
|
||||
r.add("command", "tries=" + cmd.getOptionValue("K"));
|
||||
|
||||
if (cmd.hasOption("k"))
|
||||
{
|
||||
if (!cmd.hasOption("K"))
|
||||
r.add("command", "retry=1");
|
||||
r.add("command", "ignore_errors");
|
||||
}
|
||||
|
||||
if (cmd.hasOption("g"))
|
||||
r.add("command", "at-once=" + cmd.getOptionValue("g"));
|
||||
|
||||
if (cmd.hasOption("e"))
|
||||
r.add("command", "cl=" + cmd.getOptionValue("e"));
|
||||
|
||||
String numKeys;
|
||||
if (cmd.hasOption("n"))
|
||||
numKeys = cmd.getOptionValue("n");
|
||||
else
|
||||
numKeys = "1000000";
|
||||
r.add("command", "n=" + numKeys);
|
||||
|
||||
String uniqueKeys;
|
||||
if (cmd.hasOption("F"))
|
||||
uniqueKeys = cmd.getOptionValue("F");
|
||||
else
|
||||
uniqueKeys = numKeys;
|
||||
|
||||
if (r.opts.containsKey("write") || r.opts.containsKey("counterwrite"))
|
||||
{
|
||||
if (!uniqueKeys.equals(numKeys))
|
||||
r.add("-key", "populate=1.." + uniqueKeys);
|
||||
}
|
||||
else if (cmd.hasOption("r"))
|
||||
{
|
||||
r.add("-key", "dist=uniform(1.." + uniqueKeys + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!cmd.hasOption("s"))
|
||||
r.add("-key", "dist=gauss(1.." + uniqueKeys + ",5)");
|
||||
else
|
||||
r.add("-key", String.format("dist=gauss(1..%s,%.2f)", uniqueKeys,
|
||||
0.5 / Float.parseFloat(cmd.getOptionValue("s"))));
|
||||
}
|
||||
|
||||
String colCount;
|
||||
if (cmd.hasOption("c"))
|
||||
colCount = cmd.getOptionValue("c");
|
||||
else
|
||||
colCount = "5";
|
||||
|
||||
String colSize;
|
||||
if (cmd.hasOption("S"))
|
||||
colSize = cmd.getOptionValue("S");
|
||||
else
|
||||
colSize = "34";
|
||||
|
||||
r.add("-col", "n=fixed(" + colCount + ")");
|
||||
if (cmd.hasOption("V"))
|
||||
{
|
||||
r.add("-col", "size=uniform(1.." + Integer.parseInt(colSize) * 2 + ")");
|
||||
r.add("-col", "data=rand()");
|
||||
}
|
||||
else
|
||||
{
|
||||
r.add("-col", "size=fixed(" + colSize + ")");
|
||||
r.add("-col", "data=repeat(1)");
|
||||
}
|
||||
if (cmd.hasOption("Q"))
|
||||
r.add("-col", "names=" + cmd.getOptionValue("Q"));
|
||||
|
||||
if (cmd.hasOption("U"))
|
||||
r.add("-col", "comparator=" + cmd.getOptionValue("U"));
|
||||
|
||||
if (cmd.hasOption("y") && cmd.getOptionValue("y").equals("Super"))
|
||||
r.add("-col", "super=" + (cmd.hasOption("u") ? cmd.getOptionValue("u") : "1"));
|
||||
|
||||
if (cmd.hasOption("t"))
|
||||
r.add("-rate", "threads=" + cmd.getOptionValue("t"));
|
||||
else
|
||||
r.add("-rate", "threads=50");
|
||||
|
||||
if (cmd.hasOption("th"))
|
||||
r.add("-rate", "limit=" + cmd.getOptionValue("th") + "/s");
|
||||
|
||||
if (cmd.hasOption("f"))
|
||||
r.add("-log", "file=" + cmd.getOptionValue("f"));
|
||||
|
||||
if (cmd.hasOption("p"))
|
||||
r.add("-port", cmd.getOptionValue("p"));
|
||||
|
||||
if (cmd.hasOption("i"))
|
||||
r.add("-log", "interval=" + cmd.getOptionValue("i"));
|
||||
else
|
||||
r.add("-log", "interval=10");
|
||||
|
||||
if (cmd.hasOption("x"))
|
||||
r.add("-schema", "index=" + cmd.getOptionValue("x"));
|
||||
|
||||
if (cmd.hasOption("R") || cmd.hasOption("l") || cmd.hasOption("O"))
|
||||
{
|
||||
StringBuilder rep = new StringBuilder();
|
||||
if (cmd.hasOption("R"))
|
||||
rep.append("strategy=" + cmd.getOptionValue("R"));
|
||||
if (cmd.hasOption("l"))
|
||||
{
|
||||
if (rep.length() > 0)
|
||||
rep.append(",");
|
||||
rep.append("factor=" + cmd.getOptionValue("l"));
|
||||
}
|
||||
if (cmd.hasOption("O"))
|
||||
{
|
||||
if (rep.length() > 0)
|
||||
rep.append(",");
|
||||
rep.append(cmd.getOptionValue("O").replace(':','='));
|
||||
}
|
||||
r.add("-schema", "replication(" + rep + ")");
|
||||
}
|
||||
|
||||
if (cmd.hasOption("L"))
|
||||
r.add("-mode", cmd.hasOption("P") ? "prepared cql2" : "cql2");
|
||||
else if (cmd.hasOption("L3"))
|
||||
r.add("-mode", (cmd.hasOption("P") ? "prepared" : "") + (cmd.hasOption("b") ? "native" : "") + "cql3");
|
||||
else
|
||||
r.add("-mode", "thrift");
|
||||
|
||||
if (cmd.hasOption("W"))
|
||||
r.add("-schema", "no-replicate-on-write");
|
||||
|
||||
if (cmd.hasOption("I"))
|
||||
r.add("-schema", "compression=" + cmd.getOptionValue("I"));
|
||||
|
||||
if (cmd.hasOption("d"))
|
||||
r.add("-node", cmd.getOptionValue("d"));
|
||||
|
||||
if (cmd.hasOption("D"))
|
||||
r.add("-node", "file=" + cmd.getOptionValue("D"));
|
||||
|
||||
|
||||
if (cmd.hasOption("send-to"))
|
||||
r.add("-send-to", cmd.getOptionValue("send-to"));
|
||||
|
||||
if (cmd.hasOption("Z"))
|
||||
r.add("-schema", "compaction=" + cmd.getOptionValue("Z"));
|
||||
|
||||
if (cmd.hasOption("ns"))
|
||||
r.add("-log", "no-summary");
|
||||
|
||||
if (cmd.hasOption("tf"))
|
||||
r.add("-transport", "factory=" + cmd.getOptionValue("tf"));
|
||||
|
||||
// THESE DON'T SEEM TO AFFECT PROGRAM BEHAVIOUR
|
||||
// if(cmd.hasOption(SSL_TRUSTSTORE))
|
||||
// encOptions.truststore = cmd.getOptionValue(SSL_TRUSTSTORE);
|
||||
//
|
||||
// if(cmd.hasOption(SSL_TRUSTSTORE_PW))
|
||||
// encOptions.truststore_password = cmd.getOptionValue(SSL_TRUSTSTORE_PW);
|
||||
//
|
||||
// if(cmd.hasOption(SSL_PROTOCOL))
|
||||
// encOptions.protocol = cmd.getOptionValue(SSL_PROTOCOL);
|
||||
//
|
||||
// if(cmd.hasOption(SSL_ALGORITHM))
|
||||
// encOptions.algorithm = cmd.getOptionValue(SSL_ALGORITHM);
|
||||
//
|
||||
// if(cmd.hasOption(SSL_STORE_TYPE))
|
||||
// encOptions.store_type = cmd.getOptionValue(SSL_STORE_TYPE);
|
||||
//
|
||||
// if(cmd.hasOption(SSL_CIPHER_SUITES))
|
||||
// encOptions.cipher_suites = cmd.getOptionValue(SSL_CIPHER_SUITES).split(",");
|
||||
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
printHelpMessage();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
r.printNewCommand();
|
||||
return r.get();
|
||||
}
|
||||
|
||||
private static final class Converter
|
||||
{
|
||||
private Map<String, List<String>> opts = new LinkedHashMap<>();
|
||||
List<String> command;
|
||||
public void add(String option, String suboption)
|
||||
{
|
||||
if (option.equals("command"))
|
||||
{
|
||||
command.add(suboption);
|
||||
return;
|
||||
}
|
||||
List<String> params = opts.get(option);
|
||||
if (params == null)
|
||||
opts.put(option, params = new ArrayList());
|
||||
params.add(suboption);
|
||||
}
|
||||
StressSettings get(){
|
||||
Map<String, String[]> clArgs = new HashMap<>();
|
||||
for (Map.Entry<String, List<String>> e : opts.entrySet())
|
||||
clArgs .put(e.getKey(), e.getValue().toArray(new String[0]));
|
||||
return StressSettings.get(clArgs);
|
||||
}
|
||||
void setCommand(String command)
|
||||
{
|
||||
command = Command.get(command).toString().toLowerCase();
|
||||
opts.put(command, this.command = new ArrayList<>());
|
||||
}
|
||||
void printNewCommand()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder("stress");
|
||||
for (Map.Entry<String, List<String>> e : opts.entrySet())
|
||||
{
|
||||
sb.append(" ");
|
||||
sb.append(e.getKey());
|
||||
for (String opt : e.getValue())
|
||||
{
|
||||
sb.append(" ");
|
||||
sb.append(opt);
|
||||
}
|
||||
}
|
||||
System.out.println("Running in legacy support mode. Translating command to: ");
|
||||
System.out.println(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void printHelpMessage()
|
||||
{
|
||||
System.out.println("Usage: ./bin/cassandra-stress legacy [options]\n\nOptions:");
|
||||
System.out.println("THIS IS A LEGACY SUPPORT MODE");
|
||||
|
||||
for(Object o : availableOptions.getOptions())
|
||||
{
|
||||
Option option = (Option) o;
|
||||
String upperCaseName = option.getLongOpt().toUpperCase();
|
||||
System.out.println(String.format("-%s%s, --%s%s%n\t\t%s%n", option.getOpt(), (option.hasArg()) ? " "+upperCaseName : "",
|
||||
option.getLongOpt(), (option.hasArg()) ? "="+upperCaseName : "", option.getDescription()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelpMessage();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
abstract class Option
|
||||
{
|
||||
|
||||
abstract boolean accept(String param);
|
||||
abstract boolean happy();
|
||||
abstract String shortDisplay();
|
||||
abstract String longDisplay();
|
||||
abstract List<String> multiLineDisplay();
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object that)
|
||||
{
|
||||
return this.getClass() == that.getClass();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cassandra.stress.generatedata.*;
|
||||
|
||||
/**
|
||||
* For selecting a data generator
|
||||
*/
|
||||
class OptionDataGen extends Option
|
||||
{
|
||||
|
||||
private static final Pattern FULL = Pattern.compile("([A-Z]+)\\(([^)]+)\\)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern ARGS = Pattern.compile("[^,]+");
|
||||
|
||||
final String prefix;
|
||||
private DataGenFactory factory;
|
||||
private final DataGenFactory defaultFactory;
|
||||
|
||||
public OptionDataGen(String prefix, String defaultSpec)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.defaultFactory = defaultSpec == null ? null : get(defaultSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(String param)
|
||||
{
|
||||
if (!param.toLowerCase().startsWith(prefix))
|
||||
return false;
|
||||
factory = get(param.substring(prefix.length()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DataGenFactory get(String spec)
|
||||
{
|
||||
Matcher m = FULL.matcher(spec);
|
||||
if (!m.matches())
|
||||
throw new IllegalArgumentException("Illegal data generator specification: " + spec);
|
||||
String name = m.group(1);
|
||||
Impl impl = LOOKUP.get(name.toLowerCase());
|
||||
if (impl == null)
|
||||
throw new IllegalArgumentException("Illegal data generator type: " + name);
|
||||
List<String> params = new ArrayList<>();
|
||||
m = ARGS.matcher(m.group(2));
|
||||
while (m.find())
|
||||
params.add(m.group());
|
||||
return impl.getFactory(params);
|
||||
}
|
||||
|
||||
public DataGenFactory get()
|
||||
{
|
||||
return factory != null ? factory : defaultFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean happy()
|
||||
{
|
||||
return factory != null || defaultFactory != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortDisplay()
|
||||
{
|
||||
return prefix + "ALG()";
|
||||
}
|
||||
|
||||
public String longDisplay()
|
||||
{
|
||||
return shortDisplay() + ": Specify a data generator from:";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> multiLineDisplay()
|
||||
{
|
||||
return Arrays.asList(
|
||||
GroupedOptions.formatMultiLine("RANDOM()", "Completely random byte generation"),
|
||||
GroupedOptions.formatMultiLine("REPEAT(<freq>)", "An MD5 hash of (opIndex % freq) combined with the column index"),
|
||||
GroupedOptions.formatMultiLine("DICT(<file>)","Random words from a dictionary; the file should be in the format \"<freq> <word>\"")
|
||||
);
|
||||
}
|
||||
|
||||
private static final Map<String, Impl> LOOKUP;
|
||||
static
|
||||
{
|
||||
final Map<String, Impl> lookup = new HashMap<>();
|
||||
lookup.put("random", new RandomImpl());
|
||||
lookup.put("rand", new RandomImpl());
|
||||
lookup.put("rnd", new RandomImpl());
|
||||
lookup.put("repeat", new RepeatImpl());
|
||||
lookup.put("dict", new DictionaryImpl());
|
||||
lookup.put("dictionary", new DictionaryImpl());
|
||||
LOOKUP = lookup;
|
||||
}
|
||||
|
||||
private static interface Impl
|
||||
{
|
||||
public DataGenFactory getFactory(List<String> params);
|
||||
}
|
||||
|
||||
private static final class RandomImpl implements Impl
|
||||
{
|
||||
@Override
|
||||
public DataGenFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 0)
|
||||
throw new IllegalArgumentException("Invalid parameter list for random generator: " + params);
|
||||
return new RandomFactory();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RepeatImpl implements Impl
|
||||
{
|
||||
|
||||
@Override
|
||||
public DataGenFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 1)
|
||||
throw new IllegalArgumentException("Invalid parameter list for repeating generator: " + params);
|
||||
try
|
||||
{
|
||||
int repeatFrequency = Integer.parseInt(params.get(0));
|
||||
return new RepeatsFactory(repeatFrequency);
|
||||
} catch (Exception _)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for repeating generator: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DictionaryImpl implements Impl
|
||||
{
|
||||
|
||||
@Override
|
||||
public DataGenFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 1)
|
||||
throw new IllegalArgumentException("Invalid parameter list for dictionary generator: " + params);
|
||||
try
|
||||
{
|
||||
final File file = new File(params.get(0));
|
||||
return DataGenStringDictionary.getFactory(file);
|
||||
} catch (Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for dictionary generator: " + params, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RandomFactory implements DataGenFactory
|
||||
{
|
||||
@Override
|
||||
public DataGen get()
|
||||
{
|
||||
return new DataGenBytesRandom();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RepeatsFactory implements DataGenFactory
|
||||
{
|
||||
final int frequency;
|
||||
private RepeatsFactory(int frequency)
|
||||
{
|
||||
this.frequency = frequency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataGen get()
|
||||
{
|
||||
return new DataGenStringRepeats(frequency);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cassandra.stress.generatedata.*;
|
||||
import org.apache.commons.math3.distribution.ExponentialDistribution;
|
||||
import org.apache.commons.math3.distribution.NormalDistribution;
|
||||
import org.apache.commons.math3.distribution.UniformRealDistribution;
|
||||
import org.apache.commons.math3.distribution.WeibullDistribution;
|
||||
|
||||
/**
|
||||
* For selecting a mathematical distribution
|
||||
*/
|
||||
class OptionDistribution extends Option
|
||||
{
|
||||
|
||||
private static final Pattern FULL = Pattern.compile("([A-Z]+)\\((.+)\\)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern ARGS = Pattern.compile("[^,]+");
|
||||
|
||||
final String prefix;
|
||||
private String spec;
|
||||
private final String defaultSpec;
|
||||
|
||||
public OptionDistribution(String prefix, String defaultSpec)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.defaultSpec = defaultSpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(String param)
|
||||
{
|
||||
if (!param.toLowerCase().startsWith(prefix))
|
||||
return false;
|
||||
spec = param.substring(prefix.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DistributionFactory get(String spec)
|
||||
{
|
||||
Matcher m = FULL.matcher(spec);
|
||||
if (!m.matches())
|
||||
throw new IllegalArgumentException("Illegal distribution specification: " + spec);
|
||||
String name = m.group(1);
|
||||
Impl impl = LOOKUP.get(name.toLowerCase());
|
||||
if (impl == null)
|
||||
throw new IllegalArgumentException("Illegal distribution type: " + name);
|
||||
List<String> params = new ArrayList<>();
|
||||
m = ARGS.matcher(m.group(2));
|
||||
while (m.find())
|
||||
params.add(m.group());
|
||||
return impl.getFactory(params);
|
||||
}
|
||||
|
||||
public DistributionFactory get()
|
||||
{
|
||||
return spec != null ? get(spec) : get(defaultSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean happy()
|
||||
{
|
||||
return spec != null || defaultSpec != null;
|
||||
}
|
||||
|
||||
public String longDisplay()
|
||||
{
|
||||
return shortDisplay() + ": Specify a mathematical distribution";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> multiLineDisplay()
|
||||
{
|
||||
return Arrays.asList(
|
||||
GroupedOptions.formatMultiLine("EXP(min..max)", "An exponential distribution over the range [min..max]"),
|
||||
GroupedOptions.formatMultiLine("EXTREME(min..max,shape)", "An extreme value (Weibull) distribution over the range [min..max]"),
|
||||
GroupedOptions.formatMultiLine("GAUSSIAN(min..max,stdvrng)", "A gaussian/normal distribution, where mean=(min+max)/2, and stdev is (mean-min)/stdvrng"),
|
||||
GroupedOptions.formatMultiLine("GAUSSIAN(min..max,mean,stdev)", "A gaussian/normal distribution, with explicitly defined mean and stdev"),
|
||||
GroupedOptions.formatMultiLine("UNIFORM(min..max)", "A uniform distribution over the range [min, max]"),
|
||||
GroupedOptions.formatMultiLine("FIXED(val)", "A fixed distribution, always returning the same value"),
|
||||
"Aliases: extr, gauss, normal, norm, weibull"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortDisplay()
|
||||
{
|
||||
return prefix + "DIST(?)";
|
||||
}
|
||||
|
||||
private static final Map<String, Impl> LOOKUP;
|
||||
static
|
||||
{
|
||||
final Map<String, Impl> lookup = new HashMap<>();
|
||||
lookup.put("exp", new ExponentialImpl());
|
||||
lookup.put("extr", new ExtremeImpl());
|
||||
lookup.put("extreme", lookup.get("extreme"));
|
||||
lookup.put("weibull", lookup.get("weibull"));
|
||||
lookup.put("gaussian", new GaussianImpl());
|
||||
lookup.put("normal", lookup.get("gaussian"));
|
||||
lookup.put("gauss", lookup.get("gaussian"));
|
||||
lookup.put("norm", lookup.get("gaussian"));
|
||||
lookup.put("uniform", new UniformImpl());
|
||||
lookup.put("fixed", new FixedImpl());
|
||||
LOOKUP = lookup;
|
||||
}
|
||||
|
||||
// factory builders
|
||||
|
||||
private static interface Impl
|
||||
{
|
||||
public DistributionFactory getFactory(List<String> params);
|
||||
}
|
||||
|
||||
private static final class GaussianImpl implements Impl
|
||||
{
|
||||
|
||||
@Override
|
||||
public DistributionFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() > 3 || params.size() < 1)
|
||||
throw new IllegalArgumentException("Invalid parameter list for gaussian distribution: " + params);
|
||||
try
|
||||
{
|
||||
String[] bounds = params.get(0).split("\\.\\.+");
|
||||
final long min = Long.parseLong(bounds[0]);
|
||||
final long max = Long.parseLong(bounds[1]);
|
||||
final double mean, stdev;
|
||||
if (params.size() == 3)
|
||||
{
|
||||
mean = Double.parseDouble(params.get(1));
|
||||
stdev = Double.parseDouble(params.get(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
final double stdevsToEdge = params.size() == 1 ? 3d : Double.parseDouble(params.get(1));
|
||||
mean = (min + max) / 2d;
|
||||
stdev = ((max - min) / 2d) / stdevsToEdge;
|
||||
}
|
||||
return new GaussianFactory(min, max, mean, stdev);
|
||||
} catch (Exception _)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExponentialImpl implements Impl
|
||||
{
|
||||
@Override
|
||||
public DistributionFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 1)
|
||||
throw new IllegalArgumentException("Invalid parameter list for gaussian distribution: " + params);
|
||||
try
|
||||
{
|
||||
String[] bounds = params.get(0).split("\\.\\.+");
|
||||
final long min = Long.parseLong(bounds[0]);
|
||||
final long max = Long.parseLong(bounds[1]);
|
||||
ExponentialDistribution findBounds = new ExponentialDistribution(1d);
|
||||
// max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
|
||||
// over entire range, but this results in overly skewed distribution, so take sqrt
|
||||
final double mean = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
|
||||
return new ExpFactory(min, max, mean);
|
||||
} catch (Exception _)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExtremeImpl implements Impl
|
||||
{
|
||||
@Override
|
||||
public DistributionFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 2)
|
||||
throw new IllegalArgumentException("Invalid parameter list for extreme (Weibull) distribution: " + params);
|
||||
try
|
||||
{
|
||||
String[] bounds = params.get(0).split("\\.\\.+");
|
||||
final long min = Long.parseLong(bounds[0]);
|
||||
final long max = Long.parseLong(bounds[1]);
|
||||
final double shape = Double.parseDouble(params.get(1));
|
||||
WeibullDistribution findBounds = new WeibullDistribution(shape, 1d);
|
||||
// max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
|
||||
// over entire range, but this results in overly skewed distribution, so take sqrt
|
||||
final double scale = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
|
||||
return new ExtremeFactory(min, max, shape, scale);
|
||||
} catch (Exception _)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for extreme (Weibull) distribution: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UniformImpl implements Impl
|
||||
{
|
||||
|
||||
@Override
|
||||
public DistributionFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 1)
|
||||
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
|
||||
try
|
||||
{
|
||||
String[] bounds = params.get(0).split("\\.\\.+");
|
||||
final long min = Long.parseLong(bounds[0]);
|
||||
final long max = Long.parseLong(bounds[1]);
|
||||
return new UniformFactory(min, max);
|
||||
} catch (Exception _)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FixedImpl implements Impl
|
||||
{
|
||||
|
||||
@Override
|
||||
public DistributionFactory getFactory(List<String> params)
|
||||
{
|
||||
if (params.size() != 1)
|
||||
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
|
||||
try
|
||||
{
|
||||
final long key = Long.parseLong(params.get(0));
|
||||
return new FixedFactory(key);
|
||||
} catch (Exception _)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// factories
|
||||
|
||||
private static final class ExpFactory implements DistributionFactory
|
||||
{
|
||||
final long min, max;
|
||||
final double mean;
|
||||
private ExpFactory(long min, long max, double mean)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.mean = mean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distribution get()
|
||||
{
|
||||
return new DistributionOffsetApache(new ExponentialDistribution(mean), min, max);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExtremeFactory implements DistributionFactory
|
||||
{
|
||||
final long min, max;
|
||||
final double shape, scale;
|
||||
private ExtremeFactory(long min, long max, double shape, double scale)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.shape = shape;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distribution get()
|
||||
{
|
||||
return new DistributionOffsetApache(new WeibullDistribution(shape, scale), min, max);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GaussianFactory implements DistributionFactory
|
||||
{
|
||||
final long min, max;
|
||||
final double mean, stdev;
|
||||
private GaussianFactory(long min, long max, double mean, double stdev)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.stdev = stdev;
|
||||
this.mean = mean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distribution get()
|
||||
{
|
||||
return new DistributionBoundApache(new NormalDistribution(mean, stdev), min, max);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UniformFactory implements DistributionFactory
|
||||
{
|
||||
final long min, max;
|
||||
private UniformFactory(long min, long max)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distribution get()
|
||||
{
|
||||
return new DistributionBoundApache(new UniformRealDistribution(min, max), min, max);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FixedFactory implements DistributionFactory
|
||||
{
|
||||
final long key;
|
||||
private FixedFactory(long key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distribution get()
|
||||
{
|
||||
return new DistributionFixed(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return prefix.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that)
|
||||
{
|
||||
return super.equals(that) && ((OptionDistribution) that).prefix.equals(this.prefix);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* For specifying multiple grouped sub-options in the form: group(arg1=,arg2,arg3) etc.
|
||||
*/
|
||||
abstract class OptionMulti extends Option
|
||||
{
|
||||
|
||||
private static final Pattern ARGS = Pattern.compile("([^,]+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final class Delegate extends GroupedOptions
|
||||
{
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return OptionMulti.this.options();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<? extends Option> options();
|
||||
|
||||
private final String name;
|
||||
private final Pattern pattern;
|
||||
private final String description;
|
||||
private final Delegate delegate = new Delegate();
|
||||
public OptionMulti(String name, String description)
|
||||
{
|
||||
this.name = name;
|
||||
pattern = Pattern.compile(name + "\\((.*)\\)", Pattern.CASE_INSENSITIVE);
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(String param)
|
||||
{
|
||||
Matcher m = pattern.matcher(param);
|
||||
if (!m.matches())
|
||||
return false;
|
||||
m = ARGS.matcher(m.group(1));
|
||||
int last = -1;
|
||||
while (m.find())
|
||||
{
|
||||
if (m.start() != last + 1)
|
||||
throw new IllegalArgumentException("Invalid " + name + " specification: " + param);
|
||||
last = m.end();
|
||||
if (!delegate.accept(m.group()))
|
||||
throw new IllegalArgumentException("Invalid " + name + " specification: " + m.group());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
sb.append("(");
|
||||
for (Option option : options())
|
||||
{
|
||||
sb.append(option);
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortDisplay()
|
||||
{
|
||||
return name + "(?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String longDisplay()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
sb.append("(");
|
||||
for (Option opt : options())
|
||||
{
|
||||
sb.append(opt.shortDisplay());
|
||||
}
|
||||
sb.append("): ");
|
||||
sb.append(description);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> multiLineDisplay()
|
||||
{
|
||||
final List<String> r = new ArrayList<>();
|
||||
for (Option option : options())
|
||||
r.add(option.longDisplay());
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean happy()
|
||||
{
|
||||
return delegate.happy();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cassandra.locator.AbstractReplicationStrategy;
|
||||
|
||||
/**
|
||||
* For specifying replication options
|
||||
*/
|
||||
class OptionReplication extends Option
|
||||
{
|
||||
|
||||
private static final Pattern FULL = Pattern.compile("replication\\((.*)\\)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern OPTION = Pattern.compile("([^,=]+)=([^,]+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private String strategy = "org.apache.cassandra.locator.SimpleStrategy";
|
||||
private Map<String, String> options = new HashMap<>();
|
||||
|
||||
public String getStrategy()
|
||||
{
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public Map<String, String> getOptions()
|
||||
{
|
||||
if (!options.containsKey("replication_factor") && strategy.endsWith("SimpleStrategy"))
|
||||
options.put("replication_factor", "1");
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean accept(String param)
|
||||
{
|
||||
Matcher m = FULL.matcher(param);
|
||||
if (!m.matches())
|
||||
return false;
|
||||
String args = m.group(1);
|
||||
m = OPTION.matcher(args);
|
||||
int last = -1;
|
||||
while (m.find())
|
||||
{
|
||||
if (m.start() != last + 1)
|
||||
throw new IllegalArgumentException("Invalid replication specification: " + param);
|
||||
last = m.end();
|
||||
String key = m.group(1).toLowerCase();
|
||||
sw: switch(key)
|
||||
{
|
||||
case "factor":
|
||||
try
|
||||
{
|
||||
Integer.parseInt(m.group(2));
|
||||
} catch (NumberFormatException e)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid replication factor: " + param);
|
||||
}
|
||||
options.put("replication_factor", m.group(2));
|
||||
break;
|
||||
case "strategy":
|
||||
for (String name : new String[] { m.group(2), "org.apache.cassandra.locator." + m.group(2) })
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(name);
|
||||
if (!AbstractReplicationStrategy.class.isAssignableFrom(clazz))
|
||||
throw new RuntimeException();
|
||||
strategy = name;
|
||||
break sw;
|
||||
} catch (Exception _)
|
||||
{
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid replication strategy: " + param);
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean happy()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortDisplay()
|
||||
{
|
||||
return "replication(?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String longDisplay()
|
||||
{
|
||||
return "replication(factor=?,strategy=?,<option1>=?,...)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> multiLineDisplay()
|
||||
{
|
||||
return Arrays.asList(
|
||||
GroupedOptions.formatMultiLine("factor=?","The replication factor to use (default 1)"),
|
||||
GroupedOptions.formatMultiLine("strategy=?","The replication strategy to use (default SimpleStrategy)"),
|
||||
GroupedOptions.formatMultiLine("option=?","Arbitrary replication strategy options")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* For parsing a simple (sub)option for a command/major option
|
||||
*/
|
||||
class OptionSimple extends Option
|
||||
{
|
||||
|
||||
final String displayPrefix;
|
||||
final Pattern matchPrefix;
|
||||
final String defaultValue;
|
||||
final Pattern pattern;
|
||||
final String description;
|
||||
final boolean required;
|
||||
String value;
|
||||
|
||||
public OptionSimple(String prefix, String valuePattern, String defaultValue, String description, boolean required)
|
||||
{
|
||||
this.displayPrefix = prefix;
|
||||
this.matchPrefix = Pattern.compile(Pattern.quote(prefix), Pattern.CASE_INSENSITIVE);
|
||||
this.pattern = Pattern.compile(valuePattern, Pattern.CASE_INSENSITIVE);
|
||||
this.defaultValue = defaultValue;
|
||||
this.description = description;
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public OptionSimple(String displayPrefix, Pattern matchPrefix, Pattern valuePattern, String defaultValue, String description, boolean required)
|
||||
{
|
||||
this.displayPrefix = displayPrefix;
|
||||
this.matchPrefix = matchPrefix;
|
||||
this.pattern = valuePattern;
|
||||
this.defaultValue = defaultValue;
|
||||
this.description = description;
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public boolean setByUser()
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public boolean present()
|
||||
{
|
||||
return value != null || defaultValue != null;
|
||||
}
|
||||
|
||||
public String value()
|
||||
{
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
public boolean accept(String param)
|
||||
{
|
||||
if (matchPrefix.matcher(param).lookingAt())
|
||||
{
|
||||
if (value != null)
|
||||
throw new IllegalArgumentException("Suboption " + displayPrefix + " has been specified more than once");
|
||||
String v = param.substring(displayPrefix.length());
|
||||
if (!pattern.matcher(v).matches())
|
||||
throw new IllegalArgumentException("Invalid option " + param + "; must match pattern " + pattern);
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean happy()
|
||||
{
|
||||
return !required || value != null;
|
||||
}
|
||||
|
||||
public String shortDisplay()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!required)
|
||||
sb.append("[");
|
||||
sb.append(displayPrefix);
|
||||
if (displayPrefix.endsWith("="))
|
||||
sb.append("?");
|
||||
if (displayPrefix.endsWith("<"))
|
||||
sb.append("?");
|
||||
if (displayPrefix.endsWith(">"))
|
||||
sb.append("?");
|
||||
if (!required)
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String longDisplay()
|
||||
{
|
||||
if (description.equals("") && defaultValue == null && pattern.pattern().equals(""))
|
||||
return null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(displayPrefix);
|
||||
if (displayPrefix.endsWith("="))
|
||||
sb.append("?");
|
||||
if (displayPrefix.endsWith("<"))
|
||||
sb.append("?");
|
||||
if (displayPrefix.endsWith(">"))
|
||||
sb.append("?");
|
||||
if (defaultValue != null)
|
||||
{
|
||||
sb.append(" (default=");
|
||||
sb.append(defaultValue);
|
||||
sb.append(")");
|
||||
}
|
||||
return GroupedOptions.formatLong(sb.toString(), description);
|
||||
}
|
||||
|
||||
public List<String> multiLineDisplay()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return displayPrefix.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that)
|
||||
{
|
||||
return that instanceof OptionSimple && ((OptionSimple) that).displayPrefix.equals(this.displayPrefix);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.stress.generatedata.*;
|
||||
|
||||
/**
|
||||
* For parsing column options
|
||||
*/
|
||||
public class SettingsColumn implements Serializable
|
||||
{
|
||||
|
||||
public final int maxColumnsPerKey;
|
||||
public final List<ByteBuffer> names;
|
||||
public final String comparator;
|
||||
public final boolean useTimeUUIDComparator;
|
||||
public final int superColumns;
|
||||
public final boolean useSuperColumns;
|
||||
public final boolean variableColumnCount;
|
||||
|
||||
private final DistributionFactory sizeDistribution;
|
||||
private final DistributionFactory countDistribution;
|
||||
private final DataGenFactory dataGenFactory;
|
||||
|
||||
public SettingsColumn(GroupedOptions options)
|
||||
{
|
||||
this((Options) options,
|
||||
options instanceof NameOptions ? (NameOptions) options : null,
|
||||
options instanceof CountOptions ? (CountOptions) options : null
|
||||
);
|
||||
}
|
||||
|
||||
public SettingsColumn(Options options, NameOptions name, CountOptions count)
|
||||
{
|
||||
sizeDistribution = options.size.get();
|
||||
superColumns = Integer.parseInt(options.superColumns.value());
|
||||
dataGenFactory = options.generator.get();
|
||||
useSuperColumns = superColumns > 0;
|
||||
{
|
||||
comparator = options.comparator.value();
|
||||
AbstractType parsed = null;
|
||||
|
||||
try
|
||||
{
|
||||
parsed = TypeParser.parse(comparator);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.err.println(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
useTimeUUIDComparator = parsed instanceof TimeUUIDType;
|
||||
|
||||
if (!(parsed instanceof TimeUUIDType || parsed instanceof AsciiType || parsed instanceof UTF8Type))
|
||||
{
|
||||
System.err.println("Currently supported types are: TimeUUIDType, AsciiType, UTF8Type.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
if (name != null)
|
||||
{
|
||||
assert count == null;
|
||||
|
||||
AbstractType comparator;
|
||||
try
|
||||
{
|
||||
comparator = TypeParser.parse(this.comparator);
|
||||
} catch (Exception e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
final String[] names = name.name.value().split(",");
|
||||
this.names = new ArrayList<>(names.length);
|
||||
|
||||
for (String columnName : names)
|
||||
this.names.add(comparator.fromString(columnName));
|
||||
|
||||
final int nameCount = this.names.size();
|
||||
countDistribution = new DistributionFactory()
|
||||
{
|
||||
@Override
|
||||
public Distribution get()
|
||||
{
|
||||
return new DistributionFixed(nameCount);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
this.countDistribution = count.count.get();
|
||||
this.names = null;
|
||||
}
|
||||
maxColumnsPerKey = (int) countDistribution.get().maxValue();
|
||||
variableColumnCount = countDistribution.get().minValue() < maxColumnsPerKey;
|
||||
}
|
||||
|
||||
public RowGen newRowGen()
|
||||
{
|
||||
return new RowGenDistributedSize(dataGenFactory.get(), countDistribution.get(), sizeDistribution.get());
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
private static abstract class Options extends GroupedOptions
|
||||
{
|
||||
final OptionSimple superColumns = new OptionSimple("super=", "[0-9]+", "0", "Number of super columns to use (no super columns used if not specified)", false);
|
||||
final OptionSimple comparator = new OptionSimple("comparator=", "TimeUUIDType|AsciiType|UTF8Type", "AsciiType", "Column Comparator to use", false);
|
||||
final OptionDistribution size = new OptionDistribution("size=", "FIXED(34)");
|
||||
final OptionDataGen generator = new OptionDataGen("data=", "REPEAT(50)");
|
||||
}
|
||||
|
||||
private static final class NameOptions extends Options
|
||||
{
|
||||
final OptionSimple name = new OptionSimple("names=", ".*", null, "Column names", true);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(name, superColumns, comparator, size, generator);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CountOptions extends Options
|
||||
{
|
||||
final OptionDistribution count = new OptionDistribution("n=", "FIXED(5)");
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(count, superColumns, comparator, size, generator);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
static SettingsColumn get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-col");
|
||||
if (params == null)
|
||||
return new SettingsColumn(new CountOptions());
|
||||
|
||||
GroupedOptions options = GroupedOptions.select(params, new NameOptions(), new CountOptions());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -col options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsColumn(options);
|
||||
}
|
||||
|
||||
static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-col", new NameOptions(), new CountOptions());
|
||||
}
|
||||
|
||||
static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.thrift.ConsistencyLevel;
|
||||
|
||||
// Generic command settings - common to read/write/etc
|
||||
public class SettingsCommand implements Serializable
|
||||
{
|
||||
|
||||
public final Command type;
|
||||
public final long count;
|
||||
public final int tries;
|
||||
public final boolean ignoreErrors;
|
||||
public final ConsistencyLevel consistencyLevel;
|
||||
public final double targetUncertainty;
|
||||
public final int minimumUncertaintyMeasurements;
|
||||
public final int maximumUncertaintyMeasurements;
|
||||
|
||||
public SettingsCommand(Command type, GroupedOptions options)
|
||||
{
|
||||
this(type, (Options) options,
|
||||
options instanceof Count ? (Count) options : null,
|
||||
options instanceof Uncertainty ? (Uncertainty) options : null
|
||||
);
|
||||
}
|
||||
|
||||
public SettingsCommand(Command type, Options options, Count count, Uncertainty uncertainty)
|
||||
{
|
||||
this.type = type;
|
||||
this.tries = Math.max(1, Integer.parseInt(options.retries.value()) + 1);
|
||||
this.ignoreErrors = options.ignoreErrors.setByUser();
|
||||
this.consistencyLevel = ConsistencyLevel.valueOf(options.consistencyLevel.value().toUpperCase());
|
||||
if (count != null)
|
||||
{
|
||||
this.count = Long.parseLong(count.count.value());
|
||||
this.targetUncertainty = -1;
|
||||
this.minimumUncertaintyMeasurements = -1;
|
||||
this.maximumUncertaintyMeasurements = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.count = -1;
|
||||
this.targetUncertainty = Double.parseDouble(uncertainty.uncertainty.value());
|
||||
this.minimumUncertaintyMeasurements = Integer.parseInt(uncertainty.minMeasurements.value());
|
||||
this.maximumUncertaintyMeasurements = Integer.parseInt(uncertainty.maxMeasurements.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
static abstract class Options extends GroupedOptions
|
||||
{
|
||||
final OptionSimple retries = new OptionSimple("tries=", "[0-9]+", "9", "Number of tries to perform for each operation before failing", false);
|
||||
final OptionSimple ignoreErrors = new OptionSimple("ignore_errors", "", null, "Do not print/log errors", false);
|
||||
final OptionSimple consistencyLevel = new OptionSimple("cl=", "ONE|QUORUM|LOCAL_QUORUM|EACH_QUORUM|ALL|ANY", "ONE", "Consistency level to use", false);
|
||||
}
|
||||
|
||||
static class Count extends Options
|
||||
{
|
||||
|
||||
final OptionSimple count = new OptionSimple("n=", "[0-9]+", null, "Number of operations to perform", true);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(count, retries, ignoreErrors, consistencyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
static class Uncertainty extends Options
|
||||
{
|
||||
|
||||
final OptionSimple uncertainty = new OptionSimple("err<", "0\\.[0-9]+", "0.02", "Run until the standard error of the mean is below this fraction", false);
|
||||
final OptionSimple minMeasurements = new OptionSimple("n>", "[0-9]+", "30", "Run at least this many iterations before accepting uncertainty convergence", false);
|
||||
final OptionSimple maxMeasurements = new OptionSimple("n<", "[0-9]+", "200", "Run at most this many iterations before accepting uncertainty convergence", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(uncertainty, minMeasurements, maxMeasurements, retries, ignoreErrors, consistencyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
static SettingsCommand get(Map<String, String[]> clArgs)
|
||||
{
|
||||
for (Command cmd : Command.values())
|
||||
{
|
||||
if (cmd.category == null)
|
||||
continue;
|
||||
final String[] params = clArgs.remove(cmd.toString().toLowerCase());
|
||||
if (params != null)
|
||||
{
|
||||
switch (cmd.category)
|
||||
{
|
||||
case BASIC:
|
||||
return build(cmd, params);
|
||||
case MULTI:
|
||||
return SettingsCommandMulti.build(cmd, params);
|
||||
case MIXED:
|
||||
return SettingsCommandMixed.build(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static SettingsCommand build(Command type, String[] params)
|
||||
{
|
||||
GroupedOptions options = GroupedOptions.select(params, new Count(), new Uncertainty());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp(type);
|
||||
System.out.println("Invalid " + type + " options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsCommand(type, options);
|
||||
}
|
||||
|
||||
static void printHelp(Command type)
|
||||
{
|
||||
printHelp(type.toString().toLowerCase());
|
||||
}
|
||||
|
||||
static void printHelp(String type)
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, type.toString().toLowerCase(), new Uncertainty(), new Count());
|
||||
}
|
||||
|
||||
static Runnable helpPrinter(final String type)
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp(type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static Runnable helpPrinter(final Command type)
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp(type);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.stress.generatedata.Distribution;
|
||||
import org.apache.cassandra.stress.generatedata.DistributionFactory;
|
||||
import org.apache.commons.math3.distribution.EnumeratedDistribution;
|
||||
import org.apache.commons.math3.util.Pair;
|
||||
|
||||
// Settings unique to the mixed command type
|
||||
public class SettingsCommandMixed extends SettingsCommandMulti
|
||||
{
|
||||
|
||||
// Ratios for selecting commands - index for each Command, NaN indicates the command is not requested
|
||||
private final List<Pair<Command, Double>> ratios;
|
||||
private final DistributionFactory clustering;
|
||||
|
||||
public SettingsCommandMixed(Options options)
|
||||
{
|
||||
super(Command.MIXED, options.parent);
|
||||
|
||||
OptionSimple[] ratiosIn = options.probabilities.ratios;
|
||||
List<Pair<Command, Double>> ratiosOut = new ArrayList<>();
|
||||
for (int i = 0 ; i < ratiosIn.length ; i++)
|
||||
{
|
||||
if (ratiosIn[i] != null && ratiosIn[i].present())
|
||||
{
|
||||
double d = Double.parseDouble(ratiosIn[i].value());
|
||||
if (d > 0)
|
||||
ratiosOut.add(new Pair<>(Command.values()[i], d));
|
||||
}
|
||||
}
|
||||
|
||||
ratios = ratiosOut;
|
||||
clustering = options.clustering.get();
|
||||
|
||||
if (ratios.size() == 0)
|
||||
throw new IllegalArgumentException("Must specify at least one command with a non-zero ratio");
|
||||
}
|
||||
|
||||
public List<Command> getCommands()
|
||||
{
|
||||
final List<Command> r = new ArrayList<>();
|
||||
for (Pair<Command, Double> p : ratios)
|
||||
r.add(p.getFirst());
|
||||
return r;
|
||||
}
|
||||
|
||||
public CommandSelector selector()
|
||||
{
|
||||
return new CommandSelector(ratios, clustering.get());
|
||||
}
|
||||
|
||||
// Class for randomly selecting the next command type
|
||||
|
||||
public static final class CommandSelector
|
||||
{
|
||||
|
||||
final EnumeratedDistribution<Command> selector;
|
||||
final Distribution count;
|
||||
private Command cur;
|
||||
private long remaining;
|
||||
|
||||
public CommandSelector(List<Pair<Command, Double>> ratios, Distribution count)
|
||||
{
|
||||
selector = new EnumeratedDistribution<>(ratios);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public Command next()
|
||||
{
|
||||
while (remaining == 0)
|
||||
{
|
||||
remaining = count.next();
|
||||
cur = selector.sample();
|
||||
}
|
||||
remaining--;
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
static final class Probabilities extends OptionMulti
|
||||
{
|
||||
// entry for each in Command.values()
|
||||
final OptionSimple[] ratios;
|
||||
final List<OptionSimple> grouping;
|
||||
|
||||
public Probabilities()
|
||||
{
|
||||
super("ratio", "Specify the ratios for operations to perform; e.g. (reads=2,writes=1) will perform 2 reads for each write");
|
||||
OptionSimple[] ratios = new OptionSimple[Command.values().length];
|
||||
List<OptionSimple> grouping = new ArrayList<>();
|
||||
for (Command command : Command.values())
|
||||
{
|
||||
if (command.category == null)
|
||||
continue;
|
||||
String defaultValue;
|
||||
switch (command)
|
||||
{
|
||||
case MIXED:
|
||||
continue;
|
||||
case READ:
|
||||
case WRITE:
|
||||
defaultValue = "1";
|
||||
break;
|
||||
default:
|
||||
defaultValue = null;
|
||||
}
|
||||
OptionSimple ratio = new OptionSimple(command.toString().toLowerCase() +
|
||||
"=", "[0-9]+(\\.[0-9]+)?", defaultValue, "Performs this many " + command + " operations out of total", false);
|
||||
ratios[command.ordinal()] = ratio;
|
||||
grouping.add(ratio);
|
||||
}
|
||||
this.grouping = grouping;
|
||||
this.ratios = ratios;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return grouping;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Options extends GroupedOptions
|
||||
{
|
||||
final SettingsCommandMulti.Options parent;
|
||||
protected Options(SettingsCommandMulti.Options parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
final OptionDistribution clustering = new OptionDistribution("clustering=", "GAUSSIAN(1..10)");
|
||||
final Probabilities probabilities = new Probabilities();
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
final List<Option> options = new ArrayList<>();
|
||||
options.add(clustering);
|
||||
options.add(probabilities);
|
||||
options.addAll(parent.options());
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CLI utility methods
|
||||
|
||||
public static SettingsCommandMixed build(String[] params)
|
||||
{
|
||||
GroupedOptions options = GroupedOptions.select(params,
|
||||
new Options(new SettingsCommandMulti.Options(new Uncertainty())),
|
||||
new Options(new SettingsCommandMulti.Options(new Count())));
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid MIXED options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsCommandMixed((Options) options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "mixed",
|
||||
new Options(new SettingsCommandMulti.Options(new Uncertainty())),
|
||||
new Options(new SettingsCommandMulti.Options(new Count())));
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// Settings common to commands that operate over multiple keys at once
|
||||
public class SettingsCommandMulti extends SettingsCommand
|
||||
{
|
||||
|
||||
public final int keysAtOnce;
|
||||
|
||||
public SettingsCommandMulti(Command type, Options options)
|
||||
{
|
||||
super(type, options.parent);
|
||||
this.keysAtOnce = Integer.parseInt(options.maxKeys.value());
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
static final class Options extends GroupedOptions
|
||||
{
|
||||
final GroupedOptions parent;
|
||||
Options(GroupedOptions parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
final OptionSimple maxKeys = new OptionSimple("at-once=", "[0-9]+", "1000", "Number of keys per operation", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
final List<Option> options = new ArrayList<>();
|
||||
options.add(maxKeys);
|
||||
options.addAll(parent.options());
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsCommand build(Command type, String[] params)
|
||||
{
|
||||
GroupedOptions options = GroupedOptions.select(params, new Options(new Uncertainty()), new Options(new Count()));
|
||||
if (options == null)
|
||||
{
|
||||
printHelp(type);
|
||||
System.out.println("Invalid " + type + " options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsCommandMulti(type, (Options) options);
|
||||
}
|
||||
|
||||
public static void printHelp(Command type)
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, type.toString().toLowerCase(), new Options(new Uncertainty()), new Options(new Count()));
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter(final Command type)
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp(type);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.stress.generatedata.DataGenHexFromDistribution;
|
||||
import org.apache.cassandra.stress.generatedata.DataGenHexFromOpIndex;
|
||||
import org.apache.cassandra.stress.generatedata.DistributionFactory;
|
||||
import org.apache.cassandra.stress.generatedata.KeyGen;
|
||||
|
||||
// Settings for key generation
|
||||
public class SettingsKey implements Serializable
|
||||
{
|
||||
|
||||
private final int keySize;
|
||||
private final DistributionFactory distribution;
|
||||
private final long[] range;
|
||||
|
||||
public SettingsKey(DistributionOptions options)
|
||||
{
|
||||
this.keySize = Integer.parseInt(options.size.value());
|
||||
this.distribution = options.dist.get();
|
||||
this.range = null;
|
||||
}
|
||||
|
||||
public SettingsKey(PopulateOptions options)
|
||||
{
|
||||
this.keySize = Integer.parseInt(options.size.value());
|
||||
this.distribution = null;
|
||||
String[] bounds = options.populate.value().split("\\.\\.+");
|
||||
this.range = new long[] { Long.parseLong(bounds[0]), Long.parseLong(bounds[1]) };
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
private static final class DistributionOptions extends GroupedOptions
|
||||
{
|
||||
final OptionDistribution dist;
|
||||
final OptionSimple size = new OptionSimple("size=", "[0-9]+", "10", "Key size in bytes", false);
|
||||
|
||||
public DistributionOptions(String defaultLimit)
|
||||
{
|
||||
dist = new OptionDistribution("dist=", "GAUSSIAN(1.." + defaultLimit + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(dist, size);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PopulateOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple populate;
|
||||
final OptionSimple size = new OptionSimple("size=", "[0-9]+", "10", "Key size in bytes", false);
|
||||
|
||||
public PopulateOptions(String defaultLimit)
|
||||
{
|
||||
populate = new OptionSimple("populate=", "[0-9]+\\.\\.+[0-9]+",
|
||||
"1.." + defaultLimit,
|
||||
"Populate all keys in sequence", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(populate, size);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyGen newKeyGen()
|
||||
{
|
||||
if (range != null)
|
||||
return new KeyGen(new DataGenHexFromOpIndex(range[0], range[1]), keySize);
|
||||
return new KeyGen(new DataGenHexFromDistribution(distribution.get()), keySize);
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsKey get(Map<String, String[]> clArgs, SettingsCommand command)
|
||||
{
|
||||
// set default size to number of commands requested, unless set to err convergence, then use 1M
|
||||
String defaultLimit = command.count <= 0 ? "1000000" : Long.toString(command.count);
|
||||
|
||||
String[] params = clArgs.remove("-key");
|
||||
if (params == null)
|
||||
{
|
||||
// return defaults:
|
||||
switch(command.type)
|
||||
{
|
||||
case WRITE:
|
||||
case COUNTERWRITE:
|
||||
return new SettingsKey(new PopulateOptions(defaultLimit));
|
||||
default:
|
||||
return new SettingsKey(new DistributionOptions(defaultLimit));
|
||||
}
|
||||
}
|
||||
GroupedOptions options = GroupedOptions.select(params, new PopulateOptions(defaultLimit), new DistributionOptions(defaultLimit));
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -key options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return options instanceof PopulateOptions ?
|
||||
new SettingsKey((PopulateOptions) options) :
|
||||
new SettingsKey((DistributionOptions) options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-key", new PopulateOptions("N"), new DistributionOptions("N"));
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingsLog implements Serializable
|
||||
{
|
||||
|
||||
public final boolean noSummary;
|
||||
public final File file;
|
||||
public final int intervalMillis;
|
||||
|
||||
public SettingsLog(Options options)
|
||||
{
|
||||
noSummary = options.noSummmary.setByUser();
|
||||
|
||||
if (options.outputFile.setByUser())
|
||||
file = new File(options.outputFile.value());
|
||||
else
|
||||
file = null;
|
||||
|
||||
String interval = options.interval.value();
|
||||
if (interval.endsWith("ms"))
|
||||
intervalMillis = Integer.parseInt(interval.substring(0, interval.length() - 2));
|
||||
else if (interval.endsWith("s"))
|
||||
intervalMillis = 1000 * Integer.parseInt(interval.substring(0, interval.length() - 1));
|
||||
else
|
||||
intervalMillis = 1000 * Integer.parseInt(interval);
|
||||
if (intervalMillis <= 0)
|
||||
throw new IllegalArgumentException("Log interval must be greater than zero");
|
||||
}
|
||||
|
||||
public PrintStream getOutput() throws FileNotFoundException
|
||||
{
|
||||
return file == null ? new PrintStream(System.out) : new PrintStream(file);
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
public static final class Options extends GroupedOptions
|
||||
{
|
||||
final OptionSimple noSummmary = new OptionSimple("no-summary", "", null, "Disable printing of aggregate statistics at the end of a test", false);
|
||||
final OptionSimple outputFile = new OptionSimple("file=", ".*", null, "Log to a file", false);
|
||||
final OptionSimple interval = new OptionSimple("interval=", "[0-9]+(ms|s|)", "1s", "Log progress every <value> seconds or milliseconds", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(noSummmary, outputFile, interval);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsLog get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-log");
|
||||
if (params == null)
|
||||
return new SettingsLog(new Options());
|
||||
|
||||
GroupedOptions options = GroupedOptions.select(params, new Options());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -log options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsLog((Options) options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-log", new Options());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.stress.generatedata.Distribution;
|
||||
|
||||
public class SettingsMisc implements Serializable
|
||||
{
|
||||
|
||||
static boolean maybeDoSpecial(Map<String, String[]> clArgs)
|
||||
{
|
||||
if (maybePrintHelp(clArgs))
|
||||
return true;
|
||||
if (maybePrintDistribution(clArgs))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static final class PrintDistribution extends GroupedOptions
|
||||
{
|
||||
final OptionDistribution dist = new OptionDistribution("dist=", null);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(dist);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean maybePrintDistribution(Map<String, String[]> clArgs)
|
||||
{
|
||||
final String[] args = clArgs.get("print");
|
||||
if (args == null)
|
||||
return false;
|
||||
final PrintDistribution dist = new PrintDistribution();
|
||||
if (null == GroupedOptions.select(args, dist))
|
||||
{
|
||||
printHelpPrinter().run();
|
||||
System.out.println("Invalid print options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
printDistribution(dist.dist.get().get());
|
||||
return true;
|
||||
}
|
||||
|
||||
static void printDistribution(Distribution dist)
|
||||
{
|
||||
PrintStream out = System.out;
|
||||
out.println("% of samples Range % of total");
|
||||
String format = "%-16.1f%-12d%12.1f";
|
||||
double rangemax = dist.inverseCumProb(1d) / 100d;
|
||||
for (double d : new double[] { 0.1d, 0.2d, 0.3d, 0.4d, 0.5d, 0.6d, 0.7d, 0.8d, 0.9d, 0.95d, 0.99d, 1d })
|
||||
{
|
||||
double sampleperc = d * 100;
|
||||
long max = dist.inverseCumProb(d);
|
||||
double rangeperc = max/ rangemax;
|
||||
out.println(String.format(format, sampleperc, max, rangeperc));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean maybePrintHelp(Map<String, String[]> clArgs)
|
||||
{
|
||||
if (!clArgs.containsKey("-?") && !clArgs.containsKey("help"))
|
||||
return false;
|
||||
String[] params = clArgs.remove("-?");
|
||||
if (params == null)
|
||||
params = clArgs.remove("help");
|
||||
if (params.length == 0)
|
||||
{
|
||||
if (!clArgs.isEmpty())
|
||||
{
|
||||
if (clArgs.size() == 1)
|
||||
{
|
||||
String p = clArgs.keySet().iterator().next();
|
||||
if (clArgs.get(p).length == 0)
|
||||
params = new String[] {p};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (params.length == 1)
|
||||
{
|
||||
printHelp(params[0]);
|
||||
return true;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid command/option provided to help");
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
System.out.println("Usage: ./bin/cassandra-stress <command> [options]");
|
||||
System.out.println();
|
||||
System.out.println("---Commands---");
|
||||
for (Command cmd : Command.values())
|
||||
{
|
||||
System.out.println(String.format("%-20s : %s", cmd.toString().toLowerCase(), cmd.description));
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println("---Options---");
|
||||
for (CliOption cmd : CliOption.values())
|
||||
{
|
||||
System.out.println(String.format("-%-20s : %s", cmd.toString().toLowerCase(), cmd.description));
|
||||
}
|
||||
}
|
||||
|
||||
public static void printHelp(String command)
|
||||
{
|
||||
Command cmd = Command.get(command);
|
||||
if (cmd != null)
|
||||
{
|
||||
cmd.printHelp();
|
||||
return;
|
||||
}
|
||||
CliOption opt = CliOption.get(command);
|
||||
if (opt != null)
|
||||
{
|
||||
opt.printHelp();
|
||||
return;
|
||||
}
|
||||
printHelp();
|
||||
throw new IllegalArgumentException("Invalid command or option provided to command help");
|
||||
}
|
||||
|
||||
public static Runnable helpHelpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
System.out.println("Usage: ./bin/cassandra-stress help <command|option>");
|
||||
System.out.println("Commands:");
|
||||
for (Command cmd : Command.values())
|
||||
System.out.println(" " + cmd.toString().toLowerCase() + (cmd.extraName != null ? ", " + cmd.extraName : ""));
|
||||
System.out.println("Options:");
|
||||
for (CliOption op : CliOption.values())
|
||||
System.out.println(" -" + op.toString().toLowerCase() + (op.extraName != null ? ", " + op.extraName : ""));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable printHelpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "print", new GroupedOptions()
|
||||
{
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(new OptionDistribution("dist=", null));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable sendToDaemonHelpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
System.out.println("Usage: -sendToDaemon <host>");
|
||||
System.out.println();
|
||||
System.out.println("Specify a host running the stress server to send this stress command to");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static String getSendToDaemon(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-send-to");
|
||||
if (params == null)
|
||||
params = clArgs.remove("-sendto");
|
||||
if (params == null)
|
||||
return null;
|
||||
if (params.length != 1)
|
||||
{
|
||||
sendToDaemonHelpPrinter().run();
|
||||
System.out.println("Invalid -send-to specifier: " + Arrays.toString(params));
|
||||
System.exit(1);
|
||||
}
|
||||
return params[0];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.datastax.driver.core.ProtocolOptions;
|
||||
|
||||
public class SettingsMode implements Serializable
|
||||
{
|
||||
|
||||
public final ConnectionAPI api;
|
||||
public final ConnectionStyle style;
|
||||
public final CqlVersion cqlVersion;
|
||||
private final String compression;
|
||||
|
||||
public SettingsMode(GroupedOptions options)
|
||||
{
|
||||
if (options instanceof Cql3Options)
|
||||
{
|
||||
cqlVersion = CqlVersion.CQL3;
|
||||
Cql3Options opts = (Cql3Options) options;
|
||||
api = opts.useNative.setByUser() ? ConnectionAPI.JAVA_DRIVER_NATIVE : ConnectionAPI.THRIFT;
|
||||
style = opts.usePrepared.setByUser() ? ConnectionStyle.CQL_PREPARED : ConnectionStyle.CQL;
|
||||
compression = ProtocolOptions.Compression.valueOf(opts.useCompression.value().toUpperCase()).name();
|
||||
}
|
||||
else if (options instanceof Cql3SimpleNativeOptions)
|
||||
{
|
||||
cqlVersion = CqlVersion.CQL3;
|
||||
Cql3SimpleNativeOptions opts = (Cql3SimpleNativeOptions) options;
|
||||
api = ConnectionAPI.SIMPLE_NATIVE;
|
||||
style = opts.usePrepared.setByUser() ? ConnectionStyle.CQL_PREPARED : ConnectionStyle.CQL;
|
||||
compression = ProtocolOptions.Compression.NONE.name();
|
||||
}
|
||||
else if (options instanceof Cql2Options)
|
||||
{
|
||||
cqlVersion = CqlVersion.CQL2;
|
||||
api = ConnectionAPI.THRIFT;
|
||||
Cql2Options opts = (Cql2Options) options;
|
||||
style = opts.usePrepared.setByUser() ? ConnectionStyle.CQL_PREPARED : ConnectionStyle.CQL;
|
||||
compression = ProtocolOptions.Compression.NONE.name();
|
||||
}
|
||||
else if (options instanceof ThriftOptions)
|
||||
{
|
||||
ThriftOptions opts = (ThriftOptions) options;
|
||||
cqlVersion = CqlVersion.NOCQL;
|
||||
api = opts.smart.setByUser() ? ConnectionAPI.THRIFT_SMART : ConnectionAPI.THRIFT;
|
||||
style = ConnectionStyle.THRIFT;
|
||||
compression = ProtocolOptions.Compression.NONE.name();
|
||||
}
|
||||
else
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public ProtocolOptions.Compression compression()
|
||||
{
|
||||
return ProtocolOptions.Compression.valueOf(compression);
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
private static final class Cql3Options extends GroupedOptions
|
||||
{
|
||||
final OptionSimple api = new OptionSimple("cql3", "", null, "", true);
|
||||
final OptionSimple useNative = new OptionSimple("native", "", null, "", false);
|
||||
final OptionSimple usePrepared = new OptionSimple("prepared", "", null, "", false);
|
||||
final OptionSimple useCompression = new OptionSimple("compression=", "none|lz4|snappy", "none", "", false);
|
||||
final OptionSimple port = new OptionSimple("port=", "[0-9]+", "9046", "", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(useNative, usePrepared, api, useCompression, port);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Cql3SimpleNativeOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple api = new OptionSimple("cql3", "", null, "", true);
|
||||
final OptionSimple useSimpleNative = new OptionSimple("simplenative", "", null, "", true);
|
||||
final OptionSimple usePrepared = new OptionSimple("prepared", "", null, "", false);
|
||||
final OptionSimple port = new OptionSimple("port=", "[0-9]+", "9046", "", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(useSimpleNative, usePrepared, api, port);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Cql2Options extends GroupedOptions
|
||||
{
|
||||
final OptionSimple api = new OptionSimple("cql2", "", null, "", true);
|
||||
final OptionSimple usePrepared = new OptionSimple("prepared", "", null, "", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(usePrepared, api);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ThriftOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple api = new OptionSimple("thrift", "", null, "", true);
|
||||
final OptionSimple smart = new OptionSimple("smart", "", null, "", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(api, smart);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsMode get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-mode");
|
||||
if (params == null)
|
||||
{
|
||||
ThriftOptions opts = new ThriftOptions();
|
||||
opts.smart.accept("smart");
|
||||
return new SettingsMode(opts);
|
||||
}
|
||||
|
||||
GroupedOptions options = GroupedOptions.select(params, new ThriftOptions(), new Cql2Options(), new Cql3Options(), new Cql3SimpleNativeOptions());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -mode options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsMode(options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-mode", new ThriftOptions(), new Cql2Options(), new Cql3Options(), new Cql3SimpleNativeOptions());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingsNode implements Serializable
|
||||
{
|
||||
|
||||
public final List<String> nodes;
|
||||
|
||||
public SettingsNode(Options options)
|
||||
{
|
||||
if (options.file.setByUser())
|
||||
{
|
||||
try
|
||||
{
|
||||
String node;
|
||||
List<String> tmpNodes = new ArrayList<String>();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(options.file.value())));
|
||||
try
|
||||
{
|
||||
while ((node = in.readLine()) != null)
|
||||
{
|
||||
if (node.length() > 0)
|
||||
tmpNodes.add(node);
|
||||
}
|
||||
nodes = Arrays.asList(tmpNodes.toArray(new String[tmpNodes.size()]));
|
||||
}
|
||||
finally
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
nodes = Arrays.asList(options.list.value().split(","));
|
||||
}
|
||||
|
||||
public String randomNode()
|
||||
{
|
||||
int index = (int) (Math.random() * nodes.size());
|
||||
if (index >= nodes.size())
|
||||
index = nodes.size() - 1;
|
||||
return nodes.get(index);
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
public static final class Options extends GroupedOptions
|
||||
{
|
||||
final OptionSimple file = new OptionSimple("file=", ".*", null, "Node file (one per line)", false);
|
||||
final OptionSimple list = new OptionSimple("", "[^=,]+(,[^=,]+)*", "localhost", "comma delimited list of hosts", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(file, list);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsNode get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-node");
|
||||
if (params == null)
|
||||
return new SettingsNode(new Options());
|
||||
|
||||
GroupedOptions options = GroupedOptions.select(params, new Options());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -node options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsNode((Options) options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-node", new Options());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingsPort implements Serializable
|
||||
{
|
||||
|
||||
public final int nativePort;
|
||||
public final int thriftPort;
|
||||
|
||||
public SettingsPort(PortOptions options)
|
||||
{
|
||||
nativePort = Integer.parseInt(options.nativePort.value());
|
||||
thriftPort = Integer.parseInt(options.thriftPort.value());
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
private static final class PortOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple nativePort = new OptionSimple("native=", "[0-9]+", "9042", "Use this port for the Cassandra native protocol", false);
|
||||
final OptionSimple thriftPort = new OptionSimple("thrift=", "[0-9]+", "9160", "Use this port for the thrift protocol", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(nativePort, thriftPort);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsPort get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-port");
|
||||
if (params == null)
|
||||
{
|
||||
return new SettingsPort(new PortOptions());
|
||||
}
|
||||
PortOptions options = GroupedOptions.select(params, new PortOptions());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -port options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsPort(options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-port", new PortOptions());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingsRate implements Serializable
|
||||
{
|
||||
|
||||
public final boolean auto;
|
||||
public final int minAutoThreads;
|
||||
public final int maxAutoThreads;
|
||||
public final int threadCount;
|
||||
public final int opRateTargetPerSecond;
|
||||
|
||||
public SettingsRate(ThreadOptions options)
|
||||
{
|
||||
auto = false;
|
||||
threadCount = Integer.parseInt(options.threads.value());
|
||||
String rateOpt = options.rate.value();
|
||||
opRateTargetPerSecond = Integer.parseInt(rateOpt.substring(0, rateOpt.length() - 2));
|
||||
minAutoThreads = -1;
|
||||
maxAutoThreads = -1;
|
||||
}
|
||||
|
||||
public SettingsRate(AutoOptions auto)
|
||||
{
|
||||
this.auto = true;
|
||||
this.minAutoThreads = Integer.parseInt(auto.minThreads.value());
|
||||
this.maxAutoThreads = Integer.parseInt(auto.maxThreads.value());
|
||||
this.threadCount = -1;
|
||||
this.opRateTargetPerSecond = 0;
|
||||
}
|
||||
|
||||
|
||||
// Option Declarations
|
||||
|
||||
private static final class AutoOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple auto = new OptionSimple("auto", "", null, "test with increasing number of threadCount until performance plateaus", false);
|
||||
final OptionSimple minThreads = new OptionSimple("threads>=", "[0-9]+", "4", "run at least this many clients concurrently", false);
|
||||
final OptionSimple maxThreads = new OptionSimple("threads<=", "[0-9]+", "1000", "run at most this many clients concurrently", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(auto, minThreads, maxThreads);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ThreadOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple threads = new OptionSimple("threads=", "[0-9]+", null, "run this many clients concurrently", true);
|
||||
final OptionSimple rate = new OptionSimple("limit=", "[0-9]+/s", "0/s", "limit operations per second across all clients", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(threads, rate);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsRate get(Map<String, String[]> clArgs, SettingsCommand command)
|
||||
{
|
||||
String[] params = clArgs.remove("-rate");
|
||||
if (params == null)
|
||||
{
|
||||
switch (command.type)
|
||||
{
|
||||
case WRITE:
|
||||
case COUNTERWRITE:
|
||||
if (command.count > 0)
|
||||
{
|
||||
ThreadOptions options = new ThreadOptions();
|
||||
options.accept("threads=50");
|
||||
return new SettingsRate(options);
|
||||
}
|
||||
}
|
||||
return new SettingsRate(new AutoOptions());
|
||||
}
|
||||
GroupedOptions options = GroupedOptions.select(params, new AutoOptions(), new ThreadOptions());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -rate options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
if (options instanceof AutoOptions)
|
||||
return new SettingsRate((AutoOptions) options);
|
||||
else if (options instanceof ThreadOptions)
|
||||
return new SettingsRate((ThreadOptions) options);
|
||||
else
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-rate", new ThreadOptions(), new AutoOptions());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class SettingsSchema implements Serializable
|
||||
{
|
||||
|
||||
public static final String DEFAULT_COMPARATOR = "AsciiType";
|
||||
public static final String DEFAULT_VALIDATOR = "BytesType";
|
||||
|
||||
private final String replicationStrategy;
|
||||
private final Map<String, String> replicationStrategyOptions;
|
||||
|
||||
private final IndexType indexType;
|
||||
private final boolean replicateOnWrite;
|
||||
private final String compression;
|
||||
private final String compactionStrategy;
|
||||
public final String keyspace;
|
||||
public final String columnFamily;
|
||||
|
||||
public SettingsSchema(Options options)
|
||||
{
|
||||
replicateOnWrite = !options.noReplicateOnWrite.setByUser();
|
||||
replicationStrategy = options.replication.getStrategy();
|
||||
replicationStrategyOptions = options.replication.getOptions();
|
||||
if (options.index.setByUser())
|
||||
indexType = IndexType.valueOf(options.index.value().toUpperCase());
|
||||
else
|
||||
indexType = null;
|
||||
compression = options.compression.value();
|
||||
compactionStrategy = options.compactionStrategy.value();
|
||||
if (compactionStrategy != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
CFMetaData.createCompactionStrategy(compactionStrategy);
|
||||
} catch (ConfigurationException e)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid compaction strategy: " + compactionStrategy);
|
||||
}
|
||||
}
|
||||
keyspace = options.keyspace.value();
|
||||
columnFamily = options.columnFamily.value();
|
||||
}
|
||||
|
||||
private void createKeyspacesCql3(StressSettings settings)
|
||||
{
|
||||
// settings.getJavaDriverClient().execute("create table Standard1")
|
||||
}
|
||||
|
||||
public void createKeySpaces(StressSettings settings)
|
||||
{
|
||||
createKeySpacesThrift(settings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create Keyspace with Standard and Super/Counter column families
|
||||
*/
|
||||
public void createKeySpacesThrift(StressSettings settings)
|
||||
{
|
||||
KsDef ksdef = new KsDef();
|
||||
|
||||
// column family for standard columns
|
||||
CfDef standardCfDef = new CfDef(keyspace, columnFamily);
|
||||
Map<String, String> compressionOptions = new HashMap<String, String>();
|
||||
if (compression != null)
|
||||
compressionOptions.put("sstable_compression", compression);
|
||||
|
||||
String comparator = settings.columns.comparator;
|
||||
standardCfDef.setComparator_type(comparator)
|
||||
.setDefault_validation_class(DEFAULT_VALIDATOR)
|
||||
.setCompression_options(compressionOptions);
|
||||
|
||||
if (!settings.columns.useTimeUUIDComparator)
|
||||
{
|
||||
for (int i = 0; i < settings.columns.maxColumnsPerKey; 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, "Super1")
|
||||
.setColumn_type("Super");
|
||||
superCfDef.setComparator_type(DEFAULT_COMPARATOR)
|
||||
.setSubcomparator_type(comparator)
|
||||
.setDefault_validation_class(DEFAULT_VALIDATOR)
|
||||
.setCompression_options(compressionOptions);
|
||||
|
||||
// column family for standard counters
|
||||
CfDef counterCfDef = new CfDef(keyspace, "Counter1")
|
||||
.setComparator_type(comparator)
|
||||
.setDefault_validation_class("CounterColumnType")
|
||||
.setReplicate_on_write(replicateOnWrite)
|
||||
.setCompression_options(compressionOptions);
|
||||
|
||||
// column family with counter super columns
|
||||
CfDef counterSuperCfDef = new CfDef(keyspace, "SuperCounter1")
|
||||
.setComparator_type(comparator)
|
||||
.setDefault_validation_class("CounterColumnType")
|
||||
.setReplicate_on_write(replicateOnWrite)
|
||||
.setColumn_type("Super")
|
||||
.setCompression_options(compressionOptions);
|
||||
|
||||
ksdef.setName(keyspace);
|
||||
ksdef.setStrategy_class(replicationStrategy);
|
||||
|
||||
if (!replicationStrategyOptions.isEmpty())
|
||||
{
|
||||
ksdef.setStrategy_options(replicationStrategyOptions);
|
||||
}
|
||||
|
||||
if (compactionStrategy != null)
|
||||
{
|
||||
standardCfDef.setCompaction_strategy(compactionStrategy);
|
||||
superCfDef.setCompaction_strategy(compactionStrategy);
|
||||
counterCfDef.setCompaction_strategy(compactionStrategy);
|
||||
counterSuperCfDef.setCompaction_strategy(compactionStrategy);
|
||||
}
|
||||
|
||||
ksdef.setCf_defs(new ArrayList<CfDef>(Arrays.asList(standardCfDef, superCfDef, counterCfDef, counterSuperCfDef)));
|
||||
|
||||
Cassandra.Client client = settings.getRawThriftClient(false);
|
||||
|
||||
try
|
||||
{
|
||||
client.system_add_keyspace(ksdef);
|
||||
|
||||
/* CQL3 counter cf */
|
||||
client.set_cql_version("3.0.0"); // just to create counter cf for cql3
|
||||
|
||||
client.set_keyspace(keyspace);
|
||||
client.execute_cql3_query(createCounterCFStatementForCQL3(settings), Compression.NONE, ConsistencyLevel.ONE);
|
||||
|
||||
if (settings.mode.cqlVersion.isCql())
|
||||
client.set_cql_version(settings.mode.cqlVersion.connectVersion);
|
||||
/* end */
|
||||
|
||||
System.out.println(String.format("Created keyspaces. Sleeping %ss for propagation.", settings.node.nodes.size()));
|
||||
Thread.sleep(settings.node.nodes.size() * 1000); // seconds
|
||||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
System.err.println("Unable to create stress keyspace: " + e.getWhy());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.err.println("!!!! " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBuffer createCounterCFStatementForCQL3(StressSettings options)
|
||||
{
|
||||
StringBuilder counter3 = new StringBuilder("CREATE TABLE \"Counter3\" (KEY blob PRIMARY KEY, ");
|
||||
|
||||
for (int i = 0; i < options.columns.maxColumnsPerKey; i++)
|
||||
{
|
||||
counter3.append("c").append(i).append(" counter");
|
||||
if (i != options.columns.maxColumnsPerKey - 1)
|
||||
counter3.append(", ");
|
||||
}
|
||||
counter3.append(");");
|
||||
|
||||
return ByteBufferUtil.bytes(counter3.toString());
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
private static final class Options extends GroupedOptions
|
||||
{
|
||||
final OptionReplication replication = new OptionReplication();
|
||||
final OptionSimple index = new OptionSimple("index=", "KEYS|CUSTOM|COMPOSITES", null, "Type of index to create on needed column families (KEYS)", false);
|
||||
final OptionSimple keyspace = new OptionSimple("keyspace=", ".*", "Keyspace1", "The keyspace name to use", false);
|
||||
final OptionSimple columnFamily = new OptionSimple("columnfamily=", ".*", "Standard1", "The column family name to use", false);
|
||||
final OptionSimple compactionStrategy = new OptionSimple("compaction=", ".*", null, "The compaction strategy to use", false);
|
||||
final OptionSimple noReplicateOnWrite = new OptionSimple("no-replicate-on-write", "", null, "Set replicate_on_write to false for counters. Only counter add with CL=ONE will work", false);
|
||||
final OptionSimple compression = new OptionSimple("compression=", ".*", null, "Specify the compression to use for sstable, default:no compression", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(replication, index, keyspace, columnFamily, compactionStrategy, noReplicateOnWrite, compression);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsSchema get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-schema");
|
||||
if (params == null)
|
||||
return new SettingsSchema(new Options());
|
||||
|
||||
GroupedOptions options = GroupedOptions.select(params, new Options());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -schema options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsSchema((Options) options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-schema", new Options());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.thrift.transport.TTransportFactory;
|
||||
|
||||
public class SettingsTransport implements Serializable
|
||||
{
|
||||
|
||||
private final String fqFactoryClass;
|
||||
private TTransportFactory factory;
|
||||
|
||||
public SettingsTransport(TOptions options)
|
||||
{
|
||||
if (options instanceof SSLOptions)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fqFactoryClass = options.factory.value();
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(fqFactoryClass);
|
||||
if (!TTransportFactory.class.isAssignableFrom(clazz))
|
||||
throw new ClassCastException();
|
||||
// check we can instantiate it
|
||||
clazz.newInstance();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid transport factory class: " + options.factory.value(), e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized TTransportFactory getFactory()
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.factory = (TTransportFactory) Class.forName(fqFactoryClass).newInstance();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
// Option Declarations
|
||||
|
||||
static class TOptions extends GroupedOptions
|
||||
{
|
||||
final OptionSimple factory = new OptionSimple("factory=", ".*", "org.apache.cassandra.cli.transport.FramedTransportFactory", "Fully-qualified TTransportFactory class name for creating a connection. Note: For Thrift over SSL, use org.apache.cassandra.stress.SSLTransportFactory.", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(factory);
|
||||
}
|
||||
}
|
||||
|
||||
static final class SSLOptions extends TOptions
|
||||
{
|
||||
final OptionSimple trustStore = new OptionSimple("truststore=", ".*", null, "SSL: full path to truststore", false);
|
||||
final OptionSimple trustStorePw = new OptionSimple("truststore-password=", ".*", null, "", false);
|
||||
final OptionSimple protocol = new OptionSimple("ssl-protocol=", ".*", "TLS", "SSL: connections protocol to use", false);
|
||||
final OptionSimple alg = new OptionSimple("ssl-alg=", ".*", "SunX509", "SSL: algorithm", false);
|
||||
final OptionSimple storeType = new OptionSimple("store-type=", ".*", "TLS", "SSL: comma delimited list of encryption suites to use", false);
|
||||
final OptionSimple ciphers = new OptionSimple("ssl-ciphers=", ".*", "TLS", "SSL: comma delimited list of encryption suites to use", false);
|
||||
|
||||
@Override
|
||||
public List<? extends Option> options()
|
||||
{
|
||||
return Arrays.asList(factory, trustStore, trustStorePw, protocol, alg, storeType, ciphers);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI Utility Methods
|
||||
|
||||
public static SettingsTransport get(Map<String, String[]> clArgs)
|
||||
{
|
||||
String[] params = clArgs.remove("-transport");
|
||||
if (params == null)
|
||||
return new SettingsTransport(new TOptions());
|
||||
|
||||
GroupedOptions options = GroupedOptions.select(params, new TOptions());
|
||||
if (options == null)
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Invalid -transport options provided, see output for valid options");
|
||||
System.exit(1);
|
||||
}
|
||||
return new SettingsTransport((TOptions) options);
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
GroupedOptions.printOptions(System.out, "-transport", new TOptions());
|
||||
}
|
||||
|
||||
public static Runnable helpPrinter()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
package org.apache.cassandra.stress.settings;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
import com.datastax.driver.core.Metadata;
|
||||
import org.apache.cassandra.stress.util.JavaDriverClient;
|
||||
import org.apache.cassandra.stress.util.SimpleThriftClient;
|
||||
import org.apache.cassandra.stress.util.SmartThriftClient;
|
||||
import org.apache.cassandra.thrift.Cassandra;
|
||||
import org.apache.cassandra.thrift.InvalidRequestException;
|
||||
import org.apache.cassandra.thrift.TFramedTransportFactory;
|
||||
import org.apache.cassandra.transport.SimpleClient;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.apache.thrift.transport.TSocket;
|
||||
import org.apache.thrift.transport.TTransport;
|
||||
|
||||
public class StressSettings implements Serializable
|
||||
{
|
||||
|
||||
public final SettingsCommand command;
|
||||
public final SettingsRate rate;
|
||||
public final SettingsKey keys;
|
||||
public final SettingsColumn columns;
|
||||
public final SettingsLog log;
|
||||
public final SettingsMode mode;
|
||||
public final SettingsNode node;
|
||||
public final SettingsSchema schema;
|
||||
public final SettingsTransport transport;
|
||||
public final SettingsPort port;
|
||||
public final String sendToDaemon;
|
||||
|
||||
public StressSettings(SettingsCommand command, SettingsRate rate, SettingsKey keys, SettingsColumn columns, SettingsLog log, SettingsMode mode, SettingsNode node, SettingsSchema schema, SettingsTransport transport, SettingsPort port, String sendToDaemon)
|
||||
{
|
||||
this.command = command;
|
||||
this.rate = rate;
|
||||
this.keys = keys;
|
||||
this.columns = columns;
|
||||
this.log = log;
|
||||
this.mode = mode;
|
||||
this.node = node;
|
||||
this.schema = schema;
|
||||
this.transport = transport;
|
||||
this.port = port;
|
||||
this.sendToDaemon = sendToDaemon;
|
||||
}
|
||||
|
||||
public SmartThriftClient getSmartThriftClient()
|
||||
{
|
||||
Metadata metadata = getJavaDriverClient().getCluster().getMetadata();
|
||||
return new SmartThriftClient(this, schema.keyspace, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrift client connection
|
||||
* @return cassandra client connection
|
||||
*/
|
||||
public SimpleThriftClient getThriftClient()
|
||||
{
|
||||
return new SimpleThriftClient(getRawThriftClient(node.randomNode(), true));
|
||||
}
|
||||
|
||||
public Cassandra.Client getRawThriftClient(boolean setKeyspace)
|
||||
{
|
||||
return getRawThriftClient(node.randomNode(), setKeyspace);
|
||||
}
|
||||
|
||||
public Cassandra.Client getRawThriftClient(String host)
|
||||
{
|
||||
return getRawThriftClient(host, true);
|
||||
}
|
||||
|
||||
public Cassandra.Client getRawThriftClient(String host, boolean setKeyspace)
|
||||
{
|
||||
TSocket socket = new TSocket(host, port.thriftPort);
|
||||
Cassandra.Client client;
|
||||
|
||||
try
|
||||
{
|
||||
TTransport transport = this.transport.getFactory().getTransport(socket);
|
||||
transport.open();
|
||||
|
||||
client = new Cassandra.Client(new TBinaryProtocol(transport));
|
||||
|
||||
if (mode.cqlVersion.isCql())
|
||||
client.set_cql_version(mode.cqlVersion.connectVersion);
|
||||
|
||||
if (setKeyspace)
|
||||
client.set_keyspace("Keyspace1");
|
||||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
throw new RuntimeException(e.getWhy());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
public SimpleClient getSimpleNativeClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
String currentNode = node.randomNode();
|
||||
SimpleClient client = new SimpleClient(currentNode, port.nativePort);
|
||||
client.connect(false);
|
||||
client.execute("USE \"Keyspace1\";", org.apache.cassandra.db.ConsistencyLevel.ONE);
|
||||
return client;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile JavaDriverClient client;
|
||||
|
||||
public JavaDriverClient getJavaDriverClient()
|
||||
{
|
||||
if (client != null)
|
||||
return client;
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
String currentNode = node.randomNode();
|
||||
if (client != null)
|
||||
return client;
|
||||
|
||||
JavaDriverClient c = new JavaDriverClient(currentNode, port.nativePort);
|
||||
c.connect(mode.compression());
|
||||
c.execute("USE \"Keyspace1\";", org.apache.cassandra.db.ConsistencyLevel.ONE);
|
||||
return client = c;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void maybeCreateKeyspaces()
|
||||
{
|
||||
if (command.type == Command.WRITE || command.type == Command.COUNTERWRITE)
|
||||
schema.createKeySpaces(this);
|
||||
|
||||
}
|
||||
|
||||
public static StressSettings parse(String[] args)
|
||||
{
|
||||
final Map<String, String[]> clArgs = parseMap(args);
|
||||
if (clArgs.containsKey("legacy"))
|
||||
return Legacy.build(Arrays.copyOfRange(args, 1, args.length));
|
||||
if (SettingsMisc.maybeDoSpecial(clArgs))
|
||||
System.exit(1);
|
||||
return get(clArgs);
|
||||
}
|
||||
|
||||
public static StressSettings get(Map<String, String[]> clArgs)
|
||||
{
|
||||
SettingsCommand command = SettingsCommand.get(clArgs);
|
||||
if (command == null)
|
||||
throw new IllegalArgumentException("No command specified");
|
||||
String sendToDaemon = SettingsMisc.getSendToDaemon(clArgs);
|
||||
SettingsPort port = SettingsPort.get(clArgs);
|
||||
SettingsRate rate = SettingsRate.get(clArgs, command);
|
||||
SettingsKey keys = SettingsKey.get(clArgs, command);
|
||||
SettingsColumn columns = SettingsColumn.get(clArgs);
|
||||
SettingsLog log = SettingsLog.get(clArgs);
|
||||
SettingsMode mode = SettingsMode.get(clArgs);
|
||||
SettingsNode node = SettingsNode.get(clArgs);
|
||||
SettingsSchema schema = SettingsSchema.get(clArgs);
|
||||
SettingsTransport transport = SettingsTransport.get(clArgs);
|
||||
if (!clArgs.isEmpty())
|
||||
{
|
||||
printHelp();
|
||||
System.out.println("Error processing command line arguments. The following were ignored:");
|
||||
for (Map.Entry<String, String[]> e : clArgs.entrySet())
|
||||
{
|
||||
System.out.print(e.getKey());
|
||||
for (String v : e.getValue())
|
||||
{
|
||||
System.out.print(" ");
|
||||
System.out.print(v);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
return new StressSettings(command, rate, keys, columns, log, mode, node, schema, transport, port, sendToDaemon);
|
||||
}
|
||||
|
||||
private static Map<String, String[]> parseMap(String[] args)
|
||||
{
|
||||
// first is the main command/operation, so specified without a -
|
||||
if (args.length == 0)
|
||||
{
|
||||
System.out.println("No command provided");
|
||||
printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
final LinkedHashMap<String, String[]> r = new LinkedHashMap<>();
|
||||
String key = null;
|
||||
List<String> params = new ArrayList<>();
|
||||
for (int i = 0 ; i < args.length ; i++)
|
||||
{
|
||||
if (i == 0 || args[i].startsWith("-"))
|
||||
{
|
||||
if (i > 0)
|
||||
r.put(key, params.toArray(new String[0]));
|
||||
key = args[i].toLowerCase();
|
||||
params.clear();
|
||||
}
|
||||
else
|
||||
params.add(args[i]);
|
||||
}
|
||||
r.put(key, params.toArray(new String[0]));
|
||||
return r;
|
||||
}
|
||||
|
||||
public static void printHelp()
|
||||
{
|
||||
SettingsMisc.printHelp();
|
||||
}
|
||||
|
||||
public synchronized void disconnect()
|
||||
{
|
||||
if (client == null)
|
||||
return;
|
||||
|
||||
client.disconnect();
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +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.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.thrift.Cassandra.Client;
|
||||
import org.apache.thrift.protocol.TProtocol;
|
||||
|
||||
public class CassandraClient extends Client
|
||||
{
|
||||
public Map<Integer, Integer> preparedStatements = new HashMap<Integer, Integer>();
|
||||
|
||||
public CassandraClient(TProtocol protocol)
|
||||
{
|
||||
super(protocol);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* 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.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import com.datastax.driver.core.*;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
import org.apache.cassandra.security.SSLFactory;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
import org.jboss.netty.logging.Slf4JLoggerFactory;
|
||||
|
||||
public class JavaDriverClient
|
||||
{
|
||||
|
||||
static
|
||||
{
|
||||
InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
|
||||
}
|
||||
|
||||
public final String host;
|
||||
public final int port;
|
||||
private final EncryptionOptions.ClientEncryptionOptions encryptionOptions;
|
||||
private Cluster cluster;
|
||||
private Session session;
|
||||
|
||||
public JavaDriverClient(String host, int port)
|
||||
{
|
||||
this(host, port, new EncryptionOptions.ClientEncryptionOptions());
|
||||
}
|
||||
|
||||
public JavaDriverClient(String host, int port, EncryptionOptions.ClientEncryptionOptions encryptionOptions)
|
||||
{
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.encryptionOptions = encryptionOptions;
|
||||
}
|
||||
|
||||
public PreparedStatement prepare(String query)
|
||||
{
|
||||
return getSession().prepare(query);
|
||||
}
|
||||
|
||||
public void connect(ProtocolOptions.Compression compression) throws Exception
|
||||
{
|
||||
Cluster.Builder clusterBuilder = Cluster.builder()
|
||||
.addContactPoint(host).withPort(port);
|
||||
clusterBuilder.withCompression(compression);
|
||||
if (encryptionOptions.enabled)
|
||||
{
|
||||
SSLContext sslContext;
|
||||
sslContext = SSLFactory.createSSLContext(encryptionOptions, true);
|
||||
SSLOptions sslOptions = new SSLOptions(sslContext, encryptionOptions.cipher_suites);
|
||||
clusterBuilder.withSSL(sslOptions);
|
||||
}
|
||||
cluster = clusterBuilder.build();
|
||||
Metadata metadata = cluster.getMetadata();
|
||||
System.out.printf("Connected to cluster: %s\n",
|
||||
metadata.getClusterName());
|
||||
for (Host host : metadata.getAllHosts())
|
||||
{
|
||||
System.out.printf("Datatacenter: %s; Host: %s; Rack: %s\n",
|
||||
host.getDatacenter(), host.getAddress(), host.getRack());
|
||||
}
|
||||
|
||||
session = cluster.connect();
|
||||
}
|
||||
|
||||
public Cluster getCluster()
|
||||
{
|
||||
return cluster;
|
||||
}
|
||||
|
||||
public Session getSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
public ResultSet execute(String query, org.apache.cassandra.db.ConsistencyLevel consistency)
|
||||
{
|
||||
SimpleStatement stmt = new SimpleStatement(query);
|
||||
stmt.setConsistencyLevel(from(consistency));
|
||||
return getSession().execute(stmt);
|
||||
}
|
||||
|
||||
public ResultSet executePrepared(PreparedStatement stmt, List<ByteBuffer> queryParams, org.apache.cassandra.db.ConsistencyLevel consistency)
|
||||
{
|
||||
|
||||
stmt.setConsistencyLevel(from(consistency));
|
||||
BoundStatement bstmt = stmt.bind(queryParams.toArray(new ByteBuffer[queryParams.size()]));
|
||||
return getSession().execute(bstmt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ConsistencyLevel from a C* ConsistencyLevel. This exists in the Java Driver ConsistencyLevel,
|
||||
* but it is not public.
|
||||
*
|
||||
* @param cl
|
||||
* @return
|
||||
*/
|
||||
ConsistencyLevel from(org.apache.cassandra.db.ConsistencyLevel cl)
|
||||
{
|
||||
switch (cl)
|
||||
{
|
||||
case ANY:
|
||||
return com.datastax.driver.core.ConsistencyLevel.ANY;
|
||||
case ONE:
|
||||
return com.datastax.driver.core.ConsistencyLevel.ONE;
|
||||
case TWO:
|
||||
return com.datastax.driver.core.ConsistencyLevel.TWO;
|
||||
case THREE:
|
||||
return com.datastax.driver.core.ConsistencyLevel.THREE;
|
||||
case QUORUM:
|
||||
return com.datastax.driver.core.ConsistencyLevel.QUORUM;
|
||||
case ALL:
|
||||
return com.datastax.driver.core.ConsistencyLevel.ALL;
|
||||
case LOCAL_QUORUM:
|
||||
return com.datastax.driver.core.ConsistencyLevel.LOCAL_QUORUM;
|
||||
case EACH_QUORUM:
|
||||
return com.datastax.driver.core.ConsistencyLevel.EACH_QUORUM;
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public void disconnect()
|
||||
{
|
||||
FBUtilities.waitOnFuture(cluster.shutdown());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,334 +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.util;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
||||
import org.apache.cassandra.stress.Session;
|
||||
import org.apache.cassandra.stress.Stress;
|
||||
import org.apache.cassandra.transport.SimpleClient;
|
||||
import org.apache.cassandra.thrift.Compression;
|
||||
import org.apache.cassandra.thrift.CqlPreparedResult;
|
||||
import org.apache.cassandra.thrift.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.Hex;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
public abstract class Operation
|
||||
{
|
||||
public final int index;
|
||||
|
||||
protected final Session session;
|
||||
protected static volatile Double nextGaussian = null;
|
||||
|
||||
public Operation(int idx)
|
||||
{
|
||||
index = idx;
|
||||
session = Stress.session;
|
||||
}
|
||||
|
||||
public Operation(Session client, int idx)
|
||||
{
|
||||
index = idx;
|
||||
session = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run operation
|
||||
* @param client Cassandra Thrift client connection
|
||||
* @throws IOException on any I/O error.
|
||||
*/
|
||||
public abstract void run(CassandraClient client) throws IOException;
|
||||
|
||||
public void run(SimpleClient client) throws IOException {}
|
||||
|
||||
// Utility methods
|
||||
|
||||
protected List<ByteBuffer> generateValues()
|
||||
{
|
||||
if (session.averageSizeValues)
|
||||
{
|
||||
return generateRandomizedValues();
|
||||
}
|
||||
|
||||
List<ByteBuffer> values = new ArrayList<ByteBuffer>();
|
||||
|
||||
for (int i = 0; i < session.getCardinality(); i++)
|
||||
{
|
||||
String hash = getMD5(Integer.toString(i));
|
||||
int times = session.getColumnSize() / hash.length();
|
||||
int sumReminder = session.getColumnSize() % hash.length();
|
||||
|
||||
String value = multiplyString(hash, times) + hash.substring(0, sumReminder);
|
||||
values.add(ByteBuffer.wrap(value.getBytes()));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate values of average size specified by -S, up to cardinality specified by -C
|
||||
* @return Collection of the values
|
||||
*/
|
||||
protected List<ByteBuffer> generateRandomizedValues()
|
||||
{
|
||||
List<ByteBuffer> values = new ArrayList<ByteBuffer>();
|
||||
|
||||
int limit = 2 * session.getColumnSize();
|
||||
|
||||
for (int i = 0; i < session.getCardinality(); i++)
|
||||
{
|
||||
byte[] value = new byte[Stress.randomizer.nextInt(limit)];
|
||||
Stress.randomizer.nextBytes(value);
|
||||
values.add(ByteBuffer.wrap(value));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* key generator using Gauss or Random algorithm
|
||||
* @return byte[] representation of the key string
|
||||
*/
|
||||
protected byte[] generateKey()
|
||||
{
|
||||
return (session.useRandomGenerator()) ? generateRandomKey() : generateGaussKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Random key generator
|
||||
* @return byte[] representation of the key string
|
||||
*/
|
||||
private byte[] generateRandomKey()
|
||||
{
|
||||
String format = "%0" + session.getTotalKeysLength() + "d";
|
||||
return String.format(format, Stress.randomizer.nextInt(Stress.session.getNumDifferentKeys() - 1)).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gauss key generator
|
||||
* @return byte[] representation of the key string
|
||||
*/
|
||||
private byte[] generateGaussKey()
|
||||
{
|
||||
String format = "%0" + session.getTotalKeysLength() + "d";
|
||||
|
||||
for (;;)
|
||||
{
|
||||
double token = nextGaussian(session.getMean(), session.getSigma());
|
||||
|
||||
if (0 <= token && token < session.getNumDifferentKeys())
|
||||
{
|
||||
return String.format(format, (int) token).getBytes(UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gaussian distribution.
|
||||
* @param mu is the mean
|
||||
* @param sigma is the standard deviation
|
||||
*
|
||||
* @return next Gaussian distribution number
|
||||
*/
|
||||
private static double nextGaussian(int mu, float sigma)
|
||||
{
|
||||
Random random = Stress.randomizer;
|
||||
|
||||
Double currentState = nextGaussian;
|
||||
nextGaussian = null;
|
||||
|
||||
if (currentState == null)
|
||||
{
|
||||
double x2pi = random.nextDouble() * 2 * Math.PI;
|
||||
double g2rad = Math.sqrt(-2.0 * Math.log(1.0 - random.nextDouble()));
|
||||
|
||||
currentState = Math.cos(x2pi) * g2rad;
|
||||
nextGaussian = Math.sin(x2pi) * g2rad;
|
||||
}
|
||||
|
||||
return mu + currentState * sigma;
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5 string generation
|
||||
* @param input String
|
||||
* @return md5 representation of the string
|
||||
*/
|
||||
private String getMD5(String input)
|
||||
{
|
||||
MessageDigest md = FBUtilities.threadLocalMD5Digest();
|
||||
byte[] messageDigest = md.digest(input.getBytes(UTF_8));
|
||||
StringBuilder hash = new StringBuilder(new BigInteger(1, messageDigest).toString(16));
|
||||
|
||||
while (hash.length() < 32)
|
||||
hash.append("0").append(hash);
|
||||
|
||||
return hash.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equal to python/ruby - 's' * times
|
||||
* @param str String to multiple
|
||||
* @param times multiplication times
|
||||
* @return multiplied string
|
||||
*/
|
||||
private String multiplyString(String str, int times)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < times; i++)
|
||||
result.append(str);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected ByteBuffer columnName(int index, boolean timeUUIDComparator)
|
||||
{
|
||||
return timeUUIDComparator
|
||||
? TimeUUIDType.instance.decompose(UUIDGen.getTimeUUID())
|
||||
: ByteBufferUtil.bytes(String.format("C%d", index));
|
||||
}
|
||||
|
||||
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 (!session.ignoreErrors())
|
||||
throw new IOException(message);
|
||||
else
|
||||
System.err.println(message);
|
||||
}
|
||||
|
||||
protected String getUnQuotedCqlBlob(String term, boolean isCQL3)
|
||||
{
|
||||
return getUnQuotedCqlBlob(term.getBytes(), isCQL3);
|
||||
}
|
||||
|
||||
protected String getUnQuotedCqlBlob(byte[] term, boolean isCQL3)
|
||||
{
|
||||
return isCQL3
|
||||
? "0x" + Hex.bytesToHex(term)
|
||||
: Hex.bytesToHex(term);
|
||||
}
|
||||
|
||||
protected List<ByteBuffer> queryParamsAsByteBuffer(List<String> queryParams)
|
||||
{
|
||||
return Lists.transform(queryParams, new Function<String, ByteBuffer>()
|
||||
{
|
||||
public ByteBuffer apply(String param)
|
||||
{
|
||||
if (param.startsWith("0x"))
|
||||
param = param.substring(2);
|
||||
return ByteBufferUtil.hexToBytes(param);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected static String formatCqlQuery(String query, List<String> parms)
|
||||
{
|
||||
int marker, position = 0;
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
if (-1 == (marker = query.indexOf('?')) || parms.size() == 0)
|
||||
return query;
|
||||
|
||||
for (String parm : parms)
|
||||
{
|
||||
result.append(query.substring(position, marker));
|
||||
result.append(parm);
|
||||
|
||||
position = marker + 1;
|
||||
if (-1 == (marker = query.indexOf('?', position + 1)))
|
||||
break;
|
||||
}
|
||||
|
||||
if (position < query.length())
|
||||
result.append(query.substring(position));
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected Integer getPreparedStatement(CassandraClient client, String cqlQuery) throws Exception
|
||||
{
|
||||
Integer statementId = client.preparedStatements.get(cqlQuery.hashCode());
|
||||
if (statementId == null)
|
||||
{
|
||||
CqlPreparedResult response = session.cqlVersion.startsWith("3")
|
||||
? client.prepare_cql3_query(ByteBufferUtil.bytes(cqlQuery), Compression.NONE)
|
||||
: client.prepare_cql_query(ByteBufferUtil.bytes(cqlQuery), Compression.NONE);
|
||||
statementId = response.itemId;
|
||||
client.preparedStatements.put(cqlQuery.hashCode(), statementId);
|
||||
}
|
||||
|
||||
return statementId;
|
||||
}
|
||||
|
||||
private static final Map<Integer, byte[]> preparedStatementsNative = new HashMap<Integer, byte[]>();
|
||||
|
||||
protected static byte[] getPreparedStatement(SimpleClient client, String cqlQuery) throws Exception
|
||||
{
|
||||
byte[] statementId = preparedStatementsNative.get(cqlQuery.hashCode());
|
||||
if (statementId == null)
|
||||
{
|
||||
statementId = client.prepare(cqlQuery).statementId.bytes;
|
||||
preparedStatementsNative.put(cqlQuery.hashCode(), statementId);
|
||||
}
|
||||
return statementId;
|
||||
}
|
||||
|
||||
protected String wrapInQuotesIfRequired(String string)
|
||||
{
|
||||
return session.cqlVersion.startsWith("3")
|
||||
? "\"" + string + "\""
|
||||
: string;
|
||||
}
|
||||
|
||||
public interface CQLQueryExecutor
|
||||
{
|
||||
public boolean execute(String query, List<String> queryParameters) throws Exception;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
// represents a sample of long (latencies) together with the probability of selection of each sample (i.e. the ratio of
|
||||
// samples to total number of events). This is used to ensure that, when merging, the result has samples from each
|
||||
// with equal probability
|
||||
public final class SampleOfLongs
|
||||
{
|
||||
|
||||
// nanos
|
||||
final long[] sample;
|
||||
|
||||
// probability with which each sample was selected
|
||||
final double p;
|
||||
|
||||
SampleOfLongs(long[] sample, int p)
|
||||
{
|
||||
this.sample = sample;
|
||||
this.p = 1 / (float) p;
|
||||
}
|
||||
|
||||
SampleOfLongs(long[] sample, double p)
|
||||
{
|
||||
this.sample = sample;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
static SampleOfLongs merge(Random rnd, List<SampleOfLongs> merge, int maxSamples)
|
||||
{
|
||||
int maxLength = 0;
|
||||
double targetp = 1;
|
||||
for (SampleOfLongs sampleOfLongs : merge)
|
||||
{
|
||||
maxLength += sampleOfLongs.sample.length;
|
||||
targetp = Math.min(targetp, sampleOfLongs.p);
|
||||
}
|
||||
long[] sample = new long[maxLength];
|
||||
int count = 0;
|
||||
for (SampleOfLongs latencies : merge)
|
||||
{
|
||||
long[] in = latencies.sample;
|
||||
double p = targetp / latencies.p;
|
||||
for (int i = 0 ; i < in.length ; i++)
|
||||
if (rnd.nextDouble() < p)
|
||||
sample[count++] = in[i];
|
||||
}
|
||||
if (count > maxSamples)
|
||||
{
|
||||
targetp = subsample(rnd, maxSamples, sample, count, targetp);
|
||||
count = maxSamples;
|
||||
}
|
||||
sample = Arrays.copyOf(sample, count);
|
||||
Arrays.sort(sample);
|
||||
return new SampleOfLongs(sample, targetp);
|
||||
}
|
||||
|
||||
public SampleOfLongs subsample(Random rnd, int maxSamples)
|
||||
{
|
||||
if (maxSamples > sample.length)
|
||||
return this;
|
||||
|
||||
long[] sample = this.sample.clone();
|
||||
double p = subsample(rnd, maxSamples, sample, sample.length, this.p);
|
||||
sample = Arrays.copyOf(sample, maxSamples);
|
||||
return new SampleOfLongs(sample, p);
|
||||
}
|
||||
|
||||
private static double subsample(Random rnd, int maxSamples, long[] sample, int count, double p)
|
||||
{
|
||||
// want exactly maxSamples, so select random indexes up to maxSamples
|
||||
for (int i = 0 ; i < maxSamples ; i++)
|
||||
{
|
||||
int take = i + rnd.nextInt(count - i);
|
||||
long tmp = sample[i];
|
||||
sample[i] = sample[take];
|
||||
sample[take] = tmp;
|
||||
}
|
||||
|
||||
// calculate new p; have selected with probability maxSamples / count
|
||||
// so multiply p by this probability
|
||||
p *= maxSamples / (double) sample.length;
|
||||
return p;
|
||||
}
|
||||
|
||||
public double medianLatency()
|
||||
{
|
||||
if (sample.length == 0)
|
||||
return 0;
|
||||
return sample[sample.length >> 1] * 0.000001d;
|
||||
}
|
||||
|
||||
// 0 < rank < 1
|
||||
public double rankLatency(float rank)
|
||||
{
|
||||
if (sample.length == 0)
|
||||
return 0;
|
||||
int index = (int)(rank * sample.length);
|
||||
if (index >= sample.length)
|
||||
index = sample.length - 1;
|
||||
return sample[index] * 0.000001d;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.thrift.TException;
|
||||
|
||||
public class SimpleThriftClient implements ThriftClient
|
||||
{
|
||||
|
||||
final Cassandra.Client client;
|
||||
public SimpleThriftClient(Cassandra.Client client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void batch_mutate(Map<ByteBuffer, Map<String, List<Mutation>>> record, ConsistencyLevel consistencyLevel) throws TException
|
||||
{
|
||||
client.batch_mutate(record, consistencyLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ColumnOrSuperColumn> get_slice(ByteBuffer key, ColumnParent column_parent, SlicePredicate predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
return client.get_slice(key, column_parent, predicate, consistency_level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeySlice> get_indexed_slices(ColumnParent column_parent, IndexClause index_clause, SlicePredicate column_predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
return client.get_indexed_slices(column_parent, index_clause, column_predicate, consistency_level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeySlice> get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
return client.get_range_slices(column_parent, predicate, range, consistency_level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ByteBuffer, List<ColumnOrSuperColumn>> multiget_slice(List<ByteBuffer> keys, ColumnParent column_parent, SlicePredicate predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
return client.multiget_slice(keys, column_parent, predicate, consistency_level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(ByteBuffer key, ColumnParent column_parent, Column column, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
client.insert(key, column_parent, column, consistency_level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer prepare_cql3_query(String query, Compression compression) throws InvalidRequestException, TException
|
||||
{
|
||||
return client.prepare_cql3_query(ByteBufferUtil.bytes(query), compression).itemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_prepared_cql_query(int itemId, ByteBuffer key, List<ByteBuffer> values) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
return client.execute_prepared_cql_query(itemId, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer prepare_cql_query(String query, Compression compression) throws InvalidRequestException, TException
|
||||
{
|
||||
return client.prepare_cql_query(ByteBufferUtil.bytes(query), compression).itemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_cql3_query(String query, ByteBuffer key, Compression compression, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
return client.execute_cql3_query(ByteBufferUtil.bytes(query), compression, consistency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_prepared_cql3_query(int itemId, ByteBuffer key, List<ByteBuffer> values, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
return client.execute_prepared_cql3_query(itemId, values, consistency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_cql_query(String query, ByteBuffer key, Compression compression) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
return client.execute_cql_query(ByteBufferUtil.bytes(query), compression);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.datastax.driver.core.Host;
|
||||
import com.datastax.driver.core.Metadata;
|
||||
import org.apache.cassandra.stress.settings.StressSettings;
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.thrift.TException;
|
||||
|
||||
public class SmartThriftClient implements ThriftClient
|
||||
{
|
||||
|
||||
final String keyspace;
|
||||
final Metadata metadata;
|
||||
final StressSettings settings;
|
||||
final ConcurrentHashMap<Host, ConcurrentLinkedQueue<Client>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
final AtomicInteger queryIdCounter = new AtomicInteger();
|
||||
final ConcurrentHashMap<Integer, String> queryStrings = new ConcurrentHashMap<>();
|
||||
final ConcurrentHashMap<String, Integer> queryIds = new ConcurrentHashMap<>();
|
||||
|
||||
public SmartThriftClient(StressSettings settings, String keyspace, Metadata metadata)
|
||||
{
|
||||
this.metadata = metadata;
|
||||
this.keyspace = keyspace;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
private final AtomicInteger roundrobin = new AtomicInteger();
|
||||
|
||||
private Integer getId(String query)
|
||||
{
|
||||
Integer r;
|
||||
if ((r = queryIds.get(query)) != null)
|
||||
return r;
|
||||
r = queryIdCounter.incrementAndGet();
|
||||
if (queryIds.putIfAbsent(query, r) == null)
|
||||
return r;
|
||||
queryStrings.put(r, query);
|
||||
return queryIds.get(query);
|
||||
}
|
||||
|
||||
final class Client
|
||||
{
|
||||
final Cassandra.Client client;
|
||||
final Host host;
|
||||
final Map<Integer, Integer> queryMap = new HashMap<>();
|
||||
|
||||
Client(Cassandra.Client client, Host host)
|
||||
{
|
||||
this.client = client;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
Integer get(Integer id, boolean cql3) throws TException
|
||||
{
|
||||
Integer serverId = queryMap.get(id);
|
||||
if (serverId != null)
|
||||
return serverId;
|
||||
prepare(id, cql3);
|
||||
return queryMap.get(id);
|
||||
}
|
||||
|
||||
void prepare(Integer id, boolean cql3) throws TException
|
||||
{
|
||||
String query;
|
||||
while ( null == (query = queryStrings.get(id)) ) ;
|
||||
if (cql3)
|
||||
{
|
||||
Integer serverId = client.prepare_cql3_query(ByteBufferUtil.bytes(query), Compression.NONE).itemId;
|
||||
queryMap.put(id, serverId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Integer serverId = client.prepare_cql_query(ByteBufferUtil.bytes(query), Compression.NONE).itemId;
|
||||
queryMap.put(id, serverId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Client get(ByteBuffer pk)
|
||||
{
|
||||
Set<Host> hosts = metadata.getReplicas(keyspace, pk);
|
||||
int count = roundrobin.incrementAndGet() % hosts.size();
|
||||
if (count < 0)
|
||||
count = -count;
|
||||
Iterator<Host> iter = hosts.iterator();
|
||||
while (count > 0 && iter.hasNext())
|
||||
iter.next();
|
||||
Host host = iter.next();
|
||||
ConcurrentLinkedQueue<Client> q = cache.get(host);
|
||||
if (q == null)
|
||||
{
|
||||
ConcurrentLinkedQueue<Client> newQ = new ConcurrentLinkedQueue<Client>();
|
||||
q = cache.putIfAbsent(host, newQ);
|
||||
if (q == null)
|
||||
q = newQ;
|
||||
}
|
||||
Client tclient = q.poll();
|
||||
if (tclient != null)
|
||||
return tclient;
|
||||
return new Client(settings.getRawThriftClient(host.getAddress().getHostAddress()), host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batch_mutate(Map<ByteBuffer, Map<String, List<Mutation>>> record, ConsistencyLevel consistencyLevel) throws TException
|
||||
{
|
||||
for (Map.Entry<ByteBuffer, Map<String, List<Mutation>>> e : record.entrySet())
|
||||
{
|
||||
Client client = get(e.getKey());
|
||||
try
|
||||
{
|
||||
client.client.batch_mutate(Collections.singletonMap(e.getKey(), e.getValue()), consistencyLevel);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ColumnOrSuperColumn> get_slice(ByteBuffer key, ColumnParent parent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
Client client = get(key);
|
||||
try
|
||||
{
|
||||
return client.client.get_slice(key, parent, predicate, consistencyLevel);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(ByteBuffer key, ColumnParent column_parent, Column column, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
Client client = get(key);
|
||||
try
|
||||
{
|
||||
client.client.insert(key, column_parent, column, consistency_level);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_cql_query(String query, ByteBuffer key, Compression compression) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
Client client = get(key);
|
||||
try
|
||||
{
|
||||
return client.client.execute_cql_query(ByteBufferUtil.bytes(query), compression);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_cql3_query(String query, ByteBuffer key, Compression compression, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
Client client = get(key);
|
||||
try
|
||||
{
|
||||
return client.client.execute_cql3_query(ByteBufferUtil.bytes(query), compression, consistency);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer prepare_cql3_query(String query, Compression compression) throws InvalidRequestException, TException
|
||||
{
|
||||
return getId(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_prepared_cql3_query(int queryId, ByteBuffer key, List<ByteBuffer> values, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
Client client = get(key);
|
||||
try
|
||||
{
|
||||
return client.client.execute_prepared_cql3_query(client.get(queryId, true), values, consistency);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer prepare_cql_query(String query, Compression compression) throws InvalidRequestException, TException
|
||||
{
|
||||
return getId(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlResult execute_prepared_cql_query(int queryId, ByteBuffer key, List<ByteBuffer> values) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
|
||||
{
|
||||
Client client = get(key);
|
||||
try
|
||||
{
|
||||
return client.client.execute_prepared_cql_query(client.get(queryId, true), values);
|
||||
} finally
|
||||
{
|
||||
cache.get(client.host).add(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ByteBuffer, List<ColumnOrSuperColumn>> multiget_slice(List<ByteBuffer> keys, ColumnParent column_parent, SlicePredicate predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeySlice> get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeySlice> get_indexed_slices(ColumnParent column_parent, IndexClause index_clause, SlicePredicate column_predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.thrift.TException;
|
||||
|
||||
public interface ThriftClient
|
||||
{
|
||||
|
||||
public void batch_mutate(Map<ByteBuffer, Map<String, List<Mutation>>> record, ConsistencyLevel consistencyLevel) throws TException;
|
||||
|
||||
List<ColumnOrSuperColumn> get_slice(ByteBuffer key, ColumnParent parent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws InvalidRequestException, UnavailableException, TimedOutException, TException;
|
||||
|
||||
void insert(ByteBuffer key, ColumnParent column_parent, Column column, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException;
|
||||
|
||||
Map<ByteBuffer, List<ColumnOrSuperColumn>> multiget_slice(List<ByteBuffer> keys, ColumnParent column_parent, SlicePredicate predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException;
|
||||
|
||||
List<KeySlice> get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException;
|
||||
|
||||
List<KeySlice> get_indexed_slices(ColumnParent column_parent, IndexClause index_clause, SlicePredicate column_predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, TException;
|
||||
|
||||
Integer prepare_cql3_query(String query, Compression compression) throws InvalidRequestException, TException;
|
||||
|
||||
CqlResult execute_prepared_cql3_query(int itemId, ByteBuffer key, List<ByteBuffer> values, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException;
|
||||
|
||||
CqlResult execute_cql_query(String query, ByteBuffer key, Compression compression) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException;
|
||||
|
||||
CqlResult execute_cql3_query(String query, ByteBuffer key, Compression compression, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException;
|
||||
|
||||
Integer prepare_cql_query(String query, Compression compression) throws InvalidRequestException, TException;
|
||||
|
||||
CqlResult execute_prepared_cql_query(int itemId, ByteBuffer key, List<ByteBuffer> values) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException;
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
// a timer - this timer must be used by a single thread, and co-ordinates with other timers by
|
||||
public final class Timer
|
||||
{
|
||||
|
||||
private static final int SAMPLE_SIZE_SHIFT = 10;
|
||||
private static final int SAMPLE_SIZE_MASK = (1 << SAMPLE_SIZE_SHIFT) - 1;
|
||||
|
||||
private final Random rnd = new Random();
|
||||
|
||||
// in progress snap start
|
||||
private long sampleStartNanos;
|
||||
|
||||
// each entry is present with probability 1/p(opCount) or 1/(p(opCount)-1)
|
||||
private final long[] sample = new long[1 << SAMPLE_SIZE_SHIFT];
|
||||
private int opCount;
|
||||
|
||||
// aggregate info
|
||||
private int keyCount;
|
||||
private long total;
|
||||
private long max;
|
||||
private long maxStart;
|
||||
private long upToDateAsOf;
|
||||
private long lastSnap = System.nanoTime();
|
||||
|
||||
// communication with summary/logging thread
|
||||
private volatile CountDownLatch reportRequest;
|
||||
volatile TimingInterval report;
|
||||
private volatile TimingInterval finalReport;
|
||||
|
||||
public void start(){
|
||||
// decide if we're logging this event
|
||||
sampleStartNanos = System.nanoTime();
|
||||
}
|
||||
|
||||
private static int p(int index)
|
||||
{
|
||||
return 1 + (index >>> SAMPLE_SIZE_SHIFT);
|
||||
}
|
||||
|
||||
public void stop(int keys)
|
||||
{
|
||||
maybeReport();
|
||||
long now = System.nanoTime();
|
||||
long time = now - sampleStartNanos;
|
||||
if (rnd.nextInt(p(opCount)) == 0)
|
||||
sample[index(opCount)] = time;
|
||||
if (time > max)
|
||||
{
|
||||
maxStart = sampleStartNanos;
|
||||
max = time;
|
||||
}
|
||||
total += time;
|
||||
opCount += 1;
|
||||
keyCount += keys;
|
||||
upToDateAsOf = now;
|
||||
}
|
||||
|
||||
private static int index(int count)
|
||||
{
|
||||
return count & SAMPLE_SIZE_MASK;
|
||||
}
|
||||
|
||||
private TimingInterval buildReport()
|
||||
{
|
||||
final List<SampleOfLongs> sampleLatencies = Arrays.asList
|
||||
( new SampleOfLongs(Arrays.copyOf(sample, index(opCount)), p(opCount)),
|
||||
new SampleOfLongs(Arrays.copyOfRange(sample, index(opCount), Math.min(opCount, sample.length)), p(opCount) - 1)
|
||||
);
|
||||
final TimingInterval report = new TimingInterval(lastSnap, upToDateAsOf, max, maxStart, max, keyCount, total, opCount,
|
||||
SampleOfLongs.merge(rnd, sampleLatencies, Integer.MAX_VALUE));
|
||||
// reset counters
|
||||
opCount = 0;
|
||||
keyCount = 0;
|
||||
total = 0;
|
||||
max = 0;
|
||||
lastSnap = upToDateAsOf;
|
||||
return report;
|
||||
}
|
||||
|
||||
// checks to see if a report has been requested, and if so produces the report, signals and clears the request
|
||||
private void maybeReport()
|
||||
{
|
||||
if (reportRequest != null)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
report = buildReport();
|
||||
reportRequest.countDown();
|
||||
reportRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checks to see if the timer is dead; if not requests a report, and otherwise fulfills the request itself
|
||||
synchronized void requestReport(CountDownLatch signal)
|
||||
{
|
||||
if (finalReport != null)
|
||||
{
|
||||
report = finalReport;
|
||||
finalReport = new TimingInterval(0);
|
||||
signal.countDown();
|
||||
}
|
||||
else
|
||||
reportRequest = signal;
|
||||
}
|
||||
|
||||
// closes the timer; if a request is outstanding, it furnishes the request, otherwise it populates finalReport
|
||||
public synchronized void close()
|
||||
{
|
||||
if (reportRequest == null)
|
||||
finalReport = buildReport();
|
||||
else
|
||||
{
|
||||
finalReport = new TimingInterval(0);
|
||||
report = buildReport();
|
||||
reportRequest.countDown();
|
||||
reportRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
// relatively simple timing class for getting a uniform sample of latencies, and saving other metrics
|
||||
// ensures accuracy of timing by having single threaded timers that are check-pointed by the snapping thread,
|
||||
// which waits for them to report back. They report back the data up to the last event prior to the check-point.
|
||||
// if the threads are blocked/paused this may mean a period of time longer than the checkpoint elapses, but that all
|
||||
// metrics calculated over the interval are accurate
|
||||
public class Timing
|
||||
{
|
||||
|
||||
private final CopyOnWriteArrayList<Timer> timers = new CopyOnWriteArrayList<>();
|
||||
private volatile TimingInterval history;
|
||||
private final Random rnd = new Random();
|
||||
|
||||
// TIMING
|
||||
|
||||
private TimingInterval snapInterval(Random rnd) throws InterruptedException
|
||||
{
|
||||
final Timer[] timers = this.timers.toArray(new Timer[0]);
|
||||
final CountDownLatch ready = new CountDownLatch(timers.length);
|
||||
for (int i = 0 ; i < timers.length ; i++)
|
||||
{
|
||||
final Timer timer = timers[i];
|
||||
timer.requestReport(ready);
|
||||
}
|
||||
|
||||
// TODO fail gracefully after timeout if a thread is stuck
|
||||
if (!ready.await(2L, TimeUnit.MINUTES))
|
||||
throw new RuntimeException("Timed out waiting for a timer thread - seems one got stuck");
|
||||
|
||||
// reports have been filled in by timer threadCount, so merge
|
||||
List<TimingInterval> intervals = new ArrayList<>();
|
||||
for (Timer timer : timers)
|
||||
intervals.add(timer.report);
|
||||
|
||||
return TimingInterval.merge(rnd, intervals, Integer.MAX_VALUE, history.endNanos());
|
||||
}
|
||||
|
||||
// build a new timer and add it to the set of running timers
|
||||
public Timer newTimer()
|
||||
{
|
||||
final Timer timer = new Timer();
|
||||
timers.add(timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
history = new TimingInterval(System.nanoTime());
|
||||
}
|
||||
|
||||
public TimingInterval snapInterval() throws InterruptedException
|
||||
{
|
||||
final TimingInterval interval = snapInterval(rnd);
|
||||
history = TimingInterval.merge(rnd, Arrays.asList(interval, history), 50000, history.startNanos());
|
||||
return interval;
|
||||
}
|
||||
|
||||
public TimingInterval getHistory()
|
||||
{
|
||||
return history;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
// represents measurements taken over an interval of time
|
||||
// used for both single timer results and merged timer results
|
||||
public final class TimingInterval
|
||||
{
|
||||
// nanos
|
||||
private final long start;
|
||||
private final long end;
|
||||
public final long maxLatency;
|
||||
public final long pauseLength;
|
||||
public final long pauseStart;
|
||||
public final long totalLatency;
|
||||
|
||||
// discrete
|
||||
public final long keyCount;
|
||||
public final long operationCount;
|
||||
|
||||
final SampleOfLongs sample;
|
||||
|
||||
TimingInterval(long time)
|
||||
{
|
||||
start = end = time;
|
||||
maxLatency = totalLatency = 0;
|
||||
keyCount = operationCount = 0;
|
||||
pauseStart = pauseLength = 0;
|
||||
sample = new SampleOfLongs(new long[0], 1d);
|
||||
}
|
||||
TimingInterval(long start, long end, long maxLatency, long pauseStart, long pauseLength, long keyCount, long totalLatency, long operationCount, SampleOfLongs sample)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = Math.max(end, start);
|
||||
this.maxLatency = maxLatency;
|
||||
this.keyCount = keyCount;
|
||||
this.totalLatency = totalLatency;
|
||||
this.operationCount = operationCount;
|
||||
this.pauseStart = pauseStart;
|
||||
this.pauseLength = pauseLength;
|
||||
this.sample = sample;
|
||||
}
|
||||
|
||||
// merge multiple timer intervals together
|
||||
static TimingInterval merge(Random rnd, List<TimingInterval> intervals, int maxSamples, long start)
|
||||
{
|
||||
int operationCount = 0, keyCount = 0;
|
||||
long maxLatency = 0, totalLatency = 0;
|
||||
List<SampleOfLongs> latencies = new ArrayList<>();
|
||||
long end = 0;
|
||||
long pauseStart = 0, pauseEnd = Long.MAX_VALUE;
|
||||
for (TimingInterval interval : intervals)
|
||||
{
|
||||
end = Math.max(end, interval.end);
|
||||
operationCount += interval.operationCount;
|
||||
maxLatency = Math.max(interval.maxLatency, maxLatency);
|
||||
totalLatency += interval.totalLatency;
|
||||
keyCount += interval.keyCount;
|
||||
latencies.addAll(Arrays.asList(interval.sample));
|
||||
if (interval.pauseLength > 0)
|
||||
{
|
||||
pauseStart = Math.max(pauseStart, interval.pauseStart);
|
||||
pauseEnd = Math.min(pauseEnd, interval.pauseStart + interval.pauseLength);
|
||||
}
|
||||
}
|
||||
if (pauseEnd < pauseStart)
|
||||
pauseEnd = pauseStart = 0;
|
||||
return new TimingInterval(start, end, maxLatency, pauseStart, pauseEnd - pauseStart, keyCount, totalLatency, operationCount,
|
||||
SampleOfLongs.merge(rnd, latencies, maxSamples));
|
||||
|
||||
}
|
||||
|
||||
public double realOpRate()
|
||||
{
|
||||
return operationCount / ((end - start) * 0.000000001d);
|
||||
}
|
||||
|
||||
public double adjustedOpRate()
|
||||
{
|
||||
return operationCount / ((end - (start + pauseLength)) * 0.000000001d);
|
||||
}
|
||||
|
||||
public double keyRate()
|
||||
{
|
||||
return keyCount / ((end - start) * 0.000000001d);
|
||||
}
|
||||
|
||||
public double meanLatency()
|
||||
{
|
||||
return (totalLatency / (double) operationCount) * 0.000001d;
|
||||
}
|
||||
|
||||
public double maxLatency()
|
||||
{
|
||||
return maxLatency * 0.000001d;
|
||||
}
|
||||
|
||||
public long runTime()
|
||||
{
|
||||
return (end - start) / 1000000;
|
||||
}
|
||||
|
||||
public double medianLatency()
|
||||
{
|
||||
return sample.medianLatency();
|
||||
}
|
||||
|
||||
// 0 < rank < 1
|
||||
public double rankLatency(float rank)
|
||||
{
|
||||
return sample.rankLatency(rank);
|
||||
}
|
||||
|
||||
public final long endNanos()
|
||||
{
|
||||
return end;
|
||||
}
|
||||
|
||||
public final long endMillis()
|
||||
{
|
||||
return end / 1000000;
|
||||
}
|
||||
|
||||
public long startNanos()
|
||||
{
|
||||
return start;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package org.apache.cassandra.stress.util;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
// TODO: do not assume normal distribution of measurements.
|
||||
public class Uncertainty
|
||||
{
|
||||
|
||||
private int measurements;
|
||||
private double sumsquares;
|
||||
private double sum;
|
||||
private double stdev;
|
||||
private double mean;
|
||||
private double uncertainty;
|
||||
|
||||
private CopyOnWriteArrayList<WaitForTargetUncertainty> waiting = new CopyOnWriteArrayList<>();
|
||||
|
||||
private static final class WaitForTargetUncertainty
|
||||
{
|
||||
final double targetUncertainty;
|
||||
final int minMeasurements;
|
||||
final int maxMeasurements;
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
private WaitForTargetUncertainty(double targetUncertainty, int minMeasurements, int maxMeasurements)
|
||||
{
|
||||
this.targetUncertainty = targetUncertainty;
|
||||
this.minMeasurements = minMeasurements;
|
||||
this.maxMeasurements = maxMeasurements;
|
||||
}
|
||||
|
||||
void await() throws InterruptedException
|
||||
{
|
||||
latch.await();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void update(double value)
|
||||
{
|
||||
measurements++;
|
||||
sumsquares += value * value;
|
||||
sum += value;
|
||||
mean = sum / measurements;
|
||||
stdev = Math.sqrt((sumsquares / measurements) - (mean * mean));
|
||||
uncertainty = (stdev / Math.sqrt(measurements)) / mean;
|
||||
|
||||
for (WaitForTargetUncertainty waiter : waiting)
|
||||
{
|
||||
if ((uncertainty < waiter.targetUncertainty && measurements >= waiter.minMeasurements) || (measurements >= waiter.maxMeasurements))
|
||||
{
|
||||
waiter.latch.countDown();
|
||||
// can safely remove as working over snapshot with COWArrayList
|
||||
waiting.remove(waiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void await(double targetUncertainty, int minMeasurements, int maxMeasurements) throws InterruptedException
|
||||
{
|
||||
final WaitForTargetUncertainty wait = new WaitForTargetUncertainty(targetUncertainty, minMeasurements, maxMeasurements);
|
||||
waiting.add(wait);
|
||||
wait.await();
|
||||
}
|
||||
|
||||
public double getUncertainty()
|
||||
{
|
||||
return uncertainty;
|
||||
}
|
||||
|
||||
public void wakeAll()
|
||||
{
|
||||
for (WaitForTargetUncertainty waiting : this.waiting)
|
||||
{
|
||||
waiting.latch.countDown();
|
||||
this.waiting.remove(waiting);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue