edu.brown.benchmark.seats.SEATSClient.java Source code

Java tutorial

Introduction

Here is the source code for edu.brown.benchmark.seats.SEATSClient.java

Source

/***************************************************************************
 *  Copyright (C) 2011 by H-Store Project                                  *
 *  Brown University                                                       *
 *  Massachusetts Institute of Technology                                  *
 *  Yale University                                                        *
 *                                                                         *
 *  http://hstore.cs.brown.edu/                                            *
 *                                                                         *
 *  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) 2009 Vertica Systems Inc.
 *
 * 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.seats;

import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.collections15.Buffer;
import org.apache.commons.collections15.BufferUtils;
import org.apache.commons.collections15.buffer.CircularFifoBuffer;
import org.apache.commons.collections15.map.ListOrderedMap;
import org.apache.log4j.Logger;
import org.voltdb.VoltProcedure;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.client.Client;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcedureCallback;
import org.voltdb.types.TimestampType;
import org.voltdb.utils.Pair;

import edu.brown.api.BenchmarkComponent;
import edu.brown.benchmark.seats.procedures.DeleteReservation;
import edu.brown.benchmark.seats.procedures.FindFlights;
import edu.brown.benchmark.seats.procedures.FindOpenSeats;
import edu.brown.benchmark.seats.procedures.NewReservation;
import edu.brown.benchmark.seats.procedures.UpdateCustomer;
import edu.brown.benchmark.seats.procedures.UpdateReservation;
import edu.brown.benchmark.seats.util.ErrorType;
import edu.brown.benchmark.seats.util.Reservation;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.rand.AbstractRandomGenerator;
import edu.brown.rand.DefaultRandomGenerator;
import edu.brown.rand.RandomDistribution;
import edu.brown.statistics.ObjectHistogram;
import edu.brown.utils.StringUtil;

/**
 * SEATS Benchmark Client Driver
 * @author pavlo
 */
public class SEATSClient extends BenchmarkComponent {
    private static final Logger LOG = Logger.getLogger(SEATSClient.class);
    private static final LoggerBoolean debug = new LoggerBoolean();
    private static final LoggerBoolean trace = new LoggerBoolean();
    static {
        LoggerUtil.attachObserver(LOG, debug, trace);
    }

    /**
     * Airline Benchmark Transactions
     */
    public static enum Transaction {
        DELETE_RESERVATION(DeleteReservation.class, SEATSConstants.FREQUENCY_DELETE_RESERVATION), FIND_FLIGHTS(
                FindFlights.class, SEATSConstants.FREQUENCY_FIND_FLIGHTS), FIND_OPEN_SEATS(FindOpenSeats.class,
                        SEATSConstants.FREQUENCY_FIND_OPEN_SEATS), NEW_RESERVATION(NewReservation.class,
                                SEATSConstants.FREQUENCY_NEW_RESERVATION), UPDATE_CUSTOMER(UpdateCustomer.class,
                                        SEATSConstants.FREQUENCY_UPDATE_CUSTOMER), UPDATE_RESERVATION(
                                                UpdateReservation.class,
                                                SEATSConstants.FREQUENCY_UPDATE_RESERVATION),;

        private Transaction(Class<? extends VoltProcedure> proc_class, int weight) {
            this.proc_class = proc_class;
            this.execName = proc_class.getSimpleName();
            this.default_weight = weight;
            this.displayName = StringUtil.title(this.name().replace("_", " "));
        }

        public final Class<? extends VoltProcedure> proc_class;
        public final int default_weight;
        public final String displayName;
        public final String execName;

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

        public static Transaction get(Integer idx) {
            assert (idx >= 0);
            return (Transaction.idx_lookup.get(idx));
        }

        public static Transaction get(String name) {
            return (Transaction.name_lookup.get(name.toLowerCase().intern()));
        }

        public int getDefaultWeight() {
            return (this.default_weight);
        }

        public String getDisplayName() {
            return (this.displayName);
        }

        public String getExecName() {
            return (this.execName);
        }
    }

    protected abstract class AbstractCallback<T> implements ProcedureCallback {
        final Transaction txn;
        final T element;

        public AbstractCallback(Transaction txn, T t) {
            this.txn = txn;
            this.element = t;
        }

