package com.breakersoft.plow.http;
import static org.rrd4j.ConsolFun.AVERAGE;
import static org.rrd4j.ConsolFun.MAX;
import static org.rrd4j.DsType.COUNTER;
import static org.rrd4j.DsType.GAUGE;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import org.rrd4j.ConsolFun;
import org.rrd4j.core.RrdDb;
import org.rrd4j.core.RrdDef;
import org.rrd4j.core.Sample;
import org.rrd4j.graph.RrdGraph;
import org.rrd4j.graph.RrdGraphConstants;
import org.rrd4j.graph.RrdGraphDef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.breakersoft.plow.PlowCfg;
import com.breakersoft.plow.PlowThreadPools;
import com.breakersoft.plow.monitor.PlowStats;
import com.google.common.io.Files;
@Controller
public class RrdGraphController {
private static final Logger logger = LoggerFactory.getLogger(RrdGraphController.class);
private static final BasicStroke dottedStroke = new BasicStroke(
1f,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND,
1f,
new float[] {2f},
0f);
private static final String PLOW_RRD = "plow.rrd";
private static final String THREAD_RRD = "thread.rrd";
@Autowired
PlowThreadPools plowThreadPools;
@Autowired
PlowCfg plowCfg;
private String rrdPath(String rrd) {
return plowCfg.get("plow.rrd.path") + "/" + rrd;
}
@Scheduled(cron="0 * * * * *")
public void poll() {
if (!plowCfg.get("plow.rrd.enabled", false)) {
return;
}
updatePlowRrd();
updateThreadRrd();
}
@PostConstruct
public void init() {
if (!plowCfg.get("plow.rrd.enabled", false)) {
logger.info("RRD graphs disabled.");
return;
}
try {
Files.createParentDirs(new File(rrdPath(PLOW_RRD)));
} catch (IOException e1) {
throw new RuntimeException("Failed to initialize RRD support: " + e1);
}
if (!new File(rrdPath(PLOW_RRD)).isFile()) {
createPlowRrd();
}
if (!new File(rrdPath(THREAD_RRD)).isFile()) {
createThreadRrd();
}
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/node_dsp.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] nodeDispatcherStatsGraph() throws IOException {
final String path = rrdPath(PLOW_RRD);
final RrdGraphDef graphDef = baseGraph("Node Dispatcher Traffic", "dps/sec");
graphDef.datasource("linea", path, "nodeDispatchHit", ConsolFun.AVERAGE);
graphDef.datasource("lineb", path, "nodeDispatchMiss", ConsolFun.AVERAGE);
graphDef.datasource("linec", path, "nodeDispatchFail", ConsolFun.AVERAGE);
graphDef.area("linea", new Color(152, 175, 54), "Hit");
graphDef.area("lineb", new Color(74, 104, 15), "Miss");
graphDef.area("linec", new Color(164, 11, 23), "Error");
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/proc_dsp.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] procDispatcherStatsGraph() throws IOException {
final String path = rrdPath(PLOW_RRD);
final RrdGraphDef graphDef = baseGraph("Proc Dispatcher Traffic", "dps/sec");
graphDef.datasource("linea", path, "procDispatchHit", ConsolFun.AVERAGE);
graphDef.datasource("lineb", path, "procDispatchMiss", ConsolFun.AVERAGE);
graphDef.datasource("linec", path, "procDispatchFail", ConsolFun.AVERAGE);
graphDef.area("linea", new Color(152, 175, 54), "Hit");
graphDef.area("lineb", new Color(74, 104, 15), "Miss");
graphDef.area("linec", new Color(164, 11, 23), "Error");
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/task_exec.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] taskCountsGraph() throws IOException {
final String path = rrdPath(PLOW_RRD);
final RrdGraphDef graphDef = baseGraph("Task Executions", "task/sec");
graphDef.datasource("linea", path, "taskStartedCount", ConsolFun.AVERAGE);
graphDef.datasource("lineb", path, "taskStartedFailCount", ConsolFun.AVERAGE);
graphDef.datasource("linec", path, "taskStoppedCount", ConsolFun.AVERAGE);
graphDef.datasource("lined", path, "taskStoppedFailCount", ConsolFun.AVERAGE);
graphDef.area("linea", new Color(8, 175, 193), "Started");
graphDef.stack("linec", new Color(133, 225, 224), "Stopped");
graphDef.line("lineb", new Color(243, 165, 41), "Error Started", 2);
graphDef.line("lined", new Color(241, 93, 5), "Error Stopped", 2);
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/job_launch.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] jobLaunchGraph() throws IOException {
final String path = rrdPath(PLOW_RRD);
final RrdGraphDef graphDef = baseGraph("Job Launch", "jobs/sec");
graphDef.datasource("linea", path, "jobLaunchCount", ConsolFun.AVERAGE);
graphDef.datasource("lineb", path, "jobLaunchFailCount", ConsolFun.AVERAGE);
graphDef.datasource("linec", path, "jobFinishCount", ConsolFun.AVERAGE);
graphDef.datasource("lined", path, "jobKillCount", ConsolFun.AVERAGE);
graphDef.area("linea", new Color(8, 175, 193), "Launched");
graphDef.stack("linec", new Color(133, 225, 224), "Finished");
graphDef.line("lineb", new Color(241, 93, 5), "Launch Fail", 2);
graphDef.line("lined", new Color(243, 165, 41), "Jobs Killed", 2);
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/rnd_traffic.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] rndTrafficGraph() throws IOException {
final String path = rrdPath(PLOW_RRD);
final RrdGraphDef graphDef = baseGraph("Rnd Traffic", "ops/sec");
graphDef.datasource("ping", path, "rndPingCount", ConsolFun.AVERAGE);
graphDef.datasource("task", path, "rndTaskComplete", ConsolFun.AVERAGE);
graphDef.area("ping", new Color(179, 96, 157), "Ping");
graphDef.stack("task", new Color(255, 163, 231), "Task Complete");
graphDef.comment("\\r");
graphDef.gprint("ping", MAX, "Max Pings = %.3f%S");
graphDef.gprint("ping", AVERAGE, "Avg Pings = %.3f%S\n");
graphDef.gprint("task", MAX, "Max Task Complate = %.3f%s");
graphDef.gprint("task", AVERAGE, "Avg Task Complete = %.3f%S\n");
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/node_threads.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] nodeDispatcherThreads() throws IOException {
final String path = rrdPath(THREAD_RRD);
final RrdGraphDef graphDef = baseGraph("Node Dispatcher Queue", "threads/sec");
graphDef.datasource("threads", path, "nodeActiveThreads", ConsolFun.AVERAGE);
graphDef.datasource("queue", path, "nodeWaiting", ConsolFun.AVERAGE);
graphDef.line("threads", new Color(152, 175, 54), "Active Threads");
graphDef.area("queue", new Color(74, 104, 15), "Queued Work");
graphDef.comment("\\r");
graphDef.gprint("threads", MAX, "Max Threads = %.3f%S");
graphDef.gprint("threads", AVERAGE, "Avg Threads = %.3f%S\n");
graphDef.gprint("queue", MAX, "Max Queued = %.3f%s");
graphDef.gprint("queue", AVERAGE, "Avg Queued = %.3f%S\n");
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/proc_threads.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] procDispatcherThreads() throws IOException {
final String path = rrdPath(THREAD_RRD);
final RrdGraphDef graphDef = baseGraph("Proc Dispatcher Queue", "threads/sec");
graphDef.datasource("linea", path, "procActiveThreads", ConsolFun.AVERAGE);
graphDef.datasource("lineb", path, "procWaiting", ConsolFun.AVERAGE);
graphDef.area("lineb", new Color(74, 104, 15), "Queued Work");
graphDef.line("linea", new Color(152, 175, 54), "Active Threads");
return createImage(graphDef);
}
@ResponseBody
@RequestMapping(value="/monitor/graphs/async_threads.png", method=RequestMethod.GET, produces=MediaType.IMAGE_PNG_VALUE)
public byte[] asyncWorkQueueThreads() throws IOException {
final String path = rrdPath(THREAD_RRD);
final RrdGraphDef graphDef = baseGraph("Async Work Queue", "threads/sec");
graphDef.datasource("linea", path, "asyncActiveThreads", ConsolFun.AVERAGE);
graphDef.datasource("lineb", path, "asyncWaiting", ConsolFun.AVERAGE);
graphDef.area("lineb", new Color(74, 104, 15), "Queued Work");
graphDef.line("linea", new Color(152, 175, 54), "Active Threads");
return createImage(graphDef);
}
private byte[] createImage(RrdGraphDef graphDef) throws IOException {
RrdGraph graph = new RrdGraph(graphDef);
BufferedImage bim = new BufferedImage(graph.getRrdGraphInfo().getWidth(),
graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB);
graph.render(bim.getGraphics());
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
ImageIO.write(bim, "png", baos);
baos.flush();
return baos.toByteArray();
} finally {
if (baos != null) {
baos.close();
}
}
}
private RrdGraphDef baseGraph(String title, String vertLabel) {
final long now = System.currentTimeMillis() / 1000;
RrdGraphDef graphDef = new RrdGraphDef();
graphDef.setTimeSpan(now - 3600 , now);
graphDef.setTitle(title);
graphDef.setVerticalLabel(vertLabel);
graphDef.setImageFormat("png");
graphDef.setWidth(800);
graphDef.setHeight(150);
graphDef.setAntiAliasing(true);
graphDef.setTextAntiAliasing(true);
graphDef.setColor(RrdGraphConstants.COLOR_BACK, new Color(44, 44, 44));
graphDef.setColor(RrdGraphConstants.COLOR_FONT, new Color(240, 240, 240));
graphDef.setColor(RrdGraphConstants.COLOR_FRAME, new Color(55, 55, 55));
graphDef.setColor(RrdGraphConstants.COLOR_CANVAS, new Color(55, 55, 55));
graphDef.setColor(RrdGraphConstants.COLOR_GRID, new Color(82, 82, 82, 155));
graphDef.setColor(RrdGraphConstants.COLOR_SHADEA, new Color(44, 44, 44));
graphDef.setColor(RrdGraphConstants.COLOR_SHADEB, new Color(44, 44, 44));
graphDef.setGridStroke(dottedStroke);
return graphDef;
}
public void updateThreadRrd() {
RrdDb rrdDb;
try {
rrdDb = new RrdDb(rrdPath(THREAD_RRD));
final String data = join(new long[] {
System.currentTimeMillis() / 1000,
plowThreadPools.nodeDispatcherExecutor().getThreadPoolExecutor().getPoolSize(),
plowThreadPools.nodeDispatcherExecutor().getActiveCount(),
plowThreadPools.nodeDispatcherExecutor().getThreadPoolExecutor().getCompletedTaskCount(),
(long) plowThreadPools.nodeDispatcherExecutor().getThreadPoolExecutor().getQueue().size(),
(long) plowThreadPools.nodeDispatcherExecutor().getThreadPoolExecutor().getQueue().remainingCapacity(),
plowThreadPools.pipelineExecutor().getThreadPoolExecutor().getPoolSize(),
plowThreadPools.pipelineExecutor().getActiveCount(),
plowThreadPools.pipelineExecutor().getThreadPoolExecutor().getCompletedTaskCount(),
(long) plowThreadPools.pipelineExecutor().getThreadPoolExecutor().getQueue().size(),
(long) plowThreadPools.pipelineExecutor().getThreadPoolExecutor().getQueue().remainingCapacity(),
plowThreadPools.stateChangeExecutor().getThreadPoolExecutor().getPoolSize(),
plowThreadPools.stateChangeExecutor().getActiveCount(),
plowThreadPools.stateChangeExecutor().getThreadPoolExecutor().getCompletedTaskCount(),
(long) plowThreadPools.stateChangeExecutor().getThreadPoolExecutor().getQueue().size(),
(long) plowThreadPools.stateChangeExecutor().getThreadPoolExecutor().getQueue().remainingCapacity()
}, ":");
logger.info("Writing Threads RRD sample {}", data);
Sample sample = rrdDb.createSample();
sample.setAndUpdate(data);
rrdDb.close();
} catch (Exception e) {
logger.warn("Failed to write RRD file at {}, {}", rrdPath(THREAD_RRD), e);
}
}
public void updatePlowRrd() {
RrdDb rrdDb;
try {
rrdDb = new RrdDb(rrdPath(PLOW_RRD));
final String data = join(new long[] {
System.currentTimeMillis() / 1000,
PlowStats.nodeDispatchHit.get(),
PlowStats.nodeDispatchMiss.get(),
PlowStats.nodeDispatchFail.get(),
PlowStats.procDispatchHit.get(),
PlowStats.procDispatchMiss.get(),
PlowStats.procDispatchFail.get(),
PlowStats.procAllocCount.get(),
PlowStats.procUnallocCount.get(),
PlowStats.procAllocFailCount.get(),
PlowStats.procUnallocFailCount.get(),
PlowStats.procOrphanedCount.get(),
PlowStats.taskStartedCount.get(),
PlowStats.taskStartedFailCount.get(),
PlowStats.taskStoppedCount.get(),
PlowStats.taskStoppedFailCount.get(),
PlowStats.jobLaunchCount.get(),
PlowStats.jobLaunchFailCount.get(),
PlowStats.jobFinishCount.get(),
PlowStats.jobKillCount.get(),
PlowStats.rndPingCount.get(),
PlowStats.rndTaskComplete.get()
},":");
logger.info("Writing Plow RRD sample {}", data);
Sample sample = rrdDb.createSample();
sample.setAndUpdate(data);
rrdDb.close();
} catch (Exception e) {
logger.warn("Failed to write Plow RRD file at {}, {}", rrdPath(PLOW_RRD), e);
}
}
private void createThreadRrd() {
logger.info("Initialzing Thread RRD data at: {}", rrdPath(THREAD_RRD));
RrdDef rrdDef = new RrdDef(rrdPath(THREAD_RRD), 60);
rrdDef.addArchive(AVERAGE, 0.5, 1, 1440);
rrdDef.addArchive(AVERAGE, 0.5, 5, 288);
rrdDef.addArchive(MAX, 0.5, 1, 1440);
rrdDef.addArchive(MAX, 0.5, 5, 288);
rrdDef.addDatasource("nodeThreads", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("nodeActiveThreads", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("nodeExecuted", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("nodeWaiting", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("nodeCapacity", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procThreads", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procActiveThreads", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procExecuted", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procWaiting", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procCapacity", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("asyncThreads", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("asyncActiveThreads", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("asyncExecuted", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("asyncWaiting", GAUGE, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("asyncCapacity", GAUGE, 600, Double.NaN, Double.NaN);
saveRrdDef(rrdDef);
}
private void createPlowRrd() {
logger.info("Initialzing Plow RRD data at: {}", rrdPath(PLOW_RRD));
RrdDef rrdDef = new RrdDef(rrdPath(PLOW_RRD), 60);
rrdDef.addArchive(AVERAGE, 0.5, 1, 1440);
rrdDef.addArchive(AVERAGE, 0.5, 5, 288);
rrdDef.addArchive(MAX, 0.5, 1, 1440);
rrdDef.addArchive(MAX, 0.5, 5, 288);
rrdDef.addDatasource("nodeDispatchHit", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("nodeDispatchMiss", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("nodeDispatchFail", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procDispatchHit", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procDispatchMiss", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procDispatchFail", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procAllocCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procUnallocCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procAllocFailCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procUnallocFailCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("procOrphan", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("taskStartedCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("taskStartedFailCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("taskStoppedCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("taskStoppedFailCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("jobLaunchCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("jobLaunchFailCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("jobFinishCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("jobKillCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("rndPingCount", COUNTER, 600, Double.NaN, Double.NaN);
rrdDef.addDatasource("rndTaskComplete", COUNTER, 600, Double.NaN, Double.NaN);
saveRrdDef(rrdDef);
}
private void saveRrdDef(RrdDef rrdDef) {
RrdDb rrdDb;
try {
rrdDb = new RrdDb(rrdDef);
rrdDb.close();
} catch (IOException e) {
throw new RuntimeException("Failed to initialize RRD support: " + e);
}
}
public static String join(final long[] values, final String delimit) {
StringBuilder sb = new StringBuilder(256);
for (long value: values) {
sb.append(String.valueOf(value));
sb.append(delimit);
}
sb.delete(sb.length()-1, sb.length());
return sb.toString();
}
}