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