        public final void clientCallback(ClientResponse clientResponse) {
            incrementTransactionCounter(clientResponse, txn.ordinal());
            this.clientCallbackImpl(clientResponse);

            if (debug.val) {
                if (txn == Transaction.UPDATE_RESERVATION && clientResponse.getStatus() == Status.ABORT_USER) {
                    LOG.error(String.format("Unexpected Error in %s: %s", this.txn.name(),
                            clientResponse.getStatusString()), clientResponse.getException());
                }

                if (clientResponse.getStatus() == Status.ABORT_UNEXPECTED) {
                    LOG.error(String.format("Unexpected Error in %s: %s", this.txn.name(),
                            clientResponse.getStatusString()), clientResponse.getException());
                }
            }

        }

        public abstract void clientCallbackImpl(ClientResponse clientResponse);
    }

    // -----------------------------------------------------------------
    // RESERVED SEAT BITMAPS
    // -----------------------------------------------------------------

    public enum CacheType {
        PENDING_INSERTS(SEATSConstants.CACHE_LIMIT_PENDING_INSERTS), PENDING_UPDATES(
                SEATSConstants.CACHE_LIMIT_PENDING_UPDATES), PENDING_DELETES(
                        SEATSConstants.CACHE_LIMIT_PENDING_DELETES),;

        private CacheType(int limit) {
            this.limit = limit;
        }

        private final int limit;
    }

    private static final Map<CacheType, Buffer<Reservation>> CACHE_RESERVATIONS = new EnumMap<CacheType, Buffer<Reservation>>(
            CacheType.class);
    private static final Map<Long, Set<Long>> CACHE_CUSTOMER_BOOKED_FLIGHTS = new ConcurrentHashMap<Long, Set<Long>>();
    private static final Map<Long, BitSet> CACHE_BOOKED_SEATS = new ConcurrentHashMap<Long, BitSet>();
    private static final BitSet FULL_FLIGHT_BITSET = new BitSet(SEATSConstants.FLIGHTS_NUM_SEATS);
    static {
        FULL_FLIGHT_BITSET.set(0, SEATSConstants.FLIGHTS_NUM_SEATS);
        for (CacheType ctype : CacheType.values()) {
            Buffer<Reservation> buffer = BufferUtils
                    .synchronizedBuffer(new CircularFifoBuffer<Reservation>(ctype.limit));
            CACHE_RESERVATIONS.put(ctype, buffer);
        } // FOR
    } // STATIC

    // -----------------------------------------------------------------
    // ADDITIONAL DATA MEMBERS
    // -----------------------------------------------------------------

    private final SEATSProfile profile;
    private final SEATSConfig config;
    private final AbstractRandomGenerator rng;
    private final AtomicBoolean first = new AtomicBoolean(true);
    private final RandomDistribution.FlatHistogram<Transaction> xacts;

    // -----------------------------------------------------------------
    // REQUIRED METHODS
    // -----------------------------------------------------------------

    public static void main(String args[]) {
        edu.brown.api.BenchmarkComponent.main(SEATSClient.class, args, false);
    }

    public SEATSClient(String[] args) {
        super(args);

        this.rng = new DefaultRandomGenerator();
        this.config = SEATSConfig.createConfig(this.getCatalogContext(), m_extraParams);
        this.profile = new SEATSProfile(this.getCatalogContext(), this.rng);

        if (this.noClientConnections() == false) {
            this.profile.loadProfile(this.getClientHandle());
            if (trace.val)
                LOG.trace("Airport Max Customer Id:\n" + this.profile.airport_max_customer_id);

            // Make sure we have the information we need in the BenchmarkProfile
            String error_msg = null;
            if (this.profile.getFlightIdCount() == 0) {
                error_msg = "The benchmark profile does not have any flight ids.";
            } else if (this.profile.getCustomerIdCount() == 0) {
                error_msg = "The benchmark profile does not have any customer ids.";
            } else if (this.profile.getFlightStartDate() == null) {
                error_msg = "The benchmark profile does not have a valid flight start date.";
            }
            if (error_msg != null)
                throw new RuntimeException(error_msg);
        }

        // Initialize Default Transaction Weights
        final ObjectHistogram<Transaction> weights = new ObjectHistogram<Transaction>();
        for (Transaction t : Transaction.values()) {
            int weight = this.getTransactionWeight(t.getExecName(), t.getDefaultWeight());
            weights.put(t, weight);
        } // FOR

        if (this.isSinglePartitionOnly()) {
            weights.clear();
            weights.put(Transaction.FIND_OPEN_SEATS, 75);
            weights.put(Transaction.UPDATE_CUSTOMER, 25);
        }

        // Create xact lookup array
        this.xacts = new RandomDistribution.FlatHistogram<Transaction>(this.rng, weights);
        assert (weights.getSampleCount() == 100) : "The total weight for the transactions is "
                + weights.getSampleCount() + ". It needs to be 100";
        if (debug.val)
            LOG.debug("Transaction Execution Distribution:\n" + weights);
    }

