From f2787a8df6b75419d78536a67d261fa8b5b5c423 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Tue, 24 Mar 2020 13:03:40 +0000
Subject: [PATCH] Profiles: Don't decay during first 90 minutes of uptime
 Change decay from .75 twice a day to .84 four times a day; approx. same
 overall decay in a day (.5) Parameterize decay variables for clarity Fix
 multiple NPEs in ProfileOrganizer CLI Other cleanups

---
 .../peermanager/CapacityCalculator.java       | 11 +++-
 .../i2p/router/peermanager/PeerManager.java   |  7 ++-
 .../i2p/router/peermanager/PeerProfile.java   | 58 ++++++++++++++-----
 .../router/peermanager/ProfileOrganizer.java  | 41 ++++++++-----
 .../peermanager/ProfilePersistenceHelper.java |  2 +
 5 files changed, 84 insertions(+), 35 deletions(-)

diff --git a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
index 493e54a8af..326b709d2e 100644
--- a/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
+++ b/router/java/src/net/i2p/router/peermanager/CapacityCalculator.java
@@ -2,6 +2,7 @@ package net.i2p.router.peermanager;
 
 import net.i2p.data.router.RouterInfo;
 import net.i2p.router.RouterContext;
+import net.i2p.router.NetworkDatabaseFacade;
 import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
 import net.i2p.stat.Rate;
 import net.i2p.stat.RateAverages;
