package net.johnewart.gearman.server.web;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import net.johnewart.gearman.engine.core.JobManager;
import net.johnewart.gearman.engine.core.QueuedJob;
import net.johnewart.gearman.engine.metrics.QueueMetrics;
import net.johnewart.gearman.engine.queue.JobQueue;
import net.johnewart.gearman.server.util.JobQueueMetrics;
import net.johnewart.gearman.server.util.JobQueueMonitor;
import net.johnewart.gearman.server.util.SystemSnapshot;
import net.johnewart.shuzai.Frequency;
import net.johnewart.shuzai.SampleMethod;
import net.johnewart.shuzai.TimeSeries;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class GearmanServlet extends HttpServlet {
private static final String CONTENT_TYPE = "application/json";
private static final JsonFactory jsonFactory = new JsonFactory(new ObjectMapper());
private final Logger LOG = LoggerFactory.getLogger(GearmanServlet.class);
private final JobQueueMonitor jobQueueMonitor;
private final JobManager jobManager;
private final QueueMetrics jobQueueMetrics;
public GearmanServlet(JobQueueMonitor jobQueueMonitor, JobManager jobManager, QueueMetrics jobQueueMetrics)
{
this.jobQueueMonitor = jobQueueMonitor;
this.jobManager = jobManager;
this.jobQueueMetrics = jobQueueMetrics;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final String classPrefix = req.getParameter("class");
final boolean pretty = Boolean.parseBoolean(req.getParameter("pretty"));
final boolean history = Boolean.parseBoolean(req.getParameter("history"));
final boolean systemsnapshots = Boolean.parseBoolean(req.getParameter("system"));
final String jobQueueName = req.getParameter("jobQueue");
resp.setStatus(HttpServletResponse.SC_OK);
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setContentType(CONTENT_TYPE);
final OutputStream output = resp.getOutputStream();
final JsonGenerator json = jsonFactory.createJsonGenerator(output, JsonEncoding.UTF8);
if (pretty) {
json.useDefaultPrettyPrinter();
}
if(systemsnapshots)
{
writeSystemSnapshots(json);
} else {
json.writeStartObject();
{
if(jobQueueName == null)
{
writeAllJobQueueStats(json);
} else {
if(history)
{
final DateTime startTime, endTime;
if (req.getParameter("start") != null) {
startTime = new DateTime(Long.valueOf(req.getParameter("start")));
} else {
startTime = DateTime.now().minusHours(8);
}
if (req.getParameter("end") != null) {
endTime = new DateTime(Long.valueOf(req.getParameter("end")));
} else {
endTime = DateTime.now();
}
writeJobQueueSnapshots(jobQueueName, json, startTime, endTime);
} else {
writeJobQueueDetails(jobQueueName, json);
}
}
}
json.writeEndObject();
}
json.close();
}
public void writeSystemSnapshots(JsonGenerator json) throws IOException {
List<SystemSnapshot> snapshots = new ArrayList<>();
if (jobQueueMonitor != null) {
snapshots.addAll(jobQueueMonitor.getSystemSnapshots());
}
json.writeStartObject();
{
json.writeFieldName("snapshots");
json.writeStartArray();
for (SystemSnapshot snapshot : snapshots) {
json.writeStartObject();
{
json.writeNumberField("timestamp", snapshot.getTimestamp().getTime());
json.writeNumberField("totalPending", snapshot.getTotalJobsPending());
json.writeNumberField("totalProcessed", snapshot.getTotalJobsProcessed());
json.writeNumberField("diffQueued", snapshot.getJobsQueuedSinceLastSnapshot());
json.writeNumberField("diffProcessed", snapshot.getJobsProcessedSinceLastSnapshot());
json.writeNumberField("heapUsed", snapshot.getHeapUsed());
json.writeNumberField("heapSize", snapshot.getHeapSize());
}
json.writeEndObject();
}
json.writeEndArray();
json.writeFieldName("latest");
json.writeStartObject();
{
json.writeNumberField("totalPending", jobQueueMetrics.getPendingJobsCount());
json.writeNumberField("totalProcessed", jobQueueMetrics.getCompletedJobCount());
}
json.writeEndObject();
}
json.writeEndObject();
}
public void writeAllJobQueueStats(JsonGenerator json) throws IOException {
ConcurrentHashMap<String, JobQueue> jobQueues = jobManager.getJobQueues();
for(Map.Entry<String, JobQueue> entry : jobQueues.entrySet())
{
final String jobQueueName = entry.getKey();
final JobQueue jobQueue = entry.getValue();
if(jobQueue != null) {
json.writeNumberField(jobQueueName, jobQueueMetrics.getPendingJobsCount(jobQueueName));
}
}
}
public void writeJobQueueSnapshots(String jobQueueName, JsonGenerator json,
DateTime startTime,
DateTime endTime) throws IOException
{
if(jobQueueMonitor != null)
{
ImmutableMap<String, JobQueueMetrics> snapshotMap = jobQueueMonitor.getSnapshots();
if( snapshotMap != null && snapshotMap.containsKey(jobQueueName))
{
DateTime end = snapshotMap.get(jobQueueName).lowJobs.index().last();
// If the last data point is before our end time, we need to use that data as the end
if(end.isBefore(endTime)) {
endTime = end;
}
JobQueueMetrics jobQueueMetrics = snapshotMap.get(jobQueueName);
TimeSeries high = jobQueueMetrics.highJobs.downSampleToTimeWindow(startTime,
endTime, Frequency.of(5, TimeUnit.MINUTES), SampleMethod.MEAN);
TimeSeries mid = jobQueueMetrics.midJobs.downSampleToTimeWindow(startTime,
endTime, Frequency.of(5, TimeUnit.MINUTES), SampleMethod.MEAN);
TimeSeries low = jobQueueMetrics.lowJobs.downSampleToTimeWindow(startTime,
endTime, Frequency.of(5, TimeUnit.MINUTES), SampleMethod.MEAN);
json.writeFieldName("times");
json.writeStartArray();
for(DateTime t : high.index().asList()) {
json.writeNumber(t.toDate().getTime());
}
json.writeNumber(new Date().getTime());
json.writeEndArray();
json.writeFieldName("high");
json.writeStartArray();
for(BigDecimal d : high.values()) {
json.writeNumber(d);
}
json.writeNumber(jobQueueMetrics.highJobs.lastValue());
json.writeEndArray();
json.writeFieldName("mid");
json.writeStartArray();
for(BigDecimal d : mid.values()) {
json.writeNumber(d);
}
json.writeNumber(jobQueueMetrics.midJobs.lastValue());
json.writeEndArray();
json.writeFieldName("low");
json.writeStartArray();
for(BigDecimal d : low.values()) {
json.writeNumber(d);
}
json.writeNumber(jobQueueMetrics.lowJobs.lastValue());
json.writeEndArray();
}
} else {
json.writeFieldName("snapshots");
json.writeString("Disabled.");
}
}
public void writeJobQueueDetails(String jobQueueName, JsonGenerator json) throws IOException {
if(jobManager.getJobQueues().containsKey(jobQueueName))
{
JobQueue jobQueue = jobManager.getJobQueues().get(jobQueueName);
ImmutableSet<QueuedJob> jobs = ImmutableSet.copyOf(jobQueue.getAllJobs());
json.writeFieldName("jobs");
json.writeStartArray();
for(QueuedJob job : jobs)
{
json.writeStartObject();
{
json.writeStringField("unique_id", job.getUniqueID());
json.writeNumberField("time_to_run", job.getTimeToRun());
}
json.writeEndObject();
}
json.writeEndArray();
}
}
}