    protected SEATSProfile getProfile() {
        return this.profile;
    }

    @Override
    public String[] getTransactionDisplayNames() {
        String names[] = new String[Transaction.values().length];
        int ii = 0;
        for (Transaction t : Transaction.values()) {
            names[ii++] = t.getDisplayName();
        }
        return names;
    }

    @Override
    protected void runLoop() throws IOException {
        Client client = this.getClientHandle();
        // Execute Transactions
        try {
            while (true) {
                runOnce();
                client.backpressureBarrier();
            } // WHILE
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        } catch (NoConnectionsException e) {
            /*
             * Client has no clean mechanism for terminating with the DB.
             */
            return;
        } catch (IOException e) {
            /*
             * At shutdown an IOException is thrown for every connection to
             * the DB that is lost Ignore the exception here in order to not
             * get spammed, but will miss lost connections at runtime
             */
        }
    }

    @Override
    protected boolean runOnce() throws IOException {
        if (this.first.compareAndSet(true, false)) {
            // Fire off a FindOpenSeats so that we can prime ourselves
            Pair<Object[], ProcedureCallback> ret = this.getFindOpenSeatsParams();
            assert (ret != null);
            Object params[] = ret.getFirst();
            ProcedureCallback callback = ret.getSecond();
            this.getClientHandle().callProcedure(callback, Transaction.FIND_OPEN_SEATS.getExecName(), params);
        }

        int tries = 10;
        Pair<Object[], ProcedureCallback> ret = null;
        Transaction txn = null;
        while (tries-- > 0 && ret == null) {
            txn = this.xacts.nextValue();
            if (debug.val)
                LOG.debug("Attempting to execute " + txn);
            this.startComputeTime(txn.displayName);
            try {
                switch (txn) {
                case DELETE_RESERVATION: {
                    ret = this.getDeleteReservationParams();
                    break;
                }
                case FIND_FLIGHTS: {
                    ret = this.getFindFlightsParams();
                    break;
                }
                case FIND_OPEN_SEATS: {
                    ret = this.getFindOpenSeatsParams();
                    break;
                }
                case NEW_RESERVATION: {
                    ret = this.getNewReservationParams();
                    break;
                }
                case UPDATE_CUSTOMER: {
                    ret = this.getUpdateCustomerParams();
                    break;
                }
                case UPDATE_RESERVATION: {
                    ret = this.getUpdateReservationParams();
                    break;
                }
                default:
                    assert (false) : "Unexpected transaction: " + txn;
                } // SWITCH
            } finally {
                this.stopComputeTime(txn.displayName);
            }
            if (ret != null && debug.val)
                LOG.debug("Executed a new invocation of " + txn);
        }
        if (ret != null) {
            Object params[] = ret.getFirst();
            ProcedureCallback callback = ret.getSecond();
            this.getClientHandle().callProcedure(callback, txn.getExecName(), params);
        }

        if (tries == 0 && debug.val)
            LOG.warn("I have nothing to do!");
        return (tries > 0);
    }

    // -----------------------------------------------------------------
    // UTILITY METHODS
    // -----------------------------------------------------------------

    /**
     * Take an existing Reservation that we know is legit and randomly decide to 
     * either queue it for a later update or delete transaction 
     * @param r
     */
    protected void requeueReservation(Reservation r) {
        int idx = rng.nextInt(100);
        if (idx > 20)
            return;

        // Queue this motha trucka up for a deletin' or an updatin'
        CacheType ctype = null;
        if (rng.nextBoolean()) {
            ctype = CacheType.PENDING_DELETES;
        } else {
            ctype = CacheType.PENDING_UPDATES;
        }
        assert (ctype != null);

        Buffer<Reservation> cache = CACHE_RESERVATIONS.get(ctype);
        assert (cache != null);
        cache.add(r);
        if (debug.val)
            LOG.debug(String.format("Queued %s for %s [cacheSize=%d]\nFlightId: %d\nCustomerId: %d", r, ctype,
                    cache.size(), r.flight_id, r.customer_id));
    }

