package net.i2p.router.web; import java.io.IOException; import java.io.Writer; import java.text.DecimalFormat; import java.text.Collator; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.stat.Frequency; import net.i2p.stat.FrequencyStat; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; /** * Dump the stats to the web admin interface */ public class StatsGenerator { private RouterContext _context; public StatsGenerator(RouterContext context) { _context = context; } public void generateStatsPage(Writer out, boolean showAll) throws IOException { StringBuilder buf = new StringBuilder(16*1024); buf.append("<div class=\"joblog\"><form action=\"\">"); buf.append("<select name=\"go\" onChange='location.href=this.value'>"); out.write(buf.toString()); buf.setLength(0); Map<String, SortedSet<String>> unsorted = _context.statManager().getStatsByGroup(); Map<String, Set<String>> groups = new TreeMap<String, Set<String>>(new AlphaComparator()); groups.putAll(unsorted); for (String group : groups.keySet()) { buf.append("<option value=\"#").append(group).append("\">"); buf.append(_t(group)).append("</option>\n"); // let's just do the groups //Set stats = (Set)entry.getValue(); //for (Iterator statIter = stats.iterator(); statIter.hasNext(); ) { // String stat = (String)statIter.next(); // buf.append("<option value=\"/stats.jsp#"); // buf.append(stat); // buf.append("\">..."); // buf.append(stat); // buf.append("</option>\n"); //} //out.write(buf.toString()); //buf.setLength(0); } buf.append("</select> <input type=\"submit\" value=\"").append(_t("GO")).append("\" />"); buf.append("</form>"); buf.append(_t("Statistics gathered during this router's uptime")).append(" ("); long uptime = _context.router().getUptime(); buf.append(DataHelper.formatDuration2(uptime)); buf.append("). ").append( _t("The data gathered is quantized over a 1 minute period, so should just be used as an estimate.")); buf.append(' ').append( _t("These statistics are primarily used for development and debugging.")); out.write(buf.toString()); buf.setLength(0); for (Map.Entry<String, Set<String>> entry : groups.entrySet()) { String group = entry.getKey(); Set<String> stats = entry.getValue(); buf.append("<h3><a name=\""); buf.append(group); buf.append("\">"); buf.append(_t(group)); buf.append("</a></h3>"); buf.append("<ul>"); out.write(buf.toString()); buf.setLength(0); for (String stat : stats) { buf.append("<li><b><a name=\""); buf.append(stat); buf.append("\">"); buf.append(stat); buf.append("</a></b><br>"); if (_context.statManager().isFrequency(stat)) renderFrequency(stat, buf); else renderRate(stat, buf, showAll); out.write(buf.toString()); buf.setLength(0); } out.write("</ul><br>\n"); } out.write("</div>"); out.flush(); } private void renderFrequency(String name, StringBuilder buf) { FrequencyStat freq = _context.statManager().getFrequency(name); buf.append("<i>"); buf.append(freq.getDescription()); buf.append("</i><br>"); if (freq.getEventCount() <= 0) { buf.append(_t("No lifetime events")).append("<br>\n"); return; } long uptime = _context.router().getUptime(); long periods[] = freq.getPeriods(); Arrays.sort(periods); buf.append("<ul>"); for (int i = 0; i < periods.length; i++) { if (periods[i] > uptime) break; buf.append("<li>"); renderPeriod(buf, periods[i], _t("frequency")); Frequency curFreq = freq.getFrequency(periods[i]); buf.append(DataHelper.formatDuration2(Math.round(curFreq.getAverageInterval()))); buf.append("; "); buf.append(_t("Rolling average events per period")); buf.append(": "); buf.append(num(curFreq.getAverageEventsPerPeriod())); buf.append("; "); buf.append(_t("Highest events per period")); buf.append(": "); buf.append(num(curFreq.getMaxAverageEventsPerPeriod())); buf.append("; "); //if (showAll && (curFreq.getMaxAverageEventsPerPeriod() > 0) && (curFreq.getAverageEventsPerPeriod() > 0) ) { // buf.append("(current is "); // buf.append(pct(curFreq.getAverageEventsPerPeriod()/curFreq.getMaxAverageEventsPerPeriod())); // buf.append(" of max)"); //} //buf.append(" <i>avg interval between updates:</i> (").append(num(curFreq.getAverageInterval())).append("ms, min "); //buf.append(num(curFreq.getMinAverageInterval())).append("ms)"); buf.append(_t("Lifetime average events per period")).append(": "); buf.append(num(curFreq.getStrictAverageEventsPerPeriod())); buf.append("</li>\n"); } // Display the strict average buf.append("<li><b>").append(_t("Lifetime average frequency")).append(":</b> "); buf.append(DataHelper.formatDuration2(freq.getFrequency())); buf.append(" ("); buf.append(ngettext("1 event", "{0} events", (int) freq.getEventCount())); buf.append(")</li></ul><br>\n"); } private void renderRate(String name, StringBuilder buf, boolean showAll) { RateStat rate = _context.statManager().getRate(name); String d = rate.getDescription(); if (! "".equals(d)) { buf.append("<i>"); buf.append(d); buf.append("</i><br>"); } if (rate.getLifetimeEventCount() <= 0) { buf.append(_t("No lifetime events")).append("<br>\n"); return; } long now = _context.clock().now(); long periods[] = rate.getPeriods(); Arrays.sort(periods); buf.append("<ul>"); for (int i = 0; i < periods.length; i++) { Rate curRate = rate.getRate(periods[i]); if (curRate.getLastCoalesceDate() <= curRate.getCreationDate()) break; buf.append("<li>"); renderPeriod(buf, periods[i], _t("rate")); if (curRate.getLastEventCount() > 0) { buf.append(_t("Average")).append(": "); buf.append(num(curRate.getAverageValue())); buf.append("; "); buf.append(_t("Highest average")); buf.append(": "); buf.append(num(curRate.getExtremeAverageValue())); buf.append("; "); // This is rarely interesting // Don't bother to translate if (showAll) { buf.append("Highest total in a period: "); buf.append(num(curRate.getExtremeTotalValue())); buf.append("; "); } // Saturation stats, which nobody understands, even when it isn't meaningless // Don't bother to translate if (showAll && curRate.getLifetimeTotalEventTime() > 0) { buf.append("Saturation: "); buf.append(pct(curRate.getLastEventSaturation())); buf.append("; Saturated limit: "); buf.append(num(curRate.getLastSaturationLimit())); buf.append("; Peak saturation: "); buf.append(pct(curRate.getExtremeEventSaturation())); buf.append("; Peak saturated limit: "); buf.append(num(curRate.getExtremeSaturationLimit())); buf.append("; "); } buf.append(ngettext("There was 1 event in this period.", "There were {0} events in this period.", (int)curRate.getLastEventCount())); buf.append(' '); buf.append(_t("The period ended {0} ago.", DataHelper.formatDuration2(now - curRate.getLastCoalesceDate()))); } else { buf.append(" <i>").append(_t("No events")).append("</i> "); } long numPeriods = curRate.getLifetimePeriods(); if (numPeriods > 0) { double avgFrequency = curRate.getLifetimeEventCount() / (double)numPeriods; buf.append(" (").append(_t("Average event count")).append(": "); buf.append(num(avgFrequency)); buf.append("; ").append(_t("Events in peak period")).append(": "); // This isn't really the highest event count, but the event count during the period with the highest total value. buf.append(curRate.getExtremeEventCount()); buf.append(")"); } if (curRate.getSummaryListener() != null) { buf.append(" <a href=\"graph?stat=").append(name) .append('.').append(periods[i]); buf.append("\">").append(_t("Graph Data")).append("</a> - "); buf.append(" <a href=\"graph?stat=").append(name) .append('.').append(periods[i]); buf.append("&showEvents=true\">").append(_t("Graph Event Count")).append("</a>"); // This can really blow up your browser if you click on it //buf.append(" - <a href=\"viewstat.jsp?stat=").append(name); //buf.append("&period=").append(periods[i]); //buf.append("&format=xml\">").append(_t("Export Data as XML")).append("</a>"); } buf.append("</li>\n"); } // Display the strict average buf.append("<li><b>").append(_t("Lifetime average value")).append(":</b> "); buf.append(num(rate.getLifetimeAverageValue())); buf.append(" ("); buf.append(ngettext("1 event", "{0} events", (int) rate.getLifetimeEventCount())); buf.append(")<br></li>" + "</ul>" + "<br>\n"); } private static void renderPeriod(StringBuilder buf, long period, String name) { buf.append("<b>"); buf.append(DataHelper.formatDuration2(period)); buf.append(" "); buf.append(name); buf.append(":</b> "); } private final static DecimalFormat _fmt = new DecimalFormat("###,##0.0##"); private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } } private final static DecimalFormat _pct = new DecimalFormat("#0.00%"); private final static String pct(double num) { synchronized (_pct) { return _pct.format(num); } } /** * Translated sort * Inner class, can't be Serializable * @since 0.9.3 */ private class AlphaComparator implements Comparator<String> { public int compare(String lhs, String rhs) { String lname = _t(lhs); String rname = _t(rhs); return Collator.getInstance().compare(lname, rname); } } /** translate a string */ private String _t(String s) { return Messages.getString(s, _context); } /** translate a string */ private String _t(String s, Object o) { return Messages.getString(s, o, _context); } /** translate a string */ private String ngettext(String s, String p, int n) { return Messages.getString(n, s, p, _context); } }