Java tutorial
/*************************************************************************** * 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 com.oltpbenchmark.benchmarks.auctionmark; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; 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.commons.collections15.map.ListOrderedMap; import org.apache.log4j.Logger; import com.oltpbenchmark.benchmarks.auctionmark.procedures.LoadConfig; import com.oltpbenchmark.benchmarks.auctionmark.procedures.ResetDatabase; import com.oltpbenchmark.benchmarks.auctionmark.util.AuctionMarkUtil; import com.oltpbenchmark.benchmarks.auctionmark.util.GlobalAttributeGroupId; import com.oltpbenchmark.benchmarks.auctionmark.util.GlobalAttributeValueId; import com.oltpbenchmark.benchmarks.auctionmark.util.ItemCommentResponse; import com.oltpbenchmark.benchmarks.auctionmark.util.ItemId; import com.oltpbenchmark.benchmarks.auctionmark.util.ItemInfo; import com.oltpbenchmark.benchmarks.auctionmark.util.ItemStatus; import com.oltpbenchmark.benchmarks.auctionmark.util.UserId; import com.oltpbenchmark.benchmarks.auctionmark.util.UserIdGenerator; import com.oltpbenchmark.catalog.Table; import com.oltpbenchmark.util.Histogram; import com.oltpbenchmark.util.JSONUtil; import com.oltpbenchmark.util.RandomDistribution.DiscreteRNG; import com.oltpbenchmark.util.RandomDistribution.FlatHistogram; import com.oltpbenchmark.util.RandomDistribution.Gaussian; import com.oltpbenchmark.util.RandomDistribution.Zipf; import com.oltpbenchmark.util.RandomGenerator; import com.oltpbenchmark.util.SQLUtil; import com.oltpbenchmark.util.StringUtil; /** * AuctionMark Profile Information * @author pavlo */ public class AuctionMarkProfile { private static final Logger LOG = Logger.getLogger(AuctionMarkProfile.class); /** * 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 // ---------------------------------------------------------------- private final AuctionMarkBenchmark benchmark; private int client_id; /** * Specialized random number generator */ protected transient final RandomGenerator rng; /** * The total number of clients in this benchmark invocation. Each * client will be responsible for adding new auctions for unique set of sellers * This may change per benchmark invocation. */ private transient final int num_clients; // ---------------------------------------------------------------- // SERIALIZABLE DATA MEMBERS // ---------------------------------------------------------------- /** * Database Scale Factor */ protected double scale_factor; /** * The start time used when creating the data for this benchmark */ private Timestamp loaderStartTime; /** * The stop time for when the loader was finished * We can reset anything that has a timestamp after this one */ private Timestamp loaderStopTime; /** * A histogram for the number of users that have the number of items listed * ItemCount -> # of Users */ protected Histogram<Long> users_per_itemCount = new Histogram<Long>(); // ---------------------------------------------------------------- // TRANSIENT DATA MEMBERS // ---------------------------------------------------------------- /** * Histogram for number of items per category (stored as category_id) */ protected transient Histogram<Integer> items_per_category = new Histogram<Integer>(); /** * 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>(); @SuppressWarnings("unchecked") protected transient final LinkedList<ItemInfo> allItemSets[] = new LinkedList[] { this.items_available, this.items_endingSoon, this.items_waitingForPurchase, this.items_completed, }; /** * Internal list of GlobalAttributeGroupIds */ protected transient List<GlobalAttributeGroupId> gag_ids = new ArrayList<GlobalAttributeGroupId>(); /** * Internal map of UserIdGenerators */ private transient UserIdGenerator userIdGenerator; /** * 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<Integer> randomCategory; private transient FlatHistogram<Long> randomItemCount; /** * The last time that we called CHECK_WINNING_BIDS on this client */ private transient final Timestamp lastCloseAuctionsTime = new Timestamp(0); /** * When this client started executing */ private transient final Timestamp clientStartTime = new Timestamp(0); /** * Current Timestamp */ private transient final Timestamp currentTime = new Timestamp(0); /** * TODO */ protected transient final Histogram<UserId> seller_item_cnt = new Histogram<UserId>(); /** * TODO */ protected transient final List<ItemCommentResponse> pending_commentResponses = new ArrayList<ItemCommentResponse>(); // ----------------------------------------------------------------- // TEMPORARY VARIABLES // ----------------------------------------------------------------- private transient final Set<ItemInfo> tmp_seenItems = new HashSet<ItemInfo>(); private transient final Histogram<UserId> tmp_userIdHistogram = new Histogram<UserId>(true); private transient final Timestamp tmp_now = new Timestamp(System.currentTimeMillis()); // ----------------------------------------------------------------- // CONSTRUCTOR // ----------------------------------------------------------------- /** * Constructor - Keep your pimp hand strong! */ public AuctionMarkProfile(AuctionMarkBenchmark benchmark, RandomGenerator rng) { this(benchmark, -1, rng); } private AuctionMarkProfile(AuctionMarkBenchmark benchmark, int client_id, RandomGenerator rng) { this.benchmark = benchmark; this.client_id = client_id; this.rng = rng; this.scale_factor = benchmark.getWorkloadConfiguration().getScaleFactor(); this.num_clients = benchmark.getWorkloadConfiguration().getTerminals(); this.loaderStartTime = new Timestamp(System.currentTimeMillis()); this.randomInitialPrice = new Zipf(this.rng, AuctionMarkConstants.ITEM_INITIAL_PRICE_MIN, AuctionMarkConstants.ITEM_INITIAL_PRICE_MAX, AuctionMarkConstants.ITEM_INITIAL_PRICE_SIGMA); // Random time difference in a second scale this.randomTimeDiff = new Gaussian(this.rng, AuctionMarkConstants.ITEM_PRESERVE_DAYS * 24 * 60 * 60 * -1, AuctionMarkConstants.ITEM_DURATION_DAYS_MAX * 24 * 60 * 60); this.randomDuration = new Gaussian(this.rng, AuctionMarkConstants.ITEM_DURATION_DAYS_MIN, AuctionMarkConstants.ITEM_DURATION_DAYS_MAX); this.randomPurchaseDuration = new Zipf(this.rng, AuctionMarkConstants.ITEM_PURCHASE_DURATION_DAYS_MIN, AuctionMarkConstants.ITEM_PURCHASE_DURATION_DAYS_MAX, AuctionMarkConstants.ITEM_PURCHASE_DURATION_DAYS_SIGMA); this.randomNumImages = new Zipf(this.rng, AuctionMarkConstants.ITEM_NUM_IMAGES_MIN, AuctionMarkConstants.ITEM_NUM_IMAGES_MAX, AuctionMarkConstants.ITEM_NUM_IMAGES_SIGMA); this.randomNumAttributes = new Zipf(this.rng, AuctionMarkConstants.ITEM_NUM_GLOBAL_ATTRS_MIN, AuctionMarkConstants.ITEM_NUM_GLOBAL_ATTRS_MAX, AuctionMarkConstants.ITEM_NUM_GLOBAL_ATTRS_SIGMA); this.randomNumComments = new Zipf(this.rng, AuctionMarkConstants.ITEM_NUM_COMMENTS_MIN, AuctionMarkConstants.ITEM_NUM_COMMENTS_MAX, AuctionMarkConstants.ITEM_NUM_COMMENTS_SIGMA); if (LOG.isTraceEnabled()) LOG.trace("AuctionMarkBenchmarkProfile :: constructor"); } // ----------------------------------------------------------------- // SERIALIZATION METHODS // ----------------------------------------------------------------- protected final void saveProfile(Connection conn) throws SQLException { this.loaderStopTime = new Timestamp(System.currentTimeMillis()); // CONFIG_PROFILE Table catalog_tbl = this.benchmark.getCatalog().getTable(AuctionMarkConstants.TABLENAME_CONFIG_PROFILE); assert (catalog_tbl != null); PreparedStatement stmt = conn.prepareStatement(SQLUtil.getInsertSQL(catalog_tbl)); int param_idx = 1; stmt.setObject(param_idx++, this.scale_factor); // CFP_SCALE_FACTOR stmt.setObject(param_idx++, this.loaderStartTime); // CFP_LOADER_START stmt.setObject(param_idx++, this.loaderStopTime); // CFP_LOADER_STOP stmt.setObject(param_idx++, this.users_per_itemCount.toJSONString()); // CFP_USER_ITEM_HISTOGRAM int result = stmt.executeUpdate(); stmt.close(); assert (result == 1); if (LOG.isDebugEnabled()) LOG.debug("Saving profile information into " + catalog_tbl); return; } private AuctionMarkProfile copyProfile(AuctionMarkWorker worker, AuctionMarkProfile other) { this.client_id = worker.getId(); this.scale_factor = other.scale_factor; this.loaderStartTime = other.loaderStartTime; this.loaderStopTime = other.loaderStopTime; this.users_per_itemCount = other.users_per_itemCount; this.items_per_category = other.items_per_category; this.gag_ids = other.gag_ids; // Initialize the UserIdGenerator so we can figure out whether our // client should even have these ids this.initializeUserIdGenerator(this.client_id); assert (this.userIdGenerator != null); for (int i = 0; i < this.allItemSets.length; i++) { LinkedList<ItemInfo> list = this.allItemSets[i]; assert (list != null); LinkedList<ItemInfo> origList = other.allItemSets[i]; assert (origList != null); for (ItemInfo itemInfo : origList) { UserId sellerId = itemInfo.getSellerId(); if (this.userIdGenerator.checkClient(sellerId)) { this.seller_item_cnt.set(sellerId, sellerId.getItemCount()); list.add(itemInfo); } } // FOR Collections.shuffle(list); } // FOR for (ItemCommentResponse cr : other.pending_commentResponses) { UserId sellerId = new UserId(cr.sellerId); if (this.userIdGenerator.checkClient(sellerId)) { this.pending_commentResponses.add(cr); } } // FOR if (LOG.isTraceEnabled()) LOG.trace("SellerItemCounts:\n" + this.seller_item_cnt); return (this); } protected static void clearCachedProfile() { cachedProfile = null; } /** * Load the profile information stored in the database * @param */ protected void loadProfile(AuctionMarkWorker worker) throws SQLException { synchronized (AuctionMarkProfile.class) { // Check whether we have a cached Profile we can copy from if (cachedProfile == null) { // Store everything in the cached profile. // We can then copy from that and extract out only the records // that we need for each AuctionMarkWorker cachedProfile = new AuctionMarkProfile(this.benchmark, this.rng); // Otherwise we have to go fetch everything again // So first we want to reset the database Connection conn = worker.getConnection(); if (AuctionMarkConstants.RESET_DATABASE_ENABLE) { if (LOG.isDebugEnabled()) LOG.debug("Reseting database from last execution run"); worker.getProcedure(ResetDatabase.class).run(conn); } // Then invoke LoadConfig to pull down the profile information we need if (LOG.isDebugEnabled()) LOG.debug("Loading AuctionMarkProfile for the first time"); ResultSet results[] = worker.getProcedure(LoadConfig.class).run(conn); int result_idx = 0; // CONFIG_PROFILE loadConfigProfile(cachedProfile, results[result_idx++]); // IMPORTANT: We need to set these timestamps here. It must be done // after we have loaded benchmarkStartTime cachedProfile.setAndGetClientStartTime(); cachedProfile.updateAndGetCurrentTime(); // ITEM CATEGORY COUNTS loadItemCategoryCounts(cachedProfile, results[result_idx++]); // GLOBAL_ATTRIBUTE_GROUPS loadGlobalAttributeGroups(cachedProfile, results[result_idx++]); // PENDING COMMENTS loadPendingItemComments(cachedProfile, results[result_idx++]); // ITEMS while (result_idx < results.length) { // assert(results[result_idx].isClosed() == false) : // "Unexpected closed ITEM ResultSet [idx=" + result_idx + "]"; loadItems(cachedProfile, results[result_idx]); result_idx++; } // FOR for (ResultSet r : results) r.close(); conn.commit(); if (LOG.isDebugEnabled()) LOG.debug("Loaded profile:\n" + cachedProfile.toString()); } } // SYNCH if (LOG.isTraceEnabled()) LOG.trace("Using cached SEATSProfile"); this.copyProfile(worker, cachedProfile); } private final void initializeUserIdGenerator(int clientId) { assert (this.users_per_itemCount != null); assert (this.users_per_itemCount.isEmpty() == false); this.userIdGenerator = new UserIdGenerator(this.users_per_itemCount, this.num_clients, (clientId < 0 ? null : clientId)); } private static final void loadConfigProfile(AuctionMarkProfile profile, ResultSet vt) throws SQLException { boolean adv = vt.next(); assert (adv) : String.format("Failed to get data from %s\n%s", AuctionMarkConstants.TABLENAME_CONFIG_PROFILE, vt); int col = 1; profile.scale_factor = vt.getDouble(col++); profile.loaderStartTime = vt.getTimestamp(col++); profile.loaderStopTime = vt.getTimestamp(col++); JSONUtil.fromJSONString(profile.users_per_itemCount, vt.getString(col++)); if (LOG.isDebugEnabled()) LOG.debug(String.format("Loaded %s data", AuctionMarkConstants.TABLENAME_CONFIG_PROFILE)); } private static final void loadItemCategoryCounts(AuctionMarkProfile profile, ResultSet vt) throws SQLException { while (vt.next()) { int col = 1; long i_c_id = vt.getLong(col++); int count = vt.getInt(col++); profile.items_per_category.put((int) i_c_id, count); } // WHILE if (LOG.isDebugEnabled()) LOG.debug(String.format("Loaded %d CATEGORY records from %s", profile.items_per_category.getValueCount(), AuctionMarkConstants.TABLENAME_ITEM)); } private static final void loadItems(AuctionMarkProfile profile, ResultSet vt) throws SQLException { int ctr = 0; while (vt.next()) { int col = 1; ItemId i_id = new ItemId(vt.getLong(col++)); double i_current_price = vt.getDouble(col++); Timestamp i_end_date = vt.getTimestamp(col++); int i_num_bids = (int) vt.getLong(col++); // IMPORTANT: Do not set the status here so that we make sure that // it is added to the right queue ItemInfo itemInfo = new ItemInfo(i_id, i_current_price, i_end_date, i_num_bids); profile.addItemToProperQueue(itemInfo, false); ctr++; } // WHILE if (LOG.isDebugEnabled()) LOG.debug(String.format("Loaded %d records from %s", ctr, AuctionMarkConstants.TABLENAME_ITEM)); } private static final void loadPendingItemComments(AuctionMarkProfile profile, ResultSet vt) throws SQLException { while (vt.next()) { int col = 1; long ic_id = vt.getLong(col++); long ic_i_id = vt.getLong(col++); long ic_u_id = vt.getLong(col++); ItemCommentResponse cr = new ItemCommentResponse(ic_id, ic_i_id, ic_u_id); profile.pending_commentResponses.add(cr); } // WHILE if (LOG.isDebugEnabled()) LOG.debug(String.format("Loaded %d records from %s", profile.pending_commentResponses.size(), AuctionMarkConstants.TABLENAME_ITEM_COMMENT)); } private static final void loadGlobalAttributeGroups(AuctionMarkProfile profile, ResultSet vt) throws SQLException { while (vt.next()) { int col = 1; long gag_id = vt.getLong(col++); profile.gag_ids.add(new GlobalAttributeGroupId(gag_id)); } // WHILE if (LOG.isDebugEnabled()) LOG.debug(String.format("Loaded %d records from %s", profile.gag_ids.size(), AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_GROUP)); } // ----------------------------------------------------------------- // TIME METHODS // ----------------------------------------------------------------- private Timestamp getScaledCurrentTimestamp(Timestamp time) { assert (this.clientStartTime != null); tmp_now.setTime(System.currentTimeMillis()); time.setTime(AuctionMarkUtil.getScaledTimestamp(this.loaderStartTime, this.clientStartTime, tmp_now)); if (LOG.isTraceEnabled()) LOG.trace(String.format("Scaled:%d / Now:%d / BenchmarkStart:%d / ClientStart:%d", time.getTime(), tmp_now.getTime(), this.loaderStartTime.getTime(), this.clientStartTime.getTime())); return (time); } public synchronized Timestamp updateAndGetCurrentTime() { this.getScaledCurrentTimestamp(this.currentTime); if (LOG.isTraceEnabled()) LOG.trace("CurrentTime: " + currentTime); return this.currentTime; } public Timestamp getCurrentTime() { return this.currentTime; } public Timestamp getLoaderStartTime() { return (this.loaderStartTime); } public Timestamp getLoaderStopTime() { return (this.loaderStopTime); } public Timestamp setAndGetClientStartTime() { assert (this.clientStartTime.getTime() == 0); this.clientStartTime.setTime(System.currentTimeMillis()); return (this.clientStartTime); } public Timestamp getClientStartTime() { return (this.clientStartTime); } public boolean hasClientStartTime() { return (this.clientStartTime.getTime() != 0); } public synchronized Timestamp updateAndGetLastCloseAuctionsTime() { this.getScaledCurrentTimestamp(this.lastCloseAuctionsTime); return this.lastCloseAuctionsTime; } public Timestamp getLastCloseAuctionsTime() { return this.lastCloseAuctionsTime; } public boolean hasLastCloseAuctionsTime() { return (this.lastCloseAuctionsTime.getTime() != 0); } // ----------------------------------------------------------------- // 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 // ---------------------------------------------------------------- /** * Note that this synchronization block only matters for the loader * @param min_item_count * @param clientId - Will use null if less than zero * @param exclude * @return */ private synchronized UserId getRandomUserId(int min_item_count, int clientId, UserId... exclude) { // We use the UserIdGenerator to ensure that we always select the next UserId for // a given client from the same set of UserIds if (this.randomItemCount == null) { this.randomItemCount = new FlatHistogram<Long>(this.rng, this.users_per_itemCount); } if (this.userIdGenerator == null) this.initializeUserIdGenerator(clientId); UserId user_id = null; int tries = 1000; final long num_users = this.userIdGenerator.getTotalUsers() - 1; 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 this.userIdGenerator.setCurrentItemCount((int) itemCount); long cur_position = this.userIdGenerator.getCurrentPosition(); long new_position = rng.number(cur_position, num_users); user_id = this.userIdGenerator.seekToPosition((int) new_position); 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 (LOG.isTraceEnabled()) 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 (LOG.isTraceEnabled()) LOG.trace("Selected " + user_id); break; } // WHILE if (user_id == null && LOG.isDebugEnabled()) { LOG.warn(String.format( "Failed to select a random UserId " + "[minItemCount=%d, clientId=%d, exclude=%s, totalPossible=%d, currentPosition=%d]", min_item_count, clientId, Arrays.toString(exclude), this.userIdGenerator.getTotalUsers(), this.userIdGenerator.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, -1, 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, 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(Histogram<UserId> previousBidders, UserId... exclude) { // This is very inefficient, but it's probably good enough for now tmp_userIdHistogram.clear(); tmp_userIdHistogram.putHistogram(previousBidders); for (UserId ex : exclude) tmp_userIdHistogram.removeAll(ex); tmp_userIdHistogram.put(this.getRandomBuyerId(exclude)); try { LOG.trace("New Histogram:\n" + tmp_userIdHistogram); } catch (NullPointerException ex) { for (UserId user_id : tmp_userIdHistogram.values()) { System.err.println(String.format("%s => NEW:%s / ORIG:%s", user_id, tmp_userIdHistogram.get(user_id), previousBidders.get(user_id))); } throw ex; } FlatHistogram<UserId> rand_h = new FlatHistogram<UserId>(rng, tmp_userIdHistogram); return (rand_h.nextValue()); } /** * Gets a random SellerID for the given client * @return */ public UserId getRandomSellerId(int client) { return (this.getRandomUserId(1, client)); } public void addPendingItemCommentResponse(ItemCommentResponse cr) { if (this.client_id != -1) { UserId sellerId = new UserId(cr.sellerId); if (this.userIdGenerator.checkClient(sellerId) == false) { return; } } this.pending_commentResponses.add(cr); } // ---------------------------------------------------------------- // ITEM METHODS // ---------------------------------------------------------------- public ItemId getNextItemId(UserId seller_id) { Integer cnt = this.seller_item_cnt.get(seller_id); if (cnt == null || cnt == 0) { cnt = seller_id.getItemCount(); this.seller_item_cnt.put(seller_id, cnt); } this.seller_item_cnt.put(seller_id); return (new ItemId(seller_id, cnt.intValue())); } private boolean addItem(LinkedList<ItemInfo> items, ItemInfo itemInfo) { boolean added = false; int idx = items.indexOf(itemInfo); if (idx != -1) { // HACK: Always swap existing ItemInfos with our new one, since it will // more up-to-date information ItemInfo existing = items.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 (items.size() < AuctionMarkConstants.ITEM_ID_CACHE_SIZE) { items.addLast(itemInfo); added = true; // Otherwise, we can will randomly decide whether to pop one out } else if (this.rng.nextBoolean()) { items.pop(); items.addLast(itemInfo); added = true; } return (added); } public void updateItemQueues() { Timestamp currentTime = this.updateAndGetCurrentTime(); assert (currentTime != null); for (LinkedList<ItemInfo> items : allItemSets) { // If the items is already in the completed queue, then we don't need // to do anything with it. if (items == this.items_completed) continue; for (ItemInfo itemInfo : items) { this.addItemToProperQueue(itemInfo, currentTime); } // FOR } if (LOG.isDebugEnabled()) { 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 Timestamp baseTime = (is_loader ? this.getLoaderStartTime() : this.getCurrentTime()); assert (itemInfo.endDate != null); assert (baseTime != null) : "is_loader=" + is_loader; return addItemToProperQueue(itemInfo, baseTime); } private ItemStatus addItemToProperQueue(ItemInfo itemInfo, Timestamp baseTime) { // Always check whether we even want it for this client // The loader's profile and the cache profile will always have a negative client_id, // which means that we always want to keep it if (this.client_id != -1) { if (this.userIdGenerator == null) this.initializeUserIdGenerator(this.client_id); if (this.userIdGenerator.checkClient(itemInfo.getSellerId()) == false) { return (null); } } long remaining = itemInfo.endDate.getTime() - baseTime.getTime(); ItemStatus new_status = (itemInfo.status != null ? itemInfo.status : ItemStatus.OPEN); // Already ended if (remaining <= AuctionMarkConstants.ITEM_ALREADY_ENDED) { if (itemInfo.numBids > 0 && itemInfo.status != ItemStatus.CLOSED) { new_status = ItemStatus.WAITING_FOR_PURCHASE; } else { new_status = ItemStatus.CLOSED; } } // About to end soon else if (remaining < AuctionMarkConstants.ITEM_ENDING_SOON) { new_status = ItemStatus.ENDING_SOON; } if (new_status != itemInfo.status) { if (itemInfo.status != null) assert (new_status.ordinal() > itemInfo.status.ordinal()) : "Trying to improperly move " + itemInfo + " from " + itemInfo.status + " to " + new_status; switch (new_status) { case OPEN: this.addItem(this.items_available, itemInfo); break; case ENDING_SOON: this.items_available.remove(itemInfo); this.addItem(this.items_endingSoon, itemInfo); break; case WAITING_FOR_PURCHASE: (itemInfo.status == ItemStatus.OPEN ? this.items_available : this.items_endingSoon) .remove(itemInfo); this.addItem(this.items_waitingForPurchase, itemInfo); break; case CLOSED: if (itemInfo.status == ItemStatus.OPEN) this.items_available.remove(itemInfo); else if (itemInfo.status == ItemStatus.ENDING_SOON) this.items_endingSoon.remove(itemInfo); else this.items_waitingForPurchase.remove(itemInfo); this.addItem(this.items_completed, itemInfo); break; default: } // SWITCH itemInfo.status = new_status; } if (LOG.isTraceEnabled()) LOG.trace(String.format("%s - #%d [%s]", new_status, itemInfo.itemId.encode(), itemInfo.getEndDate())); return (new_status); } /** * * @param itemSet * @param needCurrentPrice * @param needFutureEndDate TODO * @return */ private ItemInfo getRandomItem(LinkedList<ItemInfo> itemSet, boolean needCurrentPrice, boolean needFutureEndDate) { Timestamp currentTime = this.updateAndGetCurrentTime(); int num_items = itemSet.size(); int idx = -1; ItemInfo itemInfo = null; if (LOG.isTraceEnabled()) LOG.trace(String.format("Getting random ItemInfo [numItems=%d, currentTime=%s, needCurrentPrice=%s]", num_items, currentTime, needCurrentPrice)); long tries = 1000; tmp_seenItems.clear(); while (num_items > 0 && tries-- > 0 && tmp_seenItems.size() < num_items) { idx = this.rng.nextInt(num_items); ItemInfo temp = itemSet.get(idx); assert (temp != null); if (tmp_seenItems.contains(temp)) continue; tmp_seenItems.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 (LOG.isTraceEnabled()) LOG.trace("CurrentTime:" + currentTime + " / EndTime:" + temp.getEndDate() + " [compareTo=" + compareTo + "]"); if (temp.hasEndDate() == false || compareTo) { continue; } } // Uniform itemInfo = temp; break; } // WHILE if (itemInfo == null) { if (LOG.isDebugEnabled()) 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); 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 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 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 ItemInfo getRandomWaitForPurchaseItem() { return this.getRandomItem(this.items_waitingForPurchase, false, false); } public int getWaitForPurchaseItemsCount() { return this.items_waitingForPurchase.size(); } /********************************************************************************************** * COMPLETED ITEMS **********************************************************************************************/ 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()); } public ItemInfo getRandomItem() { assert (this.getAllItemsCount() > 0); int idx = -1; while (idx == -1 || allItemSets[idx].isEmpty()) { idx = rng.nextInt(allItemSets.length); } // WHILE return (this.getRandomItem(allItemSets[idx], false, false)); } // ---------------------------------------------------------------- // GLOBAL ATTRIBUTE METHODS // ---------------------------------------------------------------- /** * Return a random GlobalAttributeValueId * @return */ public 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 int getRandomCategoryId() { if (this.randomCategory == null) { this.randomCategory = new FlatHistogram<Integer>(this.rng, this.items_per_category); } return randomCategory.nextInt(); } @Override public String toString() { Map<String, Object> m = new ListOrderedMap<String, Object>(); m.put("Scale Factor", this.scale_factor); m.put("Loader Start", this.loaderStartTime); m.put("Loader Stop", this.loaderStopTime); m.put("Last CloseAuctions", (this.lastCloseAuctionsTime.getTime() > 0 ? this.lastCloseAuctionsTime : null)); m.put("Client Start", this.clientStartTime); m.put("Current Virtual Time", this.currentTime); m.put("Pending ItemCommentResponses", this.pending_commentResponses.size()); // Item Queues Histogram<ItemStatus> itemCounts = new Histogram<ItemStatus>(true); for (ItemStatus status : ItemStatus.values()) { int cnt = 0; switch (status) { case OPEN: cnt = this.items_available.size(); break; case ENDING_SOON: cnt = this.items_endingSoon.size(); break; case WAITING_FOR_PURCHASE: cnt = this.items_waitingForPurchase.size(); break; case CLOSED: cnt = this.items_completed.size(); break; default: assert (false) : "Unexpected " + status; } // SWITCH itemCounts.put(status, cnt); } m.put("Item Queues", itemCounts); return (StringUtil.formatMaps(m)); } }