    /**
     * Returns true if the given BitSet for a Flight has all of its seats reserved 
     * @param seats
     * @return
     */
    protected boolean isFlightFull(BitSet seats) {
        assert (FULL_FLIGHT_BITSET.size() == seats.size());
        return FULL_FLIGHT_BITSET.equals(seats);
    }

    /**
     * Returns true if the given Customer already has a reservation booked on the target Flight
     * @param customer_id
     * @param flight_id
     * @return
     */
    protected boolean isCustomerBookedOnFlight(long customer_id, long flight_id) {
        Set<Long> flights = CACHE_CUSTOMER_BOOKED_FLIGHTS.get(customer_id);
        return (flights != null && flights.contains(flight_id));
    }

    protected final Set<Long> getCustomerBookedFlights(long customer_id) {
        Set<Long> f_ids = CACHE_CUSTOMER_BOOKED_FLIGHTS.get(customer_id);
        if (f_ids == null) {
            f_ids = new HashSet<Long>();
            CACHE_CUSTOMER_BOOKED_FLIGHTS.put(customer_id, f_ids);
        }
        return (f_ids);
    }

    protected final void clearCache() {
        for (BitSet seats : CACHE_BOOKED_SEATS.values()) {
            seats.clear();
        } // FOR
        for (Buffer<Reservation> queue : CACHE_RESERVATIONS.values()) {
            queue.clear();
        } // FOR
        for (Set<Long> f_ids : CACHE_CUSTOMER_BOOKED_FLIGHTS.values()) {
            synchronized (f_ids) {
                f_ids.clear();
            } // SYNCH
        } // FOR
    }

    @Override
    public String toString() {
        Map<String, Object> m = new ListOrderedMap<String, Object>();
        for (CacheType ctype : CACHE_RESERVATIONS.keySet()) {
            m.put(ctype.name(), CACHE_RESERVATIONS.get(ctype).size());
        } // FOR
        m.put("CACHE_CUSTOMER_BOOKED_FLIGHTS", CACHE_CUSTOMER_BOOKED_FLIGHTS.size());
        m.put("CACHE_BOOKED_SEATS", CACHE_BOOKED_SEATS.size());
        m.put("PROFILE", this.profile);

        return StringUtil.formatMaps(m);
    }

    // -----------------------------------------------------------------
    // DeleteReservation
    // -----------------------------------------------------------------

    class DeleteReservationCallback extends AbstractCallback<Reservation> {
        public DeleteReservationCallback(Reservation r) {
            super(Transaction.DELETE_RESERVATION, r);
        }

        @Override
        public void clientCallbackImpl(ClientResponse clientResponse) {
            if (clientResponse.getStatus() == Status.OK) {
                // We can remove this from our set of full flights because know that there is now a free seat
                BitSet seats = getSeatsBitSet(element.flight_id);
                seats.set(element.seatnum, false);

                // And then put it up for a pending insert
                if (rng.nextInt(100) < SEATSConstants.PROB_REQUEUE_DELETED_RESERVATION) {
                    Buffer<Reservation> cache = CACHE_RESERVATIONS.get(CacheType.PENDING_INSERTS);
                    assert (cache != null) : "Unexpected " + CacheType.PENDING_INSERTS;
                    synchronized (cache) {
                        cache.add(element);
                    } // SYNCH
                }
            } else if (debug.val) {
                LOG.info(
                        "DeleteReservation " + clientResponse.getStatus() + ": " + clientResponse.getStatusString(),
                        clientResponse.getException());
                LOG.info("BUSTED ID: " + element.flight_id + " / " + element.flight_id);
            }
        }
    }

