diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelHelper.java
index b605cbf361bc1efd8da3485395cc69d39bbd8f4c..db64f77c2588763efd7ba9f52de273bdbb68875d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/TunnelHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelHelper.java
@@ -9,13 +9,14 @@ public class TunnelHelper extends HelperBase {
     public TunnelHelper() {}
     
     public String getTunnelSummary() {
+        TunnelRenderer renderer = new TunnelRenderer(_context);
         try {
             if (_out != null) {
-                _context.tunnelManager().renderStatusHTML(_out);
+                renderer.renderStatusHTML(_out);
                 return "";
             } else {
                 ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
-                _context.tunnelManager().renderStatusHTML(new OutputStreamWriter(baos));
+                renderer.renderStatusHTML(new OutputStreamWriter(baos));
                 return new String(baos.toByteArray());
             }
         } catch (IOException ioe) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..656068f11cd62db3c25349667b7dcb793a4a5ac9
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
@@ -0,0 +1,313 @@
+package net.i2p.router.web;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
+import net.i2p.data.Hash;
+import net.i2p.data.RouterInfo;
+import net.i2p.data.TunnelId;
+import net.i2p.router.Router;
+import net.i2p.router.RouterContext;
+import net.i2p.router.TunnelInfo;
+import net.i2p.router.TunnelPoolSettings;
+import net.i2p.router.tunnel.HopConfig;
+import net.i2p.router.tunnel.pool.TunnelPool;
+import net.i2p.stat.RateStat;
+import net.i2p.util.ObjectCounter;
+
+/**
+ * 
+ */
+public class TunnelRenderer {
+    private RouterContext _context;
+    
+    public TunnelRenderer(RouterContext ctx) {
+        _context = ctx;
+    }
+    
+    public void renderStatusHTML(Writer out) throws IOException {
+        out.write("<div class=\"wideload\"><h2><a name=\"exploratory\" ></a>Exploratory tunnels (<a href=\"/configtunnels.jsp#exploratory\">config</a>):</h2>\n");
+        renderPool(out, _context.tunnelManager().getInboundExploratoryPool(), _context.tunnelManager().getOutboundExploratoryPool());
+        
+        List<Hash> destinations = null;
+        Map<Hash, TunnelPool> clientInboundPools = _context.tunnelManager().getInboundClientPools();
+        Map<Hash, TunnelPool> clientOutboundPools = _context.tunnelManager().getOutboundClientPools();
+        destinations = new ArrayList(clientInboundPools.keySet());
+        for (int i = 0; i < destinations.size(); i++) {
+            Hash client = destinations.get(i);
+            TunnelPool in = null;
+            TunnelPool outPool = null;
+            in = clientInboundPools.get(client);
+            outPool = clientOutboundPools.get(client);
+            String name = (in != null ? in.getSettings().getDestinationNickname() : null);
+            if ( (name == null) && (outPool != null) )
+                name = outPool.getSettings().getDestinationNickname();
+            if (name == null)
+                name = client.toBase64().substring(0,4);
+            out.write("<h2><a name=\"" + client.toBase64().substring(0,4)
+                      + "\" ></a>Client tunnels for " + name);
+            if (_context.clientManager().isLocal(client))
+                out.write(" (<a href=\"/configtunnels.jsp#" + client.toBase64().substring(0,4) +"\">config</a>):</h2>\n");
+            else
+                out.write(" (dead):</h2>\n");
+            renderPool(out, in, outPool);
+        }
+        
+        List participating = _context.tunnelDispatcher().listParticipatingTunnels();
+        Collections.sort(participating, new TunnelComparator());
+        out.write("<h2><a name=\"participating\"></a>Participating tunnels:</h2><table>\n");
+        out.write("<tr><th>Receive on</th><th>From</th><th>"
+                  + "Send on</th><th>To</th><th>Expiration</th>"
+                  + "<th>Usage</th><th>Rate</th><th>Role</th></tr>\n");
+        long processed = 0;
+        RateStat rs = _context.statManager().getRate("tunnel.participatingMessageCount");
+        if (rs != null)
+            processed = (long)rs.getRate(10*60*1000).getLifetimeTotalValue();
+        int inactive = 0;
+        for (int i = 0; i < participating.size(); i++) {
+            HopConfig cfg = (HopConfig)participating.get(i);
+            if (cfg.getProcessedMessagesCount() <= 0) {
+                inactive++;
+                continue;
+            }
+            out.write("<tr>");
+            if (cfg.getReceiveTunnel() != null)
+                out.write(" <td class=\"cells\" align=\"center\">" + cfg.getReceiveTunnel().getTunnelId() +"</td>");
+            else
+                out.write(" <td class=\"cells\" align=\"center\">n/a</td>");
+            if (cfg.getReceiveFrom() != null)
+                out.write(" <td class=\"cells\" align=\"right\">" + netDbLink(cfg.getReceiveFrom()) +"</td>");
+            else
+                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
+            if (cfg.getSendTunnel() != null)
+                out.write(" <td class=\"cells\" align=\"center\">" + cfg.getSendTunnel().getTunnelId() +"</td>");
+            else
+                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
+            if (cfg.getSendTo() != null)
+                out.write(" <td class=\"cells\" align=\"center\">" + netDbLink(cfg.getSendTo()) +"</td>");
+            else
+//                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
+                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
+            long timeLeft = cfg.getExpiration()-_context.clock().now();
+            if (timeLeft > 0)
+                out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration(timeLeft) + "</td>");
+            else
+                out.write(" <td class=\"cells\" align=\"center\">(grace period)</td>");
+            out.write(" <td class=\"cells\" align=\"center\">" + cfg.getProcessedMessagesCount() + "KB</td>");
+            int lifetime = (int) ((_context.clock().now() - cfg.getCreation()) / 1000);
+            if (lifetime <= 0)
+                lifetime = 1;
+            if (lifetime > 10*60)
+                lifetime = 10*60;
+            int bps = 1024 * (int) cfg.getProcessedMessagesCount() / lifetime;
+            out.write(" <td class=\"cells\" align=\"center\">" + bps + "Bps</td>");
+            if (cfg.getSendTo() == null)
+                out.write(" <td class=\"cells\" align=\"center\">Outbound Endpoint</td>");
+            else if (cfg.getReceiveFrom() == null)
+                out.write(" <td class=\"cells\" align=\"center\">Inbound Gateway</td>");
+            else
+                out.write(" <td class=\"cells\" align=\"center\">Participant</td>");
+            out.write("</tr>\n");
+            processed += cfg.getProcessedMessagesCount();
+        }
+        out.write("</table>\n");
+        out.write("<div class=\"statusnotes\"><b>Inactive participating tunnels: " + inactive + "</b></div>\n");
+        out.write("<div class=\"statusnotes\"><b>Lifetime bandwidth usage: " + DataHelper.formatSize(processed*1024) + "B</b></div>\n");
+        renderPeers(out);
+    }
+    
+    private static class TunnelComparator implements Comparator {
+         public int compare(Object l, Object r) {
+             return (int) (((HopConfig)r).getProcessedMessagesCount() - ((HopConfig)l).getProcessedMessagesCount());
+        }
+    }
+
+    private void renderPool(Writer out, TunnelPool in, TunnelPool outPool) throws IOException {
+        List<TunnelInfo> tunnels = null;
+        if (in == null)
+            tunnels = new ArrayList();
+        else
+            tunnels = in.listTunnels();
+        if (outPool != null)
+            tunnels.addAll(outPool.listTunnels());
+        
+        long processedIn = (in != null ? in.getLifetimeProcessed() : 0);
+        long processedOut = (outPool != null ? outPool.getLifetimeProcessed() : 0);
+        
+        int live = 0;
+        int maxLength = 1;
+        for (int i = 0; i < tunnels.size(); i++) {
+            TunnelInfo info = tunnels.get(i);
+            if (info.getLength() > maxLength)
+                maxLength = info.getLength();
+        }
+        out.write("<table><tr><th>In/Out</th><th>Expiry</th><th>Usage</th><th>Gateway</th>");
+        if (maxLength > 3) {
+            out.write("<th align=\"center\" colspan=\"" + (maxLength - 2));
+            out.write("\">Participants</th>");
+        }
+        else if (maxLength == 3) {
+            out.write("<th>Participant</th>");
+        }
+        if (maxLength > 1) {
+            out.write("<th>Endpoint</th>");
+        }
+        out.write("</tr>\n");
+        for (int i = 0; i < tunnels.size(); i++) {
+            TunnelInfo info = tunnels.get(i);
+            long timeLeft = info.getExpiration()-_context.clock().now();
+            if (timeLeft <= 0)
+                continue; // don't display tunnels in their grace period
+            live++;
+            if (info.isInbound())
+                out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/inbound.png\" alt=\"Inbound\" title=\"Inbound\"></td>");
+            else
+                out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"Outbound\"></td>");
+            out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration(timeLeft) + "</td>\n");
+            out.write(" <td class=\"cells\" align=\"center\">" + info.getProcessedMessagesCount() + "KB</td>\n");
+            for (int j = 0; j < info.getLength(); j++) {
+                Hash peer = info.getPeer(j);
+                TunnelId id = (info.isInbound() ? info.getReceiveTunnelId(j) : info.getSendTunnelId(j));
+                if (_context.routerHash().equals(peer)) {
+                    out.write(" <td class=\"cells\" align=\"center\">" + (id == null ? "" : "" + id) + "</td>");
+                } else {
+                    String cap = getCapacity(peer);
+                    out.write(" <td class=\"cells\" align=\"center\">" + netDbLink(peer) + (id == null ? "" : " " + id) + cap + "</td>");                
+                }
+                if (info.getLength() < maxLength && (info.getLength() == 1 || j == info.getLength() - 2)) {
+                    for (int k = info.getLength(); k < maxLength; k++)
+                        out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
+                }
+            }
+            out.write("</tr>\n");
+            
+            if (info.isInbound()) 
+                processedIn += info.getProcessedMessagesCount();
+            else
+                processedOut += info.getProcessedMessagesCount();
+        }
+        out.write("</table>\n");
+        if (in != null) {
+            List pending = in.listPending();
+            if (pending.size() > 0)
+                out.write("<div class=\"statusnotes\"><center><b>Build in progress: " + pending.size() + " inbound</b></center></div>\n");
+            live += pending.size();
+        }
+        if (outPool != null) {
+            List pending = outPool.listPending();
+            if (pending.size() > 0)
+                out.write("<div class=\"statusnotes\"><center><b>Build in progress: " + pending.size() + " outbound</b></center></div>\n");
+            live += pending.size();
+        }
+        if (live <= 0)
+            out.write("<div class=\"statusnotes\"><center><b>No tunnels; waiting for the grace period to end.</center></b></div>\n");
+        out.write("<div class=\"statusnotes\"><center><b>Lifetime bandwidth usage: " + DataHelper.formatSize(processedIn*1024) + "B in, " +
+                  DataHelper.formatSize(processedOut*1024) + "B out</b></center></div>");
+    }
+    
+    private void renderPeers(Writer out) throws IOException {
+        // count up the peers in the local pools
+        ObjectCounter<Hash> lc = new ObjectCounter();
+        int tunnelCount = countTunnelsPerPeer(lc);
+
+        // count up the peers in the participating tunnels
+        ObjectCounter<Hash> pc = new ObjectCounter();
+        int partCount = countParticipatingPerPeer(pc);
+
+        Set<Hash> peers = new HashSet(lc.objects());
+        peers.addAll(pc.objects());
+        List<Hash> peerList = new ArrayList(peers);
+        Collections.sort(peerList, new HashComparator());
+
+        out.write("<h2><a name=\"peers\"></a>Tunnel Counts By Peer:</h2>\n");
+        out.write("<table><tr><th>Peer</th><th>Expl. + Client</th><th>% of total</th><th>Part. from + to</th><th>% of total</th></tr>\n");
+        for (Hash h : peerList) {
+             out.write("<tr> <td class=\"cells\" align=\"center\">");
+             out.write(netDbLink(h));
+             out.write(" <td class=\"cells\" align=\"center\">" + lc.count(h));
+             out.write(" <td class=\"cells\" align=\"center\">");
+             if (tunnelCount > 0)
+                 out.write("" + (lc.count(h) * 100 / tunnelCount));
+             else
+                 out.write('0');
+             out.write(" <td class=\"cells\" align=\"center\">" + pc.count(h));
+             out.write(" <td class=\"cells\" align=\"center\">");
+             if (partCount > 0)
+                 out.write("" + (pc.count(h) * 100 / partCount));
+             else
+                 out.write('0');
+             out.write('\n');
+        }
+        out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>Tunnels</b> <td align=\"center\"><b>" + tunnelCount);
+        out.write("</b> <td>&nbsp;</td> <td align=\"center\"><b>" + partCount);
+        out.write("</b> <td>&nbsp;</td></tr></table></div>\n");
+    }
+
+    /* duplicate of that in tunnelPoolManager for now */
+    /** @return total number of non-fallback expl. + client tunnels */
+    private int countTunnelsPerPeer(ObjectCounter<Hash> lc) {
+        List<TunnelPool> pools = new ArrayList();
+        _context.tunnelManager().listPools(pools);
+        int tunnelCount = 0;
+        for (TunnelPool tp : pools) {
+            for (TunnelInfo info : tp.listTunnels()) {
+                if (info.getLength() > 1) {
+                    tunnelCount++;
+                    for (int j = 0; j < info.getLength(); j++) {
+                        Hash peer = info.getPeer(j);
+                        if (!_context.routerHash().equals(peer))
+                            lc.increment(peer);
+                    }
+                }
+            }
+        }
+        return tunnelCount;
+    }
+
+    /** @return total number of part. tunnels */
+    private int countParticipatingPerPeer(ObjectCounter<Hash> pc) {
+        List<HopConfig> participating = _context.tunnelDispatcher().listParticipatingTunnels();
+        for (HopConfig cfg : participating) {
+            Hash from = cfg.getReceiveFrom();
+            if (from != null)
+                pc.increment(from);
+            Hash to = cfg.getSendTo();
+            if (to != null)
+                pc.increment(to);
+        }
+        return participating.size();
+    }
+
+    private static class HashComparator implements Comparator {
+         public int compare(Object l, Object r) {
+             return ((Hash)l).toBase64().compareTo(((Hash)r).toBase64());
+        }
+    }
+
+    private String getCapacity(Hash peer) {
+        RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer);
+        if (info != null) {
+            String caps = info.getCapabilities();
+            for (char c = Router.CAPABILITY_BW12; c <= Router.CAPABILITY_BW256; c++) {
+                if (caps.indexOf(c) >= 0)
+                    return " " + c;
+            }
+        }
+        return "";
+    }
+
+    private String netDbLink(Hash peer) {
+        return _context.commSystem().renderPeerHTML(peer);
+    }
+}
diff --git a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java
index 3a19cd1178946638d7023e10b9e466719663bfe0..4b8fb486e45f7e395c333fdb0cf0309319ad6ef3 100644
--- a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java
+++ b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java
@@ -10,11 +10,14 @@ package net.i2p.router;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.TunnelId;
+import net.i2p.router.tunnel.pool.TunnelPool;
 
 /**
  * Build and maintain tunnels throughout the network.
@@ -50,4 +53,10 @@ class DummyTunnelManagerFacade implements TunnelManagerFacade {
     public void restart() {}
     public void shutdown() {}
     public void startup() {}
+
+    public void listPools(List<TunnelPool> out) {}
+    public Map<Hash, TunnelPool> getInboundClientPools() { return null; }
+    public Map<Hash, TunnelPool> getOutboundClientPools() { return null; }
+    public TunnelPool getInboundExploratoryPool() { return null; }
+    public TunnelPool getOutboundExploratoryPool() { return null; }
 }
diff --git a/router/java/src/net/i2p/router/TunnelManagerFacade.java b/router/java/src/net/i2p/router/TunnelManagerFacade.java
index e8bdc31d380460596c721d9c710207ca6f350770..148499a43d4991ed7bddeba3485837be105c6336 100644
--- a/router/java/src/net/i2p/router/TunnelManagerFacade.java
+++ b/router/java/src/net/i2p/router/TunnelManagerFacade.java
@@ -8,11 +8,14 @@ package net.i2p.router;
  *
  */
 
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.TunnelId;
+import net.i2p.router.tunnel.pool.TunnelPool;
 
 /**
  * Build and maintain tunnels throughout the network.
@@ -74,4 +77,14 @@ public interface TunnelManagerFacade extends Service {
     public void setOutboundSettings(TunnelPoolSettings settings);
     public void setInboundSettings(Hash client, TunnelPoolSettings settings);
     public void setOutboundSettings(Hash client, TunnelPoolSettings settings);
+    /** for TunnelRenderer in router console */
+    public void listPools(List<TunnelPool> out);
+    /** for TunnelRenderer in router console */
+    public Map<Hash, TunnelPool> getInboundClientPools();
+    /** for TunnelRenderer in router console */
+    public Map<Hash, TunnelPool> getOutboundClientPools();
+    /** for TunnelRenderer in router console */
+    public TunnelPool getInboundExploratoryPool();
+    /** for TunnelRenderer in router console */
+    public TunnelPool getOutboundExploratoryPool();
 }
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
index f7c752c8c981ed1d61f2271888d4d8a2120cc81c..d879750359ba08992cb4effd9c856040c1dea5f4 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
@@ -391,7 +391,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
     }
     
     /** list of TunnelPool instances currently in play */
