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\">&nbsp;").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}