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 }