/*
*
* Derby - Class org.apache.derbyTesting.system.oe.client.Submitter
*
* 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.derbyTesting.system.oe.client;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import org.apache.derbyTesting.system.oe.util.OERandom;
/**
* Class that submits Order Entry transactions
* to a database through an instance of an Operations class.
* This class is responsible for the mix of transactions
* and the generation of the random input values.
*
* Sub-classes can override the mix.
*/
public class Submitter {
/**
* Offset of Stock Level transaction in returned
* arrays with run information.
*/
public static final int STOCK_LEVEL = 0;
/**
* Offset of Order Status by Name transaction in returned
* arrays with run information.
*/
public static final int ORDER_STATUS_BY_NAME = 1;
/**
* Offset of Order Status by Id transaction in returned
* arrays with run information.
*/
public static final int ORDER_STATUS_BY_ID = 2;
/**
* Offset of Payment by Name transaction in returned
* arrays with run information.
*/
public static final int PAYMENT_BY_NAME = 3;
/**
* Offset of Payement by ID transaction in returned
* arrays with run information.
*/
public static final int PAYMENT_BY_ID = 4;
/**
* Offset of Delivery transaction in returned
* arrays with run information.
*/
public static final int DELIVERY_SCHEDULE = 5;
/**
* Offset of New Order transaction in returned
* arrays with run information.
*/
public static final int NEW_ORDER = 6;
/**
* Offset of New Order transaction that rolled back in returned
* arrays with run information.
*/
public static final int NEW_ORDER_ROLLBACK = 7;
/**
* Display to write the output to.
*/
private final Display display;
/**
* How the business transactions are implemented.
*/
private final Operations ops;
/**
* My own random number generator.
*/
private final OERandom rand;
/**
* Scale of the database.
*/
private final short maxW;
/**
* Record of how many transactions are implemented.
*/
private final int[] transactionCount;
/**
* Generate a new random number generator
* that follows the rules according to 2.1.6.1
* @param conn
* @return rand number generator
* @throws SQLException
*/
public static OERandom getRuntimeRandom(Connection conn)
throws SQLException
{
OERandom rand = new OERandom(-1);
ResultSet rs = conn.createStatement().executeQuery(
"SELECT CLOAD FROM C");
rs.next();
int cload = rs.getInt(1);
rs.close();
for (;;)
{
int c = rand.randomInt(0, 255);
int delta = Math.abs(cload - c);
if (delta == 96 || delta == 112)
continue;
if (delta < 65 || delta > 119)
continue;
rand = new OERandom(c);
break;
}
return rand;
}
/**
* Return a Submitter than only executes stock level transactions.
*/
public static Submitter stockLevelOnly(Display display,
Operations ops, OERandom rand,
short maxW)
{
return new Submitter(display, ops, rand, maxW)
{
protected int mixType(final int chooseType)
{
return Submitter.STOCK_LEVEL;
}
};
}
/**
* Return a Submitter than only executes order
* status by identifier transactions.
*/
public static Submitter orderStatusByIdOnly(Display display,
Operations ops, OERandom rand,
short maxW)
{
return new Submitter(display, ops, rand, maxW)
{
protected int mixType(final int chooseType)
{
return Submitter.ORDER_STATUS_BY_ID;
}
};
}
/**
* Return a Submitter than only executes order
* status by name transactions.
*/
public static Submitter orderStatusByNameOnly(Display display,
Operations ops, OERandom rand,
short maxW)
{
return new Submitter(display, ops, rand, maxW)
{
protected int mixType(final int chooseType)
{
return Submitter.ORDER_STATUS_BY_NAME;
}
};
}
/**
* Return a Submitter than only executes payment
* by identifier transactions.
*/
public static Submitter paymentByIdOnly(Display display,
Operations ops, OERandom rand,
short maxW)
{
return new Submitter(display, ops, rand, maxW)
{
protected int mixType(final int chooseType)
{
return Submitter.PAYMENT_BY_ID;
}
};
}
/**
* Return a Submitter than only executes payment
* by name transactions.
*/
public static Submitter paymentByNameOnly(Display display,
Operations ops, OERandom rand,
short maxW)
{
return new Submitter(display, ops, rand, maxW)
{
protected int mixType(final int chooseType)
{
return Submitter.PAYMENT_BY_NAME;
}
};
}
/**
* Return a Submitter than only executes new order
* transactions with no rollback
*/
public static Submitter newOrderOnly(Display display,
Operations ops, OERandom rand,
short maxW)
{
return new Submitter(display, ops, rand, maxW)
{
protected int mixType(final int chooseType)
{
return Submitter.NEW_ORDER;
}
};
}
/**
* Create a submitter that has a fixed mix of transactions
* at input time.
*
* @see Submitter#mixType(int)
*/
public Submitter(Display display, Operations ops, OERandom rand,
short maxW)
{
this.display = display;
this.ops = ops;
this.rand = rand;
this.maxW = maxW;
transactionCount = new int[NEW_ORDER_ROLLBACK+1];
}
/**
* Reset the transaction counts to zero.
*/
public void clearTransactionCount()
{
Arrays.fill(transactionCount, 0);
}
/**
* Run a fixed number of transactions returning the
* time in milli-seconds required to execute all of them.
* @param displayData Passed onto Display calls
* @param count Number of transactions to run
* @return Elapsed time in ms to run count transactions
* @throws Exception
*/
public long runTransactions(final Object displayData, final int count)
throws Exception
{
long startms = System.currentTimeMillis();
for (int i = 0; i < count; i++)
runTransaction(displayData);
long endms = System.currentTimeMillis();
return endms - startms;
}
/**
* Run an order entry transaction picking the specific
* transaction at random with a hard-coded mix.
* @param displayData Passed onto Display calls
* @throws Exception Error executing the transaction
*/
public void runTransaction(final Object displayData) throws Exception
{
int chooseType = rand.randomInt(1, 100);
int type = mixType(chooseType);
switch (type)
{
case Submitter.DELIVERY_SCHEDULE:
runScheduleDelivery(displayData);
break;
case Submitter.NEW_ORDER:
runNewOrder(displayData, false);
break;
case Submitter.NEW_ORDER_ROLLBACK:
runNewOrder(displayData, true);
break;
case Submitter.ORDER_STATUS_BY_ID:
runOrderStatus(displayData, false);
break;
case Submitter.ORDER_STATUS_BY_NAME:
runOrderStatus(displayData, true);
break;
case Submitter.PAYMENT_BY_ID:
runPayment(displayData, false);
break;
case Submitter.PAYMENT_BY_NAME:
runPayment(displayData, true);
break;
case Submitter.STOCK_LEVEL:
runStockLevel(displayData);
break;
}
transactionCount[type]++;
}
/**
* Return one of transaction constants to run that transaction.
* This mix in not correct for TPC-C specification.
* With the official spec the final mix of transactions
* must match a certain profile. With this setup the
* mix is fixed upon input following the TPC-C final ratios.
* This will give approximately correct results, but the final
* mix will not be in line with TPC-C rules. This is because
* different transactions have different execution times.
* @param chooseType Random number between 1 and 100 inclusive.
* @return A transaction constant from this class.
*/
protected int mixType(final int chooseType)
{
if (chooseType <= 43)
{
boolean byName = rand.randomInt(1, 100) <= 60;
return byName ?
Submitter.PAYMENT_BY_NAME : Submitter.PAYMENT_BY_ID;
}
else if (chooseType <= (43 + 4))
{
boolean byName = rand.randomInt(1, 100) <= 60;
return byName ?
Submitter.ORDER_STATUS_BY_NAME : Submitter.ORDER_STATUS_BY_ID;
}
else if (chooseType <= (43 + 4 + 4))
return Submitter.DELIVERY_SCHEDULE;
else if (chooseType <= (43 + 4 + 4 + 4))
return Submitter.STOCK_LEVEL;
else
{
// 1% rollback
boolean rollback = rand.randomInt(1, 100) == 1;
return rollback ?
Submitter.NEW_ORDER_ROLLBACK : Submitter.NEW_ORDER;
}
}
protected void runNewOrder(Object displayData, boolean forRollback)
throws Exception
{
short homeWarehouse = warehouse();
final int orderItemCount = rand.randomInt(5, 15);
int[] items = new int[orderItemCount];
short[] quantities = new short[orderItemCount];
short[] supplyW = new short[orderItemCount];
for (int i = 0; i < orderItemCount; i++)
{
// Section 2.4.1.5
// 1)
items[i] = rand.NURand8191();
// 2)
if (maxW == 1 || rand.randomInt(1, 100) > 1)
{
supplyW[i] = homeWarehouse;
}
else
{
short sw = warehouse();
while (sw == homeWarehouse)
sw = warehouse();
supplyW[i] = sw;
}
supplyW[i] = rand.randomInt(1, 100) > 1 ?
homeWarehouse : warehouse();
// 3)
quantities[i] = (short) rand.randomInt(1, 10);
}
// Section 2.4.1.4
if (forRollback)
{
items[orderItemCount - 1] = 2334432;
}
ops.newOrder(display, displayData,
homeWarehouse, rand.district(),
rand.NURand1023(),
items, quantities, supplyW);
}
protected void runScheduleDelivery(Object displayData) {
// TODO Auto-generated method stub
}
/**
* Run a payment transaction with random input values.
*/
protected void runPayment(Object displayData,
boolean byName) throws Exception {
if (byName)
{
ops.payment(display, displayData,
warehouse(), rand.district(),
warehouse(), rand.district(),
rand.randomCLast(), rand.payment().toString());
}
else
{
ops.payment(display, displayData,
warehouse(), rand.district(),
warehouse(), rand.district(),
rand.NURand1023(), rand.payment().toString());
}
}
/**
* Return a random warehouse
* @return a random warehouse
*/
private final short warehouse() {
if (maxW == 1)
return 1;
return (short) rand.randomInt(1, maxW);
}
/**
* Run a stock level transaction with random input values.
*/
protected void runStockLevel(Object displayData) throws Exception
{
ops.stockLevel(display, displayData,
warehouse(), rand.district(), rand.threshold());
}
/**
* Run an order status transaction with random input values.
*/
protected void runOrderStatus(Object displayData, boolean byName) throws Exception {
if (byName)
{
ops.orderStatus(display, displayData,
warehouse(), rand.district(), rand.randomCLast());
}
else
{
ops.orderStatus(display, displayData,
warehouse(), rand.district(), rand.NURand1023());
}
}
/**
* Print a simple report of the activity.
* @param out
*/
public void printReport(PrintStream out) {
int total = 0;
for (int i = 0; i < transactionCount.length; i++)
total += transactionCount[i];
out.println("Total Transactions: " + total);
int noTotal = transactionCount[NEW_ORDER] +
transactionCount[NEW_ORDER_ROLLBACK];
int pyCount = transactionCount[PAYMENT_BY_NAME] +
transactionCount[PAYMENT_BY_ID];
int osCount = transactionCount[ORDER_STATUS_BY_NAME] +
transactionCount[ORDER_STATUS_BY_ID];
if (noTotal != 0)
out.println(transactionCount("New Order ", noTotal, total));
if (pyCount != 0) {
out.println(transactionCount("Payment ", pyCount, total));
out.println(transactionCount(" By Name ", transactionCount[PAYMENT_BY_NAME], total));
out.println(transactionCount(" By Identifier ", transactionCount[PAYMENT_BY_ID], total));
}
if (osCount != 0) {
out.println(transactionCount("Order Status ", osCount, total));
out.println(transactionCount(" By Name ", transactionCount[ORDER_STATUS_BY_NAME], total));
out.println(transactionCount(" By Identifier ", transactionCount[ORDER_STATUS_BY_ID], total));
}
if (transactionCount[STOCK_LEVEL] != 0)
out.println(transactionCount("Stock Level ",
transactionCount[STOCK_LEVEL], total));
if (transactionCount[DELIVERY_SCHEDULE] != 0)
out.println(transactionCount("Schedule Delivery ",
transactionCount[DELIVERY_SCHEDULE], total));
}
private String transactionCount(String name, int count, int total)
{
return name + " : " + percent(count, total) +
"(" + count + ")" ;
}
private String percent(int count, int total)
{
BigDecimal c = BigDecimal.valueOf((long) count * 100L);
BigDecimal t = BigDecimal.valueOf((long) total);
BigDecimal p = c.divide(t, 2, BigDecimal.ROUND_DOWN);
return p.toString().concat("%");
}
/**
* Get the executed transaction counts.
*
* @return transactionCount
*/
public int[] getTransactionCount() {
return transactionCount;
}
}