/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.node; import static java.util.concurrent.TimeUnit.DAYS; import java.util.Arrays; import freenet.node.NodeStats.PeerLoadStats; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; /** * Track average round-trip time for each peer node, get a geometric mean. */ public class NodePinger implements Runnable { private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } private final Node node; private volatile double meanPing = 0; public static final double CRAZY_MAX_PING_TIME = 365.25 * DAYS.toMillis(1); NodePinger(Node n) { this.node = n; } void start() { run(); } @Override public void run() { try { PeerNode[] peers = null; synchronized(node.peers) { peers = node.peers.connectedPeers(); } if(peers == null || peers.length == 0) return; // Now we don't have to care about synchronization anymore recalculateMean(peers); capacityInputRealtime.calculate(peers); capacityInputBulk.calculate(peers); capacityOutputRealtime.calculate(peers); capacityOutputBulk.calculate(peers); } finally { // Requeue after to avoid exacerbating overload node.getTicker().queueTimedJob(this, 200); } } /** Recalculate the mean ping time */ private void recalculateMean(PeerNode[] peers) { if(peers.length == 0) return; meanPing = calculateMedianPing(peers); if(logMINOR) Logger.minor(this, "Median ping: "+meanPing); } private double calculateMedianPing(PeerNode[] peers) { double[] allPeers = new double[peers.length]; for(int i = 0; i < peers.length; i++) { PeerNode peer = peers[i]; allPeers[i] = peer.averagePingTime(); } Arrays.sort(allPeers); return allPeers[peers.length / 2]; } public double averagePingTime() { return meanPing; } final CapacityChecker capacityInputRealtime = new CapacityChecker(true, true); final CapacityChecker capacityInputBulk = new CapacityChecker(true, false); final CapacityChecker capacityOutputRealtime = new CapacityChecker(false, true); final CapacityChecker capacityOutputBulk = new CapacityChecker(false, false); class CapacityChecker { final boolean isInput; final boolean isRealtime; private double min; private double median; private double firstQuartile; private double lastQuartile; private double max; CapacityChecker(boolean input, boolean realtime) { isInput = input; isRealtime = realtime; } void calculate(PeerNode[] peers) { double[] allPeers = new double[peers.length]; int x = 0; for(PeerNode peer : peers) { PeerLoadStats stats = peer.outputLoadTracker(isRealtime).getLastIncomingLoadStats(); if(stats == null) continue; allPeers[x++] = stats.peerLimit(isInput); } if(x != peers.length) { allPeers = Arrays.copyOf(allPeers, x); } Arrays.sort(allPeers); if(x == 0) return; synchronized(this) { min = allPeers[0]; median = allPeers[x / 2]; firstQuartile = allPeers[x / 4]; lastQuartile = allPeers[(x * 3) / 4]; max = allPeers[x - 1]; if(logMINOR) Logger.minor(this, "Quartiles for peer capacities: "+(isInput?"input ":"output ")+(isRealtime?"realtime: ":"bulk: ")+Arrays.toString(getQuartiles())); } } synchronized double[] getQuartiles() { return new double[] { min, firstQuartile, median, lastQuartile, max }; } /** Get min(half the median, first quartile). Used as a threshold. */ synchronized double getThreshold() { return Math.min(median/2, firstQuartile); } } public double capacityThreshold(boolean isRealtime, boolean isInput) { return capacityChecker(isRealtime, isInput).getThreshold(); } private CapacityChecker capacityChecker(boolean isRealtime, boolean isInput) { if(isRealtime) { return isInput ? capacityInputRealtime : capacityOutputRealtime; } else { return isInput ? capacityInputBulk: capacityOutputBulk; } } }