Package edu.brown.benchmark.auctionmark

Source Code of edu.brown.benchmark.auctionmark.AuctionMarkProfile

/***************************************************************************
*  Copyright (C) 2010 by H-Store Project                                  *
*  Brown University                                                       *
*  Massachusetts Institute of Technology                                  *
*  Yale University                                                        *
*                                                                         *
*  Andy Pavlo (pavlo@cs.brown.edu)                                        *
*  http://www.cs.brown.edu/~pavlo/                                        *
*                                                                         *
*  Visawee Angkanawaraphan (visawee@cs.brown.edu)                         *
*  http://www.cs.brown.edu/~visawee/                                      *
*                                                                         *
*  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.auctionmark;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.voltdb.TheHashinator;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Table;
import org.voltdb.client.Client;
import org.voltdb.client.ClientResponse;
import org.voltdb.types.TimestampType;

import edu.brown.benchmark.auctionmark.AuctionMarkConstants.ItemStatus;
import edu.brown.benchmark.auctionmark.procedures.LoadConfig;
import edu.brown.benchmark.auctionmark.util.GlobalAttributeGroupId;
import edu.brown.benchmark.auctionmark.util.GlobalAttributeValueId;
import edu.brown.benchmark.auctionmark.util.ItemId;
import edu.brown.benchmark.auctionmark.util.ItemInfo;
import edu.brown.benchmark.auctionmark.util.UserId;
import edu.brown.benchmark.auctionmark.util.UserIdGenerator;
import edu.brown.catalog.CatalogUtil;
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.RandomDistribution.DiscreteRNG;
import edu.brown.rand.RandomDistribution.FlatHistogram;
import edu.brown.rand.RandomDistribution.Gaussian;
import edu.brown.rand.RandomDistribution.Zipf;
import edu.brown.statistics.ObjectHistogram;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.JSONUtil;
import edu.brown.utils.StringUtil;

/**
* AuctionMark Profile Information
* @author pavlo
*/
public class AuctionMarkProfile {
    private static final Logger LOG = Logger.getLogger(AuctionMarkProfile.class);
    private static final LoggerBoolean debug = new LoggerBoolean();
    private static final LoggerBoolean trace = new LoggerBoolean();
    static {
        LoggerUtil.attachObserver(LOG, debug, trace);
    }

    /**
     * We maintain a cached version of the profile that we will copy from
     * This prevents the need to have every single client thread load up a separate copy
     */
    private static AuctionMarkProfile cachedProfile;
   
    // ----------------------------------------------------------------
    // REQUIRED REFERENCES
    // ----------------------------------------------------------------
   
    /**
     * Specialized random number generator
     */
    protected final AbstractRandomGenerator rng;
    private final int num_clients;
    protected transient File data_directory;

    // ----------------------------------------------------------------
    // SERIALIZABLE DATA MEMBERS
    // ----------------------------------------------------------------

    /**
     * Database Scale Factor
     */
    private double scale_factor;
   
    /**
     * The start time used when creating the data for this benchmark
     */
    private TimestampType benchmarkStartTime;
   
    /**
     * A histogram for the number of users that have the number of items listed
     * ItemCount -> # of Users
     */
    protected ObjectHistogram<Long> users_per_item_count = new ObjectHistogram<Long>();
   

    // ----------------------------------------------------------------
    // TRANSIENT DATA MEMBERS
    // ----------------------------------------------------------------
   
    /**
     * Histogram for number of items per category (stored as category_id)
     */
    protected ObjectHistogram<Long> item_category_histogram = new ObjectHistogram<Long>();

    /**
     * Three status types for an item:
     *  (1) Available - The auction of this item is still open
     *  (2) Ending Soon
     *  (2) Wait for Purchase - The auction of this item is still open.
     *      There is a bid winner and the bid winner has not purchased the item.
     *  (3) Complete (The auction is closed and (There is no bid winner or
     *      the bid winner has already purchased the item)
     */
    private transient final LinkedList<ItemInfo> items_available = new LinkedList<ItemInfo>();
    private transient final LinkedList<ItemInfo> items_endingSoon = new LinkedList<ItemInfo>();
    private transient final LinkedList<ItemInfo> items_waitingForPurchase = new LinkedList<ItemInfo>();
    private transient final LinkedList<ItemInfo> items_completed = new LinkedList<ItemInfo>();
   
    /**
     * Internal list of GlobalAttributeGroupIds
     */
    protected transient List<GlobalAttributeGroupId> gag_ids = new ArrayList<GlobalAttributeGroupId>();

    /**
     * Internal map of UserIdGenerators
     */
    private transient final Map<Integer, UserIdGenerator> userIdGenerators = new HashMap<Integer, UserIdGenerator>();
   
