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("

Exploratory tunnels (config):

\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); 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(" (config):

\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; for (int i = 0; i < participating.size(); i++) { HopConfig cfg = (HopConfig)participating.get(i); if (cfg.getProcessedMessagesCount() <= 0) { inactive++; 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(" "); 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"); processed += cfg.getProcessedMessagesCount(); } out.write("
Receive onFrom" + "Send onToExpirationUsageRateRole
" + cfg.getReceiveTunnel().getTunnelId() +"n/a" + netDbLink(cfg.getReceiveFrom()) +" " + cfg.getSendTunnel().getTunnelId() +" " + netDbLink(cfg.getSendTo()) +"  " + DataHelper.formatDuration(timeLeft) + "(grace period)" + cfg.getProcessedMessagesCount() + "KB" + bps + "BpsOutbound EndpointInbound GatewayParticipant
\n"); out.write("
Inactive participating tunnels: " + inactive + "
\n"); out.write("
Lifetime bandwidth usage: " + DataHelper.formatSize(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/OutExpiryUsageGatewayParticipantsParticipantEndpoint
\"Inbound\"
\"Outbound\"" + DataHelper.formatDuration(timeLeft) + "" + info.getProcessedMessagesCount() + "KB" + (id == null ? "" : "" + id) + "" + netDbLink(peer) + (id == null ? "" : " " + id) + cap + " 
\n"); if (in != null) { List pending = in.listPending(); if (pending.size() > 0) out.write("
Build in progress: " + pending.size() + " inbound
\n"); live += pending.size(); } if (outPool != null) { List pending = outPool.listPending(); if (pending.size() > 0) 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.formatSize(processedIn*1024) + "B in, " + DataHelper.formatSize(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 HashComparator()); out.write("

Tunnel Counts By Peer:

\n"); out.write("\n"); for (Hash h : peerList) { out.write("
PeerExpl. + Client% of totalPart. from + to% 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("
Tunnels " + 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 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); } }