package com.limegroup.gnutella.dht; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.math.BigInteger; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Executor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.collection.Comparators; import org.limewire.concurrent.ExecutorsHelper; import org.limewire.core.settings.DHTSettings; import org.limewire.inject.EagerSingleton; import org.limewire.inspection.Inspectable; import org.limewire.inspection.InspectableContainer; import org.limewire.inspection.InspectionHistogram; import org.limewire.inspection.InspectionPoint; import org.limewire.io.IpPort; import org.limewire.io.NetworkUtils; import org.limewire.lifecycle.Service; import org.limewire.mojito.EntityKey; import org.limewire.mojito.KUID; import org.limewire.mojito.MojitoDHT; import org.limewire.mojito.concurrent.DHTFuture; import org.limewire.mojito.concurrent.DHTFutureAdapter; import org.limewire.mojito.db.DHTValue; import org.limewire.mojito.db.Database; import org.limewire.mojito.result.FindValueResult; import org.limewire.mojito.result.StoreResult; import org.limewire.mojito.routing.Bucket; import org.limewire.mojito.routing.Contact; import org.limewire.mojito.routing.RouteTable; import org.limewire.mojito.routing.Vendor; import org.limewire.mojito.routing.Version; import org.limewire.mojito.settings.ContextSettings; import org.limewire.mojito.settings.KademliaSettings; import org.limewire.mojito.statistics.DHTStats; import org.limewire.statistic.StatsUtils; import org.limewire.util.ByteUtils; import org.limewire.util.DebugRunnable; import com.google.inject.Inject; import com.google.inject.name.Named; import com.limegroup.gnutella.connection.ConnectionLifecycleEvent; import com.limegroup.gnutella.messages.vendor.DHTContactsMessage; import com.limegroup.gnutella.util.ClassCNetworks; /** * This DHT manager starts either an active or a passive DHT controller. * It also handles switching from one mode to the other. * <p> * This class offloads blocking operations to a thread pool * so that it never blocks on critical threads such as MessageDispatcher. */ @EagerSingleton public class DHTManagerImpl implements DHTManager, Service { private static final Log LOG = LogFactory.getLog(DHTManagerImpl.class); /** * The Vendor code of this DHT Node. */ private final Vendor vendor = ContextSettings.getVendor(); /** * The Version of this DHT Node. */ private final Version version = ContextSettings.getVersion(); /** * The DHTController instance. */ private DHTController controller = new NullDHTController(); /** * List of event listeners for ConnectionLifeCycleEvents. */ private final List<DHTEventListener> dhtEventListeners = new ArrayList<DHTEventListener>(1); /** * The executor to use to execute blocking DHT methods, such * as stopping or starting a Mojito instance (which perform * network and disk I/O). * */ private final Executor executor; /** * The executor to use for dispatching events. */ private final Executor dispatchExecutor; private volatile boolean enabled = true; private final DHTControllerFactory dhtControllerFactory; @InspectionPoint("time for dht bootstrap") private final BootstrapTimer bootstrapTimer = new BootstrapTimer(); @InspectionPoint("dht get statistics") private final TimeValuesInspectable getInspectable = new TimeValuesInspectable(); @InspectionPoint("dht put statistics") private final TimeValuesInspectable putInspectable = new TimeValuesInspectable(); /** * Constructs the DHTManager, using the given Executor to invoke blocking * methods. The executor MUST be single-threaded, otherwise there will be * failures. * * @param service executor for executing blocking DHT methods * @param dhtControllerFactory creates DHT node controllers */ @Inject public DHTManagerImpl(@Named("dhtExecutor") Executor service, DHTControllerFactory dhtControllerFactory) { this.executor = service; this.dispatchExecutor = ExecutorsHelper.newProcessingQueue("DHT-EventDispatch"); this.dhtControllerFactory = dhtControllerFactory; addEventListener(bootstrapTimer); } @Inject void register(org.limewire.lifecycle.ServiceRegistry registry) { registry.register(this); } public String getServiceName() { return org.limewire.i18n.I18nMarker.marktr("Mojito DHT"); } public void initialize() { } public void start() { } /* * (non-Javadoc) * @see com.limegroup.gnutella.dht.DHTManager#setEnabled(boolean) */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /* * (non-Javadoc) * @see com.limegroup.gnutella.dht.DHTManager#isEnabled() */ public boolean isEnabled() { if (!DHTSettings.DISABLE_DHT_NETWORK.getValue() && !DHTSettings.DISABLE_DHT_USER.getValue() && enabled) { return true; } return false; } /* * (non-Javadoc) * @see com.limegroup.gnutella.dht.DHTManager#start(com.limegroup.gnutella.dht.DHTManager.DHTMode) */ public synchronized void start(DHTMode mode) { executor.execute(createSwitchModeCommand(mode)); } /* * (non-Javadoc) * @see com.limegroup.gnutella.dht.DHTManager#stop() */ public synchronized void stop() { Runnable command = new DebugRunnable(new Runnable() { public void run() { synchronized (DHTManagerImpl.this) { try { createSwitchModeCommand(DHTMode.INACTIVE).run(); } finally { DHTManagerImpl.this.notifyAll(); } } } }); executor.execute(command); try { this.wait(10000); } catch (InterruptedException err) { LOG.error("InterruptedException", err); } } /** * Creates and returns a Runnable that switches the DHT node from * the current <code>DHTMode</code> to the given <code>mode</code>. * * @param mode the new mode of the DHT node * @return Runnable that switches the mode */ private Runnable createSwitchModeCommand(final DHTMode mode) { Runnable command = new DebugRunnable(new Runnable() { public void run() { synchronized (DHTManagerImpl.this) { // Controller already running in the current mode? if (controller.getDHTMode() == mode) { return; } controller.stop(); if (mode == DHTMode.ACTIVE) { controller = dhtControllerFactory.createActiveDHTNodeController( vendor, version, DHTManagerImpl.this); } else if (mode == DHTMode.PASSIVE) { controller = dhtControllerFactory .createPassiveDHTNodeController(vendor, version, DHTManagerImpl.this); } else if (mode == DHTMode.PASSIVE_LEAF) { controller = dhtControllerFactory.createPassiveLeafController( vendor, version, DHTManagerImpl.this); } else { controller = new NullDHTController(); } controller.start(); } } }); return command; } public void addActiveDHTNode(final SocketAddress hostAddress) { executor.execute(new Runnable() { public void run() { synchronized(DHTManagerImpl.this) { controller.addActiveDHTNode(hostAddress); } } }); } public void addPassiveDHTNode(final SocketAddress hostAddress) { executor.execute(new Runnable() { public void run() { synchronized(DHTManagerImpl.this) { controller.addPassiveDHTNode(hostAddress); } } }); } public void addressChanged() { // Do this in a different thread as there are some blocking //disk and network ops. executor.execute(new DebugRunnable(new Runnable() { public void run() { synchronized(DHTManagerImpl.this) { if (controller.isRunning()) { controller.stop(); controller.start(); } } } })); } public synchronized List<IpPort> getActiveDHTNodes(int maxNodes){ return controller.getActiveDHTNodes(maxNodes); } public synchronized DHTMode getDHTMode() { return controller.getDHTMode(); } public synchronized boolean isRunning() { return controller.isRunning(); } public synchronized boolean isBootstrapped() { return controller.isBootstrapped(); } public synchronized boolean isMemberOfDHT() { return isRunning() && isBootstrapped(); } public synchronized boolean isWaitingForNodes() { return controller.isWaitingForNodes(); } /** * Adds a listener to DHT Events. * <p> * Be aware that listeners will receive events after * after the DHT has dispatched them. It is possible that * the DHT's status may have changed between the time the * event was dispatched and the time the event is received * by a listener. */ public synchronized void addEventListener(DHTEventListener listener) { if(dhtEventListeners.contains(listener)) throw new IllegalArgumentException("Listener " + listener + " already registered"); dhtEventListeners.add(listener); } /** * Sends an event to all listeners. * <p> * Be aware that to prevent deadlock, listeners may receive * the event long after the DHT's status has changed, and the * current status may be very different. * <p> * No events will be received in a different order than they were * dispatched, though. */ public synchronized void dispatchEvent(final DHTEvent event) { if(!dhtEventListeners.isEmpty()) { final List<DHTEventListener> listeners = new ArrayList<DHTEventListener>(dhtEventListeners); dispatchExecutor.execute(new Runnable() { public void run() { for(DHTEventListener listener : listeners) { listener.handleDHTEvent(event); } } }); } } public synchronized void removeEventListener(DHTEventListener listener) { dhtEventListeners.remove(listener); } /** * This getter is for internal use only. The Mojito DHT is not meant to * be handled or passed around independently, as only the DHT controllers * know how to interact correctly with it. */ public synchronized MojitoDHT getMojitoDHT() { return controller.getMojitoDHT(); } /** * Shuts the DHT down if we got disconnected from the network. * The nodeAssigner will take care of restarting this DHT node if * it still qualifies. * <p> * If this event is not related to disconnection from the network, it * is forwarded to the controller for proper handling. */ public void handleConnectionLifecycleEvent(final ConnectionLifecycleEvent evt) { Runnable command = null; if (evt.isDisconnectedEvent() || evt.isNoInternetEvent()) { command = new DebugRunnable( new Runnable() { public void run() { synchronized(DHTManagerImpl.this) { if (controller.isRunning() && !DHTSettings.FORCE_DHT_CONNECT.getValue()) { controller.stop(); controller = new NullDHTController(); } } } }); } else { command = new Runnable() { public void run() { synchronized(DHTManagerImpl.this) { controller.handleConnectionLifecycleEvent(evt); } } }; } executor.execute(command); } public Vendor getVendor() { return vendor; } public Version getVersion() { return version; } public void handleDHTContactsMessage(final DHTContactsMessage msg) { executor.execute(new Runnable() { public void run() { synchronized(DHTManagerImpl.this) { for (Contact node : msg.getContacts()) { controller.addContact(node); } } } }); } /** * Calls the {@link MojitoDHT#put} if a bootstrappable DHT is available. * Also handles the locking properly to ensure thread safety. * * @param eKey the entity key used to perform lookup in the DHT. * * @return an instance of <code>DHTFuture</code> containing the result of the lookup. * <br> Returns null if DHT is unavailable or the DHT is not bootstrapped. * */ public synchronized DHTFuture<FindValueResult> get(EntityKey eKey) { MojitoDHT mojitoDHT = getMojitoDHT(); if (LOG.isDebugEnabled()) LOG.debug("DHT:" + mojitoDHT); if (mojitoDHT == null || !mojitoDHT.isBootstrapped()) { LOG.debug("DHT is null or is not bootstrapped"); return null; } // instantiated here so it can record its instantiation time TimeInspector<FindValueResult> inspector = new TimeInspector<FindValueResult>(getInspectable) { @Override public void handleFutureSuccess(FindValueResult result) { count(result.isSuccess()); } }; DHTFuture<FindValueResult> future = mojitoDHT.get(eKey); future.addDHTFutureListener(inspector); return future; } /** * Calls the {@link MojitoDHT#put} if a bootstrappable DHT is available. * Also handles the locking properly to ensure thread safety. * * @param key a unique id used as a key to find the associated value. * @param value the value which will be stored in the DHT. * * @return an instance of <code>DHTFuture</code> containing the result of the storage. * <br> Returns null if DHT is unavailable or the DHT is not bootstrapped. */ public synchronized DHTFuture<StoreResult> put(KUID key, DHTValue value) { MojitoDHT mojitoDHT = getMojitoDHT(); if (LOG.isDebugEnabled()) LOG.debug("DHT: " + mojitoDHT); if (mojitoDHT == null || !mojitoDHT.isBootstrapped()) { LOG.debug("DHT is null or unable to bootstrap"); return null; } // instantiated here so it can record its instantiation time TimeInspector<StoreResult> inspector = new TimeInspector<StoreResult>(putInspectable) { @Override public void handleFutureSuccess(StoreResult result) { boolean success = result.getLocations().size() > 0.8 * KademliaSettings.REPLICATION_PARAMETER.getValue(); count(success); } }; DHTFuture<StoreResult> future = mojitoDHT.put(key, value); future.addDHTFutureListener(inspector); return future; } /** a bunch of inspectables */ @SuppressWarnings("unused") @InspectableContainer private class DHTInspectables { /* * 1 - initial version, doubles reported as long * Integer.MAX_VALUE * 2 - doubles reported as Double.doubleToLongBits * 3 - Remove the BigInteger stats, use the 32 MSBits instead. */ private static final int VERSION = 3; private void addVersion(Map<String, Object> m) { m.put("sv",VERSION); } @InspectionPoint("general dht stats") public Inspectable general = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); addVersion(data); DHTMode mode = getDHTMode(); boolean running = isRunning(); boolean bootstrapped = isBootstrapped(); boolean waiting = isWaitingForNodes(); boolean enabled= isEnabled(); Version version = getVersion(); data.put("mode", Byte.valueOf(mode.byteValue())); // 4 data.put("v", Integer.valueOf(version.shortValue())); // 4 data.put("r", running); data.put("b", bootstrapped); data.put("w", waiting); data.put("e", enabled); MojitoDHT dht = getMojitoDHT(); if (dht != null) { data.put("s", dht.size().toByteArray()); // 3 RouteTable routeTable = dht.getRouteTable(); Contact localNode = routeTable.getLocalNode(); data.put("id", localNode.getNodeID().getBytes()); // 20 } return data; } }; @InspectionPoint("dht contacts") public Inspectable contacts = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); addVersion(data); MojitoDHT dht = getMojitoDHT(); if (dht != null) { RouteTable routeTable = dht.getRouteTable(); synchronized(routeTable) { double local = getDoubleKUID(routeTable.getLocalNode().getNodeID()); List<Double> activeContacts = getDouble(routeTable.getActiveContacts()); data.put("acc", StatsUtils.quickStatsDouble(activeContacts).getMap()); // 5*20 + 4 data.put("accx",StatsUtils.quickStatsDouble(getXorDistances(local, activeContacts)).getMap()); // 5*20 + 4 List<Double> cachedContacts = getDouble(routeTable.getCachedContacts()); data.put("ccc", StatsUtils.quickStatsDouble(cachedContacts).getMap()); // 5*20 + 4 data.put("cccx", StatsUtils.quickStatsDouble(getXorDistances(local, cachedContacts)).getMap()); // 5*20 + 4 List<Double> activeIps = new ArrayList<Double>(); List<Double> cachedIps = new ArrayList<Double>(); List<Double> allIps = new ArrayList<Double>(); for (Contact node : routeTable.getActiveContacts()) { double masked = getUnsignedMaskedAddress(node); activeIps.add(masked); allIps.add(masked); } for (Contact node : routeTable.getCachedContacts()) { double masked = getUnsignedMaskedAddress(node); cachedIps.add(masked); allIps.add(masked); } data.put("aips", StatsUtils.quickStatsDouble(activeIps).getMap()); data.put("cips", StatsUtils.quickStatsDouble(cachedIps).getMap()); data.put("allips", StatsUtils.quickStatsDouble(allIps).getMap()); } } return data; } }; @InspectionPoint("dht route table dump") public Inspectable RTDump = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); addVersion(data); MojitoDHT dht = getMojitoDHT(); if (dht != null) { RouteTable routeTable = dht.getRouteTable(); synchronized(routeTable) { data.put("active",routeTable.getActiveContacts()); data.put("cached",routeTable.getCachedContacts()); } } return data; } }; @InspectionPoint("dht route table class C networks") public Inspectable routeTableTop10Networks = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); addVersion(data); MojitoDHT dht = getMojitoDHT(); if (dht != null) { RouteTable routeTable = dht.getRouteTable(); synchronized(routeTable) { data.put("ta", getTopNetworks(routeTable.getActiveContacts(), 10)); data.put("tc", getTopNetworks(routeTable.getCachedContacts(), 10)); } } return data; } private byte [] getTopNetworks(Collection<? extends Contact> nodes, int count) { // Masked IP -> Count ClassCNetworks classCNetworks = new ClassCNetworks(); for (Contact node : nodes) { InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); classCNetworks.add(addr, 1); } // Return the Top IPs and their count return classCNetworks.getTopInspectable(10); } }; @InspectionPoint("dht buckets") public Inspectable buckets = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); addVersion(data); MojitoDHT dht = getMojitoDHT(); if (dht != null) { RouteTable routeTable = dht.getRouteTable(); synchronized(routeTable) { double local = getDoubleKUID(routeTable.getLocalNode().getNodeID()); Collection<Bucket> buckets = routeTable.getBuckets(); List<Double> depths = new ArrayList<Double>(buckets.size()); List<Double> sizes = new ArrayList<Double>(buckets.size()); List<Double> kuids = new ArrayList<Double>(buckets.size()); List<Double> times = new ArrayList<Double>(buckets.size()); double fresh = 0; long now = System.currentTimeMillis(); for (Bucket bucket : buckets) { depths.add((double)bucket.getDepth()); sizes.add((double)bucket.size()); kuids.add(getDoubleKUID(bucket.getBucketID())); times.add((double)(now - bucket.getTimeStamp())); if (!bucket.isRefreshRequired()) fresh++; } // bucket kuid distribution *should* be similar to the others, but is it? data.put("bk", StatsUtils.quickStatsDouble(kuids).getMap()); // 5*20 + 4 data.put("bkx", StatsUtils.quickStatsDouble(getXorDistances(local, kuids)).getMap()); // 5*20 + 4 data.put("bd", StatsUtils.quickStatsDouble(depths).getMap()); // 5*(should be one byte) + 4 data.put("bs", StatsUtils.quickStatsDouble(sizes).getMap()); // 5*(should be one byte) + 4 data.put("bt", StatsUtils.quickStatsDouble(times).getMap()); // 5*(should be one byte) + 4 data.put("bfr", (int)(100 * fresh / buckets.size())); // fresh buckets % } } return data; } }; @InspectionPoint("dht buckets detailed") public Inspectable bucketDetail = new Inspectable() { @Override public Object inspect() { List<Map<String, Object>> data = new ArrayList<Map<String, Object>>(); MojitoDHT dht = getMojitoDHT(); if (dht != null) { RouteTable routeTable = dht.getRouteTable(); synchronized(routeTable) { Collection<Bucket> buckets = routeTable.getBuckets(); for (Bucket bucket : buckets) { Map<String, Object> detail = new HashMap<String, Object>(); detail.put("i", bucket.getBucketID().getBytes()); detail.put("d", bucket.getDepth()); detail.put("t", System.currentTimeMillis() - bucket.getTimeStamp()); detail.put("a", bucket.getActiveSize()); detail.put("c", bucket.getCacheSize()); detail.put("f", !bucket.isRefreshRequired()); data.add(detail); } } } return data; } }; @InspectionPoint("dht database") public Inspectable database = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); addVersion(data); MojitoDHT dht = getMojitoDHT(); if (dht != null) { Database database = dht.getDatabase(); Double local = getDoubleKUID(dht.getLocalNodeID()); List<Double> primaryKeys = null; List<Double> requestLoads = null; List<Double> distanceToLoad = null; synchronized (database) { data.put("dvc", Integer.valueOf(database.getValueCount())); // 4 Set<KUID> keys = database.keySet(); primaryKeys = new ArrayList<Double>(keys.size()); requestLoads = new ArrayList<Double>(keys.size()); distanceToLoad = new ArrayList<Double>(keys.size()); for (KUID primaryKey : keys) { Double big = getDoubleKUID(primaryKey); double load = database.getRequestLoad(primaryKey, false); primaryKeys.add(big); requestLoads.add(load); if (local == big) continue; big = (double)(local.longValue() ^ big.longValue()); distanceToLoad.add(big - load * Integer.MAX_VALUE); } } List<Double> storedXorDistances = getXorDistances(local, primaryKeys); data.put("dsk", StatsUtils.quickStatsDouble(primaryKeys).getMap()); // 5*20 + 4 data.put("drl", StatsUtils.quickStatsDouble(requestLoads).getMap()); // 5*4 + 4 data.put("dskx", StatsUtils.quickStatsDouble(storedXorDistances).getMap()); // 5*20 + 4 data.put("dxlt", StatsUtils.quickStatsDouble(distanceToLoad).getTTestMap()); } return data; } }; @InspectionPoint("dht database top 10 keys") public Inspectable databaseTop10Keys = new Inspectable() { @Override public Object inspect() { List<byte[]>ret = new ArrayList<byte[]>(); MojitoDHT dht = getMojitoDHT(); if (dht != null) { Database database = dht.getDatabase(); Map<Double, KUID> popularKeys = new TreeMap<Double,KUID>(Comparators.inverseDoubleComparator()); synchronized(database) { Set<KUID> keys = database.keySet(); for (KUID primaryKey : keys) { popularKeys.put((double)database.getRequestLoad(primaryKey, false), primaryKey); } } // load -> key for(double load : popularKeys.keySet()) { if (ret.size() >= 20) break; ret.add(BigInteger.valueOf(Double.doubleToLongBits(load)).toByteArray()); ret.add(popularKeys.get(load).getBytes()); } } return ret; } }; @InspectionPoint("dht internal format stats") public Inspectable mojitoStats = new Inspectable() { @Override public Object inspect() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer w = new OutputStreamWriter(baos, Charset.forName("UTF-8")); try { MojitoDHT dht = getMojitoDHT(); if(dht != null) { DHTStats stats = dht.getDHTStats(); stats.dump(w, false); w.flush(); return baos.toByteArray(); } else { return null; } } catch (IOException impossible) { return impossible.getMessage(); } } }; /** Histograms of the stored keys with various detail */ @InspectionPoint("dht database 10 histogram") public Inspectable database10StoredHist = new DBHist(10); @InspectionPoint("dht database 100 histogram") public Inspectable database100StoredHist = new DBHist(100); // ~ 400 bytes uncompressed @InspectionPoint("dht database 500 histogram") public Inspectable database500StoredHist = new DBHist(500); // ~ 2kb uncompressed } /** * Inspectable that returns a histogram of the stored keys in the * database with specified accuracy. */ private class DBHist implements Inspectable { private final int breaks; /** * @param breaks how many breaks should the histogram have. */ DBHist(int breaks) { this.breaks = breaks; } @Override public Object inspect() { MojitoDHT dht = getMojitoDHT(); if (dht != null) { Database database = dht.getDatabase(); List<BigInteger> primaryKeys; synchronized(database) { Set<KUID> keys = database.keySet(); primaryKeys = new ArrayList<BigInteger>(keys.size()); for (KUID primaryKey : keys) primaryKeys.add(primaryKey.toBigInteger()); } return StatsUtils.getHistogramBigInt(primaryKeys, breaks); } return Collections.emptyList(); } } /** * @return a list of XOR distances from a provided node */ private static List<Double> getXorDistances(Double local, List<Double> others) { List<Double> distances = new ArrayList<Double>(others.size()); for (Double l : others) { // Skip the local Node! if (l != local) distances.add((double)((local.longValue() ^ l.longValue()))); } return distances; } /** * @return a list of big integers from a collection of contacts */ private static List<Double> getDouble(Collection <? extends Contact> nodes) { List<Double> doubles = new ArrayList<Double>(nodes.size()); for (Contact node : nodes) doubles.add(getDoubleKUID(node.getNodeID())); return doubles; } /** * @return the 32 most significant bits from a KUID as a double primitive. */ private static double getDoubleKUID(KUID k) { byte [] b = k.getBytes(); long x = b[0]; for (int i = 1; i < 4; i++) { x >>>= 8; x |= b[i]; } return x; } /** * Returns the masked contact address of the given Contact as an * unsigned int. */ private static double getUnsignedMaskedAddress(Contact node) { InetSocketAddress addr = (InetSocketAddress)node.getContactAddress(); long masked = NetworkUtils.getClassC(addr.getAddress()) & 0xFFFFFFFFL; return masked; } /** * Inspection point that tells us how long did the last bootstrap take. */ private static class BootstrapTimer implements DHTEventListener, Inspectable { private long start, stop; @Override public synchronized Object inspect() { Map<String,Object> ret = new HashMap<String,Object>(); ret.put("ver",1); ret.put("start",start); ret.put("stop",stop); return ret; } public synchronized void handleDHTEvent(DHTEvent evt) { if (evt.getType() == DHTEvent.Type.STARTING) start = System.currentTimeMillis(); else if (evt.getType() == DHTEvent.Type.CONNECTED && start != 0) stop = System.currentTimeMillis(); } } static class TimeInspector<T> extends DHTFutureAdapter<T> { private final long startTime = System.currentTimeMillis(); private final TimeValuesInspectable values; public TimeInspector(TimeValuesInspectable values) { this.values = values; } public int getCurrentDuration() { return ByteUtils.long2int(System.currentTimeMillis() - startTime); } public void count(boolean success) { int time = getCurrentDuration(); int index = getIndex(time); if (success) { values.successes.count(index); } else { values.failures.count(index); } synchronized (values) { if (success) { values.maxSuccessful = Math.max(values.maxSuccessful, time); } else { values.maxFailed = Math.max(values.maxFailed, time); } } } public int getIndex(int time) { int i; if (time == 0) { i = 0; } else if (time < 5000 && time > 0) { i = time / 500 + 1; } else if (time < 10000) { i = (time - 5000) / 1000 + 11; } else if (time < 60000) { i = (time - 10000) / 5000 + 16; } else if (time < 180000) { i = (time - 60000) / 10000 + 26; } else if (time < 360000) { i = (time - 180000) / 30000 + 38; } else { i = 44; } return i; } } static class TimeValuesInspectable implements Inspectable { final InspectionHistogram<Integer> successes = new InspectionHistogram<Integer>(); final InspectionHistogram<Integer> failures = new InspectionHistogram<Integer>(); volatile int maxSuccessful = 0; volatile int maxFailed = 0; @Override public synchronized Object inspect() { Map<String, Object> values = new HashMap<String, Object>(); values.put("success hist", successes.inspect()); values.put("failure hist", failures.inspect()); values.put("max success", maxSuccessful); values.put("max failure", maxFailed); return values; } } }