    /**
     * Data used for calculating temporally skewed user ids
     */
    private transient Integer current_tick = -1;
    private transient Integer window_total = null;
    private transient Integer window_size = null;
    private transient final ObjectHistogram<Integer> window_histogram = new ObjectHistogram<Integer>(true);
    private transient final List<Integer> window_partitions = new ArrayList<Integer>();
   
    /** Random time different in seconds */
    public transient final DiscreteRNG randomTimeDiff;
   
    /** Random duration in days */
    public transient final Gaussian randomDuration;

    protected transient final Zipf randomNumImages;
    protected transient final Zipf randomNumAttributes;
    protected transient final Zipf randomPurchaseDuration;
    protected transient final Zipf randomNumComments;
    protected transient final Zipf randomInitialPrice;

    private transient FlatHistogram<Long> randomCategory;
    private transient FlatHistogram<Long> randomItemCount;
   
    /**
     * The last time that we called CHECK_WINNING_BIDS on this client
     */
    private transient TimestampType lastCloseAuctionsTime;
    /**
     * When this client started executing
     */
    private TimestampType clientStartTime;
    /**
     * Current Timestamp
     */
    private TimestampType currentTime;
   
    /**
     * Keep track of previous waitForPurchase ItemIds so that we don't try to call NewPurchase
     * on them more than once
     */
    private transient Set<ItemInfo> previousWaitForPurchase = new HashSet<ItemInfo>();
   
    // -----------------------------------------------------------------
    // CONSTRUCTOR
    // -----------------------------------------------------------------

    /**
     * Constructor - Keep your pimp hand strong!
     */
    public AuctionMarkProfile(AbstractRandomGenerator rng, int num_clients) {
        this.rng = rng;
        this.num_clients = num_clients;

        this.randomInitialPrice = new Zipf(this.rng, AuctionMarkConstants.ITEM_MIN_INITIAL_PRICE,
                                                     AuctionMarkConstants.ITEM_MAX_INITIAL_PRICE, 1.001);
       
        // Random time difference in a second scale
        this.randomTimeDiff = new Gaussian(this.rng, -AuctionMarkConstants.ITEM_PRESERVE_DAYS * 24 * 60 * 60,
                                                     AuctionMarkConstants.ITEM_MAX_DURATION_DAYS * 24 * 60 * 60);
        this.randomDuration = new Gaussian(this.rng, 1, AuctionMarkConstants.ITEM_MAX_DURATION_DAYS);
        this.randomPurchaseDuration = new Zipf(this.rng, 0, AuctionMarkConstants.ITEM_MAX_PURCHASE_DURATION_DAYS, 1.001);
        this.randomNumImages = new Zipf(this.rng,   AuctionMarkConstants.ITEM_MIN_IMAGES,
                                                    AuctionMarkConstants.ITEM_MAX_IMAGES, 1.001);
        this.randomNumAttributes = new Zipf(this.rng, AuctionMarkConstants.ITEM_MIN_GLOBAL_ATTRS,
                                                    AuctionMarkConstants.ITEM_MAX_GLOBAL_ATTRS, 1.001);
        this.randomNumComments = new Zipf(this.rng, AuctionMarkConstants.ITEM_MIN_COMMENTS,
                                                    AuctionMarkConstants.ITEM_MAX_COMMENTS, 1.001);

       
        // _lastUserId = this.getTableSize(AuctionMarkConstants.TABLENAME_USER);

        LOG.debug("AuctionMarkBenchmarkProfile :: constructor");
    }
   
    // -----------------------------------------------------------------
    // SERIALIZATION METHODS
    // -----------------------------------------------------------------

    protected final void saveProfile(AuctionMarkLoader baseClient) {
        Database catalog_db = baseClient.getCatalogContext().database;
       
     // CONFIG_PROFILE
        Table catalog_tbl = catalog_db.getTables().get(AuctionMarkConstants.TABLENAME_CONFIG_PROFILE);
        VoltTable vt = CatalogUtil.getVoltTable(catalog_tbl);
        assert(vt != null);
        vt.addRow(
            this.scale_factor,                  // CFP_SCALE_FACTOR
            this.benchmarkStartTime,            // CFP_BENCHMARK_START
            this.users_per_item_count.toJSONString() // CFP_USER_ITEM_HISTOGRAM
        );
        if (debug.val)
            LOG.debug("Saving profile information into " + catalog_tbl);
        baseClient.loadVoltTable(catalog_tbl.getName(), vt);
       
        return;
    }
   
