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