-    void listPools(List<TunnelPool> out) {
+    public void listPools(List<TunnelPool> out) {
         synchronized (_clientInboundPools) {
             out.addAll(_clientInboundPools.values());
         }
@@ -409,227 +409,8 @@ public class TunnelPoolManager implements TunnelManagerFacade {
 
     public int getInboundBuildQueueSize() { return _executor.getInboundBuildQueueSize(); }
     
-    
+    /** @deprecated moved to routerconsole */
     public void renderStatusHTML(Writer out) throws IOException {
-        out.write("<div class=\"wideload\"><h2><a name=\"exploratory\" ></a>Exploratory tunnels (<a href=\"/configtunnels.jsp#exploratory\">config</a>):</h2>\n");
-        renderPool(out, _inboundExploratory, _outboundExploratory);
-        
-        List<Hash> destinations = null;
-        synchronized (_clientInboundPools) {
-            destinations = new ArrayList(_clientInboundPools.keySet());
-        }
-        for (int i = 0; i < destinations.size(); i++) {
-            Hash client = destinations.get(i);
-            TunnelPool in = null;
-            TunnelPool outPool = null;
-            synchronized (_clientInboundPools) {
-                in = _clientInboundPools.get(client);
-            }
-            synchronized (_clientOutboundPools) {
-                outPool = _clientOutboundPools.get(client);
-            }
-            String name = (in != null ? in.getSettings().getDestinationNickname() : null);
-            if ( (name == null) && (outPool != null) )
-                name = outPool.getSettings().getDestinationNickname();
-            if (name == null)
-                name = client.toBase64().substring(0,4);
-            out.write("<h2><a name=\"" + client.toBase64().substring(0,4)
-                      + "\" ></a>Client tunnels for " + name);
-            if (_context.clientManager().isLocal(client))
-                out.write(" (<a href=\"/configtunnels.jsp#" + client.toBase64().substring(0,4) +"\">config</a>):</h2>\n");
-            else
-                out.write(" (dead):</h2>\n");
-            renderPool(out, in, outPool);
-        }
-        
-        List participating = _context.tunnelDispatcher().listParticipatingTunnels();
-        Collections.sort(participating, new TunnelComparator());
-        out.write("<h2><a name=\"participating\"></a>Participating tunnels:</h2><table>\n");
-        out.write("<tr><th>Receive on</th><th>From</th><th>"
-                  + "Send on</th><th>To</th><th>Expiration</th>"
-                  + "<th>Usage</th><th>Rate</th><th>Role</th></tr>\n");
-        long processed = 0;
-        RateStat rs = _context.statManager().getRate("tunnel.participatingMessageCount");
-        if (rs != null)
-            processed = (long)rs.getRate(10*60*1000).getLifetimeTotalValue();
-        int inactive = 0;
-        for (int i = 0; i < participating.size(); i++) {
-            HopConfig cfg = (HopConfig)participating.get(i);
-            if (cfg.getProcessedMessagesCount() <= 0) {
-                inactive++;
-                continue;
-            }
-            out.write("<tr>");
-            if (cfg.getReceiveTunnel() != null)
-                out.write(" <td class=\"cells\" align=\"center\">" + cfg.getReceiveTunnel().getTunnelId() +"</td>");
-            else
-                out.write(" <td class=\"cells\" align=\"center\">n/a</td>");
-            if (cfg.getReceiveFrom() != null)
-                out.write(" <td class=\"cells\" align=\"right\">" + netDbLink(cfg.getReceiveFrom()) +"</td>");
-            else
-                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
-            if (cfg.getSendTunnel() != null)
-                out.write(" <td class=\"cells\" align=\"center\">" + cfg.getSendTunnel().getTunnelId() +"</td>");
-            else
-                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
-            if (cfg.getSendTo() != null)
-                out.write(" <td class=\"cells\" align=\"center\">" + netDbLink(cfg.getSendTo()) +"</td>");
-            else
-//                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
-                out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
-            long timeLeft = cfg.getExpiration()-_context.clock().now();
-            if (timeLeft > 0)
-                out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration(timeLeft) + "</td>");
-            else
-                out.write(" <td class=\"cells\" align=\"center\">(grace period)</td>");
-            out.write(" <td class=\"cells\" align=\"center\">" + cfg.getProcessedMessagesCount() + "KB</td>");
-            int lifetime = (int) ((_context.clock().now() - cfg.getCreation()) / 1000);
-            if (lifetime <= 0)
-                lifetime = 1;
-            if (lifetime > 10*60)
-                lifetime = 10*60;
-            int bps = 1024 * (int) cfg.getProcessedMessagesCount() / lifetime;
-            out.write(" <td class=\"cells\" align=\"center\">" + bps + "Bps</td>");
-            if (cfg.getSendTo() == null)
-                out.write(" <td class=\"cells\" align=\"center\">Outbound Endpoint</td>");
-            else if (cfg.getReceiveFrom() == null)
-                out.write(" <td class=\"cells\" align=\"center\">Inbound Gateway</td>");
-            else
-                out.write(" <td class=\"cells\" align=\"center\">Participant</td>");
-            out.write("</tr>\n");
-            processed += cfg.getProcessedMessagesCount();
-        }
-        out.write("</table>\n");
-        out.write("<div class=\"statusnotes\"><b>Inactive participating tunnels: " + inactive + "</b></div>\n");
-        out.write("<div class=\"statusnotes\"><b>Lifetime bandwidth usage: " + DataHelper.formatSize(processed*1024) + "B</b></div>\n");
-        renderPeers(out);
-    }
-    
-    class TunnelComparator implements Comparator {
-         public int compare(Object l, Object r) {
-             return (int) (((HopConfig)r).getProcessedMessagesCount() - ((HopConfig)l).getProcessedMessagesCount());
-        }
-    }
-
-    private void renderPool(Writer out, TunnelPool in, TunnelPool outPool) throws IOException {
-        List<TunnelInfo> tunnels = null;
-        if (in == null)
-            tunnels = new ArrayList();
-        else
-            tunnels = in.listTunnels();
-        if (outPool != null)
-            tunnels.addAll(outPool.listTunnels());
-        
-        long processedIn = (in != null ? in.getLifetimeProcessed() : 0);
-        long processedOut = (outPool != null ? outPool.getLifetimeProcessed() : 0);
-        
-        int live = 0;
-        int maxLength = 1;
-        for (int i = 0; i < tunnels.size(); i++) {
-            TunnelInfo info = tunnels.get(i);
-            if (info.getLength() > maxLength)
-                maxLength = info.getLength();
-        }
-        out.write("<table><tr><th>In/Out</th><th>Expiry</th><th>Usage</th><th>Gateway</th>");
-        if (maxLength > 3) {
-            out.write("<th align=\"center\" colspan=\"" + (maxLength - 2));
-            out.write("\">Participants</th>");
-        }
-        else if (maxLength == 3) {
-            out.write("<th>Participant</th>");
-        }
-        if (maxLength > 1) {
-            out.write("<th>Endpoint</th>");
-        }
-        out.write("</tr>\n");
-        for (int i = 0; i < tunnels.size(); i++) {
-            TunnelInfo info = tunnels.get(i);
-            long timeLeft = info.getExpiration()-_context.clock().now();
-            if (timeLeft <= 0)
-                continue; // don't display tunnels in their grace period
-            live++;
-            if (info.isInbound())
-                out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/inbound.png\" alt=\"Inbound\" title=\"Inbound\"></td>");
-            else
-                out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"Outbound\"></td>");
-            out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration(timeLeft) + "</td>\n");
-            out.write(" <td class=\"cells\" align=\"center\">" + info.getProcessedMessagesCount() + "KB</td>\n");
-            for (int j = 0; j < info.getLength(); j++) {
-                Hash peer = info.getPeer(j);
-                TunnelId id = (info.isInbound() ? info.getReceiveTunnelId(j) : info.getSendTunnelId(j));
-                if (_context.routerHash().equals(peer)) {
-                    out.write(" <td class=\"cells\" align=\"center\">" + (id == null ? "" : "" + id) + "</td>");
-                } else {
-                    String cap = getCapacity(peer);
-                    out.write(" <td class=\"cells\" align=\"center\">" + netDbLink(peer) + (id == null ? "" : " " + id) + cap + "</td>");                
-                }
-                if (info.getLength() < maxLength && (info.getLength() == 1 || j == info.getLength() - 2)) {
-                    for (int k = info.getLength(); k < maxLength; k++)
-                        out.write(" <td class=\"cells\" align=\"center\">&nbsp;</td>");
-                }
-            }
-            out.write("</tr>\n");
-            
-            if (info.isInbound()) 
-                processedIn += info.getProcessedMessagesCount();
-            else
-                processedOut += info.getProcessedMessagesCount();
-        }
-        out.write("</table>\n");
-        if (in != null) {
-            List pending = in.listPending();
-            if (pending.size() > 0)
-                out.write("<div class=\"statusnotes\"><center><b>Build in progress: " + pending.size() + " inbound</b></center></div>\n");
-            live += pending.size();
-        }
-        if (outPool != null) {
-            List pending = outPool.listPending();
-            if (pending.size() > 0)
-                out.write("<div class=\"statusnotes\"><center><b>Build in progress: " + pending.size() + " outbound</b></center></div>\n");
-            live += pending.size();
-        }
-        if (live <= 0)
-            out.write("<div class=\"statusnotes\"><center><b>No tunnels; waiting for the grace period to end.</center></b></div>\n");
-        out.write("<div class=\"statusnotes\"><center><b>Lifetime bandwidth usage: " + DataHelper.formatSize(processedIn*1024) + "B in, " +
-                  DataHelper.formatSize(processedOut*1024) + "B out</b></center></div>");
-    }
-    
-    private void renderPeers(Writer out) throws IOException {
-        // count up the peers in the local pools
-        ObjectCounter<Hash> lc = new ObjectCounter();
-        int tunnelCount = countTunnelsPerPeer(lc);
-
-        // count up the peers in the participating tunnels
-        ObjectCounter<Hash> pc = new ObjectCounter();
-        int partCount = countParticipatingPerPeer(pc);
-
-        Set<Hash> peers = new HashSet(lc.objects());
-        peers.addAll(pc.objects());
-        List<Hash> peerList = new ArrayList(peers);
-        Collections.sort(peerList, new HashComparator());
-
-        out.write("<h2><a name=\"peers\"></a>Tunnel Counts By Peer:</h2>\n");
-        out.write("<table><tr><th>Peer</th><th>Expl. + Client</th><th>% of total</th><th>Part. from + to</th><th>% of total</th></tr>\n");
-        for (Hash h : peerList) {
-             out.write("<tr> <td class=\"cells\" align=\"center\">");
-             out.write(netDbLink(h));
-             out.write(" <td class=\"cells\" align=\"center\">" + lc.count(h));
-             out.write(" <td class=\"cells\" align=\"center\">");
-             if (tunnelCount > 0)
-                 out.write("" + (lc.count(h) * 100 / tunnelCount));
-             else
-                 out.write('0');
-             out.write(" <td class=\"cells\" align=\"center\">" + pc.count(h));
-             out.write(" <td class=\"cells\" align=\"center\">");
-             if (partCount > 0)
-                 out.write("" + (pc.count(h) * 100 / partCount));
-             else
-                 out.write('0');
-             out.write('\n');
-        }
-        out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>Tunnels</b> <td align=\"center\"><b>" + tunnelCount);
-        out.write("</b> <td>&nbsp;</td> <td align=\"center\"><b>" + partCount);
-        out.write("</b> <td>&nbsp;</td></tr></table></div>\n");
     }
 
     /** @return total number of non-fallback expl. + client tunnels */