    private AuctionMarkProfile copyProfile(AuctionMarkProfile other) {
        this.scale_factor = other.scale_factor;
        this.benchmarkStartTime = other.benchmarkStartTime;
        this.users_per_item_count = other.users_per_item_count;
        this.item_category_histogram = other.item_category_histogram;
        this.gag_ids = other.gag_ids;
        this.previousWaitForPurchase = other.previousWaitForPurchase;
       
        this.items_available.addAll(other.items_available);
        Collections.shuffle(this.items_available);
       
        this.items_endingSoon.addAll(other.items_endingSoon);
        Collections.shuffle(this.items_endingSoon);
       
        this.items_waitingForPurchase.addAll(other.items_waitingForPurchase);
        Collections.shuffle(this.items_waitingForPurchase);
       
        this.items_completed.addAll(other.items_completed);
        Collections.shuffle(this.items_completed);
       
        return (this);
    }
   
    /**
     * Load the profile information stored in the database
     * @param
     */
    protected synchronized void loadProfile(AuctionMarkClient baseClient) {
        // Check whether we have a cached Profile we can copy from
        if (cachedProfile != null) {
            if (debug.val) LOG.debug("Using cached SEATSProfile");
            this.copyProfile(cachedProfile);
            return;
        }
       
        if (debug.val)
            LOG.debug("Loading AuctionMarkProfile for the first time");
       
        Client client = baseClient.getClientHandle();
        ClientResponse response = null;
        try {
            response = client.callProcedure(LoadConfig.class.getSimpleName());
        } catch (Exception ex) {
            throw new RuntimeException("Failed retrieve data from " + AuctionMarkConstants.TABLENAME_CONFIG_PROFILE, ex);
        }
        assert(response != null);
        assert(response.getStatus() == Status.OK) : "Unexpected " + response;

        VoltTable results[] = response.getResults();
        int result_idx = 0;
       
        // CONFIG_PROFILE
        this.loadConfigProfile(results[result_idx++]);
       
        // IMPORTANT: We need to set these timestamps here. It must be done
        // after we have loaded benchmarkStartTime
        this.setAndGetClientStartTime();
        this.updateAndGetCurrentTime();
       
        // ITEM CATEGORY COUNTS
        this.loadItemCategoryCounts(results[result_idx++]);
       
        // ITEMS
        for (ItemStatus status : ItemStatus.values()) {
            if (status.isInternal()) continue;
            this.loadItems(results[result_idx++], status);
        } // FOR
       
        // GLOBAL_ATTRIBUTE_GROUPS
        this.loadGlobalAttributeGroups(results[result_idx++]);
       
        cachedProfile = this;
    }
   
    private final void loadConfigProfile(VoltTable vt) {
        boolean adv = vt.advanceRow();
        assert(adv);
        int col = 0;
        this.scale_factor = vt.getDouble(col++);
        this.benchmarkStartTime = vt.getTimestampAsTimestamp(col++);
        JSONUtil.fromJSONString(this.users_per_item_count, vt.getString(col++));
       
        if (debug.val)
            LOG.debug(String.format("Loaded %s data", AuctionMarkConstants.TABLENAME_CONFIG_PROFILE));
    }
   
    private final void loadItemCategoryCounts(VoltTable vt) {
        while (vt.advanceRow()) {
            int col = 0;
            long i_c_id = vt.getLong(col++);
            int count = (int)vt.getLong(col++);
            this.item_category_histogram.put(i_c_id, count);
        } // WHILE
        if (debug.val)
            LOG.debug(String.format("Loaded %d item category records from %s",
                                    this.item_category_histogram.getValueCount(), AuctionMarkConstants.TABLENAME_ITEM));
    }
   
    private final void loadItems(VoltTable vt, ItemStatus status) {
        int ctr = 0;
        while (vt.advanceRow()) {
            int col = 0;
            ItemId i_id = new ItemId(vt.getLong(col++));
            double i_current_price = vt.getDouble(col++);
            TimestampType i_end_date = vt.getTimestampAsTimestamp(col++);
            int i_num_bids = (int)vt.getLong(col++);
            ItemStatus i_status = ItemStatus.get(vt.getLong(col++));
            assert(i_status == status);
           
            ItemInfo itemInfo = new ItemInfo(i_id, i_current_price, i_end_date, i_num_bids);
            this.addItemToProperQueue(itemInfo, false);
            ctr++;
        } // WHILE
       
        if (debug.val)
            LOG.debug(String.format("Loaded %d records from %s",
                                    ctr, AuctionMarkConstants.TABLENAME_ITEM));
    }
   
