/**
* Copyright (c) 2010 Yahoo! Inc. All rights reserved.
*
* Licensed 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. See accompanying
* LICENSE file.
*/
package com.couchbase.loadgen.workloads;
import com.couchbase.loadgen.Config;
import com.couchbase.loadgen.DataStore;
import com.couchbase.loadgen.Utils;
import com.couchbase.loadgen.generator.CounterGenerator;
import com.couchbase.loadgen.generator.DiscreteGenerator;
import com.couchbase.loadgen.generator.IntegerGenerator;
import com.couchbase.loadgen.memcached.Memcached;
/**
* The core benchmark scenario. Represents a set of clients doing simple CRUD
* operations. The relative proportion of different kinds of operations, and
* other properties of the workload, are controlled by parameters specified at
* runtime.
*
* Properties to control the client:
* <UL>
* <LI><b>fieldcount</b>: the number of fields in a record (default: 10)
* <LI><b>fieldlength</b>: the size of each field (default: 100)
* <LI><b>readallfields</b>: should reads read all fields (true) or just one
* (false) (default: true)
* <LI><b>writeallfields</b>: should updates and read/modify/writes update all
* fields (true) or just one (false) (default: false)
* <LI><b>readproportion</b>: what proportion of operations should be reads
* (default: 0.95)
* <LI><b>updateproportion</b>: what proportion of operations should be updates
* (default: 0.05)
* <LI><b>insertproportion</b>: what proportion of operations should be inserts
* (default: 0)
* <LI><b>scanproportion</b>: what proportion of operations should be scans
* (default: 0)
* <LI><b>readmodifywriteproportion</b>: what proportion of operations should be
* read a record, modify it, write it back (default: 0)
* <LI><b>requestdistribution</b>: what distribution should be used to select
* the records to operate on - uniform, zipfian or latest (default: uniform)
* <LI><b>maxscanlength</b>: for scans, what is the maximum number of records to
* scan (default: 1000)
* <LI><b>scanlengthdistribution</b>: for scans, what distribution should be
* used to choose the number of records to scan, for each scan, between 1 and
* maxscanlength (default: uniform)
* <LI><b>insertorder</b>: should records be inserted in order by key
* ("ordered"), or in hashed order ("hashed") (default: hashed)
* </ul>
*/
public class MemcachedCoreWorkload extends Workload {
private IntegerGenerator keysequence;
private DiscreteGenerator operationchooser;
private IntegerGenerator keychooser;
private CounterGenerator transactioninsertkeysequence;
private boolean orderedinserts;
private String insert_order = "hashed";
private boolean dotransactions;
public int field_count = 10;
public String key_prefix = "user";
public MemcachedCoreWorkload(IntegerGenerator keychooser) {
int recordcount = ((Integer)Config.getConfig().get(Config.RECORD_COUNT)).intValue();
int insertstart = ((Integer)Config.getConfig().get(Config.INSERT_START)).intValue();
dotransactions = ((Boolean)Config.getConfig().get(Config.DO_TRANSACTIONS)).booleanValue();
this.keychooser = keychooser;
if (insert_order.compareTo("hashed") == 0) {
orderedinserts = false;
} else {
orderedinserts = true;
}
keysequence = new CounterGenerator(insertstart);
operationchooser = new DiscreteGenerator();
transactioninsertkeysequence = new CounterGenerator(recordcount);
Config cfg = Config.getConfig();
if (((Double)cfg.get(Config.MEMADD)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMADD)).doubleValue(), "ADD");
if (((Double)cfg.get(Config.MEMAPPEND)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMAPPEND)).doubleValue(), "APPEND");
if (((Double)cfg.get(Config.MEMCAS)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMCAS)).doubleValue(), "CAS");
if (((Double)cfg.get(Config.MEMDECR)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMDECR)).doubleValue(), "DECR");
if (((Double)cfg.get(Config.MEMDELETE)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMDELETE)).doubleValue(), "DELETE");
if (((Double)cfg.get(Config.MEMGET)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMGET)).doubleValue(), "GET");
if (((Double)cfg.get(Config.MEMGETS)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMGETS)).doubleValue(), "GETS");
if (((Double)cfg.get(Config.MEMINCR)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMINCR)).doubleValue(), "INCR");
if (((Double)cfg.get(Config.MEMPREPEND)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMPREPEND)).doubleValue(), "PREPEND");
if (((Double)cfg.get(Config.MEMREPLACE)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMREPLACE)).doubleValue(), "REPLACE");
if (((Double)cfg.get(Config.MEMSET)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMSET)).doubleValue(), "SET");
if (((Double)cfg.get(Config.MEMUPDATE)).doubleValue() > 0)
operationchooser.addValue(((Double)cfg.get(Config.MEMUPDATE)).doubleValue(), "UPDATE");
}
public boolean doOperation(DataStore memcached) {
if (dotransactions)
return doTransaction(memcached);
else
return doInsert(memcached);
}
/**
* Do one insert operation. Because it will be called concurrently from
* multiple client threads, this function must be thread safe. However,
* avoid synchronized, or the threads will block waiting for each other, and
* it will be difficult to reach the target throughput. Ideally, this
* function would have no side effects other than DB operations.
*/
public boolean doInsert(DataStore memcached) {
int keynum = keysequence.nextInt();
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String dbkey = key_prefix + keynum;
int value_length = ((Integer)Config.getConfig().get(Config.VALUE_LENGTH)).intValue();
String value = Utils.ASCIIString(value_length);
if (((Memcached)memcached).set(dbkey, value) == 0)
return true;
else
return false;
}
/**
* Do one transaction operation. Because it will be called concurrently from
* multiple client threads, this function must be thread safe. However,
* avoid synchronized, or the threads will block waiting for each other, and
* it will be difficult to reach the target throughput. Ideally, this
* function would have no side effects other than DB operations.
*/
public boolean doTransaction(DataStore memcached) {
String op = operationchooser.nextString();
if (op.compareTo("ADD") == 0) {
doTransactionAdd((Memcached)memcached);
} else if (op.compareTo("APPEND") == 0) {
doTransactionAppend((Memcached)memcached);
} else if (op.compareTo("CAS") == 0) {
doTransactionCas((Memcached)memcached);
} else if (op.compareTo("DECR") == 0) {
doTransactionDecr((Memcached)memcached);
} else if (op.compareTo("DELETE") == 0) {
doTransactionDelete((Memcached)memcached);
} else if (op.compareTo("GET") == 0) {
doTransactionGet((Memcached)memcached);
} else if (op.compareTo("GETS") == 0) {
doTransactionGets((Memcached)memcached);
} else if (op.compareTo("INCR") == 0) {
doTransactionIncr((Memcached)memcached);
} else if (op.compareTo("PREPEND") == 0) {
doTransactionPrepend((Memcached)memcached);
} else if (op.compareTo("REPLACE") == 0) {
doTransactionReplace((Memcached)memcached);
} else if (op.compareTo("SET") == 0) {
doInsert((Memcached)memcached);
} else if (op.compareTo("UPDATE") == 0) {
doTransactionUpdate((Memcached)memcached);
}
return true;
}
public void doTransactionAdd(Memcached memcached) {
// choose the next key
int keynum = transactioninsertkeysequence.nextInt();
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String dbkey = key_prefix + keynum;
int value_length = ((Integer)Config.getConfig().get(Config.VALUE_LENGTH)).intValue();
String value = Utils.ASCIIString(value_length);
memcached.add(dbkey, value);
}
public void doTransactionAppend(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String key = key_prefix + keynum;
memcached.append(key, 0, "appended_string");
}
public void doTransactionCas(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String key = key_prefix + keynum;
long cas = memcached.gets(key);
int value_length = ((Integer)Config.getConfig().get(Config.VALUE_LENGTH)).intValue();
String value = Utils.ASCIIString(value_length);
memcached.cas(key, cas, value);
}
public void doTransactionDecr(Memcached memcached) {
}
public void doTransactionDelete(Memcached memcached) {
}
public void doTransactionGet(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String keyname = key_prefix + keynum;
memcached.get(keyname, null);
}
public long doTransactionGets(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
return memcached.gets(key_prefix + keynum);
}
public void doTransactionIncr(Memcached memcached) {
}
public void doTransactionPrepend(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String key = key_prefix + keynum;
memcached.prepend(key, 0, "prepended_string");
}
public void doTransactionReplace(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String key = key_prefix + keynum;
int value_length = ((Integer)Config.getConfig().get(Config.VALUE_LENGTH)).intValue();
String value = Utils.ASCIIString(value_length);
memcached.replace(key, value);
}
public void doTransactionSet(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String keyname = key_prefix + keynum;
int value_length = ((Integer)Config.getConfig().get(Config.VALUE_LENGTH)).intValue();
String value = Utils.ASCIIString(value_length);
memcached.set(keyname, value);
}
public void doTransactionUpdate(Memcached memcached) {
int keynum;
do {
keynum = keychooser.nextInt();
} while (keynum > transactioninsertkeysequence.lastInt());
if (!orderedinserts) {
keynum = Utils.hash(keynum);
}
String keyname = key_prefix + keynum;
int value_length = ((Integer)Config.getConfig().get(Config.VALUE_LENGTH)).intValue();
String value = Utils.ASCIIString(value_length);
memcached.update(keyname, value);
}
}