    protected Pair<Object[], ProcedureCallback> getDeleteReservationParams() {
        // Pull off the first cached reservation and drop it on the cluster...
        Buffer<Reservation> cache = CACHE_RESERVATIONS.get(CacheType.PENDING_DELETES);
        assert (cache != null) : "Unexpected " + CacheType.PENDING_DELETES;
        Reservation r = null;
        synchronized (cache) {
            if (cache.isEmpty() == false)
                r = cache.remove();
        } // SYNCH
        if (r == null) {
            return (null);
        }
        int rand;
        if (config.force_all_distributed) {
            rand = SEATSConstants.PROB_DELETE_WITH_CUSTOMER_ID_STR + 1;
        } else if (config.force_all_singlepartition) {
            rand = 100;
        } else {
            rand = rng.number(1, 100);
        }

        // Parameters
        long f_id = r.flight_id;
        long c_id = VoltType.NULL_BIGINT;
        String c_id_str = "";
        String ff_c_id_str = "";

        // Delete with the Customer's id as a string 
        if (rand <= SEATSConstants.PROB_DELETE_WITH_CUSTOMER_ID_STR) {
            c_id_str = String.format(SEATSConstants.CUSTOMER_ID_STR, r.customer_id);
        }
        // Delete using their FrequentFlyer information
        else if (rand <= SEATSConstants.PROB_DELETE_WITH_CUSTOMER_ID_STR
                + SEATSConstants.PROB_DELETE_WITH_FREQUENTFLYER_ID_STR) {
            ff_c_id_str = String.format(SEATSConstants.CUSTOMER_ID_STR, r.customer_id);
        }
        // Delete using their Customer id
        else {
            c_id = r.customer_id;
        }

        Object params[] = new Object[] { f_id, // [0] f_id
                c_id, // [1] c_id
                c_id_str, // [2] c_id_str
                ff_c_id_str, // [3] ff_c_id_str
        };

        if (trace.val)
            LOG.trace("Calling " + Transaction.DELETE_RESERVATION.getExecName());

        return new Pair<Object[], ProcedureCallback>(params, new DeleteReservationCallback(r));
    }

    // ----------------------------------------------------------------
    // FindFlights
    // ----------------------------------------------------------------

    class FindFlightsCallback implements ProcedureCallback {
        @Override
        public void clientCallback(ClientResponse clientResponse) {
            incrementTransactionCounter(clientResponse, Transaction.FIND_FLIGHTS.ordinal());
            VoltTable[] results = clientResponse.getResults();
            if (results.length > 1) {
                // Convert the data into a FlightIds that other transactions can use
                while (results[0].advanceRow()) {
                    long flight_id = results[0].getLong(0);
                    assert (flight_id != VoltType.NULL_BIGINT);
                } // WHILE
            }
        }
    }

    /**
     * Execute one of the FindFlight transactions
     * @param txn
     * @throws IOException
     */
    protected Pair<Object[], ProcedureCallback> getFindFlightsParams() {
        // Select two random airport ids
        // Does it matter whether the one airport actually flies to the other one?
        long depart_airport_id = this.profile.getRandomAirportId();
        long arrive_airport_id = this.profile.getRandomOtherAirport(depart_airport_id);

        // Select a random date from our upcoming dates
        TimestampType start_date = this.profile.getRandomUpcomingDate();
        TimestampType stop_date = new TimestampType(
                start_date.getTime() + (SEATSConstants.MICROSECONDS_PER_DAY * 2));

        // If distance is greater than zero, then we will also get flights from nearby airports
        long distance = -1;
        if (rng.nextInt(100) < SEATSConstants.PROB_FIND_FLIGHTS_NEARBY_AIRPORT) {
            distance = SEATSConstants.DISTANCES[rng.nextInt(SEATSConstants.DISTANCES.length)];
        }

        Object params[] = new Object[] { depart_airport_id, arrive_airport_id, start_date, stop_date, distance };
        if (trace.val)
            LOG.trace("Calling " + Transaction.FIND_FLIGHTS.getExecName());
        return new Pair<Object[], ProcedureCallback>(params, new FindFlightsCallback());
    }

    // ----------------------------------------------------------------
    // FindOpenSeats
    // ----------------------------------------------------------------

    class FindOpenSeatsCallback extends AbstractCallback<Long> {
        final List<Reservation> tmp_reservations = new ArrayList<Reservation>();

        public FindOpenSeatsCallback(long f) {
            super(Transaction.FIND_OPEN_SEATS, f);
        }