    private final void loadGlobalAttributeGroups(VoltTable vt) {
        while (vt.advanceRow()) {
            long gag_id = vt.getLong(0);
            assert(gag_id != VoltType.NULL_BIGINT);
            this.gag_ids.add(new GlobalAttributeGroupId(gag_id));
        } // WHILE
        if (debug.val)
            LOG.debug(String.format("Loaded %d records from %s",
                                    this.gag_ids.size(), AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_GROUP));
    }
   
   
    // -----------------------------------------------------------------
    // UTILITY METHODS
    // -----------------------------------------------------------------
   
    public void setDataDirectory(File dataDir) {
        this.data_directory = dataDir;
    }
   
    /**
     * @param window
     * @param num_ticks
     * @param total
     */
    public void enableTemporalSkew(int window, int total) {
        this.window_size = window;
        this.window_total = total;

        for (int p = 0; p < this.window_total; p++) {
            this.window_histogram.put(p, 0);
        }
        this.tick();
    }

    /**
     *
     */
    public void tick() {
        this.current_tick++;
       
        // Update Skew Window
        if (this.window_size != null) {
            Integer last_partition = -1;
            if (this.window_partitions.isEmpty() == false) {
                last_partition = CollectionUtil.last(this.window_partitions);
            }

            this.window_partitions.clear();
            for (int ctr = 0; ctr < this.window_size; ctr++) {
                last_partition++;
                if (last_partition > this.window_total) {
                    last_partition = 0;
                }
                this.window_partitions.add(last_partition);
            } // FOR
            LOG.info("Skew Tick #" + this.current_tick + " Window: " + this.window_partitions);
            if (debug.val) LOG.debug("Skew Window Histogram\n" + this.window_histogram);
            this.window_histogram.clearValues();
        }
    }
   
    private int getPartition(UserId seller_id) {
        return (TheHashinator.hashToPartition(seller_id, this.window_total));
    }

    // -----------------------------------------------------------------
    // TIME METHODS
    // -----------------------------------------------------------------

    /**
     *
     * @param benchmarkStart
     * @param clientStart
     * @param current
     * @return
     */
    public static TimestampType getScaledTimestamp(TimestampType benchmarkStart, TimestampType clientStart, TimestampType current) {
//        if (benchmarkStart == null || clientStart == null || current == null) return (null);
       
        // First get the offset between the benchmarkStart and the clientStart
        // We then subtract that value from the current time. This gives us the total elapsed
        // time from the current time to the time that the benchmark start (with the gap
        // from when the benchmark was loading data cut out)
        long base = benchmarkStart.getTime();
        long offset = current.getTime() - (clientStart.getTime() - base);
        long elapsed = (offset - base) * AuctionMarkConstants.TIME_SCALE_FACTOR;
        return (new TimestampType(base + elapsed));
    }

   
    private TimestampType getScaledCurrentTimestamp() {
        assert(this.clientStartTime != null);
        TimestampType now = new TimestampType();
        TimestampType time = AuctionMarkProfile.getScaledTimestamp(this.benchmarkStartTime, this.clientStartTime, now);
        if (trace.val)
            LOG.trace(String.format("Scaled:%d / Now:%d / BenchmarkStart:%d / ClientStart:%d",
                                   time.getMSTime(), now.getMSTime(), this.benchmarkStartTime.getMSTime(), this.clientStartTime.getMSTime()));
        return (time);
    }
   
    public synchronized TimestampType updateAndGetCurrentTime() {
        this.currentTime = this.getScaledCurrentTimestamp();
        return this.currentTime;
    }
    public TimestampType getCurrentTime() {
        return this.currentTime;
    }
   
    public TimestampType setAndGetBenchmarkStartTime() {
        assert(this.benchmarkStartTime == null);
        this.benchmarkStartTime = new TimestampType();
        return (this.benchmarkStartTime);
    }
    public TimestampType getBenchmarkStartTime() {
        return (this.benchmarkStartTime);
    }

    public TimestampType setAndGetClientStartTime() {
        assert(this.clientStartTime == null);
        this.clientStartTime = new TimestampType();
        return (this.clientStartTime);
    }
    public TimestampType getClientStartTime() {
        return (this.clientStartTime);
    }
    public boolean hasClientStartTime() {
        return (this.clientStartTime != null);
    }
   

    public synchronized TimestampType updateAndGetLastCloseAuctionsTime() {
        this.lastCloseAuctionsTime = this.getScaledCurrentTimestamp();
        return this.lastCloseAuctionsTime;
    }
    public TimestampType getLastCloseAuctionsTime() {
        return this.lastCloseAuctionsTime;
    }
    public boolean hasLastCloseAuctionsTime() {
        return (this.lastCloseAuctionsTime != null);
    }
   
   
    // -----------------------------------------------------------------
    // GENERAL METHODS
    // -----------------------------------------------------------------

