/* This file is part of VoltDB.
* Copyright (C) 2008-2009 VoltDB L.L.C.
*
* 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.mit.elt;
import org.voltdb.client.Client;
import org.voltdb.client.ProcCallException;
import org.voltdb.utils.Pair;
import edu.brown.api.BenchmarkComponent;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
public class ELTTester extends BenchmarkComponent {
private Client client;
private int m_idA;
private int m_idB;
private int m_countInvalid;
private int clientNum;
private int valueOffset = 10;
// map from client id num to table to pair of sums for columns
Hashtable<Integer, HashMap<Character, long[]>> expectedValues = null;
final int verticaPort = 5433;
final String dbName = "database";
private enum Transaction {
INSERT_A("Insert A"),
INSERT_B("Insert B"),
TEST_CONSTRAINT("Violated constraint");
private Transaction(String displayName) {this.displayName = displayName; }
public final String displayName;
}
public Class<?>[] getProcedures() {
Class <?> procs[];
procs = new Class<?>[] { InsertA.class, InsertB.class, InsertCD.class };
return procs;
}
public Class<?>[] getSupplementalClasses() {
Class<?> classes[];
classes = new Class<?>[] {};
return classes;
}
public String getDDLFilename() {
return "ELTTester-ddl.sql";
}
public String getJARFilename() {
return "ELTTester.jar";
}
public int getTotalTransactionTypes() {
return Transaction.values().length;
}
public String getTransactionDescription(int txnType) {
return Transaction.values()[txnType].displayName;
}
public boolean setupForBenchmark(Hashtable<String, String> args) {
expectedValues = new Hashtable<Integer, HashMap<Character, long[]>>();
return true;
}
/**
* Make sure the data structures for keeping track of expected values
* is all set for this client. If it is, do nothing. If not, create the
* data structure.
*
* @param clientNum The id number for the client to setup.
*/
void ensureSetupForClient(int clientNum) {
HashMap<Character, long[]> map = expectedValues.get(clientNum);
if (map == null) {
map = new HashMap<Character, long[]>();
for (char table = 'A'; table <= 'D'; table++) {
long[] colVals = new long[2];
colVals[0] = colVals[1] = 0;
map.put(table, colVals);
}
expectedValues.put(clientNum, map);
}
}
/**
* Query Vertica and sum the columns of a table/client combo.
*
* @param stmt SQL Statement instance which connects to VerticaDB.
* @param clientNum The id number of the client doing the query.
* @param table The name of the table to query.
* @return Two longs that represent the queried values.
*/
long[] sumTableForClient(Statement stmt, int clientNum, char table) {
long[] retval = new long[2];
String sql = "select sum(" + table + "_ID) as X, " +
"sum(" + table + "_VAL) as Y " +
"from " + table + " " +
"where " + table + "_CLIENT = " + String.valueOf(clientNum) + ";";
try {
ResultSet results = stmt.executeQuery(sql);
if (!results.next()) return null;
retval[0] = results.getLong("X");
retval[1] = results.getLong("Y");
} catch (SQLException e) {
System.out.println("Error: " + e.getMessage());
return null;
}
return retval;
}
/**
* Check that the query results and the expected results are right for a
* specific table and client.
*
* @param table The name of the table to check.
* @param clientNum The id number of the client who inserted the data.
* @param tableSums The queried sums of values in Vertica.
* @param expectedIDSum The expected sum of the id column in Vertica.
* @param expectedValueSum The expected sum of the val column in Vertica.
* @return True if all values match, false otherwise.
*/
boolean checkValues(char table, int clientNum, long[] tableSums, long[] expectedSums) {
if (tableSums[0] > expectedSums[0]) {
System.err.printf("Table %c id column for client %d sums to too large a value.\n", table, clientNum);
System.err.printf(" Expected %d but query returned %d\n", expectedSums[0], tableSums[0]);
return false;
}
if (tableSums[0] < expectedSums[0]) {
System.err.printf("Table %c id column for client %d sums to too small a value.\n", table, clientNum);
System.err.printf(" Expected %d but query returned %d\n", expectedSums[0], tableSums[0]);
return false;
}
if (tableSums[1] > expectedSums[1]) {
System.err.printf("Table %c val column for client %d sums to too large a value.\n", table, clientNum);
System.err.printf(" Expected %d but query returned %d\n", expectedSums[1], tableSums[1]);
return false;
}
if (tableSums[1] < expectedSums[1]) {
System.err.printf("Table %c val column for client %d sums to too small a value.\n", table, clientNum);
System.err.printf(" Expected %d but query returned %d\n", expectedSums[1], tableSums[1]);
return false;
}
System.out.printf("Table %c values for client %d check out.\n", table, clientNum);
return true;
}
public void finalizeBenchmark(Hashtable<String, String> args) {
// get the vertica host and return if not provided
String host = args.get("elhost");
if (host == null) return;
System.out.println("Verifying results at Vertica site:");
// sleep for 10s to make sure all data has a chance to get to vertica
System.out.println("Waiting 10 seconds for final \"tick\"s...");
System.out.flush();
try {
Thread.sleep(6000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Connection vdbConn;
Statement stmt;
// build the JDBC connection string for vertica
String jdbcConnectionString = "jdbc:vertica://" + host + ":" +
String.valueOf(verticaPort) + "/" + dbName;
try {
// try to load the vertica JDBC driver
Class.forName("com.vertica.Driver");
// try to connect to vertica db
vdbConn = DriverManager.getConnection(jdbcConnectionString, "dbadmin", "");
// create extra JDBC junk
stmt = vdbConn.createStatement();
}
catch (Exception e) {
return;
}
// assume success until proven otherwise
boolean success = true;
// iterate over all client's counts for calls to executeTransaction(..)
for (Entry<Integer, HashMap<Character, long[]>> entry : expectedValues.entrySet()) {
int clientNum = entry.getKey();
HashMap<Character, long[]> values = entry.getValue();
// query vertica
long[] valuesA = sumTableForClient(stmt, clientNum, 'A');
long[] valuesB = sumTableForClient(stmt, clientNum, 'B');
long[] valuesC = sumTableForClient(stmt, clientNum, 'C');
long[] valuesD = sumTableForClient(stmt, clientNum, 'D');
// check with expected
long[] expectedValuesA = values.get('A');
long[] expectedValuesB = values.get('B');
long[] expectedValuesC = values.get('C');
long[] expectedValuesD = values.get('D');
// verify the numbers: if any fail, this method fails
success &= checkValues('A', clientNum, valuesA, expectedValuesA);
success &= checkValues('B', clientNum, valuesB, expectedValuesB);
success &= checkValues('C', clientNum, valuesC, expectedValuesC);
success &= checkValues('D', clientNum, valuesD, expectedValuesD);
}
if (success)
System.out.println("*** Verified ***");
try {
stmt.close();
vdbConn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public int executeTransaction() {
int curr_txn_type = 0;
ensureSetupForClient(clientNum);
// Use table A to test a table that fills w/i upload threshold
// - generate more than 2MB of content per 4s interval to table A
// Use table B to test a table that fills slower than upload thresh.
// - generate less than 2MB of content per 4s interval to table B.
// Generate uniqueness constraint violations B ever 1k transactions.
try {
// every 1000 transactions insert into B
if ((totalTxns() % 1000) == 0) {
m_idB += 1;
curr_txn_type = 1;
try {
client.callProcedure("InsertB", clientNum, m_idB, (m_idB + valueOffset));
expectedValues.get(clientNum).get('B')[0] += m_idB;
expectedValues.get(clientNum).get('B')[1] += m_idB + valueOffset;
}
catch (ProcCallException e) {
throw new RuntimeException(e);
}
// violate a uniqueness constraint by
// inserting the previous unique ids. (client num is unique to
// current thread and key is clientnum, id.)
curr_txn_type = 2;
m_countInvalid += 1;
try {
client.callProcedure("InsertB", clientNum, m_idB, (m_idB + valueOffset));
throw new RuntimeException("Insert was supposed to fail, but did not.");
}
catch (ProcCallException e) {}
}
// otherwise, execute transaction A (the frequent txn).
// and transaction CD - the frequent cross table txn.
// within CD, C inserts each transaction, D every 10,000 transactions.
m_idA += 1;
curr_txn_type = 0;
try {
client.callProcedure("InsertA", clientNum, m_idA, (m_idA + valueOffset));
expectedValues.get(clientNum).get('A')[0] += m_idA;
expectedValues.get(clientNum).get('A')[1] += m_idA + valueOffset;
client.callProcedure("InsertCD", clientNum, m_idA, (m_idA + valueOffset));
expectedValues.get(clientNum).get('C')[0] += m_idA;
expectedValues.get(clientNum).get('C')[1] += m_idA + valueOffset;
if ((m_idA % 10000) == 0) {
expectedValues.get(clientNum).get('D')[0] += m_idA;
expectedValues.get(clientNum).get('D')[1] += m_idA + valueOffset;
}
}
catch (ProcCallException e) {
throw new RuntimeException(e);
}
}
catch (java.io.IOException e) {
throw new RuntimeException(e);
}
return curr_txn_type;
}
private int totalTxns() {
return m_idA + m_idB + m_countInvalid;
}
private void initialize(int clientNum, Client client) {
this.clientNum = clientNum;
this.client = client;
this.m_idA = 0;
this.m_idB = 0;
this.m_countInvalid = 1; // keep mod happy
}
public ELTTester(int clientNum, Client client) {
super(new String[] {});
initialize(clientNum, client);
}
public ELTTester() {
super(new String[] {});
initialize(0, null);
}
public ArrayList<Pair<String, String>> getPartitionInfo() {
ArrayList<Pair<String,String>> partitionInfo = new ArrayList<Pair<String,String>>();
partitionInfo.add(Pair.of("A", "A_CLIENT"));
partitionInfo.add(Pair.of("B", "B_CLIENT"));
partitionInfo.add(Pair.of("C", "C_CLIENT"));
return partitionInfo;
}
@Override
public String[] getTransactionDisplayNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public void runLoop() {
// TODO Auto-generated method stub
}
}