/***************************************************************************
* Copyright (C) 2011 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. *
***************************************************************************/
/* This file is part of VoltDB.
* Copyright (C) 2008-2010 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.brown.api;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.collections15.map.ListOrderedMap;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.ClientResponseImpl;
import org.voltdb.VoltSystemProcedure;
import org.voltdb.VoltTable;
import org.voltdb.VoltTableRow;
import org.voltdb.benchmark.BlockingClient;
import org.voltdb.benchmark.Verification;
import org.voltdb.benchmark.Verification.Expression;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Site;
import org.voltdb.catalog.Table;
import org.voltdb.client.Client;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ProcCallException;
import org.voltdb.client.StatsUploaderSettings;
import org.voltdb.sysprocs.LoadMultipartitionTable;
import org.voltdb.utils.Pair;
import org.voltdb.utils.VoltSampler;
import edu.brown.api.results.BenchmarkComponentResults;
import edu.brown.api.results.ResponseEntries;
import edu.brown.catalog.CatalogUtil;
import edu.brown.designer.partitioners.plan.PartitionPlan;
import edu.brown.hstore.HStoreConstants;
import edu.brown.hstore.HStoreThreadManager;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.conf.HStoreConf;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.profilers.ProfileMeasurement;
import edu.brown.statistics.Histogram;
import edu.brown.statistics.ObjectHistogram;
import edu.brown.statistics.TableStatistics;
import edu.brown.statistics.WorkloadStatistics;
import edu.brown.utils.ArgumentsParser;
import edu.brown.utils.FileUtil;
import edu.brown.utils.StringUtil;
/**
* Base class for clients that will work with the multi-host multi-process
* benchmark framework that is driven from stdin
*/
public abstract class BenchmarkComponent {
private static final Logger LOG = Logger.getLogger(BenchmarkComponent.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.setupLogging();
LoggerUtil.attachObserver(LOG, debug, trace);
}
public static String CONTROL_MESSAGE_PREFIX = "{HSTORE}";
// ============================================================================
// SHARED STATIC MEMBERS
// ============================================================================
private static Client globalClient;
private static final ReentrantLock globalClientLock = new ReentrantLock();
private static CatalogContext globalCatalog;
private static final ReentrantLock globalCatalogLock = new ReentrantLock();
private static PartitionPlan globalPartitionPlan;
private static final ReentrantLock globalPartitionPlanLock = new ReentrantLock();
private static final Set<Client> globalHasConnections = new HashSet<Client>();
private static Client getClient(Catalog catalog,
int messageSize,
boolean heavyWeight,
StatsUploaderSettings statsSettings,
boolean shareConnection) {
Client client = globalClient;
if (shareConnection == false) {
client = ClientFactory.createClient(
messageSize,
null,
heavyWeight,
statsSettings,
catalog
);
if (debug.val) LOG.debug("Created new Client handle");
} else if (client == null) {
globalClientLock.lock();
try {
if (globalClient == null) {
client = ClientFactory.createClient(
messageSize,
null,
heavyWeight,
statsSettings,
catalog
);
if (debug.val) LOG.debug("Created new shared Client handle");
}
} finally {
globalClientLock.unlock();
} // SYNCH
}
return (client);
}
private static CatalogContext getCatalogContext(File catalogPath) {
// Read back the catalog and populate catalog object
if (globalCatalog == null) {
globalCatalogLock.lock();
try {
if (globalCatalog == null) {
globalCatalog = CatalogUtil.loadCatalogContextFromJar(catalogPath);
}
} finally {
globalCatalogLock.unlock();
} // SYNCH
}
return (globalCatalog);
}
private static void applyPartitionPlan(Database catalog_db, File partitionPlanPath) {
if (globalPartitionPlan != null) return;
globalPartitionPlanLock.lock();
try {
if (globalPartitionPlan != null) return;
if (debug.val) LOG.debug("Loading PartitionPlan '" + partitionPlanPath + "' and applying it to the catalog");
globalPartitionPlan = new PartitionPlan();
try {
globalPartitionPlan.load(partitionPlanPath, catalog_db);
globalPartitionPlan.apply(catalog_db);
} catch (Exception ex) {
throw new RuntimeException("Failed to load PartitionPlan '" + partitionPlanPath + "' and apply it to the catalog", ex);
}
} finally {
globalPartitionPlanLock.unlock();
} // SYNCH
return;
}
// ============================================================================
// INSTANCE MEMBERS
// ============================================================================
/**
* Client initialized here and made available for use in derived classes
*/
private Client m_voltClient;
/**
* Manage input and output to the framework
*/
private ControlPipe m_controlPipe;
private boolean m_controlPipeAutoStart = false;
/**
*
*/
protected final ControlWorker worker = new ControlWorker(this);
/**
* State of this client
*/
protected volatile ControlState m_controlState = ControlState.PREPARING;
/**
* Username supplied to the Volt client
*/
private final String m_username;
/**
* Password supplied to the Volt client
*/
private final String m_password;
/**
* Rate at which transactions should be generated. If set to -1 the rate
* will be controlled by the derived class. Rate is in transactions per
* second
*/
final int m_txnRate;
private final boolean m_blocking;
/**
* Number of transactions to generate for every millisecond of time that
* passes
*/
final double m_txnsPerMillisecond;
/**
* Additional parameters (benchmark specific)
*/
protected final Map<String, String> m_extraParams = new HashMap<String, String>();
/**
* Storage for error descriptions
*/
protected String m_reason = "";
/**
* Display names for each transaction.
*/
private final String m_countDisplayNames[];
/**
* Client Id
*/
private final int m_id;
/**
* Total # of Clients
*/
private final int m_numClients;
/**
* If set to true, don't try to make any connections to the cluster with this client
* This is just used for testing
*/
private final boolean m_noConnections;
/**
* Total # of Partitions
*/
private final int m_numPartitions;
/**
* Path to catalog jar
*/
private final File m_catalogPath;
private CatalogContext m_catalogContext;
private final String m_projectName;
final boolean m_exitOnCompletion;
/**
* Pause Lock
*/
protected final Semaphore m_pauseLock = new Semaphore(1);
/**
* Data verification.
*/
private final float m_checkTransaction;
protected final boolean m_checkTables;
private final Random m_checkGenerator = new Random();
private final LinkedHashMap<Pair<String, Integer>, Expression> m_constraints;
private final List<String> m_tableCheckOrder = new LinkedList<String>();
protected VoltSampler m_sampler = null;
protected final int m_tickInterval;
protected final Thread m_tickThread;
protected int m_tickCounter = 0;
private final boolean m_noUploading;
private final ReentrantLock m_loaderBlock = new ReentrantLock();
private final ClientResponse m_dummyResponse = new ClientResponseImpl(-1, -1, -1, Status.OK, HStoreConstants.EMPTY_RESULT, "");
/**
* Keep track of the number of tuples loaded so that we can generate table statistics
*/
private final boolean m_tableStats;
private final File m_tableStatsDir;
private final ObjectHistogram<String> m_tableTuples = new ObjectHistogram<String>();
private final ObjectHistogram<String> m_tableBytes = new ObjectHistogram<String>();
private final Map<Table, TableStatistics> m_tableStatsData = new HashMap<Table, TableStatistics>();
protected final BenchmarkComponentResults m_txnStats = new BenchmarkComponentResults();
/**
* ClientResponse Entries
*/
protected final ResponseEntries m_responseEntries;
private boolean m_enableResponseEntries = false;
private final Map<String, ProfileMeasurement> computeTime = new HashMap<String, ProfileMeasurement>();
/**
*
*/
private BenchmarkClientFileUploader uploader = null;
/**
* Configuration
*/
private final HStoreConf m_hstoreConf;
private final ObjectHistogram<String> m_txnWeights = new ObjectHistogram<String>();
private Integer m_txnWeightsDefault = null;
private final boolean m_isLoader;
private final String m_statsDatabaseURL;
private final String m_statsDatabaseUser;
private final String m_statsDatabasePass;
private final String m_statsDatabaseJDBC;
private final int m_statsPollerInterval;
public BenchmarkComponent(final Client client) {
m_voltClient = client;
m_exitOnCompletion = false;
m_password = "";
m_username = "";
m_txnRate = -1;
m_isLoader = false;
m_blocking = false;
m_txnsPerMillisecond = 0;
m_catalogPath = null;
m_projectName = null;
m_id = 0;
m_numClients = 1;
m_noConnections = false;
m_numPartitions = 0;
m_countDisplayNames = null;
m_checkTransaction = 0;
m_checkTables = false;
m_constraints = new LinkedHashMap<Pair<String, Integer>, Expression>();
m_tickInterval = -1;
m_tickThread = null;
m_responseEntries = null;
m_tableStats = false;
m_tableStatsDir = null;
m_noUploading = false;
m_statsDatabaseURL = null;
m_statsDatabaseUser = null;
m_statsDatabasePass = null;
m_statsDatabaseJDBC = null;
m_statsPollerInterval = -1;
// FIXME
m_hstoreConf = null;
}
/**
* Constructor that initializes the framework portions of the client.
* Creates a Volt client and connects it to all the hosts provided on the
* command line with the specified username and password
*
* @param args
*/
public BenchmarkComponent(String args[]) {
if (debug.val) LOG.debug("Benchmark Component debugging");
// Initialize HStoreConf
String hstore_conf_path = null;
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
final String[] parts = arg.split("=", 2);
if (parts.length > 1 && parts[1].startsWith("${") == false && parts[0].equalsIgnoreCase("CONF")) {
hstore_conf_path = parts[1];
break;
}
} // FOR
synchronized (BenchmarkComponent.class) {
if (HStoreConf.isInitialized() == false) {
assert(hstore_conf_path != null) : "Missing HStoreConf file";
File f = new File(hstore_conf_path);
if (debug.val) LOG.debug("Initializing HStoreConf from '" + f.getName() + "' along with input parameters");
HStoreConf.init(f, args);
} else {
if (debug.val) LOG.debug("Initializing HStoreConf only with input parameters");
HStoreConf.singleton().loadFromArgs(args); // XXX Why do we need to do this??
}
} // SYNCH
m_hstoreConf = HStoreConf.singleton();
if (trace.val) LOG.trace("HStore Conf\n" + m_hstoreConf.toString(true));
int transactionRate = m_hstoreConf.client.txnrate;
boolean blocking = m_hstoreConf.client.blocking;
boolean tableStats = m_hstoreConf.client.tablestats;
String tableStatsDir = m_hstoreConf.client.tablestats_dir;
int tickInterval = m_hstoreConf.client.tick_interval;
// default values
String username = "user";
String password = "password";
ControlState state = ControlState.PREPARING; // starting state
String reason = ""; // and error string
int id = 0;
boolean isLoader = false;
int num_clients = 0;
int num_partitions = 0;
boolean exitOnCompletion = true;
float checkTransaction = 0;
boolean checkTables = false;
boolean noConnections = false;
boolean noUploading = false;
File catalogPath = null;
String projectName = null;
String partitionPlanPath = null;
boolean partitionPlanIgnoreMissing = false;
long startupWait = -1;
boolean autoStart = false;
String statsDatabaseURL = null;
String statsDatabaseUser = null;
String statsDatabasePass = null;
String statsDatabaseJDBC = null;
int statsPollInterval = 10000;
// scan the inputs once to read everything but host names
Map<String, Object> componentParams = new TreeMap<String, Object>();
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
final String[] parts = arg.split("=", 2);
if (parts.length == 1) {
state = ControlState.ERROR;
reason = "Invalid parameter: " + arg;
break;
}
else if (parts[1].startsWith("${")) {
continue;
}
else if (parts[0].equalsIgnoreCase("CONF")) {
continue;
}
if (debug.val) componentParams.put(parts[0], parts[1]);
if (parts[0].equalsIgnoreCase("CATALOG")) {
catalogPath = new File(parts[1]);
assert(catalogPath.exists()) : "The catalog file '" + catalogPath.getAbsolutePath() + " does not exist";
if (debug.val) componentParams.put(parts[0], catalogPath);
}
else if (parts[0].equalsIgnoreCase("LOADER")) {
isLoader = Boolean.parseBoolean(parts[1]);
}
else if (parts[0].equalsIgnoreCase("NAME")) {
projectName = parts[1];
}
else if (parts[0].equalsIgnoreCase("USER")) {
username = parts[1];
}
else if (parts[0].equalsIgnoreCase("PASSWORD")) {
password = parts[1];
}
else if (parts[0].equalsIgnoreCase("EXITONCOMPLETION")) {
exitOnCompletion = Boolean.parseBoolean(parts[1]);
}
else if (parts[0].equalsIgnoreCase("ID")) {
id = Integer.parseInt(parts[1]);
}
else if (parts[0].equalsIgnoreCase("NUMCLIENTS")) {
num_clients = Integer.parseInt(parts[1]);
}
else if (parts[0].equalsIgnoreCase("NUMPARTITIONS")) {
num_partitions = Integer.parseInt(parts[1]);
}
else if (parts[0].equalsIgnoreCase("CHECKTRANSACTION")) {
checkTransaction = Float.parseFloat(parts[1]);
}
else if (parts[0].equalsIgnoreCase("CHECKTABLES")) {
checkTables = Boolean.parseBoolean(parts[1]);
}
else if (parts[0].equalsIgnoreCase("NOCONNECTIONS")) {
noConnections = Boolean.parseBoolean(parts[1]);
}
else if (parts[0].equalsIgnoreCase("NOUPLOADING")) {
noUploading = Boolean.parseBoolean(parts[1]);
}
else if (parts[0].equalsIgnoreCase("WAIT")) {
startupWait = Long.parseLong(parts[1]);
}
else if (parts[0].equalsIgnoreCase("AUTOSTART")) {
autoStart = Boolean.parseBoolean(parts[1]);
}
// Procedure Stats Uploading Parameters
else if (parts[0].equalsIgnoreCase("STATSDATABASEURL")) {
statsDatabaseURL = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEUSER")) {
if (parts[1].isEmpty() == false) statsDatabaseUser = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEPASS")) {
if (parts[1].isEmpty() == false) statsDatabasePass = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEJDBC")) {
if (parts[1].isEmpty() == false) statsDatabaseJDBC = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSPOLLINTERVAL")) {
statsPollInterval = Integer.parseInt(parts[1]);
}
else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_PARTITION_PLAN)) {
partitionPlanPath = parts[1];
}
else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_PARTITION_PLAN_IGNORE_MISSING)) {
partitionPlanIgnoreMissing = Boolean.parseBoolean(parts[1]);
}
// If it starts with "benchmark.", then it always goes to the implementing class
else if (parts[0].toLowerCase().startsWith(HStoreConstants.BENCHMARK_PARAM_PREFIX)) {
if (debug.val) componentParams.remove(parts[0]);
parts[0] = parts[0].substring(HStoreConstants.BENCHMARK_PARAM_PREFIX.length());
m_extraParams.put(parts[0].toUpperCase(), parts[1]);
}
}
if (trace.val) {
Map<String, Object> m = new ListOrderedMap<String, Object>();
m.put("BenchmarkComponent", componentParams);
m.put("Extra Client", m_extraParams);
LOG.debug("Input Parameters:\n" + StringUtil.formatMaps(m));
}
// Thread.currentThread().setName(String.format("client-%02d", id));
m_catalogPath = catalogPath;
m_projectName = projectName;
m_id = id;
m_isLoader = isLoader;
m_numClients = num_clients;
m_numPartitions = num_partitions;
m_exitOnCompletion = exitOnCompletion;
m_username = username;
m_password = password;
m_txnRate = (isLoader ? -1 : transactionRate);
m_txnsPerMillisecond = (isLoader ? -1 : transactionRate / 1000.0);
m_blocking = blocking;
m_tickInterval = tickInterval;
m_noUploading = noUploading;
m_noConnections = noConnections || (isLoader && m_noUploading);
m_tableStats = tableStats;
m_tableStatsDir = (tableStatsDir.isEmpty() ? null : new File(tableStatsDir));
m_controlPipeAutoStart = autoStart;
m_statsDatabaseURL = statsDatabaseURL;
m_statsDatabaseUser = statsDatabaseUser;
m_statsDatabasePass = statsDatabasePass;
m_statsDatabaseJDBC = statsDatabaseJDBC;
m_statsPollerInterval = statsPollInterval;
// If we were told to sleep, do that here before we try to load in the catalog
// This is an attempt to keep us from overloading a single node all at once
if (startupWait > 0) {
if (debug.val) LOG.debug(String.format("Delaying client start-up by %.2f sec", startupWait/1000d));
try {
Thread.sleep(startupWait);
} catch (InterruptedException ex) {
throw new RuntimeException("Unexpected interruption", ex);
}
}
// HACK: This will instantiate m_catalog for us...
if (m_catalogPath != null) {
this.getCatalogContext();
}
// Parse workload transaction weights
if (m_hstoreConf.client.weights != null && m_hstoreConf.client.weights.trim().isEmpty() == false) {
for (String entry : m_hstoreConf.client.weights.split("(,|;)")) {
String data[] = entry.split(":");
if (data.length != 2) {
LOG.warn("Invalid transaction weight entry '" + entry + "'");
continue;
}
try {
String txnName = data[0];
int txnWeight = Integer.parseInt(data[1]);
assert(txnWeight >= 0);
// '*' is the default value
if (txnName.equals("*")) {
this.m_txnWeightsDefault = txnWeight;
if (debug.val) LOG.debug(String.format("Default Transaction Weight: %d", txnWeight));
} else {
if (debug.val) LOG.debug(String.format("%s Transaction Weight: %d", txnName, txnWeight));
this.m_txnWeights.put(txnName.toUpperCase(), txnWeight);
}
// If the weight is 100, then we'll set the default weight to zero
if (txnWeight == 100 && this.m_txnWeightsDefault == null) {
this.m_txnWeightsDefault = 0;
if (debug.val) LOG.debug(String.format("Default Transaction Weight: %d", this.m_txnWeightsDefault));
}
} catch (Throwable ex) {
LOG.warn("Invalid transaction weight entry '" + entry + "'", ex);
continue;
}
} // FOR
}
if (partitionPlanPath != null) {
boolean exists = FileUtil.exists(partitionPlanPath);
if (partitionPlanIgnoreMissing == false)
assert(exists) : "Invalid partition plan path '" + partitionPlanPath + "'";
if (exists) this.applyPartitionPlan(new File(partitionPlanPath));
}
this.initializeConnection();
// report any errors that occurred before the client was instantiated
if (state != ControlState.PREPARING)
setState(state, reason);
m_checkTransaction = checkTransaction;
m_checkTables = checkTables;
m_constraints = new LinkedHashMap<Pair<String, Integer>, Expression>();
m_countDisplayNames = getTransactionDisplayNames();
if (m_countDisplayNames != null) {
Map<Integer, String> debugLabels = new TreeMap<Integer, String>();
m_enableResponseEntries = (m_hstoreConf.client.output_responses != null);
m_responseEntries = new ResponseEntries();
for (int i = 0; i < m_countDisplayNames.length; i++) {
m_txnStats.transactions.put(i, 0);
m_txnStats.dtxns.put(i, 0);
m_txnStats.specexecs.put(i, 0);
debugLabels.put(i, m_countDisplayNames[i]);
} // FOR
m_txnStats.transactions.setDebugLabels(debugLabels);
m_txnStats.setEnableBasePartitions(m_hstoreConf.client.output_basepartitions);
m_txnStats.setEnableResponsesStatuses(m_hstoreConf.client.output_status);
} else {
m_responseEntries = null;
}
// If we need to call tick more frequently than when POLL is called,
// then we'll want to use a separate thread
if (m_tickInterval > 0 && isLoader == false) {
if (debug.val)
LOG.debug(String.format("Creating local thread that will call BenchmarkComponent.tick() every %.1f seconds",
(m_tickInterval / 1000.0)));
Runnable r = new Runnable() {
@Override
public void run() {
try {
while (true) {
BenchmarkComponent.this.invokeTickCallback(m_tickCounter++);
Thread.sleep(m_tickInterval);
} // WHILE
} catch (InterruptedException ex) {
LOG.warn("Tick thread was interrupted");
}
}
};
m_tickThread = new Thread(r);
m_tickThread.setDaemon(true);
} else {
m_tickThread = null;
}
}
// ----------------------------------------------------------------------------
// MAIN METHOD HOOKS
// ----------------------------------------------------------------------------
/**
* Derived classes implementing a main that will be invoked at the start of
* the app should call this main to instantiate themselves
*
* @param clientClass
* Derived class to instantiate
* @param args
* @param startImmediately
* Whether to start the client thread immediately or not.
*/
public static BenchmarkComponent main(final Class<? extends BenchmarkComponent> clientClass, final String args[], final boolean startImmediately) {
return main(clientClass, null, args, startImmediately);
}
protected static BenchmarkComponent main(final Class<? extends BenchmarkComponent> clientClass,
final BenchmarkClientFileUploader uploader,
final String args[],
final boolean startImmediately) {
BenchmarkComponent clientMain = null;
try {
final Constructor<? extends BenchmarkComponent> constructor =
clientClass.getConstructor(new Class<?>[] { new String[0].getClass() });
clientMain = constructor.newInstance(new Object[] { args });
if (uploader != null) clientMain.uploader = uploader;
clientMain.invokeInitCallback();
if (startImmediately) {
final ControlWorker worker = new ControlWorker(clientMain);
worker.start();
// Wait for the worker to finish
if (debug.val)
LOG.debug(String.format("Started ControlWorker for client #%02d. Waiting until finished...",
clientMain.getClientId()));
worker.join();
clientMain.invokeStopCallback();
}
else {
// if (debug.val) LOG.debug(String.format("Deploying ControlWorker for client #%02d. Waiting for control signal...", clientMain.getClientId()));
// clientMain.start();
}
}
catch (final Throwable e) {
String name = (clientMain != null ? clientMain.getProjectName()+"." : "") + clientClass.getSimpleName();
LOG.error("Unexpected error while invoking " + name, e);
throw new RuntimeException(e);
}
return (clientMain);
}
// ----------------------------------------------------------------------------
// CLUSTER CONNECTION SETUP
// ----------------------------------------------------------------------------
protected void initializeConnection() {
StatsUploaderSettings statsSettings = null;
if (m_statsDatabaseURL != null && m_statsDatabaseURL.isEmpty() == false) {
try {
statsSettings = StatsUploaderSettings.singleton(
m_statsDatabaseURL,
m_statsDatabaseUser,
m_statsDatabasePass,
m_statsDatabaseJDBC,
this.getProjectName(),
(m_isLoader ? "LOADER" : "CLIENT"),
m_statsPollerInterval,
m_catalogContext.catalog);
} catch (Throwable ex) {
throw new RuntimeException("Failed to initialize StatsUploader", ex);
}
if (debug.val)
LOG.debug("StatsUploaderSettings:\n" + statsSettings);
}
Client new_client = BenchmarkComponent.getClient(
(m_hstoreConf.client.txn_hints ? this.getCatalogContext().catalog : null),
getExpectedOutgoingMessageSize(),
useHeavyweightClient(),
statsSettings,
m_hstoreConf.client.shared_connection
);
if (m_blocking) { // && isLoader == false) {
int concurrent = m_hstoreConf.client.blocking_concurrent;
if (debug.val)
LOG.debug(String.format("Using BlockingClient [concurrent=%d]",
m_hstoreConf.client.blocking_concurrent));
if (this.isSinglePartitionOnly()) {
concurrent *= 4; // HACK
}
m_voltClient = new BlockingClient(new_client, concurrent);
} else {
m_voltClient = new_client;
}
// scan the inputs again looking for host connections
if (m_noConnections == false) {
synchronized (globalHasConnections) {
if (globalHasConnections.contains(new_client) == false) {
this.setupConnections();
globalHasConnections.add(new_client);
}
} // SYNCH
}
}
private void setupConnections() {
boolean atLeastOneConnection = false;
for (Site catalog_site : this.getCatalogContext().sites) {
final int site_id = catalog_site.getId();
final String host = catalog_site.getHost().getIpaddr();
int port = catalog_site.getProc_port();
if (debug.val)
LOG.debug(String.format("Creating connection to %s at %s:%d",
HStoreThreadManager.formatSiteName(site_id),
host, port));
try {
this.createConnection(site_id, host, port);
} catch (IOException ex) {
String msg = String.format("Failed to connect to %s on %s:%d",
HStoreThreadManager.formatSiteName(site_id), host, port);
// LOG.error(msg, ex);
// setState(ControlState.ERROR, msg + ": " + ex.getMessage());
// continue;
throw new RuntimeException(msg, ex);
}
atLeastOneConnection = true;
} // FOR
if (!atLeastOneConnection) {
setState(ControlState.ERROR, "No HOSTS specified on command line.");
throw new RuntimeException("Failed to establish connections to H-Store cluster");
}
}
private void createConnection(final Integer site_id, final String hostname, final int port)
throws UnknownHostException, IOException {
if (debug.val)
LOG.debug(String.format("Requesting connection to %s %s:%d",
HStoreThreadManager.formatSiteName(site_id), hostname, port));
m_voltClient.createConnection(site_id, hostname, port, m_username, m_password);
}
// ----------------------------------------------------------------------------
// CONTROLLER COMMUNICATION METHODS
// ----------------------------------------------------------------------------
protected void printControlMessage(ControlState state) {
printControlMessage(state, null);
}
private void printControlMessage(ControlState state, String message) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("%s %d,%d,%s", CONTROL_MESSAGE_PREFIX,
this.getClientId(),
System.currentTimeMillis(),
state));
if (message != null && message.isEmpty() == false) {
sb.append(",").append(message);
}
System.out.println(sb);
}
protected void answerWithError() {
this.printControlMessage(m_controlState, m_reason);
}
protected void answerPoll() {
BenchmarkComponentResults copy = this.m_txnStats.copy();
this.m_txnStats.clear(false);
this.printControlMessage(m_controlState, copy.toJSONString());
}
protected void answerDumpTxns() {
ResponseEntries copy = new ResponseEntries(this.m_responseEntries);
this.m_responseEntries.clear();
this.printControlMessage(ControlState.DUMPING, copy.toJSONString());
}
protected void answerOk() {
this.printControlMessage(m_controlState, "OK");
}
/**
* Implemented by derived classes. Loops indefinitely invoking stored
* procedures. Method never returns and never receives any updates.
*/
@Deprecated
abstract protected void runLoop() throws IOException;
/**
* Get the display names of the transactions that will be invoked by the
* derived class. As a side effect this also retrieves the number of
* transactions that can be invoked.
*
* @return
*/
abstract protected String[] getTransactionDisplayNames();
/**
* Increment the internal transaction counter. This should be invoked
* after the client has received a ClientResponse from the DBMS cluster
* The txn_index is the offset of the transaction that was executed. This offset
* is the same order as the array returned by getTransactionDisplayNames
* @param cresponse - The ClientResponse returned from the server
* @param txn_idx
*/
protected final void incrementTransactionCounter(final ClientResponse cresponse, final int txn_idx) {
// Only include it if it wasn't rejected
// This is actually handled in the Distributer, but it doesn't hurt to have this here
Status status = cresponse.getStatus();
if (status == Status.OK || status == Status.ABORT_USER) {
// TRANSACTION COUNTERS
boolean is_specexec = cresponse.isSpeculative();
boolean is_dtxn = (cresponse.isSinglePartition() == false);
synchronized (m_txnStats.transactions) {
m_txnStats.transactions.put(txn_idx);
if (is_dtxn) m_txnStats.dtxns.put(txn_idx);
if (is_specexec) m_txnStats.specexecs.put(txn_idx);
} // SYNCH
// LATENCIES COUNTERS
// Ignore zero latencies... Not sure why this happens...
int latency = cresponse.getClusterRoundtrip();
if (latency > 0) {
Map<Integer, ObjectHistogram<Integer>> latenciesMap = (is_dtxn ? m_txnStats.dtxnLatencies :
m_txnStats.spLatencies);
Histogram<Integer> latencies = latenciesMap.get(txn_idx);
if (latencies == null) {
synchronized (latenciesMap) {
latencies = latenciesMap.get(txn_idx);
if (latencies == null) {
latencies = new ObjectHistogram<Integer>();
latenciesMap.put(txn_idx, (ObjectHistogram<Integer>)latencies);
}
} // SYNCH
}
synchronized (latencies) {
latencies.put(latency);
} // SYNCH
}
// RESPONSE ENTRIES
if (m_enableResponseEntries) {
long timestamp = System.currentTimeMillis();
m_responseEntries.add(cresponse, m_id, txn_idx, timestamp);
}
// BASE PARTITIONS
if (m_txnStats.isBasePartitionsEnabled()) {
synchronized (m_txnStats.basePartitions) {
m_txnStats.basePartitions.put(cresponse.getBasePartition());
} // SYNCH
}
}
// else if (status == Status.ABORT_UNEXPECTED) {
// LOG.warn("Invalid " + m_countDisplayNames[txn_idx] + " response!\n" + cresponse);
// if (cresponse.getException() != null) {
// cresponse.getException().printStackTrace();
// }
// if (cresponse.getStatusString() != null) {
// LOG.warn(cresponse.getStatusString());
// }
// }
if (m_txnStats.isResponsesStatusesEnabled()) {
synchronized (m_txnStats.responseStatuses) {
m_txnStats.responseStatuses.put(status.ordinal());
} // SYNCH
}
}
// ----------------------------------------------------------------------------
// PUBLIC UTILITY METHODS
// ----------------------------------------------------------------------------
/**
* Return the scale factor for this benchmark instance
* @return
*/
public double getScaleFactor() {
return (m_hstoreConf.client.scalefactor);
}
/**
* This method will load a VoltTable into the database for the given tableName.
* The database will automatically split the tuples and send to the correct partitions
* The current thread will block until the the database cluster returns the result.
* Can be overridden for testing purposes.
* @param tableName
* @param vt
*/
public ClientResponse loadVoltTable(String tableName, VoltTable vt) {
assert(vt != null) : "Null VoltTable for '" + tableName + "'";
int rowCount = vt.getRowCount();
long rowTotal = m_tableTuples.get(tableName, 0);
int byteCount = vt.getUnderlyingBufferSize();
long byteTotal = m_tableBytes.get(tableName, 0);
if (trace.val) LOG.trace(String.format("%s: Loading %d new rows - TOTAL %d [bytes=%d/%d]",
tableName.toUpperCase(), rowCount, rowTotal, byteCount, byteTotal));
// Load up this dirty mess...
ClientResponse cr = null;
if (m_noUploading == false) {
boolean locked = m_hstoreConf.client.blocking_loader;
if (locked) m_loaderBlock.lock();
try {
int tries = 3;
String procName = VoltSystemProcedure.procCallName(LoadMultipartitionTable.class);
while (tries-- > 0) {
try {
cr = m_voltClient.callProcedure(procName, tableName, vt);
} catch (ProcCallException ex) {
// If this thing was rejected, then we'll allow us to try again.
cr = ex.getClientResponse();
if (cr.getStatus() == Status.ABORT_REJECT && tries > 0) {
if (debug.val)
LOG.warn(String.format("Loading data for %s was rejected. Going to try again\n%s",
tableName, cr.toString()));
continue;
}
// Anything else needs to be thrown out of here
throw ex;
}
break;
} // WHILE
} catch (Throwable ex) {
throw new RuntimeException("Error when trying load data for '" + tableName + "'", ex);
} finally {
if (locked) m_loaderBlock.unlock();
} // SYNCH
assert(cr != null);
assert(cr.getStatus() == Status.OK);
if (trace.val) LOG.trace(String.format("Load %s: txn #%d / %s / %d",
tableName, cr.getTransactionId(), cr.getStatus(), cr.getClientHandle()));
} else {
cr = m_dummyResponse;
}
if (cr.getStatus() != Status.OK) {
LOG.warn(String.format("Failed to load %d rows for '%s': %s",
rowCount, tableName, cr.getStatusString()), cr.getException());
return (cr);
}
m_tableTuples.put(tableName, rowCount);
m_tableBytes.put(tableName, byteCount);
// Keep track of table stats
if (m_tableStats && cr.getStatus() == Status.OK) {
final CatalogContext catalogContext = this.getCatalogContext();
assert(catalogContext != null);
final Table catalog_tbl = catalogContext.getTableByName(tableName);
assert(catalog_tbl != null) : "Invalid table name '" + tableName + "'";
synchronized (m_tableStatsData) {
TableStatistics stats = m_tableStatsData.get(catalog_tbl);
if (stats == null) {
stats = new TableStatistics(catalog_tbl);
stats.preprocess(catalogContext.database);
m_tableStatsData.put(catalog_tbl, stats);
}
vt.resetRowPosition();
while (vt.advanceRow()) {
VoltTableRow row = vt.getRow();
stats.process(catalogContext.database, row);
} // WHILE
} // SYNCH
}
return (cr);
}
/**
* Return an overridden transaction weight
* @param txnName
* @return
*/
protected final Integer getTransactionWeight(String txnName) {
return (this.getTransactionWeight(txnName, null));
}
/**
*
* @param txnName
* @param weightIfNull
* @return
*/
protected final Integer getTransactionWeight(String txnName, Integer weightIfNull) {
if (debug.val)
LOG.debug(String.format("Looking for txn weight for '%s' [weightIfNull=%s]",
txnName, weightIfNull));
Long val = this.m_txnWeights.get(txnName.toUpperCase());
if (val != null) {
return (val.intValue());
}
else if (m_txnWeightsDefault != null) {
return (m_txnWeightsDefault);
}
return (weightIfNull);
}
/**
* Get the number of tuples loaded into the given table thus far
* @param tableName
* @return
*/
public final long getTableTupleCount(String tableName) {
return (m_tableTuples.get(tableName, 0));
}
/**
* Get a read-only histogram of the number of tuples loaded in all
* of the tables
* @return
*/
public final Histogram<String> getTableTupleCounts() {
return (new ObjectHistogram<String>(m_tableTuples));
}
/**
* Get the number of bytes loaded into the given table thus far
* @param tableName
* @return
*/
public final long getTableBytes(String tableName) {
return (m_tableBytes.get(tableName, 0));
}
/**
* Generate a WorkloadStatistics object based on the table stats that
* were collected using loadVoltTable()
* @return
*/
private final WorkloadStatistics generateWorkloadStatistics() {
assert(m_tableStatsDir != null);
final CatalogContext catalogContext = this.getCatalogContext();
assert(catalogContext != null);
// Make sure we call postprocess on all of our friends
for (TableStatistics tableStats : m_tableStatsData.values()) {
try {
tableStats.postprocess(catalogContext.database);
} catch (Exception ex) {
String tableName = tableStats.getCatalogItem(catalogContext.database).getName();
throw new RuntimeException("Failed to process TableStatistics for '" + tableName + "'", ex);
}
} // FOR
if (trace.val)
LOG.trace(String.format("Creating WorkloadStatistics for %d tables [totalRows=%d, totalBytes=%d",
m_tableStatsData.size(), m_tableTuples.getSampleCount(), m_tableBytes.getSampleCount()));
WorkloadStatistics stats = new WorkloadStatistics(catalogContext.database);
stats.apply(m_tableStatsData);
return (stats);
}
/**
* Queue a local file to be sent to the client with the given client id.
* The file will be copied into the path specified by remote_file.
* When the client is started it will be passed argument <parameter>=<remote_file>
* @param client_id
* @param parameter
* @param local_file
* @param remote_file
*/
public void sendFileToClient(int client_id, String parameter, File local_file, File remote_file) throws IOException {
assert(uploader != null);
this.uploader.sendFileToClient(client_id, parameter, local_file, remote_file);
LOG.debug(String.format("Queuing local file '%s' to be sent to client %d as parameter '%s' to remote file '%s'", local_file, client_id, parameter, remote_file));
}
/**
*
* @param client_id
* @param parameter
* @param local_file
* @throws IOException
*/
public void sendFileToClient(int client_id, String parameter, File local_file) throws IOException {
String suffix = FileUtil.getExtension(local_file);
String prefix = String.format("%s-%02d-", local_file.getName().replace("." + suffix, ""), client_id);
File remote_file = FileUtil.getTempFile(prefix, suffix, false);
sendFileToClient(client_id, parameter, local_file, remote_file);
}
/**
* Queue a local file to be sent to all clients
* @param parameter
* @param local_file
* @throws IOException
*/
public void sendFileToAllClients(String parameter, File local_file) throws IOException {
for (int i = 0, cnt = this.getNumClients(); i < cnt; i++) {
sendFileToClient(i, parameter, local_file, local_file);
// this.sendFileToClient(i, parameter, local_file);
} // FOR
}
protected void setBenchmarkClientFileUploader(BenchmarkClientFileUploader uploader) {
assert(this.uploader == null);
this.uploader = uploader;
}
// ----------------------------------------------------------------------------
// CALLBACKS
// ----------------------------------------------------------------------------
protected final void invokeInitCallback() {
this.initCallback();
}
protected final void invokeStartCallback() {
this.startCallback();
}
protected final void invokeStopCallback() {
// If we were generating stats, then get the final WorkloadStatistics object
// and write it out to a file for them to use
if (m_tableStats) {
WorkloadStatistics stats = this.generateWorkloadStatistics();
assert(stats != null);
if (m_tableStatsDir.exists() == false) m_tableStatsDir.mkdirs();
File path = new File(m_tableStatsDir.getAbsolutePath() + "/" + this.getProjectName() + ".stats");
LOG.info("Writing table statistics data to '" + path + "'");
try {
stats.save(path);
} catch (IOException ex) {
throw new RuntimeException("Failed to save table statistics to '" + path + "'", ex);
}
}
this.stopCallback();
}
protected final void invokeClearCallback() {
m_txnStats.clear(true);
m_responseEntries.clear();
this.clearCallback();
}
/**
* Internal callback for each POLL tick that we get from the BenchmarkController
* This will invoke the tick() method that can be implemented benchmark clients
* @param counter
*/
protected final void invokeTickCallback(int counter) {
if (debug.val) LOG.debug("New Tick Update: " + counter);
this.tickCallback(counter);
if (debug.val) {
if (this.computeTime.isEmpty() == false) {
for (String txnName : this.computeTime.keySet()) {
ProfileMeasurement pm = this.computeTime.get(txnName);
if (pm.getInvocations() != 0) {
LOG.debug(String.format("[%02d] - %s COMPUTE TIME: %s", counter, txnName, pm.debug()));
pm.reset();
}
} // FOR
}
LOG.debug("Client Queue Time: " + this.m_voltClient.getQueueTime().debug());
this.m_voltClient.getQueueTime().reset();
}
}
/**
* Optional callback for when this BenchmarkComponent has been initialized
*/
public void initCallback() {
// Default is to do nothing
}
/**
* Optional callback for when this BenchmarkComponent has been told to start
*/
public void startCallback() {
// Default is to do nothing
}
/**
* Optional callback for when this BenchmarkComponent has been told to stop
* This is not a reliable callback and should only be used for testing
*/
public void stopCallback() {
// Default is to do nothing
}
/**
* Optional callback for when this BenchmarkComponent has been told to clear its
* internal counters.
*/
public void clearCallback() {
// Default is to do nothing
}
/**
* Internal callback for each POLL tick that we get from the BenchmarkController
* @param counter The number of times we have called this callback in the past
*/
public void tickCallback(int counter) {
// Default is to do nothing!
}
// ----------------------------------------------------------------------------
// PROFILING METHODS
// ----------------------------------------------------------------------------
/**
* Profiling method to keep track of how much time is spent in the client
* to compute the input parameters for a new txn invocation.
* Must be called before stopComputeTime()
* @see BenchmarkComponent.stopComputeTime
* @param txnName
*/
protected synchronized void startComputeTime(String txnName) {
ProfileMeasurement pm = this.computeTime.get(txnName);
if (pm == null) {
pm = new ProfileMeasurement(txnName);
this.computeTime.put(txnName, pm);
}
pm.start();
}
/**
* Stop recording the compute time for a new txn invocation.
* Must be called after startComputeTime()
* @see BenchmarkComponent.startComputeTime
* @param txnName
*/
protected synchronized void stopComputeTime(String txnName) {
ProfileMeasurement pm = this.computeTime.get(txnName);
assert(pm != null) : "Unexpected " + txnName;
pm.stop();
}
protected ProfileMeasurement getComputeTime(String txnName) {
return (this.computeTime.get(txnName));
}
protected boolean useHeavyweightClient() {
return false;
}
/**
* Implemented by derived classes. Invoke a single procedure without running
* the network. This allows BenchmarkComponent to control the rate at which
* transactions are generated.
*
* @return True if an invocation was queued and false otherwise
*/
protected boolean runOnce() throws IOException {
throw new UnsupportedOperationException();
}
/**
* Hint used when constructing the Client to control the size of buffers
* allocated for message serialization
*
* @return
*/
protected int getExpectedOutgoingMessageSize() {
return 128;
}
public ControlPipe createControlPipe(InputStream in) {
m_controlPipe = new ControlPipe(this, in, m_controlPipeAutoStart);
return (m_controlPipe);
}
/**
* Return the number of partitions in the cluster for this benchmark invocation
* @return
*/
public final int getNumPartitions() {
return (m_numPartitions);
}
/**
* Return the DBMS client handle
* This Client will already be connected to the database cluster
* @return
*/
public final Client getClientHandle() {
return (m_voltClient);
}
/**
* Special hook for setting the DBMS client handle
* This should only be invoked for RegressionSuite test cases
* @param client
*/
protected void setClientHandle(Client client) {
m_voltClient = client;
}
/**
* Return the unique client id for this invocation of BenchmarkComponent
* @return
*/
public final int getClientId() {
return (m_id);
}
/**
* Returns true if this client thread should submit only single-partition txns.
* Support for this option must be implemented in the benchmark.
* @return
*/
public final boolean isSinglePartitionOnly() {
boolean ret = (m_id < m_hstoreConf.client.singlepartition_threads);
if (debug.val && ret)
LOG.debug(String.format("Client #%03d is marked as single-partiiton only", m_id));
return (ret);
}
/**
* Return the total number of clients for this benchmark invocation
* @return
*/
public final int getNumClients() {
return (m_numClients);
}
/**
* Returns true if this BenchmarkComponent is not going to make any
* client connections to an H-Store cluster. This is used for testing
*/
protected final boolean noClientConnections() {
return (m_noConnections);
}
/**
* Return the file path to the catalog that was loaded for this benchmark invocation
* @return
*/
public File getCatalogPath() {
return (m_catalogPath);
}
/**
* Return the project name of this benchmark
* @return
*/
public final String getProjectName() {
return (m_projectName);
}
public final int getCurrentTickCounter() {
return (m_tickCounter);
}
/**
* Return the CatalogContext used for this benchmark
* @return
*/
public CatalogContext getCatalogContext() {
// Read back the catalog and populate catalog object
if (m_catalogContext == null) {
m_catalogContext = getCatalogContext(m_catalogPath);
}
return (m_catalogContext);
}
public void setCatalogContext(CatalogContext catalogContext) {
m_catalogContext = catalogContext;
}
public void applyPartitionPlan(File partitionPlanPath) {
CatalogContext catalogContext = this.getCatalogContext();
BenchmarkComponent.applyPartitionPlan(catalogContext.database, partitionPlanPath);
}
/**
* Get the HStoreConf handle
* @return
*/
public HStoreConf getHStoreConf() {
return (m_hstoreConf);
}
public void setState(final ControlState state, final String reason) {
m_controlState = state;
if (m_reason.equals("") == false)
m_reason += (" " + reason);
else
m_reason = reason;
}
private boolean checkConstraints(String procName, ClientResponse response) {
boolean isSatisfied = true;
int orig_position = -1;
// Check if all the tables in the result set satisfy the constraints.
for (int i = 0; isSatisfied && i < response.getResults().length; i++) {
Pair<String, Integer> key = Pair.of(procName, i);
if (!m_constraints.containsKey(key))
continue;
VoltTable table = response.getResults()[i];
orig_position = table.getActiveRowIndex();
table.resetRowPosition();
// Iterate through all rows and check if they satisfy the
// constraints.
while (isSatisfied && table.advanceRow()) {
isSatisfied = Verification.checkRow(m_constraints.get(key), table);
}
// Have to reset the position to its original position.
if (orig_position < 0)
table.resetRowPosition();
else
table.advanceToRow(orig_position);
}
if (!isSatisfied)
LOG.error("Transaction " + procName + " failed check");
return isSatisfied;
}
/**
* Performs constraint checking on the result set in clientResponse. It does
* simple sanity checks like if the response code is SUCCESS. If the check
* transaction flag is set to true by calling setCheckTransaction(), then it
* will check the result set against constraints.
*
* @param procName
* The name of the procedure
* @param clientResponse
* The client response
* @param errorExpected
* true if the response is expected to be an error.
* @return true if it passes all tests, false otherwise
*/
protected boolean checkTransaction(String procName,
ClientResponse clientResponse,
boolean abortExpected,
boolean errorExpected) {
final Status status = clientResponse.getStatus();
if (status != Status.OK) {
if (errorExpected)
return true;
if (abortExpected && status == Status.ABORT_USER)
return true;
if (status == Status.ABORT_CONNECTION_LOST) {
return false;
}
if (status == Status.ABORT_REJECT) {
return false;
}
if (clientResponse.getException() != null) {
clientResponse.getException().printStackTrace();
}
if (clientResponse.getStatusString() != null) {
LOG.warn(clientResponse.getStatusString());
}
throw new RuntimeException("Invalid " + procName + " response!\n" + clientResponse);
}
if (m_checkGenerator.nextFloat() >= m_checkTransaction)
return true;
return checkConstraints(procName, clientResponse);
}
/**
* Sets the given constraint for the table identified by the tableId of
* procedure 'name'. If there is already a constraint assigned to the table,
* it is updated to the new one.
*
* @param name
* The name of the constraint. For transaction check, this should
* usually be the procedure name.
* @param tableId
* The index of the table in the result set.
* @param constraint
* The constraint to use.
*/
protected void addConstraint(String name,
int tableId,
Expression constraint) {
m_constraints.put(Pair.of(name, tableId), constraint);
}
protected void addTableConstraint(String name,
Expression constraint) {
addConstraint(name, 0, constraint);
m_tableCheckOrder.add(name);
}
/**
* Removes the constraint on the table identified by tableId of procedure
* 'name'. Nothing happens if there is no constraint assigned to this table.
*
* @param name
* The name of the constraint.
* @param tableId
* The index of the table in the result set.
*/
protected void removeConstraint(String name, int tableId) {
m_constraints.remove(Pair.of(name, tableId));
}
/**
* Takes a snapshot of all the tables in the database now and check all the
* rows in each table to see if they satisfy the constraints. The
* constraints should be added with the table name and table id 0.
*
* Since the snapshot files reside on the servers, we have to copy them over
* to the client in order to check. This might be an overkill, but the
* alternative is to ask the user to write stored procedure for each table
* and execute them on all nodes. That's not significantly better, either.
*
* This function blocks. Should only be run at the end.
*
* @return true if all tables passed the test, false otherwise.
*/
protected boolean checkTables() {
return (true);
//
// String dir = "/tmp";
// String nonce = "data_verification";
// Client client = ClientFactory.createClient(getExpectedOutgoingMessageSize(), null,
// false, null);
// // Host ID to IP mappings
// LinkedHashMap<Integer, String> hostMappings = new LinkedHashMap<Integer, String>();
// /*
// * The key is the table name. the first one in the pair is the hostname,
// * the second one is file name
// */
// LinkedHashMap<String, Pair<String, String>> snapshotMappings =
// new LinkedHashMap<String, Pair<String, String>>();
// boolean isSatisfied = true;
//
// // Load the native library for loading table from snapshot file
// org.voltdb.EELibraryLoader.loadExecutionEngineLibrary(true);
//
// try {
// boolean keepTrying = true;
// VoltTable[] response = null;
//
// client.createConnection(m_host, m_username, m_password);
// // Only initiate the snapshot if it's the first client
// while (m_id == 0) {
// // Take a snapshot of the database. This call is blocking.
// response = client.callProcedure("@SnapshotSave", dir, nonce, 1).getResults();
// if (response.length != 1 || !response[0].advanceRow()
// || !response[0].getString("RESULT").equals("SUCCESS")) {
// if (keepTrying
// && response[0].getString("ERR_MSG").contains("ALREADY EXISTS")) {
// client.callProcedure("@SnapshotDelete",
// new String[] { dir },
// new String[] { nonce });
// keepTrying = false;
// continue;
// }
//
// System.err.println("Failed to take snapshot");
// return false;
// }
//
// break;
// }
//
// // Clients other than the one that initiated the snapshot
// // have to check if the snapshot has completed
// if (m_id > 0) {
// int maxTry = 10;
//
// while (maxTry-- > 0) {
// boolean found = false;
// response = client.callProcedure("@SnapshotStatus").getResults();
// if (response.length != 2) {
// System.err.println("Failed to get snapshot status");
// return false;
// }
// while (response[0].advanceRow()) {
// if (response[0].getString("NONCE").equals(nonce)) {
// found = true;
// break;
// }
// }
//
// if (found) {
// // This probably means the snapshot is done
// if (response[0].getLong("END_TIME") > 0)
// break;
// }
//
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// return false;
// }
// }
// }
//
// // Get host ID to hostname mappings
// response = client.callProcedure("@SystemInformation").getResults();
// if (response.length != 1) {
// System.err.println("Failed to get host ID to IP address mapping");
// return false;
// }
// while (response[0].advanceRow()) {
// if (!response[0].getString("key").equals("hostname"))
// continue;
// hostMappings.put((Integer) response[0].get("node_id", VoltType.INTEGER),
// response[0].getString("value"));
// }
//
// // Do a scan to get all the file names and table names
// response = client.callProcedure("@SnapshotScan", dir).getResults();
// if (response.length != 3) {
// System.err.println("Failed to get snapshot filenames");
// return false;
// }
//
// // Only copy the snapshot files we just created
// while (response[0].advanceRow()) {
// if (!response[0].getString("NONCE").equals(nonce))
// continue;
//
// String[] tables = response[0].getString("TABLES_REQUIRED").split(",");
// for (String t : tables)
// snapshotMappings.put(t, null);
// break;
// }
//
// while (response[2].advanceRow()) {
// int id = Integer.parseInt(response[2].getString("HOST_ID"));
// String tableName = response[2].getString("TABLE");
//
// if (!snapshotMappings.containsKey(tableName) || !hostMappings.containsKey(id))
// continue;
//
// snapshotMappings.put(tableName, Pair.of(hostMappings.get(id),
// response[2].getString("NAME")));
// }
// } catch (NoConnectionsException e) {
// e.printStackTrace();
// return false;
// } catch (ProcCallException e) {
// e.printStackTrace();
// return false;
// } catch (UnknownHostException e) {
// e.printStackTrace();
// return false;
// } catch (IOException e) {
// e.printStackTrace();
// return false;
// }
//
// // Iterate through all the tables
// for (String tableName : m_tableCheckOrder) {
// Pair<String, String> value = snapshotMappings.get(tableName);
// if (value == null)
// continue;
//
// String hostName = value.getFirst();
// File file = new File(dir, value.getSecond());
// FileInputStream inputStream = null;
// TableSaveFile saveFile = null;
// long rowCount = 0;
//
// Pair<String, Integer> key = Pair.of(tableName, 0);
// if (!m_constraints.containsKey(key) || hostName == null)
// continue;
//
// System.err.println("Checking table " + tableName);
//
// // Copy the file over
// String localhostName = null;
// try {
// localhostName = InetAddress.getLocalHost().getHostName();
// } catch (UnknownHostException e1) {
// localhostName = "localhost";
// }
// if (!hostName.equals("localhost") && !hostName.equals(localhostName)) {
// if (!SSHTools.copyFromRemote(file, m_username, hostName, file.getPath())) {
// System.err.println("Failed to copy the snapshot file " + file.getPath()
// + " from host "
// + hostName);
// return false;
// }
// }
//
// if (!file.exists()) {
// System.err.println("Snapshot file " + file.getPath()
// + " cannot be copied from "
// + hostName
// + " to localhost");
// return false;
// }
//
// try {
// try {
// inputStream = new FileInputStream(file);
// saveFile = new TableSaveFile(inputStream.getChannel(), 3, null);
//
// // Get chunks from table
// while (isSatisfied && saveFile.hasMoreChunks()) {
// final BBContainer chunk = saveFile.getNextChunk();
// VoltTable table = null;
//
// // This probably should not happen
// if (chunk == null)
// continue;
//
// table = PrivateVoltTableFactory.createVoltTableFromBuffer(chunk.b, true);
// // Now, check each row
// while (isSatisfied && table.advanceRow()) {
// isSatisfied = Verification.checkRow(m_constraints.get(key),
// table);
// rowCount++;
// }
// // Release the memory of the chunk we just examined, be good
// chunk.discard();
// }
// } finally {
// if (saveFile != null) {
// saveFile.close();
// }
// if (inputStream != null)
// inputStream.close();
// if (!hostName.equals("localhost") && !hostName.equals(localhostName)
// && !file.delete())
// System.err.println("Failed to delete snapshot file " + file.getPath());
// }
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// return false;
// } catch (IOException e) {
// e.printStackTrace();
// return false;
// }
//
// if (isSatisfied) {
// System.err.println("Table " + tableName
// + " with "
// + rowCount
// + " rows passed check");
// } else {
// System.err.println("Table " + tableName + " failed check");
// break;
// }
// }
//
// // Clean up the snapshot we made
// try {
// if (m_id == 0) {
// client.callProcedure("@SnapshotDelete",
// new String[] { dir },
// new String[] { nonce }).getResults();
// }
// } catch (IOException e) {
// e.printStackTrace();
// } catch (ProcCallException e) {
// e.printStackTrace();
// }
//
// System.err.println("Table checking finished "
// + (isSatisfied ? "successfully" : "with failures"));
//
// return isSatisfied;
}
}