/*************************************************************************** * 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; } }