package net.i2p.router.web; import java.io.IOException; import java.io.Serializable; import java.io.Writer; import java.text.DecimalFormat; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.router.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.RateAverages; 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; public ProfileOrganizerRenderer(ProfileOrganizer organizer, RouterContext context) { _context = context; _organizer = organizer; } /** * @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; Set order = new TreeSet(mode == 2 ? new HashComparator() : new ProfileComparator()); int older = 0; int standard = 0; for (Hash peer : peers) { if (_organizer.getUs().equals(peer)) continue; PeerProfile prof = _organizer.getProfile(peer); if (mode == 2) { RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null && info.getCapabilities().indexOf("f") >= 0) order.add(prof); continue; } 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 (PeerProfile prof : order) { 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.banlist().isBanlisted(peer)) buf.append(_("Banned")); if (prof.getIsFailing()) buf.append(' ').append(_("Failing")); if (_context.commSystem().wasUnreachable(peer)) buf.append(' ').append(_("Unreachable")); RateAverages ra = RateAverages.getTemp(); Rate failed = prof.getTunnelHistory().getFailedRate().getRate(30*60*1000); long fails = failed.computeAverages(ra, false).getTotalEventCount(); if (fails > 0) { Rate accepted = prof.getTunnelCreateResponseTime().getRate(30*60*1000); long total = fails + accepted.computeAverages(ra, false).getTotalEventCount(); 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(""); RateAverages ra = RateAverages.getTemp(); for (PeerProfile prof : order) { 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, ra)).append("").append(avg(prof, 60*60*1000l, ra)).append("").append(avg(prof, 24*60*60*1000l, ra)).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, ra)).append("").append(davg(dbh, 24*60*60*1000l, ra)).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 extends HashComparator { public int compare(PeerProfile left, PeerProfile right) { if (_context.profileOrganizer().isFast(left.getPeer())) { if (_context.profileOrganizer().isFast(right.getPeer())) { return super.compare(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 super.compare(left, right); } else { return -1; } } else if (_context.profileOrganizer().isFailing(left.getPeer())) { if (_context.profileOrganizer().isFailing(right.getPeer())) { return super.compare(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 super.compare(left, right); } } } } /** * Used for floodfill-only page * @since 0.9.8 */ private static class HashComparator implements Comparator, Serializable { public int compare(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, RateAverages ra) { RateStat rs = prof.getDbResponseTime(); if (rs == null) return _(NA); Rate r = rs.getRate(rate); if (r == null) return _(NA); r.computeAverages(ra, false); if (ra.getTotalEventCount() == 0) return _(NA); return DataHelper.formatDuration2(Math.round(ra.getAverage())); } private String davg (DBHistory dbh, long rate, RateAverages ra) { RateStat rs = dbh.getFailedLookupRate(); if (rs == null) return "0%"; Rate r = rs.getRate(rate); if (r == null) return "0%"; r.computeAverages(ra, false); if (ra.getTotalEventCount() <= 0) return "0%"; double avg = 0.5 + 100 * ra.getAverage(); 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); } }