package com.limegroup.gnutella; import java.util.HashSet; import java.util.Set; import com.limegroup.gnutella.messages.PingReply; /*************************************************************************** * Horizon statistics. We measure the horizon by looking at all ping replies * coming per connection--regardless whether they are in response to pings * originating from us. To avoid double-counting ping replies, we keep a * set of Endpoint's around, bounded in size to save memory. This scheme is * robust in the face of pong throttling. Note however that we cannot * discern pings from multiple hosts with the same private address. But you * are probably not interested in such hosts anyway. * * The problem with this scheme is that the numbers tend to grow without * bound, even if hosts leave the network. Ideally we'd like to clear all * pongs that are more than HORIZON_UPDATE_TIME milliseconds old, but that's * difficult to implement efficiently. As a simplication, we periodically * clear the set of pongs every HORIZON_UPDATE_TIME milliseconds (by calling * updateHorizonStats) and start recounting. While we are recounting, we * return the last size of the set. So pongs in the set are * HORIZON_UPDATE_TIME to 2*HORIZON_UPDATE_TIME milliseconds old. * * LOCKING: obtain this' monitor **************************************************************************/ public final class HorizonCounter { private static final HorizonCounter INSTANCE = new HorizonCounter(); /** The approximate time to expire pongs, in milliseconds. */ static long HORIZON_UPDATE_TIME=10*60*1000; //10 minutes /** The last time refreshHorizonStats was called. */ private long _lastRefreshHorizonTime=System.currentTimeMillis(); /** True iff refreshHorizonStats has been called. */ private boolean _refreshedHorizonStats=false; /** The max number of pongs to save. */ private static final int MAX_PING_REPLIES=4000; /** The endpoints of pongs seen before. Eliminates duplicates. */ private Set /* of Endpoint */ _pongs=new HashSet(); /** The size of _pingReplies before updateHorizonStats was called. */ private long _totalHorizonFileSize=0; private long _numHorizonFiles=0; private long _numHorizonHosts=0; /** INVARIANT: _nextTotalHorizonFileSize==_pingReplies.size() */ private long _nextTotalHorizonFileSize=0; private long _nextNumHorizonFiles=0; private long _nextNumHorizonHosts=0; public static HorizonCounter instance() { return INSTANCE; } public synchronized void addPong(PingReply pong) { //Have we already seen a ping from this hosts? Endpoint host=new Endpoint(pong.getAddress(), pong.getPort()); if (_pongs.size()<MAX_PING_REPLIES && _pongs.add(host)) { //Nope. Increment numbers. _nextTotalHorizonFileSize += pong.getKbytes(); _nextNumHorizonFiles += pong.getFiles(); _nextNumHorizonHosts++; } } public synchronized void refresh() { //Makes sure enough time has elapsed. long now=System.currentTimeMillis(); long elapsed=now-_lastRefreshHorizonTime; if (elapsed<HORIZON_UPDATE_TIME) return; _lastRefreshHorizonTime=now; //Ok, now update stats. _numHorizonHosts=_nextNumHorizonHosts; _numHorizonFiles=_nextNumHorizonFiles; _totalHorizonFileSize=_nextTotalHorizonFileSize; _nextNumHorizonHosts=0; _nextNumHorizonFiles=0; _nextTotalHorizonFileSize=0; _pongs.clear(); _refreshedHorizonStats=true; } /** Returns the number of hosts reachable from me. */ public synchronized long getNumHosts() { if (_refreshedHorizonStats) return _numHorizonHosts; else return _nextNumHorizonHosts; } /** Returns the number of files reachable from me. */ public synchronized long getNumFiles() { if (_refreshedHorizonStats) return _numHorizonFiles; else return _nextNumHorizonFiles; } /** Returns the size of all files reachable from me. */ public synchronized long getTotalFileSize() { if (_refreshedHorizonStats) return _totalHorizonFileSize; else return _nextTotalHorizonFileSize; } }