001package org.javasimon.javaee.reqreporter;
002
003import java.util.HashMap;
004import java.util.List;
005import java.util.Map;
006import java.util.Set;
007import java.util.TreeSet;
008import javax.servlet.http.HttpServletRequest;
009
010import org.javasimon.Split;
011import org.javasimon.javaee.SimonServletFilter;
012import org.javasimon.utils.SimonUtils;
013
014/**
015 * Reports significant splits (longer than 5% of the request) and list of all used stopwatches with their split counts.
016 * Report is sent through {@link org.javasimon.Manager#message(String)}. Following aspects of the class can be overridden:
017 * <ul>
018 * <li>Where the report goes - override {@link #reportMessage(String)},</li>
019 * <li>what is significant split - override {@link #isSignificantSplit(org.javasimon.Split, org.javasimon.Split)},</li>
020 * <li>whether stopwatch info (from stopwatch distribution part) should be included -
021 * override {@link #shouldBeAddedStopwatchInfo(ReporterStopwatchInfo)}.</li>
022 * </ul>
023 *
024 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
025 */
026public class DefaultRequestReporter implements RequestReporter {
027        private static final int NOTE_OUTPUT_MAX_LEN = 80;
028
029        private SimonServletFilter simonServletFilter;
030
031        public DefaultRequestReporter() {
032        }
033
034        @Override
035        public void reportRequest(HttpServletRequest request, Split requestSplit, List<Split> splits) {
036                StringBuilder messageBuilder = new StringBuilder(
037                        "Web request is too long (" + SimonUtils.presentNanoTime(requestSplit.runningFor()) +
038                                ") [" + requestSplit.getStopwatch().getNote() + "]");
039
040                if (splits.size() > 0) {
041                        buildSplitDetails(requestSplit, splits, messageBuilder);
042                }
043
044                reportMessage(messageBuilder.toString());
045        }
046
047        /**
048         * Reports the prepared message through the method {@link org.javasimon.Manager#message(String)} - can be overridden
049         * to emit the message to log/console/etc.
050         *
051         * @param message prepared message with report
052         */
053        protected void reportMessage(String message) {
054                simonServletFilter.getManager().message(message);
055        }
056
057        private void buildSplitDetails(Split requestSplit, List<Split> splits, StringBuilder messageBuilder) {
058                Map<String, ReporterStopwatchInfo> stopwatchInfos = new HashMap<>();
059
060                processSplitsAndAddSignificantOnes(requestSplit, splits, messageBuilder, stopwatchInfos);
061                addStopwatchSplitDistribution(messageBuilder, stopwatchInfos);
062        }
063
064        private void processSplitsAndAddSignificantOnes(Split requestSplit, List<Split> splits, StringBuilder messageBuilder, Map<String, ReporterStopwatchInfo> stopwatchInfos) {
065                for (Split split : splits) {
066                        ReporterStopwatchInfo stopwatchInfo = stopwatchInfos.get(split.getStopwatch().getName());
067                        if (stopwatchInfo == null) {
068                                stopwatchInfo = new ReporterStopwatchInfo(split.getStopwatch());
069                                stopwatchInfos.put(split.getStopwatch().getName(), stopwatchInfo);
070                        }
071                        stopwatchInfo.addSplit(split);
072
073                        if (isSignificantSplit(split, requestSplit)) {
074                                messageBuilder.append("\n\t").append(split.getStopwatch().getName()).append(": ").
075                                        append(SimonUtils.presentNanoTime(split.runningFor()));
076                        }
077                }
078        }
079
080        /**
081         * Can be overridden to decide whether {@link Split} is considered significant to be reported in the first part of the output.
082         * By default all Splits with time over 5% of total request time are significant. This includes overlapping splits too, so more than
083         * 20 splits can be reported.
084         *
085         * @param split tested Split
086         * @param requestSplit Split for the whole HTTP request
087         * @return true, if tested Split is significant
088         */
089        protected boolean isSignificantSplit(Split split, Split requestSplit) {
090                return split.runningFor() > (requestSplit.runningFor() / 20); // is more than 5%
091        }
092
093        private void addStopwatchSplitDistribution(StringBuilder messageBuilder, Map<String, ReporterStopwatchInfo> stopwatchInfos) {
094                messageBuilder.append("\nStopwatch/Split count/total/max for this request (sorted by total descending):");
095                Set<ReporterStopwatchInfo> sortedInfos = new TreeSet<>(stopwatchInfos.values());
096                for (ReporterStopwatchInfo info : sortedInfos) {
097                        if (shouldBeAddedStopwatchInfo(info)) {
098                                addStopwatchInfo(messageBuilder, info);
099                        }
100                }
101        }
102
103        /**
104         * Decides whether stopwatch info should be included in the report - by default all are included.
105         *
106         * @param info stopwatch info contains list of all splits, max split and total time of splits for the reported request
107         * @return true, if the stopatch info should be reported
108         */
109        @SuppressWarnings("UnusedParameters")
110        protected boolean shouldBeAddedStopwatchInfo(ReporterStopwatchInfo info) {
111                return true;
112        }
113
114        private void addStopwatchInfo(StringBuilder messageBuilder, ReporterStopwatchInfo info) {
115                messageBuilder.append("\n\t").append(info.stopwatch.getName()).append(": ").append(info.splits.size()).
116                        append("x, total: ").append(SimonUtils.presentNanoTime(info.total)).
117                        append(", max: ").append(SimonUtils.presentNanoTime(info.maxSplit.runningFor()));
118                if (info.stopwatch.getNote() != null) {
119                        messageBuilder.append(", note: ").append(SimonUtils.compact(info.stopwatch.getNote(), NOTE_OUTPUT_MAX_LEN));
120                }
121        }
122
123        @Override
124        public void setSimonServletFilter(SimonServletFilter simonServletFilter) {
125                this.simonServletFilter = simonServletFilter;
126        }
127}