    /**
     * Get the scale factor value for this benchmark profile
     * @return
     */
    public double getScaleFactor() {
        return (this.scale_factor);
    }
    /**
     * Set the scale factor for this benchmark profile
     * @param scale_factor
     */
    public void setScaleFactor(double scale_factor) {
        assert (scale_factor > 0) : "Invalid scale factor " + scale_factor;
        this.scale_factor = scale_factor;
    }
   
    // ----------------------------------------------------------------
    // USER METHODS
    // ----------------------------------------------------------------

    /**
     *
     * @param min_item_count
     * @param client
     * @param skew
     * @param exclude
     * @return
     */
    private UserId getRandomUserId(int min_item_count, Integer client, boolean skew, UserId...exclude) {
        if (this.randomItemCount == null) {
            synchronized (this) {
                if (this.randomItemCount == null)
                    this.randomItemCount = new FlatHistogram<Long>(this.rng, this.users_per_item_count);
            } // SYNCH
        }
        skew = (skew && this.window_size != null);
       
        // First grab the UserIdGenerator for the client (or -1 if there is no client)
        // and seek to that itemCount. We use the UserIdGenerator to ensure that we always
        // select the next UserId for a given client from the same set of UserIds
        Integer client_idx = (client == null ? -1 : client);
        UserIdGenerator gen = this.userIdGenerators.get(client_idx);
        if (gen == null) {
            synchronized (this.userIdGenerators) {
                gen = this.userIdGenerators.get(client);
                if (gen == null) {
                    gen = new UserIdGenerator(this.users_per_item_count, this.num_clients, client);
                    this.userIdGenerators.put(client_idx, gen);
                }
            } // SYNCH
        }
       
        UserId user_id = null;
        int tries = 1000;
        while (user_id == null && tries-- > 0) {
            // We first need to figure out how many items our seller needs to have
            long itemCount = -1;
            assert(min_item_count < this.users_per_item_count.getMaxValue());
            while (itemCount < min_item_count) {
                itemCount = this.randomItemCount.nextValue();
            } // WHILE
       
            // Set the current item count and then choose a random position
            // between where the generator is currently at and where it ends
            synchronized (gen) {
                gen.setCurrentItemCount(itemCount);
                long position = rng.number(gen.getCurrentPosition(), gen.getTotalUsers());
                user_id = gen.seekToPosition(position);
            } // SYNCH
            if (user_id == null) continue;
           
            // Make sure that we didn't select the same UserId as the one we were
            // told to exclude.
            if (exclude != null && exclude.length > 0) {
                for (UserId ex : exclude) {
                    if (ex != null && ex.equals(user_id)) {
                        if (trace.val) LOG.trace("Excluding " + user_id);
                        user_id = null;
                        break;
                    }
                } // FOR
                if (user_id == null) continue;
            }
           
            // If we don't care about skew, then we're done right here
            if (skew == false || this.window_size == null) {
                if (trace.val) LOG.trace("Selected " + user_id);
                break;
            }
           
            // Otherwise, check whether this seller maps to our current window
            Integer partition = this.getPartition(user_id);
            if (this.window_partitions.contains(partition)) {
                this.window_histogram.put(partition);
                break;
            }
            if (trace.val) LOG.trace("Skipping " + user_id);
            user_id = null;
        } // WHILE
        assert(user_id != null) : String.format("Failed to select a random UserId " +
                                                "[min_item_count=%d, client=%d, skew=%s, exclude=%s, totalPossible=%d, currentPosition=%d]",
                                                min_item_count, client, skew, Arrays.toString(exclude),
                                                gen.getTotalUsers(), gen.getCurrentPosition());
       
        return (user_id);
    }

