/*************************************************************************** * 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.lang.reflect.Field; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.apache.commons.collections15.CollectionUtils; import org.apache.commons.collections15.map.ListOrderedMap; import org.apache.log4j.Logger; import org.voltdb.CatalogContext; import org.voltdb.VoltTable; import org.voltdb.VoltType; import org.voltdb.catalog.Catalog; import org.voltdb.catalog.Column; import org.voltdb.catalog.Database; import org.voltdb.catalog.Table; import org.voltdb.types.TimestampType; import org.voltdb.utils.Pair; import org.voltdb.utils.VoltTypeUtil; import edu.brown.api.BenchmarkComponent; import edu.brown.benchmark.auctionmark.AuctionMarkConstants.ItemStatus; import edu.brown.benchmark.auctionmark.util.AuctionMarkCategoryParser; import edu.brown.benchmark.auctionmark.util.AuctionMarkUtil; import edu.brown.benchmark.auctionmark.util.Category; 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.LoaderItemInfo; import edu.brown.benchmark.auctionmark.util.UserId; import edu.brown.benchmark.auctionmark.util.UserIdGenerator; import edu.brown.catalog.CatalogUtil; import edu.brown.hstore.conf.HStoreConf; import edu.brown.logging.LoggerUtil; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.rand.AbstractRandomGenerator; import edu.brown.rand.DefaultRandomGenerator; import edu.brown.rand.RandomDistribution.Flat; import edu.brown.rand.RandomDistribution.Zipf; import edu.brown.statistics.ObjectHistogram; import edu.brown.utils.CollectionUtil; import edu.brown.utils.CompositeId; import edu.brown.utils.EventObservable; import edu.brown.utils.EventObservableExceptionHandler; import edu.brown.utils.EventObserver; /** * * @author pavlo * @author visawee */ public class AuctionMarkLoader extends BenchmarkComponent { private static final Logger LOG = Logger.getLogger(AuctionMarkLoader.class); private static final LoggerBoolean debug = new LoggerBoolean(); private static final LoggerBoolean trace = new LoggerBoolean(); static { LoggerUtil.attachObserver(LOG, debug, trace); } protected final AuctionMarkProfile profile; /** * Base catalog objects that we can reference to figure out how to access */ protected final Catalog catalog; protected final Database catalog_db; /** * Data Generator Classes * TableName -> AbstactTableGenerator */ private final Map<String, AbstractTableGenerator> generators = new ListOrderedMap<String, AbstractTableGenerator>(); private final Collection<String> sub_generators = new HashSet<String>(); /** The set of tables that we have finished loading **/ private final transient Collection<String> finished = new HashSet<String>(); /** * * @param args * @throws Exception */ public static void main(String args[]) throws Exception { edu.brown.api.BenchmarkComponent.main(AuctionMarkLoader.class, args, true); } /** * Constructor * * @param args */ public AuctionMarkLoader(String[] args) { super(args); if (debug.val) LOG.debug("AuctionMarkLoader::: numClients = " + this.getNumClients()); int seed = 0; String randGenClassName = DefaultRandomGenerator.class.getName(); String randGenProfilePath = null; File dataDir = null; Integer temporal_window = null; Integer temporal_total = null; for (String key : m_extraParams.keySet()) { String value = m_extraParams.get(key); // Random Generator Seed if (key.equalsIgnoreCase("RANDOMSEED")) { seed = Integer.parseInt(value); } // Random Generator Class else if (key.equalsIgnoreCase("RANDOMGENERATOR")) { randGenClassName = value; } // Random Generator Profile File else if (key.equalsIgnoreCase("RANDOMPROFILE")) { randGenProfilePath = value; } // Data directory else if (key.equalsIgnoreCase("DATADIR")) { dataDir = new File(value); } // Temporal Skew else if (key.equalsIgnoreCase("TEMPORALWINDOW")) { assert(m_extraParams.containsKey("TEMPORALTOTAL")) : "Missing TEMPORALTOTAL parameter"; temporal_window = Integer.valueOf(m_extraParams.get("TEMPORALWINDOW")); temporal_total = Integer.valueOf(m_extraParams.get("TEMPORALTOTAL")); } } // FOR // Random Generator AbstractRandomGenerator rng = null; try { rng = AbstractRandomGenerator.factory(randGenClassName, seed); if (randGenProfilePath != null) rng.loadProfile(randGenProfilePath); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } HStoreConf hstore_conf = this.getHStoreConf(); // Data Directory Path if (dataDir == null) dataDir = AuctionMarkUtil.getDataDirectory(); assert(dataDir != null); // BenchmarkProfile profile = new AuctionMarkProfile(rng, getNumClients()); profile.setDataDirectory(dataDir); profile.setAndGetBenchmarkStartTime(); profile.setScaleFactor(hstore_conf.client.scalefactor); // Temporal Skew if (temporal_window != null && temporal_window > 0) { profile.enableTemporalSkew(temporal_window, temporal_total); LOG.info(String.format("Enabling temporal skew [window=%d, total=%d]", temporal_window, temporal_total)); } // Catalog CatalogContext _catalog = null; try { _catalog = this.getCatalogContext(); } catch (Exception ex) { LOG.error("Failed to retrieve already compiled catalog", ex); System.exit(1); } this.catalog = _catalog.catalog; this.catalog_db = CatalogUtil.getDatabase(this.catalog); // --------------------------- // Fixed-Size Table Generators // --------------------------- this.registerGenerator(new RegionGenerator()); this.registerGenerator(new CategoryGenerator(new File(profile.data_directory.getAbsolutePath() + "/categories.txt"))); this.registerGenerator(new GlobalAttributeGroupGenerator()); this.registerGenerator(new GlobalAttributeValueGenerator()); // --------------------------- // Scaling-Size Table Generators // --------------------------- // USER TABLES this.registerGenerator(new UserGenerator()); this.registerGenerator(new UserAttributesGenerator()); this.registerGenerator(new UserItemGenerator()); this.registerGenerator(new UserWatchGenerator()); this.registerGenerator(new UserFeedbackGenerator()); // ITEM TABLES this.registerGenerator(new ItemGenerator()); this.registerGenerator(new ItemAttributeGenerator()); this.registerGenerator(new ItemBidGenerator()); this.registerGenerator(new ItemMaxBidGenerator()); this.registerGenerator(new ItemCommentGenerator()); this.registerGenerator(new ItemImageGenerator()); this.registerGenerator(new ItemPurchaseGenerator()); } private void registerGenerator(AbstractTableGenerator generator) { // Register this one as well as any sub-generators this.generators.put(generator.getTableName(), generator); for (AbstractTableGenerator sub_generator : generator.getSubTableGenerators()) { this.registerGenerator(sub_generator); this.sub_generators.add(sub_generator.getTableName()); } // FOR } protected AbstractTableGenerator getGenerator(String table_name) { return (this.generators.get(table_name)); } @Override public String[] getTransactionDisplayNames() { return new String[] {}; } /** * Call by the benchmark framework to load the table data */ @Override public void runLoop() { final EventObservableExceptionHandler handler = new EventObservableExceptionHandler(); final List<Thread> threads = new ArrayList<Thread>(); for (AbstractTableGenerator generator : this.generators.values()) { // if (isSubGenerator(generator)) continue; Thread t = new Thread(generator); t.setUncaughtExceptionHandler(handler); threads.add(t); // Make sure we call init first before starting any thread generator.init(); } // FOR assert(threads.size() > 0); handler.addObserver(new EventObserver<Pair<Thread,Throwable>>() { @Override public void update(EventObservable<Pair<Thread, Throwable>> o, Pair<Thread, Throwable> t) { for (Thread thread : threads) thread.interrupt(); } }); // Construct a new thread to load each table // Fire off the threads and wait for them to complete // If debug is set to true, then we'll execute them serially try { for (Thread t : threads) { t.start(); } // FOR for (Thread t : threads) { t.join(); } // FOR } catch (InterruptedException e) { LOG.fatal("Unexpected error", e); } finally { if (handler.hasError()) { throw new RuntimeException("Error while generating table data.", handler.getError()); } } profile.saveProfile(this); LOG.info("Finished generating data for all tables"); if (debug.val) LOG.debug("Table Sizes:\n" + this.getTableTupleCounts()); } /** * Load the tuples for the given table name * @param tableName */ protected void generateTableData(String tableName) { LOG.info("*** START " + tableName); final AbstractTableGenerator generator = this.generators.get(tableName); assert (generator != null); // Generate Data final VoltTable volt_table = generator.getVoltTable(); while (generator.hasMore()) { generator.generateBatch(); this.loadVoltTable(generator.getTableName(), volt_table); volt_table.clearRowData(); } // WHILE // Mark as finished generator.markAsFinished(); this.finished.add(tableName); LOG.info(String.format("*** FINISH %s - %d tuples - [%d / %d]", tableName, this.getTableTupleCount(tableName), this.finished.size(), this.generators.size())); if (debug.val) { LOG.debug("Remaining Tables: " + CollectionUtils.subtract(this.generators.keySet(), this.finished)); } } /********************************************************************************************** * AbstractTableGenerator **********************************************************************************************/ protected abstract class AbstractTableGenerator implements Runnable { private final String tableName; private final Table catalog_tbl; protected final VoltTable table; protected Long tableSize; protected Long batchSize; protected final CountDownLatch latch = new CountDownLatch(1); protected final List<String> dependencyTables = new ArrayList<String>(); /** * Some generators have children tables that we want to load tuples for each batch of this generator. * The queues we need to update every time we generate a new LoaderItemInfo */ protected final Set<SubTableGenerator<?>> sub_generators = new HashSet<SubTableGenerator<?>>(); protected final Object[] row; protected long count = 0; /** Any column with the name XX_SATTR## will automatically be filled with a random string */ protected final List<Column> random_str_cols = new ArrayList<Column>(); protected final Pattern random_str_regex = Pattern.compile("[\\w]+\\_SATTR[\\d]+", Pattern.CASE_INSENSITIVE); /** Any column with the name XX_IATTR## will automatically be filled with a random integer */ protected List<Column> random_int_cols = new ArrayList<Column>(); protected final Pattern random_int_regex = Pattern.compile("[\\w]+\\_IATTR[\\d]+", Pattern.CASE_INSENSITIVE); /** * Constructor * @param catalog_tbl */ public AbstractTableGenerator(String tableName, String...dependencies) { this.tableName = tableName; this.catalog_tbl = catalog_db.getTables().get(tableName); assert(catalog_tbl != null) : "Invalid table name '" + tableName + "'"; boolean fixed_size = AuctionMarkConstants.FIXED_TABLES.contains(catalog_tbl.getName()); boolean dynamic_size = AuctionMarkConstants.DYNAMIC_TABLES.contains(catalog_tbl.getName()); boolean data_file = AuctionMarkConstants.DATAFILE_TABLES.contains(catalog_tbl.getName()); // Generate a VoltTable instance we can use this.table = CatalogUtil.getVoltTable(catalog_tbl); this.row = new Object[this.table.getColumnCount()]; // Add the dependencies so that we know what we need to block on CollectionUtil.addAll(this.dependencyTables, dependencies); try { String field_name = "BATCHSIZE_" + catalog_tbl.getName(); Field field_handle = AuctionMarkConstants.class.getField(field_name); assert (field_handle != null); this.batchSize = (Long) field_handle.get(null); } catch (Exception ex) { throw new RuntimeException("Missing field needed for '" + tableName + "'", ex); } // Initialize dynamic parameters for tables that are not loaded from data files if (!data_file && !dynamic_size && tableName.equalsIgnoreCase(AuctionMarkConstants.TABLENAME_ITEM) == false) { try { String field_name = "TABLESIZE_" + catalog_tbl.getName(); Field field_handle = AuctionMarkConstants.class.getField(field_name); assert (field_handle != null); this.tableSize = (Long) field_handle.get(null); if (!fixed_size) { this.tableSize = Math.round(this.tableSize * profile.getScaleFactor()); } } catch (NoSuchFieldException ex) { if (debug.val) LOG.warn("No table size field for '" + tableName + "'", ex); } catch (Exception ex) { throw new RuntimeException("Missing field needed for '" + tableName + "'", ex); } } for (Column catalog_col : this.catalog_tbl.getColumns()) { if (random_str_regex.matcher(catalog_col.getName()).matches()) { assert(catalog_col.getType() == VoltType.STRING.getValue()) : catalog_col.fullName(); this.random_str_cols.add(catalog_col); if (trace.val) LOG.trace("Random String Column: " + catalog_col.fullName()); } else if (random_int_regex.matcher(catalog_col.getName()).matches()) { assert(catalog_col.getType() != VoltType.STRING.getValue()) : catalog_col.fullName(); this.random_int_cols.add(catalog_col); if (trace.val) LOG.trace("Random Integer Column: " + catalog_col.fullName()); } } // FOR if (debug.val) { if (this.random_str_cols.size() > 0) LOG.debug(String.format("%s Random String Columns: %s", tableName, CatalogUtil.debug(this.random_str_cols))); if (this.random_int_cols.size() > 0) LOG.debug(String.format("%s Random Integer Columns: %s", tableName, CatalogUtil.debug(this.random_int_cols))); } } /** * Initiate data that need dependencies */ public abstract void init(); /** * All sub-classes must implement this. This will enter new tuple data into the row */ protected abstract int populateRow(); public void run() { // First block on the CountDownLatches of all the tables that we depend on if (this.dependencyTables.size() > 0 && debug.val) LOG.debug(String.format("%s: Table generator is blocked waiting for %d other tables: %s", this.tableName, this.dependencyTables.size(), this.dependencyTables)); for (String dependency : this.dependencyTables) { AbstractTableGenerator gen = AuctionMarkLoader.this.generators.get(dependency); assert(gen != null) : "Missing table generator for '" + dependency + "'"; try { gen.latch.await(); } catch (InterruptedException ex) { throw new RuntimeException("Unexpected interruption for '" + this.tableName + "' waiting for '" + dependency + "'", ex); } } // FOR // Then invoke the loader generation method try { AuctionMarkLoader.this.generateTableData(this.tableName); } catch (Throwable ex) { throw new RuntimeException("Unexpected error while generating table data for '" + this.tableName + "'", ex); } } @SuppressWarnings("unchecked") public <T extends AbstractTableGenerator> T addSubTableGenerator(SubTableGenerator<?> sub_item) { this.sub_generators.add(sub_item); return ((T)this); } @SuppressWarnings("unchecked") public void updateSubTableGenerators(Object obj) { // Queue up this item for our multi-threaded sub-generators if (trace.val) LOG.trace(String.format("%s: Updating %d sub-generators with %s: %s", this.tableName, this.sub_generators.size(), obj, this.sub_generators)); for (@SuppressWarnings("rawtypes") SubTableGenerator sub_generator : this.sub_generators) { sub_generator.queue(obj); } // FOR } public boolean hasSubTableGenerators() { return (!this.sub_generators.isEmpty()); } public Collection<SubTableGenerator<?>> getSubTableGenerators() { return (this.sub_generators); } public Collection<String> getSubGeneratorTableNames() { List<String> names = new ArrayList<String>(); for (AbstractTableGenerator gen : this.sub_generators) { names.add(gen.catalog_tbl.getName()); } return (names); } protected int populateRandomColumns(Object row[]) { int cols = 0; // STRINGS for (Column catalog_col : this.random_str_cols) { int size = catalog_col.getSize(); row[catalog_col.getIndex()] = profile.rng.astring(profile.rng.nextInt(size - 1), size); cols++; } // FOR // INTEGER for (Column catalog_col : this.random_int_cols) { row[catalog_col.getIndex()] = profile.rng.number(0, 1<<30); cols++; } // FOR return (cols); } /** * Returns true if this generator has more tuples that it wants to add * @return */ public synchronized boolean hasMore() { return (this.count < this.tableSize); } /** * Return the table's catalog object for this generator * @return */ public Table getTableCatalog() { return (this.catalog_tbl); } /** * Return the VoltTable handle * @return */ public VoltTable getVoltTable() { return this.table; } /** * Returns the number of tuples that will be loaded into this table * @return */ public Long getTableSize() { return this.tableSize; } /** * Returns the number of tuples per batch that this generator will want loaded * @return */ public Long getBatchSize() { return this.batchSize; } /** * Returns the name of the table this this generates * @return */ public String getTableName() { return this.tableName; } /** * Returns the total number of tuples generated thusfar * @return */ public synchronized long getCount() { return this.count; } /** * When called, the generator will populate a new row record and append it to the underlying VoltTable */ public synchronized void addRow() { int cols = this.populateRow(); // RANDOM COLS cols += populateRandomColumns(this.row); assert(cols == this.table.getColumnCount()) : String.format("Invalid number of columns for %s [expected=%d, actual=%d]", this.tableName, this.table.getColumnCount(), cols); // Convert all CompositeIds into their long encodings for (int i = 0; i < cols; i++) { if (this.row[i] != null && this.row[i] instanceof CompositeId) { this.row[i] = ((CompositeId)this.row[i]).encode(); } } // FOR this.count++; this.table.addRow(this.row); } /** * */ public void generateBatch() { if (trace.val) LOG.trace(String.format("%s: Generating new batch", this.getTableName())); long batch_count = 0; while (this.hasMore() && this.table.getRowCount() < this.batchSize) { this.addRow(); batch_count++; } // WHILE if (debug.val) LOG.debug(String.format("%s: Finished generating new batch of %d tuples", this.getTableName(), batch_count)); } public void markAsFinished() { this.latch.countDown(); for (SubTableGenerator<?> sub_generator : this.sub_generators) { sub_generator.stopWhenEmpty(); } // FOR } public boolean isFinish(){ return (this.latch.getCount() == 0); } public List<String> getDependencies() { return this.dependencyTables; } @Override public String toString() { return String.format("Generator[%s]", this.tableName); } } // END CLASS /********************************************************************************************** * SubUserTableGenerator * This is for tables that are based off of the USER table **********************************************************************************************/ protected abstract class SubTableGenerator<T> extends AbstractTableGenerator { private final LinkedBlockingDeque<T> queue = new LinkedBlockingDeque<T>(); private T current; private short currentCounter; private boolean stop = false; private final String sourceTableName; public SubTableGenerator(String tableName, String sourceTableName, String...dependencies) { super(tableName, dependencies); this.sourceTableName = sourceTableName; } protected abstract short getElementCounter(T t); protected abstract int populateRow(T t, short remaining); public void queue(T t) { assert(this.queue.contains(t) == false) : "Trying to queue duplicate element for '" + this.getTableName() + "'"; this.queue.add(t); } public void stopWhenEmpty() { this.stop = true; } @Override public void init() { // Get the AbstractTableGenerator that will feed into this generator AbstractTableGenerator parent_gen = AuctionMarkLoader.this.generators.get(this.sourceTableName); assert(parent_gen != null) : "Unexpected source TableGenerator '" + this.sourceTableName + "'"; parent_gen.addSubTableGenerator(this); this.current = null; this.currentCounter = 0; } @Override public final boolean hasMore() { return (this.getNext() != null); } @Override protected final int populateRow() { T t = this.getNext(); assert(t != null); this.currentCounter--; return (this.populateRow(t, this.currentCounter)); } private final T getNext() { T last = this.current; if (this.current == null || this.currentCounter == 0) { while (this.currentCounter == 0) { try { this.current = this.queue.poll(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { return (null); } // Check whether we should stop if (this.current == null) { if (this.stop) break; continue; } this.currentCounter = this.getElementCounter(this.current); } // WHILE } if (last != this.current) { if (last != null) this.finishElementCallback(last); if (this.current != null) this.newElementCallback(this.current); } return this.current; } protected void finishElementCallback(T t) { // Nothing... } protected void newElementCallback(T t) { // Nothing... } } // END CLASS /********************************************************************************************** * REGION Generator **********************************************************************************************/ protected class RegionGenerator extends AbstractTableGenerator { public RegionGenerator() { super(AuctionMarkConstants.TABLENAME_REGION); } @Override public void init() { // Nothing to do } @Override protected int populateRow() { int col = 0; // R_ID this.row[col++] = new Integer((int) this.count); // R_NAME this.row[col++] = profile.rng.astring(6, 32); return (col); } } // END CLASS /********************************************************************************************** * CATEGORY Generator **********************************************************************************************/ protected class CategoryGenerator extends AbstractTableGenerator { private final File data_file; private final Map<String, Category> categoryMap; private final LinkedList<Category> categories = new LinkedList<Category>(); public CategoryGenerator(File data_file) { super(AuctionMarkConstants.TABLENAME_CATEGORY); this.data_file = data_file; assert(this.data_file.exists()) : "The data file for the category generator does not exist: " + this.data_file; this.categoryMap = (new AuctionMarkCategoryParser(data_file)).getCategoryMap(); this.tableSize = (long)this.categoryMap.size(); } @Override public void init() { for (Category category : this.categoryMap.values()) { if (category.isLeaf()) { profile.item_category_histogram.put((long)category.getCategoryID(), category.getItemCount()); } this.categories.add(category); } // FOR } @Override protected int populateRow() { int col = 0; Category category = this.categories.poll(); assert(category != null); // C_ID this.row[col++] = category.getCategoryID(); // C_NAME this.row[col++] = category.getName(); // C_PARENT_ID this.row[col++] = category.getParentCategoryID(); return (col); } } // END CLASS /********************************************************************************************** * GLOBAL_ATTRIBUTE_GROUP Generator **********************************************************************************************/ protected class GlobalAttributeGroupGenerator extends AbstractTableGenerator { private long num_categories = 0l; private final ObjectHistogram<Integer> category_groups = new ObjectHistogram<Integer>(); private final LinkedList<GlobalAttributeGroupId> group_ids = new LinkedList<GlobalAttributeGroupId>(); public GlobalAttributeGroupGenerator() { super(AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_GROUP, AuctionMarkConstants.TABLENAME_CATEGORY); } @Override public void init() { // Grab the number of CATEGORY items that we have inserted this.num_categories = getTableTupleCount(AuctionMarkConstants.TABLENAME_CATEGORY); for (int i = 0; i < this.tableSize; i++) { int category_id = profile.rng.number(0, (int)this.num_categories); this.category_groups.put(category_id); int id = this.category_groups.get(category_id).intValue(); int count = (int)profile.rng.number(1, AuctionMarkConstants.TABLESIZE_GLOBAL_ATTRIBUTE_VALUE_PER_GROUP); GlobalAttributeGroupId gag_id = new GlobalAttributeGroupId(category_id, id, count); assert(profile.gag_ids.contains(gag_id) == false); profile.gag_ids.add(gag_id); this.group_ids.add(gag_id); } // FOR } @Override protected int populateRow() { int col = 0; GlobalAttributeGroupId gag_id = this.group_ids.poll(); assert(gag_id != null); // GAG_ID this.row[col++] = gag_id.encode(); // GAG_C_ID this.row[col++] = gag_id.getCategoryId(); // GAG_NAME this.row[col++] = profile.rng.astring(6, 32); return (col); } } // END CLASS /********************************************************************************************** * GLOBAL_ATTRIBUTE_VALUE Generator **********************************************************************************************/ protected class GlobalAttributeValueGenerator extends AbstractTableGenerator { private ObjectHistogram<GlobalAttributeGroupId> gag_counters = new ObjectHistogram<GlobalAttributeGroupId>(true); private Iterator<GlobalAttributeGroupId> gag_iterator; private GlobalAttributeGroupId gag_current; private int gav_counter = -1; public GlobalAttributeValueGenerator() { super(AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_VALUE, AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_GROUP); } @Override public void init() { this.tableSize = 0l; for (GlobalAttributeGroupId gag_id : profile.gag_ids) { this.gag_counters.set(gag_id, 0); this.tableSize += gag_id.getCount(); } // FOR this.gag_iterator = profile.gag_ids.iterator(); } @Override protected int populateRow() { int col = 0; if (this.gav_counter == -1 || ++this.gav_counter == this.gag_current.getCount()) { this.gag_current = this.gag_iterator.next(); assert(this.gag_current != null); this.gav_counter = 0; } GlobalAttributeValueId gav_id = new GlobalAttributeValueId(this.gag_current.encode(), this.gav_counter); // GAV_ID this.row[col++] = gav_id.encode(); // GAV_GAG_ID this.row[col++] = this.gag_current.encode(); // GAV_NAME this.row[col++] = profile.rng.astring(6, 32); return (col); } } // END CLASS /********************************************************************************************** * USER Generator **********************************************************************************************/ protected class UserGenerator extends AbstractTableGenerator { private final Zipf randomBalance; private final Flat randomRegion; private final Zipf randomRating; private UserIdGenerator idGenerator; public UserGenerator() { super(AuctionMarkConstants.TABLENAME_USER, AuctionMarkConstants.TABLENAME_REGION); this.randomRegion = new Flat(profile.rng, 0, (int) AuctionMarkConstants.TABLESIZE_REGION); this.randomRating = new Zipf(profile.rng, AuctionMarkConstants.USER_MIN_RATING, AuctionMarkConstants.USER_MAX_RATING, 1.0001); this.randomBalance = new Zipf(profile.rng, AuctionMarkConstants.USER_MIN_BALANCE, AuctionMarkConstants.USER_MAX_BALANCE, 1.001); } @Override public void init() { // Populate the profile's users per item count histogram so that we know how many // items that each user should have. This will then be used to calculate the // the user ids by placing them into numeric ranges Zipf randomNumItems = new Zipf(profile.rng, AuctionMarkConstants.ITEM_MIN_ITEMS_PER_SELLER, Math.round(AuctionMarkConstants.ITEM_MAX_ITEMS_PER_SELLER * profile.getScaleFactor()), 1.001); for (long i = 0; i < this.tableSize; i++) { long num_items = randomNumItems.nextInt(); profile.users_per_item_count.put(num_items); } // FOR if (trace.val) LOG.trace("Users Per Item Count:\n" + profile.users_per_item_count); this.idGenerator = new UserIdGenerator(profile.users_per_item_count, getNumClients()); assert(this.idGenerator.hasNext()); } @Override public synchronized boolean hasMore() { return this.idGenerator.hasNext(); } @Override protected int populateRow() { int col = 0; UserId u_id = this.idGenerator.next(); // U_ID this.row[col++] = u_id; // U_RATING this.row[col++] = this.randomRating.nextInt(); // U_BALANCE this.row[col++] = (this.randomBalance.nextInt()) / 10.0; // U_COMMENTS this.row[col++] = 0; // U_R_ID this.row[col++] = this.randomRegion.nextInt(); // U_CREATED this.row[col++] = VoltTypeUtil.getRandomValue(VoltType.TIMESTAMP); // U_UPDATED this.row[col++] = VoltTypeUtil.getRandomValue(VoltType.TIMESTAMP); this.updateSubTableGenerators(u_id); return (col); } } /********************************************************************************************** * USER_ATTRIBUTES Generator **********************************************************************************************/ protected class UserAttributesGenerator extends SubTableGenerator<UserId> { private final Zipf randomNumUserAttributes; public UserAttributesGenerator() { super(AuctionMarkConstants.TABLENAME_USER_ATTRIBUTES, AuctionMarkConstants.TABLENAME_USER); this.randomNumUserAttributes = new Zipf(profile.rng, AuctionMarkConstants.USER_MIN_ATTRIBUTES, AuctionMarkConstants.USER_MAX_ATTRIBUTES, 1.001); } @Override protected short getElementCounter(UserId user_id) { return (short)(randomNumUserAttributes.nextInt()); } @Override protected int populateRow(UserId user_id, short remaining) { int col = 0; // UA_ID this.row[col++] = this.count; // UA_U_ID this.row[col++] = user_id; // UA_NAME this.row[col++] = profile.rng.astring(5, 32); // UA_VALUE this.row[col++] = profile.rng.astring(5, 32); // U_CREATED this.row[col++] = VoltTypeUtil.getRandomValue(VoltType.TIMESTAMP); return (col); } } // END CLASS /********************************************************************************************** * ITEM Generator **********************************************************************************************/ protected class ItemGenerator extends SubTableGenerator<UserId> { /** * BidDurationDay -> Pair<NumberOfBids, NumberOfWatches> */ private final Map<Long, Pair<Zipf, Zipf>> item_bid_watch_zipfs = new HashMap<Long, Pair<Zipf,Zipf>>(); /** End date distribution */ private final ObjectHistogram<String> endDateHistogram = new ObjectHistogram<String>(); private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH"); public ItemGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM, AuctionMarkConstants.TABLENAME_USER, AuctionMarkConstants.TABLENAME_CATEGORY); } @Override protected short getElementCounter(UserId user_id) { return (short)(user_id.getItemCount()); } @Override public void init() { super.init(); this.tableSize = 0l; for (Long size : profile.users_per_item_count.values()) { this.tableSize += size.intValue() * profile.users_per_item_count.get(size); } // FOR } @Override protected int populateRow(UserId seller_id, short remaining) { int col = 0; ItemId itemId = new ItemId(seller_id, remaining); TimestampType endDate = this.getRandomEndTimestamp(); TimestampType startDate = this.getRandomStartTimestamp(endDate); //LOG.info("endDate = " + endDate + " : startDate = " + startDate); long bidDurationDay = ((endDate.getTime() - startDate.getTime()) / AuctionMarkConstants.MICROSECONDS_IN_A_DAY); if (this.item_bid_watch_zipfs.containsKey(bidDurationDay) == false) { Zipf randomNumBids = new Zipf(profile.rng, AuctionMarkConstants.ITEM_MIN_BIDS_PER_DAY * (int)bidDurationDay, AuctionMarkConstants.ITEM_MAX_BIDS_PER_DAY * (int)bidDurationDay, 1.001); Zipf randomNumWatches = new Zipf(profile.rng, AuctionMarkConstants.ITEM_MIN_WATCHES_PER_DAY * (int)bidDurationDay, (int)Math.ceil(AuctionMarkConstants.ITEM_MAX_WATCHES_PER_DAY * (int)bidDurationDay * profile.getScaleFactor()), 1.001); this.item_bid_watch_zipfs.put(bidDurationDay, Pair.of(randomNumBids, randomNumWatches)); } Pair<Zipf, Zipf> p = this.item_bid_watch_zipfs.get(bidDurationDay); assert(p != null); // Calculate the number of bids and watches for this item short numBids = (short)p.getFirst().nextInt(); short numWatches = (short)p.getSecond().nextInt(); // Create the ItemInfo object that we will use to cache the local data // for this item. This will get garbage collected once all the derivative // tables are done with it. LoaderItemInfo itemInfo = new LoaderItemInfo(itemId, endDate, numBids); itemInfo.sellerId = seller_id; itemInfo.startDate = endDate; itemInfo.initialPrice = profile.randomInitialPrice.nextInt(); assert(itemInfo.initialPrice > 0) : "Invalid initial price for " + itemId; itemInfo.numImages = (short) profile.randomNumImages.nextInt(); itemInfo.numAttributes = (short) profile.randomNumAttributes.nextInt(); itemInfo.numBids = numBids; itemInfo.numWatches = numWatches; // The auction for this item has already closed if (itemInfo.endDate.getTime() <= profile.getBenchmarkStartTime().getTime()) { if (itemInfo.numBids > 0) { // Somebody won a bid and bought the item itemInfo.lastBidderId = profile.getRandomBuyerId(itemInfo.sellerId); //System.out.println("@@@ z last_bidder_id = " + itemInfo.last_bidder_id); itemInfo.purchaseDate = this.getRandomPurchaseTimestamp(itemInfo.endDate); itemInfo.numComments = (short) profile.randomNumComments.nextInt(); } itemInfo.status = ItemStatus.CLOSED; } // Item is still available else if (itemInfo.numBids > 0) { itemInfo.lastBidderId = profile.getRandomBuyerId(itemInfo.sellerId); } profile.addItemToProperQueue(itemInfo, true); // I_ID this.row[col++] = itemInfo.itemId; // I_U_ID this.row[col++] = itemInfo.sellerId; // I_C_ID this.row[col++] = profile.getRandomCategoryId(); // I_NAME this.row[col++] = profile.rng.astring(6, 32); // I_DESCRIPTION this.row[col++] = profile.rng.astring(50, 255); // I_USER_ATTRIBUTES this.row[col++] = profile.rng.astring(20, 255); // I_INITIAL_PRICE this.row[col++] = itemInfo.initialPrice; // I_CURRENT_PRICE if (itemInfo.numBids > 0) { itemInfo.currentPrice = itemInfo.initialPrice + (itemInfo.numBids * itemInfo.initialPrice * AuctionMarkConstants.ITEM_BID_PERCENT_STEP); this.row[col++] = itemInfo.currentPrice; } else { this.row[col++] = itemInfo.initialPrice; } // I_NUM_BIDS this.row[col++] = itemInfo.numBids; // I_NUM_IMAGES this.row[col++] = itemInfo.numImages; // I_NUM_GLOBAL_ATTRS this.row[col++] = itemInfo.numAttributes; // I_START_DATE this.row[col++] = itemInfo.startDate; // I_END_DATE this.row[col++] = itemInfo.endDate; // I_STATUS this.row[col++] = itemInfo.status.ordinal(); // I_UPDATED this.row[col++] = itemInfo.startDate; if (debug.val) this.endDateHistogram.put(sdf.format(itemInfo.endDate.asApproximateJavaDate())); this.updateSubTableGenerators(itemInfo); return (col); } @Override public void markAsFinished() { super.markAsFinished(); if (debug.val) LOG.debug("Item End Date Distribution:\n" + this.endDateHistogram); } private TimestampType getRandomStartTimestamp(TimestampType endDate) { long duration = ((long)profile.randomDuration.nextInt()) * AuctionMarkConstants.MICROSECONDS_IN_A_DAY; long lStartTimestamp = endDate.getTime() - duration; TimestampType startTimestamp = new TimestampType(lStartTimestamp); return startTimestamp; } private TimestampType getRandomEndTimestamp() { long timeDiff = profile.randomTimeDiff.nextLong(); TimestampType time = new TimestampType(profile.getBenchmarkStartTime().getTime() + (timeDiff * 1000000)); // LOG.info(timeDiff + " => " + sdf.format(time.asApproximateJavaDate())); return time; } private TimestampType getRandomPurchaseTimestamp(TimestampType endDate) { long duration = profile.randomPurchaseDuration.nextInt(); return new TimestampType(endDate.getTime() + duration * AuctionMarkConstants.MICROSECONDS_IN_A_DAY); } } /********************************************************************************************** * ITEM_IMAGE Generator **********************************************************************************************/ protected class ItemImageGenerator extends SubTableGenerator<LoaderItemInfo> { public ItemImageGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM_IMAGE, AuctionMarkConstants.TABLENAME_ITEM); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return itemInfo.numImages; } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; // II_ID this.row[col++] = this.count; // II_I_ID this.row[col++] = itemInfo.itemId; // II_U_ID this.row[col++] = itemInfo.sellerId; return (col); } } // END CLASS /********************************************************************************************** * ITEM_ATTRIBUTE Generator **********************************************************************************************/ protected class ItemAttributeGenerator extends SubTableGenerator<LoaderItemInfo> { public ItemAttributeGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM_ATTRIBUTE, AuctionMarkConstants.TABLENAME_ITEM, AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_GROUP, AuctionMarkConstants.TABLENAME_GLOBAL_ATTRIBUTE_VALUE); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return itemInfo.numAttributes; } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; GlobalAttributeValueId gav_id = profile.getRandomGlobalAttributeValue(); assert(gav_id != null); // IA_ID this.row[col++] = this.count; // IA_I_ID this.row[col++] = itemInfo.itemId; // IA_U_ID this.row[col++] = itemInfo.sellerId; // IA_GAV_ID this.row[col++] = gav_id.encode(); // IA_GAG_ID this.row[col++] = gav_id.getGlobalAttributeGroup().encode(); return (col); } } // END CLASS /********************************************************************************************** * ITEM_COMMENT Generator **********************************************************************************************/ protected class ItemCommentGenerator extends SubTableGenerator<LoaderItemInfo> { public ItemCommentGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM_COMMENT, AuctionMarkConstants.TABLENAME_ITEM); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return (itemInfo.purchaseDate != null ? itemInfo.numComments : 0); } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; // IC_ID this.row[col++] = new Integer((int) this.count); // IC_I_ID this.row[col++] = itemInfo.itemId; // IC_U_ID this.row[col++] = itemInfo.sellerId; // IC_BUYER_ID this.row[col++] = itemInfo.lastBidderId; // IC_QUESTION this.row[col++] = profile.rng.astring(10, 128); // IC_RESPONSE this.row[col++] = profile.rng.astring(10, 128); // IC_CREATED this.row[col++] = this.getRandomCommentDate(itemInfo.startDate, itemInfo.endDate); // IC_UPDATED this.row[col++] = this.getRandomCommentDate(itemInfo.startDate, itemInfo.endDate); return (col); } private TimestampType getRandomCommentDate(TimestampType startDate, TimestampType endDate) { int start = Math.round(startDate.getTime() / 1000000); int end = Math.round(endDate.getTime() / 1000000); return new TimestampType((profile.rng.number(start, end)) * 1000 * 1000); } } /********************************************************************************************** * ITEM_BID Generator **********************************************************************************************/ protected class ItemBidGenerator extends SubTableGenerator<LoaderItemInfo> { private LoaderItemInfo.Bid bid = null; private float currentBidPriceAdvanceStep; private long currentCreateDateAdvanceStep; private boolean new_item; public ItemBidGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM_BID, AuctionMarkConstants.TABLENAME_ITEM); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return ((short)itemInfo.numBids); } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; assert(itemInfo.numBids > 0); UserId bidderId = null; // Figure out the UserId for the person bidding on this item now if (this.new_item) { // If this is a new item and there is more than one bid, then // we'll choose the bidder's UserId at random. // If there is only one bid, then it will have to be the last bidder bidderId = (itemInfo.numBids == 1 ? itemInfo.lastBidderId : profile.getRandomBuyerId(itemInfo.sellerId)); TimestampType endDate; if (itemInfo.status == ItemStatus.OPEN) { endDate = profile.getBenchmarkStartTime(); } else { endDate = itemInfo.endDate; } this.currentCreateDateAdvanceStep = (endDate.getTime() - itemInfo.startDate.getTime()) / (remaining + 1); this.currentBidPriceAdvanceStep = itemInfo.initialPrice * AuctionMarkConstants.ITEM_BID_PERCENT_STEP; } // The last bid must always be the item's lastBidderId else if (remaining == 0) { bidderId = itemInfo.lastBidderId; } // The first bid for a two-bid item must always be different than the lastBidderId else if (itemInfo.numBids == 2) { assert(remaining == 1); bidderId = profile.getRandomBuyerId(itemInfo.lastBidderId, itemInfo.sellerId); } // Since there are multiple bids, we want randomly select one based on the previous bidders // We will get the histogram of bidders so that we are more likely to select // an existing bidder rather than a completely random one else { assert(this.bid != null); ObjectHistogram<UserId> bidderHistogram = itemInfo.getBidderHistogram(); bidderId = profile.getRandomBuyerId(bidderHistogram, this.bid.bidderId, itemInfo.sellerId); } assert(bidderId != null); float last_bid = (this.new_item ? itemInfo.initialPrice : this.bid.maxBid); this.bid = itemInfo.getNextBid(this.count, bidderId); this.bid.createDate = new TimestampType(itemInfo.startDate.getTime() + this.currentCreateDateAdvanceStep); this.bid.updateDate = this.bid.createDate; if (remaining == 0) { this.bid.maxBid = itemInfo.currentPrice; if (itemInfo.purchaseDate != null) { assert(itemInfo.getBidCount() == itemInfo.numBids) : String.format("%d != %d\n%s", itemInfo.getBidCount(), itemInfo.numBids, itemInfo); } } else { this.bid.maxBid = last_bid + this.currentBidPriceAdvanceStep; } // IB_ID this.row[col++] = new Long(this.bid.id); // IB_I_ID this.row[col++] = itemInfo.itemId; // IB_U_ID this.row[col++] = itemInfo.sellerId; // IB_BUYER_ID this.row[col++] = this.bid.bidderId; // IB_BID this.row[col++] = this.bid.maxBid - (remaining > 0 ? (this.currentBidPriceAdvanceStep/2.0f) : 0); // IB_MAX_BID this.row[col++] = this.bid.maxBid; // IB_CREATED this.row[col++] = this.bid.createDate; // IB_UPDATED this.row[col++] = this.bid.updateDate; if (remaining == 0) this.updateSubTableGenerators(itemInfo); return (col); } @Override protected void newElementCallback(LoaderItemInfo itemInfo) { this.new_item = true; this.bid = null; } } /********************************************************************************************** * ITEM_MAX_BID Generator **********************************************************************************************/ protected class ItemMaxBidGenerator extends SubTableGenerator<LoaderItemInfo> { public ItemMaxBidGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM_MAX_BID, AuctionMarkConstants.TABLENAME_ITEM_BID); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return (short)(itemInfo.getBidCount() > 0 ? 1 : 0); } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; LoaderItemInfo.Bid bid = itemInfo.getLastBid(); assert(bid != null) : "No bids?\n" + itemInfo; // IMB_I_ID this.row[col++] = itemInfo.itemId; // IMB_U_ID this.row[col++] = itemInfo.sellerId; // IMB_IB_ID this.row[col++] = bid.id; // IMB_IB_I_ID this.row[col++] = itemInfo.itemId; // IMB_IB_U_ID this.row[col++] = itemInfo.sellerId; // IMB_CREATED this.row[col++] = bid.createDate; // IMB_UPDATED this.row[col++] = bid.updateDate; if (remaining == 0) this.updateSubTableGenerators(itemInfo); return (col); } } /********************************************************************************************** * ITEM_PURCHASE Generator **********************************************************************************************/ protected class ItemPurchaseGenerator extends SubTableGenerator<LoaderItemInfo> { public ItemPurchaseGenerator() { super(AuctionMarkConstants.TABLENAME_ITEM_PURCHASE, AuctionMarkConstants.TABLENAME_ITEM_BID); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return (short)(itemInfo.getBidCount() > 0 && itemInfo.purchaseDate != null ? 1 : 0); } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; LoaderItemInfo.Bid bid = itemInfo.getLastBid(); assert(bid != null) : itemInfo; // IP_ID this.row[col++] = this.count; // IP_IB_ID this.row[col++] = bid.id; // IP_IB_I_ID this.row[col++] = itemInfo.itemId; // IP_IB_U_ID this.row[col++] = itemInfo.sellerId; // IP_DATE this.row[col++] = itemInfo.purchaseDate; if (profile.rng.number(1, 100) <= AuctionMarkConstants.PROB_PURCHASE_BUYER_LEAVES_FEEDBACK) { bid.buyer_feedback = true; } if (profile.rng.number(1, 100) <= AuctionMarkConstants.PROB_PURCHASE_SELLER_LEAVES_FEEDBACK) { bid.seller_feedback = true; } if (remaining == 0) this.updateSubTableGenerators(bid); return (col); } } // END CLASS /********************************************************************************************** * USER_FEEDBACK Generator **********************************************************************************************/ protected class UserFeedbackGenerator extends SubTableGenerator<LoaderItemInfo.Bid> { public UserFeedbackGenerator() { super(AuctionMarkConstants.TABLENAME_USER_FEEDBACK, AuctionMarkConstants.TABLENAME_ITEM_PURCHASE); } @Override protected short getElementCounter(LoaderItemInfo.Bid bid) { return (short)((bid.buyer_feedback ? 1 : 0) + (bid.seller_feedback ? 1 : 0)); } @Override protected int populateRow(LoaderItemInfo.Bid bid, short remaining) { int col = 0; boolean is_buyer = false; if (bid.buyer_feedback && bid.seller_feedback == false) { is_buyer = true; } else if (bid.seller_feedback && bid.buyer_feedback == false) { is_buyer = false; } else if (remaining > 1) { is_buyer = true; } LoaderItemInfo itemInfo = bid.getLoaderItemInfo(); // UF_ID this.row[col++] = this.count; // UF_I_ID this.row[col++] = itemInfo.itemId; // UF_I_U_ID this.row[col++] = itemInfo.sellerId; // UF_TO_ID this.row[col++] = (is_buyer ? bid.bidderId : itemInfo.sellerId); // UF_FROM_ID this.row[col++] = (is_buyer ? itemInfo.sellerId : bid.bidderId); // UF_RATING this.row[col++] = 1; // TODO // UF_DATE this.row[col++] = profile.getBenchmarkStartTime(); // Does this matter? return (col); } } /********************************************************************************************** * USER_ITEM Generator **********************************************************************************************/ protected class UserItemGenerator extends SubTableGenerator<LoaderItemInfo> { public UserItemGenerator() { super(AuctionMarkConstants.TABLENAME_USER_ITEM, AuctionMarkConstants.TABLENAME_ITEM_BID); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return (short)(itemInfo.getBidCount() > 0 && itemInfo.purchaseDate != null ? 1 : 0); } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; LoaderItemInfo.Bid bid = itemInfo.getLastBid(); assert(bid != null) : itemInfo; // UI_U_ID this.row[col++] = bid.bidderId; // UI_I_ID this.row[col++] = itemInfo.itemId; // UI_I_U_ID this.row[col++] = itemInfo.sellerId; // UI_IP_ID this.row[col++] = null; // UI_IP_IB_ID this.row[col++] = null; // UI_IP_IB_I_ID this.row[col++] = null; // UI_IP_IB_U_ID this.row[col++] = null; // UI_CREATED this.row[col++] = itemInfo.endDate; return (col); } } // END CLASS /********************************************************************************************** * USER_WATCH Generator **********************************************************************************************/ protected class UserWatchGenerator extends SubTableGenerator<LoaderItemInfo> { public UserWatchGenerator() { super(AuctionMarkConstants.TABLENAME_USER_WATCH, AuctionMarkConstants.TABLENAME_ITEM_BID); } @Override public short getElementCounter(LoaderItemInfo itemInfo) { return (itemInfo.numWatches); } @Override protected int populateRow(LoaderItemInfo itemInfo, short remaining) { int col = 0; // Make it more likely that a user that has bid on an item is watching it ObjectHistogram<UserId> bidderHistogram = itemInfo.getBidderHistogram(); UserId buyerId = null; try { profile.getRandomBuyerId(bidderHistogram, itemInfo.sellerId); } catch (NullPointerException ex) { LOG.error("Busted Bidder Histogram:\n" + bidderHistogram); throw ex; } // UW_U_ID this.row[col++] = buyerId; // UW_I_ID this.row[col++] = itemInfo.itemId; // UW_I_U_ID this.row[col++] = itemInfo.sellerId; // UW_CREATED this.row[col++] = this.getRandomCommentDate(itemInfo.startDate, itemInfo.endDate); return (col); } private TimestampType getRandomCommentDate(TimestampType startDate, TimestampType endDate) { int start = Math.round(startDate.getTime() / 1000000); int end = Math.round(endDate.getTime() / 1000000); long offset = profile.rng.number(start, end); return new TimestampType(offset * 1000000l); } } // END CLASS } // END CLASS