        @Override
        public void clientCallbackImpl(ClientResponse clientResponse) {
            // Ignore aborted txns
            if (clientResponse.getStatus() != Status.OK)
                return;

            VoltTable[] results = clientResponse.getResults();
            assert (results.length == 2) : String.format("Unexpected result set with %d tables", results.length);

            // FLIGHT INFORMATION
            VoltTable flightInfo = results[0];
            boolean adv = flightInfo.advanceRow();
            assert (adv);
            long flight_id = flightInfo.getLong("F_ID");
            assert (flight_id == element.longValue());

            // OPEN SEAT INFORMATION
            VoltTable seatInfo = results[1];
            int rowCount = seatInfo.getRowCount();
            assert (rowCount <= SEATSConstants.FLIGHTS_NUM_SEATS) : String
                    .format("Unexpected %d open seats returned for %s", rowCount, element);

            // there is some tiny probability of an empty flight .. maybe 1/(20**150)
            // if you hit this assert (with valid code), play the lottery!
            if (rowCount == 0)
                return;

            // Store pending reservations in our queue for a later transaction            
            BitSet seats = getSeatsBitSet(element);

            int clientId = getClientId();
            while (seatInfo.advanceRow()) {
                int seatnum = (int) seatInfo.getLong(1);
                if (seatnum < SEATSConstants.FLIGHTS_RESERVED_SEATS) {
                    continue;
                }

                // Just get a random customer to through on this flight
                if (trace.val)
                    LOG.trace("Looking for a random customer to fly on " + flight_id);
                long customer_id = profile.getRandomCustomerId();
                Reservation r = new Reservation(profile.getNextReservationId(clientId), flight_id, customer_id,
                        seatnum);
                tmp_reservations.add(r);
                seats.set(seatnum);
                if (trace.val)
                    LOG.trace(String.format("QUEUED INSERT: %s -> %s", flight_id, customer_id));
            } // WHILE

            if (tmp_reservations.isEmpty() == false) {
                Buffer<Reservation> cache = CACHE_RESERVATIONS.get(CacheType.PENDING_INSERTS);
                assert (cache != null) : "Unexpected " + CacheType.PENDING_INSERTS;

                Collections.shuffle(tmp_reservations);
                synchronized (cache) {
                    cache.addAll(tmp_reservations);
                } // SYNCH
                if (debug.val)
                    LOG.debug(String.format("Stored %d pending inserts for %s [totalPendingInserts=%d]",
                            tmp_reservations.size(), flight_id, cache.size()));
            }
        }
    }

    /**
     * Execute the FindOpenSeat procedure
     * @throws IOException
     */
    protected Pair<Object[], ProcedureCallback> getFindOpenSeatsParams() {
        long flight_id = profile.getRandomFlightId();
        Object params[] = new Object[] { flight_id };
        if (trace.val)
            LOG.trace("Calling " + Transaction.FIND_OPEN_SEATS.getExecName());
        return new Pair<Object[], ProcedureCallback>(params, new FindOpenSeatsCallback(flight_id));
    }

    // ----------------------------------------------------------------
    // NewReservation
    // ----------------------------------------------------------------

    class NewReservationCallback extends AbstractCallback<Reservation> {
        public NewReservationCallback(Reservation r) {
            super(Transaction.NEW_RESERVATION, r);
        }

        @Override
        public void clientCallbackImpl(ClientResponse clientResponse) {
            final VoltTable[] results = clientResponse.getResults();
            final BitSet seats = getSeatsBitSet(element.flight_id);

            // Valid NewReservation
            if (clientResponse.getStatus() == Status.OK) {
                assert (results.length > 1);
                assert (results[0].getRowCount() == 1);
                assert (results[0].asScalarLong() == 1);

                // Mark this seat as successfully reserved
                seats.set(element.seatnum);

                // Set it up so we can play with it later
                SEATSClient.this.requeueReservation(element);
            }
            // Aborted - Figure out why!
            else if (clientResponse.getStatus() == Status.ABORT_USER) {
                String msg = clientResponse.getStatusString();
                ErrorType errorType = ErrorType.getErrorType(msg);

                if (debug.val)
                    LOG.debug(
                            String.format("Client %02d :: NewReservation %s [ErrorType=%s] - %s", getClientId(),
                                    clientResponse.getStatus(), errorType, clientResponse.getStatusString()),
                            clientResponse.getException());
                switch (errorType) {
                case NO_MORE_SEATS: {
                    seats.set(0, SEATSConstants.FLIGHTS_NUM_SEATS);
                    if (debug.val)
                        LOG.debug(String.format("FULL FLIGHT: %s", element.flight_id));
                    break;
                }
                case CUSTOMER_ALREADY_HAS_SEAT: {
                    Set<Long> f_ids = getCustomerBookedFlights(element.customer_id);
                    f_ids.add(element.flight_id);
                    if (debug.val)
                        LOG.debug(String.format("ALREADY BOOKED: %s -> %s", element.customer_id, f_ids));
                    break;
                }
                case SEAT_ALREADY_RESERVED: {
                    seats.set(element.seatnum);
                    if (debug.val)
                        LOG.debug(String.format("ALREADY BOOKED SEAT: %s/%d -> %s", element.customer_id,
                                element.seatnum, seats));
                    break;
                }
                case INVALID_CUSTOMER_ID: {
                    LOG.warn("Unexpected invalid CustomerId: " + element.customer_id);
                    break;
                }
                case INVALID_FLIGHT_ID: {
                    LOG.warn("Unexpected invalid FlightId: " + element.flight_id);
                    break;
                }
                case UNKNOWN: {
                    //                        if (debug.val) 
                    LOG.warn(msg);
                    break;
                }
                default: {
                    if (debug.val)
                        LOG.debug("BUSTED ID: " + element.flight_id + " / " + element.flight_id);
                }
                } // SWITCH
            }
        }
    }

