/*
* Copyright 2012, Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.LinkBench.distributions;
import java.util.Properties;
import java.util.Random;
import com.facebook.LinkBench.Config;
import com.facebook.LinkBench.ConfigUtil;
import com.facebook.LinkBench.InvertibleShuffler;
import com.facebook.LinkBench.LinkStore;
import com.facebook.LinkBench.RealDistribution;
import com.facebook.LinkBench.RealDistribution.DistributionType;
import com.facebook.LinkBench.distributions.LinkDistributions.LinkDistribution;
/**
* Encapsulate logic for choosing id2s for request workload.
* @author tarmstrong
*
*/
public class ID2Chooser {
/*
* Constants controlling the desired probability of a link for (id1, link_type, id2)
* existing for a given operation. must be > 0
*/
public static final double P_GET_EXIST = 0.5; // Mix of new and pre-loaded
public static final double P_UPDATE_EXIST = 0.9; // Mostly pre-loaded
public static final double P_DELETE_EXIST = 0.9;
public static final double P_ADD_EXIST = 0.05; // Avoid colliding with pre-loaded too much
// How many times to try to find a unique id2
private static final int MAX_UNIQ_ITERS = 100;
private final long startid1;
private long maxid1;
/** if > 0, choose id2s in range [startid1, randomid2max) */
private final long randomid2max;
/** Number of distinct link types */
private final int linkTypeCount;
private final InvertibleShuffler nLinksShuffler;
// #links distribution from properties file
private final LinkDistribution linkDist;
// configuration for generating id2
private final int id2gen_config;
// Information about number of request threads, used to generate
// thread-unique id2s
private final int nrequesters;
private final int requesterID;
public ID2Chooser(Properties props, long startid1, long maxid1,
int nrequesters, int requesterID) {
this.startid1 = startid1;
this.maxid1 = maxid1;
this.nrequesters = nrequesters;
this.requesterID = requesterID;
// random number generator for id2
randomid2max = ConfigUtil.getLong(props, Config.RANDOM_ID2_MAX, 0L);
// configuration for generating id2
id2gen_config = ConfigUtil.getInt(props, Config.ID2GEN_CONFIG, 0);
linkTypeCount = ConfigUtil.getInt(props, Config.LINK_TYPE_COUNT, 1);
linkDist = LinkDistributions.loadLinkDistribution(props, startid1, maxid1);
nLinksShuffler = RealDistribution.getShuffler(DistributionType.LINKS,
maxid1 - startid1);
}
/**
* Choose an ids
* @param rng
* @param id1
* @param link_type
* @param outlink_ix this is the ith link of this type for this id1
* @return
*/
public long chooseForLoad(Random rng, long id1, long link_type,
long outlink_ix) {
if (randomid2max == 0) {
return id1 + outlink_ix;
} else {
return rng.nextInt((int)randomid2max);
}
}
/**
* Choose an id2 for an operation given an id1
* @param id1
* @param linkType
* @param pExisting approximate probability that id should be in
* existing range
* @return
*/
public long chooseForOp(Random rng, long id1, long linkType,
double pExisting) {
long nlinks = calcLinkCount(id1, linkType);
long range = calcID2Range(pExisting, nlinks);
return chooseForOpInternal(rng, id1, range);
}
public long[] chooseMultipleForOp(Random rng, long id1, long linkType,
int nid2s, double pExisting) {
long id2s[] = new long[nid2s];
long nlinks = calcLinkCount(id1, linkType);
long range = calcID2Range(pExisting, nlinks);
if (range <= nid2s && randomid2max == 0) {
// Range is smaller than required # of ids, fill in all from range
for (int i = 0; i < nid2s; i++) {
long id2 = id1 + i;
if (id2gen_config == 1) {
id2 = fixId2(id2, nrequesters, requesterID, randomid2max);
}
id2s[i] = id2;
}
} else {
for (int i = 0; i < nid2s; i++) {
long id2;
int iters = 0; // avoid long or infinite loop
do {
// Find a unique id2
id2 = chooseForOpInternal(rng, id1, range);
iters++;
} while (contains(id2s, i, id2) && iters <= MAX_UNIQ_ITERS);
id2s[i] = id2;
}
}
return id2s;
}
/**
* Check if id2 is in first n elements of id2s
* @param id2s
* @param i
* @param id2
* @return
*/
private boolean contains(long[] id2s, int n, long id2) {
for (int i = 0; i < n; i++) {
if (id2s[i] == id2) {
return true;
}
}
return false;
}
private long calcID2Range(double pExisting, long nlinks) {
long range = (long) Math.ceil((1/pExisting) * nlinks);
range = Math.max(1, range);// Ensure non-empty range
return range;
}
/**
* Internal helper to choose id
* @param rng
* @param id1
* @param range range size of id2s to select within
* @return
*/
private long chooseForOpInternal(Random rng, long id1, long range) {
assert(range >= 1);
// We want to sometimes add a link that already exists and sometimes
// add a new link. So generate id2 such that it has roughly pExisting
// chance of already existing.
// This happens unless randomid2max is non-zero (in which case just pick a
// random id2 upto randomid2max).
long id2;
if (randomid2max == 0) {
id2 = id1 + rng.nextInt((int)range);
} else {
id2 = rng.nextInt((int)randomid2max);
}
if (id2gen_config == 1) {
return fixId2(id2, nrequesters, requesterID, randomid2max);
} else {
return id2;
}
}
public boolean sameShuffle;
/**
* Calculates the original number of outlinks for a given id1 (i.e. the
* number that would have been loaded)
* Sets sameShuffle field to true if shuffled was same as original
* @return number of links for this id1
*/
public long calcTotalLinkCount(long id1) {
assert(id1 >= startid1 && id1 < maxid1);
// Shuffle. A low id after shuffling means many links, a high means few
long shuffled;
if (linkDist.doShuffle()) {
shuffled = startid1 + nLinksShuffler.invertPermute(id1 - startid1);
} else {
shuffled = id1;
}
assert(shuffled >= startid1 && shuffled < maxid1);
sameShuffle = shuffled == id1;
long nlinks = linkDist.getNlinks(shuffled);
return nlinks;
}
// return a new id2 that satisfies 3 conditions:
// 1. close to current id2 (can be slightly smaller, equal, or larger);
// 2. new_id2 % nrequesters = requestersId;
// 3. smaller or equal to randomid2max unless randomid2max = 0
private static long fixId2(long id2, long nrequesters,
long requesterID, long randomid2max) {
long newid2 = id2 - (id2 % nrequesters) + requesterID;
if ((newid2 > randomid2max) && (randomid2max > 0)) newid2 -= nrequesters;
return newid2;
}
public long[] getLinkTypes() {
long res[] = new long[linkTypeCount];
// Just have link types in a sequence starting at the default one
for (int i = 0; i < linkTypeCount; i++) {
res[i] = LinkStore.DEFAULT_LINK_TYPE + i;
}
return res;
}
/**
* Choose a link type.
* For now just select each type with equal probability.
*/
public long chooseRandomLinkType(Random rng) {
return LinkStore.DEFAULT_LINK_TYPE + rng.nextInt(linkTypeCount);
}
public long calcLinkCount(long id1, long linkType) {
// Divide total links between types so that total is correct
long totCount = calcTotalLinkCount(id1);
long minCount = totCount / linkTypeCount;
long leftOver = totCount - minCount;
int typeNum = (int)(linkType -LinkStore.DEFAULT_LINK_TYPE);
if (typeNum < leftOver) {
return minCount + 1;
} else {
return minCount;
}
}
}