/*************************************************************************** * Copyright (C) 2013 by H-Store Project * * Brown University * * Massachusetts Institute of Technology * * Yale University * * * * 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.smallbank; import java.io.IOException; import java.util.Arrays; import java.util.Random; import org.apache.log4j.Logger; import org.voltdb.CatalogContext; import org.voltdb.TheHashinator; import org.voltdb.client.ClientResponse; import org.voltdb.client.ProcedureCallback; import edu.brown.api.BenchmarkComponent; import edu.brown.logging.LoggerUtil; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.rand.RandomDistribution.FlatHistogram; import edu.brown.statistics.Histogram; import edu.brown.statistics.ObjectHistogram; import edu.brown.utils.StringUtil; /** * SmallBank Client Driver * @author pavlo */ public class SmallBankClient extends BenchmarkComponent { private static final Logger LOG = Logger.getLogger(SmallBankClient.class); private static final LoggerBoolean debug = new LoggerBoolean(); static { LoggerUtil.attachObserver(LOG, debug); } /** * Each Transaction element provides an ArgGenerator to create the proper * arguments used to invoke the stored procedure */ private static interface ArgGenerator { /** * Generate the proper arguments used to invoke the given stored procedure * @param acct0 * @param acct1 * @return */ public Object[] genArgs(long acct0, long acct1); } /** * Set of transactions structs with their appropriate parameters */ public static enum Transaction { AMALGAMATE(SmallBankConstants.FREQUENCY_AMALGAMATE, true, new ArgGenerator() { public Object[] genArgs(long acct0, long acct1) { return new Object[] { acct0, // acctId0 acct1, // acctId1 }; } }), BALANCE(SmallBankConstants.FREQUENCY_BALANCE, false, new ArgGenerator() { public Object[] genArgs(long acct0, long acct1) { return new Object[] { acct0, // acctId }; } }), DEPOSIT_CHECKING(SmallBankConstants.FREQUENCY_DEPOSIT_CHECKING, false, new ArgGenerator() { public Object[] genArgs(long acct0, long acct1) { return new Object[] { acct0, // acctId0 1.3 // amount (from original code) }; } }), SEND_PAYMENT(SmallBankConstants.FREQUENCY_SEND_PAYMENT, true, new ArgGenerator() { public Object[] genArgs(long acct0, long acct1) { return new Object[] { acct0, // sendAcct acct1, // destAcct 5.00 // amount }; } }), TRANSACT_SAVINGS(SmallBankConstants.FREQUENCY_TRANSACT_SAVINGS, false, new ArgGenerator() { public Object[] genArgs(long acct0, long acct1) { return new Object[] { acct0, // acctId 20.20 // amount (from original code) }; } }), WRITE_CHECK(SmallBankConstants.FREQUENCY_WRITE_CHECK, false, new ArgGenerator() { public Object[] genArgs(long acct0, long acct1) { return new Object[] { acct0, // acctId 5.0 // amount (from original code) }; } }); private final String displayName; private final String callName; private final int weight; private final ArgGenerator ag; private final boolean needsTwoAccts; /** * Constructor * @param weight Default txn frequency rate * @param needsTwoAccts If set to true, then generateParams() will create two acctIds for this txn type * @param ag The parameter generator */ private Transaction(int weight, boolean needsTwoAccts, ArgGenerator ag) { this.displayName = StringUtil.title(this.name().replace("_", " ").toLowerCase()); this.callName = this.displayName.replace(" ", ""); this.weight = weight; this.needsTwoAccts = needsTwoAccts; this.ag = ag; } }; /** * Callback Class */ private class SmallBankCallback implements ProcedureCallback { private final Transaction txnType; public SmallBankCallback(Transaction txnType) { this.txnType = txnType; } @Override public void clientCallback(ClientResponse clientResponse) { incrementTransactionCounter(clientResponse, this.txnType.ordinal()); checkTransaction(txnType.callName, clientResponse, true, false); } } // END CLASS private final FlatHistogram<Transaction> txnWeights; private final SmallBankCallback callbacks[]; private final int numAccounts; private final Random rand = new Random(); // HOTSPOT INFO private double hotspot_percentage = SmallBankConstants.HOTSPOT_PERCENTAGE; private boolean hotspot_use_fixed_size = SmallBankConstants.HOTSPOT_USE_FIXED_SIZE; private int hotspot_size = SmallBankConstants.HOTSPOT_FIXED_SIZE; // DTXN CONTROL PARAMETERS private double prob_account_hotspot = 0d; private double prob_multiaccount_dtxn = 50d; private boolean force_multisite_dtxns = false; private boolean force_singlesite_dtxns = false; public static void main(String args[]) { BenchmarkComponent.main(SmallBankClient.class, args, false); } public SmallBankClient(String args[]) { super(args); CatalogContext catalogContext = this.getCatalogContext(); TheHashinator.initialize(catalogContext.catalog); this.numAccounts = (int)Math.round(SmallBankConstants.NUM_ACCOUNTS * this.getScaleFactor()); for (String key : m_extraParams.keySet()) { String value = m_extraParams.get(key); // Probability that accounts are chosen from the hotspot if (key.equalsIgnoreCase("prob_account_hotspot")) { this.prob_account_hotspot = Double.parseDouble(value); } // Probability that multi-accounts will be on different partitions else if (key.equalsIgnoreCase("prob_multiaccount_dtxn")) { this.prob_multiaccount_dtxn = Double.parseDouble(value); } // Force all distributed txns to be multi-site else if (key.equalsIgnoreCase("force_multisite_dtxns")) { this.force_multisite_dtxns = Boolean.parseBoolean(value); } // Force all distributed txns to be single-sited else if (key.equalsIgnoreCase("force_singlesite_dtxns")) { this.force_singlesite_dtxns = Boolean.parseBoolean(value); } // Percentage-based hotspot else if (key.equalsIgnoreCase("hotspot_percentage")) { this.hotspot_percentage = Double.parseDouble(value); } // Use a fixed-size hotspot else if (key.equalsIgnoreCase("hotspot_use_fixed_size")) { this.hotspot_use_fixed_size = Boolean.parseBoolean(value); } // Fixed-size hotspot else if (key.equalsIgnoreCase("hotspot_fixed_size")) { this.hotspot_size = Integer.parseInt(value); } } // FOR if (catalogContext.numberOfPartitions == 1) { this.prob_multiaccount_dtxn = 0; } if (catalogContext.sites.size() == 1) { this.force_multisite_dtxns = false; } // Disable all multi-partition txns if (this.isSinglePartitionOnly()) { this.force_multisite_dtxns = false; this.prob_multiaccount_dtxn = 0; } // Compute hotspot size if (this.hotspot_use_fixed_size == false) { this.hotspot_size = (int)((this.hotspot_percentage/100d) * this.numAccounts); } // Initialize the sampling table Histogram<Transaction> txns = new ObjectHistogram<Transaction>(); for (Transaction t : Transaction.values()) { Integer weight = this.getTransactionWeight(t.callName); if (weight == null) weight = t.weight; txns.put(t, weight); } // FOR assert(txns.getSampleCount() == 100) : "Invalid txn percentage total: " + txns.getSampleCount() + "\n" + txns; this.txnWeights = new FlatHistogram<Transaction>(this.rand, txns); if (debug.val) LOG.debug("Transaction Workload Distribution:\n" + txns); // Setup callbacks int num_txns = Transaction.values().length; this.callbacks = new SmallBankCallback[num_txns]; for (Transaction txnType : Transaction.values()) { this.callbacks[txnType.ordinal()] = new SmallBankCallback(txnType); } // FOR } @Override protected void runLoop() throws IOException { // Not needed. } @Override protected boolean runOnce() throws IOException { Transaction target = this.txnWeights.nextValue(); this.startComputeTime(target.displayName); Object params[] = this.generateParams(target); this.stopComputeTime(target.displayName); ProcedureCallback callback = this.callbacks[target.ordinal()]; boolean ret = this.getClientHandle().callProcedure(callback, target.callName, params); if (debug.val) LOG.debug("Executing txn " + target); return (ret); } /** * Generate the txn input parameters for a new invocation. * @param target * @return */ protected Object[] generateParams(Transaction target) { final CatalogContext catalogContext = this.getCatalogContext(); long acctIds[] = new long[]{ -1, -1 }; int partitions[] = new int[acctIds.length]; int sites[] = new int[acctIds.length]; boolean is_hotspot = (this.rand.nextInt(100) < this.prob_account_hotspot); boolean is_dtxn = (this.rand.nextInt(100) < this.prob_multiaccount_dtxn); boolean retry = false; for (int i = 0; i < acctIds.length; i++) { // Outside the hotspot if (is_hotspot == false) { acctIds[i] = this.rand.nextInt(this.numAccounts - this.hotspot_size) + this.hotspot_size; } // Inside the hotspot else { acctIds[i] = this.rand.nextInt(this.hotspot_size); } // They can never be the same! if (i > 0 && acctIds[i-1] == acctIds[i]) { continue; } partitions[i] = TheHashinator.hashToPartition(acctIds[i]); sites[i] = catalogContext.getSiteIdForPartitionId(partitions[i]); // If we only need one acctId, break out here. if (i == 0 && target.needsTwoAccts == false) break; // If we need two acctIds, then we need to go generate the second one if (i == 0) continue; // DTXN if (is_dtxn) { // Check whether the accounts need to be on different sites if (this.force_multisite_dtxns) { retry = (sites[0] == sites[1]); } // Or they need to be on the same site else if (this.force_singlesite_dtxns) { retry = (sites[0] != sites[1] || partitions[0] == partitions[1]); } // Or at least on the same partition else { retry = (partitions[0] == partitions[1]); } } // SINGLE-PARTITON else { retry = (partitions[0] != partitions[1]); } if (retry) { i -= 1; continue; } } // FOR if (debug.val) LOG.debug(String.format("Accounts: %s [hotspot=%s, dtxn=%s]", Arrays.toString(acctIds), is_hotspot, is_dtxn)); return (target.ag.genArgs(acctIds[0], acctIds[1])); } @Override public String[] getTransactionDisplayNames() { String names[] = new String[Transaction.values().length]; int ii = 0; for (Transaction transaction : Transaction.values()) { names[ii++] = transaction.displayName; } return names; } }