edu.brown.benchmark.BenchmarkComponent.java Source code

Java tutorial

Introduction

Here is the source code for edu.brown.benchmark.BenchmarkComponent.java

Source

/***************************************************************************
 *   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.benchmark;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.UnknownHostException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;

import org.apache.commons.collections15.map.ListOrderedMap;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONStringer;
import org.voltdb.ClientResponseImpl;
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.StatsUploaderSettings;
import org.voltdb.utils.Pair;
import org.voltdb.utils.VoltSampler;

import edu.brown.catalog.CatalogUtil;
import edu.brown.designer.partitioners.plan.PartitionPlan;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.statistics.Histogram;
import edu.brown.statistics.TableStatistics;
import edu.brown.statistics.WorkloadStatistics;
import edu.brown.utils.ArgumentsParser;
import edu.brown.utils.FileUtil;
import edu.brown.utils.ProfileMeasurement;
import edu.brown.utils.StringUtil;
import edu.brown.hstore.HStoreConstants;
import edu.brown.hstore.HStoreThreadManager;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.conf.HStoreConf;

/**
 * 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(LOG.isDebugEnabled());
    private static final LoggerBoolean trace = new LoggerBoolean(LOG.isTraceEnabled());
    static {
        LoggerUtil.setupLogging();
        LoggerUtil.attachObserver(LOG, debug, trace);
    }

    public static String CONTROL_MESSAGE_PREFIX = "{HSTORE}";

    /**
     * These are the commands that the BenchmarkController will send to each
     * BenchmarkComponent in order to coordinate the benchmark's execution
     */
    public enum Command {
        START, POLL, CLEAR, PAUSE,
        /**
         * Stop this BenchmarkComponent instance
         */
        STOP,
        /**
         * This is the same as STOP except that the BenchmarkComponent will
         * tell the cluster to shutdown first before it exits 
         */
        SHUTDOWN,;

        protected static final Map<Integer, Command> idx_lookup = new HashMap<Integer, Command>();
        protected static final Map<String, Command> name_lookup = new HashMap<String, Command>();
        static {
            for (Command vt : EnumSet.allOf(Command.class)) {
                Command.idx_lookup.put(vt.ordinal(), vt);
                Command.name_lookup.put(vt.name().toUpperCase(), vt);
            } // FOR
        }

        public static Command get(String name) {
            return (Command.name_lookup.get(name.trim().toUpperCase()));
        }
    }

    /**
     * These represent the different states that the BenchmarkComponent's ControlPipe
     * could be in. 
     */
    public static enum ControlState {
        PREPARING, READY, RUNNING, PAUSED, ERROR;

        protected static final Map<Integer, ControlState> idx_lookup = new HashMap<Integer, ControlState>();
        protected static final Map<String, ControlState> name_lookup = new HashMap<String, ControlState>();
        static {
            for (ControlState vt : EnumSet.allOf(ControlState.class)) {
                ControlState.idx_lookup.put(vt.ordinal(), vt);
                ControlState.name_lookup.put(vt.name().toUpperCase(), vt);
            } // FOR
        }

        public static ControlState get(String name) {
            return (ControlState.name_lookup.get(name.trim().toUpperCase()));
        }
    };

    private static Client globalClient;
    private static Catalog globalCatalog;
    private static PartitionPlan globalPartitionPlan;
    private static boolean globalHasConnections = false;

    public static synchronized Client getClient(Catalog catalog, int messageSize, boolean heavyWeight,
            StatsUploaderSettings statsSettings) {
        //        Client newClient = ClientFactory.createClient(
        //                    messageSize,
        //                    null,
        //                    heavyWeight,
        //                    statsSettings,
        //                    catalog
        //            );
        //        return (newClient);

        if (globalClient == null) {
            globalClient = ClientFactory.createClient(messageSize, null, heavyWeight, statsSettings, catalog);
            if (debug.get())
                LOG.debug("Created global Client handle");
        }
        return (globalClient);
    }

    public static synchronized Catalog getCatalog(File catalogPath) {
        // Read back the catalog and populate catalog object
        if (globalCatalog == null) {
            globalCatalog = CatalogUtil.loadCatalogFromJar(catalogPath.getAbsolutePath());
        }
        return (globalCatalog);
    }

    public static synchronized void applyPartitionPlan(Database catalog_db, String partitionPlanPath) {
        if (globalPartitionPlan == null) {
            if (debug.get())
                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);
            }
        }
        return;
    }

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

    /**
     * State of this client
     */
    private 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
     */
    private final int m_txnRate;

    private final boolean m_blocking;

    /**
     * Number of transactions to generate for every millisecond of time that
     * passes
     */
    private final double m_txnsPerMillisecond;

    /**
     * Additional parameters (benchmark specific)
     */
    protected final Map<String, String> m_extraParams = new HashMap<String, String>();

    /**
     * Storage for error descriptions
     */
    private 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 Catalog m_catalog;
    private final String m_projectName;

    private final boolean m_exitOnCompletion;

    /**
     * Pause Lock
     */
    private final Semaphore m_pauseLock = new Semaphore(1);

    /**
     * Data verification.
     */
    private final float m_checkTransaction;
    private 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;

    private final int m_tickInterval;
    private final Thread m_tickThread;
    private 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 Histogram<String> m_tableTuples = new Histogram<String>();
    private final Histogram<String> m_tableBytes = new Histogram<String>();
    private final Map<Table, TableStatistics> m_tableStatsData = new HashMap<Table, TableStatistics>();
    private final TransactionCounter m_txnStats = new TransactionCounter();

    private final Map<String, ProfileMeasurement> computeTime = new HashMap<String, ProfileMeasurement>();

    /**
     * 
     */
    private BenchmarkClientFileUploader uploader = null;

    /**
     * Configuration
     */
    private final HStoreConf m_hstoreConf;
    private final Histogram<String> m_txnWeights = new Histogram<String>();
    private Integer m_txnWeightsDefault = null;

    public void printControlMessage(ControlState state) {
        printControlMessage(state, null);
    }

    public 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);
    }

    /**
     * Implements the simple state machine for the remote controller protocol.
     * Hypothetically, you can extend this and override the answerPoll() and
     * answerStart() methods for other clients.
     */
    protected class ControlPipe implements Runnable {
        final InputStream in;

        public ControlPipe(InputStream in) {
            this.in = in;
        }

        public void run() {
            final Thread self = Thread.currentThread();
            self.setName(String.format("client-%02d", m_id));

            Command command = null;

            // transition to ready and send ready message
            if (m_controlState == ControlState.PREPARING) {
                printControlMessage(ControlState.READY);
                m_controlState = ControlState.READY;
            } else {
                LOG.error("Not starting prepared!");
                LOG.error(m_controlState + " " + m_reason);
            }

            final BufferedReader in = new BufferedReader(new InputStreamReader(this.in));
            while (true) {
                try {
                    command = Command.get(in.readLine());
                    if (debug.get())
                        LOG.debug(String.format("Recieved Message: '%s'", command));
                } catch (final IOException e) {
                    // Hm. quit?
                    LOG.fatal("Error on standard input", e);
                    System.exit(-1);
                }
                if (command == null)
                    continue;
                if (debug.get())
                    LOG.debug("ControlPipe Command = " + command);

                final ControlWorker worker = new ControlWorker();
                final Thread t = new Thread(worker);
                t.setDaemon(true);

                // HACK: Convert a SHUTDOWN to a STOP if we're not the first client
                if (command == Command.SHUTDOWN && getClientId() != 0) {
                    command = Command.STOP;
                }

                switch (command) {
                case START: {
                    if (m_controlState != ControlState.READY) {
                        setState(ControlState.ERROR, "START when not READY.");
                        answerWithError();
                        continue;
                    }
                    t.start();
                    if (m_tickThread != null)
                        m_tickThread.start();
                    m_controlState = ControlState.RUNNING;
                    answerOk();
                    break;
                }
                case POLL: {
                    if (m_controlState != ControlState.RUNNING) {
                        setState(ControlState.ERROR, "POLL when not RUNNING.");
                        answerWithError();
                        continue;
                    }
                    answerPoll();

                    // Call tick on the client if we're not polling ourselves
                    if (BenchmarkComponent.this.m_tickInterval < 0) {
                        if (debug.get())
                            LOG.debug("Got poll message! Calling tick()!");
                        invokeTickCallback(m_tickCounter++);
                    }
                    if (debug.get())
                        LOG.debug(String.format("CLIENT QUEUE TIME: %.2fms / %.2fms avg",
                                m_voltClient.getQueueTime().getTotalThinkTimeMS(),
                                m_voltClient.getQueueTime().getAverageThinkTimeMS()));
                    break;
                }
                case CLEAR: {
                    m_txnStats.clear();
                    invokeClearCallback();
                    answerOk();
                    break;
                }
                case PAUSE: {
                    assert (m_controlState == ControlState.RUNNING) : "Unexpected " + m_controlState;

                    LOG.info("Pausing client");

                    // Enable the lock and then change our state
                    try {
                        m_pauseLock.acquire();
                    } catch (InterruptedException ex) {
                        LOG.fatal("Unexpected interuption!", ex);
                        System.exit(1);
                    }
                    m_controlState = ControlState.PAUSED;
                    break;
                }
                case SHUTDOWN: {
                    if (m_controlState == ControlState.RUNNING || m_controlState == ControlState.PAUSED) {
                        invokeStopCallback();
                        try {
                            m_voltClient.drain();
                            m_voltClient.callProcedure("@Shutdown");
                            m_voltClient.close();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                    System.exit(0);
                    break;
                }
                case STOP: {
                    if (m_controlState == ControlState.RUNNING || m_controlState == ControlState.PAUSED) {
                        invokeStopCallback();
                        try {
                            if (m_sampler != null) {
                                m_sampler.setShouldStop();
                                m_sampler.join();
                            }
                            m_voltClient.close();
                            if (m_checkTables) {
                                checkTables();
                            }
                        } catch (InterruptedException e) {
                            System.exit(0);
                        } finally {
                            System.exit(0);
                        }
                    }
                    LOG.fatal("STOP when not RUNNING");
                    System.exit(-1);
                    break;
                }
                default: {
                    LOG.fatal("Error on standard input: unknown command " + command);
                    System.exit(-1);
                }
                } // SWITCH
            }
        }

        public void answerWithError() {
            printControlMessage(m_controlState, m_reason);
        }

        public void answerPoll() {
            JSONStringer stringer = new JSONStringer();
            TransactionCounter copy = m_txnStats; // .copy();
            try {
                stringer.object();
                copy.toJSON(stringer);
                stringer.endObject();
                printControlMessage(m_controlState, stringer.toString());
            } catch (JSONException ex) {
                throw new RuntimeException(ex);
            }
            m_txnStats.basePartitions.clear();
        }

        public void answerOk() {
            printControlMessage(m_controlState, "OK");
        }
    }

    /**
     * Thread that executes the derives classes run loop which invokes stored
     * procedures indefinitely
     */
    private class ControlWorker extends Thread {
        @Override
        public void run() {
            invokeStartCallback();
            try {
                if (m_txnRate == -1) {
                    if (m_sampler != null) {
                        m_sampler.start();
                    }
                    runLoop();
                } else {
                    if (debug.get())
                        LOG.debug(String.format("Running rate controlled [m_txnRate=%d, m_txnsPerMillisecond=%f]",
                                m_txnRate, m_txnsPerMillisecond));
                    rateControlledRunLoop();
                }
            } catch (Throwable ex) {
                ex.printStackTrace();
                System.exit(0);
            } finally {
                if (m_exitOnCompletion)
                    System.exit(0);
            }
        }

        /*
         * Time in milliseconds since requests were last sent.
         */
        private long m_lastRequestTime;

        private void rateControlledRunLoop() {
            m_lastRequestTime = System.currentTimeMillis();
            while (true) {
                boolean bp = false;
                try {
                    // If there is back pressure don't send any requests. Update the
                    // last request time so that a large number of requests won't
                    // queue up to be sent when there is no longer any back
                    // pressure.
                    m_voltClient.backpressureBarrier();

                    // Check whether we are currently being paused
                    // We will block until we're allowed to go again
                    if (m_controlState == ControlState.PAUSED) {
                        m_pauseLock.acquire();
                    }
                    assert (m_controlState != ControlState.PAUSED) : "Unexpected " + m_controlState;

                } catch (InterruptedException e1) {
                    throw new RuntimeException();
                }

                final long now = System.currentTimeMillis();

                /*
                 * Generate the correct number of transactions based on how much
                 * time has passed since the last time transactions were sent.
                 */
                final long delta = now - m_lastRequestTime;
                if (delta > 0) {
                    final int transactionsToCreate = (int) (delta * m_txnsPerMillisecond);
                    if (transactionsToCreate < 1) {
                        // Thread.yield();
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                            System.exit(1);
                        }
                        continue;
                    }

                    for (int ii = 0; ii < transactionsToCreate; ii++) {
                        try {
                            bp = !runOnce();
                            if (bp) {
                                m_lastRequestTime = now;
                                break;
                            }
                        } catch (final IOException e) {
                            return;
                        }
                    }
                } else {
                    // Thread.yield();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                        System.exit(1);
                    }
                }

                m_lastRequestTime = now;
            }
        }
    }

    /**
     * Implemented by derived classes. Loops indefinitely invoking stored
     * procedures. Method never returns and never receives any updates.
     */
    abstract protected void runLoop() throws IOException;

    /**
     * Get the display names of the transactions that will be invoked by the
     * dervied 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 txn_idx
     */
    protected final void incrementTransactionCounter(ClientResponse cresponse, 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) {
            m_txnStats.basePartitions.put(cresponse.getBasePartition());
            m_txnStats.transactions.put(m_countDisplayNames[txn_idx]);
        }
    }

    public BenchmarkComponent(final Client client) {
        m_voltClient = client;
        m_exitOnCompletion = false;
        m_password = "";
        m_username = "";
        m_txnRate = -1;
        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_tableStats = false;
        m_tableStatsDir = null;
        m_noUploading = false;

        // 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[]) {
        // 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

        if (HStoreConf.isInitialized() == false) {
            assert (hstore_conf_path != null) : "Missing HStoreConf file";
            File f = new File(hstore_conf_path);
            if (debug.get())
                LOG.debug("Initializing HStoreConf from '" + f.getName() + "' along with input parameters");
            HStoreConf.init(f, args);
        } else {
            if (debug.get())
                LOG.debug("Initializing HStoreConf only with input parameters");
            HStoreConf.singleton().loadFromArgs(args);
        }
        m_hstoreConf = HStoreConf.singleton();
        if (trace.get())
            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;

        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.get())
                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.get())
                    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]);
            }

            // 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.get())
                    componentParams.remove(parts[0]);
                parts[0] = parts[0].substring(HStoreConstants.BENCHMARK_PARAM_PREFIX.length());
                m_extraParams.put(parts[0].toUpperCase(), parts[1]);
            }
        }
        if (trace.get()) {
            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_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));

        // 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.get())
                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.getCatalog();
        }

        // Parse workload transaction weights
        if (m_hstoreConf.client.weights != null) {
            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.get())
                            LOG.debug(String.format("Default Transaction Weight: %d", txnWeight));
                    } else {
                        if (debug.get())
                            LOG.debug(String.format("%s Transaction Weight: %d", txnName, txnWeight));
                        this.m_txnWeights.put(txnName.toUpperCase(), txnWeight);
                    }
                } 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(partitionPlanPath);
        }

        StatsUploaderSettings statsSettings = null;
        if (statsDatabaseURL != null && statsDatabaseURL.isEmpty() == false) {
            try {
                statsSettings = StatsUploaderSettings.singleton(statsDatabaseURL, statsDatabaseUser,
                        statsDatabasePass, statsDatabaseJDBC, projectName, (isLoader ? "LOADER" : "CLIENT"),
                        statsPollInterval, m_catalog);
            } catch (Throwable ex) {
                throw new RuntimeException("Failed to initialize StatsUploader", ex);
            }
            if (debug.get())
                LOG.debug("StatsUploaderSettings:\n" + statsSettings);
        }
        Client new_client = BenchmarkComponent.getClient((m_hstoreConf.client.txn_hints ? this.getCatalog() : null),
                getExpectedOutgoingMessageSize(), useHeavyweightClient(), statsSettings);
        if (m_blocking) { //  && isLoader == false) {
            if (debug.get())
                LOG.debug(String.format("Using BlockingClient [concurrent=%d]",
                        m_hstoreConf.client.blocking_concurrent));
            m_voltClient = new BlockingClient(new_client, m_hstoreConf.client.blocking_concurrent);
        } else {
            m_voltClient = new_client;
        }

        // report any errors that occurred before the client was instantiated
        if (state != ControlState.PREPARING)
            setState(state, reason);

        // scan the inputs again looking for host connections
        if (m_noConnections == false) {
            synchronized (BenchmarkComponent.class) {
                if (globalHasConnections == false) {
                    this.setupConnections();
                    globalHasConnections = true;
                }
            } // SYNCH
        }
        m_checkTransaction = checkTransaction;
        m_checkTables = checkTables;
        m_constraints = new LinkedHashMap<Pair<String, Integer>, Expression>();

        m_countDisplayNames = getTransactionDisplayNames();
        if (m_countDisplayNames != null) {
            for (String txnName : m_countDisplayNames) {
                m_txnStats.transactions.put(txnName, 0);
            } // FOR
        }

        // If we need to call tick more frequently that when POLL is called,
        // then we'll want to use a separate thread
        if (m_tickInterval > 0 && isLoader == false) {
            if (debug.get())
                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;
        }
    }

    /**
     * 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;
            if (startImmediately) {
                final ControlWorker worker = clientMain.new ControlWorker();
                worker.start();

                // Wait for the worker to finish
                if (debug.get())
                    LOG.debug(String.format("Started ControlWorker for client #%02d. Waiting until finished...",
                            clientMain.getClientId()));
                worker.join();
                clientMain.invokeStopCallback();
            } else {
                // if (debug.get()) 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);
            System.exit(-1);
        }
        return (clientMain);
    }

    /**
     * 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.get())
            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 {
                cr = m_voltClient.callProcedure("@LoadMultipartitionTable", tableName, vt);
            } catch (Exception e) {
                throw new RuntimeException("Error when trying load data for '" + tableName + "'", e);
            } finally {
                if (locked)
                    m_loaderBlock.unlock();
            } // SYNCH
            if (debug.get())
                LOG.debug(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 Catalog catalog = this.getCatalog();
            assert (catalog != null);
            final Database catalog_db = CatalogUtil.getDatabase(catalog);
            final Table catalog_tbl = catalog_db.getTables().getIgnoreCase(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(catalog_db);
                    m_tableStatsData.put(catalog_tbl, stats);
                }
                vt.resetRowPosition();
                while (vt.advanceRow()) {
                    VoltTableRow row = vt.getRow();
                    stats.process(catalog_db, 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) {
        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 Histogram<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 Catalog catalog = this.getCatalog();
        assert (catalog != null);
        final Database catalog_db = CatalogUtil.getDatabase(catalog);

        // Make sure we call postprocess on all of our friends
        for (TableStatistics tableStats : m_tableStatsData.values()) {
            try {
                tableStats.postprocess(catalog_db);
            } catch (Exception ex) {
                String tableName = tableStats.getCatalogItem(catalog_db).getName();
                throw new RuntimeException("Failed to process TableStatistics for '" + tableName + "'", ex);
            }
        } // FOR

        if (trace.get())
            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(catalog_db);
        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
    // ----------------------------------------------------------------------------

    private final void invokeStartCallback() {
        this.startCallback();
    }

    private 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();
            String path = m_tableStatsDir.getAbsolutePath() + "/" + this.getProjectName() + ".stats";
            LOG.info("Writing table statistics data to '" + path + "'");
            try {
                stats.save(path);
            } catch (IOException ex) {
                LOG.error("Failed to save table statistics to '" + path + "'", ex);
                System.exit(1);
            }
        }

        this.stopCallback();
    }

    private final void invokeClearCallback() {
        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
     */
    private final void invokeTickCallback(int counter) {
        if (debug.get())
            LOG.debug("New Tick Update: " + counter);
        this.tickCallback(counter);

        if (debug.get()) {
            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 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
    // ----------------------------------------------------------------------------

    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();
    }

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

    // update the client state and start waiting for a message.
    public void start(InputStream in) {
        m_controlPipe = new ControlPipe(in);
        m_controlPipe.run(); // blocking
    }

    public ControlPipe createControlPipe(InputStream in) {
        m_controlPipe = new ControlPipe(in);
        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);
    }

    /**
     * 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 catalog used for this benchmark.
     * @return
     * @throws Exception
     */
    public Catalog getCatalog() {
        // Read back the catalog and populate catalog object
        if (m_catalog == null) {
            m_catalog = getCatalog(m_catalogPath);
        }
        return (m_catalog);
    }

    public void setCatalog(Catalog catalog) {
        m_catalog = catalog;
    }

    public void applyPartitionPlan(String partitionPlanPath) {
        Database catalog_db = CatalogUtil.getDatabase(this.getCatalog());
        BenchmarkComponent.applyPartitionPlan(catalog_db, 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 void setupConnections() {
        boolean atLeastOneConnection = false;
        for (Site catalog_site : CatalogUtil.getAllSites(this.getCatalog())) {
            int site_id = catalog_site.getId();
            String host = catalog_site.getHost().getIpaddr();
            int port = catalog_site.getProc_port();
            if (debug.get())
                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());
                break;
            }
            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.get())
            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);
    }

    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 (clientResponse.getException() != null) {
                clientResponse.getException().printStackTrace();
            }
            if (clientResponse.getStatusString() != null) {
                LOG.warn(clientResponse.getStatusString());
            }

            System.exit(-1);
        }

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