Author: toad
Date: 2007-03-22 20:37:23 +0000 (Thu, 22 Mar 2007)
New Revision: 12277

Added:
   trunk/freenet/src/freenet/node/ConfigurablePersister.java
   trunk/freenet/src/freenet/node/NodeStats.java
   trunk/freenet/src/freenet/node/Persistable.java
   trunk/freenet/src/freenet/node/Persister.java
Modified:
   trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
   trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java
   trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java
   trunk/freenet/src/freenet/config/SubConfig.java
   trunk/freenet/src/freenet/node/InsertHandler.java
   trunk/freenet/src/freenet/node/Node.java
   trunk/freenet/src/freenet/node/NodeClientCore.java
   trunk/freenet/src/freenet/node/NodeDispatcher.java
   trunk/freenet/src/freenet/node/PacketSender.java
   trunk/freenet/src/freenet/node/PeerManager.java
   trunk/freenet/src/freenet/node/PeerNode.java
   trunk/freenet/src/freenet/node/RequestHandler.java
   trunk/freenet/src/freenet/node/RequestStarter.java
   trunk/freenet/src/freenet/node/RequestStarterGroup.java
   trunk/freenet/src/freenet/node/SSKInsertHandler.java
   trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java
Log:
Major refactoring:
- Move most stats into NodeStats
- Move peers-related stats into PeerManager
- Separate node and client throttles
- Persistable/Persister

Modified: trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java       
2007-03-22 19:16:22 UTC (rev 12276)
+++ trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java       
2007-03-22 20:37:23 UTC (rev 12277)
@@ -26,6 +26,8 @@
 import freenet.node.FSParseException;
 import freenet.node.Node;
 import freenet.node.NodeClientCore;
+import freenet.node.NodeStats;
+import freenet.node.PeerManager;
 import freenet.node.PeerNode;
 import freenet.node.PeerNodeStatus;
 import freenet.node.Version;
@@ -41,12 +43,16 @@

        private final Node node;
        private final NodeClientCore core;
+       private final NodeStats stats;
+       private final PeerManager peers;
        private boolean isReversed = false;

        protected DarknetConnectionsToadlet(Node n, NodeClientCore core, 
HighLevelSimpleClient client) {
                super(client);
                this.node = n;
                this.core = core;
+               this.stats = n.nodeStats;
+               this.peers = n.peers;
        }

        public String supportedMethods() {
@@ -72,7 +78,7 @@
                final boolean fProxyJavascriptEnabled = 
node.isFProxyJavascriptEnabled();

                /* gather connection statistics */
-               PeerNodeStatus[] peerNodeStatuses = node.getPeerNodeStatuses();
+               PeerNodeStatus[] peerNodeStatuses = peers.getPeerNodeStatuses();
                Arrays.sort(peerNodeStatuses, new Comparator() {
                        public int compare(Object first, Object second) {
                                int result = 0;
@@ -118,16 +124,16 @@
                        }
                });

-               int numberOfConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_CONNECTED);
-               int numberOfRoutingBackedOff = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
-               int numberOfTooNew = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_TOO_NEW);
-               int numberOfTooOld = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_TOO_OLD);
-               int numberOfDisconnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_DISCONNECTED);
-               int numberOfNeverConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_NEVER_CONNECTED);
-               int numberOfDisabled = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_DISABLED);
-               int numberOfBursting = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_BURSTING);
-               int numberOfListening = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_LISTENING);
-               int numberOfListenOnly = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_LISTEN_ONLY);
+               int numberOfConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_CONNECTED);
+               int numberOfRoutingBackedOff = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
+               int numberOfTooNew = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_NEW);
+               int numberOfTooOld = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_OLD);
+               int numberOfDisconnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_DISCONNECTED);
+               int numberOfNeverConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED);
+               int numberOfDisabled = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_DISABLED);
+               int numberOfBursting = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_BURSTING);
+               int numberOfListening = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_LISTENING);
+               int numberOfListenOnly = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_LISTEN_ONLY);

                int numberOfSimpleConnected = numberOfConnected + 
numberOfRoutingBackedOff;
                int numberOfNotConnected = numberOfTooNew + numberOfTooOld + 
numberOfDisconnected + numberOfNeverConnected + numberOfDisabled + 
numberOfBursting + numberOfListening + numberOfListenOnly;
@@ -150,17 +156,17 @@

                        /* node status values */
                        long nodeUptimeSeconds = (now - node.startupTime) / 
1000;
-                       int bwlimitDelayTime = (int) node.getBwlimitDelayTime();
-                       int nodeAveragePingTime = (int) 
node.getNodeAveragePingTime();
-                       int networkSizeEstimateSession = 
node.getNetworkSizeEstimate(-1);
+                       int bwlimitDelayTime = (int) 
stats.getBwlimitDelayTime();
+                       int nodeAveragePingTime = (int) 
stats.getNodeAveragePingTime();
+                       int networkSizeEstimateSession = 
stats.getNetworkSizeEstimate(-1);
                        int networkSizeEstimateRecent = 0;
                        if(nodeUptimeSeconds > (48*60*60)) {  // 48 hours
-                               networkSizeEstimateRecent = 
node.getNetworkSizeEstimate(now - (48*60*60*1000));  // 48 hours
+                               networkSizeEstimateRecent = 
stats.getNetworkSizeEstimate(now - (48*60*60*1000));  // 48 hours
                        }
                        DecimalFormat fix4 = new DecimalFormat("0.0000");
-                       double routingMissDistance =  
node.routingMissDistance.currentValue();
+                       double routingMissDistance =  
stats.routingMissDistance.currentValue();
                        DecimalFormat fix1 = new DecimalFormat("##0.0%");
-                       double backedOffPercent =  
node.backedOffPercent.currentValue();
+                       double backedOffPercent =  
stats.backedOffPercent.currentValue();
                        String nodeUptimeString = 
TimeUtil.formatTime(nodeUptimeSeconds * 1000);  // *1000 to convert to 
milliseconds

                        // BEGIN OVERVIEW TABLE
@@ -183,7 +189,7 @@
                                overviewList.addChild("li", "nodeUptime:\u00a0" 
+ nodeUptimeString);
                                overviewList.addChild("li", 
"routingMissDistance:\u00a0" + fix4.format(routingMissDistance));
                                overviewList.addChild("li", 
"backedOffPercent:\u00a0" + fix1.format(backedOffPercent));
-                               overviewList.addChild("li", 
"pInstantReject:\u00a0" + fix1.format(node.pRejectIncomingInstantly()));
+                               overviewList.addChild("li", 
"pInstantReject:\u00a0" + fix1.format(stats.pRejectIncomingInstantly()));
                                nextTableCell = overviewTableRow.addChild("td");
                        }

@@ -222,7 +228,7 @@
                                        activityList.addChild("li", "Total 
Output:\u00a0" + SizeUtil.formatSize(total[0], true) + "\u00a0(" + 
SizeUtil.formatSize(total_output_rate, true) + "ps)");
                                        activityList.addChild("li", "Payload 
Output:\u00a0" + SizeUtil.formatSize(totalPayload, true) + "\u00a0(" + 
SizeUtil.formatSize(total_payload_rate, true) + "ps) ("+percent+"%)");
                                        activityList.addChild("li", "Total 
Input:\u00a0" + SizeUtil.formatSize(total[1], true) + "\u00a0(" + 
SizeUtil.formatSize(total_input_rate, true) + "ps)");
-                                       long[] rate = node.getNodeIOStats();
+                                       long[] rate = stats.getNodeIOStats();
                                        long delta = (rate[5] - rate[2]) / 1000;
                                        if(delta > 0) {
                                                long output_rate = (rate[3] - 
rate[0]) / delta;
@@ -303,13 +309,13 @@
                                HTMLNode backoffReasonInfobox = 
nextTableCell.addChild("div", "class", "infobox");
                                backoffReasonInfobox.addChild("div", "class", 
"infobox-header", "Peer backoff reasons");
                                HTMLNode backoffReasonContent = 
backoffReasonInfobox.addChild("div", "class", "infobox-content");
-                               String [] routingBackoffReasons = 
node.getPeerNodeRoutingBackoffReasons();
+                               String [] routingBackoffReasons = 
peers.getPeerNodeRoutingBackoffReasons();
                                if(routingBackoffReasons.length == 0) {
                                        backoffReasonContent.addChild("#", 
"Good, your node is not backed off from any peers!");
                                } else {
                                        HTMLNode reasonList = 
backoffReasonContent.addChild("ul");
                                        for(int 
i=0;i<routingBackoffReasons.length;i++) {
-                                               int reasonCount = 
node.getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]);
+                                               int reasonCount = 
peers.getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]);
                                                if(reasonCount > 0) {
                                                        
reasonList.addChild("li", routingBackoffReasons[i] + '\u00a0' + reasonCount);
                                                }
@@ -400,7 +406,7 @@

                                        // status column
                                        String statusString = 
peerNodeStatus.getStatusName();
-                                       if (!advancedModeEnabled && 
(peerNodeStatus.getStatusValue() == Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF)) {
+                                       if (!advancedModeEnabled && 
(peerNodeStatus.getStatusValue() == 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF)) {
                                                statusString = "BUSY";
                                        }
                                        peerRow.addChild("td", "class", 
"peer-status").addChild("span", "class", peerNodeStatus.getStatusCSSName(), 
statusString + (peerNodeStatus.isFetchingARK() ? "*" : ""));
@@ -418,7 +424,7 @@
                                        }

                                        // version column
-                                       if (peerNodeStatus.getStatusValue() != 
Node.PEER_NODE_STATUS_NEVER_CONNECTED && 
(peerNodeStatus.isPublicInvalidVersion() || 
peerNodeStatus.isPublicReverseInvalidVersion())) {  // Don't draw attention to 
a version problem if NEVER CONNECTED
+                                       if (peerNodeStatus.getStatusValue() != 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED && 
(peerNodeStatus.isPublicInvalidVersion() || 
peerNodeStatus.isPublicReverseInvalidVersion())) {  // Don't draw attention to 
a version problem if NEVER CONNECTED
                                                peerRow.addChild("td", "class", 
"peer-version").addChild("span", "class", "peer_version_problem", 
peerNodeStatus.getSimpleVersion());
                                        } else {
                                                peerRow.addChild("td", "class", 
"peer-version").addChild("#", peerNodeStatus.getSimpleVersion());
@@ -450,7 +456,7 @@
                                        long idle = 
peerNodeStatus.getTimeLastRoutable();
                                        if (peerNodeStatus.isRoutable()) {
                                                idle = 
peerNodeStatus.getTimeLastConnectionCompleted();
-                                       } else if 
(peerNodeStatus.getStatusValue() == Node.PEER_NODE_STATUS_NEVER_CONNECTED) {
+                                       } else if 
(peerNodeStatus.getStatusValue() == 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) {
                                                idle = 
peerNodeStatus.getPeerAddedTime();
                                        }
                                        if(!peerNodeStatus.isConnected() && 
(now - idle) > (2 * 7 * 24 * 60 * 60 * (long) 1000)) { // 2 weeks
@@ -899,7 +905,7 @@
                        PeerNode[] peerNodes = node.getDarknetConnections();
                        for(int i = 0; i < peerNodes.length; i++) {
                                if 
(request.isPartSet("node_"+peerNodes[i].hashCode())) {       
-                                       
if((peerNodes[i].timeLastConnectionCompleted() < (System.currentTimeMillis() - 
1000*60*60*24*7) /* one week */) ||  (peerNodes[i].peerNodeStatus == 
Node.PEER_NODE_STATUS_NEVER_CONNECTED) || request.isPartSet("forceit")){
+                                       
if((peerNodes[i].timeLastConnectionCompleted() < (System.currentTimeMillis() - 
1000*60*60*24*7) /* one week */) ||  (peerNodes[i].peerNodeStatus == 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) || request.isPartSet("forceit")){
                                                
this.node.removeDarknetConnection(peerNodes[i]);
                                                if(logMINOR) Logger.minor(this, 
"Removed node: node_"+peerNodes[i].hashCode());
                                        }else{

Modified: trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java    2007-03-22 
19:16:22 UTC (rev 12276)
+++ trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java    2007-03-22 
20:37:23 UTC (rev 12277)
@@ -15,6 +15,7 @@
 import freenet.io.comm.UdpSocketManager;
 import freenet.node.Node;
 import freenet.node.NodeClientCore;
+import freenet.node.PeerManager;
 import freenet.node.PeerNode;
 import freenet.support.Base64;
 import freenet.support.HTMLNode;
@@ -153,7 +154,7 @@
                                                        
fs.removeValue("sentTime");
                                                        pn.queueN2NTM(fs);
                                                        Logger.normal(this, 
"Queued N2NTM to '"+pn.getName()+"': "+message);
-                                               } else 
if(pn.getPeerNodeStatus() == Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
+                                               } else 
if(pn.getPeerNodeStatus() == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
                                                        sendStatusShort = 
"Delayed";
                                                        sendStatusLong = 
"Backed off: Sending of message possibly delayed to peer";
                                                        sendStatusClass = 
"n2ntm-send-delayed";

Modified: trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java       
2007-03-22 19:16:22 UTC (rev 12276)
+++ trunk/freenet/src/freenet/clients/http/StatisticsToadlet.java       
2007-03-22 20:37:23 UTC (rev 12277)
@@ -19,6 +19,7 @@
 import freenet.node.Node;
 import freenet.node.NodeClientCore;
 import freenet.node.NodeStarter;
+import freenet.node.NodeStats;
 import freenet.node.PeerManager;
 import freenet.node.PeerNodeStatus;
 import freenet.node.RequestStarterGroup;
@@ -58,11 +59,15 @@

        private final Node node;
        private final NodeClientCore core;
+       private final NodeStats stats;
+       private final PeerManager peers;

        protected StatisticsToadlet(Node n, NodeClientCore core, 
HighLevelSimpleClient client) {
                super(client);
                this.node = n;
                this.core = core;
+               stats = node.nodeStats;
+               peers = node.peers;
        }

        public String supportedMethods() {
@@ -97,7 +102,7 @@
                final SubConfig nodeConfig = node.config.get("node");

                /* gather connection statistics */
-               PeerNodeStatus[] peerNodeStatuses = node.getPeerNodeStatuses();
+               PeerNodeStatus[] peerNodeStatuses = peers.getPeerNodeStatuses();
                Arrays.sort(peerNodeStatuses, new Comparator() {
                        public int compare(Object first, Object second) {
                                PeerNodeStatus firstNode = (PeerNodeStatus) 
first;
@@ -110,16 +115,16 @@
                        }
                });

-               int numberOfConnected = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_CONNECTED);
-               int numberOfRoutingBackedOff = 
getPeerStatusCount(peerNodeStatuses, Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
-               int numberOfTooNew = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_TOO_NEW);
-               int numberOfTooOld = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_TOO_OLD);
-               int numberOfDisconnected = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_DISCONNECTED);
-               int numberOfNeverConnected = 
getPeerStatusCount(peerNodeStatuses, Node.PEER_NODE_STATUS_NEVER_CONNECTED);
-               int numberOfDisabled = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_DISABLED);
-               int numberOfBursting = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_BURSTING);
-               int numberOfListening = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_LISTENING);
-               int numberOfListenOnly = getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_LISTEN_ONLY);
+               int numberOfConnected = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_CONNECTED);
+               int numberOfRoutingBackedOff = 
getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
+               int numberOfTooNew = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_NEW);
+               int numberOfTooOld = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_OLD);
+               int numberOfDisconnected = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_DISCONNECTED);
+               int numberOfNeverConnected = 
getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED);
+               int numberOfDisabled = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_DISABLED);
+               int numberOfBursting = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_BURSTING);
+               int numberOfListening = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_LISTENING);
+               int numberOfListenOnly = getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_LISTEN_ONLY);

                HTMLNode pageNode = ctx.getPageMaker().getPageNode("Statistics 
for " + node.getMyName(), ctx);
                HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
@@ -136,26 +141,26 @@

                        /* node status values */
                        long nodeUptimeSeconds = (now - node.startupTime) / 
1000;
-                       int bwlimitDelayTime = (int) node.getBwlimitDelayTime();
-                       int nodeAveragePingTime = (int) 
node.getNodeAveragePingTime();
-                       int networkSizeEstimateSession = 
node.getNetworkSizeEstimate(-1);
+                       int bwlimitDelayTime = (int) 
stats.getBwlimitDelayTime();
+                       int nodeAveragePingTime = (int) 
stats.getNodeAveragePingTime();
+                       int networkSizeEstimateSession = 
stats.getNetworkSizeEstimate(-1);
                        int networkSizeEstimate24h = 0;
                        int networkSizeEstimate48h = 0;
                        double numberOfRemotePeerLocationsSeenInSwaps = 
(double)node.getNumberOfRemotePeerLocationsSeenInSwaps();

                        if(nodeUptimeSeconds > (24*60*60)) {  // 24 hours
-                               networkSizeEstimate24h = 
node.getNetworkSizeEstimate(now - (24*60*60*1000));  // 48 hours
+                               networkSizeEstimate24h = 
stats.getNetworkSizeEstimate(now - (24*60*60*1000));  // 48 hours
                        }
                        if(nodeUptimeSeconds > (48*60*60)) {  // 48 hours
-                               networkSizeEstimate48h = 
node.getNetworkSizeEstimate(now - (48*60*60*1000));  // 48 hours
+                               networkSizeEstimate48h = 
stats.getNetworkSizeEstimate(now - (48*60*60*1000));  // 48 hours
                        }
                        DecimalFormat fix1p4 = new DecimalFormat("0.0000");
                        DecimalFormat fix6p6 = new 
DecimalFormat("#####0.0#####");
                        DecimalFormat fix1p6sci = new 
DecimalFormat("0.######E0");
                        DecimalFormat fix3p1pct = new DecimalFormat("##0.0%");
                        NumberFormat thousendPoint = NumberFormat.getInstance();
-                       double routingMissDistance =  
node.routingMissDistance.currentValue();
-                       double backedOffPercent =  
node.backedOffPercent.currentValue();
+                       double routingMissDistance =  
stats.routingMissDistance.currentValue();
+                       double backedOffPercent =  
stats.backedOffPercent.currentValue();
                        String nodeUptimeString = 
TimeUtil.formatTime(nodeUptimeSeconds * 1000);  // *1000 to convert to 
milliseconds

                        HTMLNode overviewTable = contentNode.addChild("table", 
"class", "column");
@@ -183,7 +188,7 @@
                                overviewList.addChild("li", "nodeUptime:\u00a0" 
+ nodeUptimeString);
                                overviewList.addChild("li", 
"routingMissDistance:\u00a0" + fix1p4.format(routingMissDistance));
                                overviewList.addChild("li", 
"backedOffPercent:\u00a0" + fix3p1pct.format(backedOffPercent));
-                               overviewList.addChild("li", 
"pInstantReject:\u00a0" + fix3p1pct.format(node.pRejectIncomingInstantly()));
+                               overviewList.addChild("li", 
"pInstantReject:\u00a0" + fix3p1pct.format(stats.pRejectIncomingInstantly()));
                                overviewList.addChild("li", 
"unclaimedFIFOSize:\u00a0" + node.getUnclaimedFIFOSize());
                                nextTableCell = overviewTableRow.addChild("td");
                        }
@@ -284,13 +289,13 @@
                                HTMLNode backoffReasonInfobox = 
nextTableCell.addChild("div", "class", "infobox");
                                backoffReasonInfobox.addChild("div", "class", 
"infobox-header", "Peer backoff reasons");
                                HTMLNode backoffReasonContent = 
backoffReasonInfobox.addChild("div", "class", "infobox-content");
-                               String [] routingBackoffReasons = 
node.getPeerNodeRoutingBackoffReasons();
+                               String [] routingBackoffReasons = 
peers.getPeerNodeRoutingBackoffReasons();
                                if(routingBackoffReasons.length == 0) {
                                        backoffReasonContent.addChild("#", 
"Good, your node is not backed off from any peers!");
                                } else {
                                        HTMLNode reasonList = 
backoffReasonContent.addChild("ul");
                                        for(int 
i=0;i<routingBackoffReasons.length;i++) {
-                                               int reasonCount = 
node.getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]);
+                                               int reasonCount = 
peers.getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]);
                                                if(reasonCount > 0) {
                                                        
reasonList.addChild("li", routingBackoffReasons[i] + '\u00a0' + reasonCount);
                                                }
