package com.limegroup.gnutella.downloader; import java.util.NoSuchElementException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.util.IntervalSet; import com.limegroup.gnutella.util.SystemUtils; /** * This SelectionStrategy sometimes selects the first available chunk and * sometimes selects a random chunk from availableIntervals. This balances * the user's need to preview a file against the network's need to maximize * distribution of the most rare block. * * If the user is idle MIN_IDLE_MILLISECONDS or more, a random chunk is always * selected. Otherwise, the following strategy is used: if the first * MIN_PRIVIEW_BYTES or MIN_PREVIEW_FRACTION of the file has not yet been * assigned to Downloaders, the first chunk is selected. If the first 50% of the * file has not been assigned to Downloaders, there's a 50% chance that the first * available chunk will be assigned and a 50% chance that a random chunk will be * assigned. Otherwise, a random chunk is assigned. */ public class BiasedRandomDownloadStrategy extends RandomDownloadStrategy { private static final Log LOG = LogFactory.getLog(BiasedRandomDownloadStrategy.class); /** * The minimum number of bytes for a reasonable preview. * This is used as a goal for the random downloader code. */ private static final int MIN_PREVIEW_BYTES = 1024 * 1024; //1 MB /** * The minimum fraction of bytes for a reasonable preview. * This is used as a goal for the random downloader code. */ private static final float MIN_PREVIEW_FRACTION = 0.1f; // 10 percent of the file /** * Once this fraction of the file is previewable, we switch to a fully * random download strategy. */ private static final float MAX_PREVIEW_FRACTION = 0.5f; // 50 percent of the file /** * Number of milliseconds the user has to be idle before being considered idle. * This factors into the download order strategy. */ /* package */ static final int MIN_IDLE_MILLISECONDS = 5 * 60 * 1000; // 5 minutes public BiasedRandomDownloadStrategy(long fileSize) { super(fileSize); } public synchronized Interval pickAssignment(IntervalSet candidateBytes, IntervalSet neededBytes, long blockSize) throws java.util.NoSuchElementException { long lowerBound = neededBytes.getFirst().low; long upperBound = neededBytes.getLast().high; // Input validation if (blockSize < 1) throw new IllegalArgumentException("Block size cannot be "+blockSize); if (lowerBound < 0) throw new IllegalArgumentException("First needed byte must be >= 0, "+lowerBound+"<0"); if (upperBound >= completedSize) throw new IllegalArgumentException("neededBytes contains bytes beyond the end of the file."+ upperBound + " >= " + completedSize); if (candidateBytes.isEmpty()) throw new NoSuchElementException(); // Determine if we should return a uniformly distributed Interval // or the first interval. // nextFloat() returns a float on [0.0 1.0) if (getIdleTime() >= MIN_IDLE_MILLISECONDS // If the user is idle, always use random strategy || pseudoRandom.nextFloat() >= getBiasProbability(lowerBound, completedSize)) { return super.pickAssignment(candidateBytes, neededBytes, blockSize); } Interval candidate = candidateBytes.getFirst(); // Calculate what the high byte offset should be. // This will be at most blockSize-1 bytes greater than the low. long alignedHigh = alignHigh(candidate.low, blockSize); // alignedHigh >= candidate.low, and therefore we // only have to check if alignedHigh > candidate.high. if (alignedHigh > candidate.high) alignedHigh = candidate.high; // Our ideal interval is [candidate.low, alignedHigh] // Optimize away creation of new objects, if possible Interval ret = candidate; if (ret.high != alignedHigh) ret = new Interval(candidate.low, alignedHigh); if (LOG.isDebugEnabled()) LOG.debug("Non-random download, probability=" +getBiasProbability(lowerBound, completedSize) +", range=" + ret + " out of choices " + candidateBytes); return ret; } /////////////////// Private Helper Methods //////////////////////////// /** * Calculates the probability that the next block assigned should be * guaranteed to be from the beginning of the file. This is calculated as a * step function that is at 100% until max(MIN_PREVIEW_BYTES, * MIN_PREVIEW_FRACTION * completedSize), then drops down to 50% until * MAX_PREVIEW_FRACTION of the file is downloaded. Above * MAX_PREVIEW_FRACTION, the function returns 0%, indicating that a fully * random downloading strategy should be used. * * @return the probability that the next chunk should be forced to be * downloaded from the beginning of the file. */ private float getBiasProbability(long previewBytesDownloaded, long fileSize) { long goal = Math.max((long)MIN_PREVIEW_BYTES, (long)(MIN_PREVIEW_FRACTION * fileSize)); // If we don't have our goal yet, devote 100% of resources to extending // the previewable length if (previewBytesDownloaded < goal) return 1.0f; // If we have less than the cutoff (currently 50% of the file) if (previewBytesDownloaded < MAX_PREVIEW_FRACTION * fileSize) return 0.5f; return 0.0f; } /** * Gets the number of milliseconds that the user has been idle. * The actual granularity of this time measurement is likely much * greater than one millisecond. * * This is stubbed out in some tests. * * @return the number of milliseconds that the user has been idle. */ protected long getIdleTime() { return SystemUtils.getIdleTime(); } }