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.router.CommSystemFacade; import net.i2p.stat.RateStat; import net.i2p.util.ObjectCounter; /** * */ public class TunnelRenderer { private RouterContext _context; private static final int DISPLAY_LIMIT = 200; public TunnelRenderer(RouterContext ctx) { _context = ctx; } public void renderStatusHTML(Writer out) throws IOException { out.write("

" + _("Exploratory tunnels") + " (" + _("configure") + ")

\n"); renderPool(out, _context.tunnelManager().getInboundExploratoryPool(), _context.tunnelManager().getOutboundExploratoryPool()); List destinations = null; Map clientInboundPools = _context.tunnelManager().getInboundClientPools(); Map 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); // TODO the following code is duplicated in SummaryHelper 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("

" + _("Client tunnels for") + ' ' + _(name)); if (_context.clientManager().isLocal(client)) out.write(" (" + _("configure") + ")

\n"); else out.write(" (" + _("dead") + ")\n"); renderPool(out, in, outPool); } List participating = _context.tunnelDispatcher().listParticipatingTunnels(); Collections.sort(participating, new TunnelComparator()); out.write("

" + _("Participating tunnels") + "

\n"); out.write("" + "\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; int displayed = 0; for (int i = 0; i < participating.size(); i++) { HopConfig cfg = (HopConfig)participating.get(i); long count = cfg.getProcessedMessagesCount(); if (count <= 0) { inactive++; continue; } processed += count; if (++displayed > DISPLAY_LIMIT) continue; out.write(""); if (cfg.getReceiveTunnel() != null) out.write(""); else out.write(""); if (cfg.getReceiveFrom() != null) out.write(""); else out.write(""); if (cfg.getSendTunnel() != null) out.write(""); else out.write(""); if (cfg.getSendTo() != null) out.write(""); else out.write(""); long timeLeft = cfg.getExpiration()-_context.clock().now(); if (timeLeft > 0) out.write(""); else out.write(""); out.write(""); 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(""); if (cfg.getSendTo() == null) out.write(""); else if (cfg.getReceiveFrom() == null) out.write(""); else out.write(""); out.write("\n"); } out.write("
" + _("Receive on") + "" + _("From") + "" + _("Send on") + "" + _("To") + "" + _("Expiration") + "" + _("Usage") + "" + _("Rate") + "" + _("Role") + "
" + cfg.getReceiveTunnel().getTunnelId() +"n/a" + netDbLink(cfg.getReceiveFrom()) +" " + cfg.getSendTunnel().getTunnelId() +" " + netDbLink(cfg.getSendTo()) +" " + DataHelper.formatDuration2(timeLeft) + "(" + _("grace period") + ")" + cfg.getProcessedMessagesCount() + " KB" + bps + " Bps" + _("Outbound Endpoint") + "" + _("Inbound Gateway") + "" + _("Participant") + "
\n"); if (displayed > DISPLAY_LIMIT) out.write("
" + _("Limited display to the {0} tunnels with the highest usage", DISPLAY_LIMIT) + "
\n"); out.write("
" + _("Inactive participating tunnels") + ": " + inactive + "
\n"); out.write("
" + _("Lifetime bandwidth usage") + ": " + DataHelper.formatSize2(processed*1024) + "B
\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 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(""); if (maxLength > 3) { out.write(""); } else if (maxLength == 3) { out.write(""); } if (maxLength > 1) { out.write(""); } out.write("\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(""); else out.write(""); out.write(" \n"); out.write(" \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(" "); } else { String cap = getCapacity(peer); out.write(" "); } if (info.getLength() < maxLength && (info.getLength() == 1 || j == info.getLength() - 2)) { for (int k = info.getLength(); k < maxLength; k++) out.write(" "); } } out.write("\n"); if (info.isInbound()) processedIn += info.getProcessedMessagesCount(); else processedOut += info.getProcessedMessagesCount(); } out.write("
" + _("In/Out") + "" + _("Expiry") + "" + _("Usage") + "" + _("Gateway") + "" + _("Participants") + "" + _("Participant") + "" + _("Endpoint") + "
\"Inbound\"
\"Outbound\"" + DataHelper.formatDuration2(timeLeft) + "" + info.getProcessedMessagesCount() + " KB" + (id == null ? "" : "" + id) + "" + netDbLink(peer) + (id == null ? "" : " " + id) + cap + " 
\n"); if (in != null) { List pending = in.listPending(); if (!pending.isEmpty()) { out.write("
" + _("Build in progress") + ": " + pending.size() + " " + _("inbound") + "
\n"); live += pending.size(); } } if (outPool != null) { List pending = outPool.listPending(); if (!pending.isEmpty()) { out.write("
" + _("Build in progress") + ": " + pending.size() + " " + _("outbound") + "
\n"); live += pending.size(); } } if (live <= 0) out.write("
" + _("No tunnels; waiting for the grace period to end.") + "
\n"); out.write("
" + _("Lifetime bandwidth usage") + ": " + DataHelper.formatSize2(processedIn*1024) + "B " + _("in") + ", " + DataHelper.formatSize2(processedOut*1024) + "B " + _("out") + "
"); } private void renderPeers(Writer out) throws IOException { // count up the peers in the local pools ObjectCounter lc = new ObjectCounter(); int tunnelCount = countTunnelsPerPeer(lc); // count up the peers in the participating tunnels ObjectCounter pc = new ObjectCounter(); int partCount = countParticipatingPerPeer(pc); Set peers = new HashSet(lc.objects()); peers.addAll(pc.objects()); List peerList = new ArrayList(peers); Collections.sort(peerList, new CountryComparator(this._context.commSystem())); out.write("

" + _("Tunnel Counts By Peer") + "

\n"); out.write("\n"); for (Hash h : peerList) { out.write("
" + _("Peer") + "" + _("Our Tunnels") + "" + _("% of total") + "" + _("Participating Tunnels") + "" + _("% of total") + "
"); out.write(netDbLink(h)); out.write(" " + lc.count(h)); out.write(" "); if (tunnelCount > 0) out.write("" + (lc.count(h) * 100 / tunnelCount)); else out.write('0'); out.write(" " + pc.count(h)); out.write(" "); if (partCount > 0) out.write("" + (pc.count(h) * 100 / partCount)); else out.write('0'); out.write('\n'); } out.write("
" + _("Totals") + " " + tunnelCount); out.write("   " + partCount); out.write("  
\n"); } /* duplicate of that in tunnelPoolManager for now */ /** @return total number of non-fallback expl. + client tunnels */ private int countTunnelsPerPeer(ObjectCounter lc) { List 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 pc) { List 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 static class CountryComparator implements Comparator { public CountryComparator(CommSystemFacade comm) { this.comm = comm; } public int compare(Hash l, Hash r) { // get both countries String lc = this.comm.getCountry(l); String rc = this.comm.getCountry(r); // make them non-null lc = (lc == null) ? "zzzz" : lc; rc = (rc == null) ? "zzzz" : rc; // let String handle the rest return lc.compareTo(rc); } private CommSystemFacade comm; } 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); } /** translate a string */ private String _(String s) { return Messages.getString(s, _context); } /** translate a string */ public String _(String s, Object o) { return Messages.getString(s, o, _context); } }