1
18 package net.bull.javamelody.internal.web.html;
19
20 import java.io.IOException;
21 import java.io.Writer;
22 import java.text.DecimalFormat;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.regex.Pattern;
27
28 import net.bull.javamelody.internal.common.I18N;
29 import net.bull.javamelody.internal.model.Counter;
30 import net.bull.javamelody.internal.model.CounterRequest;
31 import net.bull.javamelody.internal.model.CounterRequestAggregation;
32 import net.bull.javamelody.internal.model.Period;
33 import net.bull.javamelody.internal.model.Range;
34
35
39 public class HtmlCounterReport extends HtmlAbstractReport {
40 private static final Pattern SQL_KEYWORDS_PATTERN = Pattern.compile(
41 "\\b(select|from|where|order by|group by|update|delete|insert into|values)\\b",
42 Pattern.CASE_INSENSITIVE);
43 private final Counter counter;
44 private final Range range;
45 private final CounterRequestAggregation counterRequestAggregation;
46 private final HtmlCounterRequestGraphReport htmlCounterRequestGraphReport;
47 private final DecimalFormat systemErrorFormat = I18N.createPercentFormat();
48 private final DecimalFormat integerFormat = I18N.createIntegerFormat();
49
50 HtmlCounterReport(Counter counter, Range range, Writer writer) {
51 super(writer);
52 assert counter != null;
53 assert range != null;
54 this.counter = counter;
55 this.range = range;
56 this.counterRequestAggregation = new CounterRequestAggregation(counter);
57 this.htmlCounterRequestGraphReport = new HtmlCounterRequestGraphReport(range, writer);
58 }
59
60 @Override
61 void toHtml() throws IOException {
62 final List<CounterRequest> requests = counterRequestAggregation.getRequests();
63 if (requests.isEmpty()) {
64 writeNoRequests();
65 return;
66 }
67 final String counterName = counter.getName();
68 final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
69
70 if (isErrorAndNotJobCounter()) {
71
72 assert !requests.isEmpty();
73 final List<CounterRequest> summaryRequest = Collections.singletonList(requests.get(0));
74 writeRequests(counterName, counter.getChildCounterName(), summaryRequest, false, true,
75 false);
76 } else {
77 final List<CounterRequest> summaryRequests = Arrays.asList(globalRequest,
78 counterRequestAggregation.getWarningRequest(),
79 counterRequestAggregation.getSevereRequest());
80 writeRequests(globalRequest.getName(), counter.getChildCounterName(), summaryRequests,
81 false, false, false);
82 }
83
84
85 writeSizeAndLinks(requests, globalRequest);
86
87
88 writeln("<div id='details" + counterName + "' class='displayNone'>");
89 writeRequests(counterName, counter.getChildCounterName(), requests,
90 isRequestGraphDisplayed(counter), true, false);
91 writeln("</div>");
92
93
94 if (isErrorCounter()) {
95 writeln("<div id='logs" + counterName + "' class='displayNone'><div>");
96 new HtmlCounterErrorReport(counter, getWriter()).toHtml();
97 writeln("</div></div>");
98 }
99 }
100
101 private void writeSizeAndLinks(List<CounterRequest> requests, CounterRequest globalRequest)
102 throws IOException {
103 final long end;
104 if (range.getEndDate() != null) {
105
106
107 end = Math.min(range.getEndDate().getTime(), System.currentTimeMillis());
108 } else {
109 end = System.currentTimeMillis();
110 }
111
112 final long deltaMillis = Math.max(end - counter.getStartDate().getTime(), 1);
113 final long hitsParMinute = 60 * 1000 * globalRequest.getHits() / deltaMillis;
114 writeln("<div align='right'>");
115
116
117 final String nbKey;
118 if (isJobCounter()) {
119 nbKey = "nb_jobs";
120 } else if (isErrorCounter()) {
121 nbKey = "nb_erreurs";
122 } else {
123 nbKey = "nb_requetes";
124 }
125 writeln(getFormattedString(nbKey, integerFormat.format(hitsParMinute),
126 integerFormat.format(requests.size())));
127 final String separator = " ";
128 if (counter.isBusinessFacadeCounter()) {
129 writeln(separator);
130 writeln("<a href='?part=counterSummaryPerClass&counter=" + counter.getName()
131 + "' class='noPrint'>#Resume_par_classe#</a>");
132 if (isPdfEnabled()) {
133 writeln(separator);
134 writeln("<a href='?part=runtimeDependencies&format=pdf&counter="
135 + counter.getName() + "' class='noPrint'>#Dependencies#</a>");
136 }
137 }
138 writeln(separator);
139 writeShowHideLink("details" + counter.getName(), "#Details#");
140 if (isErrorCounter()) {
141 writeln(separator);
142 writeShowHideLink("logs" + counter.getName(), "#Dernieres_erreurs#");
143 }
144 writeln(separator);
145 if (range.getPeriod() == Period.TOUT) {
146 writeln("<a href='?action=clear_counter&counter=" + counter.getName()
147 + getCsrfTokenUrlPart() + "' title='"
148 + getFormattedString("Vider_stats", counter.getName()) + '\'');
149 writeln("class='confirm noPrint' data-confirm='"
150 + htmlEncodeButNotSpaceAndNewLine(
151 getFormattedString("confirm_vider_stats", counter.getName()))
152 + "'>#Reinitialiser#</a>");
153 }
154 writeln("</div>");
155 }
156
157 private void writeNoRequests() throws IOException {
158 if (isJobCounter()) {
159 writeln("#Aucun_job#");
160 } else if (isErrorCounter()) {
161 writeln("#Aucune_erreur#");
162 } else {
163 writeln("#Aucune_requete#");
164 }
165 }
166
167 private boolean isErrorCounter() {
168 return counter.isErrorCounter();
169 }
170
171 private boolean isJobCounter() {
172 return counter.isJobCounter();
173 }
174
175 private boolean isErrorAndNotJobCounter() {
176 return isErrorCounter() && !isJobCounter();
177 }
178
179 public static boolean isRequestGraphDisplayed(Counter parentCounter) {
180 return !(parentCounter.isErrorCounter() && !parentCounter.isJobCounter())
181 && !parentCounter.isJspOrStrutsCounter();
182 }
183
184 void writeRequestsAggregatedOrFilteredByClassName(String requestId) throws IOException {
185 final List<CounterRequest> requestList = counterRequestAggregation
186 .getRequestsAggregatedOrFilteredByClassName(requestId);
187 final boolean includeSummaryPerClassLink = requestId == null;
188 final boolean includeDetailLink = !includeSummaryPerClassLink;
189 writeRequests(counter.getName(), counter.getChildCounterName(), requestList,
190 includeDetailLink, includeDetailLink, includeSummaryPerClassLink);
191 }
192
193 private void writeRequests(String tableName, String childCounterName,
194 List<CounterRequest> requestList, boolean includeGraph, boolean includeDetailLink,
195 boolean includeSummaryPerClassLink) throws IOException {
196 assert requestList != null;
197 final HtmlTable table = new HtmlTable();
198 table.beginTable(tableName);
199 writeTableHead(childCounterName);
200 for (final CounterRequest request : requestList) {
201 table.nextRow();
202 writeRequest(request, includeGraph, includeDetailLink, includeSummaryPerClassLink);
203 }
204 table.endTable();
205 }
206
207 private void writeTableHead(String childCounterName) throws IOException {
208 if (isJobCounter()) {
209 write("<th>#Job#</th>");
210 } else if (isErrorCounter()) {
211 write("<th>#Erreur#</th>");
212 } else {
213 write("<th>#Requete#</th>");
214 }
215 if (counterRequestAggregation.isTimesDisplayed()) {
216 write("<th class='sorttable_numeric'>#temps_cumule#</th>");
217 write("<th class='sorttable_numeric'>#Hits#</th>");
218 write("<th class='sorttable_numeric'>#Temps_moyen#</th>");
219 write("<th class='sorttable_numeric'>#Temps_max#</th>");
220 write("<th class='sorttable_numeric'>#Ecart_type#</th>");
221 } else {
222 write("<th class='sorttable_numeric'>#Hits#</th>");
223 }
224 if (counterRequestAggregation.isCpuTimesDisplayed()) {
225 write("<th class='sorttable_numeric'>#temps_cpu_cumule#</th>");
226 write("<th class='sorttable_numeric'>#Temps_cpu_moyen#</th>");
227 }
228 if (counterRequestAggregation.isAllocatedKBytesDisplayed()) {
229 write("<th class='sorttable_numeric'>#Ko_alloues_moyens#</th>");
230 }
231 if (!isErrorAndNotJobCounter()) {
232 write("<th class='sorttable_numeric'>#erreur_systeme#</th>");
233 }
234 if (counterRequestAggregation.isResponseSizeDisplayed()) {
235 write("<th class='sorttable_numeric'>#Taille_moyenne#</th>");
236 }
237 if (counterRequestAggregation.isChildHitsDisplayed()) {
238 write("<th class='sorttable_numeric'>"
239 + getFormattedString("hits_fils_moyens", childCounterName));
240 write("</th><th class='sorttable_numeric'>"
241 + getFormattedString("temps_fils_moyen", childCounterName) + "</th>");
242 }
243 }
244
245 private void writeRequest(CounterRequest request, boolean includeGraph,
246 boolean includeDetailLink, boolean includeSummaryPerClassLink) throws IOException {
247 final String nextColumn = "</td> <td align='right'>";
248 write("<td class='wrappedText'>");
249 writeRequestName(request.getId(), request.getName(), includeGraph, includeDetailLink,
250 includeSummaryPerClassLink);
251 final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
252 if (counterRequestAggregation.isTimesDisplayed()) {
253 write(nextColumn);
254 writePercentage(request.getDurationsSum(), globalRequest.getDurationsSum());
255 write(nextColumn);
256 write(integerFormat.format(request.getHits()));
257 write(nextColumn);
258 final int mean = request.getMean();
259 write("<span class='");
260 write(getSlaHtmlClass(mean));
261 write("'>");
262 write(integerFormat.format(mean));
263 write("</span>");
264 write(nextColumn);
265 write(integerFormat.format(request.getMaximum()));
266 write(nextColumn);
267 write(integerFormat.format(request.getStandardDeviation()));
268 } else {
269 write(nextColumn);
270 write(integerFormat.format(request.getHits()));
271 }
272 if (counterRequestAggregation.isCpuTimesDisplayed()) {
273 write(nextColumn);
274 writePercentage(request.getCpuTimeSum(), globalRequest.getCpuTimeSum());
275 write(nextColumn);
276 final int cpuTimeMean = request.getCpuTimeMean();
277 write("<span class='");
278 write(getSlaHtmlClass(cpuTimeMean));
279 write("'>");
280 write(integerFormat.format(cpuTimeMean));
281 write("</span>");
282 }
283 if (counterRequestAggregation.isAllocatedKBytesDisplayed()) {
284 write(nextColumn);
285 final int allocatedKBytesMean = request.getAllocatedKBytesMean();
286 write(integerFormat.format(allocatedKBytesMean));
287 }
288 if (!isErrorAndNotJobCounter()) {
289 write(nextColumn);
290 write(systemErrorFormat.format(request.getSystemErrorPercentage()));
291 }
292 if (counterRequestAggregation.isResponseSizeDisplayed()) {
293 write(nextColumn);
294 write(integerFormat.format(request.getResponseSizeMean() / 1024L));
295 }
296 if (counterRequestAggregation.isChildHitsDisplayed()) {
297 write(nextColumn);
298 write(integerFormat.format(request.getChildHitsMean()));
299 write(nextColumn);
300 write(integerFormat.format(request.getChildDurationsMean()));
301 }
302 write("</td>");
303 }
304
305 void writeRequestName(String requestId, String requestName, boolean includeGraph,
306 boolean includeDetailLink, boolean includeSummaryPerClassLink) throws IOException {
307 if (includeGraph) {
308 assert includeDetailLink;
309 assert !includeSummaryPerClassLink;
310 htmlCounterRequestGraphReport.writeRequestGraph(requestId, requestName);
311 } else if (includeDetailLink) {
312 assert !includeSummaryPerClassLink;
313 write("<a href='?part=graph&graph=");
314 write(requestId);
315 write("'>");
316
317 writeDirectly(htmlEncodeRequestName(requestId, requestName));
318 write("</a>");
319 } else if (includeSummaryPerClassLink) {
320 write("<a href='?part=counterSummaryPerClass&counter=");
321 write(counter.getName());
322 write("&graph=");
323 write(requestId);
324 write("'>");
325
326 writeDirectly(htmlEncodeRequestName(requestId, requestName));
327 write("</a> ");
328 } else {
329
330 writeDirectly(htmlEncodeRequestName(requestId, requestName));
331 }
332 }
333
334 String getSlaHtmlClass(int mean) {
335 final String color;
336 if (mean < counterRequestAggregation.getWarningThreshold() || mean == 0) {
337
338
339 color = "info";
340 } else if (mean < counterRequestAggregation.getSevereThreshold()) {
341
342
343 color = "warning";
344 } else {
345
346
347
348 color = "severe";
349 }
350 return color;
351 }
352
353 private void writePercentage(long dividende, long diviseur) throws IOException {
354 if (diviseur == 0) {
355 write("0");
356 } else {
357 write(integerFormat.format(100 * dividende / diviseur));
358 }
359 }
360
361
368 static String htmlEncodeRequestName(String requestId, String requestName) {
369 if (requestId.startsWith(Counter.SQL_COUNTER_NAME)) {
370 final String htmlEncoded = htmlEncodeButNotSpace(requestName);
371
372 return SQL_KEYWORDS_PATTERN.matcher(htmlEncoded)
373 .replaceAll("<span class='sqlKeyword'>$1</span>");
374 }
375
376 return htmlEncodeButNotSpace(requestName);
377 }
378 }
379