/***************************************************************************
* Copyright (C) 2010 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Written by: *
* Andy Pavlo (pavlo@cs.brown.edu) *
* http://www.cs.brown.edu/~pavlo/ *
* *
* 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.locality;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONString;
import org.json.JSONStringer;
import org.voltdb.utils.Pair;
import edu.brown.rand.AbstractRandomGenerator;
import edu.brown.rand.RandomDistribution;
import edu.brown.statistics.ObjectHistogram;
import edu.brown.utils.FileUtil;
/**
* @author pavlo
*/
public class RandomGenerator extends AbstractRandomGenerator implements JSONString {
public enum AffinityRecordMembers {
MINIMUM, MAXIMUM, MAP, SIGMA,
}
/**
* Table Name -> Affinity Record
*/
protected final SortedMap<Pair<String, String>, AffinityRecord> affinity_map = new TreeMap<Pair<String, String>, AffinityRecord>();
protected final transient SortedMap<String, Set<Pair<String, String>>> table_pair_xref = new TreeMap<String, Set<Pair<String, String>>>();
/**
*
*/
protected class AffinityRecord {
private final SortedMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
private final transient SortedMap<Integer, RandomDistribution.Zipf> distributions = new TreeMap<Integer, RandomDistribution.Zipf>();
private double zipf_sigma = 1.01d;
private int minimum;
private int maximum;
private transient int range;
private final transient SortedMap<Integer, ObjectHistogram> histograms = new TreeMap<Integer, ObjectHistogram>();
public AffinityRecord() {
// For serialization...
}
public AffinityRecord(int minimum, int maximum) {
this.minimum = minimum;
this.maximum = maximum;
this.range = this.maximum - this.minimum;
}
public double getZipfSigma() {
return (this.zipf_sigma);
}
public void setZipfSigma(double zipf_sigma) {
// We have to regenerate all the random number generators
if (this.zipf_sigma != zipf_sigma) {
this.zipf_sigma = zipf_sigma;
for (int source : this.map.keySet()) {
int target = this.map.get(source);
this.distributions.put(source, new RandomDistribution.Zipf(RandomGenerator.this, target, target + this.range, this.zipf_sigma));
} // FOR
}
}
public int getMinimum() {
return (this.minimum);
}
public int getMaximum() {
return (this.maximum);
}
public Set<Integer> getSources() {
return (this.map.keySet());
}
public int getTarget(int source) {
return (this.map.get(source));
}
public RandomDistribution.Zipf getDistribution(int source) {
return (this.distributions.get(source));
}
public ObjectHistogram getHistogram(int source) {
return (this.histograms.get(source));
}
public void add(int source, int target) {
this.map.put(source, target);
this.distributions.put(source, new RandomDistribution.Zipf(RandomGenerator.this, target, target + this.range, this.zipf_sigma));
this.histograms.put(source, new ObjectHistogram());
}
public void toJSONString(JSONStringer stringer) throws JSONException {
stringer.key(AffinityRecordMembers.SIGMA.name()).value(this.zipf_sigma);
stringer.key(AffinityRecordMembers.MINIMUM.name()).value(this.minimum);
stringer.key(AffinityRecordMembers.MAXIMUM.name()).value(this.maximum);
stringer.key(AffinityRecordMembers.MAP.name()).object();
for (Integer source_id : this.map.keySet()) {
stringer.key(source_id.toString()).value(this.map.get(source_id));
} // FOR
stringer.endObject();
}
public void fromJSONObject(JSONObject object) throws JSONException {
this.zipf_sigma = object.getDouble(AffinityRecordMembers.SIGMA.name());
this.minimum = object.getInt(AffinityRecordMembers.MINIMUM.name());
this.maximum = object.getInt(AffinityRecordMembers.MAXIMUM.name());
this.range = this.maximum - this.minimum;
JSONObject jsonMap = object.getJSONObject(AffinityRecordMembers.MAP.name());
Iterator<String> i = jsonMap.keys();
while (i.hasNext()) {
String key = i.next();
Integer source = Integer.parseInt(key);
Integer target = jsonMap.getInt(key);
assert (source != null);
assert (target != null);
this.add(source, target);
} // WHILE
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
for (int source : this.map.keySet()) {
int target = this.map.get(source);
buffer.append(source).append(" => ").append(target).append("\n");
buffer.append(this.histograms.get(source)).append("\n-------------\n");
} // FOR
return (buffer.toString());
}
} // END CLASS
/**
* Seeds the random number generator using the default Random() constructor.
*/
public RandomGenerator() {
super(0);
}
/**
* Seeds the random number generator with seed.
*
* @param seed
*/
public RandomGenerator(Integer seed) {
super(seed);
}
/**
* Set the Zipfian distribution sigma factor
*
* @param zipf_sigma
*/
public void setZipfSigma(String table, double zipf_sigma) {
assert (this.affinity_map.containsKey(table));
this.affinity_map.get(table).setZipfSigma(zipf_sigma);
}
private void addAffinityRecord(Pair<String, String> pair, AffinityRecord record) {
this.affinity_map.put(pair, record);
if (!this.table_pair_xref.containsKey(pair.getFirst())) {
this.table_pair_xref.put(pair.getFirst(), new HashSet<Pair<String, String>>());
}
this.table_pair_xref.get(pair.getFirst()).add(pair);
}
/**
* Sets the affinity preference of a source value to a target
*
* @param source_id
* @param target_id
*/
public void addAffinity(String source_table, int source_id, String target_table, int target_id, int minimum, int maximum) {
assert (minimum <= target_id) : "Min check failed (" + minimum + " <= " + target_id + ")";
assert (maximum >= target_id);
Pair<String, String> pair = Pair.of(source_table, target_table);
AffinityRecord record = this.affinity_map.get(pair);
if (record == null) {
record = new AffinityRecord(minimum, maximum);
} else {
assert (this.affinity_map.get(pair).getMinimum() == minimum);
assert (this.affinity_map.get(pair).getMaximum() == maximum);
}
record.add(source_id, target_id);
this.addAffinityRecord(pair, record);
}
/**
* Return all the tables that we have a record for
*
* @return
*/
public Set<String> getTables() {
return (this.table_pair_xref.keySet());
}
public Set<Pair<String, String>> getTables(String source_table) {
return (this.table_pair_xref.get(source_table));
}
public Integer getTarget(String source_table, int source_id, String target_table) {
Pair<String, String> pair = Pair.of(source_table, target_table);
AffinityRecord record = this.affinity_map.get(pair);
return (record != null ? record.getTarget(source_id) : null);
}
/**
* @param record
* @param source_id
* @param exclude_base
* @return
*/
private int nextInt(AffinityRecord record, int source_id, boolean exclude_base) {
RandomDistribution.Zipf zipf = record.getDistribution(source_id);
assert (zipf != null);
int value = zipf.nextInt();
int max = record.getMaximum();
if (exclude_base) {
long range_start = record.getTarget((int) zipf.getMin());
if (value >= range_start)
value++;
if (value > max) {
value = (value % max) + 1;
if (value == max)
value--;
}
} else if (value > max) {
value = (value % max) + 1;
}
record.getHistogram(source_id).put(value);
return (value);
}
@Override
public int numberAffinity(int minimum, int maximum, int base, String source_table, String target_table) {
Pair<String, String> pair = Pair.of(source_table, target_table);
AffinityRecord record = this.affinity_map.get(pair);
int value;
/*
* System.out.println("min=" + minimum); System.out.println("max=" +
* maximum); System.out.println("base=" + base);
* System.out.println("table=" + table); System.out.println("record=" +
* (record != null)); System.exit(1);
*/
// Use an AffinityRecord to generate next number
if (record != null && record.getDistribution(base) != null) {
value = this.nextInt(record, base, false);
// Otherwise, just use the default random number generator
} else {
value = super.number(minimum, maximum);
}
return (value);
}
/**
* Generate an affinity-skewed random number that excludes the number given
*/
@Override
public int numberExcluding(int minimum, int maximum, int excluding, String source_table, String target_table) {
Pair<String, String> pair = Pair.of(source_table, target_table);
AffinityRecord record = this.affinity_map.get(pair);
int value;
// Use an AffinityRecord to generate next number
if (record != null && record.getDistribution(excluding) != null) {
value = this.nextInt(record, excluding, true);
// Otherwise, just use the default random number generator
} else {
value = super.numberExcluding(minimum, maximum, excluding);
}
return (value);
}
/**
*
*/
@Override
public void loadProfile(String input_path) throws Exception {
String contents = FileUtil.readFile(input_path);
if (contents.isEmpty()) {
throw new Exception("The partition plan file '" + input_path + "' is empty");
}
JSONObject jsonObject = new JSONObject(contents);
System.out.println(jsonObject.toString(2));
this.fromJSONObject(jsonObject);
}
/**
* @param output_path
* @throws Exception
*/
@Override
public void saveProfile(String output_path) throws Exception {
String json = this.toJSONString();
JSONObject jsonObject = new JSONObject(json);
FileOutputStream out = new FileOutputStream(output_path);
out.write(jsonObject.toString(2).getBytes());
out.close();
}
@Override
public String toJSONString() {
JSONStringer stringer = new JSONStringer();
try {
stringer.object();
this.toJSONString(stringer);
stringer.endObject();
} catch (JSONException e) {
e.printStackTrace();
System.exit(-1);
}
return stringer.toString();
}
public void toJSONString(JSONStringer stringer) throws JSONException {
for (String source_table : this.getTables()) {
stringer.key(source_table).object();
for (Pair<String, String> pair : this.getTables(source_table)) {
stringer.key(pair.getSecond()).object();
this.affinity_map.get(pair).toJSONString(stringer);
stringer.endObject();
} // FOR
stringer.endObject();
} // FOR
}
public void fromJSONObject(JSONObject object) throws JSONException {
Iterator<String> i = object.keys();
while (i.hasNext()) {
String source_table = i.next();
JSONObject innerObject = object.getJSONObject(source_table);
Iterator<String> j = innerObject.keys();
while (j.hasNext()) {
String target_table = j.next();
Pair<String, String> pair = Pair.of(source_table, target_table);
AffinityRecord record = new AffinityRecord();
record.fromJSONObject(innerObject.getJSONObject(target_table));
this.addAffinityRecord(pair, record);
} // WHILE
} // WHILE
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(this.getClass().getSimpleName()).append("\n");
for (String source_table : this.getTables()) {
for (Pair<String, String> pair : this.getTables(source_table)) {
buffer.append("TABLES: ").append(pair).append("\n");
buffer.append(this.affinity_map.get(pair));
} // FOR
} // FOR
return (buffer.toString());
}
/**
* Construct profile for TPC-C warehouses.
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int minimum = 1;
int num_warehouses = Integer.parseInt(args[0]);
String output_path = args[1];
RandomGenerator rand = new RandomGenerator();
/**
* int half = (int)Math.ceil(num_warehouses / 2.0d); String table_name =
* Constants.TABLENAME_ORDERLINE; for (int ctr = minimum; ctr < half;
* ctr++) { int source = ctr; int target = ctr + half;
* rand.addAffinity(table_name, source, target, minimum,
* num_warehouses); rand.addAffinity(table_name, target, source,
* minimum, num_warehouses); } // FOR if ((half - minimum) % 2 == 1) {
* rand.addAffinity(table_name, half, half, minimum, num_warehouses); }
*/
System.out.println(rand.toJSONString());
rand.saveProfile(output_path);
System.out.println("Saved RandomGenerator profile to '" + output_path + "'");
}
}