    /**
     * Gets a random buyer ID for all clients
     * @return
     */
    public UserId getRandomBuyerId(UserId...exclude) {
        // We don't care about skewing the buyerIds at this point, so just get one from getRandomUserId
        return (this.getRandomUserId(0, null, false, exclude));
    }
    /**
     * Gets a random buyer ID for the given client
     * @return
     */
    public UserId getRandomBuyerId(int client, UserId...exclude) {
        // We don't care about skewing the buyerIds at this point, so just get one from getRandomUserId
        return (this.getRandomUserId(0, client, false, exclude));
    }
    /**
     * Get a random buyer UserId, where the probability that a particular user is selected
     * increases based on the number of bids that they have made in the past. We won't allow
     * the last bidder to be selected again
     * @param previousBidders
     * @return
     */
    public UserId getRandomBuyerId(ObjectHistogram<UserId> previousBidders, UserId...exclude) {
        // This is very inefficient, but it's probably good enough for now
        ObjectHistogram<UserId> new_h = new ObjectHistogram<UserId>(previousBidders);
        new_h.setKeepZeroEntries(false);
        for (UserId ex : exclude) new_h.remove(ex);
        new_h.put(this.getRandomBuyerId(exclude));
        try {
            LOG.trace("New Histogram:\n" + new_h);
        } catch (NullPointerException ex) {
            for (UserId user_id : new_h.values()) {
                System.err.println(String.format("%s => NEW:%s / ORIG:%s", user_id, new_h.get(user_id), previousBidders.get(user_id)));
            }
            throw ex;
        }
       
        FlatHistogram<UserId> rand_h = new FlatHistogram<UserId>(rng, new_h);
        return (rand_h.nextValue());
    }
   
    /**
     * Gets a random SellerID for the given client
     * @return
     */
    public UserId getRandomSellerId(int client) {
        return (this.getRandomUserId(1, client, true));
    }
   
    // ----------------------------------------------------------------
    // ITEM METHODS
    // ----------------------------------------------------------------
   
    private boolean addItem(LinkedList<ItemInfo> itemSet, ItemInfo itemInfo) {
        boolean added = false;
        synchronized (itemSet) {
            if (itemSet.contains(itemInfo)) {
                // HACK: Always swap existing ItemInfos with our new one, since it will
                // more up-to-date information
                int idx = itemSet.indexOf(itemInfo);
                ItemInfo existing = itemSet.set(idx, itemInfo);
                assert(existing != null);
                return (true);
            }
            if (itemInfo.hasCurrentPrice())
                assert(itemInfo.getCurrentPrice() > 0) : "Negative current price for " + itemInfo;
           
            // If we have room, shove it right in
            // We'll throw it in the back because we know it hasn't been used yet
            if (itemSet.size() < AuctionMarkConstants.ITEM_ID_CACHE_SIZE) {
                itemSet.addLast(itemInfo);
                added = true;
           
            // Otherwise, we can will randomly decide whether to pop one out
            } else if (this.rng.nextBoolean()) {
                itemSet.pop();
                itemSet.addLast(itemInfo);
                added = true;
            }
        } // SYNCH
        return (added);
    }
    private void removeItem(LinkedList<ItemInfo> itemSet, ItemInfo itemInfo) {
        synchronized (itemSet) {
            itemSet.remove(itemInfo);
        } // SYNCH
    }
   
    public synchronized void updateItemQueues() {
        // HACK
        Set<ItemInfo> all = new HashSet<ItemInfo>();
        @SuppressWarnings("unchecked")
        LinkedList<ItemInfo> itemSets[] = new LinkedList[]{
            this.items_available,
            this.items_endingSoon,
            this.items_waitingForPurchase,
            this.items_completed,
        };
        for (LinkedList<ItemInfo> list : itemSets) {
            synchronized (list) {
                all.addAll(list);
            } // SYNCH
        } // FOR
       
        TimestampType currentTime = this.updateAndGetCurrentTime();
        assert(currentTime != null);
        if (debug.val) LOG.debug("CurrentTime: " + currentTime);
        for (ItemInfo itemInfo : all) {
            this.addItemToProperQueue(itemInfo, false);
        } // FOR
       
        if (debug.val) {
            Map<ItemStatus, Integer> m = new HashMap<ItemStatus, Integer>();
            m.put(ItemStatus.OPEN, this.items_available.size());
            m.put(ItemStatus.ENDING_SOON, this.items_endingSoon.size());
            m.put(ItemStatus.WAITING_FOR_PURCHASE, this.items_waitingForPurchase.size());
            m.put(ItemStatus.CLOSED, this.items_completed.size());
            LOG.debug(String.format("Updated Item Queues [%s]:\n%s",
                                    currentTime, StringUtil.formatMaps(m)));
        }
    }
   