@@ -91,9 +92,13 @@ class CapacityCalculator {
 
         // credit non-floodfill to reduce conn limit issues at floodfills
         // TODO only if we aren't floodfill ourselves?
-        RouterInfo ri = context.netDb().lookupRouterInfoLocally(profile.getPeer());
-        if (!FloodfillNetworkDatabaseFacade.isFloodfill(ri))
-            capacity += BONUS_NON_FLOODFILL;
+        // null for tests
+        NetworkDatabaseFacade ndb =  context.netDb();
+        if (ndb != null) {
+            RouterInfo ri = ndb.lookupRouterInfoLocally(profile.getPeer());
+            if (!FloodfillNetworkDatabaseFacade.isFloodfill(ri))
+                capacity += BONUS_NON_FLOODFILL;
+        }
 
         // a tiny tweak to break ties and encourage closeness, -.25 to +.25
         capacity -= profile.getXORDistance() * (BONUS_XOR / 128);
diff --git a/router/java/src/net/i2p/router/peermanager/PeerManager.java b/router/java/src/net/i2p/router/peermanager/PeerManager.java
index b1448ceced..9dd7fff7ca 100644
--- a/router/java/src/net/i2p/router/peermanager/PeerManager.java
+++ b/router/java/src/net/i2p/router/peermanager/PeerManager.java
@@ -55,6 +55,8 @@ class PeerManager {
      *  Rate contained in the profile, as the Rates must be coalesced.
      */
     private static final long REORGANIZE_TIME_LONG = 351*1000;
+    /** After first two hours of uptime ~= 246 */
+    static final int REORGANIZES_PER_DAY = (int) (24*60*60*1000L / REORGANIZE_TIME_LONG);
     private static final long STORE_TIME = 19*60*60*1000;
     private static final long EXPIRE_AGE = 3*24*60*60*1000;
     
@@ -119,8 +121,10 @@ class PeerManager {
 
         public void run() {
             long start = System.currentTimeMillis();
+            long uptime = _context.router().getUptime();
+            boolean shouldDecay = uptime > 90*60*1000;
             try {
-                _organizer.reorganize(true);
+                _organizer.reorganize(true, shouldDecay);
             } catch (Throwable t) {
                 _log.log(Log.CRIT, "Error evaluating profiles", t);
             }
@@ -138,7 +142,6 @@ class PeerManager {
                     _log.log(Log.CRIT, "Error storing profiles", t);
                 }
             }
-            long uptime = _context.router().getUptime();
             long delay;
             if (orgtime > 1000 || uptime > 2*60*60*1000)
                 delay = REORGANIZE_TIME_LONG;
diff --git a/router/java/src/net/i2p/router/peermanager/PeerProfile.java b/router/java/src/net/i2p/router/peermanager/PeerProfile.java
index c5102c8630..820bc078d7 100644
--- a/router/java/src/net/i2p/router/peermanager/PeerProfile.java
+++ b/router/java/src/net/i2p/router/peermanager/PeerProfile.java
@@ -7,6 +7,7 @@ import java.util.HashSet;
 import java.util.Set;
 
 import net.i2p.data.Hash;
+import net.i2p.router.CommSystemFacade;
 import net.i2p.router.RouterContext;
 import net.i2p.stat.RateStat;
 import net.i2p.util.Log;
@@ -86,8 +87,15 @@ public class PeerProfile {
     /** total number of bytes pushed through a single tunnel in a 1 minute period */
     private final float _peakTunnel1mThroughput[] = new float[THROUGHPUT_COUNT];
     /** periodically cut the measured throughput values */
-    private static final int DROP_PERIOD_MINUTES = 120;
-    private static final float DEGRADE_FACTOR = 0.75f;
+    private static final int DEGRADES_PER_DAY = 4;
+    // one in this many times, ~= 61
+    private static final int DEGRADE_PROBABILITY = PeerManager.REORGANIZES_PER_DAY / DEGRADES_PER_DAY;
+    private static final double TOTAL_DEGRADE_PER_DAY = 0.5d;
+    // the goal is to cut an unchanged profile in half in 24 hours.
+    // x**4 = .5; x = 4th root of .5,  x = .5**(1/4), x ~= 0.84
+    private static final float DEGRADE_FACTOR = (float) Math.pow(TOTAL_DEGRADE_PER_DAY, 1.0d / DEGRADES_PER_DAY);
+    //static { System.out.println("Degrade factor is " + DEGRADE_FACTOR); }
+
     private long _lastCoalesceDate = System.currentTimeMillis();
     
     /**
@@ -160,21 +168,33 @@ public class PeerProfile {
     
     /** @since 0.8.11 */
     boolean isEstablished() {
-        return _context.commSystem().isEstablished(_peer);
+        // null for tests
+        CommSystemFacade cs = _context.commSystem();
+        if (cs == null)
+            return false;
+        return cs.isEstablished(_peer);
     }
 
     /** @since 0.8.11 */
     boolean wasUnreachable() {
-        return _context.commSystem().wasUnreachable(_peer);
+        // null for tests
+        CommSystemFacade cs = _context.commSystem();
+        if (cs == null)
+            return false;
+        return cs.wasUnreachable(_peer);
     }
 
     /** @since 0.8.11 */
     boolean isSameCountry() {
-        String us = _context.commSystem().getOurCountry();
+        // null for tests
+        CommSystemFacade cs = _context.commSystem();
+        if (cs == null)
+            return false;
+        String us = cs.getOurCountry();
         return us != null &&
                (_bigCountries.contains(us) ||
                 _context.getProperty(CapacityCalculator.PROP_COUNTRY_BONUS) != null) &&
-               us.equals(_context.commSystem().getCountry(_peer));
+               us.equals(cs.getCountry(_peer));
     }
 
     /**
@@ -212,7 +232,7 @@ public class PeerProfile {
         long before = _context.clock().now() - period;
         return getLastHeardFrom() < before ||
                getLastSendSuccessful() < before ||
-             _context.commSystem().isEstablished(_peer);
+               isEstablished();
     }
     
     
@@ -437,6 +457,8 @@ public class PeerProfile {
     }
 
     /**
+     * This is the speed value
+     *
      * @return the average of the three fastest one-minute data transfers, on a per-tunnel basis,
      *         through this peer. Ever. Except that the peak values are cut in half
      *         periodically by coalesceThroughput().
@@ -523,10 +545,12 @@ public class PeerProfile {
         _expandedDB = true;
     }
 
-    private void coalesceThroughput() {
+    private void coalesceThroughput(boolean decay) {
         long now = System.currentTimeMillis();
         long measuredPeriod = now - _lastCoalesceDate;
         if (measuredPeriod >= 60*1000) {
+            // so we don't call random() twice
+            boolean shouldDecay =  decay && _context.random().nextInt(DEGRADE_PROBABILITY) <= 0;
             long tot = _peakThroughputCurrentTotal;
             float lowPeak = _peakThroughput[THROUGHPUT_COUNT-1];
             if (tot > lowPeak) {
@@ -539,7 +563,7 @@ public class PeerProfile {
                     }
                 }
             } else {
-                if (_context.random().nextInt(DROP_PERIOD_MINUTES) <= 0) {
+                if (shouldDecay) {
                     for (int i = 0; i < THROUGHPUT_COUNT; i++)
                         _peakThroughput[i] *= DEGRADE_FACTOR;
                 }
@@ -547,7 +571,7 @@ public class PeerProfile {
             
             // we degrade the tunnel throughput here too, regardless of the current
             // activity
-            if (_context.random().nextInt(DROP_PERIOD_MINUTES) <= 0) {
+            if (shouldDecay) {
                 for (int i = 0; i < THROUGHPUT_COUNT; i++) {
                     _peakTunnelThroughput[i] *= DEGRADE_FACTOR;
                     _peakTunnel1mThroughput[i] *= DEGRADE_FACTOR;
@@ -558,11 +582,13 @@ public class PeerProfile {
         }
     }
     
-    /** update the stats and rates (this should be called once a minute) */
-    public void coalesceStats() {
+    /**
+     *  Update the stats and rates. This is only called by addProfile()
+     */
+    void coalesceStats() {
         if (!_expanded) return;
 
-        coalesceOnly();
+        coalesceOnly(false);
         updateValues();
         
         if (_log.shouldLog(Log.DEBUG))
@@ -573,7 +599,7 @@ public class PeerProfile {
      *  Caller must next call updateValues()
      *  @since 0.9.4
      */
-    void coalesceOnly() {
+    void coalesceOnly(boolean shouldDecay) {
     	_coalescing = true;
     	
     	//_receiveSize.coalesceStats();
@@ -587,7 +613,7 @@ public class PeerProfile {
     		_dbHistory.coalesceStats();
     	}
     	
-    	coalesceThroughput();
+    	coalesceThroughput(shouldDecay);
     	
     	_speedValueNew = calculateSpeed();
     	_capacityValueNew = calculateCapacity();
@@ -604,7 +630,7 @@ public class PeerProfile {
      */
     void updateValues() {
     	if (!_coalescing) // can happen
-    		coalesceOnly();
+    		coalesceOnly(false);
     	_coalescing = false;
     	
     	_speedValue = _speedValueNew;
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
index 4a0aedc858..8c1e4a4954 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
@@ -21,7 +21,9 @@ import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
 import net.i2p.data.router.RouterAddress;
 import net.i2p.data.router.RouterInfo;
+import net.i2p.router.ClientManagerFacade;
 import net.i2p.router.NetworkDatabaseFacade;
+import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
 import net.i2p.router.tunnel.pool.TunnelPeerSelector;
 import net.i2p.router.util.MaskedIPSet;
@@ -782,9 +784,9 @@ public class ProfileOrganizer {
      * this method, but the averages are recalculated.
      *
      */
-    void reorganize() { reorganize(false); }
+    void reorganize() { reorganize(false, false); }
 
-    void reorganize(boolean shouldCoalesce) {
+    void reorganize(boolean shouldCoalesce, boolean shouldDecay) {
         long sortTime = 0;
         int coalesceTime = 0;
         long thresholdTime = 0;
@@ -792,7 +794,9 @@ public class ProfileOrganizer {
         int profileCount = 0;
         int expiredCount = 0;
         
-        long uptime = _context.router().getUptime();
+        // null for main()
+        Router r = _context.router();
+        long uptime = (r != null) ? r.getUptime() : 0L;
         long expireOlderThan = -1;
         if (uptime > 60*60*1000) {
             // dynamically adjust expire time to control memory usage
@@ -807,14 +811,14 @@ public class ProfileOrganizer {
         if (shouldCoalesce) {
             getReadLock();
             try {
+                long coalesceStart = System.currentTimeMillis();
                 for (PeerProfile prof : _strictCapacityOrder) {
                     if ( (expireOlderThan > 0) && (prof.getLastSendSuccessful() <= expireOlderThan) ) {
                         continue;
                     }
-                    long coalesceStart = System.currentTimeMillis();
-                    prof.coalesceOnly();
-                    coalesceTime += (int)(System.currentTimeMillis()-coalesceStart);
+                    prof.coalesceOnly(shouldDecay);
                 }
+                coalesceTime = (int)(System.currentTimeMillis()-coalesceStart);
             } finally {
                 releaseReadLock();
             }
@@ -1394,7 +1398,8 @@ public class ProfileOrganizer {
             // don't allow them in the high-cap pool, what would the point of that be?
             if (_thresholdCapacityValue <= profile.getCapacityValue() &&
                 isSelectable(peer) &&
-                !_context.commSystem().isInStrictCountry(peer)) {
+                // null for tests
+                (_context.commSystem() == null || !_context.commSystem().isInStrictCountry(peer))) {
                 _highCapacityPeers.put(peer, profile);
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("High capacity: \t" + peer);
@@ -1446,8 +1451,12 @@ public class ProfileOrganizer {
      * @return minimum number of peers to be placed in the 'fast' group
      */
     protected int getMinimumFastPeers() {
+        // null for main()
+        ClientManagerFacade cm = _context.clientManager();
+        if (cm == null)
+            return DEFAULT_MAXIMUM_FAST_PEERS;
         int def = Math.min(DEFAULT_MAXIMUM_FAST_PEERS,
-                           (6 *_context.clientManager().listClients().size()) + DEFAULT_MINIMUM_FAST_PEERS - 2);
+                           (6 * cm.listClients().size()) + DEFAULT_MINIMUM_FAST_PEERS - 2);
         return _context.getProperty(PROP_MINIMUM_FAST_PEERS, def);
     }
     
@@ -1484,6 +1493,11 @@ public class ProfileOrganizer {
      * </pre>
      */
     public static void main(String args[]) {
+        if (args.length <= 0) {
+            System.err.println("Usage: profileorganizer file.txt.gz [file2.txt.gz] ...");
+            System.exit(1);
+        }
+
         RouterContext ctx = new RouterContext(null); // new net.i2p.router.Router());
         ProfileOrganizer organizer = new ProfileOrganizer(ctx);
         organizer.setUs(Hash.FAKE_HASH);
@@ -1497,27 +1511,26 @@ public class ProfileOrganizer {
             organizer.addProfile(profile);
         }
         organizer.reorganize();
-        DecimalFormat fmt = new DecimalFormat("0,000.0");
-        fmt.setPositivePrefix("+");
+        DecimalFormat fmt = new DecimalFormat("0000.0");
         
         for (Hash peer : organizer.selectAllPeers()) {
             PeerProfile profile = organizer.getProfile(peer);
             if (!profile.getIsActive()) {
-                System.out.println("Peer " + profile.getPeer().toBase64().substring(0,4) 
+                System.out.println("Peer " + peer.toBase64().substring(0,4) 
                            + " [" + (organizer.isFast(peer) ? "IF+R" : 
                                      organizer.isHighCapacity(peer) ? "IR  " :
                                      organizer.isFailing(peer) ? "IX  " : "I   ") + "]: "
-                           + "\t Speed:\t" + fmt.format(profile.getSpeedValue())
+                           + " Speed:\t" + fmt.format(profile.getSpeedValue())
                            + " Capacity:\t" + fmt.format(profile.getCapacityValue())
                            + " Integration:\t" + fmt.format(profile.getIntegrationValue())
                            + " Active?\t" + profile.getIsActive() 
                            + " Failing?\t" + profile.getIsFailing());
             } else {
-                System.out.println("Peer " + profile.getPeer().toBase64().substring(0,4) 
+                System.out.println("Peer " + peer.toBase64().substring(0,4) 
                            + " [" + (organizer.isFast(peer) ? "F+R " : 
                                      organizer.isHighCapacity(peer) ? "R   " :
                                      organizer.isFailing(peer) ? "X   " : "    ") + "]: "
-                           + "\t Speed:\t" + fmt.format(profile.getSpeedValue())
+                           + " Speed:\t" + fmt.format(profile.getSpeedValue())
                            + " Capacity:\t" + fmt.format(profile.getCapacityValue())
                            + " Integration:\t" + fmt.format(profile.getIntegrationValue())
                            + " Active?\t" + profile.getIsActive() 
diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
index 1d5702d553..0cb477716e 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
@@ -458,6 +458,8 @@ class ProfilePersistenceHelper {
     }
 
     private Hash getHash(String name) {
+        if (name.length() < PREFIX.length() + 44)
+            return null;
         String key = name.substring(PREFIX.length());
         key = key.substring(0, 44);
         //Hash h = new Hash();
-- 
GitLab