package net.i2p.router.web;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.i2p.data.DataHelper;
import net.i2p.router.Job;
import net.i2p.router.JobStats;
import net.i2p.util.ObjectCounter;
public class JobQueueHelper extends HelperBase {
private static final int MAX_JOBS = 50;
public String getJobQueueSummary() {
try {
if (_out != null) {
renderStatusHTML(_out);
return "";
} else {
StringWriter sw = new StringWriter(32*1024);
renderStatusHTML(sw);
return sw.toString();
}
} catch (IOException ioe) {
ioe.printStackTrace();
return "";
}
}
/**
* Moved from JobQueue
* @since 0.8.9
*/
private void renderStatusHTML(Writer out) throws IOException {
List<Job> readyJobs = new ArrayList<Job>(8);
List<Job> timedJobs = new ArrayList<Job>(128);
List<Job> activeJobs = new ArrayList<Job>(8);
List<Job> justFinishedJobs = new ArrayList<Job>(8);
int numRunners = _context.jobQueue().getJobs(readyJobs, timedJobs, activeJobs, justFinishedJobs);
StringBuilder buf = new StringBuilder(32*1024);
buf.append("<b><div class=\"joblog\"><h3>").append(_t("I2P Job Queue")).append("</h3><br><div class=\"wideload\">")
.append(_t("Job runners")).append(": ").append(numRunners)
.append("</b><br>\n");
long now = _context.clock().now();
buf.append("<hr><b>").append(_t("Active jobs")).append(": ").append(activeJobs.size()).append("</b><ol>\n");
for (int i = 0; i < activeJobs.size(); i++) {
Job j = activeJobs.get(i);
buf.append("<li>(").append(_t("started {0} ago", DataHelper.formatDuration2(now-j.getTiming().getStartAfter()))).append("): ");
buf.append(j.toString()).append("</li>\n");
}
buf.append("</ol>\n");
buf.append("<hr><b>").append(_t("Just finished jobs")).append(": ").append(justFinishedJobs.size()).append("</b><ol>\n");
for (int i = 0; i < justFinishedJobs.size(); i++) {
Job j = justFinishedJobs.get(i);
buf.append("<li>(").append(_t("finished {0} ago", DataHelper.formatDuration2(now-j.getTiming().getActualEnd()))).append("): ");
buf.append(j.toString()).append("</li>\n");
}
buf.append("</ol>\n");
buf.append("<hr><b>").append(_t("Ready/waiting jobs")).append(": ").append(readyJobs.size()).append("</b><ol>\n");
ObjectCounter<String> counter = new ObjectCounter<String>();
for (int i = 0; i < readyJobs.size(); i++) {
Job j = readyJobs.get(i);
counter.increment(j.getName());
if (i >= MAX_JOBS)
continue;
buf.append("<li>[waiting ");
buf.append(DataHelper.formatDuration2(now-j.getTiming().getStartAfter()));
buf.append("]: ");
buf.append(j.toString()).append("</li>\n");
}
buf.append("</ol>\n");
getJobCounts(buf, counter);
out.write(buf.toString());
buf.setLength(0);
buf.append("<hr><b>").append(_t("Scheduled jobs")).append(": ").append(timedJobs.size()).append("</b><ol>\n");
long prev = Long.MIN_VALUE;
counter.clear();
for (int i = 0; i < timedJobs.size(); i++) {
Job j = timedJobs.get(i);
counter.increment(j.getName());
if (i >= MAX_JOBS)
continue;
long time = j.getTiming().getStartAfter() - now;
// translators: {0} is a job name, {1} is a time, e.g. 6 min
buf.append("<li>").append(_t("{0} will start in {1}", j.getName(), DataHelper.formatDuration2(time)));
// debug, don't bother translating
if (time < 0)
buf.append(" <b>DELAYED</b>");
if (time < prev)
buf.append(" <b>** OUT OF ORDER **</b>");
prev = time;
buf.append("</li>\n");
}
buf.append("</ol></div>\n");
getJobCounts(buf, counter);
out.write(buf.toString());
buf.setLength(0);
buf.append("<hr><b>").append(_t("Total Job Statistics")).append("</b>\n");
getJobStats(buf);
out.write(buf.toString());
}
/** @since 0.9.5 */
private void getJobCounts(StringBuilder buf, ObjectCounter<String> counter) {
List<String> names = new ArrayList<String>(counter.objects());
if (names.size() < 4)
return;
buf.append("<table style=\"width: 30%; margin-left: 100px;\">\n" +
"<tr><th>").append(_t("Job")).append("</th><th>").append(_t("Queued")).append("<th>");
Collections.sort(names, new JobCountComparator(counter));
for (String name : names) {
buf.append("<tr><td>").append(name)
.append("</td><td align=\"center\">").append(counter.count(name))
.append("</td></tr>\n");
}
buf.append("</table>\n");
}
/**
* Render the HTML for the job stats.
* Moved from JobQueue
* @since 0.8.9
*/
private void getJobStats(StringBuilder buf) {
buf.append("<table>\n" +
"<tr><th>").append(_t("Job")).append("</th><th>").append(_t("Runs")).append("</th>" +
"<th>").append(_t("Dropped")).append("</th>" +
"<th>").append(_t("Time")).append("</th><th><i>").append(_t("Avg")).append("</i></th><th><i>")
.append(_t("Max")).append("</i></th><th><i>").append(_t("Min")).append("</i></th>" +
"<th>").append(_t("Pending")).append("</th><th><i>").append(_t("Avg")).append("</i></th><th><i>")
.append(_t("Max")).append("</i></th><th><i>").append(_t("Min")).append("</i></th></tr>\n");
long totRuns = 0;
long totDropped = 0;
long totExecTime = 0;
long avgExecTime = 0;
long maxExecTime = -1;
long minExecTime = -1;
long totPendingTime = 0;
long avgPendingTime = 0;
long maxPendingTime = -1;
long minPendingTime = -1;
List<JobStats> tstats = new ArrayList<JobStats>(_context.jobQueue().getJobStats());
Collections.sort(tstats, new JobStatsComparator());
for (JobStats stats : tstats) {
buf.append("<tr>");
buf.append("<td><b>").append(stats.getName()).append("</b></td>");
buf.append("<td align=\"right\">").append(stats.getRuns()).append("</td>");
buf.append("<td align=\"right\">").append(stats.getDropped()).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getTotalTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getAvgTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getMaxTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getMinTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getTotalPendingTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getAvgPendingTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getMaxPendingTime())).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(stats.getMinPendingTime())).append("</td>");
buf.append("</tr>\n");
totRuns += stats.getRuns();
totDropped += stats.getDropped();
totExecTime += stats.getTotalTime();
if (stats.getMaxTime() > maxExecTime)
maxExecTime = stats.getMaxTime();
if ( (minExecTime < 0) || (minExecTime > stats.getMinTime()) )
minExecTime = stats.getMinTime();
totPendingTime += stats.getTotalPendingTime();
if (stats.getMaxPendingTime() > maxPendingTime)
maxPendingTime = stats.getMaxPendingTime();
if ( (minPendingTime < 0) || (minPendingTime > stats.getMinPendingTime()) )
minPendingTime = stats.getMinPendingTime();
}
if (totRuns != 0) {
if (totExecTime != 0)
avgExecTime = totExecTime / totRuns;
if (totPendingTime != 0)
avgPendingTime = totPendingTime / totRuns;
}
buf.append("<tr class=\"tablefooter\">");
buf.append("<td><b>").append(_t("Summary")).append("</b></td>");
buf.append("<td align=\"right\">").append(totRuns).append("</td>");
buf.append("<td align=\"right\">").append(totDropped).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(totExecTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(avgExecTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(maxExecTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(minExecTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(totPendingTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(avgPendingTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(maxPendingTime)).append("</td>");
buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(minPendingTime)).append("</td>");
buf.append("</tr></table></div>\n");
}
/** @since 0.8.9 */
private static class JobStatsComparator implements Comparator<JobStats>, Serializable {
public int compare(JobStats l, JobStats r) {
return l.getName().compareTo(r.getName());
}
}
/** @since 0.9.5 */
private static class JobCountComparator implements Comparator<String>, Serializable {
private final ObjectCounter<String> _counter;
public JobCountComparator(ObjectCounter<String> counter) {
_counter = counter;
}
public int compare(String l, String r) {
// reverse
int lc = _counter.count(l);
int rc = _counter.count(r);
if (lc > rc)
return -1;
if (lc < rc)
return 1;
return l.compareTo(r);
}
}
}