    public ItemStatus addItemToProperQueue(ItemInfo itemInfo, boolean is_loader) {
        // Calculate how much time is left for this auction
        TimestampType baseTime = (is_loader ? this.getBenchmarkStartTime() :
                                              this.getCurrentTime());
        assert(itemInfo.endDate != null);
        assert(baseTime != null) : "is_loader=" + is_loader;
        long remaining = itemInfo.endDate.getMSTime() - baseTime.getMSTime();
        ItemStatus ret;
       
        // Already ended
        if (remaining <= 100000) {
            if (itemInfo.numBids > 0 && itemInfo.status != ItemStatus.CLOSED) {
                synchronized (this.previousWaitForPurchase) {
                    if (this.previousWaitForPurchase.contains(itemInfo) == false) {
                        this.previousWaitForPurchase.add(itemInfo);
                        this.addItem(this.items_waitingForPurchase, itemInfo);
                    }
                } // SYNCH
                ret = ItemStatus.WAITING_FOR_PURCHASE;
            } else {
                this.addItem(this.items_completed, itemInfo);
                ret = ItemStatus.CLOSED;
            }
            this.removeAvailableItem(itemInfo);
            this.removeEndingSoonItem(itemInfo);
        }
        // About to end soon
        else if (remaining < AuctionMarkConstants.ENDING_SOON) {
            this.addItem(this.items_endingSoon, itemInfo);
            this.removeAvailableItem(itemInfo);
            ret = ItemStatus.ENDING_SOON;
        }
        // Still available
        else {
            this.addItem(this.items_available, itemInfo);
            ret = ItemStatus.OPEN;
        }
       
        if (trace.val)
            LOG.trace(String.format("%s - #%d [%s]", ret, itemInfo.itemId.encode(), itemInfo.getEndDate()));
       
        return (ret);
    }
   
    /**
     *
     * @param itemSet
     * @param needCurrentPrice
     * @param needFutureEndDate TODO
     * @return
     */
    private ItemInfo getRandomItem(LinkedList<ItemInfo> itemSet, boolean needCurrentPrice, boolean needFutureEndDate) {
        ItemInfo itemInfo = null;
        Set<ItemInfo> seen = new HashSet<ItemInfo>();
        TimestampType currentTime = this.updateAndGetCurrentTime();
       
        synchronized (itemSet) {
            int num_items = itemSet.size();
            Integer partition = null;
            int idx = -1;
           
            if (trace.val)
                LOG.trace(String.format("Getting random ItemInfo [numItems=%d, currentTime=%s, needCurrentPrice=%s]",
                                       num_items, currentTime, needCurrentPrice));
            long tries = 1000;
            while (num_items > 0 && tries-- > 0 && seen.size() < num_items) {
                partition = null;
                idx = this.rng.nextInt(num_items);
                ItemInfo temp = itemSet.get(idx);
                assert(temp != null);
                if (seen.contains(temp)) continue;
                seen.add(temp);
               
                // Needs to have an embedded currentPrice
                if (needCurrentPrice && temp.hasCurrentPrice() == false) {
                    continue;
                }
               
                // If they want an item that is ending in the future, then we compare it with
                // the current timestamp
                if (needFutureEndDate) {
                    boolean compareTo = (temp.getEndDate().compareTo(currentTime) < 0);
                    if (trace.val)
                        LOG.trace("CurrentTime:" + currentTime + " / EndTime:" + temp.getEndDate() + " [compareTo=" + compareTo + "]");
                    if (temp.hasEndDate() == false || compareTo) {
                        continue;
                    }
                }
               
                // Uniform
                if (this.window_size == null) {
                    itemInfo = temp;
                    break;
                }
                // Temporal Skew
                partition = this.getPartition(temp.getSellerId());
                if (this.window_partitions.contains(partition)) {
                    this.window_histogram.put(partition);
                    itemInfo = temp;
                    break;
                }
            } // WHILE
            if (itemInfo == null) {
                if (debug.val) LOG.debug("Failed to find ItemInfo [hasCurrentPrice=" + needCurrentPrice + ", needFutureEndDate=" + needFutureEndDate + "]");
                return (null);
            }
            assert(idx >= 0);
           
            // Take the item out of the set and insert back to the front
            // This is so that we can maintain MRU->LRU ordering
            itemSet.remove(idx);
            itemSet.addFirst(itemInfo);
        } // SYNCHRONIZED
        if (needCurrentPrice) {
            assert(itemInfo.hasCurrentPrice()) : "Missing currentPrice for " + itemInfo;
            assert(itemInfo.getCurrentPrice() > 0) : "Negative currentPrice '" + itemInfo.getCurrentPrice() + "' for " + itemInfo;
        }
        if (needFutureEndDate) {
            assert(itemInfo.hasEndDate()) : "Missing endDate for " + itemInfo;
        }
        return itemInfo;
    }
   
    /**********************************************************************************************
     * AVAILABLE ITEMS
     **********************************************************************************************/
    public void removeAvailableItem(ItemInfo itemInfo) {
        this.removeItem(this.items_available, itemInfo);
    }
    public ItemInfo getRandomAvailableItemId() {
        return this.getRandomItem(this.items_available, false, false);
    }
    public ItemInfo getRandomAvailableItem(boolean hasCurrentPrice) {
        return this.getRandomItem(this.items_available, hasCurrentPrice, false);
    }
    public int getAvailableItemsCount() {
        return this.items_available.size();
    }