@@ -682,39 +463,27 @@ public class TunnelPoolManager implements TunnelManagerFacade {
         return rv;
     }
 
-    /** @return total number of part. tunnels */
-    private int countParticipatingPerPeer(ObjectCounter<Hash> pc) {
-        List<HopConfig> participating = _context.tunnelDispatcher().listParticipatingTunnels();
-        for (HopConfig cfg : participating) {
-            Hash from = cfg.getReceiveFrom();
-            if (from != null)
-                pc.increment(from);
-            Hash to = cfg.getSendTo();
-            if (to != null)
-                pc.increment(to);
+    /** for TunnelRenderer in router console */
+    public Map<Hash, TunnelPool> getInboundClientPools() {
+        synchronized (_clientInboundPools) {
+            return new HashMap(_clientInboundPools);
         }
-        return participating.size();
     }
 
-    class HashComparator implements Comparator {
-         public int compare(Object l, Object r) {
-             return ((Hash)l).toBase64().compareTo(((Hash)r).toBase64());
+    /** for TunnelRenderer in router console */
+    public Map<Hash, TunnelPool> getOutboundClientPools() {
+        synchronized (_clientOutboundPools) {
+            return new HashMap(_clientOutboundPools);
         }
     }
 
-    private String getCapacity(Hash peer) {
-        RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer);
-        if (info != null) {
-            String caps = info.getCapabilities();
-            for (char c = Router.CAPABILITY_BW12; c <= Router.CAPABILITY_BW256; c++) {
-                if (caps.indexOf(c) >= 0)
-                    return " " + c;
-            }
-        }
-        return "";
+    /** for TunnelRenderer in router console */
+    public TunnelPool getInboundExploratoryPool() {
+        return _inboundExploratory;
     }
 
-    private String netDbLink(Hash peer) {
-        return _context.commSystem().renderPeerHTML(peer);
+    /** for TunnelRenderer in router console */
+    public TunnelPool getOutboundExploratoryPool() {
+        return _outboundExploratory;
     }
 }