diff --git a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java index be96a875d9c9dcfd525aa0354372763d80963028..d92e8b49badc866bf795f4575308c8cf1be6c6b7 100644 --- a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java +++ b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java @@ -74,10 +74,10 @@ public class CapacityCalculator extends Calculator { else if (profile.getTunnelHistory().getLastRejectedProbabalistic() > now - 5*60*1000) capacity -= _context.random().nextInt(5); + capacity += profile.getCapacityBonus(); if (capacity < 0) capacity = 0; - capacity += profile.getReliabilityBonus(); return capacity; } diff --git a/router/java/src/net/i2p/router/peermanager/PeerProfile.java b/router/java/src/net/i2p/router/peermanager/PeerProfile.java index c8c51ba5eaab34901fc5f6c800d20147af44bc65..31596741b3fb67289efc4f095144868479d639e6 100644 --- a/router/java/src/net/i2p/router/peermanager/PeerProfile.java +++ b/router/java/src/net/i2p/router/peermanager/PeerProfile.java @@ -174,7 +174,7 @@ public class PeerProfile { * written to disk to affect how the algorithm ranks capacity. Negative values are * penalties */ - public double getCapacityBonus() { return _capacityBonus; } + public long getCapacityBonus() { return _capacityBonus; } public void setCapacityBonus(long bonus) { _capacityBonus = bonus; } /** diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java index b5305d6401f7853ba4890ecdff5dd3201861c429..fd88c406bbc70cd9e641cfc480f4ff70ea7e03a6 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java +++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java @@ -103,8 +103,8 @@ class ProfilePersistenceHelper { buf.append("# Groups: ").append(groups).append(NL); buf.append("########################################################################").append(NL); buf.append("##").append(NL); - buf.append("# Reliability bonus: used to affect the reliability score after all other calculations are done").append(NL); - buf.append("reliabilityBonus=").append(profile.getReliabilityBonus()).append(NL); + buf.append("# Capacity bonus: used to affect the capacity score after all other calculations are done").append(NL); + buf.append("capacityBonus=").append(profile.getCapacityBonus()).append(NL); buf.append("# Integration bonus: used to affect the integration score after all other calculations are done").append(NL); buf.append("integrationBonus=").append(profile.getIntegrationBonus()).append(NL); buf.append("# Speed bonus: used to affect the speed score after all other calculations are done").append(NL); @@ -200,7 +200,7 @@ class ProfilePersistenceHelper { return null; } - profile.setReliabilityBonus(getLong(props, "reliabilityBonus")); + profile.setCapacityBonus(getLong(props, "capacityBonus")); profile.setIntegrationBonus(getLong(props, "integrationBonus")); profile.setSpeedBonus(getLong(props, "speedBonus")); diff --git a/router/java/src/net/i2p/router/peermanager/SpeedCalculator.java b/router/java/src/net/i2p/router/peermanager/SpeedCalculator.java index c2d139dc4bb23e72b156402e200425fb3419bf94..a7fc9334778883cfadcbff336fc59137b9c970ca 100644 --- a/router/java/src/net/i2p/router/peermanager/SpeedCalculator.java +++ b/router/java/src/net/i2p/router/peermanager/SpeedCalculator.java @@ -1,403 +1,27 @@ package net.i2p.router.peermanager; -import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; -import net.i2p.stat.Rate; -import net.i2p.stat.RateStat; -import net.i2p.util.Log; /** * Quantify how fast the peer is - how fast they respond to our requests, how fast * they pass messages on, etc. This should be affected both by their bandwidth/latency, - * as well as their load. The essence of the current algorithm is to determine - * approximately how many 2KB messages the peer can pass round trip within a single - * minute - not based just on itself though, but including the delays of other peers - * in the tunnels. As such, more events make it more accurate. + * as well as their load. + * + * IMPORTANT - + * This code has been through many iterations, and some versions were quite complex. + * If you are considering changes, review the change control history, and + * see the previous versions in change control to get 400+ lines of old code. * */ public class SpeedCalculator extends Calculator { - private Log _log; - private RouterContext _context; - - /** - * minimum number of events to use a particular period's data. If this many - * events haven't occurred in the period yet, the next largest period is tried. - */ - public static final String PROP_EVENT_THRESHOLD = "speedCalculator.eventThreshold"; - public static final int DEFAULT_EVENT_THRESHOLD = 50; - /** should the calculator use instantaneous rates, or period averages? */ - public static final String PROP_USE_INSTANTANEOUS_RATES = "speedCalculator.useInstantaneousRates"; - public static final boolean DEFAULT_USE_INSTANTANEOUS_RATES = false; - /** should the calculator use tunnel test time only, or include all data? */ - public static final String PROP_USE_TUNNEL_TEST_ONLY = "speedCalculator.useTunnelTestOnly"; - public static final boolean DEFAULT_USE_TUNNEL_TEST_ONLY = false; public SpeedCalculator(RouterContext context) { - _context = context; - _log = context.logManager().getLog(SpeedCalculator.class); } public double calc(PeerProfile profile) { - if (true) // measures 1 minute throughput of individual tunnels - return profile.getPeakTunnel1mThroughputKBps()*1024d; - if (true) // measures throughput of individual tunnels - return profile.getPeakTunnelThroughputKBps()*1024d; - if (true) // measures throughput across all tunnels - return profile.getPeakThroughputKBps()*1024d; - if (true) - return calcAverage(profile); - long threshold = getEventThreshold(); - boolean tunnelTestOnly = getUseTunnelTestOnly(); - - long period = 10*60*1000; - long events = getEventCount(profile, period, tunnelTestOnly); - if (events < threshold) { - period = 60*60*1000l; - events = getEventCount(profile, period, tunnelTestOnly); - if (events < threshold) { - period = 24*60*60*1000; - events = getEventCount(profile, period, tunnelTestOnly); - if (events < threshold) { - period = -1; - events = getEventCount(profile, period, tunnelTestOnly); - } - } - } - - double measuredRoundTripTime = getMeasuredRoundTripTime(profile, period, tunnelTestOnly); - double measuredRTPerMinute = 0; - if (measuredRoundTripTime > 0) - measuredRTPerMinute = (60000.0d / measuredRoundTripTime); - - double estimatedRTPerMinute = 0; - double estimatedRoundTripTime = 0; - if (!tunnelTestOnly) { - estimatedRoundTripTime = getEstimatedRoundTripTime(profile, period); - if (estimatedRoundTripTime > 0) - estimatedRTPerMinute = (60000.0d / estimatedRoundTripTime); - } - - double estimateFactor = getEstimateFactor(threshold, events); - double rv = (1-estimateFactor)*measuredRTPerMinute + (estimateFactor)*estimatedRTPerMinute; - - long slowCount = 0; - RateStat rs = profile.getTunnelTestResponseTimeSlow(); - if (rs != null) { - Rate r = rs.getRate(period); - if (r != null) - slowCount = r.getCurrentEventCount(); - } - long fastCount = 0; - rs = profile.getTunnelTestResponseTime(); - if (rs != null) { - Rate r = rs.getRate(period); - if (r != null) - fastCount = r.getCurrentEventCount(); - } - double slowPct = 0; - if (fastCount > 0) - slowPct = slowCount / fastCount; - else - rv /= 100; // if there are no tunnel tests, weigh against it - if (slowPct > 0.01) // if 1% of the traffic is dogshit slow, the peer probably sucks - rv /= 100.0*slowPct; - - rv = adjust(period, rv); - - if (_log.shouldLog(Log.DEBUG)) { - _log.debug("\n\nrv: " + rv + " events: " + events + " threshold: " + threshold + " period: " + period + " useTunnelTestOnly? " + tunnelTestOnly + "\n" - + "measuredRTT: " + measuredRoundTripTime + " measured events per minute: " + measuredRTPerMinute + "\n" - + "estimateRTT: " + estimatedRoundTripTime + " estimated events per minute: " + estimatedRTPerMinute + "\n" - + "slow count: " + slowCount + " fast count: " + fastCount + "\n" - + "estimateFactor: " + estimateFactor + "\n" - + "for peer: " + profile.getPeer().toBase64()); - } - - rv += profile.getSpeedBonus(); - return rv; - } - - private double calcAverage(PeerProfile profile) { - double avg = profile.getTunnelTestTimeAverage(); - if (avg == 0) - return 0.0; - else - return (60.0*1000.0) / avg; - } - - private double adjust(long period, double value) { - switch ((int)period) { - case 10*60*1000: - return value; - case 60*60*1000: - return value * 0.75; - case 24*60*60*1000: - return value * 0.1; - default: - return value * 0.01; - } - } - - /** - * How much do we want to prefer the measured values more than the estimated - * values, as a fraction. The value 1 means ignore the measured values, while - * the value 0 means ignore the estimate, and everything inbetween means, well - * everything inbetween. - * - */ - private double getEstimateFactor(long eventThreshold, long numEvents) { - if (true) return 0.0d; // never use the estimate - if (numEvents > eventThreshold) - return 0.0d; - else - return numEvents / eventThreshold; - } - - /** - * How many measured events do we have for the given period? If the period is negative, - * return the lifetime events. - * - */ - private long getEventCount(PeerProfile profile, long period, boolean tunnelTestOnly) { - if (period < 0) { - Rate dbResponseRate = profile.getDbResponseTime().getRate(60*60*1000l); - Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(60*60*1000l); - Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(60*60*1000l); - - long dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getLifetimeEventCount(); - long tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getLifetimeEventCount(); - long tunnelTests = tunnelTestRate.getLifetimeEventCount(); - - return dbResponses + tunnelResponses + tunnelTests; - } else { - Rate dbResponseRate = profile.getDbResponseTime().getRate(period); - Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(period); - Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(period); - - long dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getCurrentEventCount() + dbResponseRate.getLastEventCount(); - long tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getCurrentEventCount() + tunnelResponseRate.getLastEventCount(); - long tunnelTests = tunnelTestRate.getCurrentEventCount() + tunnelTestRate.getLastEventCount(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("TunnelTests for period " + period + ": " + tunnelTests + - " last: " + tunnelTestRate.getLastEventCount() + " lifetime: " + - tunnelTestRate.getLifetimeEventCount()); - - return dbResponses + tunnelResponses + tunnelTests; - } - } - - /** - * Retrieve the average measured round trip time within the period specified (including - * db responses, tunnel create responses, and tunnel tests). If the period is negative, - * it uses the lifetime stats. In addition, it weights each of those three measurements - * equally according to their event count (e.g. 4 dbResponses @ 10 seconds and 1 tunnel test - * at 5 seconds will leave the average at 9 seconds) - * - */ - private double getMeasuredRoundTripTime(PeerProfile profile, long period, boolean tunnelTestOnly) { - double activityTime = 0; - double rtt = 0; - double dbResponseTime = 0; - double tunnelResponseTime = 0; - double tunnelTestTime = 0; - - long dbResponses = 0; - long tunnelResponses = 0; - long tunnelTests = 0; - - long events = 0; - - if (period < 0) { - Rate dbResponseRate = profile.getDbResponseTime().getRate(60*60*1000l); - Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(60*60*1000l); - Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(60*60*1000l); - - dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getLifetimeEventCount(); - tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getLifetimeEventCount(); - tunnelTests = tunnelTestRate.getLifetimeEventCount(); - - dbResponseTime = tunnelTestOnly ? 0 : dbResponseRate.getLifetimeAverageValue(); - tunnelResponseTime = tunnelTestOnly ? 0 : tunnelResponseRate.getLifetimeAverageValue(); - tunnelTestTime = tunnelTestRate.getLifetimeAverageValue(); - - events = dbResponses + tunnelResponses + tunnelTests; - if (events <= 0) return 0; - activityTime = (dbResponses*dbResponseTime + tunnelResponses*tunnelResponseTime + tunnelTests*tunnelTestTime); - rtt = activityTime / events; - } else { - Rate dbResponseRate = profile.getDbResponseTime().getRate(period); - Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(period); - Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(period); - - dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getCurrentEventCount() + dbResponseRate.getLastEventCount(); - tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getCurrentEventCount() + tunnelResponseRate.getLastEventCount(); - tunnelTests = tunnelTestRate.getCurrentEventCount() + tunnelTestRate.getLastEventCount(); - - if (!tunnelTestOnly) { - dbResponseTime = avg(dbResponseRate); - tunnelResponseTime = avg(tunnelResponseRate); - } - tunnelTestTime = avg(tunnelTestRate); - - events = dbResponses + tunnelResponses + tunnelTests; - if (events <= 0) return 0; - activityTime = (dbResponses*dbResponseTime + tunnelResponses*tunnelResponseTime + tunnelTests*tunnelTestTime); - rtt = activityTime / events; - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("\nMeasured response time for " + profile.getPeer().toBase64() + " over " - + DataHelper.formatDuration(period) + " with activityTime of " + activityTime - + ": " + rtt + "\nover " + events + " events (" - + dbResponses + " dbResponses, " + tunnelResponses + " tunnelResponses, " - + tunnelTests + " tunnelTests)\ntimes (" - + dbResponseTime + "ms, " + tunnelResponseTime + "ms, " - + tunnelTestTime + "ms respectively)"); - return rtt; - } - - private double avg(Rate rate) { - long events = rate.getCurrentEventCount() + rate.getLastEventCount(); - long time = rate.getCurrentTotalEventTime() + rate.getLastTotalEventTime(); - if ( (events > 0) && (time > 0) ) - return time / events; - else - return 0.0d; - } - - private double getEstimatedRoundTripTime(PeerProfile profile, long period) { - double estSendTime = getEstimatedSendTime(profile, period); - double estRecvTime = getEstimatedReceiveTime(profile, period); - return estSendTime + estRecvTime; - } - - private double getEstimatedSendTime(PeerProfile profile, long period) { - double bps = calcRate(profile.getSendSuccessSize(), period); - if (bps <= 0) - return 0.0d; - else - return 2048.0d / bps; - } - private double getEstimatedReceiveTime(PeerProfile profile, long period) { - double bps = calcRate(profile.getReceiveSize(), period); - if (bps <= 0) - return 0.0d; - else - return 2048.0d / bps; - } - - private double calcRate(RateStat stat, long period) { - Rate rate = stat.getRate(period); - if (rate == null) return 0.0d; - return calcRate(rate, period); - } - - private double calcRate(Rate rate, long period) { - long events = rate.getCurrentEventCount(); - if (events >= 1) { - double ms = rate.getCurrentTotalEventTime(); - double bytes = rate.getCurrentTotalValue(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("calculating rate: ms=" + ((int)ms) + " bytes=" + ((int)bytes)); - if ( (bytes > 0) && (ms > 0) ) { - if (getUseInstantaneousRates()) { - return (bytes * 1000.0d) / ms; - } else { - // period average - return (bytes * 1000.0d) / period; - } - } - } + // measures 1 minute throughput of individual tunnels + double d = (profile.getPeakTunnel1mThroughputKBps()*1024d) + profile.getSpeedBonus(); + if (d >= 0) return d; return 0.0d; } - /** - * What is the minimum number of measured events we want in a period before - * trusting the values? This first checks the router's configuration, then - * the context, and then finally falls back on a static default (100). - * - */ - private long getEventThreshold() { - if (_context.router() != null) { - String threshold = _context.router().getConfigSetting(PROP_EVENT_THRESHOLD); - if (threshold != null) { - try { - return Long.parseLong(threshold); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Event threshold for speed improperly set in the router config [" + threshold + "]", nfe); - } - } - } - String threshold = _context.getProperty(PROP_EVENT_THRESHOLD, ""+DEFAULT_EVENT_THRESHOLD); - if (threshold != null) { - try { - return Long.parseLong(threshold); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Event threshold for speed improperly set in the router environment [" + threshold + "]", nfe); - } - } - return DEFAULT_EVENT_THRESHOLD; - } - - - /** - * Should we use instantaneous rates for the estimated speed, or the period rates? - * This first checks the router's configuration, then the context, and then - * finally falls back on a static default (true). - * - * @return true if we should use instantaneous rates, false if we should use period averages - */ - private boolean getUseInstantaneousRates() { - if (_context.router() != null) { - String val = _context.router().getConfigSetting(PROP_USE_INSTANTANEOUS_RATES); - if (val != null) { - return Boolean.valueOf(val).booleanValue(); - } - } - String val = _context.getProperty(PROP_USE_INSTANTANEOUS_RATES, ""+DEFAULT_USE_INSTANTANEOUS_RATES); - if (val != null) { - return Boolean.valueOf(val).booleanValue(); - } - return DEFAULT_USE_INSTANTANEOUS_RATES; - } - - /** - * Should we only use the measured tunnel testing time, or should we include - * measurements on the db responses and tunnel create responses. This first - * checks the router's configuration, then the context, and then finally falls - * back on a static default (true). - * - * @return true if we should use tunnel test time only, false if we should use all available - */ - private boolean getUseTunnelTestOnly() { - if (_context.router() != null) { - String val = _context.router().getConfigSetting(PROP_USE_TUNNEL_TEST_ONLY); - if (val != null) { - try { - boolean rv = Boolean.getBoolean(val); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("router config said " + PROP_USE_TUNNEL_TEST_ONLY + '=' + val); - return rv; - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel test only for speed improperly set in the router config [" + val + "]", nfe); - } - } - } - String val = _context.getProperty(PROP_USE_TUNNEL_TEST_ONLY, ""+DEFAULT_USE_TUNNEL_TEST_ONLY); - if (val != null) { - try { - boolean rv = Boolean.getBoolean(val); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("router context said " + PROP_USE_TUNNEL_TEST_ONLY + '=' + val); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel test only for speed improperly set in the router environment [" + val + "]", nfe); - } - } - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("no config for " + PROP_USE_TUNNEL_TEST_ONLY + ", using " + DEFAULT_USE_TUNNEL_TEST_ONLY); - return DEFAULT_USE_TUNNEL_TEST_ONLY; - } }