    /**********************************************************************************************
     * ENDING SOON ITEMS
     **********************************************************************************************/
    public void removeEndingSoonItem(ItemInfo itemInfo) {
        this.removeItem(this.items_endingSoon, itemInfo);
    }
    public ItemInfo getRandomEndingSoonItem() {
        return this.getRandomItem(this.items_endingSoon, false, true);
    }
    public ItemInfo getRandomEndingSoonItem(boolean hasCurrentPrice) {
        return this.getRandomItem(this.items_endingSoon, hasCurrentPrice, true);
    }
    public int getEndingSoonItemsCount() {
        return this.items_endingSoon.size();
    }
   
    /**********************************************************************************************
     * WAITING FOR PURCHASE ITEMS
     **********************************************************************************************/
    public void removeWaitForPurchaseItem(ItemInfo itemInfo) {
        this.removeItem(this.items_waitingForPurchase, itemInfo);
    }
    public ItemInfo getRandomWaitForPurchaseItem() {
        return this.getRandomItem(this.items_waitingForPurchase, false, false);
    }
    public int getWaitForPurchaseItemsCount() {
        return this.items_waitingForPurchase.size();
    }

    /**********************************************************************************************
     * COMPLETED ITEMS
     **********************************************************************************************/
    public void removeCompleteItem(ItemInfo itemInfo) {
        this.removeItem(this.items_completed, itemInfo);
    }
    public ItemInfo getRandomCompleteItem() {
        return this.getRandomItem(this.items_completed, false, false);
    }
    public int getCompleteItemsCount() {
        return this.items_completed.size();
    }
   
    /**********************************************************************************************
     * ALL ITEMS
     **********************************************************************************************/
    public int getAllItemsCount() {
        return (this.getAvailableItemsCount() +
                this.getEndingSoonItemsCount() +
                this.getWaitForPurchaseItemsCount() +
                this.getCompleteItemsCount());
    }
    @SuppressWarnings("unchecked")
    public ItemInfo getRandomItem() {
        assert(this.getAllItemsCount() > 0);
        LinkedList<ItemInfo> itemSets[] = new LinkedList[]{
            this.items_available,
            this.items_endingSoon,
            this.items_waitingForPurchase,
            this.items_completed,
        };
        int idx = -1;
        while (idx == -1 || itemSets[idx].isEmpty()) {
            idx = rng.nextInt(itemSets.length);
        } // WHILE
        return (this.getRandomItem(itemSets[idx], false, false));
    }

    // ----------------------------------------------------------------
    // GLOBAL ATTRIBUTE METHODS
    // ----------------------------------------------------------------

    /**
     * Return a random GlobalAttributeValueId
     * @return
     */
    public synchronized GlobalAttributeValueId getRandomGlobalAttributeValue() {
        int offset = rng.nextInt(this.gag_ids.size());
        GlobalAttributeGroupId gag_id = this.gag_ids.get(offset);
        assert(gag_id != null);
        int count = rng.nextInt(gag_id.getCount());
        GlobalAttributeValueId gav_id = new GlobalAttributeValueId(gag_id, count);
        return gav_id;
    }
   
    public synchronized long getRandomCategoryId() {
        if (this.randomCategory == null) {
            this.randomCategory = new FlatHistogram<Long>(this.rng, this.item_category_histogram);
        }
        return randomCategory.nextLong();
    }


    // -----------------------------------------------------------------
    // SERIALIZATION
    // -----------------------------------------------------------------

//    @Override
//    public void load(String input_path, Database catalog_db) throws IOException {
//        JSONUtil.load(this, catalog_db, input_path);
//    }
//
//    @Override
//    public void save(String output_path) throws IOException {
//        JSONUtil.save(this, output_path);
//    }
//
//    @Override
//    public String toJSONString() {
//        return (JSONUtil.toJSONString(this));
//    }
//
//    @Override
//    public void toJSON(JSONStringer stringer) throws JSONException {
//        JSONUtil.fieldsToJSON(stringer, this, AuctionMarkBenchmarkProfile.class, JSONUtil.getSerializableFields(this.getClass()));
//    }
//
//    @Override
//    public void fromJSON(JSONObject json_object, Database catalog_db) throws JSONException {
//        JSONUtil.fieldsFromJSON(json_object, catalog_db, this, AuctionMarkBenchmarkProfile.class, false, JSONUtil.getSerializableFields(this.getClass()));
//    }
}
TOP

Related Classes of edu.brown.benchmark.auctionmark.AuctionMarkProfile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.