forked from I2P_Developers/i2p.i2p
* Console: Move configservice.jsp rendering code from the router to the console
This commit is contained in:
@ -3,19 +3,28 @@ package net.i2p.router.web;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.router.Job;
|
||||||
|
import net.i2p.router.JobStats;
|
||||||
|
|
||||||
public class JobQueueHelper extends HelperBase {
|
public class JobQueueHelper extends HelperBase {
|
||||||
public JobQueueHelper() {}
|
|
||||||
|
|
||||||
public String getJobQueueSummary() {
|
public String getJobQueueSummary() {
|
||||||
try {
|
try {
|
||||||
if (_out != null) {
|
if (_out != null) {
|
||||||
_context.jobQueue().renderStatusHTML(_out);
|
renderStatusHTML(_out);
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||||
_context.jobQueue().renderStatusHTML(new OutputStreamWriter(baos));
|
renderStatusHTML(new OutputStreamWriter(baos));
|
||||||
return new String(baos.toByteArray());
|
return new String(baos.toByteArray());
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
@ -23,4 +32,147 @@ public class JobQueueHelper extends HelperBase {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moved from JobQueue
|
||||||
|
* @since 0.8.9
|
||||||
|
*/
|
||||||
|
private void renderStatusHTML(Writer out) throws IOException {
|
||||||
|
List<Job> readyJobs = new ArrayList(8);
|
||||||
|
List<Job> timedJobs = new ArrayList(128);
|
||||||
|
List<Job> activeJobs = new ArrayList(8);
|
||||||
|
List<Job> justFinishedJobs = new ArrayList(8);
|
||||||
|
|
||||||
|
int numRunners = _context.jobQueue().getJobs(readyJobs, timedJobs, activeJobs, justFinishedJobs);
|
||||||
|
|
||||||
|
StringBuilder buf = new StringBuilder(32*1024);
|
||||||
|
buf.append("<b><div class=\"joblog\"><h3>I2P Job Queue</h3><div class=\"wideload\">Job runners: ").append(numRunners);
|
||||||
|
buf.append("</b><br>\n");
|
||||||
|
|
||||||
|
long now = _context.clock().now();
|
||||||
|
|
||||||
|
buf.append("<hr><b>Active jobs: ").append(activeJobs.size()).append("</b><ol>\n");
|
||||||
|
for (int i = 0; i < activeJobs.size(); i++) {
|
||||||
|
Job j = activeJobs.get(i);
|
||||||
|
buf.append("<li>[started ").append(DataHelper.formatDuration(now-j.getTiming().getStartAfter())).append(" ago]: ");
|
||||||
|
buf.append(j.toString()).append("</li>\n");
|
||||||
|
}
|
||||||
|
buf.append("</ol>\n");
|
||||||
|
buf.append("<hr><b>Just finished jobs: ").append(justFinishedJobs.size()).append("</b><ol>\n");
|
||||||
|
for (int i = 0; i < justFinishedJobs.size(); i++) {
|
||||||
|
Job j = justFinishedJobs.get(i);
|
||||||
|
buf.append("<li>[finished ").append(DataHelper.formatDuration(now-j.getTiming().getActualEnd())).append(" ago]: ");
|
||||||
|
buf.append(j.toString()).append("</li>\n");
|
||||||
|
}
|
||||||
|
buf.append("</ol>\n");
|
||||||
|
buf.append("<hr><b>Ready/waiting jobs: ").append(readyJobs.size()).append("</b><ol>\n");
|
||||||
|
for (int i = 0; i < readyJobs.size(); i++) {
|
||||||
|
Job j = readyJobs.get(i);
|
||||||
|
buf.append("<li>[waiting ");
|
||||||
|
buf.append(DataHelper.formatDuration(now-j.getTiming().getStartAfter()));
|
||||||
|
buf.append("]: ");
|
||||||
|
buf.append(j.toString()).append("</li>\n");
|
||||||
|
}
|
||||||
|
buf.append("</ol>\n");
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
buf.append("<hr><b>Scheduled jobs: ").append(timedJobs.size()).append("</b><ol>\n");
|
||||||
|
TreeMap<Long, Job> ordered = new TreeMap();
|
||||||
|
for (int i = 0; i < timedJobs.size(); i++) {
|
||||||
|
Job j = timedJobs.get(i);
|
||||||
|
ordered.put(Long.valueOf(j.getTiming().getStartAfter()), j);
|
||||||
|
}
|
||||||
|
for (Job j : ordered.values()) {
|
||||||
|
long time = j.getTiming().getStartAfter() - now;
|
||||||
|
buf.append("<li>").append(j.getName()).append(" in ");
|
||||||
|
buf.append(DataHelper.formatDuration(time)).append("</li>\n");
|
||||||
|
}
|
||||||
|
buf.append("</ol></div>\n");
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
getJobStats(buf);
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
out.write(buf.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>Job</th><th>Runs</th>" +
|
||||||
|
"<th>Time</th><th><i>Avg</i></th><th><i>Max</i></th><th><i>Min</i></th>" +
|
||||||
|
"<th>Pending</th><th><i>Avg</i></th><th><i>Max</i></th><th><i>Min</i></th></tr>\n");
|
||||||
|
long totRuns = 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(_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.getTotalTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getAvgTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getMaxTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getMinTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getTotalPendingTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getAvgPendingTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getMaxPendingTime()).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(stats.getMinPendingTime()).append("</td>");
|
||||||
|
buf.append("</tr>\n");
|
||||||
|
totRuns += stats.getRuns();
|
||||||
|
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("SUMMARY").append("</b></td>");
|
||||||
|
buf.append("<td align=\"right\">").append(totRuns).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(totExecTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(avgExecTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(maxExecTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(minExecTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(totPendingTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(avgPendingTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(maxPendingTime).append("</td>");
|
||||||
|
buf.append("<td align=\"right\">").append(minPendingTime).append("</td>");
|
||||||
|
buf.append("</tr></table></div>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.8.9 */
|
||||||
|
private static class JobStatsComparator implements Comparator<JobStats> {
|
||||||
|
public int compare(JobStats l, JobStats r) {
|
||||||
|
return l.getName().compareTo(r.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
2011-09-06 zzz
|
2011-09-06 zzz
|
||||||
|
* Console: Move configservice.jsp rendering code from
|
||||||
|
the router to the console
|
||||||
* Crypto: Rework use of SHA256 for efficiency and
|
* Crypto: Rework use of SHA256 for efficiency and
|
||||||
to avoid clogging the Hash cache with one-time hashes,
|
to avoid clogging the Hash cache with one-time hashes,
|
||||||
and avoiding the global cache lock.
|
and avoiding the global cache lock.
|
||||||
|
@ -609,184 +609,47 @@ public class JobQueue {
|
|||||||
public void dropped() {}
|
public void dropped() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
////
|
/**
|
||||||
// the remainder are utility methods for dumping status info
|
* Dump the current state.
|
||||||
////
|
* For the router console jobs status page.
|
||||||
|
*
|
||||||
public void renderStatusHTML(Writer out) throws IOException {
|
* @param readyJobs out parameter
|
||||||
List<Job> readyJobs = null;
|
* @param timedJobs out parameter
|
||||||
List<Job> timedJobs = null;
|
* @param activeJobs out parameter
|
||||||
List<Job> activeJobs = new ArrayList(RUNNERS);
|
* @param justFinishedJobs out parameter
|
||||||
List<Job> justFinishedJobs = new ArrayList(RUNNERS);
|
* @return number of job runners
|
||||||
//out.write("<!-- jobQueue rendering -->\n");
|
* @since 0.8.9
|
||||||
out.flush();
|
*/
|
||||||
|
public int getJobs(Collection<Job> readyJobs, Collection<Job> timedJobs,
|
||||||
//int states[] = null;
|
Collection<Job> activeJobs, Collection<Job> justFinishedJobs) {
|
||||||
int numRunners = 0;
|
for (JobQueueRunner runner :_queueRunners.values()) {
|
||||||
|
Job job = runner.getCurrentJob();
|
||||||
{
|
if (job != null) {
|
||||||
//states = new int[_queueRunners.size()];
|
activeJobs.add(job);
|
||||||
int i = 0;
|
} else {
|
||||||
for (Iterator<JobQueueRunner> iter = _queueRunners.values().iterator(); iter.hasNext(); i++) {
|
job = runner.getLastJob();
|
||||||
JobQueueRunner runner = iter.next();
|
if (job != null)
|
||||||
//states[i] = runner.getState();
|
justFinishedJobs.add(job);
|
||||||
Job job = runner.getCurrentJob();
|
|
||||||
if (job != null) {
|
|
||||||
activeJobs.add(job);
|
|
||||||
} else {
|
|
||||||
job = runner.getLastJob();
|
|
||||||
if (job != null)
|
|
||||||
justFinishedJobs.add(job);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
numRunners = _queueRunners.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******
|
|
||||||
StringBuilder str = new StringBuilder(128);
|
|
||||||
str.append("<!-- after queueRunner sync: states: ");
|
|
||||||
for (int i = 0; states != null && i < states.length; i++)
|
|
||||||
str.append(states[i]).append(" ");
|
|
||||||
str.append(" -->\n");
|
|
||||||
str.append("<!-- jobs: ");
|
|
||||||
for (int i = 0; i < activeJobs.size(); i++)
|
|
||||||
str.append(activeJobs.get(i).toString()).append(" ");
|
|
||||||
str.append("-->\n");
|
|
||||||
out.write(str.toString());
|
|
||||||
out.flush();
|
|
||||||
*******/
|
|
||||||
|
|
||||||
synchronized (_jobLock) {
|
synchronized (_jobLock) {
|
||||||
readyJobs = new ArrayList(_readyJobs);
|
readyJobs.addAll(_readyJobs);
|
||||||
timedJobs = new ArrayList(_timedJobs);
|
timedJobs.addAll(_timedJobs);
|
||||||
}
|
}
|
||||||
//out.write("<!-- jobQueue rendering: after jobLock sync -->\n");
|
return _queueRunners.size();
|
||||||
//out.flush();
|
|
||||||
|
|
||||||
StringBuilder buf = new StringBuilder(32*1024);
|
|
||||||
buf.append("<b><div class=\"joblog\"><h3>I2P Job Queue</h3><div class=\"wideload\">Job runners: ").append(numRunners);
|
|
||||||
//buf.append(" [states=");
|
|
||||||
//if (states != null)
|
|
||||||
// for (int i = 0; i < states.length; i++)
|
|
||||||
// buf.append(states[i]).append(" ");
|
|
||||||
//buf.append(']');
|
|
||||||
buf.append("</b><br>\n");
|
|
||||||
|
|
||||||
long now = _context.clock().now();
|
|
||||||
|
|
||||||
buf.append("<hr><b>Active jobs: ").append(activeJobs.size()).append("</b><ol>\n");
|
|
||||||
for (int i = 0; i < activeJobs.size(); i++) {
|
|
||||||
Job j = activeJobs.get(i);
|
|
||||||
buf.append("<li>[started ").append(DataHelper.formatDuration(now-j.getTiming().getStartAfter())).append(" ago]: ");
|
|
||||||
buf.append(j.toString()).append("</li>\n");
|
|
||||||
}
|
|
||||||
buf.append("</ol>\n");
|
|
||||||
buf.append("<hr><b>Just finished jobs: ").append(justFinishedJobs.size()).append("</b><ol>\n");
|
|
||||||
for (int i = 0; i < justFinishedJobs.size(); i++) {
|
|
||||||
Job j = justFinishedJobs.get(i);
|
|
||||||
buf.append("<li>[finished ").append(DataHelper.formatDuration(now-j.getTiming().getActualEnd())).append(" ago]: ");
|
|
||||||
buf.append(j.toString()).append("</li>\n");
|
|
||||||
}
|
|
||||||
buf.append("</ol>\n");
|
|
||||||
buf.append("<hr><b>Ready/waiting jobs: ").append(readyJobs.size()).append("</b><ol>\n");
|
|
||||||
for (int i = 0; i < readyJobs.size(); i++) {
|
|
||||||
Job j = readyJobs.get(i);
|
|
||||||
buf.append("<li>[waiting ");
|
|
||||||
buf.append(DataHelper.formatDuration(now-j.getTiming().getStartAfter()));
|
|
||||||
buf.append("]: ");
|
|
||||||
buf.append(j.toString()).append("</li>\n");
|
|
||||||
}
|
|
||||||
buf.append("</ol>\n");
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
buf.append("<hr><b>Scheduled jobs: ").append(timedJobs.size()).append("</b><ol>\n");
|
|
||||||
TreeMap<Long, Job> ordered = new TreeMap();
|
|
||||||
for (int i = 0; i < timedJobs.size(); i++) {
|
|
||||||
Job j = timedJobs.get(i);
|
|
||||||
ordered.put(Long.valueOf(j.getTiming().getStartAfter()), j);
|
|
||||||
}
|
|
||||||
for (Iterator<Job> iter = ordered.values().iterator(); iter.hasNext(); ) {
|
|
||||||
Job j = iter.next();
|
|
||||||
long time = j.getTiming().getStartAfter() - now;
|
|
||||||
buf.append("<li>").append(j.getName()).append(" in ");
|
|
||||||
buf.append(DataHelper.formatDuration(time)).append("</li>\n");
|
|
||||||
}
|
|
||||||
buf.append("</ol></div>\n");
|
|
||||||
|
|
||||||
//out.write("<!-- jobQueue rendering: after main buffer, before stats -->\n");
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
getJobStats(buf);
|
|
||||||
|
|
||||||
//out.write("<!-- jobQueue rendering: after stats -->\n");
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
out.write(buf.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** render the HTML for the job stats */
|
|
||||||
private void getJobStats(StringBuilder buf) {
|
|
||||||
buf.append("<table>\n" +
|
|
||||||
"<tr><th>Job</th><th>Runs</th>" +
|
|
||||||
"<th>Time</th><th><i>Avg</i></th><th><i>Max</i></th><th><i>Min</i></th>" +
|
|
||||||
"<th>Pending</th><th><i>Avg</i></th><th><i>Max</i></th><th><i>Min</i></th></tr>\n");
|
|
||||||
long totRuns = 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;
|
|
||||||
|
|
||||||
TreeMap<String, JobStats> tstats = new TreeMap(_jobStats);
|
/**
|
||||||
|
* Current job stats.
|
||||||
for (Iterator<JobStats> iter = tstats.values().iterator(); iter.hasNext(); ) {
|
* For the router console jobs status page.
|
||||||
JobStats stats = iter.next();
|
*
|
||||||
buf.append("<tr>");
|
* @since 0.8.9
|
||||||
buf.append("<td><b>").append(stats.getName()).append("</b></td>");
|
*/
|
||||||
buf.append("<td align=\"right\">").append(stats.getRuns()).append("</td>");
|
public Collection<JobStats> getJobStats() {
|
||||||
buf.append("<td align=\"right\">").append(stats.getTotalTime()).append("</td>");
|
return Collections.unmodifiableCollection(_jobStats.values());
|
||||||
buf.append("<td align=\"right\">").append(stats.getAvgTime()).append("</td>");
|
}
|
||||||
buf.append("<td align=\"right\">").append(stats.getMaxTime()).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(stats.getMinTime()).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(stats.getTotalPendingTime()).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(stats.getAvgPendingTime()).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(stats.getMaxPendingTime()).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(stats.getMinPendingTime()).append("</td>");
|
|
||||||
buf.append("</tr>\n");
|
|
||||||
totRuns += stats.getRuns();
|
|
||||||
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) {
|
/** @deprecated moved to router console */
|
||||||
if (totExecTime != 0)
|
public void renderStatusHTML(Writer out) throws IOException {
|
||||||
avgExecTime = totExecTime / totRuns;
|
|
||||||
if (totPendingTime != 0)
|
|
||||||
avgPendingTime = totPendingTime / totRuns;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.append("<tr class=\"tablefooter\">");
|
|
||||||
buf.append("<td><b>").append("SUMMARY").append("</b></td>");
|
|
||||||
buf.append("<td align=\"right\">").append(totRuns).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(totExecTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(avgExecTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(maxExecTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(minExecTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(totPendingTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(avgPendingTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(maxPendingTime).append("</td>");
|
|
||||||
buf.append("<td align=\"right\">").append(minPendingTime).append("</td>");
|
|
||||||
buf.append("</tr></table></div>\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,11 @@ package net.i2p.router;
|
|||||||
|
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
|
|
||||||
/** glorified struct to contain basic job stats */
|
/**
|
||||||
class JobStats {
|
* Glorified struct to contain basic job stats.
|
||||||
|
* Public for router console only.
|
||||||
|
*/
|
||||||
|
public class JobStats {
|
||||||
private final String _job;
|
private final String _job;
|
||||||
private volatile long _numRuns;
|
private volatile long _numRuns;
|
||||||
private volatile long _totalTime;
|
private volatile long _totalTime;
|
||||||
|
Reference in New Issue
Block a user