/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* This file contains original code and/or modifications of original code.
* Any modifications made by VoltDB L.L.C. are licensed under the following
* terms and conditions:
*
* 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.
*/
/* Copyright (C) 2008
* Evan Jones
* Massachusetts Institute of Technology
*
* 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 org.voltdb.benchmark.tpcc;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.collections15.map.ListOrderedMap;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.benchmark.Clock;
import org.voltdb.catalog.Partition;
import org.voltdb.catalog.Site;
import org.voltdb.types.TimestampType;
import edu.brown.hashing.DefaultHasher;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.rand.RandomDistribution;
import edu.brown.statistics.FastIntHistogram;
import edu.brown.statistics.Histogram;
import edu.brown.utils.StringUtil;
public class TPCCSimulation {
private static final Logger LOG = Logger.getLogger(TPCCSimulation.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
public interface ProcCaller {
public void callResetWarehouse(long w_id, long districtsPerWarehouse,
long customersPerDistrict, long newOrdersPerDistrict)
throws IOException;
public void callStockLevel(short w_id, byte d_id, int threshold) throws IOException;
public void callOrderStatus(String proc, Object... paramlist) throws IOException;
public void callDelivery(short w_id, int carrier, TimestampType date) throws IOException;
public void callPaymentByName(short w_id, byte d_id, double h_amount,
short c_w_id, byte c_d_id, String c_last, TimestampType now) throws IOException;
public void callPaymentById(short w_id, byte d_id, double h_amount,
short c_w_id, byte c_d_id, int c_id, TimestampType now)
throws IOException;
public void callNewOrder(boolean rollback, boolean noop, Object... paramlist) throws IOException;
}
private final TPCCSimulation.ProcCaller client;
private final RandomGenerator generator;
private final Clock clock;
public ScaleParameters parameters;
private final long affineWarehouse;
private final double skewFactor;
private final TPCCConfig config;
/**
* W_ID -> List of W_IDs on Remote Sites
*/
public static HashMap <Integer, List<Integer>> remoteWarehouseIds = null;
protected static long lastAssignedWarehouseId = 1;
private RandomDistribution.HotWarmCold custom_skew;
private RandomDistribution.Zipf zipf;
private int tick_counter = 0;
private int temporal_counter = 0;
private final FastIntHistogram lastWarehouseHistory = new FastIntHistogram(true);
private final FastIntHistogram totalWarehouseHistory = new FastIntHistogram(true);
public TPCCSimulation(TPCCSimulation.ProcCaller client, RandomGenerator generator,
Clock clock, ScaleParameters parameters, TPCCConfig config, double skewFactor,
CatalogContext catalogContext) {
assert parameters != null;
this.client = client;
this.generator = generator;
this.clock = clock;
this.parameters = parameters;
this.affineWarehouse = lastAssignedWarehouseId;
this.skewFactor = skewFactor;
this.config = config;
if (config.neworder_skew_warehouse) {
if (debug.val) LOG.debug("Enabling W_ID Zipfian Skew: " + skewFactor);
this.zipf = new RandomDistribution.Zipf(new Random(),
parameters.starting_warehouse,
parameters.last_warehouse+1,
Math.max(1.001d, this.skewFactor));
this.custom_skew = new RandomDistribution.HotWarmCold(new Random(),
parameters.starting_warehouse+1,
parameters.last_warehouse,
TPCCConstants.HOT_DATA_WORKLOAD_SKEW, TPCCConstants.HOT_DATA_SIZE,
TPCCConstants.WARM_DATA_WORKLOAD_SKEW, TPCCConstants.WARM_DATA_SIZE);
}
if (config.warehouse_debug) {
LOG.info("Enabling WAREHOUSE debug mode");
}
lastAssignedWarehouseId += 1;
if (lastAssignedWarehouseId > parameters.last_warehouse)
lastAssignedWarehouseId = 1;
if (debug.val) {
LOG.debug(this.toString());
}
if (config.neworder_multip_remote) {
synchronized (TPCCSimulation.class) {
if (remoteWarehouseIds == null) {
remoteWarehouseIds = new HashMap<Integer, List<Integer>>();
HashMap <Integer, Integer> partitionToSite = new HashMap<Integer, Integer>();
DefaultHasher hasher = new DefaultHasher(catalogContext);
for (Site s: catalogContext.sites) {
for (Partition p: s.getPartitions())
partitionToSite.put(p.getId(), s.getId());
} // FOR
for (int w_id0 = parameters.starting_warehouse; w_id0 <= parameters.last_warehouse; w_id0++) {
final int partition0 = hasher.hash(w_id0);
final int site0 = partitionToSite.get(partition0);
final List<Integer> rList = new ArrayList<Integer>();
for (int w_id1 = parameters.starting_warehouse; w_id1 <= parameters.last_warehouse; w_id1++) {
// Figure out what partition this W_ID maps to
int partition1 = hasher.hash(w_id1);
// Check whether this partition is on our same local site
int site1 = partitionToSite.get(partition1);
if (site0 != site1) rList.add(w_id1);
} // FOR
remoteWarehouseIds.put(w_id0, rList);
} // FOR
LOG.debug("NewOrder Remote W_ID Mapping\n" + StringUtil.formatMaps(remoteWarehouseIds));
}
} // SYNCH
}
}
protected Random rng() {
return generator.rng();
}
@Override
public String toString() {
Map<String, Object> m = new ListOrderedMap<String, Object>();
m.put("Warehouses", parameters.warehouses);
m.put("W_ID Range", String.format("[%d, %d]", parameters.starting_warehouse, parameters.last_warehouse));
m.put("Districts per Warehouse", parameters.districtsPerWarehouse);
m.put("Custers per District", parameters.customersPerDistrict);
m.put("Initial Orders per District", parameters.newOrdersPerDistrict);
m.put("Items", parameters.num_items);
m.put("Affine Warehouse", lastAssignedWarehouseId);
m.put("Skew Factor", this.skewFactor);
if (this.zipf != null && this.zipf.isHistoryEnabled()) {
m.put("Skewed Warehouses", this.zipf.getHistory());
}
return ("TPCC Simulator Options\n" + StringUtil.formatMaps(m, this.config.debugMap()));
}
protected RandomDistribution.Zipf getWarehouseZipf() {
return (this.zipf);
}
public synchronized void tick(int counter) {
this.tick_counter = counter;
if (config.warehouse_debug) {
Map<String, Histogram<Integer>> m = new ListOrderedMap<String, Histogram<Integer>>();
m.put(String.format("LAST ROUND\n - SampleCount=%d", this.lastWarehouseHistory.getSampleCount()),
this.lastWarehouseHistory);
m.put(String.format("TOTAL\n - SampleCount=%d", this.totalWarehouseHistory.getSampleCount()),
this.totalWarehouseHistory);
long total = this.totalWarehouseHistory.getSampleCount();
LOG.info(String.format("ROUND #%02d - Warehouse Temporal Skew - %d / %d [%.2f]\n%s",
this.tick_counter, this.temporal_counter, total, (this.temporal_counter / (double)total),
StringUtil.formatMaps(m)));
LOG.info(StringUtil.SINGLE_LINE);
this.lastWarehouseHistory.clearValues();
}
}
private short generateWarehouseId() {
short w_id = -1;
// WAREHOUSE AFFINITY
if (config.warehouse_affinity) {
w_id = (short)this.affineWarehouse;
}
// TEMPORAL SKEW
else if (config.temporal_skew) {
if (generator.number(1, 100) <= config.temporal_skew_mix) {
if (config.temporal_skew_rotate) {
w_id = (short)((this.tick_counter % parameters.warehouses) + parameters.starting_warehouse);
} else {
w_id = (short)config.first_warehouse;
}
this.temporal_counter++;
} else {
w_id = (short)generator.number(parameters.starting_warehouse, parameters.last_warehouse);
}
}
// ZIPFIAN SKEWED WAREHOUSE ID
else if (config.neworder_skew_warehouse) {
assert(this.zipf != null);
//w_id = (short)this.zipf.nextInt();
if (generator.number(1, 100) <= config.temporal_skew_mix) {
w_id = (short)this.custom_skew.nextInt();
} else {
w_id = (short)generator.number(parameters.starting_warehouse, parameters.last_warehouse);
}
}
// GAUSSIAN SKEWED WAREHOUSE ID
else if (skewFactor > 0.0d) {
w_id = (short)generator.skewedNumber(parameters.starting_warehouse, parameters.last_warehouse, skewFactor);
}
// UNIFORM DISTRIBUTION
else {
w_id = (short)generator.number(parameters.starting_warehouse, parameters.last_warehouse);
}
assert(w_id >= parameters.starting_warehouse) : String.format("Invalid W_ID: %d [min=%d, max=%d]", w_id, parameters.starting_warehouse, parameters.last_warehouse);
assert(w_id <= parameters.last_warehouse) : String.format("Invalid W_ID: %d [min=%d, max=%d]", w_id, parameters.starting_warehouse, parameters.last_warehouse);
this.lastWarehouseHistory.put(w_id);
this.totalWarehouseHistory.put(w_id);
return w_id;
}
// ----------------------------------------------------------------------------
// REMOTE WAREHOUSE SELECTION METHODS
// ----------------------------------------------------------------------------
public static short generatePairedWarehouse(int w_id, int starting_warehouse, int last_warehouse) {
int remote_w_id = (w_id % 2 == 0 ? w_id-1 : w_id+1);
if (remote_w_id < starting_warehouse) remote_w_id = last_warehouse;
else if (remote_w_id > last_warehouse) remote_w_id = starting_warehouse;
return (short)remote_w_id;
}
// ----------------------------------------------------------------------------
// UTILITY METHODS
// ----------------------------------------------------------------------------
private byte generateDistrict() {
return (byte)generator.number(1, parameters.districtsPerWarehouse);
}
private int generateCID() {
return generator.NURand(1023, 1, parameters.customersPerDistrict);
}
private int generateItemID() {
return generator.NURand(8191, 1, parameters.num_items);
}
/** Executes a reset warehouse transaction. */
public void doResetWarehouse() throws IOException {
long w_id = generateWarehouseId();
client.callResetWarehouse(w_id, parameters.districtsPerWarehouse,
parameters.customersPerDistrict, parameters.newOrdersPerDistrict);
}
/** Executes a stock level transaction. */
public void doStockLevel() throws IOException {
int threshold = generator.number(TPCCConstants.MIN_STOCK_LEVEL_THRESHOLD,
TPCCConstants.MAX_STOCK_LEVEL_THRESHOLD);
client.callStockLevel(generateWarehouseId(), generateDistrict(), threshold);
}
/** Executes an order status transaction. */
public void doOrderStatus() throws IOException {
int y = generator.number(1, 100);
if (y <= 60) {
// 60%: order status by last name
String cLast = generator
.makeRandomLastName(parameters.customersPerDistrict);
client.callOrderStatus(TPCCConstants.ORDER_STATUS_BY_NAME,
generateWarehouseId(), generateDistrict(), cLast);
} else {
// 40%: order status by id
assert y > 60;
client.callOrderStatus(TPCCConstants.ORDER_STATUS_BY_ID,
generateWarehouseId(), generateDistrict(), generateCID());
}
}
/** Executes a delivery transaction. */
public void doDelivery() throws IOException {
int carrier = generator.number(TPCCConstants.MIN_CARRIER_ID,
TPCCConstants.MAX_CARRIER_ID);
client.callDelivery(generateWarehouseId(), carrier, clock.getDateTime());
}
/** Executes a payment transaction. */
public void doPayment() throws IOException {
boolean allow_remote = (parameters.warehouses > 1 && config.payment_multip != false);
double remote_prob = (config.payment_multip_mix >= 0 ? config.payment_multip_mix : 15) * 10d;
short w_id = generateWarehouseId();
byte d_id = generateDistrict();
short c_w_id;
byte c_d_id;
if (allow_remote == false || generator.number(1, 1000) <= (1000-remote_prob)) {
// 85%: paying through own warehouse (or there is only 1 warehouse)
c_w_id = w_id;
c_d_id = d_id;
} else {
// 15%: paying through another warehouse:
if (config.warehouse_pairing) {
c_w_id = generatePairedWarehouse(w_id, parameters.starting_warehouse, parameters.last_warehouse);
}
else if (config.payment_multip_remote) {
c_w_id = (short)generator.numberRemoteWarehouseId(parameters.starting_warehouse, parameters.last_warehouse, (int)w_id);
} else {
// select in range [1, num_warehouses] excluding w_id
c_w_id = (short)generator.numberExcluding(parameters.starting_warehouse, parameters.last_warehouse, w_id);
}
assert c_w_id != w_id;
c_d_id = generateDistrict();
}
double h_amount = generator.fixedPoint(2, TPCCConstants.MIN_PAYMENT,
TPCCConstants.MAX_PAYMENT);
TimestampType now = clock.getDateTime();
if (generator.number(1, 100) <= 60) {
// 60%: payment by last name
String c_last = generator.makeRandomLastName(parameters.customersPerDistrict);
client.callPaymentByName(w_id, d_id, h_amount, c_w_id, c_d_id, c_last, now);
} else {
// 40%: payment by id
client.callPaymentById(w_id, d_id, h_amount, c_w_id, c_d_id,
generateCID(), now);
}
}
/** Executes a new order transaction. */
public void doNewOrder() throws IOException {
short warehouse_id = generateWarehouseId();
int ol_cnt = generator.number(TPCCConstants.MIN_OL_CNT, TPCCConstants.MAX_OL_CNT);
// % of transactions that roll back
boolean rollback = (generator.number(1, 100) < config.neworder_abort);
int local_warehouses = 0;
int remote_warehouses = 0;
int[] item_id = new int[ol_cnt];
short[] supply_w_id = new short[ol_cnt];
int[] quantity = new int[ol_cnt];
for (int i = 0; i < ol_cnt; ++i) {
if (rollback && i + 1 == ol_cnt) {
// LOG.fine("[NOT_ERROR] Causing a rollback on purpose defined in TPCC spec. "
// + "You can ignore following 'ItemNotFound' exception.");
item_id[i] = parameters.num_items + 1;
} else {
item_id[i] = generateItemID();
}
// 1% of items are from a remote warehouse
boolean remote = config.neworder_multip && (generator.number(1, 100) == 1);
if (parameters.warehouses > 1 && remote) {
short remote_w_id;
if (config.warehouse_pairing) {
remote_w_id = generatePairedWarehouse(warehouse_id, parameters.starting_warehouse, parameters.last_warehouse);
}
else if (config.neworder_multip_remote) {
remote_w_id = (short)generator.numberRemoteWarehouseId(parameters.starting_warehouse, parameters.last_warehouse, (int) warehouse_id);
}
else {
remote_w_id = (short)generator.numberExcluding(parameters.starting_warehouse, parameters.last_warehouse, (int) warehouse_id);
}
supply_w_id[i] = remote_w_id;
if (supply_w_id[i] != warehouse_id) remote_warehouses++;
else local_warehouses++;
} else {
supply_w_id[i] = warehouse_id;
local_warehouses++;
}
quantity[i] = generator.number(1, TPCCConstants.MAX_OL_QUANTITY);
}
// Whether to force this transaction to be multi-partitioned
if (parameters.warehouses > 1 && config.neworder_multip == true && config.neworder_multip_mix > 0) {
// First force the entire thing to be single-partitioned
for (int idx = 0; idx < ol_cnt; idx++) {
supply_w_id[idx] = warehouse_id;
} // FOR
// Then check whether we should flip a random SUPPLY_W_ID to be remote
if (generator.number(1, 1000) <= (config.neworder_multip_mix*100)) {
if (trace.val) LOG.trace("Forcing Multi-Partition NewOrder Transaction");
// Flip a random one
int idx = generator.number(0, ol_cnt-1);
short remote_w_id;
if (config.warehouse_pairing) {
remote_w_id = generatePairedWarehouse(warehouse_id, parameters.starting_warehouse, parameters.last_warehouse);
}
else if (config.neworder_multip_remote) {
remote_w_id = (short)generator.numberRemoteWarehouseId(parameters.starting_warehouse, parameters.last_warehouse, (int) warehouse_id);
} else {
remote_w_id = (short)generator.numberExcluding(parameters.starting_warehouse, parameters.last_warehouse, (int) warehouse_id);
}
supply_w_id[idx] = remote_w_id;
if (supply_w_id[idx] != warehouse_id) remote_warehouses++;
else local_warehouses++;
}
}
// Prevent aborts
if (rollback && (
(remote_warehouses > 0 && config.neworder_abort_no_multip) ||
(remote_warehouses == 0 && config.neworder_abort_no_singlep))
) {
item_id[ol_cnt-1] = generateItemID();
}
if (trace.val)
LOG.trace("newOrder(W_ID=" + warehouse_id + ") -> [" +
"local_warehouses=" + local_warehouses + ", " +
"remote_warehouses=" + remote_warehouses + "]");
TimestampType now = clock.getDateTime();
client.callNewOrder(rollback, config.noop, warehouse_id, generateDistrict(), generateCID(),
now, item_id, supply_w_id, quantity);
}
/**
* Selects and executes a transaction at random. The number of new order
* transactions executed per minute is the official "tpmC" metric. See TPC-C
* 5.4.2 (page 71).
*
* @return the transaction that was executed..
*/
public int doOne(TPCCClient.Transaction t) throws IOException {
// This is not strictly accurate: The requirement is for certain
// *minimum* percentages to be maintained. This is close to the right
// thing, but not precisely correct. See TPC-C 5.2.4 (page 68).
if (config.noop || config.neworder_only) {
doNewOrder();
return TPCCClient.Transaction.NEW_ORDER.ordinal();
}
switch (t) {
case STOCK_LEVEL:
doStockLevel();
break;
case DELIVERY:
doDelivery();
break;
case ORDER_STATUS:
doOrderStatus();
break;
case PAYMENT:
doPayment();
break;
case NEW_ORDER:
doNewOrder();
break;
default:
assert(false) : "Unexpected transaction " + t;
}
return (t.ordinal());
}
}