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 RouterContext _context; private ProfileOrganizer _organizer; private ProfileComparator _comparator; public ProfileOrganizerRenderer(ProfileOrganizer organizer, RouterContext context) { _context = context; _organizer = organizer; _comparator = new ProfileComparator(); } public void renderStatusHTML(Writer out) throws IOException { 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); for (Iterator iter = peers.iterator(); iter.hasNext();) { Hash peer = (Hash)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) continue; order.add(prof); } int fast = 0; int reliable = 0; int integrated = 0; int failing = 0; StringBuilder buf = new StringBuilder(16*1024); buf.append("

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

\n

"); buf.append(_("Showing {0} recent profiles.", order.size())).append('\n'); buf.append(_("Hiding {0} older profiles.", peers.size()-order.size())); 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 = (PeerProfile)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)) { failing++; } 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)); 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(" profile"); buf.append(" +-
"); buf.append("

").append(_("Floodfill and Integrated Peers")).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 = (PeerProfile)iter.next(); Hash peer = prof.getPeer(); buf.append(""); RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null) buf.append(""); else buf.append(""); 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) { buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append(""); } } buf.append("
PeerCapsInteg. ValueLast Heard AboutLast Heard FromLast Successful SendLast Good SendLast Failed SendLast Bad Send10m Resp. Time1h Resp. Time1d Resp. TimeSuccessful LookupsGood LookupsFailed LookupsBad LookupsNew StoresOld Stores1h Fail Rate1d Fail Rate
"); buf.append(_context.commSystem().renderPeerHTML(peer)); buf.append("").append(DataHelper.stripHTML(info.getCapabilities())).append(" ").append(num(prof.getIntegrationValue())).append("").append(DataHelper.formatDuration(time)).append("").append(DataHelper.formatDuration(time)).append("").append(DataHelper.formatDuration(time)).append("").append(DataHelper.formatDuration(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(dbh.getSuccessfulLookups()).append("").append(dbh.getFailedLookups()).append("").append(dbh.getUnpromptedDbStoreNew()).append("").append(dbh.getUnpromptedDbStoreOld()).append("").append(davg(dbh, 60*60*1000l)).append("").append(davg(dbh, 24*60*60*1000l)).append("
"); buf.append("

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

"); buf.append("

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

"); buf.append("

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

"); out.write(buf.toString()); out.flush(); } private class ProfileComparator implements Comparator { public int compare(Object lhs, Object rhs) { if ( (lhs == null) || (rhs == null) ) throw new NullPointerException("lhs=" + lhs + " rhs=" + rhs); if ( !(lhs instanceof PeerProfile) || !(rhs instanceof PeerProfile) ) throw new ClassCastException("lhs=" + lhs.getClass().getName() + " rhs=" + rhs.getClass().getName()); PeerProfile left = (PeerProfile)lhs; PeerProfile right = (PeerProfile)rhs; 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 = "n/a"; private static 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 Math.round(d/c) + "ms"; } private static String davg (DBHistory dbh, long rate) { RateStat rs = dbh.getFailedLookupRate(); if (rs == null) return na; Rate r = rs.getRate(rate); if (r == null) return na; long c = r.getCurrentEventCount() + r.getLastEventCount(); return "" + c; } /** translate a string */ private String _(String s) { return Messages.getString(s, _context); } /** * translate a string with a parameter * This is a lot more expensive than _(s), so use sparingly. * * @param s string to be translated containing {0} * The {0} will be replaced by the parameter. * Single quotes must be doubled, i.e. ' -> '' in the string. * @param o parameter, not translated. * To tranlslate parameter also, use _("foo {0} bar", _("baz")) * Do not double the single quotes in the parameter. * Use autoboxing to call with ints, longs, floats, etc. */ private String _(String s, Object o) { return Messages.getString(s, o, _context); } }