package edu.uw.cse.netlab.reputation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.global.GlobalManagerListener;
import org.gudy.azureus2.core3.peer.impl.transport.PEPeerTransportProtocol;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.ByteFormatter;
import org.gudy.azureus2.core3.util.HashWrapper;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import com.aelitis.azureus.core.peermanager.unchoker.UnchokerUtil;
public class GloballyAwareOneHopUnchoker
{
public static final int RECOMPUTE_INTERVAL_SECS = 9;
private static Logger logger = Logger.getLogger(GloballyAwareOneHopUnchoker.class.getName());
int nonlan_upload_budget;
int lan_upload_budget = 0;
boolean use_lan_speed = false;
private Map<HashWrapper, List<PEPeerTransportProtocol>> swarm_to_active = Collections.synchronizedMap(new HashMap<HashWrapper, List<PEPeerTransportProtocol>>());
private Map<HashWrapper, List<PEPeerTransportProtocol>> global_unchokes = Collections.synchronizedMap(new HashMap<HashWrapper, List<PEPeerTransportProtocol>>());
private Map<HashWrapper, List<PEPeerTransportProtocol>> global_chokes = Collections.synchronizedMap(new HashMap<HashWrapper, List<PEPeerTransportProtocol>>());
public static volatile GloballyAwareOneHopUnchoker inst = null;
public static GloballyAwareOneHopUnchoker get() {
if (inst == null)
inst = new GloballyAwareOneHopUnchoker();
return inst;
}
private GloballyAwareOneHopUnchoker() {
logger.info("Creating globally aware unchoker instance...");
COConfigurationManager.addAndFireParameterListener("Max Upload Speed KBs",
new ParameterListener() {
public void parameterChanged(String parameterName) {
nonlan_upload_budget = COConfigurationManager.getIntParameter("Max Upload Speed KBs");
if (nonlan_upload_budget == 0)
nonlan_upload_budget = 100000;
logger.finer("nonlan_upload_budget: " + nonlan_upload_budget);
}
});
COConfigurationManager.addAndFireParameterListener("LAN Speed Enabled",
new ParameterListener() {
public void parameterChanged(String parameterName) {
use_lan_speed = COConfigurationManager.getBooleanParameter("LAN Speed Enabled");
logger.finer("lan speed: " + Boolean.toString(use_lan_speed));
}
});
COConfigurationManager.addAndFireParameterListener(
"Max LAN Download Speed KBs", new ParameterListener() {
public void parameterChanged(String parameterName) {
lan_upload_budget = COConfigurationManager.getIntParameter("Max LAN Download Speed KBs");
if (lan_upload_budget == 0)
lan_upload_budget = 100000;
logger.finer("lan upload limit: " + lan_upload_budget);
}
});
AzureusCoreImpl.getSingleton().getGlobalManager().addListener(
new GlobalManagerListener() {
public void destroyInitiated() {
}
public void destroyed() {
}
public void downloadManagerAdded(DownloadManager dm) {
}
public void downloadManagerRemoved(DownloadManager dm) {
try {
logger.fine("Removing active peers for download manager: "
+ dm.getDisplayName());
swarm_to_active.remove(dm.getTorrent().getHashWrapper());
} catch (TOTorrentException e) {
e.printStackTrace();
}
}
public void seedingStatusChanged(boolean seeding_only_mode) {
}
});
if (nonlan_upload_budget == 0)
nonlan_upload_budget = 100000;
if (lan_upload_budget == 0)
lan_upload_budget = 100000;
logger.finer("initial budgets: lan: " + lan_upload_budget + " / nonlan: "
+ nonlan_upload_budget);
}
public boolean hasExtraCapacity() {
if (nonlan_upload_budget > 0) {
int bound = (int) (0.9 * (double) (nonlan_upload_budget * 1024));
boolean haveIt = AzureusCoreImpl.getSingleton().getGlobalManager().getStats().getDataAndProtocolSendRate() < bound;
if( logger.isLoggable(Level.FINEST) ) {
if (haveIt) {
logger.finest("have extra capacity: "
+ AzureusCoreImpl.getSingleton().getGlobalManager().getStats().getDataAndProtocolSendRate()
+ " " + nonlan_upload_budget * 1024 + " bound: " + bound);
} else {
logger.finest("no extra capacity: "
+ AzureusCoreImpl.getSingleton().getGlobalManager().getStats().getDataAndProtocolSendRate()
+ " " + nonlan_upload_budget * 1024 + " bound: " + bound);
}
}
return haveIt;
}
return AzureusCoreImpl.getSingleton().getGlobalManager().getStats().getDataAndProtocolSendRate() < 10 * 1024;
}
private long last_recompute;
public synchronized void full_unchoke_recompute() {
long start = System.currentTimeMillis();
logger.finer("=============== Globally aware full unchoke recompute... "
+ swarm_to_active.size() + " active peer lists =================");
global_chokes.clear();
global_unchokes.clear();
List<PEPeerTransportProtocol> all_peers = new ArrayList<PEPeerTransportProtocol>();
for (HashWrapper hw : swarm_to_active.keySet()) {
List<PEPeerTransportProtocol> peers = swarm_to_active.get(hw);
all_peers.addAll(peers);
global_chokes.put(hw, new ArrayList<PEPeerTransportProtocol>());
global_unchokes.put(hw, new ArrayList<PEPeerTransportProtocol>());
}
int max_to_unchoke = COConfigurationManager.getIntParameter("Max.Peer.Connections.Total");
if (max_to_unchoke == 0) {
max_to_unchoke = 500;
}
logger.finer("max to unchoke: " + max_to_unchoke);
int total_unchokes = 0, total_lan_unchokes = 0;
for (PEPeerTransportProtocol p : all_peers) {
p.setReputation(computeReputation(p));
if (p.isLANLocal() && total_lan_unchokes < 5) { // magic #. keep a few LAN local peers always unchoked. these will be over quickly anyway
global_unchokes.get(new HashWrapper(p.getControl().getHash())).add(p);
total_lan_unchokes++;
if( logger.isLoggable(Level.FINER) ) {
logger.finer(p + " from " + p.getControl().getDisplayName()
+ " is LAN local and will be unchoked, total lan unchokes: "
+ total_lan_unchokes);
}
}
}
final PEPeerTransportProtocol[] peers_by_reputation = all_peers.toArray(new PEPeerTransportProtocol[0]);
Arrays.sort(peers_by_reputation, new Comparator<PEPeerTransportProtocol>() {
public int compare(PEPeerTransportProtocol o1, PEPeerTransportProtocol o2) {
double v = o2.getReputation() - o2.getReputation();
if (Math.abs(v) < 1e-14)
return 0;
if (v > 0)
return -1;
return 1;
}
});
if( logger.isLoggable(Level.FINEST) ) {
for (PEPeerTransportProtocol p : peers_by_reputation) {
logger.finest(p.getControl().getDisplayName() + " " + p + " reputation: "
+ p.getReputation() + " (is OneSwarm?: "
+ Boolean.toString(p.isOneSwarm()) + ")");
}
}
/**
* Unchoke policy is this:
* 1. Descending order peers with reputation > 1.0
* 2. Any OneSwarm peers (randomly)
* 3. Any BitTorrent peers (randomly) if our ratio is < 1.0
* 4. Any snubbed peers (randomly)
*/
// 1. Descending order peers with reputation > 1.0
int positiveROI_cutoff = 0;
for (positiveROI_cutoff = 0; total_unchokes < max_to_unchoke
&& positiveROI_cutoff < all_peers.size()
&& calc_budget() < nonlan_upload_budget; positiveROI_cutoff++) {
PEPeerTransportProtocol peer = peers_by_reputation[positiveROI_cutoff];
if (peer.getReputation() < 1.0)
break;
if (UnchokerUtil.isUnchokable(peer, false)) {
global_unchokes.get(new HashWrapper(peer.getControl().getHash())).add(
peer);
total_unchokes++;
peer.setOptimisticUnchoke(false);
if( logger.isLoggable(Level.FINER) ) {
logger.finer("1) added reputation > 1.0, " + peer + " / total: "
+ total_unchokes + " rep: " + peer.getReputation());
}
} else {
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("1) not unchokable: " + peer + " (rep: "
+ peer.getReputation());
}
}
}
if (COConfigurationManager.getBooleanParameter("oneswarm.disallow.ratio.less.than.one") == false) {
if( logger.isLoggable(Level.FINER) ) {
logger.finer("allowing ratio < 1.0 peers, these include: "
+ (all_peers.size() - positiveROI_cutoff) + " candidates");
}
// Randomly permute peers < 1.0
PEPeerTransportProtocol[] shoddy_peers = new PEPeerTransportProtocol[peers_by_reputation.length
- positiveROI_cutoff];
System.arraycopy(peers_by_reputation, positiveROI_cutoff, shoddy_peers,
0, shoddy_peers.length);
Collections.shuffle(Arrays.asList(shoddy_peers));
if( logger.isLoggable(Level.FINER) ) {
logger.finer("before 2). shoddy_peer.length: " + shoddy_peers.length
+ " / positiveROI_cutoff: " + positiveROI_cutoff
+ " max_to_unchoke: " + max_to_unchoke + " unchokes_size: "
+ total_unchokes + " budget: " + calc_budget());
}
// 2. Any OneSwarm peers (randomly)
for (int i = 0; i < shoddy_peers.length
&& total_unchokes < max_to_unchoke
&& calc_budget() < nonlan_upload_budget; i++) {
PEPeerTransportProtocol peer = shoddy_peers[i];
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("2) shoddy rep: " + peer.getReputation() + " " + peer);
}
if (UnchokerUtil.isUnchokable(peer, false) && peer.isOneSwarm()) {
global_unchokes.get(new HashWrapper(peer.getControl().getHash())).add(
peer);
total_unchokes++;
peer.setOptimisticUnchoke(true);
if( logger.isLoggable(Level.FINER) ) {
logger.finer("adding in 2) " + peer + " / total: " + total_unchokes
+ " rep: " + peer.getReputation());
}
} else {
if( logger.isLoggable(Level.FINER) ) {
logger.finest("2. not unchokable (or not OneSwarm)" + peer);
}
}
}
if( logger.isLoggable(Level.FINER) ) {
logger.finer("before 3). shoddy_peer.length: " + shoddy_peers.length
+ " / positiveROI_cutoff: " + positiveROI_cutoff
+ " max_to_unchoke: " + max_to_unchoke + " unchokes_size: "
+ total_unchokes + " budget: " + calc_budget());
}
// 3. Any BitTorrent peers (randomly) if our ratio is < 1.0
for (int i = 0; i < shoddy_peers.length
&& total_unchokes < max_to_unchoke
&& calc_budget() < nonlan_upload_budget; i++) {
PEPeerTransportProtocol peer = shoddy_peers[i];
// Some of these will be OneSwarm peers that we added previously, skip these.
List<PEPeerTransportProtocol> unchoked_this_swarm = global_unchokes.get(new HashWrapper(
peer.getControl().getHash()));
if (unchoked_this_swarm.contains(peer)) {
continue;
}
if (UnchokerUtil.isUnchokable(peer, false)) {
unchoked_this_swarm.add(peer);
total_unchokes++;
peer.setOptimisticUnchoke(true);
if( logger.isLoggable(Level.FINER) ) {
logger.finer("adding in 3) " + peer + " / total: " + total_unchokes
+ " rep: " + peer.getReputation());
}
} else {
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("not unchokable 3): " + peer);
}
}
} // if( System.getProperty("oneswarm.allow.ratio.less.than.one") != null )
if( logger.isLoggable(Level.FINER) ) {
logger.finer("before 4). shoddy_peer.length: " + shoddy_peers.length
+ " / positiveROI_cutoff: " + positiveROI_cutoff
+ " max_to_unchoke: " + max_to_unchoke + " unchokes_size: "
+ total_unchokes + " budget: " + calc_budget());
}
// 4. Any snubbed peers
for (int i = 0; total_unchokes < max_to_unchoke && i < all_peers.size()
&& calc_budget() < nonlan_upload_budget; i++) {
PEPeerTransportProtocol peer = peers_by_reputation[i];
List<PEPeerTransportProtocol> unchoked_this_swarm = global_unchokes.get(new HashWrapper(
peer.getControl().getHash()));
if (unchoked_this_swarm.contains(peer)) {
continue;
}
if (UnchokerUtil.isUnchokable(peer, true)) {
unchoked_this_swarm.add(peer);
total_unchokes++;
peer.setOptimisticUnchoke(true);
if( logger.isLoggable(Level.FINER) ) {
logger.finer("adding in 4) " + peer + " / total: " + total_unchokes
+ " rep: " + peer.getReputation());
}
} else {
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("4. not unchokable " + peer);
}
}
}
} // not allowing ratio < 1.0
else {
if( logger.isLoggable(Level.FINE) ) {
logger.fine("global unchoker not allowing ratio less than 1.0, thus skipping: "
+ (all_peers.size() - positiveROI_cutoff) + " candidates");
}
}
/**
* If not unchoked, choke
*/
int chokes = 0;
for (int i = 0; i < all_peers.size(); i++) {
PEPeerTransportProtocol peer = peers_by_reputation[i];
HashWrapper this_hash = new HashWrapper(peer.getControl().getHash());
if (global_unchokes.get(this_hash).contains(peer) == false) {
global_chokes.get(this_hash).add(peer);
peer.setWeight(1);
chokes++;
}
}
if( logger.isLoggable(Level.FINE) ) {
logger.fine("=========== ended in " + (System.currentTimeMillis() - start)
+ " ms, upload budget: " + calc_budget() + " with " + total_unchokes
+ " unchokes of " + all_peers.size() + " chokes: " + chokes
+ " =================");
}
}
private double computeReputation(PEPeerTransportProtocol p) {
try {
// From the perspective of the one hop unchoker, these peers are useless
if (p.isOneSwarm() == false) {
return 0.0;
}
return Math.max(
Computation.direct_reputation(p.getCertificate().getPublicKey()),
Computation.indirect_reputation(p.getSharedIntermediaries()));
} catch (IOException e) {
e.printStackTrace();
return -1; // -1 ~ "has no standing"
}
}
private double calc_budget() {
logger.finest("calc_budget");
double min_roi = Double.MAX_VALUE;
for (List<PEPeerTransportProtocol> list : global_unchokes.values()) {
for (PEPeerTransportProtocol peer : list) {
if (peer.getReputation() < min_roi && peer.getReputation() != 0)
min_roi = peer.getReputation();
/**
* -1 is a sentinel value so we know when we've set this later. F2F connections appear as the same peer (with different
* channel IDs). We need to set their weight exactly once, but they might multiple times in the list.
*/
peer.setWeight(-1.0);
}
}
double total = 0;
for (List<PEPeerTransportProtocol> list : global_unchokes.values()) {
for (PEPeerTransportProtocol peer : list) {
if (peer.getWeight() > 0)
continue; // we've already set this one.
/**
* These are considered separately
*/
if (peer.isLANLocal()) {
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("\tskipping LAN local: " + peer);
}
peer.setWeight(5.0); // this doesn't really matter since it's ignored by the LAN rate limiter
continue;
}
// TODO: magic numbers: 20% and 5 KBps
double used = Math.min(0.20 * nonlan_upload_budget,
(peer.getReputation() / min_roi) * 5.0);
// In case we didn't actually find any oneswarm peers, provision everyone at 5.0
if (min_roi == Double.MAX_VALUE)
used = 5.0;
peer.setWeight(used);
total += used;
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("\traw budget: " + peer + " " + used + " / " + total
+ " min_roi: " + min_roi);
}
}
}
/**
* This can arise if we have no history with any peer.
*/
if (total == 0)
return 0;
/**
* Normalize
*/
int id = (int) (Math.random() * 100); // disambiguate log output
for (List<PEPeerTransportProtocol> list : global_unchokes.values()) {
for (PEPeerTransportProtocol peer : list) {
peer.setWeight(peer.getWeight() / total);
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("\t" + id + " " + peer + " weight: " + peer.getWeight());
}
}
}
if( logger.isLoggable(Level.FINEST) ) {
logger.finest("\tout: "
+ total
+ " actual UL: "
+ AzureusCoreImpl.getSingleton().getGlobalManager().getStats().getDataAndProtocolSendRate()
/ 1024);
}
return total;
}
public void consider_peers(HashWrapper swarm, ArrayList all_peers) {
// by: isdal
// type safetypy not all peers are of type PEPeerTransportProtocol
ArrayList<PEPeerTransportProtocol> pepeers = new ArrayList<PEPeerTransportProtocol>(
all_peers.size());
for (Object o : all_peers) {
if (o instanceof PEPeerTransportProtocol) {
pepeers.add((PEPeerTransportProtocol) o);
}
}
swarm_to_active.put(swarm, pepeers);
if( logger.isLoggable(Level.FINER) ) {
logger.finer("adding peers for swarm: "
+ ByteFormatter.encodeString(swarm.getBytes()) + " next recompute: "
+ ((last_recompute + 9 * 1000) - System.currentTimeMillis()));
}
/**
* don't do this every time a swarm calls its unchoke recompute. instead, wait 9 secs in between to keep it to ~once per TFT round
*/
if (last_recompute + RECOMPUTE_INTERVAL_SECS * 1000 < System.currentTimeMillis()) {
full_unchoke_recompute();
last_recompute = System.currentTimeMillis();
}
}
public synchronized ArrayList unchokes_for_swarm(HashWrapper swarm) {
for (List<PEPeerTransportProtocol> l : global_unchokes.values()) {
if (l.size() > 0) {
logger.finer("unchoke size: " + l.size() + " for "
+ ByteFormatter.encodeString(swarm.getBytes()));
}
}
ArrayList out = (ArrayList) global_unchokes.get(swarm);
if (out == null) {
logger.warning("Null global_unchokes for swarm: "
+ ByteFormatter.encodeString(swarm.getBytes()));
return new ArrayList();
}
return out;
}
public synchronized ArrayList chokes_for_swarm(HashWrapper swarm) {
for (List<PEPeerTransportProtocol> l : global_chokes.values()) {
if (l.size() > 0) {
logger.finer("choke size: " + l.size() + " for "
+ ByteFormatter.encodeString(swarm.getBytes()));
}
}
ArrayList out = (ArrayList) global_chokes.get(swarm);
if (out == null) {
//logger.warning("Null global_chokes for swarm: " + ByteFormatter.encodeString(swarm.getBytes()));
return new ArrayList();
}
return out;
}
}