package com.limegroup.gnutella.search;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.limegroup.gnutella.ManagedConnection;
import com.limegroup.gnutella.messages.QueryRequest;
/**
* This class handles query "probes." Probe queries are the initial queries
* that are sent out to determine the popularity of the file. This allows
* queries down new connections to have more information for choosing the TTL.
*/
final class ProbeQuery {
/**
* Constant list of hosts to probe query at ttl=1.
*/
private final List TTL_1_PROBES;
/**
* Constant list of hosts to probe query at ttl=2.
*/
private final List TTL_2_PROBES;
/**
* Constant reference to the query handler instance.
*/
private final QueryHandler QUERY_HANDLER;
/**
* Constructs a new <tt>ProbeQuery</tt> instance with the specified
* list of connections to query and with the data enclosed in the
* <tt>QueryHandler</tt>.
*
* @param connections the <tt>List</tt> of connections to query
* @param qh the <tt>QueryHandler</tt> instance containing data
* for the probe
*/
ProbeQuery(List connections, QueryHandler qh) {
QUERY_HANDLER = qh;
LinkedList[] lists =
createProbeLists(connections, qh.QUERY);
TTL_1_PROBES = lists[0];
TTL_2_PROBES = lists[1];
}
/**
* Obtains the time to wait for probe results to return.
*
* @return the time to wait for this probe to complete, in
* milliseconds
*/
long getTimeToWait() {
// determine the wait time. we wait a little longer per
// hop for probes to give them more time -- also weight
// this depending on how many TTL=1 probes we're sending
if(!TTL_2_PROBES.isEmpty())
return (long)((double)QUERY_HANDLER.getTimeToWaitPerHop()*1.3);
if(!TTL_1_PROBES.isEmpty())
return (long)((double)QUERY_HANDLER.getTimeToWaitPerHop()*
((double)TTL_1_PROBES.size()/2.0));
return 0L;
}
/**
* Sends the next probe query out on the network if there
* are more to send.
*
* @return the number of hosts theoretically hit by this new
* probe
*/
int sendProbe() {
Iterator iter = TTL_1_PROBES.iterator();
int hosts = 0;
QueryRequest query = QUERY_HANDLER.createQuery((byte)1);
while(iter.hasNext()) {
ManagedConnection mc = (ManagedConnection)iter.next();
hosts +=
QueryHandler.sendQueryToHost(query,
mc, QUERY_HANDLER);
}
query = QUERY_HANDLER.createQuery((byte)2);
iter = TTL_2_PROBES.iterator();
while(iter.hasNext()) {
ManagedConnection mc = (ManagedConnection)iter.next();
hosts +=
QueryHandler.sendQueryToHost(query,
mc, QUERY_HANDLER);
}
TTL_1_PROBES.clear();
TTL_2_PROBES.clear();
return hosts;
}
/**
* Helper method that creates the list of nodes to query for the probe.
* This list will vary in size depending on how popular the content appears
* to be.
*/
private static LinkedList[] createProbeLists(List connections,
QueryRequest query) {
Iterator iter = connections.iterator();
LinkedList missConnections = new LinkedList();
LinkedList oldConnections = new LinkedList();
LinkedList hitConnections = new LinkedList();
// iterate through our connections, adding them to the hit, miss, or
// old connections list
while(iter.hasNext()) {
ManagedConnection mc = (ManagedConnection)iter.next();
if(mc.isUltrapeerQueryRoutingConnection()) {
if(mc.shouldForwardQuery(query)) {
hitConnections.add(mc);
} else {
missConnections.add(mc);
}
} else {
oldConnections.add(mc);
}
}
// final list of connections to query
LinkedList[] returnLists = new LinkedList[2];
LinkedList ttl1List = new LinkedList();
LinkedList ttl2List = new LinkedList();
returnLists[0] = ttl1List;
returnLists[1] = ttl2List;
// do we have adequate data to determine some measure of the file's
// popularity?
boolean adequateData =
(missConnections.size()+hitConnections.size()) > 8;
// if we don't have enough data from QRP tables, just send out a
// traditional probe also, if we don't have an adequate number of QRP
// tables to access the popularity of the file, just send out an
// old-style probe at TTL=2
if(hitConnections.size() == 0 || !adequateData) {
return createAggressiveProbe(oldConnections, missConnections,
hitConnections, returnLists);
}
int numHitConnections = hitConnections.size();
double popularity =
(double)((double)numHitConnections/
((double)missConnections.size()+numHitConnections));
// if the file appears to be very popular, send it to only one host
if(popularity == 1.0) {
ttl1List.add(hitConnections.removeFirst());
return returnLists;
}
if(numHitConnections > 3) {
// TTL=1 queries are cheap -- send a lot of them if we can
int numToTry = Math.min(9, numHitConnections);
int startIndex = numHitConnections-numToTry;
int endIndex = numHitConnections;
ttl1List.addAll(hitConnections.subList(startIndex, endIndex));
return returnLists;
}
// otherwise, it's not very widely distributed content -- send
// the query to all hit connections plus 3 TTL=2 connections
ttl1List.addAll(hitConnections);
addToList(ttl2List, oldConnections, missConnections, 3);
return returnLists;
}
/**
* Helper method that adds as many elements as possible up to the
* desired number from two lists into a third list. This method
* takes as many elements as possible from <tt>list1</tt>, only
* using elements from <tt>list2</tt> if the desired number of
* elements to add cannot be fulfilled from <tt>list1</tt> alone.
*
* @param listToAddTo the list that elements should be added to
* @param list1 the first list to add elements from, with priority
* given to this list
* @param list2 the second list to add elements from -- only used
* in the case where <tt>list1</tt> is smaller than <tt>numElements</tt>
* @param numElements the desired number of elements to add to
* <tt>listToAddTo</tt> -- note that this number will not be reached
* if the list1.size()+list2.size() < numElements
*/
private static void addToList(List listToAddTo, List list1, List list2,
int numElements) {
if(list1.size() >= numElements) {
listToAddTo.addAll(list1.subList(0, numElements));
return;
} else {
listToAddTo.addAll(list1);
}
numElements = numElements - list1.size();
if(list2.size() >= numElements) {
listToAddTo.addAll(list2.subList(0, numElements));
} else {
listToAddTo.addAll(list2);
}
}
/**
* Helper method that creates lists of TTL=1 and TTL=2 connections to query
* for an aggressive probe. This is desired, for example, when the desired
* file appears to be rare or when there is not enough data to determine
* the file's popularity.
*
* @param oldConnections the <tt>List</tt> of old-style connections
* @param missConnections the <tt>List</tt> of new connections that did
* not have a hit for this query
* @param hitConnections the <tt>List</tt> of connections with hits
* @param returnLists the array of TTL=1 and TTL=2 connections to query
*/
private static LinkedList[]
createAggressiveProbe(List oldConnections, List missConnections,
List hitConnections, LinkedList[] returnLists) {
// add as many connections as possible from first the old connections
// list, then the connections that did not have hits
addToList(returnLists[1], oldConnections, missConnections, 3);
// add any hits there are to the TTL=1 list
int maxIndex = Math.min(4, hitConnections.size());
returnLists[0].addAll(hitConnections.subList(0, maxIndex));
return returnLists;
}
}