package net.i2p.router.web; import java.io.IOException; import java.io.Writer; import java.text.DecimalFormat; import java.util.Comparator; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.RouterInfo; import net.i2p.router.RouterContext; import net.i2p.router.peermanager.DBHistory; import net.i2p.router.peermanager.PeerProfile; import net.i2p.router.peermanager.ProfileOrganizer; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; /** * Helper class to refactor the HTML rendering from out of the ProfileOrganizer * */ class ProfileOrganizerRenderer { private final RouterContext _context; private final ProfileOrganizer _organizer; private final ProfileComparator _comparator; public ProfileOrganizerRenderer(ProfileOrganizer organizer, RouterContext context) { _context = context; _organizer = organizer; _comparator = new ProfileComparator(); } /** * @param mode 0 = high cap; 1 = all; 2 = floodfill */ public void renderStatusHTML(Writer out, int mode) throws IOException { boolean full = mode == 1; Set peers = _organizer.selectAllPeers(); long now = _context.clock().now(); long hideBefore = now - 90*60*1000; TreeSet order = new TreeSet(_comparator); TreeSet integratedPeers = new TreeSet(_comparator); int older = 0; int standard = 0; for (Iterator iter = peers.iterator(); iter.hasNext();) { Hash peer = iter.next(); if (_organizer.getUs().equals(peer)) continue; PeerProfile prof = _organizer.getProfile(peer); //if (_organizer.isWellIntegrated(peer)) { // integratedPeers.add(prof); //} else { RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null && info.getCapabilities().indexOf("f") >= 0) integratedPeers.add(prof); //} if (prof.getLastSendSuccessful() <= hideBefore) { older++; continue; } if ((!full) && !_organizer.isHighCapacity(peer)) { standard++; continue; } order.add(prof); } int fast = 0; int reliable = 0; int integrated = 0; StringBuilder buf = new StringBuilder(16*1024); //// //// don't bother reindenting //// if (mode < 2) { //buf.append("

").append(_("Peer Profiles")).append("

\n

"); buf.append(ngettext("Showing 1 recent profile.", "Showing {0} recent profiles.", order.size())).append('\n'); if (older > 0) buf.append(ngettext("Hiding 1 older profile.", "Hiding {0} older profiles.", older)).append('\n'); if (standard > 0) buf.append("").append(ngettext("Hiding 1 standard profile.", "Hiding {0} standard profiles.", standard)).append("\n"); buf.append("

"); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); int prevTier = 1; for (Iterator iter = order.iterator(); iter.hasNext();) { PeerProfile prof = iter.next(); Hash peer = prof.getPeer(); int tier = 0; boolean isIntegrated = false; if (_organizer.isFast(peer)) { tier = 1; fast++; reliable++; } else if (_organizer.isHighCapacity(peer)) { tier = 2; reliable++; } else if (_organizer.isFailing(peer)) { } else { tier = 3; } if (_organizer.isWellIntegrated(peer)) { isIntegrated = true; integrated++; } if (tier != prevTier) buf.append("\n"); prevTier = tier; buf.append(""); //buf.append("\n"); buf.append(""); // let's not build the whole page in memory (~500 bytes per peer) out.write(buf.toString()); buf.setLength(0); } buf.append("
").append(_("Peer")).append("").append(_("Groups (Caps)")).append("").append(_("Speed")).append("").append(_("Capacity")).append("").append(_("Integration")).append("").append(_("Status")).append(" 

"); buf.append(_context.commSystem().renderPeerHTML(peer)); // debug //if(prof.getIsExpandedDB()) // buf.append(" ** "); buf.append(""); switch (tier) { case 1: buf.append(_("Fast, High Capacity")); break; case 2: buf.append(_("High Capacity")); break; case 3: buf.append(_("Standard")); break; default: buf.append(_("Failing")); break; } if (isIntegrated) buf.append(", ").append(_("Integrated")); RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null) { // prevent HTML injection in the caps and version buf.append(" (").append(DataHelper.stripHTML(info.getCapabilities())); String v = info.getOption("router.version"); if (v != null) buf.append(' ').append(DataHelper.stripHTML(v)); buf.append(')'); } buf.append("").append(num(prof.getSpeedValue())); long bonus = prof.getSpeedBonus(); if (bonus != 0) { if (bonus > 0) buf.append(" (+"); else buf.append(" ("); buf.append(bonus).append(')'); } buf.append("").append(num(prof.getCapacityValue())); bonus = prof.getCapacityBonus(); if (bonus != 0) { if (bonus > 0) buf.append(" (+"); else buf.append(" ("); buf.append(bonus).append(')'); } buf.append("").append(num(prof.getIntegrationValue())); buf.append(""); if (_context.shitlist().isShitlisted(peer)) buf.append(_("Banned")); if (prof.getIsFailing()) buf.append(' ').append(_("Failing")); if (_context.commSystem().wasUnreachable(peer)) buf.append(' ').append(_("Unreachable")); Rate failed = prof.getTunnelHistory().getFailedRate().getRate(30*60*1000); long fails = failed.getCurrentEventCount() + failed.getLastEventCount(); if (fails > 0) { Rate accepted = prof.getTunnelCreateResponseTime().getRate(30*60*1000); long total = fails + accepted.getCurrentEventCount() + accepted.getLastEventCount(); if (total / fails <= 10) // hide if < 10% buf.append(' ').append(fails).append('/').append(total).append(' ').append(_("Test Fails")); } buf.append(" ").append(_("profile")).append(""); buf.append("").append(_("profile")).append(""); buf.append(" +-
"); //// //// don't bother reindenting //// } else { //buf.append("

").append(_("Floodfill and Integrated Peers")) // .append(" (").append(integratedPeers.size()).append(")

\n"); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); for (Iterator iter = integratedPeers.iterator(); iter.hasNext();) { PeerProfile prof = iter.next(); Hash peer = prof.getPeer(); buf.append(""); RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null) buf.append(""); else buf.append(""); buf.append(""); long time; time = now - prof.getLastHeardAbout(); buf.append(""); time = now - prof.getLastHeardFrom(); buf.append(""); time = now - prof.getLastSendSuccessful(); buf.append(""); time = now - prof.getLastSendFailed(); buf.append(""); buf.append(""); buf.append(""); buf.append(""); DBHistory dbh = prof.getDBHistory(); if (dbh != null) { time = now - dbh.getLastLookupSuccessful(); buf.append(""); time = now - dbh.getLastLookupFailed(); buf.append(""); time = now - dbh.getLastStoreSuccessful(); buf.append(""); time = now - dbh.getLastStoreFailed(); buf.append(""); buf.append(""); buf.append(""); } else { for (int i = 0; i < 6; i++) buf.append("\n"); } buf.append("
").append(_("Peer")).append("").append(_("Caps")).append("").append(_("Integ. Value")).append("").append(_("Last Heard About")).append("").append(_("Last Heard From")).append("").append(_("Last Good Send")).append("").append(_("Last Bad Send")).append("").append(_("10m Resp. Time")).append("").append(_("1h Resp. Time")).append("").append(_("1d Resp. Time")).append("").append(_("Last Good Lookup")).append("").append(_("Last Bad Lookup")).append("").append(_("Last Good Store")).append("").append(_("Last Bad Store")).append("").append(_("1h Fail Rate")).append("").append(_("1d Fail Rate")).append("
"); buf.append(_context.commSystem().renderPeerHTML(peer)); buf.append("").append(DataHelper.stripHTML(info.getCapabilities())).append(" ").append(num(prof.getIntegrationValue())).append("").append(DataHelper.formatDuration2(time)).append("").append(DataHelper.formatDuration2(time)).append("").append(DataHelper.formatDuration2(time)).append("").append(DataHelper.formatDuration2(time)).append("").append(avg(prof, 10*60*1000l)).append("").append(avg(prof, 60*60*1000l)).append("").append(avg(prof, 24*60*60*1000l)).append("").append(DataHelper.formatDuration2(time)).append("").append(DataHelper.formatDuration2(time)).append("").append(DataHelper.formatDuration2(time)).append("").append(DataHelper.formatDuration2(time)).append("").append(davg(dbh, 60*60*1000l)).append("").append(davg(dbh, 24*60*60*1000l)).append("").append(_(NA)); } buf.append("
"); //// //// don't bother reindenting //// } if (mode < 2) { buf.append("

").append(_("Thresholds")).append("

"); buf.append("

").append(_("Speed")).append(": ").append(num(_organizer.getSpeedThreshold())) .append(" (").append(fast).append(' ').append(_("fast peers")).append(")
"); buf.append("").append(_("Capacity")).append(": ").append(num(_organizer.getCapacityThreshold())) .append(" (").append(reliable).append(' ').append(_("high capacity peers")).append(")
"); buf.append("").append(_("Integration")).append(": ").append(num(_organizer.getIntegrationThreshold())) .append(" (").append(integrated).append(' ').append(_(" well integrated peers")).append(")

"); buf.append("

").append(_("Definitions")).append("

    "); buf.append("
  • ").append(_("groups")).append(": ").append(_("as determined by the profile organizer")).append("
  • "); buf.append("
  • ").append(_("caps")).append(": ").append(_("capabilities in the netDb, not used to determine profiles")).append("
  • "); buf.append("
  • ").append(_("speed")).append(": ").append(_("peak throughput (bytes per second) over a 1 minute period that the peer has sustained in a single tunnel")).append("
  • "); buf.append("
  • ").append(_("capacity")).append(": ").append(_("how many tunnels can we ask them to join in an hour?")).append("
  • "); buf.append("
  • ").append(_("integration")).append(": ").append(_("how many new peers have they told us about lately?")).append("
  • "); buf.append("
  • ").append(_("status")).append(": ").append(_("is the peer banned, or unreachable, or failing tunnel tests?")).append("
  • "); buf.append("
"); //// //// don't bother reindenting //// } // mode < 2 out.write(buf.toString()); out.flush(); } private class ProfileComparator implements Comparator { public int compare(PeerProfile left, PeerProfile right) { if (_context.profileOrganizer().isFast(left.getPeer())) { if (_context.profileOrganizer().isFast(right.getPeer())) { return compareHashes(left, right); } else { return -1; // fast comes first } } else if (_context.profileOrganizer().isHighCapacity(left.getPeer())) { if (_context.profileOrganizer().isFast(right.getPeer())) { return 1; } else if (_context.profileOrganizer().isHighCapacity(right.getPeer())) { return compareHashes(left, right); } else { return -1; } } else if (_context.profileOrganizer().isFailing(left.getPeer())) { if (_context.profileOrganizer().isFailing(right.getPeer())) { return compareHashes(left, right); } else { return 1; } } else { // left is not failing if (_context.profileOrganizer().isFast(right.getPeer())) { return 1; } else if (_context.profileOrganizer().isHighCapacity(right.getPeer())) { return 1; } else if (_context.profileOrganizer().isFailing(right.getPeer())) { return -1; } else { return compareHashes(left, right); } } } private int compareHashes(PeerProfile left, PeerProfile right) { return left.getPeer().toBase64().compareTo(right.getPeer().toBase64()); } } private final static DecimalFormat _fmt = new DecimalFormat("###,##0.00"); private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } } private final static String NA = HelperBase._x("n/a"); private String avg (PeerProfile prof, long rate) { RateStat rs = prof.getDbResponseTime(); if (rs == null) return _(NA); Rate r = rs.getRate(rate); if (r == null) return _(NA); long c = r.getCurrentEventCount() + r.getLastEventCount(); if (c == 0) return _(NA); double d = r.getCurrentTotalValue() + r.getLastTotalValue(); return DataHelper.formatDuration2(Math.round(d/c)); } private String davg (DBHistory dbh, long rate) { RateStat rs = dbh.getFailedLookupRate(); if (rs == null) return "0%"; Rate r = rs.getRate(rate); if (r == null) return "0%"; long c = r.getCurrentEventCount() + r.getLastEventCount(); if (c <= 0) return "0%"; double avg = 0.5 + 100 * (r.getCurrentTotalValue() + r.getLastTotalValue()) / c; return ((int) avg) + "%"; } /** translate a string */ private String _(String s) { return Messages.getString(s, _context); } /** translate (ngettext) @since 0.8.5 */ public String ngettext(String s, String p, int n) { return Messages.getString(n, s, p, _context); } }