package com.comcast.cmb.common.controller;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.labels.StandardXYItemLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Hour;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeTableXYDataset;
import org.jfree.ui.RectangleEdge;
public class CMBVisualizerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pathInfo = request.getPathInfo();
if (pathInfo == null) {
return;
}
if (pathInfo.toLowerCase().contains("responsetimeimg")) {
doResponseTimeChart(request, response);
} else if (pathInfo.toLowerCase().contains("calldistributionimg")) {
doApiPieChart(request, response);
} else if (pathInfo.toLowerCase().contains("callcountimg")) {
doAPICallCountChart(request, response);
}
}
protected void doResponseTimeChart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int resolutionMS = 10;
String action = null;
boolean redisOnly = false;
boolean cassandraOnly = false;
if (request.getParameter("redis") != null) {
redisOnly = Boolean.parseBoolean(request.getParameter("redis"));
resolutionMS = 1;
} else if (request.getParameter("cassandra") != null) {
cassandraOnly = Boolean.parseBoolean(request.getParameter("cassandra"));
resolutionMS = 1;
} else if (request.getParameter("ac") != null) {
action = request.getParameter("ac");
resolutionMS = 10;
} else if (request.getParameter("rs") != null) {
resolutionMS = Integer.parseInt(request.getParameter("rs"));
};
byte b[] = generateResponseTimeChart(resolutionMS, action, redisOnly, cassandraOnly);
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentLength(b.length);
response.setContentType("image/jpeg");
response.getOutputStream().write(b);
response.flushBuffer();
}
protected void doAPICallCountChart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
byte b[] = generateApiCallCountChart();
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentLength(b.length);
response.setContentType("image/jpeg");
response.getOutputStream().write(b);
response.flushBuffer();
}
protected void doApiPieChart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
byte b[] = generateApiDistributionPieChart();
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentLength(b.length);
response.setContentType("image/jpeg");
response.getOutputStream().write(b);
response.flushBuffer();
}
private byte[] generateApiDistributionPieChart() throws IOException {
DefaultPieDataset piedataset = new DefaultPieDataset();
Map<String, Long> apiCounts = new HashMap<String, Long>();
long total = 0;
String topApi = null;
long topCount = 0;
for (String api : CMBControllerServlet.callStats.keySet()) {
AtomicLong[][] rt = CMBControllerServlet.callResponseTimesByApi.get(api);
long count = 0;
for (int i=0; i<rt.length; i++) {
for (int k=0; k<rt[0].length; k++) {
count += rt[i][k].longValue();
}
}
total+=count;
apiCounts.put(api, count);
if (count > topCount) {
topCount = count;
topApi = api;
}
}
for (String api : apiCounts.keySet()) {
piedataset.setValue(api, 1.0*apiCounts.get(api)/total);
}
JFreeChart chart = ChartFactory.createPieChart("API Call Distribution", piedataset, true, true, false);
PiePlot pieplot = (PiePlot)chart.getPlot();
List<Color> palette = new ArrayList<Color>();
palette.add(new Color(0x660000));
palette.add(new Color(0x990000));
palette.add(new Color(0xFF0000));
palette.add(new Color(0x003300));
palette.add(new Color(0x006600));
palette.add(new Color(0x00FF00));
palette.add(new Color(0x003399));
palette.add(new Color(0x0066FF));
palette.add(new Color(0x00CCFF));
palette.add(new Color(0x330066));
palette.add(new Color(0x660066));
palette.add(new Color(0xCCCC33));
palette.add(new Color(0x990066));
palette.add(new Color(0xFF99FF));
palette.add(new Color(0xFF6633));
int i=0;
for (String api : apiCounts.keySet()) {
pieplot.setSectionPaint(api, palette.get(i%palette.size()));
i++;
}
pieplot.setNoDataMessage("No data available");
pieplot.setExplodePercent(topApi, 0.20000000000000001D);
pieplot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} ({2} percent)"));
pieplot.setLabelBackgroundPaint(new Color(220, 220, 220));
pieplot.setLegendLabelToolTipGenerator(new StandardPieSectionLabelGenerator("Tooltip for legend item {0}"));
pieplot.setSimpleLabels(true);
pieplot.setInteriorGap(0.0D);
// generate jpeg
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ChartUtilities.writeChartAsJPEG(bos, chart, 2400, 400);
return bos.toByteArray();
}
private byte[] generateResponseTimeChart(int resolutionMS, String action, boolean showRedisOnly, boolean showCassandraOnly) throws IOException {
String label = "";
AtomicLong[][] responseTimesMS;
if (showRedisOnly) {
label = "Redis Percentiles 1 MS";
responseTimesMS = CMBControllerServlet.callResponseTimesRedisMS;
} else if (showCassandraOnly) {
label = "Cassandra Percentiles 1 MS";
responseTimesMS = CMBControllerServlet.callResponseTimesCassandraMS;
} else if (action != null) {
label = action + " Percentiles 10 MS";
responseTimesMS = CMBControllerServlet.callResponseTimesByApi.get(action);
} else {
if (resolutionMS == 1) {
label = "API Percentiles 1 MS";
responseTimesMS = CMBControllerServlet.callResponseTimes1MS;
} else if (resolutionMS == 100) {
label = "API Percentiles 100 MS";
responseTimesMS = CMBControllerServlet.callResponseTimes100MS;
} else if (resolutionMS == 1000) {
label = "API Percentiles 1000 MS";
responseTimesMS = CMBControllerServlet.callResponseTimes1000MS;
} else {
label = "API Percentiles 10 MS";
responseTimesMS = CMBControllerServlet.callResponseTimes10MS;
}
}
// clone and normalize data
long[][] rt = new long[CMBControllerServlet.NUM_MINUTES][CMBControllerServlet.NUM_BUCKETS];
long[] totals = new long[CMBControllerServlet.NUM_MINUTES];
long grandTotal = 0;
int activeMinutes = 0;
for (int i=0; i<CMBControllerServlet.NUM_MINUTES; i++) {
for (int k=0; k<CMBControllerServlet.NUM_BUCKETS; k++) {
rt[i][k] = responseTimesMS[i][k].longValue();
totals[i] += rt[i][k];
}
if (totals[i] > 0) {
grandTotal += totals[i];
activeMinutes++;
}
}
for (int i=0; i<CMBControllerServlet.NUM_MINUTES; i++) {
for (int k=0; k<CMBControllerServlet.NUM_BUCKETS; k++) {
if (totals[i] == 0) {
rt[i][k] = 0;
} else {
rt[i][k] = 100*rt[i][k]/totals[i];
}
}
}
// convert data for rendering
TimeTableXYDataset dataset = new TimeTableXYDataset();
for (int i=0; i<CMBControllerServlet.NUM_MINUTES; i++) {
for (int k=0; k<CMBControllerServlet.NUM_BUCKETS; k++) {
dataset.add(new Minute(i, new Hour()), rt[i][k], (resolutionMS*k)+"ms");
}
}
// generate chart
DateAxis domainAxis = new DateAxis("Minute");
domainAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
domainAxis.setLowerMargin(0.01);
domainAxis.setUpperMargin(0.01);
NumberAxis rangeAxis = new NumberAxis("Response Time");
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setUpperMargin(0.10); // leave some space for item labels
StackedXYBarRenderer renderer = new StackedXYBarRenderer(0.15);
renderer.setDrawBarOutline(true);
renderer.setBaseItemLabelsVisible(true);
renderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator());
/*int r=0, g=200, b=0;
int inc = 200 / CMBControllerServlet.NUM_BUCKETS;
for (int i=0; i<CMBControllerServlet.NUM_BUCKETS; i++) {
renderer.setSeriesPaint(i, new Color(r,g,b));
r+=inc;
g-=inc;
}*/
renderer.setSeriesPaint(0, new Color(47,133,18));
renderer.setSeriesPaint(1, new Color(151,195,30));
renderer.setSeriesPaint(2, new Color(253,249,50));
renderer.setSeriesPaint(3, new Color(253,191,35));
renderer.setSeriesPaint(4, new Color(253,123,26));
renderer.setSeriesPaint(5, new Color(216,106,20));
renderer.setSeriesPaint(6, new Color(181,97,28));
renderer.setSeriesPaint(7, new Color(208,56,20));
renderer.setSeriesPaint(8, new Color(253,30,19));
renderer.setSeriesPaint(9, new Color(120,54,210));
XYPlot plot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);
String title = "Response Time Percentiles";
if (activeMinutes > 0) {
title = label + " [" + grandTotal/(activeMinutes*60) + " call/sec " + activeMinutes + " mins " + grandTotal + " calls]";
}
JFreeChart chart = new JFreeChart(title, plot);
chart.removeLegend();
// chart.addSubtitle(new TextTitle(""));
LegendTitle legend = new LegendTitle(plot);
legend.setFrame(new BlockBorder());
legend.setPosition(RectangleEdge.BOTTOM);
chart.addSubtitle(legend);
// generate jpeg
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ChartUtilities.writeChartAsJPEG(bos, chart, 2400, 400);
return bos.toByteArray();
}
private byte[] generateApiCallCountChart() throws IOException {
// convert data
TimeTableXYDataset dataset = new TimeTableXYDataset();
for (String ac : CMBControllerServlet.callResponseTimesByApi.keySet()) {
AtomicLong[][] responseTimesMS = CMBControllerServlet.callResponseTimesByApi.get(ac);
for (int i=0; i<CMBControllerServlet.NUM_MINUTES; i++) {
long total = 0;
for (int k=0; k<CMBControllerServlet.NUM_BUCKETS; k++) {
total += responseTimesMS[i][k].longValue();
}
dataset.add(new Minute(i, new Hour()), total, ac);
}
}
// generate chart
DateAxis domainAxis = new DateAxis("Minute");
domainAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
domainAxis.setLowerMargin(0.01);
domainAxis.setUpperMargin(0.01);
NumberAxis rangeAxis = new NumberAxis("Calls");
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setUpperMargin(0.10); // leave some space for item labels
StackedXYBarRenderer renderer = new StackedXYBarRenderer(0.15);
renderer.setDrawBarOutline(true);
renderer.setBaseItemLabelsVisible(true);
renderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator());
renderer.setSeriesPaint(0, new Color(0x660000));
renderer.setSeriesPaint(1, new Color(0x990000));
renderer.setSeriesPaint(2, new Color(0xFF0000));
renderer.setSeriesPaint(3, new Color(0x003300));
renderer.setSeriesPaint(4, new Color(0x006600));
renderer.setSeriesPaint(5, new Color(0x00FF00));
renderer.setSeriesPaint(6, new Color(0x003399));
renderer.setSeriesPaint(7, new Color(0x0066FF));
renderer.setSeriesPaint(8, new Color(0x00CCFF));
renderer.setSeriesPaint(9, new Color(0x330066));
renderer.setSeriesPaint(10, new Color(0x660066));
renderer.setSeriesPaint(11, new Color(0xCCCC33));
renderer.setSeriesPaint(12, new Color(0x990066));
renderer.setSeriesPaint(13, new Color(0xFF99FF));
renderer.setSeriesPaint(14, new Color(0xFF6633));
XYPlot plot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);
String title = "Call Mix";
JFreeChart chart = new JFreeChart(title, plot);
chart.removeLegend();
// chart.addSubtitle(new TextTitle(""));
LegendTitle legend = new LegendTitle(plot);
legend.setFrame(new BlockBorder());
legend.setPosition(RectangleEdge.BOTTOM);
chart.addSubtitle(legend);
// generate jpeg
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ChartUtilities.writeChartAsJPEG(bos, chart, 2400, 400);
return bos.toByteArray();
}
}