@@ -368,7 +373,7 @@
                                bandwidthList.addChild("li", "Total 
Output:\u00a0" + SizeUtil.formatSize(total[0]) + " (" + 
SizeUtil.formatSize(total_output_rate, true) + "ps)");
                                bandwidthList.addChild("li", "Payload 
Output:\u00a0" + SizeUtil.formatSize(totalPayload) + " (" + 
SizeUtil.formatSize(total_payload_rate, true) + "ps) ("+percent+"%)");
                                bandwidthList.addChild("li", "Total 
Input:\u00a0" + SizeUtil.formatSize(total[1]) + " (" + 
SizeUtil.formatSize(total_input_rate, true) + "ps)");
-                               long[] rate = node.getNodeIOStats();
+                               long[] rate = stats.getNodeIOStats();
                                long delta = (rate[5] - rate[2]) / 1000;
                                if(delta > 0) {
                                        long output_rate = (rate[3] - rate[0]) 
/ delta;
@@ -462,12 +467,12 @@
                                long maxJavaMem = (long)maxMemory;
                                int availableCpus = rt.availableProcessors();

-                               int threadCount = node.getActiveThreadCount();
+                               int threadCount = stats.getActiveThreadCount();

                                jvmStatsList.addChild("li", "Used Java 
memory:\u00a0" + SizeUtil.formatSize(usedJavaMem, true));
                                jvmStatsList.addChild("li", "Allocated Java 
memory:\u00a0" + SizeUtil.formatSize(allocatedJavaMem, true));
                                jvmStatsList.addChild("li", "Maximum Java 
memory:\u00a0" + SizeUtil.formatSize(maxJavaMem, true));
-                               jvmStatsList.addChild("li", "Running 
threads:\u00a0" + thousendPoint.format(threadCount) + '/' + 
node.getThreadLimit());
+                               jvmStatsList.addChild("li", "Running 
threads:\u00a0" + thousendPoint.format(threadCount) + '/' + 
stats.getThreadLimit());
                                jvmStatsList.addChild("li", "Available 
CPUs:\u00a0" + availableCpus);
                                jvmStatsList.addChild("li", "JVM Vendor:\u00a0" 
+ System.getProperty("java.vm.vendor"));
                                jvmStatsList.addChild("li", "JVM 
Version:\u00a0" + System.getProperty("java.vm.version"));
@@ -609,7 +614,7 @@
                nodeCircleInfoboxContent.addChild("span", new String[] { 
"style", "class" }, new String[] { generatePeerCircleStyleString(0.875, false, 
1.0), "mark" }, "+");
                nodeCircleInfoboxContent.addChild("span", new String[] { 
"style", "class" }, new String[] { generatePeerCircleStyleString(0.875, false, 
1.0), "mark" }, "+");
                nodeCircleInfoboxContent.addChild("span", new String[] { 
"style", "class" }, new String[] { "position: absolute; top: " + 
PEER_CIRCLE_RADIUS + "px; left: " + (PEER_CIRCLE_RADIUS + 
PEER_CIRCLE_ADDITIONAL_FREE_SPACE) + "px", "mark" }, "+");
-               final Object[] knownLocsCopy = node.getKnownLocations(-1);
+               final Object[] knownLocsCopy = stats.getKnownLocations(-1);
                final Double[] locations = (Double[])knownLocsCopy[0];
                final Long[] timestamps = (Long[])knownLocsCopy[1];
                Double location;
@@ -669,7 +674,7 @@
                peerCircleInfoboxContent.addChild("span", new String[] { 
"style", "class" }, new String[] { "position: absolute; top: " + 
PEER_CIRCLE_RADIUS + "px; left: " + (PEER_CIRCLE_RADIUS + 
PEER_CIRCLE_ADDITIONAL_FREE_SPACE) + "px", "mark" }, "+");
                //
                double myLocation = node.getLocation();
-               PeerNodeStatus[] peerNodeStatuses = node.getPeerNodeStatuses();
+               PeerNodeStatus[] peerNodeStatuses = peers.getPeerNodeStatuses();
                PeerNodeStatus peerNodeStatus;
                double peerLocation;
                double peerDistance;

Modified: trunk/freenet/src/freenet/config/SubConfig.java
===================================================================
--- trunk/freenet/src/freenet/config/SubConfig.java     2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/config/SubConfig.java     2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -302,4 +302,10 @@
                }
        }

+       public String getRawOption(String name) {
+               if(config instanceof PersistentConfig)
+                       return ((PersistentConfig) 
config).origConfigFileContents.get(prefix + SimpleFieldSet.MULTI_LEVEL_CHAR + 
name);
+               else return null;
+       }
+
 }

Added: trunk/freenet/src/freenet/node/ConfigurablePersister.java
===================================================================
--- trunk/freenet/src/freenet/node/ConfigurablePersister.java                   
        (rev 0)
+++ trunk/freenet/src/freenet/node/ConfigurablePersister.java   2007-03-22 
20:37:23 UTC (rev 12277)
@@ -0,0 +1,74 @@
+package freenet.node;
+
+import java.io.File;
+import java.io.IOException;
+
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
+import freenet.node.Node.NodeInitException;
+import freenet.support.api.StringCallback;
+
+public class ConfigurablePersister extends Persister {
+
+       public ConfigurablePersister(Persistable t, SubConfig nodeConfig, 
String optionName, 
+                       String defaultFilename, int sortOrder, boolean expert, 
boolean forceWrite, String shortDesc, String longDesc) throws NodeInitException 
{
+               super(t);
+               nodeConfig.register(optionName, defaultFilename, sortOrder, 
expert, forceWrite, shortDesc, longDesc, new StringCallback() {
+
+                       public String get() {
+                               return persistTarget.toString();
+                       }
+
+                       public void set(String val) throws 
InvalidConfigValueException {
+                               setThrottles(val);
+                       }
+                       
+               });
+               
+               String throttleFile = nodeConfig.getString(optionName);
+               try {
+                       setThrottles(throttleFile);
+               } catch (InvalidConfigValueException e2) {
+                       throw new 
NodeInitException(Node.EXIT_THROTTLE_FILE_ERROR, e2.getMessage());
+               }
+               
+       }
+
+       private void setThrottles(String val) throws 
InvalidConfigValueException {
+               File f = new File(val);
+               File tmp = new File(val+".tmp");
+               while(true) {
+                       if(f.exists()) {
+                               if(!(f.canRead() && f.canWrite()))
+                                       throw new 
InvalidConfigValueException("File exists and cannot read/write it");
+                               break;
+                       } else {
+                               try {
+                                       f.createNewFile();
+                               } catch (IOException e) {
+                                       throw new 
InvalidConfigValueException("File does not exist and cannot be created");
+                               }
+                       }
+               }
+               while(true) {
+                       if(tmp.exists()) {
+                               if(!(tmp.canRead() && tmp.canWrite()))
+                                       throw new 
InvalidConfigValueException("File exists and cannot read/write it");
+                               break;
+                       } else {
+                               try {
+                                       tmp.createNewFile();
+                               } catch (IOException e) {
+                                       throw new 
InvalidConfigValueException("File does not exist and cannot be created");
+                               }
+                       }
+               }
+               
+               Persister tp;
+               synchronized(this) {
+                       persistTarget = f;
+                       persistTemp = tmp;
+               }
+       }
+
+}

Modified: trunk/freenet/src/freenet/node/InsertHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/InsertHandler.java   2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/InsertHandler.java   2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -338,8 +338,8 @@
                        totalReceived += sender.getTotalReceivedBytes();
                }
                if(logMINOR) Logger.minor(this, "Remote CHK insert cost 
"+totalSent+ '/' +totalReceived+" bytes ("+code+ ')');
-               node.remoteChkInsertBytesSentAverage.report(totalSent);
-               node.remoteChkInsertBytesReceivedAverage.report(totalReceived);
+               
node.nodeStats.remoteChkInsertBytesSentAverage.report(totalSent);
+               
node.nodeStats.remoteChkInsertBytesReceivedAverage.report(totalReceived);
         }
     }


Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java    2007-03-22 19:16:22 UTC (rev 
12276)
+++ trunk/freenet/src/freenet/node/Node.java    2007-03-22 20:37:23 UTC (rev 
12277)
@@ -172,6 +172,9 @@
                        }
        }

+       /** Stats */
+       public final NodeStats nodeStats;
+       
        /** Config object for the whole node. */
        public final PersistentConfig config;

@@ -188,12 +191,6 @@
        // Disabled to prevent long pauses every 30 seconds.
        static int aggressiveGCModificator = -1 /*250*/;

-       /** Minimum free heap memory bytes required to accept a request 
(perhaps fewer OOMs this way) */
-       public static final long MIN_FREE_HEAP_BYTES_FOR_ROUTING_SUCCESS = 3L * 
1024 * 1024;  // 3 MiB
-       
-       /** Minimum free heap memory percentage required to accept a request 
(perhaps fewer OOMs this way) */
-       public static final double MIN_FREE_HEAP_PERCENT_FOR_ROUTING_SUCCESS = 
0.01;  // 1%
-       
        /** If true, local requests and inserts aren't cached.
         * This opens up a glaring vulnerability; connected nodes
         * can then probe the store, and if the node doesn't have the
@@ -232,31 +229,7 @@
        public static final int RANDOMIZED_BURSTING_HANDSHAKE_BURST_SIZE = 3;
        // If we don't receive any packets at all in this period, from any 
node, tell the user
        public static final long ALARM_TIME = 60*1000;
-       /** Sub-max ping time. If ping is greater than this, we reject some 
requests. */
-       public static final long SUB_MAX_PING_TIME = 700;
-       /** Maximum overall average ping time. If ping is greater than this,
-        * we reject all requests. */
-       public static final long MAX_PING_TIME = 1500;
-       /** Maximum throttled packet delay. If the throttled packet delay is 
greater
-        * than this, reject all packets. */
-       public static final long MAX_THROTTLE_DELAY = 3000;
-       /** If the throttled packet delay is less than this, reject no packets; 
if it's
-        * between the two, reject some packets. */
-       public static final long SUB_MAX_THROTTLE_DELAY = 2000;
-       /** How high can bwlimitDelayTime be before we alert (in milliseconds)*/
-       public static final long MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD = 
MAX_THROTTLE_DELAY*2;
-       /** How high can nodeAveragePingTime be before we alert (in 
milliseconds)*/
-       public static final long MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD = 
MAX_PING_TIME*2;
-       /** How long we're over the bwlimitDelayTime threshold before we alert 
(in milliseconds)*/
-       public static final long MAX_BWLIMIT_DELAY_TIME_ALERT_DELAY = 
10*60*1000;  // 10 minutes
-       /** How long we're over the nodeAveragePingTime threshold before we 
alert (in milliseconds)*/
-       public static final long MAX_NODE_AVERAGE_PING_TIME_ALERT_DELAY = 
10*60*1000;  // 10 minutes

-       /** Accept one request every 10 seconds regardless, to ensure we update 
the
-        * block send time.
-        */
-       public static final int MAX_INTERREQUEST_TIME = 10*1000;
-
        // 900ms
        static final int MIN_INTERVAL_BETWEEN_INCOMING_SWAP_REQUESTS = 900;
        public static final int SYMMETRIC_KEY_LENGTH = 32; // 256 bits - note 
that this isn't used everywhere to determine it
@@ -323,8 +296,7 @@
        private DSAPrivateKey myPrivKey;
        /** My public key */
        private DSAPublicKey myPubKey;
-       /** Memory Checker thread */
-       private final Thread myMemoryChecker;
+       
        /** My ARK SSK private key */
        InsertableClientSSK myARK;
        /** My ARK sequence number */
@@ -336,43 +308,16 @@
        long myOldARKNumber;
        /** FetchContext for ARKs */
        public final FetchContext arkFetcherContext;
-       /** Next time to log the PeerNode status summary */
-       private long nextPeerNodeStatusLogTime = -1;
-       /** PeerNode status summary log interval (milliseconds) */
-       private static final long peerNodeStatusLogInterval = 5000;
-       /** PeerNode statuses, by status */
-       private final HashMap peerNodeStatuses;
-       /** PeerNode routing backoff reasons, by reason */
-       private final HashMap peerNodeRoutingBackoffReasons;
-       /** Next time to update oldestNeverConnectedPeerAge */
-       private long nextOldestNeverConnectedPeerAgeUpdateTime = -1;
-       /** oldestNeverConnectedPeerAge update interval (milliseconds) */
-       private static final long oldestNeverConnectedPeerAgeUpdateInterval = 
5000;
-       /** age of oldest never connected peer (milliseconds) */
-       private long oldestNeverConnectedPeerAge;
-       /** Next time to update PeerManagerUserAlert stats */
-       private long nextPeerManagerUserAlertStatsUpdateTime = -1;
-       /** PeerManagerUserAlert stats update interval (milliseconds) */
-       private static final long peerManagerUserAlertStatsUpdateInterval = 
1000;  // 1 second
-       /** first time bwlimitDelay was over PeerManagerUserAlert threshold */
-       private long firstBwlimitDelayTimeThresholdBreak ;
-       /** first time nodeAveragePing was over PeerManagerUserAlert threshold 
*/
-       private long firstNodeAveragePingTimeThresholdBreak;
-       /** bwlimitDelay PeerManagerUserAlert should happen if true */
-       public boolean bwlimitDelayAlertRelevant;
-       /** nodeAveragePing PeerManagerUserAlert should happen if true */
-       public boolean nodeAveragePingAlertRelevant;
-       /** Average proportion of requests rejected immediately due to overload 
*/
-       public final TimeDecayingRunningAverage pInstantRejectIncoming;
+       
        /** IP detector */
        public final NodeIPDetector ipDetector;
        /** For debugging/testing, set this to true to stop the
-        * probabilistic decrement at the edges of the HTLs.
-        */
+        * probabilistic decrement at the edges of the HTLs. */
        boolean disableProbabilisticHTLs;
        /** If true, disable all hang-check functionality */
        public boolean disableHangCheckers;

+       /** HashSet of currently running request UIDs */
        private final HashSet runningUIDs;

        byte[] myIdentity; // FIXME: simple identity block; should be unique
@@ -388,27 +333,26 @@
        private String mySignedReference = null;
        private String myName;
        final LocationManager lm;
-       final PeerManager peers; // my peers
+       /** My peers */
+       public final PeerManager peers;
        /** Directory to put node, peers, etc into */
        final File nodeDir;
        /** Directory to put extra peer data into */
        final File extraPeerDataDir;
-       public final RandomSource random; // strong RNG
+       /** Strong RNG */
+       public final RandomSource random;
        final UdpSocketManager usm;
        final FNPPacketMangler packetMangler;
        final DNSRequester dnsr;
        public final PacketSender ps;
        final NodeDispatcher dispatcher;
-       final NodePinger nodePinger;
        static final int MAX_MEMORY_CACHED_PUBKEYS = 1000;
        final LRUHashtable cachedPubKeys;
        final boolean testnetEnabled;
        final TestnetHandler testnetHandler;
        final StaticSwapRequestInterval swapInterval;
        public final DoubleTokenBucket outputThrottle;
-       final TokenBucket requestOutputThrottle;
-       final TokenBucket requestInputThrottle;
-       private boolean inputLimitDefault;
+       boolean inputLimitDefault;
        public static final short DEFAULT_MAX_HTL = (short)10;
        public static final int DEFAULT_SWAP_INTERVAL = 2000;
        private short maxHTL;
@@ -439,16 +383,6 @@
        public static final int EXIT_RESTART_FAILED = 24;
        public static final int EXIT_TEST_ERROR = 25;

-       public static final int PEER_NODE_STATUS_CONNECTED = 1;
-       public static final int PEER_NODE_STATUS_ROUTING_BACKED_OFF = 2;
-       public static final int PEER_NODE_STATUS_TOO_NEW = 3;
-       public static final int PEER_NODE_STATUS_TOO_OLD = 4;
-       public static final int PEER_NODE_STATUS_DISCONNECTED = 5;
-       public static final int PEER_NODE_STATUS_NEVER_CONNECTED = 6;
-       public static final int PEER_NODE_STATUS_DISABLED = 7;
-       public static final int PEER_NODE_STATUS_BURSTING = 8;
-       public static final int PEER_NODE_STATUS_LISTENING = 9;
-       public static final int PEER_NODE_STATUS_LISTEN_ONLY = 10;
        public static final int N2N_MESSAGE_TYPE_FPROXY_USERALERT = 1;
        public static final int N2N_TEXT_MESSAGE_TYPE_USERALERT = 
N2N_MESSAGE_TYPE_FPROXY_USERALERT;  // **FIXME** For backwards-compatibility, 
remove when removing DMT.nodeToNodeTextMessage
        public static final int EXTRA_PEER_DATA_TYPE_N2NTM = 1;
@@ -461,44 +395,7 @@

        public final NodeClientCore clientCore;
        final String bindto;
-       /** Average delay caused by throttling for sending a packet */
-       final TimeDecayingRunningAverage throttledPacketSendAverage;

-       // Stats
-       final TimeDecayingRunningAverage remoteChkFetchBytesSentAverage;
-       final TimeDecayingRunningAverage remoteSskFetchBytesSentAverage;
-       final TimeDecayingRunningAverage remoteChkInsertBytesSentAverage;
-       final TimeDecayingRunningAverage remoteSskInsertBytesSentAverage;
-       final TimeDecayingRunningAverage remoteChkFetchBytesReceivedAverage;
-       final TimeDecayingRunningAverage remoteSskFetchBytesReceivedAverage;
-       final TimeDecayingRunningAverage remoteChkInsertBytesReceivedAverage;
-       final TimeDecayingRunningAverage remoteSskInsertBytesReceivedAverage;
-       final TimeDecayingRunningAverage localChkFetchBytesSentAverage;
-       final TimeDecayingRunningAverage localSskFetchBytesSentAverage;
-       final TimeDecayingRunningAverage localChkInsertBytesSentAverage;
-       final TimeDecayingRunningAverage localSskInsertBytesSentAverage;
-       final TimeDecayingRunningAverage localChkFetchBytesReceivedAverage;
-       final TimeDecayingRunningAverage localSskFetchBytesReceivedAverage;
-       final TimeDecayingRunningAverage localChkInsertBytesReceivedAverage;
-       final TimeDecayingRunningAverage localSskInsertBytesReceivedAverage;
-       File persistTarget; 
-       File persistTemp;
-       private long previous_input_stat;
-       private long previous_output_stat;
-       private long previous_io_stat_time;
-       private long last_input_stat;
-       private long last_output_stat;
-       private long last_io_stat_time;
-       private final Object ioStatSync = new Object();
-       /** Next time to update the node I/O stats */
-       private long nextNodeIOStatsUpdateTime = -1;
-       /** Node I/O stats update interval (milliseconds) */
-       private static final long nodeIOStatsUpdateInterval = 2000;
-       /** Next time to update routableConnectionStats */
-       private long nextRoutableConnectionStatsUpdateTime = -1;
-       /** routableConnectionStats update interval (milliseconds) */
-       private static final long routableConnectionStatsUpdateInterval = 7 * 
1000;  // 7 seconds
-       
        // The version we were before we restarted.
        public int lastVersion;

@@ -525,15 +422,6 @@
        // Debugging stuff
        private static final boolean USE_RAM_PUBKEYS_CACHE = true;

-       // various metrics
-       public RunningAverage routingMissDistance = new 
TimeDecayingRunningAverage(0.0, 180000, 0.0, 1.0);
-       public RunningAverage backedOffPercent = new 
TimeDecayingRunningAverage(0.0, 180000, 0.0, 1.0);
-       protected final ThrottlePersister throttlePersister;
-       
-       // ThreadCounting stuffs
-       private final ThreadGroup rootThreadGroup;
-       private int threadLimit;
-
        /**
         * Read all storable settings (identity etc) from the node file.
         * @param filename The name of the file to read from.
@@ -793,7 +681,6 @@
                String tmp = "Initializing Node using freenet Build 
#"+Version.buildNumber()+" r"+Version.cvsRevision+" and freenet-ext Build 
#"+NodeStarter.extBuildNumber+" r"+NodeStarter.extRevisionNumber+" with 
"+System.getProperty("java.vm.vendor")+" JVM version 
"+System.getProperty("java.vm.version")+" running on 
"+System.getProperty("os.arch")+' '+System.getProperty("os.name")+' 
'+System.getProperty("os.version");
                Logger.normal(this, tmp);
                System.out.println(tmp);
-               pInstantRejectIncoming = new TimeDecayingRunningAverage(0, 
60000, 0.0, 1.0);
                nodeStarter=ns;
                if(logConfigHandler != lc)
                        logConfigHandler=lc;
@@ -805,10 +692,6 @@
                cachedPubKeys = new LRUHashtable();
                lm = new LocationManager(random);

-               ThreadGroup tg = Thread.currentThread().getThreadGroup();
-               while(tg.getParent() != null) tg = tg.getParent();
-               this.rootThreadGroup = tg;
-
                try {
                        localhostAddress = InetAddress.getByName("127.0.0.1");
                } catch (UnknownHostException e3) {
@@ -819,14 +702,10 @@
                requestSenders = new HashMap();
                transferringRequestSenders = new HashMap();
                insertSenders = new HashMap();
-               peerNodeStatuses = new HashMap();
-               peerNodeRoutingBackoffReasons = new HashMap();
                runningUIDs = new HashSet();
                dnsr = new DNSRequester(this);
                ps = new PacketSender(this);
                bootID = random.nextLong();
-               throttledPacketSendAverage =
-                       new TimeDecayingRunningAverage(1, 10*60*1000 /* should 
be significantly longer than a typical transfer */, 0, Long.MAX_VALUE);

                buildOldAgeUserAlert = new BuildOldAgeUserAlert();

@@ -882,12 +761,6 @@

                aggressiveGCModificator = nodeConfig.getInt("aggressiveGC");

-               //Memory Checking thread
-               // TODO: proper config. callbacks : maybe we shoudln't start 
the thread at all if it's not worthy
-               this.myMemoryChecker = new Thread(new MemoryChecker(), "Memory 
checker");
-               this.myMemoryChecker.setPriority(Thread.MAX_PRIORITY);
-               this.myMemoryChecker.setDaemon(true);
-
                // FIXME maybe these configs should actually be under a node.ip 
subconfig?
                ipDetector = new NodeIPDetector(this);
                sortOrder = ipDetector.registerConfigs(nodeConfig, sortOrder);
@@ -977,13 +850,6 @@

                Logger.normal(Node.class, "Creating node...");

-               previous_input_stat = 0;
-               previous_output_stat = 0;
-               previous_io_stat_time = 1;
-               last_input_stat = 0;
-               last_output_stat = 0;
-               last_io_stat_time = 3;
-
                // Bandwidth limit

                nodeConfig.register("outputBandwidthLimit", "15K", sortOrder++, 
false, true,
@@ -996,27 +862,20 @@
                                        public void set(int obwLimit) throws 
InvalidConfigValueException {
                                                if(obwLimit <= 0) throw new 
InvalidConfigValueException("Bandwidth limit must be positive");
                                                
outputThrottle.changeNanosAndBucketSizes((1000L * 1000L * 1000L) / obwLimit, 
obwLimit/2, (obwLimit * 2) / 5);
-                                               obwLimit = (obwLimit * 4) / 5; 
// fudge factor; take into account non-request activity
-                                               
requestOutputThrottle.changeNanosAndBucketSize((1000L*1000L*1000L) /  obwLimit, 
Math.max(obwLimit*60, 32768*20));
-                                               if(inputLimitDefault) {
-                                                       int ibwLimit = obwLimit 
* 4;
-                                                       
requestInputThrottle.changeNanosAndBucketSize((1000L*1000L*1000L) /  ibwLimit, 
Math.max(ibwLimit*60, 32768*20));
-                                               }
+                                               
nodeStats.setOutputLimit(obwLimit);
                                        }
                });

                int obwLimit = nodeConfig.getInt("outputBandwidthLimit");
                outputThrottle = new DoubleTokenBucket(obwLimit/2, 
(1000L*1000L*1000L) /  obwLimit, obwLimit, (obwLimit * 2) / 5);
                obwLimit = (obwLimit * 4) / 5;  // fudge factor; take into 
account non-request activity
-               requestOutputThrottle = 
-                       new TokenBucket(Math.max(obwLimit*60, 32768*20), 
(1000L*1000L*1000L) /  obwLimit, 0);

                nodeConfig.register("inputBandwidthLimit", "-1", sortOrder++, 
false, true,
                                "Input bandwidth limit (bytes per second)", 
"Input bandwidth limit (bytes/sec); the node will try not to exceed this; -1 = 
4x set outputBandwidthLimit",
                                new IntCallback() {
                                        public int get() {
                                                if(inputLimitDefault) return -1;
-                                               return (((int) ((1000L * 1000L 
* 1000L) / requestInputThrottle.getNanosPerTick())) * 5) / 4;
+                                               return 
nodeStats.getInputLimit();
                                        }
                                        public void set(int ibwLimit) throws 
InvalidConfigValueException {
                                                if(ibwLimit == -1) {
@@ -1027,7 +886,7 @@
                                                        ibwLimit = ibwLimit * 4 
/ 5; // fudge factor; take into account non-request activity
                                                }
                                                if(ibwLimit <= 0) throw new 
InvalidConfigValueException("Bandwidth limit must be positive or -1");
-                                               
requestInputThrottle.changeNanosAndBucketSize((1000L*1000L*1000L) /  ibwLimit, 
Math.max(ibwLimit*60, 32768*20));
+                                               
nodeStats.setInputLimit(ibwLimit);
                                        }
                });

@@ -1038,28 +897,8 @@
                } else {
                        ibwLimit = ibwLimit * 4 / 5;
                }
-               requestInputThrottle = 
-                       new TokenBucket(Math.max(ibwLimit*60, 32768*20), 
(1000L*1000L*1000L) / ibwLimit, 0);


-               nodeConfig.register("threadLimit", 300, sortOrder++, true, 
true, "Thread limit", "The node will try to limit its thread usage to the 
specified value, refusing new requests",
-                               new IntCallback() {
-                                       public int get() {
-                                               return threadLimit;
-                                       }
-                                       public void set(int val) throws 
InvalidConfigValueException {
-                                               if(val == get()) return;
-                                               if(val < 250)
-                                                       throw new 
InvalidConfigValueException("This value is to low for that setting, increase 
it!");
-                                               threadLimit = val;
-                                       }
-               });
-               
-               
-               threadLimit = nodeConfig.getInt("threadLimit");
-               
-               // FIXME add an averaging/long-term/soft bandwidth limit. (bug 
76)
-               
                // SwapRequestInterval

                nodeConfig.register("swapRequestSendInterval", 
DEFAULT_SWAP_INTERVAL, sortOrder++, true, false,
@@ -1157,7 +996,6 @@
                peers = new PeerManager(this, new File(nodeDir, 
"peers-"+portNumber).getPath());
                peers.writePeers();
                peers.updatePMUserAlert();
-               nodePinger = new NodePinger(this);

                usm.setDispatcher(dispatcher=new NodeDispatcher(this));
                usm.setLowLevelFilter(packetMangler = new 
FNPPacketMangler(this));
@@ -1437,62 +1275,25 @@
                        throw new NodeInitException(EXIT_STORE_OTHER, msg);
                }

-               nodeConfig.register("throttleFile", "throttle.dat", 
sortOrder++, true, false, "File to store the persistent throttle data to", 
"File to store the persistent throttle data to", new StringCallback() {
-
-                       public String get() {
-                               return persistTarget.toString();
-                       }
-
-                       public void set(String val) throws 
InvalidConfigValueException {
-                               setThrottles(val);
-                       }
-                       
-               });
-               
-               String throttleFile = nodeConfig.getString("throttleFile");
-               try {
-                       setThrottles(throttleFile);
-               } catch (InvalidConfigValueException e2) {
-                       throw new NodeInitException(EXIT_THROTTLE_FILE_ERROR, 
e2.getMessage());
-               }
-               
-               throttlePersister = new ThrottlePersister();
-               
-               SimpleFieldSet throttleFS = null;
-               try {
-                       throttleFS = SimpleFieldSet.readFrom(persistTarget, 
false, true);
-               } catch (IOException e) {
+               // FIXME back compatibility
+               SimpleFieldSet oldThrottleFS = null;
+               File oldThrottle = new File("throttle.dat");
+               String oldThrottleName = 
nodeConfig.getRawOption("throttleFile");
+               if(oldThrottleName != null)
+                       oldThrottle = new File(oldThrottleName);
+               if(oldThrottle.exists() && (!new 
File("node-throttle.dat").exists()) && lastVersion < 1021) {
+                       // Migrate from old throttle file to new node- and 
client- throttle files
                        try {
-                               throttleFS = 
SimpleFieldSet.readFrom(persistTemp, false, true);
-                       } catch (FileNotFoundException e1) {
+                               oldThrottleFS = SimpleFieldSet.readFrom(new 
File("throttle.dat"), false, true);
+                       } catch (IOException e) {
                                // Ignore
-                       } catch (IOException e1) {
-                               Logger.error(this, "Could not read 
"+persistTarget+" ("+e+") and could not read "+persistTemp+" either ("+e1+ ')');
                        }
+                       oldThrottle.delete();
                }
-
-               if(logMINOR) Logger.minor(this, "Read 
throttleFS:\n"+throttleFS);

-               // Guesstimates. Hopefully well over the reality.
-               localChkFetchBytesSentAverage = new 
TimeDecayingRunningAverage(500, 180000, 0.0, 1024*1024*1024, throttleFS == null 
? null : throttleFS.subset("LocalChkFetchBytesSentAverage"));
-               localSskFetchBytesSentAverage = new 
TimeDecayingRunningAverage(500, 180000, 0.0, 1024*1024*1024, throttleFS == null 
? null : throttleFS.subset("LocalSskFetchBytesSentAverage"));
-               localChkInsertBytesSentAverage = new 
TimeDecayingRunningAverage(32768, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalChkInsertBytesSentAverage"));
-               localSskInsertBytesSentAverage = new 
TimeDecayingRunningAverage(2048, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalSskInsertBytesSentAverage"));
-               localChkFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(32768, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalChkFetchBytesReceivedAverage"));
-               localSskFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(2048, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalSskFetchBytesReceivedAverage"));
-               localChkInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(1024, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalChkInsertBytesReceivedAverage"));
-               localSskInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(500, 180000, 0.0, 1024*1024*1024, throttleFS == null 
? null : throttleFS.subset("LocalChkInsertBytesReceivedAverage"));
-
-               remoteChkFetchBytesSentAverage = new 
TimeDecayingRunningAverage(32768+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkFetchBytesSentAverage"));
-               remoteSskFetchBytesSentAverage = new 
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteSskFetchBytesSentAverage"));
-               remoteChkInsertBytesSentAverage = new 
TimeDecayingRunningAverage(32768+32768+1024, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkInsertBytesSentAverage"));
-               remoteSskInsertBytesSentAverage = new 
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteSskInsertBytesSentAverage"));
-               remoteChkFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(32768+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkFetchBytesReceivedAverage"));
-               remoteSskFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(2048+500, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("RemoteSskFetchBytesReceivedAverage"));
-               remoteChkInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(32768+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkInsertBytesReceivedAverage"));
-               remoteSskInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteSskInsertBytesReceivedAverage"));
+               nodeStats = new NodeStats(this, sortOrder, nodeConfig, 
oldThrottleFS, ibwLimit, ibwLimit);

-               clientCore = new NodeClientCore(this, config, nodeConfig, 
nodeDir, portNumber, sortOrder, throttleFS == null ? null : 
throttleFS.subset("RequestStarters"));
+               clientCore = new NodeClientCore(this, config, nodeConfig, 
nodeDir, portNumber, sortOrder, oldThrottleFS == null ? null : 
oldThrottleFS.subset("RequestStarters"));

                nodeConfig.register("disableHangCheckers", false, sortOrder++, 
true, false, "Disable all hang checkers", "Disable all hang checkers/watchdog 
functions. Set this if you are profiling Fred.", new BooleanCallback() {

@@ -1533,46 +1334,6 @@
                System.out.println("Node constructor completed");
        }

-       private void setThrottles(String val) throws 
InvalidConfigValueException {
-               File f = new File(val);
-               File tmp = new File(val+".tmp");
-               while(true) {
-                       if(f.exists()) {
-                               if(!(f.canRead() && f.canWrite()))
-                                       throw new 
InvalidConfigValueException("File exists and cannot read/write it");
-                               break;
-                       } else {
-                               try {
-                                       f.createNewFile();
-                               } catch (IOException e) {
-                                       throw new 
InvalidConfigValueException("File does not exist and cannot be created");
-                               }
-                       }
-               }
-               while(true) {
-                       if(tmp.exists()) {
-                               if(!(tmp.canRead() && tmp.canWrite()))
-                                       throw new 
InvalidConfigValueException("File exists and cannot read/write it");
-                               break;
-                       } else {
-                               try {
-                                       tmp.createNewFile();
-                               } catch (IOException e) {
-                                       throw new 
InvalidConfigValueException("File does not exist and cannot be created");
-                               }
-                       }
-               }
-               
-               ThrottlePersister tp;
-               synchronized(Node.this) {
-                       persistTarget = f;
-                       persistTemp = tmp;
-                       tp = throttlePersister;
-               }
-               if(tp != null)
-                       tp.interrupt();
-       }
-
        static final String ERROR_SUN_NPTL = 
                "WARNING: Your system appears to be running a Sun JVM with 
NPTL. " +
                "This has been known to cause the node to freeze up due to the 
JVM losing a lock. " +
@@ -1587,11 +1348,9 @@

                if(!noSwaps)
                        lm.startSender(this, this.swapInterval);
-               nodePinger.start();
                dnsr.start();
-               ps.start();
+               ps.start(nodeStats);
                usm.start(disableHangCheckers);
-               myMemoryChecker.start();
                peers.start();

                if(isUsingWrapper()) {
@@ -1628,6 +1387,8 @@

                checkForEvilJVMBug();

+               this.nodeStats.start();
+               
                // TODO: implement a "required" version if needed
                if(!nodeUpdater.isEnabled() && 
(NodeStarter.RECOMMENDED_EXT_BUILD_NUMBER > NodeStarter.extBuildNumber))
                        clientCore.alerts.register(new ExtOldAgeUserAlert());
@@ -1647,9 +1408,6 @@
                // Process any data in the extra peer data directory
                peers.readExtraPeerData();

-               Thread t = new Thread(throttlePersister, "Throttle data 
persister thread");
-               t.setDaemon(true);
-               t.start();
                Logger.normal(this, "Started node");

                hasStarted = true;
@@ -1824,139 +1582,6 @@

        }

-       private long lastAcceptedRequest = -1;
-       
-       private long lastCheckedUncontended = -1;
-       
-       static final int ESTIMATED_SIZE_OF_ONE_THROTTLED_PACKET = 
-               1024 + DMT.packetTransmitSize(1024, 32)
-               + FNPPacketMangler.HEADERS_LENGTH_ONE_MESSAGE;
-       
-       /* return reject reason as string if should reject, otherwise return 
null */
-       public String shouldRejectRequest(boolean canAcceptAnyway, boolean 
isInsert, boolean isSSK) {
-               if(logMINOR) dumpByteCostAverages();
-               
-               if(threadLimit < getActiveThreadCount())
-                       return "Accepting the request would mean going above 
the maximum number of allowed threads";
-               
-               double bwlimitDelayTime = 
throttledPacketSendAverage.currentValue();
-               
-               // If no recent reports, no packets have been sent; correct the 
average downwards.
-               long now = System.currentTimeMillis();
-               boolean checkUncontended = false;
-               synchronized(this) {
-                       if(now - lastCheckedUncontended > 1000) {
-                               checkUncontended = true;
-                               lastCheckedUncontended = now;
-                       }
-               }
-               if(checkUncontended && 
throttledPacketSendAverage.lastReportTime() < now - 5000) {  // if last report 
more than 5 seconds ago
-                       // shouldn't take long
-                       
outputThrottle.blockingGrab(ESTIMATED_SIZE_OF_ONE_THROTTLED_PACKET);
-                       
outputThrottle.recycle(ESTIMATED_SIZE_OF_ONE_THROTTLED_PACKET);
-                       long after = System.currentTimeMillis();
-                       // Report time it takes to grab the bytes.
-                       throttledPacketSendAverage.report(after - now);
-                       now = after;
-                       // will have changed, use new value
-                       synchronized(this) {
-                               bwlimitDelayTime = 
throttledPacketSendAverage.currentValue();
-                       }
-               }
-               
-               double pingTime = nodePinger.averagePingTime();
-               synchronized(this) {
-                       // Round trip time
-                       if(pingTime > MAX_PING_TIME) {
-                               if((now - lastAcceptedRequest > 
MAX_INTERREQUEST_TIME) && canAcceptAnyway) {
-                                       if(logMINOR) Logger.minor(this, 
"Accepting request anyway (take one every 10 secs to keep bwlimitDelayTime 
updated)");
-                               } else {
-                                       pInstantRejectIncoming.report(1.0);
-                                       return ">MAX_PING_TIME 
("+TimeUtil.formatTime((long)pingTime, 2, true)+ ')';
-                               }
-                       } else if(pingTime > SUB_MAX_PING_TIME) {
-                               double x = ((double)(pingTime - 
SUB_MAX_PING_TIME)) / (MAX_PING_TIME - SUB_MAX_PING_TIME);
-                               if(random.nextDouble() < x) {
-                                       pInstantRejectIncoming.report(1.0);
-                                       return ">SUB_MAX_PING_TIME 
("+TimeUtil.formatTime((long)pingTime, 2, true)+ ')';
-                               }
-                       }
-               
-                       // Bandwidth limited packets
-                       if(bwlimitDelayTime > MAX_THROTTLE_DELAY) {
-                               if((now - lastAcceptedRequest > 
MAX_INTERREQUEST_TIME) && canAcceptAnyway) {
-                                       if(logMINOR) Logger.minor(this, 
"Accepting request anyway (take one every 10 secs to keep bwlimitDelayTime 
updated)");
-                               } else {
-                                       pInstantRejectIncoming.report(1.0);
-                                       return ">MAX_THROTTLE_DELAY 
("+TimeUtil.formatTime((long)bwlimitDelayTime, 2, true)+ ')';
-                               }
-                       } else if(bwlimitDelayTime > SUB_MAX_THROTTLE_DELAY) {
-                               double x = ((double)(bwlimitDelayTime - 
SUB_MAX_THROTTLE_DELAY)) / (MAX_THROTTLE_DELAY - SUB_MAX_THROTTLE_DELAY);
-                               if(random.nextDouble() < x) {
-                                       pInstantRejectIncoming.report(1.0);
-                                       return ">SUB_MAX_THROTTLE_DELAY 
("+TimeUtil.formatTime((long)bwlimitDelayTime, 2, true)+ ')';
-                               }
-                       }
-                       
-               }
-               
-               // Do we have the bandwidth?
-               double expected =
-                       (isInsert ? (isSSK ? 
this.remoteSskInsertBytesSentAverage : this.remoteChkInsertBytesSentAverage)
-                                       : (isSSK ? 
this.remoteSskFetchBytesSentAverage : 
this.remoteChkFetchBytesSentAverage)).currentValue();
-               int expectedSent = (int)Math.max(expected, 0);
-               if(!requestOutputThrottle.instantGrab(expectedSent)) {
-                       pInstantRejectIncoming.report(1.0);
-                       return "Insufficient output bandwidth";
-               }
-               expected = 
-                       (isInsert ? (isSSK ? 
this.remoteSskInsertBytesReceivedAverage : 
this.remoteChkInsertBytesReceivedAverage)
-                                       : (isSSK ? 
this.remoteSskFetchBytesReceivedAverage : 
this.remoteChkFetchBytesReceivedAverage)).currentValue();
-               int expectedReceived = (int)Math.max(expected, 0);
-               if(!requestInputThrottle.instantGrab(expectedReceived)) {
-                       requestOutputThrottle.recycle(expectedSent);
-                       pInstantRejectIncoming.report(1.0);
-                       return "Insufficient input bandwidth";
-               }
-
-               Runtime r = Runtime.getRuntime();
-               long maxHeapMemory = r.maxMemory();
-               long freeHeapMemory = r.freeMemory();
-               if(freeHeapMemory < MIN_FREE_HEAP_BYTES_FOR_ROUTING_SUCCESS) {
-                       pInstantRejectIncoming.report(1.0);
-                       return "<MIN_FREE_HEAP_BYTES_FOR_ROUTING_SUCCESS 
("+SizeUtil.formatSize(freeHeapMemory, false)+" of 
"+SizeUtil.formatSize(maxHeapMemory, false)+')';
-               }
-               double percentFreeHeapMemoryOfMax = ((double) freeHeapMemory) / 
((double) maxHeapMemory);
-               if(percentFreeHeapMemoryOfMax < 
MIN_FREE_HEAP_PERCENT_FOR_ROUTING_SUCCESS) {
-                       pInstantRejectIncoming.report(1.0);
-                       DecimalFormat fix3p1pct = new DecimalFormat("##0.0%");
-                       return "<MIN_FREE_HEAP_PERCENT_FOR_ROUTING_SUCCESS 
("+SizeUtil.formatSize(freeHeapMemory, false)+" of 
"+SizeUtil.formatSize(maxHeapMemory, false)+" 
("+fix3p1pct.format(percentFreeHeapMemoryOfMax)+"))";
-               }
-
-               synchronized(this) {
-                       if(logMINOR) Logger.minor(this, "Accepting request?");
-                       lastAcceptedRequest = now;
-               }
-               
-               pInstantRejectIncoming.report(0.0);
-
-               // Accept
-               return null;
-       }
-       
-       private void dumpByteCostAverages() {
-               Logger.minor(this, "Byte cost averages: REMOTE:"+
-                               " CHK insert 
"+remoteChkInsertBytesSentAverage.currentValue()+ '/' 
+remoteChkInsertBytesReceivedAverage.currentValue()+
-                               " SSK insert 
"+remoteSskInsertBytesSentAverage.currentValue()+ '/' 
+remoteSskInsertBytesReceivedAverage.currentValue()+
-                               " CHK fetch 
"+remoteChkFetchBytesSentAverage.currentValue()+ '/' 
+remoteChkFetchBytesReceivedAverage.currentValue()+
-                               " SSK fetch 
"+remoteSskFetchBytesSentAverage.currentValue()+ '/' 
+remoteSskFetchBytesReceivedAverage.currentValue());
-               Logger.minor(this, "Byte cost averages: LOCAL"+
-                               " CHK insert 
"+localChkInsertBytesSentAverage.currentValue()+ '/' 
+localChkInsertBytesReceivedAverage.currentValue()+
-                               " SSK insert 
"+localSskInsertBytesSentAverage.currentValue()+ '/' 
+localSskInsertBytesReceivedAverage.currentValue()+
-                               " CHK fetch 
"+localChkFetchBytesSentAverage.currentValue()+ '/' 
+localChkFetchBytesReceivedAverage.currentValue()+
-                               " SSK fetch 
"+localSskFetchBytesSentAverage.currentValue()+ '/' 
+localSskFetchBytesReceivedAverage.currentValue());
-       }
-
        public SimpleFieldSet exportPrivateFieldSet() {
                SimpleFieldSet fs = exportPublicFieldSet(false);
                fs.put("dsaPrivKey", myPrivKey.asFieldSet());
@@ -2041,215 +1666,7 @@
         * Export volatile data about the node as a SimpleFieldSet
         */
        public SimpleFieldSet exportVolatileFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet(true);
-               long now = System.currentTimeMillis();
-               fs.put("isUsingWrapper", isUsingWrapper());
-               long nodeUptimeSeconds = 0;
-               synchronized(this) {
-                       fs.put("startupTime", startupTime);
-                       nodeUptimeSeconds = (now - startupTime) / 1000;
-                       fs.put("uptimeSeconds", nodeUptimeSeconds);
-               }
-               fs.put("averagePingTime", getNodeAveragePingTime());
-               fs.put("bwlimitDelayTime", getBwlimitDelayTime());
-               fs.put("networkSizeEstimateSession", 
getNetworkSizeEstimate(-1));
-               int networkSizeEstimate24hourRecent = 
getNetworkSizeEstimate(now - (24*60*60*1000));  // 24 hours
-               fs.put("networkSizeEstimate24hourRecent", 
networkSizeEstimate24hourRecent);
-               int networkSizeEstimate48hourRecent = 
getNetworkSizeEstimate(now - (48*60*60*1000));  // 48 hours
-               fs.put("networkSizeEstimate48hourRecent", 
networkSizeEstimate48hourRecent);
-               fs.put("routingMissDistance", 
routingMissDistance.currentValue());
-               fs.put("backedOffPercent", backedOffPercent.currentValue());
-               fs.put("pInstantReject", pRejectIncomingInstantly());
-               fs.put("unclaimedFIFOSize", usm.getUnclaimedFIFOSize());
-               
-               /* gather connection statistics */
-               PeerNodeStatus[] peerNodeStatuses = getPeerNodeStatuses();
-               Arrays.sort(peerNodeStatuses, new Comparator() {
-                       public int compare(Object first, Object second) {
-                               PeerNodeStatus firstNode = (PeerNodeStatus) 
first;
-                               PeerNodeStatus secondNode = (PeerNodeStatus) 
second;
-                               int statusDifference = 
firstNode.getStatusValue() - secondNode.getStatusValue();
-                               if (statusDifference != 0) {
-                                       return statusDifference;
-                               }
-                               return 
firstNode.getName().compareToIgnoreCase(secondNode.getName());
-                       }
-               });
-               
-               int numberOfConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_CONNECTED);
-               int numberOfRoutingBackedOff = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
-               int numberOfTooNew = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_TOO_NEW);
-               int numberOfTooOld = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_TOO_OLD);
-               int numberOfDisconnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_DISCONNECTED);
-               int numberOfNeverConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_NEVER_CONNECTED);
-               int numberOfDisabled = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_DISABLED);
-               int numberOfBursting = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_BURSTING);
-               int numberOfListening = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_LISTENING);
-               int numberOfListenOnly = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
Node.PEER_NODE_STATUS_LISTEN_ONLY);
-               
-               int numberOfSimpleConnected = numberOfConnected + 
numberOfRoutingBackedOff;
-               int numberOfNotConnected = numberOfTooNew + numberOfTooOld + 
numberOfDisconnected + numberOfNeverConnected + numberOfDisabled + 
numberOfBursting + numberOfListening + numberOfListenOnly;
-
-               fs.put("numberOfConnected", numberOfConnected);
-               fs.put("numberOfRoutingBackedOff", numberOfRoutingBackedOff);
-               fs.put("numberOfTooNew", numberOfTooNew);
-               fs.put("numberOfTooOld", numberOfTooOld);
-               fs.put("numberOfDisconnected", numberOfDisconnected);
-               fs.put("numberOfNeverConnected", numberOfNeverConnected);
-               fs.put("numberOfDisabled", numberOfDisabled);
-               fs.put("numberOfBursting", numberOfBursting);
-               fs.put("numberOfListening", numberOfListening);
-               fs.put("numberOfListenOnly", numberOfListenOnly);
-               
-               fs.put("numberOfSimpleConnected", numberOfSimpleConnected);
-               fs.put("numberOfNotConnected", numberOfNotConnected);
-
-               fs.put("numberOfInserts", getNumInserts());
-               fs.put("numberOfRequests", getNumRequests());
-               fs.put("numberOfTransferringRequests", 
getNumTransferringRequests());
-               fs.put("numberOfARKFetchers", getNumARKFetchers());
-
-               long[] total = IOStatisticCollector.getTotalIO();
-               long total_output_rate = (total[0]) / nodeUptimeSeconds;
-               long total_input_rate = (total[1]) / nodeUptimeSeconds;
-               long totalPayloadOutput = getTotalPayloadSent();
-               long total_payload_output_rate = totalPayloadOutput / 
nodeUptimeSeconds;
-               int total_payload_output_percent = (int) (100 * 
totalPayloadOutput / total[0]);
-               fs.put("totalOutputBytes", total[0]);
-               fs.put("totalOutputRate", total_output_rate);
-               fs.put("totalPayloadOutputBytes", totalPayloadOutput);
-               fs.put("totalPayloadOutputRate", total_payload_output_rate);
-               fs.put("totalPayloadOutputPercent", 
total_payload_output_percent);
-               fs.put("totalInputBytes", total[1]);
-               fs.put("totalInputRate", total_input_rate);
-               long[] rate = getNodeIOStats();
-               long delta = (rate[5] - rate[2]) / 1000;
-               long recent_output_rate = (rate[3] - rate[0]) / delta;
-               long recent_input_rate = (rate[4] - rate[1]) / delta;
-               fs.put("recentOutputRate", recent_output_rate);
-               fs.put("recentInputRate", recent_input_rate);
-
-               String [] routingBackoffReasons = 
getPeerNodeRoutingBackoffReasons();
-               if(routingBackoffReasons.length != 0) {
-                       for(int i=0;i<routingBackoffReasons.length;i++) {
-                               fs.put("numberWithRoutingBackoffReasons." + 
routingBackoffReasons[i], 
getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]));
-                       }
-               }
-
-               double swaps = (double)getSwaps();
-               double noSwaps = (double)getNoSwaps();
-               double numberOfRemotePeerLocationsSeenInSwaps = 
(double)getNumberOfRemotePeerLocationsSeenInSwaps();
-               fs.putSingle("numberOfRemotePeerLocationsSeenInSwaps", 
Double.toString(numberOfRemotePeerLocationsSeenInSwaps));
-               double avgConnectedPeersPerNode = 0.0;
-               if ((numberOfRemotePeerLocationsSeenInSwaps > 0.0) && ((swaps > 
0.0) || (noSwaps > 0.0))) {
-                       avgConnectedPeersPerNode = 
numberOfRemotePeerLocationsSeenInSwaps/(swaps+noSwaps);
-               }
-               fs.putSingle("avgConnectedPeersPerNode", 
Double.toString(avgConnectedPeersPerNode));
-
-               int startedSwaps = getStartedSwaps();
-               int swapsRejectedAlreadyLocked = 
getSwapsRejectedAlreadyLocked();
-               int swapsRejectedNowhereToGo = getSwapsRejectedNowhereToGo();
-               int swapsRejectedRateLimit = getSwapsRejectedRateLimit();
-               int swapsRejectedLoop = getSwapsRejectedLoop();
-               int swapsRejectedRecognizedID = getSwapsRejectedRecognizedID();
-               double locationChangePerSession = getLocationChangeSession();
-               double locationChangePerSwap = 0.0;
-               double locationChangePerMinute = 0.0;
-               double swapsPerMinute = 0.0;
-               double noSwapsPerMinute = 0.0;
-               double swapsPerNoSwaps = 0.0;
-               if (swaps > 0) {
-                       locationChangePerSwap = locationChangePerSession/swaps;
-               }
-               if ((swaps > 0.0) && (nodeUptimeSeconds >= 60)) {
-                       locationChangePerMinute = 
locationChangePerSession/(double)(nodeUptimeSeconds/60.0);
-               }
-               if ((swaps > 0.0) && (nodeUptimeSeconds >= 60)) {
-                       swapsPerMinute = swaps/(double)(nodeUptimeSeconds/60.0);
-               }
-               if ((noSwaps > 0.0) && (nodeUptimeSeconds >= 60)) {
-                       noSwapsPerMinute = 
noSwaps/(double)(nodeUptimeSeconds/60.0);
-               }
-               if ((swaps > 0.0) && (noSwaps > 0.0)) {
-                       swapsPerNoSwaps = swaps/noSwaps;
-               }
-               fs.put("locationChangePerSession", locationChangePerSession);
-               fs.put("locationChangePerSwap", locationChangePerSwap);
-               fs.put("locationChangePerMinute", locationChangePerMinute);
-               fs.put("swapsPerMinute", swapsPerMinute);
-               fs.put("noSwapsPerMinute", noSwapsPerMinute);
-               fs.put("swapsPerNoSwaps", swapsPerNoSwaps);
-               fs.put("swaps", swaps);
-               fs.put("noSwaps", noSwaps);
-               fs.put("startedSwaps", startedSwaps);
-               fs.put("swapsRejectedAlreadyLocked", 
swapsRejectedAlreadyLocked);
-               fs.put("swapsRejectedNowhereToGo", swapsRejectedNowhereToGo);
-               fs.put("swapsRejectedRateLimit", swapsRejectedRateLimit);
-               fs.put("swapsRejectedLoop", swapsRejectedLoop);
-               fs.put("swapsRejectedRecognizedID", swapsRejectedRecognizedID);
-
-               long fix32kb = 32 * 1024;
-               long cachedKeys = getChkDatacache().keyCount();
-               long cachedSize = cachedKeys * fix32kb;
-               long storeKeys = getChkDatastore().keyCount();
-               long storeSize = storeKeys * fix32kb;
-               long overallKeys = cachedKeys + storeKeys;
-               long overallSize = cachedSize + storeSize;
-               
-               long maxOverallKeys = getMaxTotalKeys();
-               long maxOverallSize = maxOverallKeys * fix32kb;
-               
-               double percentOverallKeysOfMax = 
(double)(overallKeys*100)/(double)maxOverallKeys;
-               
-               long cachedStoreHits = getChkDatacache().hits();
-               long cachedStoreMisses = getChkDatacache().misses();
-               long cacheAccesses = cachedStoreHits + cachedStoreMisses;
-               double percentCachedStoreHitsOfAccesses = 
(double)(cachedStoreHits*100) / (double)cacheAccesses;
-               long storeHits = getChkDatastore().hits();
-               long storeMisses = getChkDatastore().misses();
-               long storeAccesses = storeHits + storeMisses;
-               double percentStoreHitsOfAccesses = (double)(storeHits*100) / 
(double)storeAccesses;
-               long overallAccesses = storeAccesses + cacheAccesses;
-               double avgStoreAccessRate = 
(double)overallAccesses/(double)nodeUptimeSeconds;
-               
-               fs.put("cachedKeys", cachedKeys);
-               fs.put("cachedSize", cachedSize);
-               fs.put("storeKeys", storeKeys);
-               fs.put("storeSize", storeSize);
-               fs.put("overallKeys", overallKeys);
-               fs.put("overallSize", overallSize);
-               fs.put("maxOverallKeys", maxOverallKeys);
-               fs.put("maxOverallSize", maxOverallSize);
-               fs.put("percentOverallKeysOfMax", percentOverallKeysOfMax);
-               fs.put("cachedStoreHits", cachedStoreHits);
-               fs.put("cachedStoreMisses", cachedStoreMisses);
-               fs.put("cacheAccesses", cacheAccesses);
-               fs.put("percentCachedStoreHitsOfAccesses", 
percentCachedStoreHitsOfAccesses);
-               fs.put("storeHits", storeHits);
-               fs.put("storeMisses", storeMisses);
-               fs.put("storeAccesses", storeAccesses);
-               fs.put("percentStoreHitsOfAccesses", 
percentStoreHitsOfAccesses);
-               fs.put("overallAccesses", overallAccesses);
-               fs.put("avgStoreAccessRate", avgStoreAccessRate);
-
-               Runtime rt = Runtime.getRuntime();
-               float freeMemory = (float) rt.freeMemory();
-               float totalMemory = (float) rt.totalMemory();
-               float maxMemory = (float) rt.maxMemory();
-
-               long usedJavaMem = (long)(totalMemory - freeMemory);
-               long allocatedJavaMem = (long)totalMemory;
-               long maxJavaMem = (long)maxMemory;
-               int availableCpus = rt.availableProcessors();
-
-               fs.put("freeJavaMemory", (long)freeMemory);
-               fs.put("usedJavaMemory", usedJavaMem);
-               fs.put("allocatedJavaMemory", allocatedJavaMem);
-               fs.put("maximumJavaMemory", maxJavaMem);
-               fs.put("availableCPUs", availableCpus);
-               fs.put("runningThreadCount", getActiveThreadCount());
-               
-               return fs;
+               return nodeStats.exportVolatileFieldSet();
        }

        /**
@@ -3013,172 +2430,7 @@
                return false;
        }

-       public double getBwlimitDelayTime() {
-               return throttledPacketSendAverage.currentValue();
-       }
-       
-       public double getNodeAveragePingTime() {
-               return nodePinger.averagePingTime();
-       }
-
        /**
-        * Add a ARKFetcher to the map
-        */
-       /**
-        * Add a PeerNode status to the map
-        */
-       public void addPeerNodeStatus(int pnStatus, PeerNode peerNode) {
-               Integer peerNodeStatus = new Integer(pnStatus);
-               HashSet statusSet = null;
-               synchronized(peerNodeStatuses) {
-                       if(peerNodeStatuses.containsKey(peerNodeStatus)) {
-                               statusSet = (HashSet) 
peerNodeStatuses.get(peerNodeStatus);
-                               if(statusSet.contains(peerNode)) {
-                                       Logger.error(this, 
"addPeerNodeStatus(): identity '"+peerNode.getIdentityString()+"' already in 
peerNodeStatuses as "+peerNode+" with status code "+peerNodeStatus);
-                                       return;
-                               }
-                               peerNodeStatuses.remove(peerNodeStatus);
-                       } else {
-                               statusSet = new HashSet();
-                       }
-                       if(logMINOR) Logger.minor(this, "addPeerNodeStatus(): 
adding PeerNode for '"+peerNode.getIdentityString()+"' with status code 
"+peerNodeStatus);
-                       statusSet.add(peerNode);
-                       peerNodeStatuses.put(peerNodeStatus, statusSet);
-               }
-       }
-
-       /**
-        * How many PeerNodes have a particular status?
-        */
-       public int getPeerNodeStatusSize(int pnStatus) {
-               Integer peerNodeStatus = new Integer(pnStatus);
-               HashSet statusSet = null;
-               synchronized(peerNodeStatuses) {
-                       if(peerNodeStatuses.containsKey(peerNodeStatus)) {
-                               statusSet = (HashSet) 
peerNodeStatuses.get(peerNodeStatus);
-                       } else {
-                               statusSet = new HashSet();
-                       }
-                       return statusSet.size();
-               }
-       }
-
-       /**
-        * Remove a PeerNode status from the map
-        */
-       public void removePeerNodeStatus(int pnStatus, PeerNode peerNode) {
-               Integer peerNodeStatus = new Integer(pnStatus);
-               HashSet statusSet = null;
-               synchronized(peerNodeStatuses) {
-                       if(peerNodeStatuses.containsKey(peerNodeStatus)) {
-                               statusSet = (HashSet) 
peerNodeStatuses.get(peerNodeStatus);
-                               if(!statusSet.contains(peerNode)) {
-                                       Logger.error(this, 
"removePeerNodeStatus(): identity '"+peerNode.getIdentityString()+"' not in 
peerNodeStatuses with status code "+peerNodeStatus);
-                                       return;
-                               }
-                               peerNodeStatuses.remove(peerNodeStatus);
-                       } else {
-                               statusSet = new HashSet();
-                       }
-                       if(logMINOR) Logger.minor(this, 
"removePeerNodeStatus(): removing PeerNode for 
'"+peerNode.getIdentityString()+"' with status code "+peerNodeStatus);
-                       if(statusSet.contains(peerNode)) {
-                               statusSet.remove(peerNode);
-                       }
-                       peerNodeStatuses.put(peerNodeStatus, statusSet);
-               }
-       }
-
-       /**
-        * Log the current PeerNode status summary if the timer has expired
-        */
-       public void maybeLogPeerNodeStatusSummary(long now) {
-         if(now > nextPeerNodeStatusLogTime) {
-               if((now - nextPeerNodeStatusLogTime) > (10*1000) && 
nextPeerNodeStatusLogTime > 0)
-                 Logger.error(this,"maybeLogPeerNodeStatusSummary() not called 
for more than 10 seconds ("+(now - nextPeerNodeStatusLogTime)+").  PacketSender 
getting bogged down or something?");
-               
-               int numberOfConnected = 0;
-               int numberOfRoutingBackedOff = 0;
-               int numberOfTooNew = 0;
-               int numberOfTooOld = 0;
-               int numberOfDisconnected = 0;
-               int numberOfNeverConnected = 0;
-               int numberOfDisabled = 0;
-               int numberOfListenOnly = 0;
-               int numberOfListening = 0;
-               int numberOfBursting = 0;
-               
-               PeerNodeStatus[] pns = getPeerNodeStatuses();
-               
-               for(int i=0; i<pns.length; i++){
-                       switch (pns[i].getStatusValue()) {
-                       case PEER_NODE_STATUS_CONNECTED:
-                               numberOfConnected++;
-                               break;
-                       case PEER_NODE_STATUS_ROUTING_BACKED_OFF:
-                               numberOfRoutingBackedOff++;
-                               break;
-                       case PEER_NODE_STATUS_TOO_NEW:
-                               numberOfTooNew++;
-                               break;
-                       case PEER_NODE_STATUS_TOO_OLD:
-                               numberOfTooOld++;
-                               break;
-                       case PEER_NODE_STATUS_DISCONNECTED:
-                               numberOfDisconnected++;
-                               break;
-                       case PEER_NODE_STATUS_NEVER_CONNECTED:
-                               numberOfNeverConnected++;
-                               break;
-                       case PEER_NODE_STATUS_DISABLED:
-                               numberOfDisabled++;
-                               break;
-                       case PEER_NODE_STATUS_LISTEN_ONLY:
-                               numberOfListenOnly++;
-                               break;
-                       case PEER_NODE_STATUS_LISTENING:
-                               numberOfListening++;
-                               break;
-                       case PEER_NODE_STATUS_BURSTING:
-                               numberOfBursting++;
-                               break;
-                       default:
-                               Logger.error(this, "Unknown peer status value : 
"+pns[i].getStatusValue());
-                               break;
-                       }
-               }
-               Logger.normal(this, "Connected: "+numberOfConnected+"  Routing 
Backed Off: "+numberOfRoutingBackedOff+"  Too New: "+numberOfTooNew+"  Too Old: 
"+numberOfTooOld+"  Disconnected: "+numberOfDisconnected+"  Never Connected: 
"+numberOfNeverConnected+"  Disabled: "+numberOfDisabled+"  Bursting: 
"+numberOfBursting+"  Listening: "+numberOfListening+"  Listen Only: 
"+numberOfListenOnly);
-               nextPeerNodeStatusLogTime = now + peerNodeStatusLogInterval;
-               }
-       }
-
-       /**
-        * Update oldestNeverConnectedPeerAge if the timer has expired
-        */
-       public void maybeUpdateOldestNeverConnectedPeerAge(long now) {
-         if(now > nextOldestNeverConnectedPeerAgeUpdateTime) {
-               oldestNeverConnectedPeerAge = 0;
-               if(peers != null) {
-                 PeerNode[] peerList = peers.myPeers;
-                 for(int i=0;i<peerList.length;i++) {
-                       PeerNode pn = peerList[i];
-                       if(pn.getPeerNodeStatus() == 
PEER_NODE_STATUS_NEVER_CONNECTED) {
-                         if((now - pn.getPeerAddedTime()) > 
oldestNeverConnectedPeerAge) {
-                               oldestNeverConnectedPeerAge = now - 
pn.getPeerAddedTime();
-                         }
-                       }
-                 }
-               }
-               if(oldestNeverConnectedPeerAge > 0 && logMINOR)
-                 Logger.minor(this, "Oldest never connected peer is 
"+oldestNeverConnectedPeerAge+"ms old");
-               nextOldestNeverConnectedPeerAgeUpdateTime = now + 
oldestNeverConnectedPeerAgeUpdateInterval;
-         }
-       }
-
-       public long getOldestNeverConnectedPeerAge() {
-         return oldestNeverConnectedPeerAge;
-       }
-
-       /**
         * Handle a received node to node message
         */
        public void receivedNodeToNodeMessage(Message m) {
@@ -3295,14 +2547,6 @@
          return usm;
        }

-       public int getNetworkSizeEstimate(long timestamp) {
-         return lm.getNetworkSizeEstimate( timestamp );
-       }
-
-       public Object[] getKnownLocations(long timestamp) {
-         return lm.getKnownLocations( timestamp );
-       }
-       
        public int getSwaps() {
                return LocationManager.swaps;
        }
@@ -3335,115 +2579,6 @@
                return LocationManager.swapsRejectedRecognizedID;
        }

-       /**
-        * Add a PeerNode routing backoff reason to the map
-        */
-       public void addPeerNodeRoutingBackoffReason(String 
peerNodeRoutingBackoffReason, PeerNode peerNode) {
-               synchronized(peerNodeRoutingBackoffReasons) {
-                       HashSet reasonSet = null;
-                       
if(peerNodeRoutingBackoffReasons.containsKey(peerNodeRoutingBackoffReason)) {
-                               reasonSet = (HashSet) 
peerNodeRoutingBackoffReasons.get(peerNodeRoutingBackoffReason);
-                               if(reasonSet.contains(peerNode)) {
-                                       Logger.error(this, 
"addPeerNodeRoutingBackoffReason(): identity '"+peerNode.getIdentityString()+"' 
already in peerNodeRoutingBackoffReasons as "+peerNode+" with status code 
"+peerNodeRoutingBackoffReason);
-                                       return;
-                               }
-                               
peerNodeRoutingBackoffReasons.remove(peerNodeRoutingBackoffReason);
-                       } else {
-                               reasonSet = new HashSet();
-                       }
-                       if(logMINOR) Logger.minor(this, 
"addPeerNodeRoutingBackoffReason(): adding PeerNode for 
'"+peerNode.getIdentityString()+"' with status code 
"+peerNodeRoutingBackoffReason);
-                       reasonSet.add(peerNode);
-                       
peerNodeRoutingBackoffReasons.put(peerNodeRoutingBackoffReason, reasonSet);
-               }
-       }
-       
-       /**
-        * What are the currently tracked PeerNode routing backoff reasons?
-        */
-       public String [] getPeerNodeRoutingBackoffReasons() {
-               String [] reasonStrings;
-               synchronized(peerNodeRoutingBackoffReasons) {
-                       reasonStrings = (String []) 
peerNodeRoutingBackoffReasons.keySet().toArray(new 
String[peerNodeRoutingBackoffReasons.size()]);
-               }
-               Arrays.sort(reasonStrings);
-               return reasonStrings;
-       }
-       
-       /**
-        * How many PeerNodes have a particular routing backoff reason?
-        */
-       public int getPeerNodeRoutingBackoffReasonSize(String 
peerNodeRoutingBackoffReason) {
-               HashSet reasonSet = null;
-               synchronized(peerNodeRoutingBackoffReasons) {
-                       
if(peerNodeRoutingBackoffReasons.containsKey(peerNodeRoutingBackoffReason)) {
-                               reasonSet = (HashSet) 
peerNodeRoutingBackoffReasons.get(peerNodeRoutingBackoffReason);
-                               return reasonSet.size();
-                       } else {
-                               return 0;
-                       }
-               }
-       }
-
-       /**
-        * Remove a PeerNode routing backoff reason from the map
-        */
-       public void removePeerNodeRoutingBackoffReason(String 
peerNodeRoutingBackoffReason, PeerNode peerNode) {
-               HashSet reasonSet = null;
-               synchronized(peerNodeRoutingBackoffReasons) {
-                       
if(peerNodeRoutingBackoffReasons.containsKey(peerNodeRoutingBackoffReason)) {
-                               reasonSet = (HashSet) 
peerNodeRoutingBackoffReasons.get(peerNodeRoutingBackoffReason);
-                               if(!reasonSet.contains(peerNode)) {
-                                       Logger.error(this, 
"removePeerNodeRoutingBackoffReason(): identity 
'"+peerNode.getIdentityString()+"' not in peerNodeRoutingBackoffReasons with 
status code "+peerNodeRoutingBackoffReason);
-                                       return;
-                               }
-                               
peerNodeRoutingBackoffReasons.remove(peerNodeRoutingBackoffReason);
-                       } else {
-                               reasonSet = new HashSet();
-                       }
-                       if(logMINOR) Logger.minor(this, 
"removePeerNodeRoutingBackoffReason(): removing PeerNode for 
'"+peerNode.getIdentityString()+"' with status code 
"+peerNodeRoutingBackoffReason);
-                       if(reasonSet.contains(peerNode)) {
-                               reasonSet.remove(peerNode);
-                       }
-                       if(reasonSet.size() > 0) {
-                               
peerNodeRoutingBackoffReasons.put(peerNodeRoutingBackoffReason, reasonSet);
-                       }
-               }
-       }
-
-       /**
-        * Update peerManagerUserAlertStats if the timer has expired
-        */
-       public void maybeUpdatePeerManagerUserAlertStats(long now) {
-               if(now > nextPeerManagerUserAlertStatsUpdateTime) {
-                       if(getBwlimitDelayTime() > 
MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD) {
-                               if(firstBwlimitDelayTimeThresholdBreak == 0) {
-                                       firstBwlimitDelayTimeThresholdBreak = 
now;
-                               }
-                       } else {
-                               firstBwlimitDelayTimeThresholdBreak = 0;
-                       }
-                       if((firstBwlimitDelayTimeThresholdBreak != 0) && ((now 
- firstBwlimitDelayTimeThresholdBreak) >= MAX_BWLIMIT_DELAY_TIME_ALERT_DELAY)) {
-                               bwlimitDelayAlertRelevant = true;
-                       } else {
-                               bwlimitDelayAlertRelevant = false;
-                       }
-                       if(getNodeAveragePingTime() > 
MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD) {
-                               if(firstNodeAveragePingTimeThresholdBreak == 0) 
{
-                                       firstNodeAveragePingTimeThresholdBreak 
= now;
-                               }
-                       } else {
-                               firstNodeAveragePingTimeThresholdBreak = 0;
-                       }
-                       if((firstNodeAveragePingTimeThresholdBreak != 0) && 
((now - firstNodeAveragePingTimeThresholdBreak) >= 
MAX_NODE_AVERAGE_PING_TIME_ALERT_DELAY)) {
-                               nodeAveragePingAlertRelevant = true;
-                       } else {
-                               nodeAveragePingAlertRelevant = false;
-                       }
-                       if(logMINOR && Logger.shouldLog(Logger.DEBUG, this)) 
Logger.debug(this, "mUPMUAS: "+now+": "+getBwlimitDelayTime()+" >? 
"+MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD+" since 
"+firstBwlimitDelayTimeThresholdBreak+" ("+bwlimitDelayAlertRelevant+") 
"+getNodeAveragePingTime()+" >? "+MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD+" 
since "+firstNodeAveragePingTimeThresholdBreak+" 
("+nodeAveragePingAlertRelevant+ ')');
-                       nextPeerManagerUserAlertStatsUpdateTime = now + 
peerManagerUserAlertStatsUpdateInterval;
-               }
-       }
-
        public PeerNode[] getPeerNodes() {
                return peers.myPeers;
        }
@@ -3452,14 +2587,6 @@
                return peers.connectedPeers;
        }

-       public PeerNodeStatus[] getPeerNodeStatuses() {
-               PeerNodeStatus[] peerNodeStatuses = new 
PeerNodeStatus[peers.myPeers.length];
-               for (int peerIndex = 0, peerCount = peers.myPeers.length; 
peerIndex < peerCount; peerIndex++) {
-                       peerNodeStatuses[peerIndex] = 
peers.myPeers[peerIndex].getStatus();
-               }
-               return peerNodeStatuses;
-       }
-
        /**
         * Return a peer of the node given its ip and port, name or identity, 
as a String
         */
@@ -3490,10 +2617,6 @@
                clientCore.queueRandomReinsert(block);
        }

-       public double pRejectIncomingInstantly() {
-               return pInstantRejectIncoming.currentValue();
-       }
-       
        public String getExtraPeerDataDir() {
                return extraPeerDataDir.getPath();
        }
@@ -3525,148 +2648,6 @@
        // FIXME convert these kind of threads to Checkpointed's and implement 
a handler
        // using the PacketSender/Ticker. Would save a few threads.

-       class ThrottlePersister implements Runnable {
-
-               void interrupt() {
-                       synchronized(this) {
-                               notifyAll();
-                       }
-               }
-               
-               public void run() {
-                       while(true) {
-                               try {
-                                       persistThrottle();
-                               } catch (OutOfMemoryError e) {
-                                       OOMHandler.handleOOM(e);
-                                       System.err.println("Will restart 
ThrottlePersister...");
-                               } catch (Throwable t) {
-                                       Logger.error(this, "Caught in 
ThrottlePersister: "+t, t);
-                                       System.err.println("Caught in 
ThrottlePersister: "+t);
-                                       t.printStackTrace();
-                                       System.err.println("Will restart 
ThrottlePersister...");
-                               }
-                               try {
-                                       synchronized(this) {
-                                               wait(60*1000);
-                                       }
-                               } catch (InterruptedException e) {
-                                       // Maybe it's time to wake up?
-                               }
-                       }
-               }
-               
-       }
-
-       public void persistThrottle() {
-               if(logMINOR) Logger.minor(this, "Trying to persist 
throttles...");
-               SimpleFieldSet fs = persistThrottlesToFieldSet();
-               try {
-                       FileOutputStream fos = new 
FileOutputStream(persistTemp);
-                       // FIXME common pattern, reuse it.
-                       BufferedOutputStream bos = new 
BufferedOutputStream(fos);
-                       OutputStreamWriter osw = new OutputStreamWriter(bos, 
"UTF-8");
-                       BufferedWriter bw = new BufferedWriter(osw);
-                       try {
-                               fs.writeTo(bw);
-                       } catch (IOException e) {
-                               try {
-                                       fos.close();
-                                       persistTemp.delete();
-                                       return;
-                               } catch (IOException e1) {
-                                       // Ignore
-                               }
-                       }
-                       try {
-                               bw.close();
-                       } catch (IOException e) {
-                               // Huh?
-                               Logger.error(this, "Caught while closing: "+e, 
e);
-                               return;
-                       }
-                       // Try an atomic rename
-                       if(!persistTemp.renameTo(persistTarget)) {
-                               // Not supported on some systems (Windows)
-                               if(!persistTarget.delete()) {
-                                       if(persistTarget.exists()) {
-                                               Logger.error(this, "Could not 
delete "+persistTarget+" - check permissions");
-                                       }
-                               }
-                               if(!persistTemp.renameTo(persistTarget)) {
-                                       Logger.error(this, "Could not rename 
"+persistTemp+" to "+persistTarget+" - check permissions");
-                               }
-                       }
-               } catch (FileNotFoundException e) {
-                       Logger.error(this, "Could not store throttle data to 
disk: "+e, e);
-                       return;
-               } catch (UnsupportedEncodingException e) {
-                       Logger.error(this, "Unsupported encoding: UTF-8 !!!!: 
"+e, e);
-               }
-               
-       }
-
-       private SimpleFieldSet persistThrottlesToFieldSet() {
-               SimpleFieldSet fs = new SimpleFieldSet(true);
-               fs.put("RequestStarters", 
clientCore.requestStarters.persistToFieldSet());
-               fs.put("RemoteChkFetchBytesSentAverage", 
remoteChkFetchBytesSentAverage.exportFieldSet(true));
-               fs.put("RemoteSskFetchBytesSentAverage", 
remoteSskFetchBytesSentAverage.exportFieldSet(true));
-               fs.put("RemoteChkInsertBytesSentAverage", 
remoteChkInsertBytesSentAverage.exportFieldSet(true));
-               fs.put("RemoteSskInsertBytesSentAverage", 
remoteSskInsertBytesSentAverage.exportFieldSet(true));
-               fs.put("RemoteChkFetchBytesReceivedAverage", 
remoteChkFetchBytesReceivedAverage.exportFieldSet(true));
-               fs.put("RemoteSskFetchBytesReceivedAverage", 
remoteSskFetchBytesReceivedAverage.exportFieldSet(true));
-               fs.put("RemoteChkInsertBytesReceivedAverage", 
remoteChkInsertBytesReceivedAverage.exportFieldSet(true));
-               fs.put("RemoteSskInsertBytesReceivedAverage", 
remoteSskInsertBytesReceivedAverage.exportFieldSet(true));
-               fs.put("LocalChkFetchBytesSentAverage", 
localChkFetchBytesSentAverage.exportFieldSet(true));
-               fs.put("LocalSskFetchBytesSentAverage", 
localSskFetchBytesSentAverage.exportFieldSet(true));
-               fs.put("LocalChkInsertBytesSentAverage", 
localChkInsertBytesSentAverage.exportFieldSet(true));
-               fs.put("LocalSskInsertBytesSentAverage", 
localSskInsertBytesSentAverage.exportFieldSet(true));
-               fs.put("LocalChkFetchBytesReceivedAverage", 
localChkFetchBytesReceivedAverage.exportFieldSet(true));
-               fs.put("LocalSskFetchBytesReceivedAverage", 
localSskFetchBytesReceivedAverage.exportFieldSet(true));
-               fs.put("LocalChkInsertBytesReceivedAverage", 
localChkInsertBytesReceivedAverage.exportFieldSet(true));
-               fs.put("LocalSskInsertBytesReceivedAverage", 
localSskInsertBytesReceivedAverage.exportFieldSet(true));
-
-               // FIXME persist the rest
-               return fs;
-       }
-
-       /**
-        * Update the node-wide bandwidth I/O stats if the timer has expired
-        */
-       public void maybeUpdateNodeIOStats(long now) {
-               if(now > nextNodeIOStatsUpdateTime) {
-                       long[] io_stats = IOStatisticCollector.getTotalIO();
-                       long outdiff;
-                       long indiff;
-                       synchronized(ioStatSync) {
-                               previous_output_stat = last_output_stat;
-                               previous_input_stat = last_input_stat;
-                               previous_io_stat_time = last_io_stat_time;
-                               last_output_stat = io_stats[ 0 ];
-                               last_input_stat = io_stats[ 1 ];
-                               last_io_stat_time = now;
-                               outdiff = last_output_stat - 
previous_output_stat;
-                               indiff = last_input_stat - previous_input_stat;
-                       }
-                       if(logMINOR)
-                               Logger.minor(this, "Last 2 seconds: input: 
"+indiff+" output: "+outdiff);
-                       nextNodeIOStatsUpdateTime = now + 
nodeIOStatsUpdateInterval;
-               }
-       }
-
-       public long[] getNodeIOStats() {
-               long[] result = new long[6];
-               synchronized(ioStatSync) {
-                       result[ 0 ] = previous_output_stat;
-                       result[ 1 ] = previous_input_stat;
-                       result[ 2 ] = previous_io_stat_time;
-                       result[ 3 ] = last_output_stat;
-                       result[ 4 ] = last_input_stat;
-                       result[ 5 ] = last_io_stat_time;
-               }
-               return result;
-       }
-
        public int getNumARKFetchers() {
                PeerNode[] p = peers.myPeers;
                int x = 0;
@@ -3705,30 +2686,6 @@
                return myPubKey;
        }

-       public void waitUntilNotOverloaded(boolean isInsert) {
-               while(threadLimit < getActiveThreadCount()){
-                       try{
-                               wait(5000);
-                       } catch (InterruptedException e) {}
-               }
-       }
-
-       /**
-        * Update hadRoutableConnectionCount/routableConnectionCheckCount on 
peers if the timer has expired
-        */
-       public void maybeUpdatePeerNodeRoutableConnectionStats(long now) {
-               if(now > nextRoutableConnectionStatsUpdateTime) {
-                       if(peers != null && -1 != 
nextRoutableConnectionStatsUpdateTime) {
-                               PeerNode[] peerList = peers.myPeers;
-                               for(int i=0;i<peerList.length;i++) {
-                                       PeerNode pn = peerList[i];
-                                       pn.checkRoutableConnectionStatus();
-                               }
-                       }
-                       nextRoutableConnectionStatsUpdateTime = now + 
routableConnectionStatsUpdateInterval;
-               }
-       }
-
        public Ticker getTicker() {
                return ps;
        }
@@ -3760,12 +2717,4 @@
                        System.out.println("Failed to get stats from JE 
environment: " + e);
                }
        }
-       
-       public int getActiveThreadCount() {
-               return rootThreadGroup.activeCount();
-       }
-
-       public int getThreadLimit() {
-               return threadLimit;
-       }
 }

Modified: trunk/freenet/src/freenet/node/NodeClientCore.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeClientCore.java  2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/NodeClientCore.java  2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -1,6 +1,7 @@
 package freenet.node;

 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.URI;

@@ -57,7 +58,7 @@
 /**
  * The connection between the node and the client layer.
  */
-public class NodeClientCore {
+public class NodeClientCore implements Persistable {

        private static boolean logMINOR;
        public final USKManager uskManager;
@@ -76,6 +77,7 @@
        final FilenameGenerator tempFilenameGenerator;
        public final BucketFactory tempBucketFactory;
        final Node node;
+       final NodeStats nodeStats;
        public final RandomSource random;
        final File tempDir;

@@ -96,6 +98,7 @@
        public boolean ignoreTooManyPathComponents;
        /** If true, requests are resumed lazily i.e. startup does not block 
waiting for them. */
        private boolean lazyResume;
+       protected final Persister persister;

        // Client stuff that needs to be configged - FIXME
        static final int MAX_ARCHIVE_HANDLERS = 200; // don't take up much 
RAM... FIXME
@@ -104,8 +107,9 @@
        static final long MAX_ARCHIVED_FILE_SIZE = 1024*1024; // arbitrary... 
FIXME
        static final int MAX_CACHED_ELEMENTS = 1024; // equally arbitrary! 
FIXME hopefully we can cache many of these though

-       NodeClientCore(Node node, Config config, SubConfig nodeConfig, File 
nodeDir, int portNumber, int sortOrder, SimpleFieldSet throttleFS) throws 
NodeInitException {
+       NodeClientCore(Node node, Config config, SubConfig nodeConfig, File 
nodeDir, int portNumber, int sortOrder, SimpleFieldSet oldThrottleFS) throws 
NodeInitException {
                this.node = node;
+               this.nodeStats = node.nodeStats;
                this.random = node.random;
                this.backgroundBlockEncoder = new BackgroundBlockEncoder();
                Thread t = new Thread(backgroundBlockEncoder, "Background block 
encoder");
@@ -117,6 +121,17 @@
                this.formPassword = Base64.encode(pwdBuf);
                alerts = new UserAlertManager(this);
                logMINOR = Logger.shouldLog(Logger.MINOR, this);
+               
+               persister = new ConfigurablePersister(this, nodeConfig, 
"clientThrottleFile", "client-throttle.dat", sortOrder++, true, false, 
+                               "File to store client statistics in", "File to 
store client throttling statistics in (used to decide how often to send 
requests)");
+               
+               SimpleFieldSet throttleFS = persister.read();
+               
+               if(throttleFS == null)
+                       throttleFS = oldThrottleFS;
+
+               if(logMINOR) Logger.minor(this, "Read 
throttleFS:\n"+throttleFS);
+               
                if(logMINOR) Logger.minor(this, "Serializing 
RequestStarterGroup from:\n"+throttleFS);
                requestStarters = new RequestStarterGroup(node, this, 
portNumber, random, config, throttleFS);

@@ -333,6 +348,8 @@
        }

        public void start(Config config) throws NodeInitException {
+
+               persister.start();

                // TMCI
                try{
@@ -428,8 +445,8 @@

                if(status != RequestSender.TIMED_OUT && status != 
RequestSender.GENERATED_REJECTED_OVERLOAD && status != 
RequestSender.INTERNAL_ERROR) {
                        if(logMINOR) Logger.minor(this, "CHK fetch cost 
"+rs.getTotalSentBytes()+ '/' +rs.getTotalReceivedBytes()+" bytes ("+status+ 
')');
-               
node.localChkFetchBytesSentAverage.report(rs.getTotalSentBytes());
-               
node.localChkFetchBytesReceivedAverage.report(rs.getTotalReceivedBytes());
+               
nodeStats.localChkFetchBytesSentAverage.report(rs.getTotalSentBytes());
+               
nodeStats.localChkFetchBytesReceivedAverage.report(rs.getTotalReceivedBytes());
                }

                        if((status == RequestSender.TIMED_OUT) ||
@@ -527,8 +544,8 @@

                if(status != RequestSender.TIMED_OUT && status != 
RequestSender.GENERATED_REJECTED_OVERLOAD && status != 
RequestSender.INTERNAL_ERROR) {
                if(logMINOR) Logger.minor(this, "SSK fetch cost 
"+rs.getTotalSentBytes()+ '/' +rs.getTotalReceivedBytes()+" bytes ("+status+ 
')');
-               
node.localSskFetchBytesSentAverage.report(rs.getTotalSentBytes());
-               
node.localSskFetchBytesReceivedAverage.report(rs.getTotalReceivedBytes());
+               
nodeStats.localSskFetchBytesSentAverage.report(rs.getTotalSentBytes());
+               
nodeStats.localSskFetchBytesReceivedAverage.report(rs.getTotalReceivedBytes());
                }

                        if((status == RequestSender.TIMED_OUT) ||
@@ -670,8 +687,8 @@
                int sent = is.getTotalSentBytes();
                int received = is.getTotalReceivedBytes();
                if(logMINOR) Logger.minor(this, "Local CHK insert cost "+sent+ 
'/' +received+" bytes ("+status+ ')');
-               node.localChkInsertBytesSentAverage.report(sent);
-               node.localChkInsertBytesReceivedAverage.report(received);
+               nodeStats.localChkInsertBytesSentAverage.report(sent);
+               nodeStats.localChkInsertBytesReceivedAverage.report(received);
         }

                if(status == CHKInsertSender.SUCCESS) {
@@ -777,8 +794,8 @@
                int sent = is.getTotalSentBytes();
                int received = is.getTotalReceivedBytes();
                if(logMINOR) Logger.minor(this, "Local SSK insert cost "+sent+ 
'/' +received+" bytes ("+status+ ')');
-               node.localSskInsertBytesSentAverage.report(sent);
-               node.localSskInsertBytesReceivedAverage.report(received);
+               nodeStats.localSskInsertBytesSentAverage.report(sent);
+               nodeStats.localSskInsertBytesReceivedAverage.report(received);
         }

                if(is.hasCollided()) {
@@ -970,4 +987,8 @@
                        if(filename == null) return false;
                }
        }
+
+       public SimpleFieldSet persistThrottlesToFieldSet() {
+               return requestStarters.persistToFieldSet();
+       }
 }

Modified: trunk/freenet/src/freenet/node/NodeDispatcher.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeDispatcher.java  2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/NodeDispatcher.java  2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -38,9 +38,11 @@

        private static boolean logMINOR;
        final Node node;
+       final NodeStats nodeStats;

        NodeDispatcher(Node node) {
                this.node = node;
+               this.nodeStats = node.nodeStats;
                logMINOR = Logger.shouldLog(Logger.MINOR, this);
        }

@@ -140,7 +142,7 @@
                        }
                        return true;
                }
-               String rejectReason = node.shouldRejectRequest(!isSSK, false, 
isSSK);
+               String rejectReason = nodeStats.shouldRejectRequest(!isSSK, 
false, isSSK);
                if(rejectReason != null) {
                        // can accept 1 CHK request every so often, but not 
with SSKs because they aren't throttled so won't sort out bwlimitDelayTime, 
which was the whole reason for accepting them when overloaded...
                        Logger.normal(this, "Rejecting request from 
"+m.getSource().getPeer()+" preemptively because "+rejectReason);
@@ -185,7 +187,7 @@
                        return true;
                }
                // SSKs don't fix bwlimitDelayTime so shouldn't be accepted 
when overloaded.
-               String rejectReason = node.shouldRejectRequest(!isSSK, true, 
isSSK);
+               String rejectReason = nodeStats.shouldRejectRequest(!isSSK, 
true, isSSK);
                if(rejectReason != null) {
                        Logger.normal(this, "Rejecting insert from 
"+m.getSource().getPeer()+" preemptively because "+rejectReason);
                        Message rejected = DMT.createFNPRejectedOverload(id, 
true);

Added: trunk/freenet/src/freenet/node/NodeStats.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeStats.java                               
(rev 0)
+++ trunk/freenet/src/freenet/node/NodeStats.java       2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -0,0 +1,723 @@
+package freenet.node;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.text.DecimalFormat;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import freenet.config.InvalidConfigValueException;
+import freenet.config.SubConfig;
+import freenet.crypt.RandomSource;
+import freenet.io.comm.DMT;
+import freenet.io.comm.IOStatisticCollector;
+import freenet.node.Node.NodeInitException;
+import freenet.support.Logger;
+import freenet.support.OOMHandler;
+import freenet.support.SimpleFieldSet;
+import freenet.support.SizeUtil;
+import freenet.support.TimeUtil;
+import freenet.support.TokenBucket;
+import freenet.support.api.IntCallback;
+import freenet.support.api.StringCallback;
+import freenet.support.math.RunningAverage;
+import freenet.support.math.TimeDecayingRunningAverage;
+
+/** Node (as opposed to NodeClientCore) level statistics. Includes 
shouldRejectRequest(), but not limited
+ * to stuff required to implement that. */
+public class NodeStats implements Persistable {
+
+       /** Minimum free heap memory bytes required to accept a request 
(perhaps fewer OOMs this way) */
+       public static final long MIN_FREE_HEAP_BYTES_FOR_ROUTING_SUCCESS = 3L * 
1024 * 1024;  // 3 MiB
+       
+       /** Minimum free heap memory percentage required to accept a request 
(perhaps fewer OOMs this way) */
+       public static final double MIN_FREE_HEAP_PERCENT_FOR_ROUTING_SUCCESS = 
0.01;  // 1%
+       
+       /** Sub-max ping time. If ping is greater than this, we reject some 
requests. */
+       public static final long SUB_MAX_PING_TIME = 700;
+       /** Maximum overall average ping time. If ping is greater than this,
+        * we reject all requests. */
+       public static final long MAX_PING_TIME = 1500;
+       /** Maximum throttled packet delay. If the throttled packet delay is 
greater
+        * than this, reject all packets. */
+       public static final long MAX_THROTTLE_DELAY = 3000;
+       /** If the throttled packet delay is less than this, reject no packets; 
if it's
+        * between the two, reject some packets. */
+       public static final long SUB_MAX_THROTTLE_DELAY = 2000;
+       /** How high can bwlimitDelayTime be before we alert (in milliseconds)*/
+       public static final long MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD = 
MAX_THROTTLE_DELAY*2;
+       /** How high can nodeAveragePingTime be before we alert (in 
milliseconds)*/
+       public static final long MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD = 
MAX_PING_TIME*2;
+       /** How long we're over the bwlimitDelayTime threshold before we alert 
(in milliseconds)*/
+       public static final long MAX_BWLIMIT_DELAY_TIME_ALERT_DELAY = 
10*60*1000;  // 10 minutes
+       /** How long we're over the nodeAveragePingTime threshold before we 
alert (in milliseconds)*/
+       public static final long MAX_NODE_AVERAGE_PING_TIME_ALERT_DELAY = 
10*60*1000;  // 10 minutes
+       /** Accept one request every 10 seconds regardless, to ensure we update 
the
+        * block send time.
+        */
+       public static final int MAX_INTERREQUEST_TIME = 10*1000;
+
+       private final Node node;
+       public final PeerManager peers;
+       
+       final RandomSource hardRandom;
+       
+       private boolean logMINOR;
+       
+       /** Memory Checker thread */
+       private final Thread myMemoryChecker;
+
+       /** first time bwlimitDelay was over PeerManagerUserAlert threshold */
+       private long firstBwlimitDelayTimeThresholdBreak ;
+       /** first time nodeAveragePing was over PeerManagerUserAlert threshold 
*/
+       private long firstNodeAveragePingTimeThresholdBreak;
+       /** bwlimitDelay PeerManagerUserAlert should happen if true */
+       public boolean bwlimitDelayAlertRelevant;
+       /** nodeAveragePing PeerManagerUserAlert should happen if true */
+       public boolean nodeAveragePingAlertRelevant;
+       /** Average proportion of requests rejected immediately due to overload 
*/
+       public final TimeDecayingRunningAverage pInstantRejectIncoming;
+
+       /** Average delay caused by throttling for sending a packet */
+       final TimeDecayingRunningAverage throttledPacketSendAverage;
+       
+       // Stats
+       final TimeDecayingRunningAverage remoteChkFetchBytesSentAverage;
+       final TimeDecayingRunningAverage remoteSskFetchBytesSentAverage;
+       final TimeDecayingRunningAverage remoteChkInsertBytesSentAverage;
+       final TimeDecayingRunningAverage remoteSskInsertBytesSentAverage;
+       final TimeDecayingRunningAverage remoteChkFetchBytesReceivedAverage;
+       final TimeDecayingRunningAverage remoteSskFetchBytesReceivedAverage;
+       final TimeDecayingRunningAverage remoteChkInsertBytesReceivedAverage;
+       final TimeDecayingRunningAverage remoteSskInsertBytesReceivedAverage;
+       final TimeDecayingRunningAverage localChkFetchBytesSentAverage;
+       final TimeDecayingRunningAverage localSskFetchBytesSentAverage;
+       final TimeDecayingRunningAverage localChkInsertBytesSentAverage;
+       final TimeDecayingRunningAverage localSskInsertBytesSentAverage;
+       final TimeDecayingRunningAverage localChkFetchBytesReceivedAverage;
+       final TimeDecayingRunningAverage localSskFetchBytesReceivedAverage;
+       final TimeDecayingRunningAverage localChkInsertBytesReceivedAverage;
+       final TimeDecayingRunningAverage localSskInsertBytesReceivedAverage;
+
+       File persistTarget; 
+       File persistTemp;
+       private long previous_input_stat;
+       private long previous_output_stat;
+       private long previous_io_stat_time;
+       private long last_input_stat;
+       private long last_output_stat;
+       private long last_io_stat_time;
+       private final Object ioStatSync = new Object();
+       /** Next time to update the node I/O stats */
+       private long nextNodeIOStatsUpdateTime = -1;
+       /** Node I/O stats update interval (milliseconds) */
+       private static final long nodeIOStatsUpdateInterval = 2000;
+       
+       /** Token bucket for output bandwidth used by requests */
+       final TokenBucket requestOutputThrottle;
+       /** Token bucket for input bandwidth used by requests */
+       final TokenBucket requestInputThrottle;
+
+       // various metrics
+       public RunningAverage routingMissDistance = new 
TimeDecayingRunningAverage(0.0, 180000, 0.0, 1.0);
+       public RunningAverage backedOffPercent = new 
TimeDecayingRunningAverage(0.0, 180000, 0.0, 1.0);
+       protected final Persister persister;
+       
+       // ThreadCounting stuffs
+       private final ThreadGroup rootThreadGroup;
+       private int threadLimit;
+       
+       final NodePinger nodePinger;
+
+       // Peers stats
+       /** Next time to update PeerManagerUserAlert stats */
+       private long nextPeerManagerUserAlertStatsUpdateTime = -1;
+       /** PeerManagerUserAlert stats update interval (milliseconds) */
+       private static final long peerManagerUserAlertStatsUpdateInterval = 
1000;  // 1 second
+       
+       NodeStats(Node node, int sortOrder, SubConfig statsConfig, 
SimpleFieldSet oldThrottleFS, int obwLimit, int ibwLimit) throws 
NodeInitException {
+               logMINOR = Logger.shouldLog(Logger.MINOR, this);
+               this.node = node;
+               this.peers = node.peers;
+               this.hardRandom = node.random;
+               pInstantRejectIncoming = new TimeDecayingRunningAverage(0, 
60000, 0.0, 1.0);
+               ThreadGroup tg = Thread.currentThread().getThreadGroup();
+               while(tg.getParent() != null) tg = tg.getParent();
+               this.rootThreadGroup = tg;
+               throttledPacketSendAverage =
+                       new TimeDecayingRunningAverage(1, 10*60*1000 /* should 
be significantly longer than a typical transfer */, 0, Long.MAX_VALUE);
+               //Memory Checking thread
+               // TODO: proper config. callbacks : maybe we shoudln't start 
the thread at all if it's not worthy
+               this.myMemoryChecker = new Thread(new MemoryChecker(), "Memory 
checker");
+               this.myMemoryChecker.setPriority(Thread.MAX_PRIORITY);
+               this.myMemoryChecker.setDaemon(true);
+               
+               nodePinger = new NodePinger(node);
+
+               previous_input_stat = 0;
+               previous_output_stat = 0;
+               previous_io_stat_time = 1;
+               last_input_stat = 0;
+               last_output_stat = 0;
+               last_io_stat_time = 3;
+
+               statsConfig.register("threadLimit", 300, sortOrder++, true, 
true, "Thread limit", "The node will try to limit its thread usage to the 
specified value, refusing new requests",
+                               new IntCallback() {
+                                       public int get() {
+                                               return threadLimit;
+                                       }
+                                       public void set(int val) throws 
InvalidConfigValueException {
+                                               if(val == get()) return;
+                                               if(val < 250)
+                                                       throw new 
InvalidConfigValueException("This value is to low for that setting, increase 
it!");
+                                               threadLimit = val;
+                                       }
+               });
+               
+               
+               threadLimit = statsConfig.getInt("threadLimit");
+               
+               persister = new ConfigurablePersister(this, statsConfig, 
"throttleFile", "node-throttle.dat", sortOrder++, true, false, 
+                               "File to store node statistics in", "File to 
store node statistics in (not client statistics, and these are used to decide 
whether to accept requests so please don't delete)");
+               
+               SimpleFieldSet throttleFS = persister.read();
+               
+               if(throttleFS == null)
+                       throttleFS = oldThrottleFS;
+
+               if(logMINOR) Logger.minor(this, "Read 
throttleFS:\n"+throttleFS);
+               
+               // Guesstimates. Hopefully well over the reality.
+               localChkFetchBytesSentAverage = new 
TimeDecayingRunningAverage(500, 180000, 0.0, 1024*1024*1024, throttleFS == null 
? null : throttleFS.subset("LocalChkFetchBytesSentAverage"));
+               localSskFetchBytesSentAverage = new 
TimeDecayingRunningAverage(500, 180000, 0.0, 1024*1024*1024, throttleFS == null 
? null : throttleFS.subset("LocalSskFetchBytesSentAverage"));
+               localChkInsertBytesSentAverage = new 
TimeDecayingRunningAverage(32768, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalChkInsertBytesSentAverage"));
+               localSskInsertBytesSentAverage = new 
TimeDecayingRunningAverage(2048, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalSskInsertBytesSentAverage"));
+               localChkFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(32768, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalChkFetchBytesReceivedAverage"));
+               localSskFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(2048, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalSskFetchBytesReceivedAverage"));
+               localChkInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(1024, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("LocalChkInsertBytesReceivedAverage"));
+               localSskInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(500, 180000, 0.0, 1024*1024*1024, throttleFS == null 
? null : throttleFS.subset("LocalChkInsertBytesReceivedAverage"));
+
+               remoteChkFetchBytesSentAverage = new 
TimeDecayingRunningAverage(32768+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkFetchBytesSentAverage"));
+               remoteSskFetchBytesSentAverage = new 
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteSskFetchBytesSentAverage"));
+               remoteChkInsertBytesSentAverage = new 
TimeDecayingRunningAverage(32768+32768+1024, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkInsertBytesSentAverage"));
+               remoteSskInsertBytesSentAverage = new 
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteSskInsertBytesSentAverage"));
+               remoteChkFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(32768+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkFetchBytesReceivedAverage"));
+               remoteSskFetchBytesReceivedAverage = new 
TimeDecayingRunningAverage(2048+500, 180000, 0.0, 1024*1024*1024, throttleFS == 
null ? null : throttleFS.subset("RemoteSskFetchBytesReceivedAverage"));
+               remoteChkInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(32768+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteChkInsertBytesReceivedAverage"));
+               remoteSskInsertBytesReceivedAverage = new 
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024, 
throttleFS == null ? null : 
throttleFS.subset("RemoteSskInsertBytesReceivedAverage"));
+               
+               requestOutputThrottle = 
+                       new TokenBucket(Math.max(obwLimit*60, 32768*20), 
(1000L*1000L*1000L) /  obwLimit, 0);
+               requestInputThrottle = 
+                       new TokenBucket(Math.max(ibwLimit*60, 32768*20), 
(1000L*1000L*1000L) / ibwLimit, 0);
+       }
+       
+       public void start() throws NodeInitException {
+               nodePinger.start();
+               myMemoryChecker.start();
+               persister.start();
+       }
+       
+       private long lastAcceptedRequest = -1;
+       
+       private long lastCheckedUncontended = -1;
+       
+       static final int ESTIMATED_SIZE_OF_ONE_THROTTLED_PACKET = 
+               1024 + DMT.packetTransmitSize(1024, 32)
+               + FNPPacketMangler.HEADERS_LENGTH_ONE_MESSAGE;
+       
+       /* return reject reason as string if should reject, otherwise return 
null */
+       public String shouldRejectRequest(boolean canAcceptAnyway, boolean 
isInsert, boolean isSSK) {
+               if(logMINOR) dumpByteCostAverages();
+               
+               if(threadLimit < getActiveThreadCount())
+                       return "Accepting the request would mean going above 
the maximum number of allowed threads";
+               
+               double bwlimitDelayTime = 
throttledPacketSendAverage.currentValue();
+               
+               // If no recent reports, no packets have been sent; correct the 
average downwards.
+               long now = System.currentTimeMillis();
+               boolean checkUncontended = false;
+               synchronized(this) {
+                       if(now - lastCheckedUncontended > 1000) {
+                               checkUncontended = true;
+                               lastCheckedUncontended = now;
+                       }
+               }
+               if(checkUncontended && 
throttledPacketSendAverage.lastReportTime() < now - 5000) {  // if last report 
more than 5 seconds ago
+                       // shouldn't take long
+                       
node.outputThrottle.blockingGrab(ESTIMATED_SIZE_OF_ONE_THROTTLED_PACKET);
+                       
node.outputThrottle.recycle(ESTIMATED_SIZE_OF_ONE_THROTTLED_PACKET);
+                       long after = System.currentTimeMillis();
+                       // Report time it takes to grab the bytes.
+                       throttledPacketSendAverage.report(after - now);
+                       now = after;
+                       // will have changed, use new value
+                       synchronized(this) {
+                               bwlimitDelayTime = 
throttledPacketSendAverage.currentValue();
+                       }
+               }
+               
+               double pingTime = nodePinger.averagePingTime();
+               synchronized(this) {
+                       // Round trip time
+                       if(pingTime > MAX_PING_TIME) {
+                               if((now - lastAcceptedRequest > 
MAX_INTERREQUEST_TIME) && canAcceptAnyway) {
+                                       if(logMINOR) Logger.minor(this, 
"Accepting request anyway (take one every 10 secs to keep bwlimitDelayTime 
updated)");
+                               } else {
+                                       pInstantRejectIncoming.report(1.0);
+                                       return ">MAX_PING_TIME 
("+TimeUtil.formatTime((long)pingTime, 2, true)+ ')';
+                               }
+                       } else if(pingTime > SUB_MAX_PING_TIME) {
+                               double x = ((double)(pingTime - 
SUB_MAX_PING_TIME)) / (MAX_PING_TIME - SUB_MAX_PING_TIME);
+                               if(hardRandom.nextDouble() < x) {
+                                       pInstantRejectIncoming.report(1.0);
+                                       return ">SUB_MAX_PING_TIME 
("+TimeUtil.formatTime((long)pingTime, 2, true)+ ')';
+                               }
+                       }
+               
+                       // Bandwidth limited packets
+                       if(bwlimitDelayTime > MAX_THROTTLE_DELAY) {
+                               if((now - lastAcceptedRequest > 
MAX_INTERREQUEST_TIME) && canAcceptAnyway) {
+                                       if(logMINOR) Logger.minor(this, 
"Accepting request anyway (take one every 10 secs to keep bwlimitDelayTime 
updated)");
+                               } else {
+                                       pInstantRejectIncoming.report(1.0);
+                                       return ">MAX_THROTTLE_DELAY 
("+TimeUtil.formatTime((long)bwlimitDelayTime, 2, true)+ ')';
+                               }
+                       } else if(bwlimitDelayTime > SUB_MAX_THROTTLE_DELAY) {
+                               double x = ((double)(bwlimitDelayTime - 
SUB_MAX_THROTTLE_DELAY)) / (MAX_THROTTLE_DELAY - SUB_MAX_THROTTLE_DELAY);
+                               if(hardRandom.nextDouble() < x) {
+                                       pInstantRejectIncoming.report(1.0);
+                                       return ">SUB_MAX_THROTTLE_DELAY 
("+TimeUtil.formatTime((long)bwlimitDelayTime, 2, true)+ ')';
+                               }
+                       }
+                       
+               }
+               
+               // Do we have the bandwidth?
+               double expected =
+                       (isInsert ? (isSSK ? 
this.remoteSskInsertBytesSentAverage : this.remoteChkInsertBytesSentAverage)
+                                       : (isSSK ? 
this.remoteSskFetchBytesSentAverage : 
this.remoteChkFetchBytesSentAverage)).currentValue();
+               int expectedSent = (int)Math.max(expected, 0);
+               if(!requestOutputThrottle.instantGrab(expectedSent)) {
+                       pInstantRejectIncoming.report(1.0);
+                       return "Insufficient output bandwidth";
+               }
+               expected = 
+                       (isInsert ? (isSSK ? 
this.remoteSskInsertBytesReceivedAverage : 
this.remoteChkInsertBytesReceivedAverage)
+                                       : (isSSK ? 
this.remoteSskFetchBytesReceivedAverage : 
this.remoteChkFetchBytesReceivedAverage)).currentValue();
+               int expectedReceived = (int)Math.max(expected, 0);
+               if(!requestInputThrottle.instantGrab(expectedReceived)) {
+                       requestOutputThrottle.recycle(expectedSent);
+                       pInstantRejectIncoming.report(1.0);
+                       return "Insufficient input bandwidth";
+               }
+
+               Runtime r = Runtime.getRuntime();
+               long maxHeapMemory = r.maxMemory();
+               long freeHeapMemory = r.freeMemory();
+               if(freeHeapMemory < MIN_FREE_HEAP_BYTES_FOR_ROUTING_SUCCESS) {
+                       pInstantRejectIncoming.report(1.0);
+                       return "<MIN_FREE_HEAP_BYTES_FOR_ROUTING_SUCCESS 
("+SizeUtil.formatSize(freeHeapMemory, false)+" of 
"+SizeUtil.formatSize(maxHeapMemory, false)+')';
+               }
+               double percentFreeHeapMemoryOfMax = ((double) freeHeapMemory) / 
((double) maxHeapMemory);
+               if(percentFreeHeapMemoryOfMax < 
MIN_FREE_HEAP_PERCENT_FOR_ROUTING_SUCCESS) {
+                       pInstantRejectIncoming.report(1.0);
+                       DecimalFormat fix3p1pct = new DecimalFormat("##0.0%");
+                       return "<MIN_FREE_HEAP_PERCENT_FOR_ROUTING_SUCCESS 
("+SizeUtil.formatSize(freeHeapMemory, false)+" of 
"+SizeUtil.formatSize(maxHeapMemory, false)+" 
("+fix3p1pct.format(percentFreeHeapMemoryOfMax)+"))";
+               }
+
+               synchronized(this) {
+                       if(logMINOR) Logger.minor(this, "Accepting request?");
+                       lastAcceptedRequest = now;
+               }
+               
+               pInstantRejectIncoming.report(0.0);
+
+               // Accept
+               return null;
+       }
+       
+       private void dumpByteCostAverages() {
+               Logger.minor(this, "Byte cost averages: REMOTE:"+
+                               " CHK insert 
"+remoteChkInsertBytesSentAverage.currentValue()+ '/' 
+remoteChkInsertBytesReceivedAverage.currentValue()+
+                               " SSK insert 
"+remoteSskInsertBytesSentAverage.currentValue()+ '/' 
+remoteSskInsertBytesReceivedAverage.currentValue()+
+                               " CHK fetch 
"+remoteChkFetchBytesSentAverage.currentValue()+ '/' 
+remoteChkFetchBytesReceivedAverage.currentValue()+
+                               " SSK fetch 
"+remoteSskFetchBytesSentAverage.currentValue()+ '/' 
+remoteSskFetchBytesReceivedAverage.currentValue());
+               Logger.minor(this, "Byte cost averages: LOCAL"+
+                               " CHK insert 
"+localChkInsertBytesSentAverage.currentValue()+ '/' 
+localChkInsertBytesReceivedAverage.currentValue()+
+                               " SSK insert 
"+localSskInsertBytesSentAverage.currentValue()+ '/' 
+localSskInsertBytesReceivedAverage.currentValue()+
+                               " CHK fetch 
"+localChkFetchBytesSentAverage.currentValue()+ '/' 
+localChkFetchBytesReceivedAverage.currentValue()+
+                               " SSK fetch 
"+localSskFetchBytesSentAverage.currentValue()+ '/' 
+localSskFetchBytesReceivedAverage.currentValue());
+       }
+
+       public double getBwlimitDelayTime() {
+               return throttledPacketSendAverage.currentValue();
+       }
+       
+       public double getNodeAveragePingTime() {
+               return nodePinger.averagePingTime();
+       }
+
+       public int getNetworkSizeEstimate(long timestamp) {
+               return node.lm.getNetworkSizeEstimate( timestamp );
+       }
+
+       public Object[] getKnownLocations(long timestamp) {
+               return node.lm.getKnownLocations( timestamp );
+       }
+       
+       public double pRejectIncomingInstantly() {
+               return pInstantRejectIncoming.currentValue();
+       }
+       
+       /**
+        * Update peerManagerUserAlertStats if the timer has expired
+        */
+       public void maybeUpdatePeerManagerUserAlertStats(long now) {
+               if(now > nextPeerManagerUserAlertStatsUpdateTime) {
+                       if(getBwlimitDelayTime() > 
MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD) {
+                               if(firstBwlimitDelayTimeThresholdBreak == 0) {
+                                       firstBwlimitDelayTimeThresholdBreak = 
now;
+                               }
+                       } else {
+                               firstBwlimitDelayTimeThresholdBreak = 0;
+                       }
+                       if((firstBwlimitDelayTimeThresholdBreak != 0) && ((now 
- firstBwlimitDelayTimeThresholdBreak) >= MAX_BWLIMIT_DELAY_TIME_ALERT_DELAY)) {
+                               bwlimitDelayAlertRelevant = true;
+                       } else {
+                               bwlimitDelayAlertRelevant = false;
+                       }
+                       if(getNodeAveragePingTime() > 
MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD) {
+                               if(firstNodeAveragePingTimeThresholdBreak == 0) 
{
+                                       firstNodeAveragePingTimeThresholdBreak 
= now;
+                               }
+                       } else {
+                               firstNodeAveragePingTimeThresholdBreak = 0;
+                       }
+                       if((firstNodeAveragePingTimeThresholdBreak != 0) && 
((now - firstNodeAveragePingTimeThresholdBreak) >= 
MAX_NODE_AVERAGE_PING_TIME_ALERT_DELAY)) {
+                               nodeAveragePingAlertRelevant = true;
+                       } else {
+                               nodeAveragePingAlertRelevant = false;
+                       }
+                       if(logMINOR && Logger.shouldLog(Logger.DEBUG, this)) 
Logger.debug(this, "mUPMUAS: "+now+": "+getBwlimitDelayTime()+" >? 
"+MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD+" since 
"+firstBwlimitDelayTimeThresholdBreak+" ("+bwlimitDelayAlertRelevant+") 
"+getNodeAveragePingTime()+" >? "+MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD+" 
since "+firstNodeAveragePingTimeThresholdBreak+" 
("+nodeAveragePingAlertRelevant+ ')');
+                       nextPeerManagerUserAlertStatsUpdateTime = now + 
peerManagerUserAlertStatsUpdateInterval;
+               }
+       }
+
+       public SimpleFieldSet persistThrottlesToFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               fs.put("RemoteChkFetchBytesSentAverage", 
remoteChkFetchBytesSentAverage.exportFieldSet(true));
+               fs.put("RemoteSskFetchBytesSentAverage", 
remoteSskFetchBytesSentAverage.exportFieldSet(true));
+               fs.put("RemoteChkInsertBytesSentAverage", 
remoteChkInsertBytesSentAverage.exportFieldSet(true));
+               fs.put("RemoteSskInsertBytesSentAverage", 
remoteSskInsertBytesSentAverage.exportFieldSet(true));
+               fs.put("RemoteChkFetchBytesReceivedAverage", 
remoteChkFetchBytesReceivedAverage.exportFieldSet(true));
+               fs.put("RemoteSskFetchBytesReceivedAverage", 
remoteSskFetchBytesReceivedAverage.exportFieldSet(true));
+               fs.put("RemoteChkInsertBytesReceivedAverage", 
remoteChkInsertBytesReceivedAverage.exportFieldSet(true));
+               fs.put("RemoteSskInsertBytesReceivedAverage", 
remoteSskInsertBytesReceivedAverage.exportFieldSet(true));
+               fs.put("LocalChkFetchBytesSentAverage", 
localChkFetchBytesSentAverage.exportFieldSet(true));
+               fs.put("LocalSskFetchBytesSentAverage", 
localSskFetchBytesSentAverage.exportFieldSet(true));
+               fs.put("LocalChkInsertBytesSentAverage", 
localChkInsertBytesSentAverage.exportFieldSet(true));
+               fs.put("LocalSskInsertBytesSentAverage", 
localSskInsertBytesSentAverage.exportFieldSet(true));
+               fs.put("LocalChkFetchBytesReceivedAverage", 
localChkFetchBytesReceivedAverage.exportFieldSet(true));
+               fs.put("LocalSskFetchBytesReceivedAverage", 
localSskFetchBytesReceivedAverage.exportFieldSet(true));
+               fs.put("LocalChkInsertBytesReceivedAverage", 
localChkInsertBytesReceivedAverage.exportFieldSet(true));
+               fs.put("LocalSskInsertBytesReceivedAverage", 
localSskInsertBytesReceivedAverage.exportFieldSet(true));
+               return fs;
+       }
+
+       /**
+        * Update the node-wide bandwidth I/O stats if the timer has expired
+        */
+       public void maybeUpdateNodeIOStats(long now) {
+               if(now > nextNodeIOStatsUpdateTime) {
+                       long[] io_stats = IOStatisticCollector.getTotalIO();
+                       long outdiff;
+                       long indiff;
+                       synchronized(ioStatSync) {
+                               previous_output_stat = last_output_stat;
+                               previous_input_stat = last_input_stat;
+                               previous_io_stat_time = last_io_stat_time;
+                               last_output_stat = io_stats[ 0 ];
+                               last_input_stat = io_stats[ 1 ];
+                               last_io_stat_time = now;
+                               outdiff = last_output_stat - 
previous_output_stat;
+                               indiff = last_input_stat - previous_input_stat;
+                       }
+                       if(logMINOR)
+                               Logger.minor(this, "Last 2 seconds: input: 
"+indiff+" output: "+outdiff);
+                       nextNodeIOStatsUpdateTime = now + 
nodeIOStatsUpdateInterval;
+               }
+       }
+
+       public long[] getNodeIOStats() {
+               long[] result = new long[6];
+               synchronized(ioStatSync) {
+                       result[ 0 ] = previous_output_stat;
+                       result[ 1 ] = previous_input_stat;
+                       result[ 2 ] = previous_io_stat_time;
+                       result[ 3 ] = last_output_stat;
+                       result[ 4 ] = last_input_stat;
+                       result[ 5 ] = last_io_stat_time;
+               }
+               return result;
+       }
+
+       public void waitUntilNotOverloaded(boolean isInsert) {
+               while(threadLimit < getActiveThreadCount()){
+                       try{
+                               wait(5000);
+                       } catch (InterruptedException e) {}
+               }
+       }
+
+       
+       public int getActiveThreadCount() {
+               return rootThreadGroup.activeCount();
+       }
+
+       public int getThreadLimit() {
+               return threadLimit;
+       }
+
+       public SimpleFieldSet exportVolatileFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet(true);
+               long now = System.currentTimeMillis();
+               fs.put("isUsingWrapper", node.isUsingWrapper());
+               long nodeUptimeSeconds = 0;
+               synchronized(this) {
+                       fs.put("startupTime", node.startupTime);
+                       nodeUptimeSeconds = (now - node.startupTime) / 1000;
+                       fs.put("uptimeSeconds", nodeUptimeSeconds);
+               }
+               fs.put("averagePingTime", getNodeAveragePingTime());
+               fs.put("bwlimitDelayTime", getBwlimitDelayTime());
+               fs.put("networkSizeEstimateSession", 
getNetworkSizeEstimate(-1));
+               int networkSizeEstimate24hourRecent = 
getNetworkSizeEstimate(now - (24*60*60*1000));  // 24 hours
+               fs.put("networkSizeEstimate24hourRecent", 
networkSizeEstimate24hourRecent);
+               int networkSizeEstimate48hourRecent = 
getNetworkSizeEstimate(now - (48*60*60*1000));  // 48 hours
+               fs.put("networkSizeEstimate48hourRecent", 
networkSizeEstimate48hourRecent);
+               fs.put("routingMissDistance", 
routingMissDistance.currentValue());
+               fs.put("backedOffPercent", backedOffPercent.currentValue());
+               fs.put("pInstantReject", pRejectIncomingInstantly());
+               fs.put("unclaimedFIFOSize", node.usm.getUnclaimedFIFOSize());
+               
+               /* gather connection statistics */
+               PeerNodeStatus[] peerNodeStatuses = peers.getPeerNodeStatuses();
+               Arrays.sort(peerNodeStatuses, new Comparator() {
+                       public int compare(Object first, Object second) {
+                               PeerNodeStatus firstNode = (PeerNodeStatus) 
first;
+                               PeerNodeStatus secondNode = (PeerNodeStatus) 
second;
+                               int statusDifference = 
firstNode.getStatusValue() - secondNode.getStatusValue();
+                               if (statusDifference != 0) {
+                                       return statusDifference;
+                               }
+                               return 
firstNode.getName().compareToIgnoreCase(secondNode.getName());
+                       }
+               });
+               
+               int numberOfConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_CONNECTED);
+               int numberOfRoutingBackedOff = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
+               int numberOfTooNew = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_NEW);
+               int numberOfTooOld = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_OLD);
+               int numberOfDisconnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_DISCONNECTED);
+               int numberOfNeverConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED);
+               int numberOfDisabled = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_DISABLED);
+               int numberOfBursting = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_BURSTING);
+               int numberOfListening = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_LISTENING);
+               int numberOfListenOnly = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_LISTEN_ONLY);
+               
+               int numberOfSimpleConnected = numberOfConnected + 
numberOfRoutingBackedOff;
+               int numberOfNotConnected = numberOfTooNew + numberOfTooOld + 
numberOfDisconnected + numberOfNeverConnected + numberOfDisabled + 
numberOfBursting + numberOfListening + numberOfListenOnly;
+
+               fs.put("numberOfConnected", numberOfConnected);
+               fs.put("numberOfRoutingBackedOff", numberOfRoutingBackedOff);
+               fs.put("numberOfTooNew", numberOfTooNew);
+               fs.put("numberOfTooOld", numberOfTooOld);
+               fs.put("numberOfDisconnected", numberOfDisconnected);
+               fs.put("numberOfNeverConnected", numberOfNeverConnected);
+               fs.put("numberOfDisabled", numberOfDisabled);
+               fs.put("numberOfBursting", numberOfBursting);
+               fs.put("numberOfListening", numberOfListening);
+               fs.put("numberOfListenOnly", numberOfListenOnly);
+               
+               fs.put("numberOfSimpleConnected", numberOfSimpleConnected);
+               fs.put("numberOfNotConnected", numberOfNotConnected);
+
+               fs.put("numberOfInserts", node.getNumInserts());
+               fs.put("numberOfRequests", node.getNumRequests());
+               fs.put("numberOfTransferringRequests", 
node.getNumTransferringRequests());
+               fs.put("numberOfARKFetchers", node.getNumARKFetchers());
+
+               long[] total = IOStatisticCollector.getTotalIO();
+               long total_output_rate = (total[0]) / nodeUptimeSeconds;
+               long total_input_rate = (total[1]) / nodeUptimeSeconds;
+               long totalPayloadOutput = node.getTotalPayloadSent();
+               long total_payload_output_rate = totalPayloadOutput / 
nodeUptimeSeconds;
+               int total_payload_output_percent = (int) (100 * 
totalPayloadOutput / total[0]);
+               fs.put("totalOutputBytes", total[0]);
+               fs.put("totalOutputRate", total_output_rate);
+               fs.put("totalPayloadOutputBytes", totalPayloadOutput);
+               fs.put("totalPayloadOutputRate", total_payload_output_rate);
+               fs.put("totalPayloadOutputPercent", 
total_payload_output_percent);
+               fs.put("totalInputBytes", total[1]);
+               fs.put("totalInputRate", total_input_rate);
+               long[] rate = getNodeIOStats();
+               long delta = (rate[5] - rate[2]) / 1000;
+               long recent_output_rate = (rate[3] - rate[0]) / delta;
+               long recent_input_rate = (rate[4] - rate[1]) / delta;
+               fs.put("recentOutputRate", recent_output_rate);
+               fs.put("recentInputRate", recent_input_rate);
+
+               String [] routingBackoffReasons = 
peers.getPeerNodeRoutingBackoffReasons();
+               if(routingBackoffReasons.length != 0) {
+                       for(int i=0;i<routingBackoffReasons.length;i++) {
+                               fs.put("numberWithRoutingBackoffReasons." + 
routingBackoffReasons[i], 
peers.getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]));
+                       }
+               }
+
+               double swaps = (double)node.getSwaps();
+               double noSwaps = (double)node.getNoSwaps();
+               double numberOfRemotePeerLocationsSeenInSwaps = 
(double)node.getNumberOfRemotePeerLocationsSeenInSwaps();
+               fs.putSingle("numberOfRemotePeerLocationsSeenInSwaps", 
Double.toString(numberOfRemotePeerLocationsSeenInSwaps));
+               double avgConnectedPeersPerNode = 0.0;
+               if ((numberOfRemotePeerLocationsSeenInSwaps > 0.0) && ((swaps > 
0.0) || (noSwaps > 0.0))) {
+                       avgConnectedPeersPerNode = 
numberOfRemotePeerLocationsSeenInSwaps/(swaps+noSwaps);
+               }
+               fs.putSingle("avgConnectedPeersPerNode", 
Double.toString(avgConnectedPeersPerNode));
+
+               int startedSwaps = node.getStartedSwaps();
+               int swapsRejectedAlreadyLocked = 
node.getSwapsRejectedAlreadyLocked();
+               int swapsRejectedNowhereToGo = 
node.getSwapsRejectedNowhereToGo();
+               int swapsRejectedRateLimit = node.getSwapsRejectedRateLimit();
+               int swapsRejectedLoop = node.getSwapsRejectedLoop();
+               int swapsRejectedRecognizedID = 
node.getSwapsRejectedRecognizedID();
+               double locationChangePerSession = 
node.getLocationChangeSession();
+               double locationChangePerSwap = 0.0;
+               double locationChangePerMinute = 0.0;
+               double swapsPerMinute = 0.0;
+               double noSwapsPerMinute = 0.0;
+               double swapsPerNoSwaps = 0.0;
+               if (swaps > 0) {
+                       locationChangePerSwap = locationChangePerSession/swaps;
+               }
+               if ((swaps > 0.0) && (nodeUptimeSeconds >= 60)) {
+                       locationChangePerMinute = 
locationChangePerSession/(double)(nodeUptimeSeconds/60.0);
+               }
+               if ((swaps > 0.0) && (nodeUptimeSeconds >= 60)) {
+                       swapsPerMinute = swaps/(double)(nodeUptimeSeconds/60.0);
+               }
+               if ((noSwaps > 0.0) && (nodeUptimeSeconds >= 60)) {
+                       noSwapsPerMinute = 
noSwaps/(double)(nodeUptimeSeconds/60.0);
+               }
+               if ((swaps > 0.0) && (noSwaps > 0.0)) {
+                       swapsPerNoSwaps = swaps/noSwaps;
+               }
+               fs.put("locationChangePerSession", locationChangePerSession);
+               fs.put("locationChangePerSwap", locationChangePerSwap);
+               fs.put("locationChangePerMinute", locationChangePerMinute);
+               fs.put("swapsPerMinute", swapsPerMinute);
+               fs.put("noSwapsPerMinute", noSwapsPerMinute);
+               fs.put("swapsPerNoSwaps", swapsPerNoSwaps);
+               fs.put("swaps", swaps);
+               fs.put("noSwaps", noSwaps);
+               fs.put("startedSwaps", startedSwaps);
+               fs.put("swapsRejectedAlreadyLocked", 
swapsRejectedAlreadyLocked);
+               fs.put("swapsRejectedNowhereToGo", swapsRejectedNowhereToGo);
+               fs.put("swapsRejectedRateLimit", swapsRejectedRateLimit);
+               fs.put("swapsRejectedLoop", swapsRejectedLoop);
+               fs.put("swapsRejectedRecognizedID", swapsRejectedRecognizedID);
+
+               long fix32kb = 32 * 1024;
+               long cachedKeys = node.getChkDatacache().keyCount();
+               long cachedSize = cachedKeys * fix32kb;
+               long storeKeys = node.getChkDatastore().keyCount();
+               long storeSize = storeKeys * fix32kb;
+               long overallKeys = cachedKeys + storeKeys;
+               long overallSize = cachedSize + storeSize;
+               
+               long maxOverallKeys = node.getMaxTotalKeys();
+               long maxOverallSize = maxOverallKeys * fix32kb;
+               
+               double percentOverallKeysOfMax = 
(double)(overallKeys*100)/(double)maxOverallKeys;
+               
+               long cachedStoreHits = node.getChkDatacache().hits();
+               long cachedStoreMisses = node.getChkDatacache().misses();
+               long cacheAccesses = cachedStoreHits + cachedStoreMisses;
+               double percentCachedStoreHitsOfAccesses = 
(double)(cachedStoreHits*100) / (double)cacheAccesses;
+               long storeHits = node.getChkDatastore().hits();
+               long storeMisses = node.getChkDatastore().misses();
+               long storeAccesses = storeHits + storeMisses;
+               double percentStoreHitsOfAccesses = (double)(storeHits*100) / 
(double)storeAccesses;
+               long overallAccesses = storeAccesses + cacheAccesses;
+               double avgStoreAccessRate = 
(double)overallAccesses/(double)nodeUptimeSeconds;
+               
+               fs.put("cachedKeys", cachedKeys);
+               fs.put("cachedSize", cachedSize);
+               fs.put("storeKeys", storeKeys);
+               fs.put("storeSize", storeSize);
+               fs.put("overallKeys", overallKeys);
+               fs.put("overallSize", overallSize);
+               fs.put("maxOverallKeys", maxOverallKeys);
+               fs.put("maxOverallSize", maxOverallSize);
+               fs.put("percentOverallKeysOfMax", percentOverallKeysOfMax);
+               fs.put("cachedStoreHits", cachedStoreHits);
+               fs.put("cachedStoreMisses", cachedStoreMisses);
+               fs.put("cacheAccesses", cacheAccesses);
+               fs.put("percentCachedStoreHitsOfAccesses", 
percentCachedStoreHitsOfAccesses);
+               fs.put("storeHits", storeHits);
+               fs.put("storeMisses", storeMisses);
+               fs.put("storeAccesses", storeAccesses);
+               fs.put("percentStoreHitsOfAccesses", 
percentStoreHitsOfAccesses);
+               fs.put("overallAccesses", overallAccesses);
+               fs.put("avgStoreAccessRate", avgStoreAccessRate);
+
+               Runtime rt = Runtime.getRuntime();
+               float freeMemory = (float) rt.freeMemory();
+               float totalMemory = (float) rt.totalMemory();
+               float maxMemory = (float) rt.maxMemory();
+
+               long usedJavaMem = (long)(totalMemory - freeMemory);
+               long allocatedJavaMem = (long)totalMemory;
+               long maxJavaMem = (long)maxMemory;
+               int availableCpus = rt.availableProcessors();
+
+               fs.put("freeJavaMemory", (long)freeMemory);
+               fs.put("usedJavaMemory", usedJavaMem);
+               fs.put("allocatedJavaMemory", allocatedJavaMem);
+               fs.put("maximumJavaMemory", maxJavaMem);
+               fs.put("availableCPUs", availableCpus);
+               fs.put("runningThreadCount", getActiveThreadCount());
+               
+               return fs;
+       }
+
+       public void setOutputLimit(int obwLimit) {
+               obwLimit = (obwLimit * 4) / 5; // fudge factor; take into 
account non-request activity
+               
requestOutputThrottle.changeNanosAndBucketSize((1000L*1000L*1000L) /  obwLimit, 
Math.max(obwLimit*60, 32768*20));
+               if(node.inputLimitDefault) {
+                       int ibwLimit = obwLimit * 4;
+                       
requestInputThrottle.changeNanosAndBucketSize((1000L*1000L*1000L) /  ibwLimit, 
Math.max(ibwLimit*60, 32768*20));
+               }
+       }
+
+       public void setInputLimit(int ibwLimit) {
+               
requestInputThrottle.changeNanosAndBucketSize((1000L*1000L*1000L) /  ibwLimit, 
Math.max(ibwLimit*60, 32768*20));
+       }
+
+       public int getInputLimit() {
+               return (((int) ((1000L * 1000L * 1000L) / 
requestInputThrottle.getNanosPerTick())) * 5) / 4;
+       }
+
+       public boolean isTestnetEnabled() {
+               return node.isTestnetEnabled();
+       }
+       
+
+}

Modified: trunk/freenet/src/freenet/node/PacketSender.java
===================================================================
--- trunk/freenet/src/freenet/node/PacketSender.java    2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/PacketSender.java    2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -35,6 +35,8 @@
     private final TreeMap timedJobsByTime;
     final Thread myThread;
     final Node node;
+    final PeerManager peers;
+    NodeStats stats;
     long lastClearedOldSwapChains;
     long lastReportedNoPackets;
     long lastReceivedPacketFromAnyNode;
@@ -48,6 +50,7 @@
         resendPackets = new LinkedList();
         timedJobsByTime = new TreeMap();
         this.node = node;
+        this.peers = node.peers;
         myThread = new Thread(this, "PacketSender thread for 
"+node.portNumber);
         myThread.setDaemon(true);
         myThread.setPriority(Thread.MAX_PRIORITY);
@@ -110,7 +113,8 @@
        }
     }

-    void start() {
+    void start(NodeStats stats) {
+       this.stats = stats;
         Logger.normal(this, "Starting PacketSender");
         System.out.println("Starting PacketSender");
        long now = System.currentTimeMillis();
@@ -163,15 +167,15 @@
             PeerNode pn = nodes[i];
             // Only routing backed off nodes should need status updating since 
everything else
             // should get updated immediately when it's changed
-            if(pn.getPeerNodeStatus() == 
Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
+            if(pn.getPeerNodeStatus() == 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
                 pn.setPeerNodeStatus(now);
             }
         }
-        node.maybeLogPeerNodeStatusSummary(now);
-        node.maybeUpdateOldestNeverConnectedPeerAge(now);
-        node.maybeUpdatePeerManagerUserAlertStats(now);
-        node.maybeUpdateNodeIOStats(now);
-        node.maybeUpdatePeerNodeRoutableConnectionStats(now);
+        peers.maybeLogPeerNodeStatusSummary(now);
+        peers.maybeUpdateOldestNeverConnectedPeerAge(now);
+        stats.maybeUpdatePeerManagerUserAlertStats(now);
+        stats.maybeUpdateNodeIOStats(now);
+        peers.maybeUpdatePeerNodeRoutableConnectionStats(now);
         long nextActionTime = Long.MAX_VALUE;
         long oldTempNow = now;
         for(int i=0;i<nodes.length;i++) {

Modified: trunk/freenet/src/freenet/node/PeerManager.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerManager.java     2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/PeerManager.java     2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -15,6 +15,7 @@
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Vector;
 import java.util.ArrayList;
@@ -51,8 +52,38 @@

     final String filename;

-    final PeerManagerUserAlert ua;
+    private PeerManagerUserAlert ua;

+       // Peers stuff
+       /** age of oldest never connected peer (milliseconds) */
+       private long oldestNeverConnectedPeerAge;
+       /** Next time to update oldestNeverConnectedPeerAge */
+       private long nextOldestNeverConnectedPeerAgeUpdateTime = -1;
+       /** oldestNeverConnectedPeerAge update interval (milliseconds) */
+       private static final long oldestNeverConnectedPeerAgeUpdateInterval = 
5000;
+       /** Next time to log the PeerNode status summary */
+       private long nextPeerNodeStatusLogTime = -1;
+       /** PeerNode status summary log interval (milliseconds) */
+       private static final long peerNodeStatusLogInterval = 5000;
+       /** PeerNode statuses, by status */
+       private final HashMap peerNodeStatuses;
+       /** PeerNode routing backoff reasons, by reason */
+       private final HashMap peerNodeRoutingBackoffReasons;
+       /** Next time to update routableConnectionStats */
+       private long nextRoutableConnectionStatsUpdateTime = -1;
+       /** routableConnectionStats update interval (milliseconds) */
+       private static final long routableConnectionStatsUpdateInterval = 7 * 
1000;  // 7 seconds
+       public static final int PEER_NODE_STATUS_CONNECTED = 1;
+       public static final int PEER_NODE_STATUS_ROUTING_BACKED_OFF = 2;
+       public static final int PEER_NODE_STATUS_TOO_NEW = 3;
+       public static final int PEER_NODE_STATUS_TOO_OLD = 4;
+       public static final int PEER_NODE_STATUS_DISCONNECTED = 5;
+       public static final int PEER_NODE_STATUS_NEVER_CONNECTED = 6;
+       public static final int PEER_NODE_STATUS_DISABLED = 7;
+       public static final int PEER_NODE_STATUS_BURSTING = 8;
+       public static final int PEER_NODE_STATUS_LISTENING = 9;
+       public static final int PEER_NODE_STATUS_LISTEN_ONLY = 10;
+       
     /**
      * Create a PeerManager by reading a list of peers from
      * a file.
@@ -62,9 +93,10 @@
     public PeerManager(Node node, String filename) {
         Logger.normal(this, "Creating PeerManager");
         logMINOR = Logger.shouldLog(Logger.MINOR, this);
+               peerNodeStatuses = new HashMap();
+               peerNodeRoutingBackoffReasons = new HashMap();
         System.out.println("Creating PeerManager");
         this.filename = filename;
-        ua = new PeerManagerUserAlert(node);
         myPeers = new PeerNode[0];
         connectedPeers = new PeerNode[0];
         this.node = node;
@@ -161,10 +193,10 @@
             if(myPeers[i] == pn) isInPeers=true;
         }
         int peerNodeStatus = pn.getPeerNodeStatus();
-        node.removePeerNodeStatus( peerNodeStatus, pn );
+        removePeerNodeStatus( peerNodeStatus, pn );
         String peerNodePreviousRoutingBackoffReason = 
pn.getPreviousBackoffReason();
         if(peerNodePreviousRoutingBackoffReason != null) {
-               
node.removePeerNodeRoutingBackoffReason(peerNodePreviousRoutingBackoffReason, 
pn);
+               
removePeerNodeRoutingBackoffReason(peerNodePreviousRoutingBackoffReason, pn);
         }
         pn.removeExtraPeerDataDir();
         if(!isInPeers) return false;
@@ -472,11 +504,11 @@
        if (calculateMisrouting) {
                PeerNode nbo = _closerPeer(pn, routedTo, notIgnored, loc, 
ignoreSelf, true, minVersion);
                if(nbo != null) {
-                       node.routingMissDistance.report(distance(best, 
nbo.getLocation().getValue()));
-                       int numberOfConnected = 
node.getPeerNodeStatusSize(Node.PEER_NODE_STATUS_CONNECTED);
-                       int numberOfRoutingBackedOff = 
node.getPeerNodeStatusSize(Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
+                       
node.nodeStats.routingMissDistance.report(distance(best, 
nbo.getLocation().getValue()));
+                       int numberOfConnected = 
getPeerNodeStatusSize(PEER_NODE_STATUS_CONNECTED);
+                       int numberOfRoutingBackedOff = 
getPeerNodeStatusSize(PEER_NODE_STATUS_ROUTING_BACKED_OFF);
                        if (numberOfRoutingBackedOff + numberOfConnected > 0 ) {
-                               node.backedOffPercent.report((double) 
numberOfRoutingBackedOff / (double) (numberOfRoutingBackedOff + 
numberOfConnected));
+                               node.nodeStats.backedOffPercent.report((double) 
numberOfRoutingBackedOff / (double) (numberOfRoutingBackedOff + 
numberOfConnected));
                        }
                }
        }
@@ -684,7 +716,7 @@
                synchronized(ua) {
                        ua.conns = conns;
                        ua.peers = peers;
-                       ua.neverConn = 
node.getPeerNodeStatusSize(Node.PEER_NODE_STATUS_NEVER_CONNECTED);
+                       ua.neverConn = 
getPeerNodeStatusSize(PEER_NODE_STATUS_NEVER_CONNECTED);
                }
                if(anyConnectedPeers())
                        node.onConnectedPeer();
@@ -722,6 +754,7 @@
        }

        public void start() {
+        ua = new PeerManagerUserAlert(node.nodeStats);
                node.clientCore.alerts.register(ua);
        }

@@ -741,4 +774,258 @@
                if(countNoBackoff == 0) return count;
                return countNoBackoff;
        }
+       
+       // Stats stuff
+       
+       /**
+        * Update oldestNeverConnectedPeerAge if the timer has expired
+        */
+       public void maybeUpdateOldestNeverConnectedPeerAge(long now) {
+         if(now > nextOldestNeverConnectedPeerAgeUpdateTime) {
+               oldestNeverConnectedPeerAge = 0;
+                 PeerNode[] peerList = myPeers;
+                 for(int i=0;i<peerList.length;i++) {
+                       PeerNode pn = peerList[i];
+                       if(pn.getPeerNodeStatus() == 
PEER_NODE_STATUS_NEVER_CONNECTED) {
+                         if((now - pn.getPeerAddedTime()) > 
oldestNeverConnectedPeerAge) {
+                               oldestNeverConnectedPeerAge = now - 
pn.getPeerAddedTime();
+                         }
+                       }
+                 }
+               if(oldestNeverConnectedPeerAge > 0 && logMINOR)
+                 Logger.minor(this, "Oldest never connected peer is 
"+oldestNeverConnectedPeerAge+"ms old");
+               nextOldestNeverConnectedPeerAgeUpdateTime = now + 
oldestNeverConnectedPeerAgeUpdateInterval;
+         }
+       }
+
+       public long getOldestNeverConnectedPeerAge() {
+         return oldestNeverConnectedPeerAge;
+       }
+
+       /**
+        * Log the current PeerNode status summary if the timer has expired
+        */
+       public void maybeLogPeerNodeStatusSummary(long now) {
+         if(now > nextPeerNodeStatusLogTime) {
+               if((now - nextPeerNodeStatusLogTime) > (10*1000) && 
nextPeerNodeStatusLogTime > 0)
+                 Logger.error(this,"maybeLogPeerNodeStatusSummary() not called 
for more than 10 seconds ("+(now - nextPeerNodeStatusLogTime)+").  PacketSender 
getting bogged down or something?");
+               
+               int numberOfConnected = 0;
+               int numberOfRoutingBackedOff = 0;
+               int numberOfTooNew = 0;
+               int numberOfTooOld = 0;
+               int numberOfDisconnected = 0;
+               int numberOfNeverConnected = 0;
+               int numberOfDisabled = 0;
+               int numberOfListenOnly = 0;
+               int numberOfListening = 0;
+               int numberOfBursting = 0;
+               
+               PeerNodeStatus[] pns = getPeerNodeStatuses();
+               
+               for(int i=0; i<pns.length; i++){
+                       switch (pns[i].getStatusValue()) {
+                       case PEER_NODE_STATUS_CONNECTED:
+                               numberOfConnected++;
+                               break;
+                       case PEER_NODE_STATUS_ROUTING_BACKED_OFF:
+                               numberOfRoutingBackedOff++;
+                               break;
+                       case PEER_NODE_STATUS_TOO_NEW:
+                               numberOfTooNew++;
+                               break;
+                       case PEER_NODE_STATUS_TOO_OLD:
+                               numberOfTooOld++;
+                               break;
+                       case PEER_NODE_STATUS_DISCONNECTED:
+                               numberOfDisconnected++;
+                               break;
+                       case PEER_NODE_STATUS_NEVER_CONNECTED:
+                               numberOfNeverConnected++;
+                               break;
+                       case PEER_NODE_STATUS_DISABLED:
+                               numberOfDisabled++;
+                               break;
+                       case PEER_NODE_STATUS_LISTEN_ONLY:
+                               numberOfListenOnly++;
+                               break;
+                       case PEER_NODE_STATUS_LISTENING:
+                               numberOfListening++;
+                               break;
+                       case PEER_NODE_STATUS_BURSTING:
+                               numberOfBursting++;
+                               break;
+                       default:
+                               Logger.error(this, "Unknown peer status value : 
"+pns[i].getStatusValue());
+                               break;
+                       }
+               }
+               Logger.normal(this, "Connected: "+numberOfConnected+"  Routing 
Backed Off: "+numberOfRoutingBackedOff+"  Too New: "+numberOfTooNew+"  Too Old: 
"+numberOfTooOld+"  Disconnected: "+numberOfDisconnected+"  Never Connected: 
"+numberOfNeverConnected+"  Disabled: "+numberOfDisabled+"  Bursting: 
"+numberOfBursting+"  Listening: "+numberOfListening+"  Listen Only: 
"+numberOfListenOnly);
+               nextPeerNodeStatusLogTime = now + peerNodeStatusLogInterval;
+               }
+       }
+
+       /**
+        * Add a PeerNode status to the map
+        */
+       public void addPeerNodeStatus(int pnStatus, PeerNode peerNode) {
+               Integer peerNodeStatus = new Integer(pnStatus);
+               HashSet statusSet = null;
+               synchronized(peerNodeStatuses) {
+                       if(peerNodeStatuses.containsKey(peerNodeStatus)) {
+                               statusSet = (HashSet) 
peerNodeStatuses.get(peerNodeStatus);
+                               if(statusSet.contains(peerNode)) {
+                                       Logger.error(this, 
"addPeerNodeStatus(): identity '"+peerNode.getIdentityString()+"' already in 
peerNodeStatuses as "+peerNode+" with status code "+peerNodeStatus);
+                                       return;
+                               }
+                               peerNodeStatuses.remove(peerNodeStatus);
+                       } else {
+                               statusSet = new HashSet();
+                       }
+                       if(logMINOR) Logger.minor(this, "addPeerNodeStatus(): 
adding PeerNode for '"+peerNode.getIdentityString()+"' with status code 
"+peerNodeStatus);
+                       statusSet.add(peerNode);
+                       peerNodeStatuses.put(peerNodeStatus, statusSet);
+               }
+       }
+
+       /**
+        * How many PeerNodes have a particular status?
+        */
+       public int getPeerNodeStatusSize(int pnStatus) {
+               Integer peerNodeStatus = new Integer(pnStatus);
+               HashSet statusSet = null;
+               synchronized(peerNodeStatuses) {
+                       if(peerNodeStatuses.containsKey(peerNodeStatus)) {
+                               statusSet = (HashSet) 
peerNodeStatuses.get(peerNodeStatus);
+                       } else {
+                               statusSet = new HashSet();
+                       }
+                       return statusSet.size();
+               }
+       }
+
+       /**
+        * Remove a PeerNode status from the map
+        */
+       public void removePeerNodeStatus(int pnStatus, PeerNode peerNode) {
+               Integer peerNodeStatus = new Integer(pnStatus);
+               HashSet statusSet = null;
+               synchronized(peerNodeStatuses) {
+                       if(peerNodeStatuses.containsKey(peerNodeStatus)) {
+                               statusSet = (HashSet) 
peerNodeStatuses.get(peerNodeStatus);
+                               if(!statusSet.contains(peerNode)) {
+                                       Logger.error(this, 
"removePeerNodeStatus(): identity '"+peerNode.getIdentityString()+"' not in 
peerNodeStatuses with status code "+peerNodeStatus);
+                                       return;
+                               }
+                               peerNodeStatuses.remove(peerNodeStatus);
+                       } else {
+                               statusSet = new HashSet();
+                       }
+                       if(logMINOR) Logger.minor(this, 
"removePeerNodeStatus(): removing PeerNode for 
'"+peerNode.getIdentityString()+"' with status code "+peerNodeStatus);
+                       if(statusSet.contains(peerNode)) {
+                               statusSet.remove(peerNode);
+                       }
+                       peerNodeStatuses.put(peerNodeStatus, statusSet);
+               }
+       }
+
+       /**
+        * Add a PeerNode routing backoff reason to the map
+        */
+       public void addPeerNodeRoutingBackoffReason(String 
peerNodeRoutingBackoffReason, PeerNode peerNode) {
+               synchronized(peerNodeRoutingBackoffReasons) {
+                       HashSet reasonSet = null;
+                       
if(peerNodeRoutingBackoffReasons.containsKey(peerNodeRoutingBackoffReason)) {
+                               reasonSet = (HashSet) 
peerNodeRoutingBackoffReasons.get(peerNodeRoutingBackoffReason);
+                               if(reasonSet.contains(peerNode)) {
+                                       Logger.error(this, 
"addPeerNodeRoutingBackoffReason(): identity '"+peerNode.getIdentityString()+"' 
already in peerNodeRoutingBackoffReasons as "+peerNode+" with status code 
"+peerNodeRoutingBackoffReason);
+                                       return;
+                               }
+                               
peerNodeRoutingBackoffReasons.remove(peerNodeRoutingBackoffReason);
+                       } else {
+                               reasonSet = new HashSet();
+                       }
+                       if(logMINOR) Logger.minor(this, 
"addPeerNodeRoutingBackoffReason(): adding PeerNode for 
'"+peerNode.getIdentityString()+"' with status code 
"+peerNodeRoutingBackoffReason);
+                       reasonSet.add(peerNode);
+                       
peerNodeRoutingBackoffReasons.put(peerNodeRoutingBackoffReason, reasonSet);
+               }
+       }
+       
+       /**
+        * What are the currently tracked PeerNode routing backoff reasons?
+        */
+       public String [] getPeerNodeRoutingBackoffReasons() {
+               String [] reasonStrings;
+               synchronized(peerNodeRoutingBackoffReasons) {
+                       reasonStrings = (String []) 
peerNodeRoutingBackoffReasons.keySet().toArray(new 
String[peerNodeRoutingBackoffReasons.size()]);
+               }
+               Arrays.sort(reasonStrings);
+               return reasonStrings;
+       }
+       
+       /**
+        * How many PeerNodes have a particular routing backoff reason?
+        */
+       public int getPeerNodeRoutingBackoffReasonSize(String 
peerNodeRoutingBackoffReason) {
+               HashSet reasonSet = null;
+               synchronized(peerNodeRoutingBackoffReasons) {
+                       
if(peerNodeRoutingBackoffReasons.containsKey(peerNodeRoutingBackoffReason)) {
+                               reasonSet = (HashSet) 
peerNodeRoutingBackoffReasons.get(peerNodeRoutingBackoffReason);
+                               return reasonSet.size();
+                       } else {
+                               return 0;
+                       }
+               }
+       }
+
+       /**
+        * Remove a PeerNode routing backoff reason from the map
+        */
+       public void removePeerNodeRoutingBackoffReason(String 
peerNodeRoutingBackoffReason, PeerNode peerNode) {
+               HashSet reasonSet = null;
+               synchronized(peerNodeRoutingBackoffReasons) {
+                       
if(peerNodeRoutingBackoffReasons.containsKey(peerNodeRoutingBackoffReason)) {
+                               reasonSet = (HashSet) 
peerNodeRoutingBackoffReasons.get(peerNodeRoutingBackoffReason);
+                               if(!reasonSet.contains(peerNode)) {
+                                       Logger.error(this, 
"removePeerNodeRoutingBackoffReason(): identity 
'"+peerNode.getIdentityString()+"' not in peerNodeRoutingBackoffReasons with 
status code "+peerNodeRoutingBackoffReason);
+                                       return;
+                               }
+                               
peerNodeRoutingBackoffReasons.remove(peerNodeRoutingBackoffReason);
+                       } else {
+                               reasonSet = new HashSet();
+                       }
+                       if(logMINOR) Logger.minor(this, 
"removePeerNodeRoutingBackoffReason(): removing PeerNode for 
'"+peerNode.getIdentityString()+"' with status code 
"+peerNodeRoutingBackoffReason);
+                       if(reasonSet.contains(peerNode)) {
+                               reasonSet.remove(peerNode);
+                       }
+                       if(reasonSet.size() > 0) {
+                               
peerNodeRoutingBackoffReasons.put(peerNodeRoutingBackoffReason, reasonSet);
+                       }
+               }
+       }
+
+       public PeerNodeStatus[] getPeerNodeStatuses() {
+               PeerNodeStatus[] peerNodeStatuses = new 
PeerNodeStatus[myPeers.length];
+               for (int peerIndex = 0, peerCount = myPeers.length; peerIndex < 
peerCount; peerIndex++) {
+                       peerNodeStatuses[peerIndex] = 
myPeers[peerIndex].getStatus();
+               }
+               return peerNodeStatuses;
+       }
+
+       /**
+        * Update hadRoutableConnectionCount/routableConnectionCheckCount on 
peers if the timer has expired
+        */
+       public void maybeUpdatePeerNodeRoutableConnectionStats(long now) {
+               if(now > nextRoutableConnectionStatsUpdateTime) {
+                       if(-1 != nextRoutableConnectionStatsUpdateTime) {
+                               PeerNode[] peerList = myPeers;
+                               for(int i=0;i<peerList.length;i++) {
+                                       PeerNode pn = peerList[i];
+                                       pn.checkRoutableConnectionStatus();
+                               }
+                       }
+                       nextRoutableConnectionStatsUpdateTime = now + 
routableConnectionStatsUpdateInterval;
+               }
+       }
+
 }

Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java        2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/PeerNode.java        2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -175,6 +175,9 @@
     /** The Node we serve */
     final Node node;

+    /** The PeerManager we serve */
+    final PeerManager peers;
+    
     /** MessageItem's to send ASAP */
     private final LinkedList messagesToSendNow;

@@ -248,7 +251,7 @@
     private long connectedTime;

     /** The status of this peer node in terms of Node.PEER_NODE_STATUS_* */
-    public int peerNodeStatus = Node.PEER_NODE_STATUS_DISCONNECTED;
+    public int peerNodeStatus = PeerManager.PEER_NODE_STATUS_DISCONNECTED;

     /** Holds a String-Long pair that shows which message types (as name) have 
been send to this peer. */
     private final Hashtable localNodeSentMessageTypes = new Hashtable();
@@ -335,6 +338,7 @@
     public PeerNode(SimpleFieldSet fs, Node node2, boolean fromLocal) throws 
FSParseException, PeerParseException, ReferenceSignatureVerificationException {
        logMINOR = Logger.shouldLog(Logger.MINOR, this);
         this.node = node2;
+        this.peers = node.peers;
         String identityString = fs.get("identity");
        if(identityString == null)
                throw new PeerParseException("No identity!");
@@ -493,7 +497,7 @@

         // Not connected yet; need to handshake
         isConnected = false;
-        node.addPeerNodeStatus(Node.PEER_NODE_STATUS_DISCONNECTED, this);
+        peers.addPeerNodeStatus(PeerManager.PEER_NODE_STATUS_DISCONNECTED, 
this);

         messagesToSendNow = new LinkedList();

@@ -1838,7 +1842,7 @@
         synchronized(this) {
                idle = (int) ((now - timeLastReceivedPacket) / 1000);
         }
-        if((getPeerNodeStatus() == Node.PEER_NODE_STATUS_NEVER_CONNECTED) && 
(getPeerAddedTime() > 1))
+        if((getPeerNodeStatus() == 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) && (getPeerAddedTime() > 1))
             idle = (int) ((now - getPeerAddedTime()) / 1000);
         return getName()+ '\t' +getPeer()+ '\t' +getIdentityString()+ '\t' 
+getLocation().getValue()+ '\t' +getPeerNodeStatusString()+ '\t' +idle;
     }
@@ -2203,7 +2207,7 @@
        }

        public void reportThrottledPacketSendTime(long timeDiff) {
-               node.throttledPacketSendAverage.report(timeDiff);
+               node.nodeStats.throttledPacketSendAverage.report(timeDiff);
                if(logMINOR) Logger.minor(this, "Reporting throttled packet 
send time: "+timeDiff+" to "+getPeer());
        }

@@ -2316,50 +2320,50 @@

   public String getPeerNodeStatusString() {
        int status = getPeerNodeStatus();
-       if(status == Node.PEER_NODE_STATUS_CONNECTED)
+       if(status == PeerManager.PEER_NODE_STATUS_CONNECTED)
                return "CONNECTED";
-       if(status == Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF)
+       if(status == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF)
                return "BACKED OFF";
-       if(status == Node.PEER_NODE_STATUS_TOO_NEW)
+       if(status == PeerManager.PEER_NODE_STATUS_TOO_NEW)
                return "TOO NEW";
-       if(status == Node.PEER_NODE_STATUS_TOO_OLD)
+       if(status == PeerManager.PEER_NODE_STATUS_TOO_OLD)
                return "TOO OLD";
-       if(status == Node.PEER_NODE_STATUS_DISCONNECTED)
+       if(status == PeerManager.PEER_NODE_STATUS_DISCONNECTED)
                return "DISCONNECTED";
-       if(status == Node.PEER_NODE_STATUS_NEVER_CONNECTED)
+       if(status == PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED)
                return "NEVER CONNECTED";
-       if(status == Node.PEER_NODE_STATUS_DISABLED)
+       if(status == PeerManager.PEER_NODE_STATUS_DISABLED)
                return "DISABLED";
-       if(status == Node.PEER_NODE_STATUS_LISTEN_ONLY)
+       if(status == PeerManager.PEER_NODE_STATUS_LISTEN_ONLY)
                return "LISTEN ONLY";
-       if(status == Node.PEER_NODE_STATUS_LISTENING)
+       if(status == PeerManager.PEER_NODE_STATUS_LISTENING)
                return "LISTENING";
-       if(status == Node.PEER_NODE_STATUS_BURSTING)
+       if(status == PeerManager.PEER_NODE_STATUS_BURSTING)
                return "BURSTING";
        return "UNKNOWN STATUS";
   }

   public String getPeerNodeStatusCSSClassName() {
        int status = getPeerNodeStatus();
-       if(status == Node.PEER_NODE_STATUS_CONNECTED)
+       if(status == PeerManager.PEER_NODE_STATUS_CONNECTED)
                return "peer_connected";
-       if(status == Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF)
+       if(status == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF)
                return "peer_backed_off";
-       if(status == Node.PEER_NODE_STATUS_TOO_NEW)
+       if(status == PeerManager.PEER_NODE_STATUS_TOO_NEW)
                return "peer_too_new";
-       if(status == Node.PEER_NODE_STATUS_TOO_OLD)
+       if(status == PeerManager.PEER_NODE_STATUS_TOO_OLD)
                return "peer_too_old";
-       if(status == Node.PEER_NODE_STATUS_DISCONNECTED)
+       if(status == PeerManager.PEER_NODE_STATUS_DISCONNECTED)
                return "peer_disconnected";
-       if(status == Node.PEER_NODE_STATUS_NEVER_CONNECTED)
+       if(status == PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED)
                return "peer_never_connected";
-       if(status == Node.PEER_NODE_STATUS_DISABLED)
+       if(status == PeerManager.PEER_NODE_STATUS_DISABLED)
                return "peer_disabled";
-       if(status == Node.PEER_NODE_STATUS_BURSTING)
+       if(status == PeerManager.PEER_NODE_STATUS_BURSTING)
                return "peer_bursting";
-       if(status == Node.PEER_NODE_STATUS_LISTENING)
+       if(status == PeerManager.PEER_NODE_STATUS_LISTENING)
                return "peer_listening";
-       if(status == Node.PEER_NODE_STATUS_LISTEN_ONLY)
+       if(status == PeerManager.PEER_NODE_STATUS_LISTEN_ONLY)
                return "peer_listen_only";
        return "peer_unknown_status";
   }
@@ -2370,46 +2374,46 @@
                        checkConnectionsAndTrackers();
                        int oldPeerNodeStatus = peerNodeStatus;
                        if(isRoutable()) {  // Function use also updates 
timeLastConnected and timeLastRoutable
-                               peerNodeStatus = 
Node.PEER_NODE_STATUS_CONNECTED;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_CONNECTED;
                                if(now < localRoutingBackedOffUntil ) {
-                                       peerNodeStatus = 
Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF;
+                                       peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF;
                                        
if(!lastRoutingBackoffReason.equals(previousRoutingBackoffReason) || 
(previousRoutingBackoffReason == null)) {
                                                if(previousRoutingBackoffReason 
!= null) {
-                                                       
node.removePeerNodeRoutingBackoffReason(previousRoutingBackoffReason, this);
+                                                       
peers.removePeerNodeRoutingBackoffReason(previousRoutingBackoffReason, this);
                                                }
-                                               
node.addPeerNodeRoutingBackoffReason(lastRoutingBackoffReason, this);
+                                               
peers.addPeerNodeRoutingBackoffReason(lastRoutingBackoffReason, this);
                                                previousRoutingBackoffReason = 
lastRoutingBackoffReason;
                                        }
                                } else {
                                        if(previousRoutingBackoffReason != 
null) {
-                                               
node.removePeerNodeRoutingBackoffReason(previousRoutingBackoffReason, this);
+                                               
peers.removePeerNodeRoutingBackoffReason(previousRoutingBackoffReason, this);
                                                previousRoutingBackoffReason = 
null;
                                        }
                                }
                        } else if(isDisabled) {
-                               peerNodeStatus = Node.PEER_NODE_STATUS_DISABLED;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_DISABLED;
                        } else if(isConnected() && 
verifiedIncompatibleNewerVersion) {
-                               peerNodeStatus = Node.PEER_NODE_STATUS_TOO_NEW;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_TOO_NEW;
                        } else if(isConnected && 
verifiedIncompatibleOlderVersion) {
-                               peerNodeStatus = Node.PEER_NODE_STATUS_TOO_OLD;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_TOO_OLD;
                        } else if(neverConnected) {
-                               peerNodeStatus = 
Node.PEER_NODE_STATUS_NEVER_CONNECTED;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED;
                        } else if(isListenOnly) {
-                               peerNodeStatus = 
Node.PEER_NODE_STATUS_LISTEN_ONLY;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_LISTEN_ONLY;
                        } else if(isBursting) {
-                               peerNodeStatus = Node.PEER_NODE_STATUS_BURSTING;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_BURSTING;
                        } else if(isBurstOnly) {
-                               peerNodeStatus = 
Node.PEER_NODE_STATUS_LISTENING;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_LISTENING;
                        } else {
-                               peerNodeStatus = 
Node.PEER_NODE_STATUS_DISCONNECTED;
+                               peerNodeStatus = 
PeerManager.PEER_NODE_STATUS_DISCONNECTED;
                        }
                        if(!isConnected && (previousRoutingBackoffReason != 
null)) {
-                               
node.removePeerNodeRoutingBackoffReason(previousRoutingBackoffReason, this);
+                               
peers.removePeerNodeRoutingBackoffReason(previousRoutingBackoffReason, this);
                                previousRoutingBackoffReason = null;
                        }
                        if(peerNodeStatus != oldPeerNodeStatus) {
-                         node.removePeerNodeStatus( oldPeerNodeStatus, this );
-                         node.addPeerNodeStatus( peerNodeStatus, this );
+                               peers.removePeerNodeStatus( oldPeerNodeStatus, 
this );
+                         peers.addPeerNodeStatus( peerNodeStatus, this );
                        }
                }
        }

Added: trunk/freenet/src/freenet/node/Persistable.java
===================================================================
--- trunk/freenet/src/freenet/node/Persistable.java                             
(rev 0)
+++ trunk/freenet/src/freenet/node/Persistable.java     2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -0,0 +1,15 @@
+/* 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 freenet.support.SimpleFieldSet;
+
+/**
+ * Something that can be persisted to disk in the form of a SimpleFieldSet.
+ */
+public interface Persistable {
+
+       SimpleFieldSet persistThrottlesToFieldSet();
+
+}

Added: trunk/freenet/src/freenet/node/Persister.java
===================================================================
--- trunk/freenet/src/freenet/node/Persister.java                               
(rev 0)
+++ trunk/freenet/src/freenet/node/Persister.java       2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -0,0 +1,133 @@
+package freenet.node;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import freenet.support.Logger;
+import freenet.support.OOMHandler;
+import freenet.support.SimpleFieldSet;
+
+class Persister implements Runnable {
+
+       Persister(Persistable t, File persistTemp, File persistTarget) {
+               this.persistable = t;
+               this.persistTemp = persistTemp;
+               this.persistTarget = persistTarget;
+       }
+       
+       // Subclass must set the others later
+       protected Persister(Persistable t) {
+               this.persistable = t;
+       }
+       
+       final Persistable persistable;
+       File persistTemp;
+       File persistTarget;
+       
+       void interrupt() {
+               synchronized(this) {
+                       notifyAll();
+               }
+       }
+       
+       public void run() {
+               while(true) {
+                       try {
+                               persistThrottle();
+                       } catch (OutOfMemoryError e) {
+                               OOMHandler.handleOOM(e);
+                               System.err.println("Will restart 
ThrottlePersister...");
+                       } catch (Throwable t) {
+                               Logger.error(this, "Caught in 
ThrottlePersister: "+t, t);
+                               System.err.println("Caught in 
ThrottlePersister: "+t);
+                               t.printStackTrace();
+                               System.err.println("Will restart 
ThrottlePersister...");
+                       }
+                       try {
+                               synchronized(this) {
+                                       wait(60*1000);
+                               }
+                       } catch (InterruptedException e) {
+                               // Maybe it's time to wake up?
+                       }
+               }
+       }
+       
+       public void persistThrottle() {
+               boolean logMINOR = Logger.shouldLog(Logger.MINOR, this);
+               if(logMINOR) Logger.minor(this, "Trying to persist 
throttles...");
+               SimpleFieldSet fs = persistable.persistThrottlesToFieldSet();
+               try {
+                       FileOutputStream fos = new 
FileOutputStream(persistTemp);
+                       // FIXME common pattern, reuse it.
+                       BufferedOutputStream bos = new 
BufferedOutputStream(fos);
+                       OutputStreamWriter osw = new OutputStreamWriter(bos, 
"UTF-8");
+                       BufferedWriter bw = new BufferedWriter(osw);
+                       try {
+                               fs.writeTo(bw);
+                       } catch (IOException e) {
+                               try {
+                                       fos.close();
+                                       persistTemp.delete();
+                                       return;
+                               } catch (IOException e1) {
+                                       // Ignore
+                               }
+                       }
+                       try {
+                               bw.close();
+                       } catch (IOException e) {
+                               // Huh?
+                               Logger.error(this, "Caught while closing: "+e, 
e);
+                               return;
+                       }
+                       // Try an atomic rename
+                       if(!persistTemp.renameTo(persistTarget)) {
+                               // Not supported on some systems (Windows)
+                               if(!persistTarget.delete()) {
+                                       if(persistTarget.exists()) {
+                                               Logger.error(this, "Could not 
delete "+persistTarget+" - check permissions");
+                                       }
+                               }
+                               if(!persistTemp.renameTo(persistTarget)) {
+                                       Logger.error(this, "Could not rename 
"+persistTemp+" to "+persistTarget+" - check permissions");
+                               }
+                       }
+               } catch (FileNotFoundException e) {
+                       Logger.error(this, "Could not store throttle data to 
disk: "+e, e);
+                       return;
+               } catch (UnsupportedEncodingException e) {
+                       Logger.error(this, "Unsupported encoding: UTF-8 !!!!: 
"+e, e);
+               }
+               
+       }
+
+       public SimpleFieldSet read() {
+               SimpleFieldSet throttleFS = null;
+               try {
+                       throttleFS = SimpleFieldSet.readFrom(persistTarget, 
false, true);
+               } catch (IOException e) {
+                       try {
+                               throttleFS = 
SimpleFieldSet.readFrom(persistTemp, false, true);
+                       } catch (FileNotFoundException e1) {
+                               // Ignore
+                       } catch (IOException e1) {
+                               Logger.error(this, "Could not read 
"+persistTarget+" ("+e+") and could not read "+persistTemp+" either ("+e1+ ')');
+                       }
+               }
+               return throttleFS;
+       }
+
+       public void start() {
+               Thread t = new Thread(this, "Persister for "+persistable);
+               t.setDaemon(true);
+               t.start();
+       }
+
+}

Modified: trunk/freenet/src/freenet/node/RequestHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/RequestHandler.java  2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/RequestHandler.java  2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -197,12 +197,12 @@
                int rcvd = rs.getTotalReceivedBytes() + receivedBytes;
                if(key instanceof NodeSSK) {
                        if(logMINOR) Logger.minor(this, "Remote SSK fetch cost 
"+sent+ '/' +rcvd+" bytes ("+status+ ')');
-                       node.remoteSskFetchBytesSentAverage.report(sent);
-                       node.remoteSskFetchBytesReceivedAverage.report(rcvd);
+                       
node.nodeStats.remoteSskFetchBytesSentAverage.report(sent);
+                       
node.nodeStats.remoteSskFetchBytesReceivedAverage.report(rcvd);
                } else {
                        if(logMINOR) Logger.minor(this, "Remote CHK fetch cost 
"+sent+ '/' +rcvd+" bytes ("+status+ ')');
-                       node.remoteChkFetchBytesSentAverage.report(sent);
-                       node.remoteChkFetchBytesReceivedAverage.report(rcvd);
+                       
node.nodeStats.remoteChkFetchBytesSentAverage.report(sent);
+                       
node.nodeStats.remoteChkFetchBytesReceivedAverage.report(rcvd);
                }
             }


Modified: trunk/freenet/src/freenet/node/RequestStarter.java
===================================================================
--- trunk/freenet/src/freenet/node/RequestStarter.java  2007-03-22 19:16:22 UTC 
(rev 12276)
+++ trunk/freenet/src/freenet/node/RequestStarter.java  2007-03-22 20:37:23 UTC 
(rev 12277)
@@ -47,12 +47,14 @@
        final RunningAverage averageOutputBytesPerRequest;
        RequestScheduler sched;
        final NodeClientCore core;
+       final NodeStats stats;
        private long sentRequestTime;
        private final boolean isInsert;

        public RequestStarter(NodeClientCore node, BaseRequestThrottle 
throttle, String name, TokenBucket outputBucket, TokenBucket inputBucket,
                        RunningAverage averageOutputBytesPerRequest, 
RunningAverage averageInputBytesPerRequest, boolean isInsert) {
                this.core = node;
+               this.stats = core.nodeStats;
                this.throttle = throttle;
                this.name = name;
                this.outputBucket = outputBucket;
@@ -122,7 +124,7 @@
                                                        // Ignore
                                                }
                                } while(now < sleepUntil);
-                               core.node.waitUntilNotOverloaded(isInsert);
+                               stats.waitUntilNotOverloaded(isInsert);
                                return;
                        } else {
                                if(logMINOR) Logger.minor(this, "Waiting...");  
                        

Modified: trunk/freenet/src/freenet/node/RequestStarterGroup.java
===================================================================
--- trunk/freenet/src/freenet/node/RequestStarterGroup.java     2007-03-22 
19:16:22 UTC (rev 12276)
+++ trunk/freenet/src/freenet/node/RequestStarterGroup.java     2007-03-22 
20:37:23 UTC (rev 12277)
@@ -36,6 +36,7 @@

        RequestStarterGroup(Node node, NodeClientCore core, int portNumber, 
RandomSource random, Config config, SimpleFieldSet fs) {
                SubConfig schedulerConfig = new SubConfig("node.scheduler", 
config);
+               NodeStats stats = core.nodeStats;

                throttleWindow = new ThrottleWindowManager(2.0, fs == null ? 
null : fs.subset("ThrottleWindow"), node);
                throttleWindowCHK = new ThrottleWindowManager(2.0, fs == null ? 
null : fs.subset("ThrottleWindowCHK"), node);
@@ -43,27 +44,27 @@
                throttleWindowInsert = new ThrottleWindowManager(2.0, fs == 
null ? null : fs.subset("ThrottleWindowInsert"), node);
                throttleWindowRequest = new ThrottleWindowManager(2.0, fs == 
null ? null : fs.subset("ThrottleWindowRequest"), node);
                chkRequestThrottle = new MyRequestThrottle(throttleWindow, 
5000, "CHK Request", fs == null ? null : fs.subset("CHKRequestThrottle"), 
32768);
-               chkRequestStarter = new RequestStarter(core, 
chkRequestThrottle, "CHK Request starter ("+portNumber+ ')', 
node.requestOutputThrottle, node.requestInputThrottle, 
node.localChkFetchBytesSentAverage, node.localChkFetchBytesReceivedAverage, 
false);
+               chkRequestStarter = new RequestStarter(core, 
chkRequestThrottle, "CHK Request starter ("+portNumber+ ')', 
stats.requestOutputThrottle, stats.requestInputThrottle, 
stats.localChkFetchBytesSentAverage, stats.localChkFetchBytesReceivedAverage, 
false);
                chkFetchScheduler = new ClientRequestScheduler(false, false, 
random, chkRequestStarter, node, schedulerConfig, "CHKrequester");
                chkRequestStarter.setScheduler(chkFetchScheduler);
                chkRequestStarter.start();
                //insertThrottle = new ChainedRequestThrottle(10000, 2.0F, 
requestThrottle);
                // FIXME reenable the above
                chkInsertThrottle = new MyRequestThrottle(throttleWindow, 
20000, "CHK Insert", fs == null ? null : fs.subset("CHKInsertThrottle"), 32768);
-               chkInsertStarter = new RequestStarter(core, chkInsertThrottle, 
"CHK Insert starter ("+portNumber+ ')', node.requestOutputThrottle, 
node.requestInputThrottle, node.localChkInsertBytesSentAverage, 
node.localChkInsertBytesReceivedAverage, true);
+               chkInsertStarter = new RequestStarter(core, chkInsertThrottle, 
"CHK Insert starter ("+portNumber+ ')', stats.requestOutputThrottle, 
stats.requestInputThrottle, stats.localChkInsertBytesSentAverage, 
stats.localChkInsertBytesReceivedAverage, true);
                chkPutScheduler = new ClientRequestScheduler(true, false, 
random, chkInsertStarter, node, schedulerConfig, "CHKinserter");
                chkInsertStarter.setScheduler(chkPutScheduler);
                chkInsertStarter.start();

                sskRequestThrottle = new MyRequestThrottle(throttleWindow, 
5000, "SSK Request", fs == null ? null : fs.subset("SSKRequestThrottle"), 1024);
-               sskRequestStarter = new RequestStarter(core, 
sskRequestThrottle, "SSK Request starter ("+portNumber+ ')', 
node.requestOutputThrottle, node.requestInputThrottle, 
node.localSskFetchBytesSentAverage, node.localSskFetchBytesReceivedAverage, 
false);
+               sskRequestStarter = new RequestStarter(core, 
sskRequestThrottle, "SSK Request starter ("+portNumber+ ')', 
stats.requestOutputThrottle, stats.requestInputThrottle, 
stats.localSskFetchBytesSentAverage, stats.localSskFetchBytesReceivedAverage, 
false);
                sskFetchScheduler = new ClientRequestScheduler(false, true, 
random, sskRequestStarter, node, schedulerConfig, "SSKrequester");
                sskRequestStarter.setScheduler(sskFetchScheduler);
                sskRequestStarter.start();
                //insertThrottle = new ChainedRequestThrottle(10000, 2.0F, 
requestThrottle);
                // FIXME reenable the above
                sskInsertThrottle = new MyRequestThrottle(throttleWindow, 
20000, "SSK Insert", fs == null ? null : fs.subset("SSKInsertThrottle"), 1024);
-               sskInsertStarter = new RequestStarter(core, sskInsertThrottle, 
"SSK Insert starter ("+portNumber+ ')', node.requestOutputThrottle, 
node.requestInputThrottle, node.localSskInsertBytesSentAverage, 
node.localSskFetchBytesReceivedAverage, true);
+               sskInsertStarter = new RequestStarter(core, sskInsertThrottle, 
"SSK Insert starter ("+portNumber+ ')', stats.requestOutputThrottle, 
stats.requestInputThrottle, stats.localSskInsertBytesSentAverage, 
stats.localSskFetchBytesReceivedAverage, true);
                sskPutScheduler = new ClientRequestScheduler(true, true, 
random, sskInsertStarter, node, schedulerConfig, "SSKinserter");
                sskInsertStarter.setScheduler(sskPutScheduler);
                sskInsertStarter.start();

Modified: trunk/freenet/src/freenet/node/SSKInsertHandler.java
===================================================================
--- trunk/freenet/src/freenet/node/SSKInsertHandler.java        2007-03-22 
19:16:22 UTC (rev 12276)
+++ trunk/freenet/src/freenet/node/SSKInsertHandler.java        2007-03-22 
20:37:23 UTC (rev 12277)
@@ -312,8 +312,8 @@
                        totalReceived += sender.getTotalReceivedBytes();
                }
                if(logMINOR) Logger.minor(this, "Remote SSK insert cost 
"+totalSent+ '/' +totalReceived+" bytes ("+code+ ')');
-               node.remoteSskInsertBytesSentAverage.report(totalSent);
-               node.remoteSskInsertBytesReceivedAverage.report(totalReceived);
+               
node.nodeStats.remoteSskInsertBytesSentAverage.report(totalSent);
+               
node.nodeStats.remoteSskInsertBytesReceivedAverage.report(totalReceived);
         }

     }

Modified: trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java 
2007-03-22 19:16:22 UTC (rev 12276)
+++ trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java 
2007-03-22 20:37:23 UTC (rev 12277)
@@ -3,12 +3,12 @@
  * http://www.gnu.org/ for further details of the GPL. */
 package freenet.node.useralerts;

-import freenet.node.Node;
+import freenet.node.NodeStats;
 import freenet.support.HTMLNode;

 public class PeerManagerUserAlert implements UserAlert {

-       final Node n;
+       final NodeStats n;
        public int conns = 0;
        public int peers = 0;
        public int neverConn = 0;
@@ -35,7 +35,7 @@
        /** How high can oldestNeverConnectedPeerAge be before we alert (in 
milliseconds)*/
        static final long MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD = 
((long) 2)*7*24*60*60*1000;  // 2 weeks

-       public PeerManagerUserAlert(Node n) {
+       public PeerManagerUserAlert(NodeStats n) {
                this.n = n;
        }

@@ -61,9 +61,9 @@
                        return "Too many open connections";
                if(peers > MAX_PEER_ALERT_THRESHOLD)
                        return "Too many peers";
-               if(n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD))
+               if(n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD))
                        return "bwlimitDelayTime too high";
-               if(n.nodeAveragePingAlertRelevant && (nodeAveragePingTime > 
Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD))
+               if(n.nodeAveragePingAlertRelevant && (nodeAveragePingTime > 
NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD))
                        return "nodeAveragePingTime too high";
                if(oldestNeverConnectedPeerAge > 
MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD)
                        return "Never connected peer(s) too old";
@@ -126,11 +126,11 @@
                "This will also marginally impact your performance as all peers 
(connected or not) consume a small amount of bandwidth and CPU. Consider 
\"cleaning up\" your peer list.";

        static final String TOO_HIGH_BWLIMITDELAYTIME =
-               "This node has to wait too long for available bandwidth 
({BWLIMIT_DELAY_TIME} > "+Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD+").  
Increase your output bandwidth limit and/or remove some peers to improve the 
situation.";
+               "This node has to wait too long for available bandwidth 
({BWLIMIT_DELAY_TIME} > "+NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD+").  
Increase your output bandwidth limit and/or remove some peers to improve the 
situation.";

        static final String TOO_HIGH_PING =
                "This node is having trouble talking with its peers quickly 
enough ({PING_TIME} > "+
-               Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD+").  Increase 
your output bandwidth limit and/or remove some peers to improve the situation.";
+               NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD+").  
Increase your output bandwidth limit and/or remove some peers to improve the 
situation.";

        static final String NEVER_CONNECTED_TWO_WEEKS =
                "One or more of your node's peers have never connected in the 
two weeks since they were added.  Consider removing them since they are 
marginally affecting performance.";
@@ -157,11 +157,11 @@
                        s = replace(TOO_MANY_CONNECTIONS, "\\{CONNS\\}", 
Integer.toString(conns));
                } else if(peers > MAX_PEER_ALERT_THRESHOLD) {
                        s = replace(TOO_MANY_PEERS, "\\{PEERS\\}", 
Integer.toString(peers));
-               } else if(n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
+               } else if(n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
                        s = replace(TOO_HIGH_BWLIMITDELAYTIME, 
"\\{BWLIMIT_DELAY_TIME\\}", Integer.toString(bwlimitDelayTime));

                        // FIXME I'm not convinced about the next one!
-               } else if(n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
+               } else if(n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
                        s = replace(TOO_HIGH_PING, "\\{PING_TIME\\}", 
Integer.toString(nodeAveragePingTime));
                } else if(oldestNeverConnectedPeerAge > 
MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD) {
                        s = NEVER_CONNECTED_TWO_WEEKS;
@@ -217,9 +217,9 @@
                        alertNode.addChild("#", replace(TOO_MANY_CONNECTIONS, 
"\\{CONNS\\}", Integer.toString(conns)));
                } else if (peers > MAX_PEER_ALERT_THRESHOLD) {
                        alertNode.addChild("#", replace(TOO_MANY_PEERS, 
"\\{PEERS\\}", Integer.toString(peers)));
-               } else if (n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
+               } else if (n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
                        alertNode.addChild("#", 
replace(TOO_HIGH_BWLIMITDELAYTIME, "\\{BWLIMIT_DELAY_TIME\\}", 
Integer.toString(bwlimitDelayTime)));
-               } else if (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
+               } else if (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
                        alertNode.addChild("#", replace(TOO_HIGH_PING, 
"\\{PING_TIME\\}", Integer.toString(nodeAveragePingTime)));
                } else if (oldestNeverConnectedPeerAge > 
MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD) {
                        alertNode.addChild("#", NEVER_CONNECTED_TWO_WEEKS);
@@ -235,8 +235,8 @@
                                ((peers - conns) > 
MAX_DISCONN_PEER_ALERT_THRESHOLD) ||
                                (conns > MAX_CONN_ALERT_THRESHOLD) ||
                                (peers > MAX_PEER_ALERT_THRESHOLD) ||
-                               (n.bwlimitDelayAlertRelevant && 
(bwlimitDelayTime > Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) ||
-                               (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)))
+                               (n.bwlimitDelayAlertRelevant && 
(bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) ||
+                               (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)))
                        return UserAlert.CRITICAL_ERROR;
                return UserAlert.ERROR;
        }
@@ -245,15 +245,15 @@
                // only update here so we don't get odd behavior with it 
fluctuating
                bwlimitDelayTime = (int) n.getBwlimitDelayTime();
                nodeAveragePingTime = (int) n.getNodeAveragePingTime();
-               oldestNeverConnectedPeerAge = (int) 
n.getOldestNeverConnectedPeerAge();
+               oldestNeverConnectedPeerAge = (int) 
n.peers.getOldestNeverConnectedPeerAge();
                return ((peers == 0) ||
                                (conns < 3) ||
                                (neverConn > 
MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD) ||
                                ((peers - conns) > 
MAX_DISCONN_PEER_ALERT_THRESHOLD) ||
                                (conns > MAX_CONN_ALERT_THRESHOLD) ||
                                (peers > MAX_PEER_ALERT_THRESHOLD) ||
-                               (n.bwlimitDelayAlertRelevant && 
(bwlimitDelayTime > Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) ||
-                               (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) ||
+                               (n.bwlimitDelayAlertRelevant && 
(bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) ||
+                               (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) ||
                                (oldestNeverConnectedPeerAge > 
MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD)) &&
                                isValid;
        }


Reply via email to