/***************************************************************************
* Copyright (C) 2012 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.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.SysProcSelector;
import org.voltdb.VoltSystemProcedure;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.benchmark.tpcc.TPCCProjectBuilder;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.Site;
import org.voltdb.client.Client;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NullCallback;
import org.voltdb.client.ProcCallException;
import org.voltdb.processtools.ProcessSetManager;
import org.voltdb.processtools.SSHTools;
import org.voltdb.sysprocs.DatabaseDump;
import org.voltdb.sysprocs.EvictHistory;
import org.voltdb.sysprocs.EvictedAccessHistory;
import org.voltdb.sysprocs.ExecutorStatus;
import org.voltdb.sysprocs.GarbageCollection;
import org.voltdb.sysprocs.MarkovUpdate;
import org.voltdb.sysprocs.NoOp;
import org.voltdb.sysprocs.Quiesce;
import org.voltdb.sysprocs.ResetProfiling;
import org.voltdb.sysprocs.Statistics;
import org.voltdb.utils.LogKeys;
import org.voltdb.utils.Pair;
import org.voltdb.utils.VoltTableUtil;
import edu.brown.api.BenchmarkControllerUtil.ProfilingOutput;
import edu.brown.api.results.BenchmarkResults;
import edu.brown.api.results.CSVResultsPrinter;
import edu.brown.api.results.JSONResultsPrinter;
import edu.brown.api.results.MemoryStatsPrinter;
import edu.brown.api.results.TableStatsPrinter;
import edu.brown.api.results.ResponseEntries;
import edu.brown.api.results.ResultsChecker;
import edu.brown.api.results.ResultsPrinter;
import edu.brown.api.results.ResultsUploader;
import edu.brown.benchmark.AbstractProjectBuilder;
import edu.brown.catalog.CatalogUtil;
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.markov.containers.MarkovGraphsContainer;
import edu.brown.markov.containers.MarkovGraphsContainerUtil;
import edu.brown.profilers.ProfileMeasurement;
import edu.brown.statistics.Histogram;
import edu.brown.statistics.ObjectHistogram;
import edu.brown.utils.ArgumentsParser;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.EventObservable;
import edu.brown.utils.EventObservableExceptionHandler;
import edu.brown.utils.EventObserver;
import edu.brown.utils.FileUtil;
import edu.brown.utils.MathUtil;
import edu.brown.utils.StringUtil;
import edu.brown.utils.ThreadUtil;
public class BenchmarkController {
public static final Logger LOG = Logger.getLogger(BenchmarkController.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.setupLogging();
LoggerUtil.attachObserver(LOG, debug, trace);
}
// ============================================================================
// STATIC CONFIGURATION
// ============================================================================
/**
* HStoreConf parameters to not forward to clients
*/
private static final String EXCLUDED_CLIENT_PARAMS[] = {
"client.jvm_args"
};
// ============================================================================
// INSTANCE MEMBERS
// ============================================================================
/** Clients **/
final ProcessSetManager clientPSM;
/** Server Sites **/
final ProcessSetManager sitePSM;
Thread self = null;
boolean stop = false;
boolean failed = false;
boolean cleaned = false;
final BenchmarkConfig m_config;
HStoreConf hstore_conf;
BenchmarkResults currentResults = null;
// ----------------------------------------------------------------------------
// CLIENT INFORMATION
// ----------------------------------------------------------------------------
final List<String> m_clients = new ArrayList<String>();
final List<String> m_clientThreads = new ArrayList<String>();
final AtomicInteger m_clientsNotReady = new AtomicInteger(0);
final Collection<BenchmarkInterest> m_interested = new HashSet<BenchmarkInterest>();
// ----------------------------------------------------------------------------
// STATUS THREADS
// ----------------------------------------------------------------------------
final List<ClientStatusThread> m_statusThreads = new ArrayList<ClientStatusThread>();
final AtomicBoolean m_statusThreadShouldContinue = new AtomicBoolean(true);
/**
* BenchmarkResults Processing
*/
protected int m_pollIndex = 0;
protected long m_maxCompletedPoll = 0;
protected final long m_pollCount;
private int totalNumClients;
protected CountDownLatch resultsToRead;
ResultsUploader resultsUploader = null;
protected PeriodicEvictionThread evictorThread;
private final ScheduledThreadPoolExecutor threadPool;
Class<? extends BenchmarkComponent> m_clientClass = null;
Class<? extends AbstractProjectBuilder> m_builderClass = null;
Class<? extends BenchmarkComponent> m_loaderClass = null;
final AbstractProjectBuilder projectBuilder;
final File jarFileName;
/**
* SiteId -> Set[Host, Port]
*/
Map<Integer, Set<Pair<String, Integer>>> m_launchHosts;
private CatalogContext catalogContext;
/**
* Keeps track of any files to send to clients
*/
private final BenchmarkClientFileUploader m_clientFileUploader = new BenchmarkClientFileUploader();
private final AtomicInteger m_clientFilesUploaded = new AtomicInteger(0);
// ----------------------------------------------------------------------------
// FAILURE HANDLERS
// ----------------------------------------------------------------------------
/**
* UncaughtExceptionHandler
* This just forwards the error message to the failure EventObserver
*/
private final EventObservableExceptionHandler exceptionHandler = new EventObservableExceptionHandler();
{
this.exceptionHandler.addObserver(new EventObserver<Pair<Thread,Throwable>>() {
final EventObservable<String> inner = new EventObservable<String>();
{
inner.addObserver(failure_observer);
}
@Override
public void update(EventObservable<Pair<Thread, Throwable>> o, Pair<Thread, Throwable> arg) {
Thread thread = arg.getFirst();
Throwable throwable = arg.getSecond();
LOG.error(String.format("Unexpected error from %s", thread.getName()), throwable);
String msg = String.format("%s - %s", thread.getName(), throwable.getMessage());
inner.notifyObservers(msg);
}
});
}
/**
* Failure EventObserver
*/
private final EventObserver<String> failure_observer = new EventObserver<String>() {
final AtomicBoolean lock = new AtomicBoolean(false);
@Override
public void update(EventObservable<String> o, String msg) {
if (lock.compareAndSet(false, true)) {
LOG.fatal(msg);
BenchmarkController.this.stop = true;
BenchmarkController.this.failed = true;
// Stop all the benchmark interests
for (BenchmarkInterest bi : m_interested) {
bi.stop();
} // FOR
ThreadUtil.sleep(1500);
clientPSM.prepareShutdown(false);
sitePSM.prepareShutdown(false);
if (self != null) BenchmarkController.this.self.interrupt();
}
}
};
@SuppressWarnings("unchecked")
public BenchmarkController(BenchmarkConfig config, CatalogContext catalogContext) {
this.m_config = config;
this.self = Thread.currentThread();
this.hstore_conf = HStoreConf.singleton();
if (catalogContext != null) this.initializeCatalog(catalogContext);
// Setup ProcessSetManagers...
this.clientPSM = new ProcessSetManager(hstore_conf.client.log_dir,
hstore_conf.client.log_backup,
0,
this.failure_observer);
this.sitePSM = new ProcessSetManager(hstore_conf.site.log_dir,
hstore_conf.site.log_backup,
config.client_initialPollingDelay,
this.failure_observer);
Map<String, Field> builderFields = new HashMap<String, Field>();
builderFields.put("m_clientClass", null);
builderFields.put("m_loaderClass", null);
try {
m_builderClass = (Class<? extends AbstractProjectBuilder>)Class.forName(m_config.projectBuilderClass);
} catch (Exception ex) {
LOG.fatal(String.format("Failed load class for ProjectBuilder '%s'", m_config.projectBuilderClass), ex);
throw new RuntimeException(ex);
}
for (String fieldName : builderFields.keySet()) {
try {
//Hackish, client expected to have these field as a static member
Field f = m_builderClass.getField(fieldName);
builderFields.put(fieldName, f);
} catch (NoSuchFieldError ex) {
LOG.fatal(String.format("ProjectBuilder '%s' is missing field '%s'", m_config.projectBuilderClass, fieldName), ex);
throw new RuntimeException(ex);
} catch (Exception ex) {
LOG.fatal(String.format("Unexpected error in ProjectBuilder '%s' when retrieving field '%s'", m_config.projectBuilderClass, fieldName), ex);
throw new RuntimeException(ex);
}
}
try {
m_clientClass = (Class<? extends BenchmarkComponent>)builderFields.get("m_clientClass").get(null);
m_loaderClass = (Class<? extends BenchmarkComponent>)builderFields.get("m_loaderClass").get(null);
// if (m_config.localmode == false) {
// this.jarFileName = config.hosts[0] + "." + this.jarFileName;
// }
} catch (Exception e) {
LogKeys logkey = LogKeys.benchmark_BenchmarkController_ErrorDuringReflectionForClient;
LOG.l7dlog( Level.FATAL, logkey.name(),
new Object[] { m_config.projectBuilderClass }, e);
System.exit(-1);
}
m_pollCount = hstore_conf.client.duration / hstore_conf.client.interval;
resultsUploader = new ResultsUploader(m_config.projectBuilderClass, config);
AbstractProjectBuilder tempBuilder = null;
try {
tempBuilder = m_builderClass.newInstance();
} catch (Exception e) {
LogKeys logkey =
LogKeys.benchmark_BenchmarkController_UnableToInstantiateProjectBuilder;
LOG.l7dlog( Level.FATAL, logkey.name(),
new Object[] { m_builderClass.getSimpleName() }, e);
System.exit(-1);
}
assert(tempBuilder != null);
this.projectBuilder = tempBuilder;
this.projectBuilder.addAllDefaults();
this.jarFileName = FileUtil.join(hstore_conf.client.jar_dir, this.projectBuilder.getJarName(false));
assert(this.jarFileName.getPath().isEmpty() == false) :
"Invalid Project jar file name '" + this.jarFileName + "'";
if (config.snapshotFrequency != null
&& config.snapshotPath != null
&& config.snapshotPrefix != null
&& config.snapshotRetain > 0) {
this.projectBuilder.setSnapshotSettings(
config.snapshotFrequency,
config.snapshotRetain,
config.snapshotPath,
config.snapshotPrefix);
}
this.threadPool = ThreadUtil.getScheduledThreadPoolExecutor(
"benchmark",
this.exceptionHandler,
2, 1024 * 128);
}
private void initializeCatalog(CatalogContext catalogContext) {
assert(catalogContext != null);
this.catalogContext = catalogContext;
int total_num_clients = m_config.clients.length * hstore_conf.client.threads_per_host;
if (hstore_conf.client.processesperclient_per_partition) {
total_num_clients *= catalogContext.numberOfPartitions;
}
this.totalNumClients = total_num_clients;
int num_results = (int)(m_pollCount * this.totalNumClients);
if (hstore_conf.client.output_responses != null) num_results += this.totalNumClients;
this.resultsToRead = new CountDownLatch(num_results);
}
private String makeHeader(String label) {
return (StringUtil.SET_BOLD_TEXT +
StringUtil.header(label.toUpperCase() + " :: " + this.getProjectName()) +
StringUtil.SET_PLAIN_TEXT);
}
public String getProjectName() {
return (this.projectBuilder.getProjectName().toUpperCase());
}
public Catalog getCatalog() {
return (this.catalogContext.catalog);
}
protected BenchmarkResults getBenchmarkResults() {
return (this.currentResults);
}
protected CountDownLatch getResultsToReadLatch() {
return (this.resultsToRead);
}
protected ProcessSetManager getClientProcessSetManager() {
return (this.clientPSM);
}
protected ProcessSetManager getSiteProcessSetManager() {
return (this.sitePSM);
}
protected void clientIsReady(String clientName) {
m_clientsNotReady.decrementAndGet();
}
protected Collection<BenchmarkInterest> getBenchmarkInterests() {
return (this.m_interested);
}
public void registerInterest(BenchmarkInterest interest) {
synchronized(m_interested) {
m_interested.add(interest);
}
}
/**
* SETUP BENCHMARK
*/
public void setupBenchmark() {
// Load the catalog that we just made
if (debug.val) LOG.debug("Loading catalog from '" + this.jarFileName + "'");
this.initializeCatalog(CatalogUtil.loadCatalogContextFromJar(this.jarFileName));
// Clear out any HStoreConf parameters that we're not suppose to
// forward to the sites and clients
for (String key : EXCLUDED_CLIENT_PARAMS)
m_config.clientParameters.remove(key);
// Now figure out which hosts we really want to launch this mofo on
Set<String> unique_hosts = new HashSet<String>();
if (m_config.useCatalogHosts == false) {
if (debug.val) LOG.debug("Creating host information from BenchmarkConfig");
m_launchHosts = new HashMap<Integer, Set<Pair<String,Integer>>>();
int site_id = HStoreConstants.FIRST_PARTITION_ID;
for (String host : m_config.hosts) {
if (trace.val) LOG.trace(String.format("Creating host info for %s: %s:%d",
HStoreThreadManager.formatSiteName(site_id), host, HStoreConstants.DEFAULT_PORT));
Set<Pair<String, Integer>> s = new HashSet<Pair<String,Integer>>();
s.add(Pair.of(host, HStoreConstants.DEFAULT_PORT));
m_launchHosts.put(site_id, s);
unique_hosts.add(host);
site_id++;
} // FOR
} else {
if (debug.val) LOG.debug("Retrieving host information from catalog");
m_launchHosts = CatalogUtil.getExecutionSites(this.catalogContext.catalog);
for (Entry<Integer, Set<Pair<String, Integer>>> e : m_launchHosts.entrySet()) {
Pair<String, Integer> p = CollectionUtil.first(e.getValue());
assert(p != null);
if (trace.val)
LOG.trace(String.format("Retrieved host info for %s from catalog: %s:%d",
HStoreThreadManager.formatSiteName(e.getKey()),
p.getFirst(), p.getSecond()));
unique_hosts.add(p.getFirst());
} // FOR
}
// copy the catalog to the servers, but don't bother in local mode
if (m_config.localmode == false) {
// HACK
m_config.hosts = new String[unique_hosts.size()];
unique_hosts.toArray(m_config.hosts);
Set<String> copyto_hosts = new HashSet<String>();
CollectionUtil.addAll(copyto_hosts, unique_hosts);
CollectionUtil.addAll(copyto_hosts, m_config.clients);
Collection<Thread> threads = new ArrayList<Thread>();
// HStoreSite
// IMPORTANT: Don't try to kill things if we're going to profile... for obvious reasons... duh!
if (m_config.profileSiteIds.isEmpty() && m_config.noSites == false) {
for (String host : unique_hosts) {
KillStragglers ks = new KillStragglers(m_config.remoteUser, host, m_config.remotePath, m_config.sshOptions).enableKillAll();
threads.add(new Thread(ks));
Runtime.getRuntime().addShutdownHook(new Thread(ks));
} // FOR
}
// Client
for (String host : m_config.clients) {
KillStragglers ks = new KillStragglers(m_config.remoteUser, host, m_config.remotePath, m_config.sshOptions).enableKillClient();
threads.add(new Thread(ks));
Runtime.getRuntime().addShutdownHook(new Thread(ks));
} // FOR
if (debug.val) LOG.debug("Killing stragglers on " + threads.size() + " hosts");
try {
ThreadUtil.runNewPool(threads, Math.min(25, threads.size()));
} catch (Exception e) {
LogKeys logkey = LogKeys.benchmark_BenchmarkController_UnableToRunRemoteKill;
LOG.l7dlog(Level.FATAL, logkey.name(), e);
LOG.fatal("Couldn't run remote kill operation.", e);
System.exit(-1);
}
// START THE SERVERS
if (m_config.noSites == false) {
this.startSites();
}
} else {
// START A SERVER LOCALLY IN-PROCESS
// VoltDB.Configuration localconfig = new VoltDB.Configuration();
// localconfig.m_pathToCatalog = this.jarFileName.getAbsolutePath();
// m_localserver = null;//new ServerThread(localconfig);
// m_localserver.start();
// m_localserver.waitForInitialization();
}
if (m_loaderClass != null && m_config.noLoader == false) {
ProfileMeasurement stopwatch = new ProfileMeasurement("load").start();
this.startLoader();
stopwatch.stop();
if (this.failed) System.exit(1);
LOG.info(String.format("Completed %s loading phase in %.2f sec",
this.projectBuilder.getProjectName().toUpperCase(),
stopwatch.getTotalThinkTimeSeconds()));
} else if (debug.val && m_config.noLoader) {
LOG.debug("Skipping data loading phase");
}
// Start the clients
if (m_config.noExecute == false) {
this.startClients();
}
// registerInterest(uploader);
}
/**
* Deploy the HStoreSites on the remote nodes
*/
public void startSites() {
LOG.info(makeHeader("BENCHMARK INITIALIZE"));
if (debug.val) LOG.debug("Number of hosts to start: " + m_launchHosts.size());
int hosts_started = 0;
// For each client output option, we'll enable the corresponding
// site config parameter so that we can collect the proper data
for (ProfilingOutput po : BenchmarkControllerUtil.PROFILING_OUTPUTS) {
if (po.siteParam == null) continue;
assert(HStoreConf.isConfParameter(po.clientParam)) : "Invalid Client Param: " + po;
assert(HStoreConf.isConfParameter(po.siteParam)) : "Invalid Site Param: " + po;
String clientOptVal = (String)hstore_conf.get(po.clientParam);
if (clientOptVal != null && clientOptVal.isEmpty() == false) {
if (clientOptVal.equalsIgnoreCase("true")) {
LOG.warn(String.format("The HStoreConf parameter '%s' should be a file path, not a boolean value", po.clientParam));
} else {
m_config.siteParameters.put(po.siteParam, Boolean.TRUE.toString());
}
}
} // FOR
List<String> siteBaseCommand = new ArrayList<String>();
if (hstore_conf.global.sshprefix != null &&
hstore_conf.global.sshprefix.isEmpty() == false) {
siteBaseCommand.add(hstore_conf.global.sshprefix + " && ");
}
siteBaseCommand.add("ant hstore-site");
siteBaseCommand.add("-Dconf=" + m_config.hstore_conf_path);
siteBaseCommand.add("-Dproject=" + this.projectBuilder.getProjectName());
for (Entry<String, String> e : m_config.siteParameters.entrySet()) {
String value = e.getValue();
if (value.startsWith("\"") == false) {
value = '"' + value + '"';
}
String opt = String.format("-D%s=%s", e.getKey(), value);
siteBaseCommand.add(opt);
if (trace.val)
LOG.trace(" " + opt);
} // FOR
for (Entry<Integer, Set<Pair<String, Integer>>> e : m_launchHosts.entrySet()) {
Integer site_id = e.getKey();
Pair<String, Integer> p = CollectionUtil.first(e.getValue());
assert(p != null);
String host = p.getFirst();
String host_id = String.format("site-%02d-%s", site_id, host);
// Check whether this one of the sites that will be started externally
if (m_config.profileSiteIds.contains(site_id)) {
LOG.info(String.format("Skipping HStoreSite %s because it will be started by profiler",
HStoreThreadManager.formatSiteName(site_id)));
continue;
}
LOG.info(String.format("Starting HStoreSite %s on %s",
HStoreThreadManager.formatSiteName(site_id), host));
// String debugString = "";
// if (m_config.listenForDebugger) {
// debugString =
// " -agentlib:jdwp=transport=dt_socket,address=8001,server=y,suspend=n ";
// }
// -agentlib:hprof=cpu=samples,
// depth=32,interval=10,lineno=y,monitor=y,thread=y,force=y,
// file=" + host + "_hprof_tpcc.txt"
// Site Specific Parameters
List<String> siteCommand = new ArrayList<String>(siteBaseCommand);
siteCommand.add("-Dsite.id=" + site_id);
String exec_command[] = SSHTools.convert(m_config.remoteUser, host, m_config.remotePath, m_config.sshOptions, siteCommand);
String fullCommand = StringUtil.join(" ", exec_command);
resultsUploader.setCommandLineForHost(host, fullCommand);
if (trace.val) LOG.trace("START " + HStoreThreadManager.formatSiteName(site_id) + ": " + fullCommand);
sitePSM.startProcess(host_id, exec_command);
hosts_started++;
} // FOR
// WAIT FOR SERVERS TO BE READY
int waiting = hosts_started;
if (waiting > 0) {
LOG.info(String.format("Waiting for %d HStoreSite%s with %d partition%s to finish initialization",
waiting, (waiting > 1 ? "s" : ""),
catalogContext.numberOfPartitions, (catalogContext.numberOfPartitions > 1 ? "s" : "")));
do {
ProcessSetManager.OutputLine line = sitePSM.nextBlocking();
if (line == null) break;
if (line.value.contains(HStoreConstants.SITE_READY_MSG)) {
waiting--;
}
} while (waiting > 0);
if (waiting > 0) {
throw new RuntimeException("Failed to start all HStoreSites. Halting benchmark");
}
}
// If one of them was started on the outside, let's just sleep for a
// few seconds to make sure that our clients can connect
if (m_config.profileSiteIds.isEmpty() == false) {
int sleep = 5000;
LOG.info(String.format("Sleeping for %.1f seconds to let the profiler come online", sleep/1000d));
ThreadUtil.sleep(sleep);
}
if (debug.val) LOG.debug("All remote HStoreSites are initialized");
}
/**
* Invoke the benchmark loader
*/
public void startLoader() {
LOG.info(makeHeader("BENCHMARK LOAD"));
String title = String.format("Starting %s Benchmark Loader - %s / ScaleFactor %.2f",
this.projectBuilder.getProjectName().toUpperCase(),
m_loaderClass.getSimpleName(),
hstore_conf.client.scalefactor);
if (hstore_conf.client.blocking_loader) title += " / Blocking";
LOG.info(title);
final List<String> allLoaderArgs = new ArrayList<String>();
final List<String> loaderCommand = new ArrayList<String>();
// set loader max heap to MAX(1M,6M) based on thread count.
int lthreads = 2;
if (m_config.clientParameters.containsKey("loadthreads")) {
lthreads = Integer.parseInt(m_config.clientParameters.get("loadthreads"));
if (lthreads < 1) lthreads = 1;
if (lthreads > 6) lthreads = 6;
}
int loaderheap = 1024 * lthreads;
if (trace.val) LOG.trace("LOADER HEAP " + loaderheap);
String debugString = "";
if (m_config.listenForDebugger) {
debugString = " -agentlib:jdwp=transport=dt_socket,address=8002,server=y,suspend=n ";
}
if (hstore_conf.global.sshprefix != null &&
hstore_conf.global.sshprefix.isEmpty() == false) {
loaderCommand.add(hstore_conf.global.sshprefix + " && ");
}
loaderCommand.add("java");
loaderCommand.add("-Dhstore.tag=loader");
loaderCommand.add("-XX:-ReduceInitialCardMarks");
loaderCommand.add("-XX:-ReduceInitialCardMarks");
loaderCommand.add("-XX:+HeapDumpOnOutOfMemoryError");
loaderCommand.add("-XX:HeapDumpPath=" + hstore_conf.global.temp_dir);
loaderCommand.add(String.format("-Xmx%dm", loaderheap));
if (hstore_conf.client.jvm_args != null) {
for (String arg : hstore_conf.client.jvm_args.split(" ")) {
loaderCommand.add(arg);
} // FOR
}
if (debugString.isEmpty() == false) loaderCommand.add(debugString);
String classpath = ""; // Disable this so that we just pull from the build dir -> "hstore.jar" + ":" + this.jarFileName;
if (System.getProperty("java.class.path") != null) {
classpath = classpath + ":" + System.getProperty("java.class.path");
}
loaderCommand.add("-cp \"" + classpath + "\"");
loaderCommand.add(m_loaderClass.getCanonicalName());
this.addHostConnections(allLoaderArgs);
allLoaderArgs.add("CONF=" + m_config.hstore_conf_path);
allLoaderArgs.add("NAME=" + this.projectBuilder.getProjectName());
allLoaderArgs.add("BENCHMARK.CONF=" + m_config.benchmark_conf_path);
allLoaderArgs.add("NUMCLIENTS=" + totalNumClients);
allLoaderArgs.add("LOADER=true");
allLoaderArgs.add("EXITONCOMPLETION=false");
if (m_config.statsDatabaseURL != null) {
allLoaderArgs.add("STATSDATABASEURL=" + m_config.statsDatabaseURL);
allLoaderArgs.add("STATSDATABASEUSER=" + m_config.statsDatabaseUser);
allLoaderArgs.add("STATSDATABASEPASS=" + m_config.statsDatabasePass);
allLoaderArgs.add("STATSDATABASEJDBC=" + m_config.statsDatabaseJDBC);
allLoaderArgs.add("STATSPOLLINTERVAL=" + m_config.statsPollInterval);
// LOG.info("Loader Stats Database: " + m_config.statsDatabaseURL);
}
for (Entry<String,String> e : m_config.clientParameters.entrySet()) {
String arg = String.format("%s=%s", e.getKey(), e.getValue());
allLoaderArgs.add(arg);
} // FOR
// RUN THE LOADER
// if (true || m_config.localmode) {
try {
if (trace.val) {
LOG.trace("Loader Class: " + m_loaderClass);
LOG.trace("Parameters: " + StringUtil.join(" ", allLoaderArgs));
}
BenchmarkComponent.main(m_loaderClass,
m_clientFileUploader,
allLoaderArgs.toArray(new String[0]),
true);
} catch (Throwable ex) {
this.failed = true;
throw new RuntimeException("Failed to load data using " + m_loaderClass.getSimpleName(), ex);
}
// }
// else {
// if (debug.val) LOG.debug("Loader Command: " + loaderCommand.toString());
// String[] command = SSHTools.convert(
// m_config.remoteUser,
// m_config.clients[0],
// m_config.remotePath,
// m_config.sshOptions,
// loaderCommand.toString());
// status = ShellTools.cmdToStdOut(command);
// assert(status);
// }
}
/**
* Invoke the benchmark clients on the remote nodes
*/
private void startClients() {
final ArrayList<String> allClientArgs = new ArrayList<String>();
if (hstore_conf.global.sshprefix != null &&
hstore_conf.global.sshprefix.isEmpty() == false) {
allClientArgs.add(hstore_conf.global.sshprefix + " && ");
}
allClientArgs.add("java");
if (m_config.listenForDebugger) {
allClientArgs.add(""); //placeholder for agent lib
}
allClientArgs.add("-Dhstore.tag=client");
allClientArgs.add("-ea");
allClientArgs.add("-XX:-ReduceInitialCardMarks");
allClientArgs.add("-XX:+HeapDumpOnOutOfMemoryError");
allClientArgs.add("-XX:HeapDumpPath=/tmp");
allClientArgs.add(String.format("-Xmx%dm", m_config.clientHeapSize));
// JProfiler
// allClientArgs.add("-agentpath:/home/pavlo/Programs/jprofiler/bin/linux-x64/libjprofilerti.so=port=8849");
if (hstore_conf.client.jvm_args != null) {
for (String arg : hstore_conf.client.jvm_args.split(" ")) {
allClientArgs.add(arg);
} // FOR
}
/*
* This is needed to do database verification at the end of the run. In
* order load the snapshot tables, we need the checksum stuff in the
* native library.
*/
allClientArgs.add("-Djava.library.path=.");
String classpath = ""; // "voltdbfat.jar" + ":" + this.jarFileName;
if (System.getProperty("java.class.path") != null) {
classpath = classpath + ":" + System.getProperty("java.class.path");
}
allClientArgs.add("-cp");
allClientArgs.add("\"" + classpath + "\"");
// The first parameter must be the BenchmarkComponentSet class name
// Follow by the Client class name.
allClientArgs.add(BenchmarkComponentSet.class.getCanonicalName());
allClientArgs.add(m_clientClass.getCanonicalName());
for (Entry<String,String> e : m_config.clientParameters.entrySet()) {
String value = e.getValue();
if (value.startsWith("\"") == false) {
value = '"' + value + '"';
}
String opt = String.format("%s=%s", e.getKey(), value);
allClientArgs.add(opt);
if (trace.val)
LOG.trace(" " + opt);
} // FOR
this.addHostConnections(allClientArgs);
allClientArgs.add("CONF=" + m_config.hstore_conf_path);
allClientArgs.add("NAME=" + this.projectBuilder.getProjectName());
allClientArgs.add("CHECKTRANSACTION=" + m_config.checkTransaction);
allClientArgs.add("CHECKTABLES=" + m_config.checkTables);
allClientArgs.add("LOADER=false");
if (m_config.statsDatabaseURL != null) {
allClientArgs.add("STATSDATABASEURL=" + m_config.statsDatabaseURL);
allClientArgs.add("STATSDATABASEUSER=" + m_config.statsDatabaseUser);
allClientArgs.add("STATSDATABASEPASS=" + m_config.statsDatabasePass);
allClientArgs.add("STATSDATABASEJDBC=" + m_config.statsDatabaseJDBC);
allClientArgs.add("STATSPOLLINTERVAL=" + m_config.statsPollInterval);
// allClientArgs.add("STATSTAG=" + m_config.statsTag);
}
int threads_per_client = hstore_conf.client.threads_per_host;
if (hstore_conf.client.processesperclient_per_partition) {
threads_per_client *= catalogContext.numberOfPartitions;
}
final Map<String, Map<File, File>> sent_files = new ConcurrentHashMap<String, Map<File,File>>();
final AtomicInteger clientIndex = new AtomicInteger(0);
List<Runnable> runnables = new ArrayList<Runnable>();
final Client local_client = (m_clientFileUploader.hasFilesToSend() ? getClientConnection(): null);
// Add all of the client hostnames in our list first so that we can check
// to see whether we have duplicates. If so, then we'll make sure that
// their hostnames are unique in our results print outs
Histogram<String> clientNames = new ObjectHistogram<String>();
for (String clientHost : m_config.clients) {
clientNames.put(clientHost.trim());
} // FOR
Histogram<String> clientNamesIdxs = new ObjectHistogram<String>();
for (int host_idx = 0; host_idx < m_config.clients.length; host_idx++) {
// The clientHost is the hostname that we actaully need to run on
final String clientHost = m_config.clients[host_idx].trim();
// The clientHostId is a unique identifier for a single invocation
// on the clientHost. This is needed so that we can have multiple
// JVMs running at the same host
String _hostId = clientHost;
if (clientNames.get(clientHost) > 1) {
int ctr = (int)clientNamesIdxs.get(clientHost, 0l);
_hostId = String.format("%s-%02d", clientHost, ctr);
clientNamesIdxs.put(clientHost);
}
final String clientHostId = "client-" + _hostId;
m_clients.add(clientHostId);
final List<String> curClientArgs = new ArrayList<String>(allClientArgs);
final List<Integer> clientIds = new ArrayList<Integer>();
for (int j = 0; j < threads_per_client; j++) {
int clientId = clientIndex.getAndIncrement();
m_clientThreads.add(BenchmarkControllerUtil.getClientName(clientHost, clientId));
clientIds.add(clientId);
} // FOR
runnables.add(new Runnable() {
@Override
public void run() {
for (int i = 0, cnt = clientIds.size(); i < cnt; i++) {
if (m_config.listenForDebugger) {
String arg = "-agentlib:jdwp=transport=dt_socket,address="
+ (8003 + i) + ",server=y,suspend=n ";
curClientArgs.set(1, arg);
}
// Check whether we need to send files to this client
int clientId = clientIds.get(i);
if (m_clientFileUploader.hasFilesToSend(clientId)) {
Collection<String> uploadArgs = processClientFileUploads(clientHost, clientId, sent_files);
if (uploadArgs.isEmpty() == false) curClientArgs.addAll(uploadArgs);
if (local_client != null && i % 3 == 0) {
String procName = VoltSystemProcedure.procCallName(NoOp.class);
try {
local_client.callProcedure(new NullCallback(), procName);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
} // FOR
curClientArgs.add("ID=" + StringUtil.join(",", clientIds));
String args[] = SSHTools.convert(m_config.remoteUser, clientHost, m_config.remotePath, m_config.sshOptions, curClientArgs);
String fullCommand = StringUtil.join(" ", args);
resultsUploader.setCommandLineForClient(clientHostId, fullCommand);
if (trace.val) LOG.trace("Client Commnand: " + fullCommand);
clientPSM.startProcess(clientHostId, args);
}
});
} // FOR
m_clientsNotReady.set(m_clientThreads.size());
assert(m_clientThreads.size() == totalNumClients) :
String.format("%d != %d", m_clientThreads.size(), totalNumClients);
// Let 'er rip!
ThreadUtil.runGlobalPool(runnables);
// ----------------------------------------------------------------------------
// RESULT PRINTING SETUP
// ----------------------------------------------------------------------------
// JSON Output
if (hstore_conf.client.output_results_json) {
this.registerInterest(new JSONResultsPrinter(hstore_conf));
}
// CSV Output
if (hstore_conf.client.output_results_csv != null && hstore_conf.client.output_results_csv.isEmpty() == false) {
if (hstore_conf.client.output_results_csv.equalsIgnoreCase("true")) {
LOG.warn("The HStoreConf parameter 'hstore_conf.client.output_csv' should be a file path, not a boolean value");
}
File f = new File(hstore_conf.client.output_results_csv);
this.registerInterest(new CSVResultsPrinter(f));
}
// Default Table Output
if (hstore_conf.client.output_results_table) {
this.registerInterest(new ResultsPrinter(hstore_conf));
}
// Memory Stats Output
if (hstore_conf.client.output_memory != null && hstore_conf.client.output_memory.isEmpty() == false) {
if (hstore_conf.client.output_memory.equalsIgnoreCase("true")) {
LOG.warn("The HStoreConf parameter 'hstore_conf.client.output_memory' should be a file path, not a boolean value");
}
File f = new File(hstore_conf.client.output_memory);
this.registerInterest(new MemoryStatsPrinter(this.getClientConnection(), f));
}
// Table Stats Output
if (hstore_conf.client.output_table_stats_periodically != null && hstore_conf.client.output_table_stats_periodically.isEmpty() == false) {
if (hstore_conf.client.output_table_stats_periodically.equalsIgnoreCase("true")) {
LOG.warn("The HStoreConf parameter 'hstore_conf.client.output_table_stats_periodically' should be a file path, not a boolean value");
}
File f = new File(hstore_conf.client.output_table_stats_periodically);
this.registerInterest(new TableStatsPrinter(this.getClientConnection(), f));
}
// Kill Benchmark on Zero Results
if (m_config.killOnZeroResults) {
if (debug.val) LOG.debug("Will kill benchmark if results are zero for two poll intervals");
ResultsChecker checker = new ResultsChecker(this.failure_observer);
this.registerInterest(checker);
}
// ----------------------------------------------------------------------------
// RESULT PRINTING SETUP
// ----------------------------------------------------------------------------
if (hstore_conf.client.anticache_enable && catalogContext.getEvictableTables().isEmpty() == false) {
this.evictorThread = new PeriodicEvictionThread(catalogContext, getClientConnection(),
hstore_conf.client.anticache_evict_size, m_interested);
}
}
private void addHostConnections(Collection<String> params) {
for (Site catalog_site : catalogContext.sites) {
for (Pair<String, Integer> p : m_launchHosts.get(catalog_site.getId())) {
String address = String.format("%s:%d:%d", p.getFirst(), p.getSecond(), catalog_site.getId());
params.add("HOST=" + address);
if (trace.val)
LOG.trace(String.format("HStoreSite %s: %s", HStoreThreadManager.formatSiteName(catalog_site.getId()), address));
break;
} // FOR
} // FOR
}
private Client getClientConnection() {
// Connect to random host and using a random port that it's listening on
Integer site_id = CollectionUtil.random(m_launchHosts.keySet());
assert(site_id != null);
Pair<String, Integer> p = CollectionUtil.random(m_launchHosts.get(site_id));
assert(p != null);
if (debug.val)
LOG.debug(String.format("Creating new client connection to HStoreSite %s",
HStoreThreadManager.formatSiteName(site_id)));
Client new_client = ClientFactory.createClient(128, null, false, null);
try {
new_client.createConnection(null, p.getFirst(), p.getSecond(), "user", "password");
} catch (Exception ex) {
throw new RuntimeException(String.format("Failed to connect to HStoreSite %s at %s:%d",
HStoreThreadManager.formatSiteName(site_id), p.getFirst(), p.getSecond()));
}
return (new_client);
}
private List<String> processClientFileUploads(String clientHost, int clientId, Map<String, Map<File, File>> sent_files) {
List<String> newArgs = new ArrayList<String>();
Map<File, File> files = null;
for (Entry<String, Pair<File, File>> e : m_clientFileUploader.getFilesToSend(clientId).entrySet()) {
String param = e.getKey();
File local_file = e.getValue().getFirst();
File remote_file = e.getValue().getSecond();
if (local_file.exists() == false) {
LOG.warn(String.format("Not sending %s file to client %d. The local file '%s' does not exist", param, clientId, local_file));
continue;
}
boolean skip = false;
synchronized (sent_files) {
files = sent_files.get(clientHost);
if (files == null) {
files = new HashMap<File, File>();
sent_files.put(clientHost, files);
}
// Check whether we have already written to this remote file on the client host
// If we have, then we need to check whether it's the same local file.
// If it is, then we're ok. If it's not, well then that's a paddlin'...
File previous = files.get(remote_file);
if (previous != null) {
if (previous.equals(local_file)) {
skip = true;
} else {
throw new RuntimeException(String.format("Trying to write two different local files ['%s', '%s'] to the same remote file '%s' on client host '%s'",
local_file, previous, remote_file, clientHost));
}
}
} // SYNCH
if (skip) {
if (debug.val)
LOG.warn(String.format("Skipping duplicate file '%s' on client host '%s'",
local_file, clientHost));
} else {
if (debug.val)
LOG.debug(String.format("Copying %s file '%s' to '%s' on client %s [clientId=%d]",
param, local_file, remote_file, clientHost, clientId));
SSHTools.copyToRemote(local_file, m_config.remoteUser, clientHost, remote_file, m_config.sshOptions);
files.put(remote_file, local_file);
m_clientFilesUploaded.incrementAndGet();
}
if (debug.val) LOG.debug(String.format("Uploaded File Parameter '%s': %s", param, remote_file));
newArgs.add(param + "=" + remote_file.getPath());
} // FOR
return (newArgs);
}
/**
* RUN BENCHMARK
*/
public void runBenchmark() throws Exception {
if (this.stop) return;
LOG.info(makeHeader("BENCHMARK EXECUTE"));
int threadsPerHost = hstore_conf.client.threads_per_host;
if (hstore_conf.client.processesperclient_per_partition) {
threadsPerHost *= catalogContext.numberOfPartitions;
}
String debugOpts = String.format("[hosts=%d, perhost=%d, txnrate=%s", m_config.clients.length,
threadsPerHost,
hstore_conf.client.txnrate);
if (hstore_conf.client.blocking) {
debugOpts += ", concurrent=" + hstore_conf.client.blocking_concurrent;
}
debugOpts += "]";
ProfileMeasurement stopwatch = new ProfileMeasurement("clients").start();
LOG.info(String.format("Starting %s execution with %d %sclient%s %s",
this.projectBuilder.getProjectName().toUpperCase(),
m_clientThreads.size(),
(hstore_conf.client.blocking ? "blocking " : ""),
(m_clientThreads.size() > 1 ? "s" : ""),
debugOpts));
if (m_config.statsDatabaseURL != null) {
LOG.info("Client Stats Database: " + m_config.statsDatabaseURL);
}
// ---------------------------------------------------------------------------------
// HACK
int gdb_sleep = 0;
if (gdb_sleep > 0) {
LOG.info("Sleeping for " + gdb_sleep + " waiting for GDB");
ThreadUtil.sleep(gdb_sleep*1000);
}
currentResults = new BenchmarkResults(hstore_conf.client.interval,
hstore_conf.client.duration,
m_clientThreads.size());
currentResults.setEnableBasePartitions(hstore_conf.client.output_basepartitions);
long nextIntervalTime = hstore_conf.client.interval;
for (int i = 0; i < m_clients.size(); i++) {
ClientStatusThread t = new ClientStatusThread(this, i);
t.setUncaughtExceptionHandler(this.exceptionHandler);
t.start();
m_statusThreads.add(t);
} // FOR
if (debug.val)
LOG.debug(String.format("Started %d %s",
m_statusThreads.size(), ClientStatusThread.class.getSimpleName()));
// Get a connection to the cluster
Client local_client = this.getClientConnection();
// Spin on whether all clients are ready
while (m_clientsNotReady.get() > 0 && this.stop == false) {
if (debug.val)
LOG.debug(String.format("Waiting for %d clients to come online", m_clientsNotReady.get()));
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
if (this.stop == false) throw ex;
return;
}
} // WHILE
stopwatch.stop();
LOG.info(String.format("Initialized %d %s client threads in %.2f sec",
m_clientThreads.size(),
this.projectBuilder.getProjectName().toUpperCase(),
stopwatch.getTotalThinkTimeSeconds()));
if (this.stop) {
if (debug.val) LOG.debug("Stop flag is set to true");
return;
}
if (debug.val)
LOG.debug("All clients are on-line and ready to go!");
if (m_clientFilesUploaded.get() > 0) {
LOG.info(String.format("Uploaded %d files to clients", m_clientFilesUploaded.get()));
}
// Reset some internal information at the cluster
this.resetCluster(local_client);
// Start up all the clients
if (debug.val)
LOG.debug(String.format("Telling %d clients to start executing", m_clients.size()));
for (String clientName : m_clients) {
if (debug.val)
LOG.debug(String.format("Sending %s to %s", ControlCommand.START, clientName));
clientPSM.writeToProcess(clientName, ControlCommand.START.name());
} // FOR
// Warm-up
if (hstore_conf.client.warmup > 0) {
LOG.info(String.format("Letting system warm-up for %.01f seconds", hstore_conf.client.warmup / 1000.0));
try {
Thread.sleep(hstore_conf.client.warmup);
} catch (InterruptedException e) {
if (debug.val) LOG.debug("Warm-up was interrupted!");
}
if (this.stop == false) {
// Recompute Markovs
// We don't need to save them to a file though
if (m_config.markovRecomputeAfterWarmup) {
// Note that this won't work the way that we want it to because it will get queued up
// in the Dtxn.Coordinator and we have no way to set the priority of it
// This means that it will have to wait until *all* of the previously submitted multi-partition
// transactions get executed before it will get executed... we really need to write our
// own transaction coordinator...
LOG.info("Requesting HStoreSites to recalculate Markov models after warm-up");
try {
this.recomputeMarkovs(local_client, false);
} catch (Exception ex) {
throw new RuntimeException("Failed to recompute Markov models", ex);
}
}
// Schedule PeriodicEvictionThread
if (this.evictorThread != null) {
int interval = hstore_conf.client.anticache_evict_interval;
this.threadPool.scheduleWithFixedDelay(this.evictorThread,
interval, interval, TimeUnit.MILLISECONDS);
}
// Reset the counters
for (String clientName : m_clients)
clientPSM.writeToProcess(clientName, ControlCommand.CLEAR.name());
LOG.info("Starting benchmark stats collection");
}
}
//
long startTime = System.currentTimeMillis();
nextIntervalTime += startTime;
long nowTime = startTime;
while (m_pollIndex < m_pollCount && this.stop == false) {
// wait some time
long sleep = nextIntervalTime - nowTime;
try {
if (this.stop == false && sleep > 0) {
if (debug.val) LOG.debug(String.format("Sleeping for %.1f sec [pollIndex=%d]",
sleep / 1000d, m_pollIndex));
Thread.sleep(sleep);
}
} catch (InterruptedException e) {
// Ignore...
if (debug.val) LOG.debug(String.format("Interrupted! [pollIndex=%d / stop=%s]",
m_pollIndex, this.stop));
} finally {
if (debug.val) LOG.debug(String.format("Awake! [pollIndex=%d / stop=%s]",
m_pollIndex, this.stop));
}
nowTime = System.currentTimeMillis();
// check if the next interval time has arrived
if (nowTime >= nextIntervalTime) {
m_pollIndex++;
// make all the clients poll
if (debug.val) LOG.debug(String.format("Sending %s to %d clients", ControlCommand.POLL, m_clients.size()));
for (String clientName : m_clients)
clientPSM.writeToProcess(clientName, ControlCommand.POLL.name() + " " + m_pollIndex);
// get ready for the next interval
nextIntervalTime = hstore_conf.client.interval * (m_pollIndex + 1) + startTime;
}
} // WHILE
if (this.stop == false) {
this.postProcessBenchmark(local_client);
}
// else if (this.failed == true) {
// this.invokeStatusSnapshot(local_client);
// }
this.stop = true;
if (m_config.noShutdown == false && this.failed == false) sitePSM.prepareShutdown(false);
// shut down all the clients
clientPSM.prepareShutdown(false);
boolean first = true;
for (String clientName : m_clients) {
if (first && m_config.noShutdown == false) {
clientPSM.writeToProcess(clientName, ControlCommand.SHUTDOWN.name());
first = false;
} else {
clientPSM.writeToProcess(clientName, ControlCommand.STOP.name());
}
} // FOR
LOG.info("Waiting for " + m_clients.size() + " clients to finish");
clientPSM.joinAll();
if (this.failed == false) {
if (this.resultsToRead.getCount() > 0) {
LOG.info(String.format("Waiting for %d status threads to finish [remaining=%d]",
m_statusThreads.size(), this.resultsToRead.getCount()));
try {
this.resultsToRead.await();
for (ClientStatusThread t : m_statusThreads) {
if (t.isFinished() == false) {
// if (debug.val)
LOG.info(String.format("ClientStatusThread '%s' asked to finish up", t.getName()));
// System.err.println(StringUtil.join("\n", t.getStackTrace()));
t.interrupt();
t.join();
}
} // FOR
} catch (InterruptedException e) {
LOG.warn(e);
}
}
// Print out the final results
// if (debug.val)
if (hstore_conf.client.output_basepartitions || hstore_conf.client.output_status) {
LOG.info("Computing final benchmark results...");
}
for (BenchmarkInterest interest : m_interested) {
String finalResults = interest.formatFinalResults(currentResults);
if (finalResults != null) System.out.println(finalResults);
} // FOR
} else if (debug.val) {
LOG.debug("Benchmark failed. Not displaying final results");
}
}
/**
* Perform various post-processing tasks that we may need
* @param client
* @throws Exception
*/
private void postProcessBenchmark(Client client) throws Exception {
if (debug.val) LOG.debug("Performing post-processing on benchmark");
// We have to tell all our clients to pause first
clientPSM.writeToAll(ControlCommand.PAUSE.name());
// Then tell the cluster to drain all txns
if (debug.val) LOG.debug("Draining execution queues on cluster");
ClientResponse cresponse = null;
String procName = VoltSystemProcedure.procCallName(Quiesce.class);
try {
cresponse = client.callProcedure(procName);
} catch (Exception ex) {
throw new Exception("Failed to execute " + procName, ex);
}
assert(cresponse.getStatus() == Status.OK) :
String.format("Failed to quiesce cluster!\n%s", cresponse);
// DUMP RESPONSE ENTRIES
if (hstore_conf.client.output_responses != null) {
File outputPath = new File(hstore_conf.client.output_responses);
this.writeResponseEntries(client, outputPath);
}
// DUMP DATABASE
if (m_config.dumpDatabase && this.stop == false) {
assert(m_config.dumpDatabaseDir != null);
try {
client.callProcedure(VoltSystemProcedure.procCallName(DatabaseDump.class),
m_config.dumpDatabaseDir);
} catch (Exception ex) {
throw new Exception("Failed to dump database contents", ex);
}
}
// DUMP PROFILING INFORMATION
for (ProfilingOutput po : BenchmarkControllerUtil.PROFILING_OUTPUTS) {
Object output = hstore_conf.get(po.clientParam);
if (output != null) {
this.writeProfilingData(client, po.key, new File(output.toString()));
}
} // FOR
// Recompute MarkovGraphs
if (m_config.markovRecomputeAfterEnd && this.stop == false) {
this.recomputeMarkovs(client, true);
}
}
private void writeResponseEntries(Client client, File outputPath) throws Exception {
// We know how many clients that we need to get results back from
CountDownLatch latch = new CountDownLatch(this.totalNumClients);
for (ClientStatusThread t : m_statusThreads) {
t.addResponseEntriesLatch(latch);
} // FOR
// Now tell everybody that part is over and we want them to dump their
// results back to us
clientPSM.writeToAll(ControlCommand.DUMP_TXNS.name());
// Wait until we get all of the responses that we need
boolean result = latch.await(10, TimeUnit.SECONDS);
if (result == false) {
LOG.warn(String.format("Only got %d out of %d response dumps from clients",
this.totalNumClients-latch.getCount(), this.totalNumClients));
}
// Merge sort them
int count = 0;
for (ClientStatusThread t : m_statusThreads) {
count += t.getResponseEntries().size();
} // FOR
LOG.info(String.format("Merging %d ClientResponse entries together...", count));
ResponseEntries fullDump = new ResponseEntries();
for (ClientStatusThread t : m_statusThreads) {
fullDump.addAll(t.getResponseEntries());
} // FOR
if (fullDump.isEmpty()) {
LOG.warn("No ClientResponse results were returned!");
return;
}
// Convert to a VoltTable and then write out to a CSV file
LOG.info(String.format("Writing %d ClientResponse entries to '%s'", fullDump.size(), outputPath));
String txnNames[] = currentResults.getTransactionNames();
FileWriter out = new FileWriter(outputPath);
VoltTable vt = ResponseEntries.toVoltTable(fullDump, txnNames);
VoltTableUtil.csv(out, vt, true);
out.close();
if (debug.val)
LOG.debug(String.format("Wrote %d response entries information to '%s'",
fullDump.size(), outputPath));
}
private void writeProfilingData(Client client, SysProcSelector sps, File outputPath) throws Exception {
Object params[];
String sysproc;
// The AntiCache history is as special case here.
// We need to use the correct sysproc instead of @Statistics
if (sps == SysProcSelector.ANTICACHEEVICTIONS) {
sysproc = VoltSystemProcedure.procCallName(EvictHistory.class);
params = new Object[]{ };
}
else if (sps == SysProcSelector.ANTICACHEACCESS) {
sysproc = VoltSystemProcedure.procCallName(EvictedAccessHistory.class);
params = new Object[]{ };
}
else {
sysproc = VoltSystemProcedure.procCallName(Statistics.class);
params = new Object[]{ sps.name(), 0 };
}
// Grab the data that we need from the cluster
ClientResponse cresponse;
try {
cresponse = client.callProcedure(sysproc, params);
} catch (Exception ex) {
throw new Exception("Failed to execute " + sysproc, ex);
}
assert(cresponse.getStatus() == Status.OK) :
String.format("Failed to get %s stats\n%s", sps, cresponse);
assert(cresponse.getResults().length == 1) :
String.format("Failed to get %s stats\n%s", sps, cresponse);
VoltTable vt = cresponse.getResults()[0];
// Combine results (optional)
boolean needCombine = false;
if (sps == SysProcSelector.TXNPROFILER) needCombine = hstore_conf.client.output_txn_profiling_combine;
if (sps == SysProcSelector.PLANNERPROFILER) needCombine = hstore_conf.client.output_planner_profiling_combine;
if (sps == SysProcSelector.TXNCOUNTER) needCombine = hstore_conf.client.output_txn_counters_combine;
if (sps == SysProcSelector.SPECEXECPROFILER) needCombine = hstore_conf.client.output_specexec_profiling_combine;
if (needCombine) {
String combineColumn;
if (sps == SysProcSelector.SPECEXECPROFILER) {
combineColumn = "SPECULATE_TYPE";
} else {
combineColumn = "PROCEDURE";
}
int offset = vt.getColumnIndex(combineColumn);
VoltTable.ColumnInfo cols[] = Arrays.copyOfRange(VoltTableUtil.extractColumnInfo(vt), offset, vt.getColumnCount());
Map<String, Object[]> totalRows = new TreeMap<String, Object[]>();
Map<String, List<Double>[]> stdevRows = new HashMap<String, List<Double>[]>();
while (vt.advanceRow()) {
String procName = vt.getString(offset);
Object row[] = totalRows.get(procName);
List<Double> stdevs[] = stdevRows.get(procName);
if (row == null) {
row = new Object[cols.length];
row[0] = procName;
for (int i = 1; i < row.length; i++) {
row[i] = new Long(0l);
} // FOR
totalRows.put(procName, row);
@SuppressWarnings("unchecked")
List<Double> temp[] = (List<Double>[])new ArrayList<?>[cols.length];
stdevs = temp;
stdevRows.put(procName, stdevs);
}
for (int i = 1; i < row.length; i++) {
if (vt.getColumnName(offset + i).endsWith("STDEV")) {
if (stdevs[i] == null) stdevs[i] = new ArrayList<Double>();
stdevs[i].add(vt.getDouble(offset + i));
// stdevs[i].put(vt.getDouble(offset + i), vt.getLong(offset + 1));
} else if (vt.getColumnType(offset + i) == VoltType.STRING) {
row[i] = vt.getString(offset + i);
} else if (vt.getColumnType(offset + i) == VoltType.FLOAT) {
row[i] = vt.getDouble(offset + i);
} else {
//LOG.info(String.format("offset: %d, i: %d, VoltTable: %s,",offset,i, vt));
//System.err.println(String.format("offset: %d, i: %d, VoltTable: %s,",offset,i, vt));
row[i] = ((Long)row[i]) + vt.getLong(offset + i);
}
} // FOR
} // WHILE
// HACK: Take the weighted average stddev.
for (String procName : totalRows.keySet()) {
// Histogram<Double> stdevs[] = stdevRows.get(procName);
List<Double> stdevs[] = stdevRows.get(procName);
Object row[] = totalRows.get(procName);
for (int i = 1; i < row.length; i++) {
if (stdevs[i] != null) {
row[i] = MathUtil.arithmeticMean(CollectionUtil.toDoubleArray(stdevs[i]));
if (trace.val)
LOG.trace(String.format("%s STDEV -> %s -> %s", procName, stdevs[i], row[i]));
}
} // FOR
} // FOR
vt = new VoltTable(cols);
for (Object[] row : totalRows.values()) {
vt.addRow(row);
} // FOR
}
// Write out CSV
FileWriter out = new FileWriter(outputPath);
VoltTableUtil.csv(out, vt, true);
out.close();
LOG.info(String.format("Wrote %s information to '%s'", sps, outputPath));
return;
}
private ClientResponse[] invokeSysProcs(Client client, Class<? extends VoltSystemProcedure> sysprocs[], Object params[][]) {
ClientResponse cr[] = new ClientResponse[sysprocs.length];
for (int i = 0; i < sysprocs.length; i++) {
String procName = VoltSystemProcedure.procCallName(sysprocs[i]);
try {
cr[i] = client.callProcedure(procName, params[i]);
} catch (Exception ex) {
LOG.error("Failed to execute sysproc " + procName, ex);
return (null);
}
assert(cr[i].getStatus() == Status.OK) :
String.format("Unexpected response status %s for sysproc %s", cr[i].getStatus(), procName);
} // FOR
return (cr);
}
@SuppressWarnings("unused")
private void invokeStatusSnapshot(Client client) {
@SuppressWarnings("unchecked")
Class<VoltSystemProcedure> sysprocs[] = (Class<VoltSystemProcedure>[])new Class<?>[]{
ExecutorStatus.class,
};
Object params[][] = { { 0 } };
this.invokeSysProcs(client, sysprocs, params);
}
private void resetCluster(Client client) {
if (debug.val)
LOG.debug("Resetting internal profiling and invoking garbage collection on cluster");
@SuppressWarnings("unchecked")
Class<VoltSystemProcedure> sysprocs[] = (Class<VoltSystemProcedure>[])new Class<?>[]{
ResetProfiling.class,
GarbageCollection.class
};
Object params[][] = { { }, { } };
this.invokeSysProcs(client, sysprocs, params);
}
/**
* Instruct the cluster to recompute the edge weight probabilities for its markov models.
* If retrieveFiles is set to true, then the BenchmarkController automatically will download
* the updated models and write them to a temp directory on the BenchmarkController's host.
* @param client
* @param retrieveFiles
*/
private void recomputeMarkovs(Client client, boolean retrieveFiles) {
LOG.info("Requesting HStoreSites to recalculate Markov models");
ClientResponse cr = null;
String procName = VoltSystemProcedure.procCallName(MarkovUpdate.class);
try {
cr = client.callProcedure(procName, true);
} catch (Exception ex) {
LOG.error("Failed to recompute MarkovGraphs", ex);
return;
}
assert(cr != null);
if (retrieveFiles == false) return;
File outputDir = FileUtil.join(hstore_conf.global.temp_dir, "markovs",
this.projectBuilder.getProjectName());
FileUtil.makeDirIfNotExists(outputDir);
// The return should be a list of SiteIds->RemotePath
// We just need to then pull down the files and then combine them into
// a single MarkovGraphContainer
Map<Integer, File> markovs = new HashMap<Integer, File>();
List<Pair<String, File>> files_to_remove = new ArrayList<Pair<String, File>>();
VoltTable results[] = cr.getResults();
assert(results.length == 1);
while (results[0].advanceRow()) {
int site_id = (int)results[0].getLong(0);
int partition_id = (int)results[0].getLong(1);
File remote_path = new File(results[0].getString(2));
// boolean is_global = (results[0].getLong(3) == 1);
Pair<String, Integer> p = CollectionUtil.first(m_launchHosts.get(site_id));
assert(p != null) : "Invalid SiteId " + site_id;
if (debug.val)
LOG.debug(String.format("Retrieving %s file '%s' from %s",
MarkovGraphsContainer.class.getSimpleName(),
remote_path, HStoreThreadManager.formatSiteName(site_id)));
SSHTools.copyFromRemote(outputDir, m_config.remoteUser, p.getFirst(), remote_path, m_config.sshOptions);
File local_file = new File(outputDir + "/" + remote_path.getName());
markovs.put(partition_id, local_file);
files_to_remove.add(Pair.of((String)null, local_file));
files_to_remove.add(Pair.of(p.getFirst(), remote_path));
} // FOR
File new_output = FileUtil.join(outputDir.getPath(),
this.projectBuilder.getProjectName() + "-new.markovs");
if (debug.val)
LOG.debug(String.format("Writing %d updated MarkovGraphsContainers to '%s'",
markovs.size(), new_output));
MarkovGraphsContainerUtil.combine(markovs, new_output, catalogContext.database);
// Clean up the remote files
for (Pair<String, File> p : files_to_remove) {
if (p.getFirst() == null) {
p.getSecond().delete();
} else {
SSHTools.deleteFile(m_config.remoteUser, p.getFirst(), p.getSecond(), m_config.sshOptions);
}
} // FOR
}
/**
* Cleanup Benchmark
*/
public synchronized void cleanUpBenchmark() {
// if (this.cleaned) return;
if (m_config.noExecute == false) {
if (debug.val)
LOG.warn("Killing clients");
clientPSM.shutdown();
}
if (m_config.noShutdown == false && this.failed == false) {
if (debug.val)
LOG.warn("Killing HStoreSites");
sitePSM.shutdown();
}
this.cleaned = true;
}
/**
*
* @return A ResultSet instance for the ongoing or just finished benchmark run.
*/
public BenchmarkResults getResults() {
assert(currentResults != null);
synchronized(currentResults) {
return currentResults.copy();
}
}
/** Call dump on each of the servers */
public void tryDumpAll() {
Client dumpClient = ClientFactory.createClient();
for (String host : m_config.hosts) {
try {
dumpClient.createConnection(null, host, HStoreConstants.DEFAULT_PORT, "program", "password");
dumpClient.callProcedure("@dump");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ProcCallException e) {
e.printStackTrace();
}
}
}
/**
* Read a MySQL connection URL from a file named "mysqlp".
* Look for the file in a few places, then try to read the first,
* and hopefully only, line from the file.
*
* @param remotePath Path to the volt binary files.
* @return Two connection string URLs (can't be null).
* @throws RuntimeException with an error message on failure.
*/
static String[] readConnectionStringFromFile(String remotePath) {
String filename = "mysqlp";
// try the current dir
File f = new File(filename);
if (f.isFile() == false) {
// try voltbin from the current dir
f = new File(remotePath + filename);
if (f.isFile() == false) {
// try the home voltbin
String path = System.getProperty("user.home");
path += "/" + remotePath + filename;
f = new File(path);
}
}
if (f.isFile() == false) {
String msg = "Cannot find suitable reporting database connection string file";
throw new RuntimeException(msg);
}
if (f.canRead() == false) {
String msg = "Reporting database connection string file at \"" +
f.getPath() + "\" cannot be read (permissions).";
throw new RuntimeException(msg);
}
String[] retval = new String[2];
try {
FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
retval[0] = br.readLine().trim();
retval[1] = br.readLine().trim();
} catch (IOException e) {
String msg = "Reporting database connection string file at \"" +
f.getPath() + "\" cannot be read (read error).";
throw new RuntimeException(msg);
}
if ((retval[0].length() == 0) || (retval[1].length() == 0)){
String msg = "Reporting database connection string file at \"" +
f.getPath() + "\" seems to be (partly) empty.";
throw new RuntimeException(msg);
}
return retval;
}
public static void main(final String[] vargs) throws Exception {
int hostCount = 1;
int sitesPerHost = 2;
int k_factor = 0;
int clientInitialPollingDelay = 10000;
String sshOptions = "";
String remotePath = "voltbin/";
String remoteUser = null; // null implies current local username
String projectBuilderClassname = TPCCProjectBuilder.class.getCanonicalName();
File catalogPath = null;
String partitionPlanPath = null;
boolean listenForDebugger = false;
int serverHeapSize = 2048;
int clientHeapSize = 1024;
boolean localmode = false;
boolean compileBenchmark = true;
boolean compileOnly = false;
boolean useCatalogHosts = false;
String workloadTrace = null;
int num_partitions = 0;
String backend = "jni";
String snapshotPath = null;
String snapshotFrequency = null;
String snapshotPrefix = null;
int snapshotRetain = -1;
float checkTransaction = 0;
boolean checkTables = false;
String statsTag = null;
String statsDatabaseURL = null;
String statsDatabaseUser = null;
String statsDatabasePass = null;
String statsDatabaseJDBC = null;
int statsPollInterval = 1000;
String applicationName = null;
String subApplicationName = null;
boolean noSites = false;
boolean noLoader = false;
boolean noUploading = false;
boolean noExecute = false;
boolean noShutdown = false;
boolean killOnZeroResults = false;
CatalogContext catalogContext = null;
// HStoreConf Path
File hstore_conf_path = null;
// Benchmark Conf Path
String benchmark_conf_path = null;
// Markov Stuff
String markov_path = null;
String markov_thresholdsPath = null;
Double markov_thresholdsValue = null;
boolean markov_recomputeAfterEnd = false;
boolean markov_recomputeAfterWarmup = false;
// Deferrable Queries
String deferrable[] = null;
// Evictable Tables
String evictable[] = null;
// Batch Evictable Tables
String[] batchEvictable = null;
boolean dumpDatabase = false;
String dumpDatabaseDir = null;
// List of SiteIds that we won't start because they'll be started by the profiler
Set<Integer> profileSiteIds = new HashSet<Integer>();
Map<String, String> clientParams = new LinkedHashMap<String, String>();
List<String> clientHosts = new ArrayList<String>();
Map<String, String> siteParams = new LinkedHashMap<String, String>();
List<String> siteHosts = new ArrayList<String>();
for (String arg : vargs) {
String[] parts = arg.split("=",2);
for (int i = 0; i < parts.length; i++)
parts[1] = parts[1].trim();
if (parts.length == 1) {
continue;
} else if (parts[1].startsWith("${")) {
continue;
/* HStoreConf File Path */
} else if (parts[0].equalsIgnoreCase("CONF")) {
hstore_conf_path = new File(parts[1]);
/* Benchmark Configuration File Path */
} else if (parts[0].equalsIgnoreCase(HStoreConstants.BENCHMARK_PARAM_PREFIX + "CONF")) {
benchmark_conf_path = parts[1];
/* Whether to kill the benchmark if we get consecutive intervals with zero results */
} else if (parts[0].equalsIgnoreCase("KILLONZERO")) {
killOnZeroResults = Boolean.parseBoolean(parts[1]);
/*
* Whether or not to check the result of each transaction.
*/
} else if (parts[0].equalsIgnoreCase("CHECKTRANSACTION")) {
checkTransaction = Float.parseFloat(parts[1]);
/*
* Whether or not to check all the tables at the end.
*/
} else if (parts[0].equalsIgnoreCase("CHECKTABLES")) {
checkTables = Boolean.parseBoolean(parts[1]);
} else if (parts[0].equalsIgnoreCase("LOCAL")) {
/*
* The number of Volt servers to start.
* Can be less then the number of provided hosts
*/
localmode = Boolean.parseBoolean(parts[1]);
} else if (parts[0].equalsIgnoreCase("HOSTCOUNT")) {
/*
* The number of Volt servers to start.
* Can be less then the number of provided hosts
*/
hostCount = Integer.parseInt(parts[1]);
} else if (parts[0].equalsIgnoreCase("SITESPERHOST")) {
/*
* The number of execution sites per host
*/
sitesPerHost = Integer.parseInt(parts[1]);
} else if (parts[0].equalsIgnoreCase("KFACTOR")) {
/*
* The number of partition replicas (k-factor)
*/
k_factor = Integer.parseInt(parts[1]);
}
else if (parts[0].equalsIgnoreCase("CLIENTHEAP")) {
/*
* The number of client processes per client host
*/
clientHeapSize = Integer.parseInt(parts[1]);
} else if (parts[0].equalsIgnoreCase("SERVERHEAP")) {
/*
* The number of client processes per client host
*/
serverHeapSize = Integer.parseInt(parts[1]);
}
// Name of the ProjectBuilder class for this benchmark.
else if (parts[0].equalsIgnoreCase(HStoreConstants.BENCHMARK_PARAM_PREFIX + "BUILDER")) {
projectBuilderClassname = parts[1];
}
// Options used when logging into client/server hosts
else if (parts[0].equalsIgnoreCase("SSHOPTIONS")) {
sshOptions = parts[1];
}
// Directory on the NFS host where the H-Store files are stored
else if (parts[0].equalsIgnoreCase("REMOTEPATH")) {
remotePath = parts[1];
}
// User that runs volt on remote client and host machines
else if (parts[0].equalsIgnoreCase("REMOTEUSER")) {
remoteUser = parts[1];
}
// Name of a host to be used for Volt servers
else if (parts[0].equalsIgnoreCase("HOST")) {
String hostnport[] = parts[1].split("\\:",2);
siteHosts.add(hostnport[0]);
} else if (parts[0].equalsIgnoreCase("LISTENFORDEBUGGER")) {
listenForDebugger = Boolean.parseBoolean(parts[1]);
} else if (parts[0].equalsIgnoreCase("BACKEND")) {
backend = parts[1];
} else if (parts[0].equalsIgnoreCase("SNAPSHOTPATH")) {
snapshotPath = parts[1];
} else if (parts[0].equalsIgnoreCase("SNAPSHOTFREQUENCY")) {
snapshotFrequency = parts[1];
} else if (parts[0].equalsIgnoreCase("SNAPSHOTPREFIX")) {
snapshotPrefix = parts[1];
} else if (parts[0].equalsIgnoreCase("SNAPSHOTRETAIN")) {
snapshotRetain = Integer.parseInt(parts[1]);
} else if (parts[0].equalsIgnoreCase("NUMCONNECTIONS")) {
clientParams.put(parts[0], parts[1]);
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEURL")) {
statsDatabaseURL = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEJDBC")) {
statsDatabaseJDBC = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEUSER")) {
statsDatabaseUser = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASEPASS")) {
statsDatabasePass = parts[1];
}
else if (parts[0].equalsIgnoreCase("STATSDATABASETAG")) {
statsTag = parts[1];
}
else if (parts[0].equalsIgnoreCase("CATALOG")) {
catalogPath = new File(parts[1]);
catalogContext = CatalogUtil.loadCatalogContextFromJar(catalogPath);
assert(catalogContext != null);
num_partitions = catalogContext.numberOfPartitions;
}
else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_PARTITION_PLAN)) {
partitionPlanPath = parts[1];
clientParams.put(ArgumentsParser.PARAM_PARTITION_PLAN, parts[1]);
siteParams.put(ArgumentsParser.PARAM_PARTITION_PLAN, parts[1]);
siteParams.put(ArgumentsParser.PARAM_PARTITION_PLAN_APPLY, "true");
}
else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_PARTITION_PLAN_NO_SECONDARY)) {
clientParams.put(ArgumentsParser.PARAM_PARTITION_PLAN_NO_SECONDARY, parts[1]);
siteParams.put(ArgumentsParser.PARAM_PARTITION_PLAN_NO_SECONDARY, parts[1]);
}
else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_PARTITION_PLAN_IGNORE_MISSING)) {
clientParams.put(ArgumentsParser.PARAM_PARTITION_PLAN_IGNORE_MISSING, parts[1]);
siteParams.put(ArgumentsParser.PARAM_PARTITION_PLAN_IGNORE_MISSING, parts[1]);
}
/*
* Whether to compile the benchmark jar
*/
else if (parts[0].equalsIgnoreCase("COMPILE")) {
compileBenchmark = Boolean.parseBoolean(parts[1]);
}
/*
* Whether to compile only the benchmark jar and then quit
*/
else if (parts[0].equalsIgnoreCase("COMPILEONLY")) {
compileOnly = Boolean.parseBoolean(parts[1]);
}
/*
* Launch the ExecutionSites using the hosts that are in the catalog
*/
else if (parts[0].equalsIgnoreCase("CATALOGHOSTS")) {
useCatalogHosts = Boolean.parseBoolean(parts[1]);
}
/*
* List of evictable tables
*/
else if (parts[0].equalsIgnoreCase("EVICTABLE")) {
LOG.info("EVICTABLE: " + parts[1]);
evictable = parts[1].split(",");
}
/*
* List of batch evictable tables
*/
else if (parts[0].equalsIgnoreCase("BATCHEVICTABLE")) {
LOG.info("BATCHEVICTABLE: " + parts[1]);
batchEvictable = parts[1].split(",");
}
/*
* List of deferrable queries
* Format: <ProcedureName>.<StatementName>
*/
else if (parts[0].equalsIgnoreCase("DEFERRABLE")) {
if (debug.val) LOG.debug("DEFERRABLE: " + parts[1]);
deferrable = parts[1].split(",");
}
/* Disable starting the database cluster */
else if (parts[0].equalsIgnoreCase("NOSITES") || parts[0].equalsIgnoreCase("NOSTART")) {
// HACK: If they stay "nostart=true", then we'll allow that
if (parts[1].equalsIgnoreCase("true")) {
noSites = true;
}
// Otherwise we expect a list of siteIds
else {
for (String s : parts[1].split(",")) {
profileSiteIds.add(Integer.valueOf(s));
} // FOR
}
/* Disable executing the data loader */
} else if (parts[0].equalsIgnoreCase("NOLOADER")) {
noLoader = Boolean.parseBoolean(parts[1]);
// LOG.info("NOLOADER = " + noLoader);
/* Run the loader but disable uploading tuples */
} else if (parts[0].equalsIgnoreCase("NOUPLOADING")) {
noUploading = Boolean.parseBoolean(parts[1]);
clientParams.put(parts[0].toUpperCase(), parts[1]);
/* Disable workload execution */
} else if (parts[0].equalsIgnoreCase("NOEXECUTE")) {
noExecute = Boolean.parseBoolean(parts[1]);
// Disable sending the shutdown command at the end of the benchmark run
} else if (parts[0].equalsIgnoreCase("NOSHUTDOWN")) {
noShutdown = Boolean.parseBoolean(parts[1]);
siteParams.put("site.status_kill_if_hung", "false");
/* Workload Trace Output */
} else if (parts[0].equalsIgnoreCase("TRACE")) {
workloadTrace = parts[1];
siteParams.put(ArgumentsParser.PARAM_WORKLOAD_OUTPUT, parts[1]);
/* Markov Model File */
} else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_MARKOV)) {
markov_path = parts[1];
siteParams.put(parts[0], parts[1]);
/* Markov Confidence Thresholds File */
} else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_MARKOV_THREADS)) {
markov_thresholdsPath = parts[1];
siteParams.put(parts[0].toLowerCase(), parts[1]);
/* Markov Confidence Thresholds Value */
} else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_MARKOV_THRESHOLDS_VALUE)) {
markov_thresholdsValue = Double.valueOf(parts[1]);
siteParams.put(parts[0].toLowerCase(), parts[1]);
/* Recompute Markovs After End */
} else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_MARKOV_RECOMPUTE_END)) {
markov_recomputeAfterEnd = Boolean.parseBoolean(parts[1]);
/* Recompute Markovs After Warmup Period*/
} else if (parts[0].equalsIgnoreCase(ArgumentsParser.PARAM_MARKOV_RECOMPUTE_WARMUP)) {
markov_recomputeAfterWarmup = Boolean.parseBoolean(parts[1]);
} else if (parts[0].equalsIgnoreCase("DUMPDATABASE")) {
dumpDatabase = Boolean.parseBoolean(parts[1]);
} else if (parts[0].equalsIgnoreCase("DUMPDATABASEDIR")) {
dumpDatabaseDir = parts[1];
} else if (parts[0].equalsIgnoreCase(HStoreConstants.BENCHMARK_PARAM_PREFIX + "INITIAL_POLLING_DELAY")) {
clientInitialPollingDelay = Integer.parseInt(parts[1]);
} else {
clientParams.put(parts[0].toLowerCase(), parts[1]);
}
}
// Initialize HStoreConf
assert(hstore_conf_path != null) : "Missing HStoreConf file";
HStoreConf hstore_conf = HStoreConf.init(hstore_conf_path, vargs);
if (trace.val)
LOG.trace("HStore Conf '" + hstore_conf_path.getAbsolutePath() + "'\n" + hstore_conf.toString(true));
if (hstore_conf.client.duration < 1000) {
LOG.error("Duration is specified in milliseconds");
System.exit(-1);
}
else if (hstore_conf.client.threads_per_host <= 0) {
LOG.error("Invalid number of threads per host '" + hstore_conf.client.threads_per_host + "'");
System.exit(-1);
}
// The number of client hosts to place client processes on
int clientCount = hstore_conf.client.count;
// Comma or colon separated list of the hostnames to be used for benchmark clients
for (String host : hstore_conf.client.hosts.split("[,;]")) {
clientHosts.add(host);
} // FOR
if (clientHosts.isEmpty())
clientHosts.add(hstore_conf.global.defaulthost);
// If no hosts were given, then use the defaults
if (siteHosts.size() == 0)
siteHosts.add(hstore_conf.global.defaulthost);
if (compileOnly == false && clientHosts.size() < clientCount) {
LogKeys logkey = LogKeys.benchmark_BenchmarkController_NotEnoughClients;
LOG.l7dlog( Level.FATAL, logkey.name(),
new Object[] { clientHosts.size(), clientCount }, null);
System.exit(-1);
}
String[] hostNames = null;
if (! (useCatalogHosts || compileOnly) ) {
if (siteHosts.size() < hostCount) {
LogKeys logkey = LogKeys.benchmark_BenchmarkController_NotEnoughHosts;
LOG.l7dlog( Level.FATAL, logkey.name(),
new Object[] { siteHosts.size(), hostCount }, null);
LOG.fatal("Don't have enough hosts(" + siteHosts.size()
+ ") for host count " + hostCount);
System.exit(-1);
}
// copy the lists of hostnames into array of the right lengths
// (this truncates the list to the right number)
hostNames = new String[hostCount];
for (int i = 0; i < hostCount; i++)
hostNames[i] = siteHosts.get(i);
} else {
hostNames = new String[0];
}
String[] clientNames = new String[clientCount];
if (compileOnly == false) {
for (int i = 0; i < clientCount; i++)
clientNames[i] = clientHosts.get(i);
}
// create a config object, mostly for the results uploader at this point
BenchmarkConfig config = new BenchmarkConfig(
hstore_conf,
hstore_conf_path,
benchmark_conf_path,
projectBuilderClassname,
backend,
hostNames,
sitesPerHost,
k_factor,
clientNames,
sshOptions,
remotePath,
remoteUser,
listenForDebugger,
serverHeapSize,
clientHeapSize,
clientInitialPollingDelay,
localmode,
checkTransaction,
checkTables,
snapshotPath,
snapshotPrefix,
snapshotFrequency,
snapshotRetain,
statsDatabaseURL,
statsDatabaseUser,
statsDatabasePass,
statsDatabaseJDBC,
statsPollInterval,
statsTag,
applicationName,
subApplicationName,
compileBenchmark,
compileOnly,
useCatalogHosts,
noSites,
noLoader,
noUploading,
noExecute,
noShutdown,
killOnZeroResults,
workloadTrace,
profileSiteIds,
partitionPlanPath,
markov_path,
markov_thresholdsPath,
markov_thresholdsValue,
markov_recomputeAfterEnd,
markov_recomputeAfterWarmup,
evictable,
batchEvictable,
deferrable,
dumpDatabase,
dumpDatabaseDir
);
// Always pass these parameters
if (catalogPath != null) {
clientParams.put("CATALOG", catalogPath.getAbsolutePath());
clientParams.put("NUMPARTITIONS", Integer.toString(num_partitions));
}
int total_num_clients = clientCount * hstore_conf.client.threads_per_host;
if (hstore_conf.client.processesperclient_per_partition) {
total_num_clients *= num_partitions;
}
clientParams.put("NUMCLIENTS", Integer.toString(total_num_clients));
clientParams.putAll(hstore_conf.getParametersLoadedFromArgs());
// Make sure we put in the parameters passed from the command-line into both components
Map<String, String> loadedArgs = hstore_conf.getParametersLoadedFromArgs();
clientParams.putAll(loadedArgs);
siteParams.putAll(loadedArgs);
config.clientParameters.putAll(clientParams);
config.siteParameters.putAll(siteParams);
if (trace.val) LOG.trace("Benchmark Configuration\n" + config.toString());
// ACTUALLY RUN THE BENCHMARK
BenchmarkController controller = new BenchmarkController(config, catalogContext);
boolean failed = false;
// Check CodeSpeed Parameters
if (hstore_conf.client.codespeed_url != null) {
assert(hstore_conf.client.codespeed_project != null) : "Missing CodeSpeed Project";
assert(hstore_conf.client.codespeed_environment != null) : "Missing CodeSpeed Environment";
assert(hstore_conf.client.codespeed_executable != null) : "Missing CodeSpeed Executable";
assert(hstore_conf.client.codespeed_commitid != null) : "Missing CodeSpeed CommitId";
}
// COMPILE BENCHMARK
if (config.compileBenchmark) {
boolean success = false;
BenchmarkCompiler compiler = new BenchmarkCompiler(controller.m_config,
controller.projectBuilder,
hstore_conf);
try {
// Actually compile and write the catalog to disk
success = compiler.compileBenchmark(controller.jarFileName);
assert(controller.jarFileName.exists()) :
"Failed to create jar file '" + controller.jarFileName + "'";
} catch (Throwable ex) {
LOG.error(String.format("Unexected error when trying to compile %s benchmark",
controller.projectBuilder.getProjectName()), ex);
System.exit(1);
}
if (config.compileOnly) {
if (success) {
LOG.info("Compilation Complete. Exiting [" + controller.jarFileName.getAbsolutePath() + "]");
} else {
LOG.info("Compilation Failed");
}
System.exit(success ? 0 : -1);
}
} else {
if (debug.val) LOG.debug("Skipping benchmark project compilation");
}
// EXECUTE BENCHMARK
try {
controller.setupBenchmark();
if (config.noExecute == false) controller.runBenchmark();
} catch (Throwable ex) {
LOG.fatal("Failed to complete benchmark", ex);
failed = true;
} finally {
controller.cleanUpBenchmark();
}
if (config.noShutdown == false && (failed || controller.failed)) System.exit(1);
// Upload Results to CodeSpeed
if (hstore_conf.client.codespeed_url != null) {
String codespeed_benchmark = controller.projectBuilder.getProjectName();
double txnrate = controller.getResults().getFinalResult().getTotalTxnPerSecond();
BenchmarkResultsUploader uploader = new BenchmarkResultsUploader(new URL(hstore_conf.client.codespeed_url),
hstore_conf.client.codespeed_project,
hstore_conf.client.codespeed_executable,
codespeed_benchmark,
hstore_conf.client.codespeed_environment,
hstore_conf.client.codespeed_commitid);
if (hstore_conf.client.codespeed_branch != null && hstore_conf.client.codespeed_branch.isEmpty() == false) {
uploader.setBranch(hstore_conf.client.codespeed_branch);
}
uploader.post(txnrate);
LOG.info("Uploaded benchmarks results to " + hstore_conf.client.codespeed_url);
}
if (config.noShutdown && config.noSites == false) {
// Wait indefinitely
LOG.info("H-Store cluster remaining online until killed");
while (true) {
Thread.sleep(1000);
} // WHILE
}
}
}