    protected Pair<Object[], ProcedureCallback> getNewReservationParams() {
        Reservation reservation = null;
        BitSet seats = null;
        Buffer<Reservation> cache = CACHE_RESERVATIONS.get(CacheType.PENDING_INSERTS);
        assert (cache != null) : "Unexpected " + CacheType.PENDING_INSERTS;

        if (debug.val)
            LOG.debug(String.format("Attempting to get a new pending insert Reservation [totalPendingInserts=%d]",
                    cache.size()));
        while (reservation == null) {
            Reservation r = null;
            synchronized (cache) {
                if (cache.isEmpty() == false)
                    r = cache.remove();
            } // SYNCH
            if (r == null) {
                if (debug.val)
                    LOG.warn("Unable to execute " + Transaction.DELETE_RESERVATION
                            + " - No available reservations to insert");
                break;
            }

            seats = this.getSeatsBitSet(r.flight_id);

            if (this.isFlightFull(seats)) {
                if (debug.val)
                    LOG.debug(String.format("%s is full", r.flight_id));
                continue;
            }
            // PAVLO: Not sure why this is always coming back as reserved? 
            //            else if (seats.get(r.seatnum)) {
            //                if (debug.val)
            //                    LOG.debug(String.format("Seat #%d on %s is already booked", r.seatnum, r.flight_id));
            //                continue;
            //            }
            else if (this.isCustomerBookedOnFlight(r.customer_id, r.flight_id)) {
                if (debug.val)
                    LOG.debug(String.format("%s is already booked on %s", r.customer_id, r.flight_id));
                continue;
            }
            reservation = r;
        } // WHILE
        if (reservation == null) {
            if (debug.val)
                LOG.debug("Failed to find a valid pending insert Reservation\n" + this.toString());
            return (null);
        }

        // Generate a random price for now
        double price = 2.0 * rng.number(SEATSConstants.RESERVATION_PRICE_MIN, SEATSConstants.RESERVATION_PRICE_MAX);

        // Generate random attributes
        long attributes[] = new long[SEATSConstants.NEW_RESERVATION_ATTRS_SIZE];
        for (int i = 0; i < attributes.length; i++) {
            attributes[i] = rng.nextLong();
        } // FOR

        // boolean updateCustomer = (rng.nextInt(100) < SEATSConstants.PROB_UPDATE_CUSTOMER_NEW_RESERVATION);

        Object params[] = new Object[] { reservation.id, reservation.customer_id, reservation.flight_id,
                reservation.seatnum, price, attributes, new TimestampType() };
        if (trace.val)
            LOG.trace("Calling " + Transaction.NEW_RESERVATION.getExecName());
        return new Pair<Object[], ProcedureCallback>(params, new NewReservationCallback(reservation));
    }

    // ----------------------------------------------------------------
    // UpdateCustomer
    // ----------------------------------------------------------------

    class UpdateCustomerCallback extends AbstractCallback<Long> {
        public UpdateCustomerCallback(long c) {
            super(Transaction.UPDATE_CUSTOMER, c);
        }

