/***************************************************************************
* Copyright (C) 2010 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Andy Pavlo (pavlo@cs.brown.edu) *
* http://www.cs.brown.edu/~pavlo/ *
* *
* Visawee Angkanawaraphan (visawee@cs.brown.edu) *
* http://www.cs.brown.edu/~visawee/ *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.benchmark.auctionmark;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.client.Client;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcedureCallback;
import org.voltdb.types.TimestampType;
import edu.brown.api.BenchmarkComponent;
import edu.brown.benchmark.auctionmark.AuctionMarkConstants.ItemStatus;
import edu.brown.benchmark.auctionmark.util.GlobalAttributeValueId;
import edu.brown.benchmark.auctionmark.util.ItemId;
import edu.brown.benchmark.auctionmark.util.ItemInfo;
import edu.brown.benchmark.auctionmark.util.UserId;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.rand.AbstractRandomGenerator;
import edu.brown.rand.DefaultRandomGenerator;
import edu.brown.statistics.ObjectHistogram;
import edu.brown.utils.CompositeId;
import edu.brown.utils.StringUtil;
public class AuctionMarkClient extends BenchmarkComponent {
private static final Logger LOG = Logger.getLogger(AuctionMarkLoader.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
protected final AuctionMarkProfile profile;
/**
* TODO
*/
private final Map<UserId, Integer> seller_item_cnt = new HashMap<UserId, Integer>();
/**
* TODO
*/
private final List<long[]> pending_commentResponse = Collections.synchronizedList(new ArrayList<long[]>());
// --------------------------------------------------------------------
// TXN PARAMETER GENERATOR
// --------------------------------------------------------------------
public interface AuctionMarkParamGenerator {
/**
* Returns true if the client will be able to successfully generate a new transaction call
* The client passes in the current BenchmarkProfile handle and an optional VoltTable. This allows
* you to invoke one txn using the output of a previously run txn.
* Note that this is not thread safe, so you'll need to combine the call to this with generate()
* in a single synchronization block.
* @param client
* @return
*/
public boolean canGenerateParam(AuctionMarkClient client);
/**
* Generate the parameters array
* Any elements that are CompositeIds will automatically be encoded before being
* shipped off to the H-Store cluster
* @param client
* @return
*/
public Object[] generateParams(AuctionMarkClient client);
}
// --------------------------------------------------------------------
// BENCHMARK TRANSACTIONS
// --------------------------------------------------------------------
public enum Transaction {
// ====================================================================
// CLOSE_AUCTIONS
// ====================================================================
CLOSE_AUCTIONS(AuctionMarkConstants.FREQUENCY_CLOSE_AUCTIONS, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
return new Object[] { client.getTimestampParameterArray(),
client.profile.getLastCloseAuctionsTime(),
client.profile.updateAndGetLastCloseAuctionsTime() };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
if (AuctionMarkConstants.ENABLE_CLOSE_AUCTIONS && client.getClientId() == 0) {
// If we've never checked before, then we'll want to do that now
if (client.profile.hasLastCloseAuctionsTime() == false) return (true);
// Otherwise check whether enough time has passed since the last time we checked
TimestampType lastCheckWinningBidTime = client.profile.getLastCloseAuctionsTime();
TimestampType currentTime = client.profile.getCurrentTime();
long time_elapsed = Math.round((currentTime.getTime() - lastCheckWinningBidTime.getTime()) / 1000.0);
if (debug.val) LOG.debug(String.format("%s [start=%s, current=%s, elapsed=%d]", Transaction.CLOSE_AUCTIONS, client.profile.getBenchmarkStartTime(), currentTime, time_elapsed));
if (time_elapsed > AuctionMarkConstants.INTERVAL_CLOSE_AUCTIONS) return (true);
}
return (false);
}
}),
// ====================================================================
// GET_ITEM
// ====================================================================
GET_ITEM(AuctionMarkConstants.FREQUENCY_GET_ITEM, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
ItemInfo itemInfo = client.profile.getRandomAvailableItemId();
return new Object[] { client.getTimestampParameterArray(),
itemInfo.itemId, itemInfo.getSellerId() };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.profile.getAvailableItemsCount() > 0);
}
}),
// ====================================================================
// GET_USER_INFO
// ====================================================================
GET_USER_INFO(AuctionMarkConstants.FREQUENCY_GET_USER_INFO, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
UserId userId = client.profile.getRandomBuyerId();
int rand;
// USER_FEEDBACK records
rand = client.profile.rng.number(0, 100);
long get_feedback = (rand <= AuctionMarkConstants.PROB_GETUSERINFO_INCLUDE_FEEDBACK ? 1 : VoltType.NULL_BIGINT);
// ITEM_COMMENT records
rand = client.profile.rng.number(0, 100);
long get_comments = (rand <= AuctionMarkConstants.PROB_GETUSERINFO_INCLUDE_COMMENTS ? 1 : VoltType.NULL_BIGINT);
// Seller ITEM records
rand = 100; // client.profile.rng.number(0, 100);
long get_seller_items = (rand <= AuctionMarkConstants.PROB_GETUSERINFO_INCLUDE_SELLER_ITEMS ? 1 : VoltType.NULL_BIGINT);
// Buyer ITEM records
rand = 100; // client.profile.rng.number(0, 100);
long get_buyer_items = (rand <= AuctionMarkConstants.PROB_GETUSERINFO_INCLUDE_BUYER_ITEMS ? 1 : VoltType.NULL_BIGINT);
// USER_WATCH records
rand = 100; // client.profile.rng.number(0, 100);
long get_watched_items = (rand <= AuctionMarkConstants.PROB_GETUSERINFO_INCLUDE_WATCHED_ITEMS ? 1 : VoltType.NULL_BIGINT);
return new Object[] { client.getTimestampParameterArray(),
userId,
get_feedback, get_comments,
get_seller_items, get_buyer_items, get_watched_items };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (true);
}
}),
// ====================================================================
// NEW_BID
// ====================================================================
NEW_BID(AuctionMarkConstants.FREQUENCY_NEW_BID, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
ItemInfo itemInfo = null;
UserId sellerId;
UserId buyerId;
double bid;
double maxBid;
boolean has_available = (client.profile.getAvailableItemsCount() > 0);
boolean has_ending = (client.profile.getEndingSoonItemsCount() > 0);
boolean has_waiting = (client.profile.getWaitForPurchaseItemsCount() > 0);
boolean has_completed = (client.profile.getCompleteItemsCount() > 0);
// Some NEW_BIDs will be for items that have already ended.
// This will simulate somebody trying to bid at the very end but failing
if ((has_waiting || has_completed) &&
(client.profile.rng.number(1, 100) <= AuctionMarkConstants.PROB_NEWBID_CLOSED_ITEM || has_available == false)) {
if (has_waiting) {
itemInfo = client.profile.getRandomWaitForPurchaseItem();
assert(itemInfo != null) : "Failed to get WaitForPurchase itemInfo [" + client.profile.getWaitForPurchaseItemsCount() + "]";
} else {
itemInfo = client.profile.getRandomCompleteItem();
assert(itemInfo != null) : "Failed to get Completed itemInfo [" + client.profile.getCompleteItemsCount() + "]";
}
sellerId = itemInfo.getSellerId();
buyerId = client.profile.getRandomBuyerId(sellerId);
// The bid/maxBid do not matter because they won't be able to actually
// update the auction
bid = client.profile.rng.nextDouble();
maxBid = bid + 100;
}
// Otherwise we want to generate information for a real bid
else {
assert(has_available || has_ending);
// 50% of NEW_BIDS will be for items that are ending soon
if ((has_ending && client.profile.rng.number(1, 100) <= AuctionMarkConstants.PROB_NEWBID_CLOSED_ITEM) || has_available == false) {
itemInfo = client.profile.getRandomEndingSoonItem(true);
}
if (itemInfo == null) {
itemInfo = client.profile.getRandomAvailableItem(true);
}
if (itemInfo == null) {
itemInfo = client.profile.getRandomItem();
}
sellerId = itemInfo.getSellerId();
buyerId = client.profile.getRandomBuyerId(sellerId);
double currentPrice = itemInfo.getCurrentPrice();
bid = client.profile.rng.fixedPoint(2, currentPrice, currentPrice * (1 + (AuctionMarkConstants.ITEM_BID_PERCENT_STEP / 2)));
maxBid = client.profile.rng.fixedPoint(2, bid, (bid * (1 + (AuctionMarkConstants.ITEM_BID_PERCENT_STEP / 2))));
}
return new Object[] { client.getTimestampParameterArray(),
itemInfo.itemId, sellerId, buyerId, maxBid, itemInfo.endDate };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.profile.getAllItemsCount() > 0);
}
}),
// ====================================================================
// NEW_COMMENT
// ====================================================================
NEW_COMMENT(AuctionMarkConstants.FREQUENCY_NEW_COMMENT, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
ItemInfo itemInfo = client.profile.getRandomCompleteItem();
UserId sellerId = itemInfo.getSellerId();
UserId buyerId = client.profile.getRandomBuyerId(sellerId);
String question = client.profile.rng.astring(10, 128);
return new Object[] { client.getTimestampParameterArray(),
itemInfo.itemId, sellerId, buyerId, question };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.profile.getCompleteItemsCount() > 0);
}
}),
// ====================================================================
// NEW_COMMENT_RESPONSE
// ====================================================================
NEW_COMMENT_RESPONSE(AuctionMarkConstants.FREQUENCY_NEW_COMMENT_RESPONSE, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
Collections.shuffle(client.pending_commentResponse, client.profile.rng);
long row[] = client.pending_commentResponse.remove(0);
assert(row != null);
long commentId = row[0];
ItemId itemId = new ItemId(row[1]);
UserId sellerId = itemId.getSellerId();
String response = client.profile.rng.astring(10, 128);
return new Object[] { client.getTimestampParameterArray(),
itemId, sellerId, commentId, response };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.pending_commentResponse.isEmpty() == false);
}
}),
// ====================================================================
// NEW_FEEDBACK
// ====================================================================
NEW_FEEDBACK(AuctionMarkConstants.FREQUENCY_NEW_FEEDBACK, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
ItemInfo itemInfo = client.profile.getRandomCompleteItem();
UserId sellerId = itemInfo.getSellerId();
UserId buyerId = client.profile.getRandomBuyerId(sellerId);
long rating = (long) client.profile.rng.number(-1, 1);
String feedback = client.profile.rng.astring(10, 80);
return new Object[] { client.getTimestampParameterArray(),
itemInfo.itemId, sellerId, buyerId, rating, feedback };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.profile.getCompleteItemsCount() > 0);
}
}),
// ====================================================================
// NEW_ITEM
// ====================================================================
NEW_ITEM(AuctionMarkConstants.FREQUENCY_NEW_ITEM, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
UserId sellerId = client.profile.getRandomSellerId(client.getClientId());
ItemId itemId = client.getNextItemId(sellerId);
String name = client.profile.rng.astring(6, 32);
String description = client.profile.rng.astring(50, 255);
long categoryId = client.profile.getRandomCategoryId();
Double initial_price = (double) client.profile.randomInitialPrice.nextInt();
String attributes = client.profile.rng.astring(50, 255);
int numAttributes = client.profile.randomNumAttributes.nextInt();
List<GlobalAttributeValueId> gavList = new ArrayList<GlobalAttributeValueId>(numAttributes);
for (int i = 0; i < numAttributes; i++) {
GlobalAttributeValueId gav_id = client.profile.getRandomGlobalAttributeValue();
if (!gavList.contains(gav_id)) gavList.add(gav_id);
} // FOR
long[] gag_ids = new long[gavList.size()];
long[] gav_ids = new long[gavList.size()];
for (int i = 0, cnt = gag_ids.length; i < cnt; i++) {
GlobalAttributeValueId gav_id = gavList.get(i);
gag_ids[i] = gav_id.getGlobalAttributeGroup().encode();
gav_ids[i] = gav_id.encode();
} // FOR
int numImages = client.profile.randomNumImages.nextInt();
String[] images = new String[numImages];
for (int i = 0; i < numImages; i++) {
images[i] = client.profile.rng.astring(20, 100);
} // FOR
long duration = client.profile.randomDuration.nextInt();
return new Object[] { client.getTimestampParameterArray(),
itemId, sellerId, categoryId,
name, description, duration, initial_price, attributes,
gag_ids, gav_ids, images };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (true);
}
}),
// ====================================================================
// NEW_PURCHASE
// ====================================================================
NEW_PURCHASE(AuctionMarkConstants.FREQUENCY_NEW_PURCHASE, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
ItemInfo itemInfo = client.profile.getRandomWaitForPurchaseItem();
UserId sellerId = itemInfo.getSellerId();
double buyer_credit = 0d;
// Whether the buyer will not have enough money
if (itemInfo.hasCurrentPrice()) {
if (client.profile.rng.number(1, 100) < AuctionMarkConstants.PROB_NEW_PURCHASE_NOT_ENOUGH_MONEY) {
buyer_credit = -1 * itemInfo.getCurrentPrice();
} else {
buyer_credit = itemInfo.getCurrentPrice();
client.profile.removeWaitForPurchaseItem(itemInfo);
}
}
return new Object[] { client.getTimestampParameterArray(),
itemInfo.itemId, sellerId, buyer_credit };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.profile.getWaitForPurchaseItemsCount() > 0);
}
}),
// ====================================================================
// UPDATE_ITEM
// ====================================================================
UPDATE_ITEM(AuctionMarkConstants.FREQUENCY_UPDATE_ITEM, new AuctionMarkParamGenerator() {
@Override
public Object[] generateParams(AuctionMarkClient client) {
ItemInfo itemInfo = client.profile.getRandomAvailableItemId();
UserId sellerId = itemInfo.getSellerId();
String description = client.profile.rng.astring(50, 255);
long delete_attribute = VoltType.NULL_BIGINT;
long add_attribute[] = {
VoltType.NULL_BIGINT,
VoltType.NULL_BIGINT
};
// Delete ITEM_ATTRIBUTE
if (client.profile.rng.number(1, 100) < AuctionMarkConstants.PROB_UPDATEITEM_DELETE_ATTRIBUTE) {
delete_attribute = 1;
}
// Add ITEM_ATTRIBUTE
else if (client.profile.rng.number(1, 100) < AuctionMarkConstants.PROB_UPDATEITEM_ADD_ATTRIBUTE) {
GlobalAttributeValueId gav_id = client.profile.getRandomGlobalAttributeValue();
assert(gav_id != null);
add_attribute[0] = gav_id.getGlobalAttributeGroup().encode();
add_attribute[1] = gav_id.encode();
}
return new Object[] { client.getTimestampParameterArray(),
itemInfo.itemId, sellerId, description,
delete_attribute, add_attribute };
}
@Override
public boolean canGenerateParam(AuctionMarkClient client) {
return (client.profile.getAvailableItemsCount() > 0);
}
}),
;
/**
* Constructor
* @param weight The execution frequency weight for this txn
* @param generator
*/
private Transaction(int weight, AuctionMarkParamGenerator generator) {
this.default_weight = weight;
this.displayName = StringUtil.title(this.name().replace("_", " "));
this.callName = this.displayName.replace(" ", "");
this.generator = generator;
}
public final int default_weight;
public final String displayName;
public final String callName;
public final AuctionMarkParamGenerator generator;
protected static final Map<Integer, Transaction> idx_lookup = new HashMap<Integer, Transaction>();
protected static final Map<String, Transaction> name_lookup = new HashMap<String, Transaction>();
static {
for (Transaction vt : EnumSet.allOf(Transaction.class)) {
Transaction.idx_lookup.put(vt.ordinal(), vt);
Transaction.name_lookup.put(vt.name().toLowerCase().intern(), vt);
}
}
public static Transaction get(Integer idx) {
assert(idx >= 0);
return (Transaction.idx_lookup.get(idx));
}
public static Transaction get(String name) {
return (Transaction.name_lookup.get(name.toLowerCase().intern()));
}
public int getDefaultWeight() {
return (this.default_weight);
}
public String getDisplayName() {
return (this.displayName);
}
public String getCallName() {
return (this.callName);
}
/**
* This will return true if we can call a new transaction for this procedure
* A txn can be called if we can generate all of the parameters we need
* @return
*/
public boolean canExecute(AuctionMarkClient client) {
if (debug.val) LOG.debug("Checking whether we can execute " + this + " now");
return this.generator.canGenerateParam(client);
}
/**
* Given a BenchmarkProfile object, call the AuctionMarkParamGenerator object for a given
* transaction type to generate a set of parameters for a new txn invocation
* @param profile
* @return
*/
public Object[] generateParams(AuctionMarkClient client) {
Object vals[] = this.generator.generateParams(client);
// Automatically encode any CompositeIds
for (int i = 0; i < vals.length; i++) {
if (vals[i] instanceof CompositeId) vals[i] = ((CompositeId)vals[i]).encode();
} // FOR
return (vals);
}
}
// -----------------------------------------------------------------
// ADDITIONAL DATA MEMBERS
// -----------------------------------------------------------------
private final Map<Transaction, Integer> weights = new HashMap<Transaction, Integer>();
private final Transaction xacts[] = new Transaction[100];
// -----------------------------------------------------------------
// REQUIRED METHODS
// -----------------------------------------------------------------
public static void main(String args[]) {
edu.brown.api.BenchmarkComponent.main(AuctionMarkClient.class, args, false);
}
/**
* Constructor
* @param args
*/
public AuctionMarkClient(String[] args) {
super(args);
int seed = 0;
String randGenClassName = DefaultRandomGenerator.class.getName();
String randGenProfilePath = null;
Integer temporal_window = null;
Integer temporal_total = null;
for (String key : m_extraParams.keySet()) {
String value = m_extraParams.get(key);
// Random Generator Seed
if (key.equalsIgnoreCase("RANDOMSEED")) {
seed = Integer.parseInt(value);
}
// Random Generator Class
else if (key.equalsIgnoreCase("RANDOMGENERATOR")) {
randGenClassName = value;
}
// Random Generator Profile File
else if (key.equalsIgnoreCase("RANDOMPROFILE")) {
randGenProfilePath = value;
}
// Temporal Skew
else if (key.equalsIgnoreCase("TEMPORALWINDOW")) {
assert(m_extraParams.containsKey("TEMPORALTOTAL")) : "Missing TEMPORALTOTAL parameter";
temporal_window = Integer.valueOf(m_extraParams.get("TEMPORALWINDOW"));
temporal_total = Integer.valueOf(m_extraParams.get("TEMPORALTOTAL"));
}
} // FOR
// Random Generator
AbstractRandomGenerator rng = null;
try {
rng = AbstractRandomGenerator.factory(randGenClassName, seed);
if (randGenProfilePath != null) rng.loadProfile(randGenProfilePath);
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
// BenchmarkProfile
profile = new AuctionMarkProfile(rng, getNumClients());
profile.loadProfile(this);
// Initialize Default Weights
for (Transaction t : Transaction.values()) {
Integer weight = this.getTransactionWeight(t.callName);
this.weights.put(t, (weight != null ? weight : t.getDefaultWeight()));
} // FOR
// Create xact lookup array
int total = 0;
for (Transaction t : Transaction.values()) {
for (int i = 0, cnt = this.weights.get(t); i < cnt; i++) {
if (trace.val)
LOG.trace("xact " + total + " = " + t + ":" + t.getCallName());
this.xacts[total++] = t;
} // FOR
} // FOR
assert(total == xacts.length) : "The total weight for the transactions is " + total + ". It needs to be " + xacts.length;
}
@Override
public String[] getTransactionDisplayNames() {
String names[] = new String[Transaction.values().length];
for (Transaction t : Transaction.values()) {
names[t.ordinal()] = t.getDisplayName();
}
return names;
}
@Override
public void runLoop() {
final Client client = this.getClientHandle();
// Execute Transactions
try {
while (true) {
runOnce();
client.backpressureBarrier();
} // WHILE
} catch (InterruptedException e) {
e.printStackTrace();
return;
} catch (NoConnectionsException e) {
/*
* Client has no clean mechanism for terminating with the DB.
*/
return;
} catch (IOException e) {
/*
* At shutdown an IOException is thrown for every connection to
* the DB that is lost Ignore the exception here in order to not
* get spammed, but will miss lost connections at runtime
*/
}
}
@Override
public void tickCallback(int counter) {
super.tickCallback(counter);
profile.tick();
}
@Override
protected boolean runOnce() throws IOException {
// We need to subtract the different between this and the profile's start time,
// since that accounts for the time gap between when the loader started and when the client start.
// Otherwise, all of our cache date will be out dated if it took a really long time
// to load everything up. Again, in order to keep things in synch, we only want to
// set this on the first call to runOnce(). This will account for start a bunch of
// clients on multiple nodes but then having to wait until they're all up and running
// before starting the actual benchmark run.
if (profile.hasClientStartTime() == false) profile.setAndGetClientStartTime();
Transaction txn = null;
Object[] params = null;
// Always update the current timestamp
profile.updateAndGetCurrentTime();
// Find the next txn and its parameters that we will run. We want to wrap this
// around a synchronization block so that nobody comes in and takes the parameters
// from us before we actually run it
int safety = 1000;
while (safety-- > 0) {
Transaction tempTxn = null;
// Always check if we need to want to run CLOSE_AUCTIONS
// We only do this from the first client
if (Transaction.CLOSE_AUCTIONS.canExecute(this)) {
tempTxn = Transaction.CLOSE_AUCTIONS;
}
// Otherwise randomly pick a transaction based on their distribution weights
else {
int idx = profile.rng.number(0, this.xacts.length - 1);
if (trace.val) {
LOG.trace("idx = " + idx);
LOG.trace("random txn = " + this.xacts[idx].getDisplayName());
}
assert (idx >= 0);
assert (idx < this.xacts.length);
tempTxn = this.xacts[idx];
}
// Only execute this txn if it is ready
// Example: NewBid can only be executed if there are item_ids retrieved by an earlier call by GetItem
if (tempTxn.canExecute(this)) {
txn = tempTxn;
if (trace.val) LOG.trace("CAN EXECUTE: " + txn);
try {
params = txn.generateParams(this);
} catch (Throwable ex) {
throw new RuntimeException("Failed to generate parameters for " + txn, ex);
}
break;
}
} // WHILE
assert (txn != null);
if (params == null) {
LOG.warn("Unable to execute " + txn + " because the parameters were null?");
return (false);
} else if (debug.val) {
LOG.info("Executing new invocation of transaction " + txn);
}
BaseCallback callback = null;
switch (txn) {
case CLOSE_AUCTIONS:
callback = new CloseAuctionsCallback(params);
break;
case GET_ITEM:
callback = new GetItemCallback(params);
break;
case GET_USER_INFO:
callback = new GetUserInfoCallback(params);
break;
case NEW_COMMENT:
callback = new NewCommentCallback(params);
break;
case NEW_ITEM:
callback = new NewItemCallback(params);
break;
case NEW_PURCHASE:
callback = new NewPurchaseCallback(params);
break;
default:
callback = new NullCallback(txn, params);
} // SWITCH
this.getClientHandle().callProcedure(callback, txn.getCallName(), params);
return (true);
}
/**********************************************************************************************
* Base Callback
**********************************************************************************************/
protected abstract class BaseCallback implements ProcedureCallback {
final Transaction txn;
final Object params[];
final ObjectHistogram<ItemStatus> updated = new ObjectHistogram<ItemStatus>();
public BaseCallback(Transaction txn, Object params[]) {
this.txn = txn;
this.params = params;
}
public abstract void process(VoltTable results[]);
@Override
public void clientCallback(ClientResponse clientResponse) {
if (trace.val) LOG.trace("clientCallback(cid = " + getClientId() + "):: txn = " + txn.getDisplayName());
incrementTransactionCounter(clientResponse, this.txn.ordinal());
VoltTable[] results = clientResponse.getResults();
if (clientResponse.getStatus() == Status.OK) {
try {
this.process(results);
} catch (Throwable ex) {
LOG.error("PARAMS: " + Arrays.toString(this.params));
for (int i = 0; i < results.length; i++) {
LOG.info(String.format("[%02d] RESULT\n%s", i, results[i]));
} // FOR
throw new RuntimeException("Failed to process results for " + this.txn, ex);
}
} else {
if (debug.val) LOG.debug(String.format("%s: %s", this.txn, clientResponse.getStatusString()), clientResponse.getException());
}
}
/**
* For the given VoltTable that contains ITEM records, process the current
* row of that table and update the benchmark profile based on item information
* stored in that row.
* @param vt
* @return
*/
public ItemId processItemRecord(VoltTable vt) {
ItemId itemId = new ItemId(vt.getLong("i_id"));
TimestampType endDate = vt.getTimestampAsTimestamp("i_end_date");
short numBids = (short)vt.getLong("i_num_bids");
double currentPrice = vt.getDouble("i_current_price");
ItemInfo itemInfo = new ItemInfo(itemId, currentPrice, endDate, numBids);
if (vt.hasColumn("ip_id")) itemInfo.status = ItemStatus.CLOSED;
if (vt.hasColumn("i_status")) itemInfo.status = ItemStatus.get(vt.getLong("i_status"));
UserId sellerId = new UserId(vt.getLong("i_u_id"));
assert (itemId.getSellerId().equals(sellerId));
ItemStatus qtype = profile.addItemToProperQueue(itemInfo, false);
this.updated.put(qtype);
return (itemId);
}
@Override
public String toString() {
String cnts[] = new String[ItemStatus.values().length];
for (ItemStatus qtype : ItemStatus.values()) {
cnts[qtype.ordinal()] = String.format("%s=+%d", qtype, updated.get(qtype, 0));
}
return String.format("%s :: %s", this.txn, StringUtil.join(", ", cnts));
}
} // END CLASS
/**********************************************************************************************
* NULL Callback
**********************************************************************************************/
protected class NullCallback extends BaseCallback {
public NullCallback(Transaction txn, Object params[]) {
super(txn, params);
}
@Override
public void process(VoltTable[] results) {
// Nothing to do...
}
} // END CLASS
/**********************************************************************************************
* CLOSE_AUCTIONS Callback
**********************************************************************************************/
protected class CloseAuctionsCallback extends BaseCallback {
public CloseAuctionsCallback(Object params[]) {
super(Transaction.CLOSE_AUCTIONS, params);
}
@Override
public void process(VoltTable[] results) {
assert (null != results && results.length > 0);
while (results[0].advanceRow()) {
ItemId itemId = this.processItemRecord(results[0]);
assert(itemId != null);
} // WHILE
if (debug.val) LOG.debug(super.toString());
profile.updateItemQueues();
}
} // END CLASS
/**********************************************************************************************
* NEW_COMMENT Callback
**********************************************************************************************/
protected class NewCommentCallback extends BaseCallback {
public NewCommentCallback(Object params[]) {
super(Transaction.NEW_COMMENT, params);
}
@Override
public void process(VoltTable[] results) {
assert(results.length == 1);
while (results[0].advanceRow()) {
long vals[] = {
results[0].getLong("ic_id"),
results[0].getLong("ic_i_id"),
results[0].getLong("ic_u_id")
};
pending_commentResponse.add(vals);
} // WHILE
}
} // END CLASS
/**********************************************************************************************
* GET_ITEM Callback
**********************************************************************************************/
protected class GetItemCallback extends BaseCallback {
public GetItemCallback(Object params[]) {
super(Transaction.GET_ITEM, params);
}
@Override
public void process(VoltTable[] results) {
assert (null != results && results.length > 0);
while (results[0].advanceRow()) {
ItemId itemId = this.processItemRecord(results[0]);
assert(itemId != null);
} // WHILE
if (debug.val) LOG.debug(super.toString());
}
} // END CLASS
/**********************************************************************************************
* GET_USER_INFO Callback
**********************************************************************************************/
protected class GetUserInfoCallback extends BaseCallback {
final boolean expect_user;
final boolean expect_feedback;
final boolean expect_comments;
final boolean expect_seller;
final boolean expect_buyer;
final boolean expect_watched;
public GetUserInfoCallback(Object params[]) {
super(Transaction.GET_USER_INFO, params);
int idx = 2;
this.expect_user = true;
this.expect_feedback = ((Long)params[idx++] != VoltType.NULL_BIGINT);
this.expect_comments = ((Long)params[idx++] != VoltType.NULL_BIGINT);
this.expect_seller = ((Long)params[idx++] != VoltType.NULL_BIGINT);
this.expect_buyer = ((Long)params[idx++] != VoltType.NULL_BIGINT);
this.expect_watched = ((Long)params[idx++] != VoltType.NULL_BIGINT);
}
@Override
public void process(VoltTable[] results) {
int idx = 0;
// USER
if (expect_user) {
VoltTable vt = results[idx++];
assert(vt != null);
assert(vt.getRowCount() > 0);
}
// USER_FEEDBACK
if (expect_feedback) {
VoltTable vt = results[idx++];
assert(vt != null);
}
// ITEM_COMMENT
if (expect_comments) {
VoltTable vt = results[idx++];
assert(vt != null);
while (vt.advanceRow()) {
long vals[] = {
vt.getLong("ic_id"),
vt.getLong("ic_i_id"),
vt.getLong("ic_u_id")
};
pending_commentResponse.add(vals);
} // WHILE
}
// ITEM Result Tables
for (int i = idx; i < results.length; i++) {
VoltTable vt = results[i];
assert(vt != null);
while (vt.advanceRow()) {
ItemId itemId = this.processItemRecord(vt);
assert(itemId != null);
} // WHILE
} // FOR
}
} // END CLASS
/**********************************************************************************************
* NEW_BID Callback
**********************************************************************************************/
protected class NewBidCallback extends BaseCallback {
public NewBidCallback(Object params[]) {
super(Transaction.NEW_BID, params);
}
@Override
public void process(VoltTable[] results) {
assert(results.length == 1);
while (results[0].advanceRow()) {
ItemId itemId = this.processItemRecord(results[0]);
assert(itemId != null);
} // WHILE
if (debug.val) LOG.debug(super.toString());
}
} // END CLASS
/**********************************************************************************************
* NEW_ITEM Callback
**********************************************************************************************/
protected class NewItemCallback extends BaseCallback {
public NewItemCallback(Object params[]) {
super(Transaction.NEW_ITEM, params);
}
@Override
public void process(VoltTable[] results) {
assert(results.length == 1);
while (results[0].advanceRow()) {
ItemId itemId = this.processItemRecord(results[0]);
assert(itemId != null);
} // WHILE
if (debug.val) LOG.debug(super.toString());
}
} // END CLASS
/**********************************************************************************************
* NEW_PURCHASE Callback
**********************************************************************************************/
protected class NewPurchaseCallback extends BaseCallback {
public NewPurchaseCallback(Object params[]) {
super(Transaction.NEW_PURCHASE, params);
}
@Override
public void process(VoltTable[] results) {
assert(results.length == 1);
while (results[0].advanceRow()) {
ItemId itemId = this.processItemRecord(results[0]);
assert(itemId != null);
} // WHILE
if (debug.val) LOG.debug(super.toString());
}
} // END CLASS
public ItemId getNextItemId(UserId seller_id) {
Integer cnt = this.seller_item_cnt.get(seller_id);
if (cnt == null || cnt == 0) {
cnt = (int)seller_id.getItemCount();
}
this.seller_item_cnt.put(seller_id, ++cnt);
return (new ItemId(seller_id, cnt));
}
public TimestampType[] getTimestampParameterArray() {
return new TimestampType[] { profile.getBenchmarkStartTime(),
profile.getClientStartTime() };
}
}