001package org.javasimon.console.plugin; 002 003import java.io.IOException; 004 005import org.javasimon.Simon; 006import org.javasimon.Stopwatch; 007import org.javasimon.callback.quantiles.BucketSample; 008import org.javasimon.callback.quantiles.BucketsSample; 009import org.javasimon.callback.quantiles.QuantilesCallback; 010import org.javasimon.console.ActionContext; 011import org.javasimon.console.SimonCallbacks; 012import org.javasimon.console.action.DetailHtmlBuilder; 013import org.javasimon.console.action.DetailPlugin; 014import org.javasimon.console.html.HtmlResourceType; 015import org.javasimon.console.json.ArrayJS; 016import org.javasimon.console.json.ObjectJS; 017import org.javasimon.console.text.StringifierFactory; 018 019/** 020 * Detail plugin to display {@link QuantilesCallback} information 021 */ 022public class QuantilesDetailPlugin extends DetailPlugin { 023 024 /** 025 * Message: Callback not registered 026 */ 027 public static final String NO_CALLBACK_MESSAGE = "Quantiles callback not registered"; 028 /** 029 * Message: Data not found in Simon 030 */ 031 private static final String NO_DATA_MESSAGE = "No data available"; 032 033 public QuantilesDetailPlugin() { 034 super("quantiles", "Distribution and Quantiles"); 035 addResource("js/javasimon-quantilesPlugin.js", HtmlResourceType.JS); 036 addResource("css/javasimon-quantilesPlugin.css", HtmlResourceType.CSS); 037 } 038 039 /** 040 * Indicate that this plugin only applies on Stopwatches. 041 */ 042 @Override 043 public boolean supports(Simon simon) { 044 return simon instanceof Stopwatch; 045 } 046 047 /** 048 * Indicate whether {@link QuantilesCallback} was registered in manager 049 */ 050 private boolean isQuantilesCallbackRegistered(ActionContext context) { 051 return SimonCallbacks.getCallbackByType(context.getManager(), QuantilesCallback.class) != null; 052 } 053 054 /** 055 * Get quantiles data from Simon 056 */ 057 private BucketsSample getData(Simon simon) { 058 return QuantilesCallback.sampleBuckets((Stopwatch) simon); 059 } 060 061 /** 062 * Generate an HTML message row 063 */ 064 private void htmlMessage(DetailHtmlBuilder htmlBuilder, String message) throws IOException { 065 htmlBuilder.beginRow() 066 .labelCell("Message").valueCell(" colspan=\"3\"", message) 067 .endRow(); 068 } 069 @Override 070 public DetailHtmlBuilder executeHtml(ActionContext context, DetailHtmlBuilder htmlBuilder, StringifierFactory htmlStringifierFactory, Simon simon) throws IOException { 071 if (isQuantilesCallbackRegistered(context)) { 072 BucketsSample bucketsSample = getData(simon); 073 if (bucketsSample == null) { 074 htmlMessage(htmlBuilder, NO_DATA_MESSAGE); 075 } else { 076 htmlBuilder.beginRow() 077 .labelCell("Median") 078 .valueCell(htmlStringifierFactory.toString(bucketsSample.getMedian(), "Time")) 079 .labelCell("90%") 080 .valueCell(htmlStringifierFactory.toString(bucketsSample.getPercentile90(), "Time")) 081 .endRow(); 082 htmlBuilder.beginRow().labelCell("Distribution").beginValueCell(); 083 htmlBuilder.begin("table") 084 .beginRow().labelCell("Min").labelCell("Max").labelCell("Counter").endRow(); 085 Integer maxCount = bucketsSample.getMaxCount(); 086 for(BucketSample bucketSample:bucketsSample.getBuckets()) { 087 final int count = bucketSample.getCount(); 088 final int barSize = count > 0 && maxCount > 0 ? count * 200 / maxCount : 0; 089 htmlBuilder.beginRow() 090 .beginValueCell().value(bucketSample.getMin(),"Time").endValueCell() 091 .beginValueCell().value(bucketSample.getMax(),"Time").endValueCell() 092 .beginValueCell().write("<div class=\"bar\" style=\"width:").write(Integer.toString(barSize)).write("px\"> ").end("div").value(count, null).endValueCell() 093 .endRow(); 094 } 095 htmlBuilder.end("table"); 096 htmlBuilder.endValueCell().endRow(); 097 } 098 } else { 099 htmlMessage(htmlBuilder, NO_CALLBACK_MESSAGE); 100 } 101 return htmlBuilder; 102 } 103 104 /** 105 * Generate a JSON message attribute 106 */ 107 private ObjectJS jsonMessage(String message, StringifierFactory jsonStringifierFactory) { 108 ObjectJS bucketsJS = new ObjectJS(); 109 bucketsJS.setSimpleAttribute("message", message, jsonStringifierFactory.getStringifier(String.class)); 110 return bucketsJS; 111 } 112 113 @Override 114 public ObjectJS executeJson(ActionContext context, StringifierFactory jsonStringifierFactory, Simon simon) { 115 ObjectJS bucketsJS; 116 if (isQuantilesCallbackRegistered(context)) { 117 BucketsSample bucketsSample = getData(simon); 118 if (bucketsSample == null) { 119 bucketsJS = jsonMessage(NO_DATA_MESSAGE, jsonStringifierFactory); 120 } else { 121 bucketsJS = ObjectJS.create(bucketsSample, jsonStringifierFactory); 122 bucketsJS.setAttribute("buckets", ArrayJS.create(bucketsSample.getBuckets(), jsonStringifierFactory)); 123 } 124 } else { 125 bucketsJS = jsonMessage(NO_CALLBACK_MESSAGE, jsonStringifierFactory); 126 } 127 return bucketsJS; 128 } 129}