        @Override
        public void clientCallbackImpl(ClientResponse clientResponse) {
            VoltTable[] results = clientResponse.getResults();
            if (clientResponse.getStatus() == Status.OK) {
                assert (results.length >= 1);
                assert (results[0].getRowCount() == 1);
                //                assert (results[0].asScalarLong() == 1);
            } else if (debug.val) {
                LOG.debug("UpdateCustomer " + ": " + clientResponse.getStatusString(),
                        clientResponse.getException());
            }
        }
    }

    protected Pair<Object[], ProcedureCallback> getUpdateCustomerParams() {
        // Pick a random customer and then have at it!
        long customer_id = profile.getRandomCustomerId();

        long c_id = VoltType.NULL_BIGINT;
        String c_id_str = null;
        long attr0 = this.rng.nextLong();
        long attr1 = this.rng.nextLong();
        long update_ff = (this.rng.number(1, 100) <= SEATSConstants.PROB_UPDATE_FREQUENT_FLYER ? 1 : 0);
        if (config.force_all_distributed)
            update_ff = 1;

        // Update with the Customer's id as a string 
        if (rng.nextInt(100) < SEATSConstants.PROB_UPDATE_WITH_CUSTOMER_ID_STR) {
            c_id_str = String.format(SEATSConstants.CUSTOMER_ID_STR, customer_id);
        }
        // Update using their Customer id
        else {
            c_id = customer_id;
        }

        Object params[] = new Object[] { c_id, c_id_str, update_ff, attr0, attr1 };

        if (trace.val)
            LOG.trace("Calling " + Transaction.UPDATE_CUSTOMER.getExecName());
        return new Pair<Object[], ProcedureCallback>(params, new UpdateCustomerCallback(customer_id));
    }

    // ----------------------------------------------------------------
    // UpdateReservation
    // ----------------------------------------------------------------

    class UpdateReservationCallback extends AbstractCallback<Reservation> {
        public UpdateReservationCallback(Reservation r) {
            super(Transaction.UPDATE_RESERVATION, r);
        }

        @Override
        public void clientCallbackImpl(ClientResponse clientResponse) {
            if (clientResponse.getStatus() == Status.OK) {
                assert (clientResponse.getResults().length == 1);
                assert (clientResponse.getResults()[0].getRowCount() == 1);
                assert (clientResponse.getResults()[0].asScalarLong() == 1
                        || clientResponse.getResults()[0].asScalarLong() == 0);

                SEATSClient.this.requeueReservation(element);
            }
        }
    }

    protected Pair<Object[], ProcedureCallback> getUpdateReservationParams() {
        if (trace.val)
            LOG.trace("Let's look for a Reservation that we can update");

        // Pull off the first pending seat change and throw that ma at the server
        Buffer<Reservation> cache = CACHE_RESERVATIONS.get(CacheType.PENDING_UPDATES);
        assert (cache != null) : "Unexpected " + CacheType.PENDING_UPDATES;
        Reservation r = null;
        synchronized (cache) {
            if (cache.isEmpty() == false)
                r = cache.remove();
        } // SYNCH
        if (r == null)
            return (null);

        long value = rng.number(1, 1 << 20);
        long attribute_idx = rng.nextInt(UpdateReservation.NUM_UPDATES);
        long seatnum = rng.nextInt(SEATSConstants.FLIGHTS_RESERVED_SEATS);
        if (debug.val)
            LOG.debug(String.format("UpdateReservation: FlightId:%d / CustomerId:%d / SeatNum:%d", r.flight_id,
                    r.customer_id, seatnum));

        Object params[] = new Object[] { r.id, r.customer_id, r.flight_id, seatnum, attribute_idx, value };

        if (trace.val)
            LOG.trace("Calling " + Transaction.UPDATE_RESERVATION.getExecName());
        return new Pair<Object[], ProcedureCallback>(params, new UpdateReservationCallback(r));
    }

    protected BitSet getSeatsBitSet(long flight_id) {
        BitSet seats = CACHE_BOOKED_SEATS.get(flight_id);
        if (seats == null) {
            //            synchronized (CACHE_BOOKED_SEATS) {
            seats = CACHE_BOOKED_SEATS.get(flight_id);
            if (seats == null) {
                seats = new BitSet(SEATSConstants.FLIGHTS_NUM_SEATS);
                CACHE_BOOKED_SEATS.put(flight_id, seats);
            }
            //            } // SYNCH
        }
        return (seats);
    }

}