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.text.DecimalFormatSymbols;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29
30 import net.bull.javamelody.JdbcWrapper;
31 import net.bull.javamelody.internal.common.I18N;
32 import net.bull.javamelody.internal.model.Collector;
33 import net.bull.javamelody.internal.model.CollectorServer;
34 import net.bull.javamelody.internal.model.Counter;
35 import net.bull.javamelody.internal.model.CounterRequest;
36 import net.bull.javamelody.internal.model.CounterRequestRumData;
37 import net.bull.javamelody.internal.model.DatabaseInformations;
38 import net.bull.javamelody.internal.model.Range;
39
40
44 class HtmlCounterRequestGraphReport extends HtmlAbstractReport {
45 private static final int MAX_REQUEST_NAME_LENGTH = 5000;
46 private static int uniqueByPageAndGraphSequence;
47 private final Range range;
48 private final DecimalFormat systemErrorFormat = I18N.createPercentFormat();
49 private final DecimalFormat nbExecutionsFormat = I18N.createPercentFormat();
50 private final DecimalFormat integerFormat = I18N.createIntegerFormat();
51 private List<Counter> counters;
52 private Map<String, CounterRequest> requestsById;
53
54 HtmlCounterRequestGraphReport(Range range, Writer writer) {
55 super(writer);
56 assert range != null;
57 this.range = range;
58 }
59
60 @Override
61 void toHtml() {
62 throw new UnsupportedOperationException();
63 }
64
65 void writeRequestGraph(String requestId, String requestName) throws IOException {
66 incrementUniqueByPageAndGraphSequence();
67
68 write("<a class='tooltip replaceImage' href='?part=graph&graph=");
69 write(requestId);
70 write("'");
71 final String id = "id" + uniqueByPageAndGraphSequence;
72 write(" data-img-id='" + id + "'");
73 write(" data-img-src='?graph=");
74 write(requestId);
75 write("&width=100&height=50'>");
76
77 write("<em><img src='?resource=db.png' id='");
78 write(id);
79 write("' alt='graph'/></em>");
80 if (requestName.length() <= MAX_REQUEST_NAME_LENGTH) {
81
82 writeDirectly(htmlEncodeRequestName(requestId, requestName));
83 write("</a>");
84 } else {
85
86
87 writeDirectly(htmlEncodeRequestName(requestId,
88 requestName.substring(0, MAX_REQUEST_NAME_LENGTH)));
89 write("</a>");
90 write("<br/> ");
91
92 final String idToShow = "request-" + requestId;
93 writeShowHideLink(idToShow, "#Details#");
94 writeln("<div id='request-" + requestId + "' class='displayNone'>");
95 write("<br/> ");
96 writeDirectly(htmlEncodeRequestName(requestId, requestName));
97 writeln("</div> ");
98 }
99 }
100
101 private static void incrementUniqueByPageAndGraphSequence() {
102 uniqueByPageAndGraphSequence++;
103 }
104
105 private static String htmlEncodeRequestName(String requestId, String requestName) {
106 return HtmlCounterReport.htmlEncodeRequestName(requestId, requestName);
107 }
108
109 void writeRequestAndGraphDetail(Collector collector, CollectorServer collectorServer,
110 String graphName) throws IOException {
111 counters = collector.getRangeCounters(range);
112 requestsById = mapAllRequestsById();
113 final CounterRequest request = requestsById.get(graphName);
114 if (request != null) {
115 if (request.getRumData() != null && request.getRumData().getHits() > 0) {
116 writeRequestRumData(request);
117 }
118 writeRequest(request);
119
120 if (JdbcWrapper.SINGLETON.getSqlCounter().isRequestIdFromThisCounter(graphName)
121 && !request.getName().toLowerCase(Locale.ENGLISH).startsWith("alter ")) {
122
123
124 writeSqlRequestExplainPlan(collector, collectorServer, request);
125 }
126 }
127 if (isGraphDisplayed(collector, request)) {
128 writeln("<table summary=''><tr><td>");
129 writeln("<div id='track' class='noPrint'>");
130 writeln("<div class='selected' id='handle'>");
131 writeln("<img src='?resource=scaler_slider.gif' alt=''/>");
132 writeln("</div></div>");
133 writeln("</td><td>");
134 writeDirectly("<div class='noPrint gray'>");
135 writeln(" ");
136 writeln("<label for='cb'><input id='cb' type='checkbox' /> #hide_maximum#</label>");
137 writeln("</div> ");
138 writeln("</td></tr></table>");
139
140 writeln("<div align='center'>");
141 writeln("<table summary=''><tr><td>");
142 final String graphNameEncoded = urlEncode(graphName);
143 writeln("<img class='synthèse' id='img' data-graph-name='"
144 + htmlEncodeButNotSpace(graphName) + "' src='"
145 + "?width=960&height=400&graph=" + graphNameEncoded + "' alt='zoom'/>");
146 writeDirectly("<br/><div align='right' class='gray'>");
147 writeln("#graph_units#");
148 writeln("</div><div align='right'>");
149 writeln("<a href='?part=lastValue&graph=" + graphNameEncoded
150 + "' title='#Lien_derniere_valeur#'>#derniere_valeur#</a>");
151 writeln(" <a href='?format=xml&period="
152 + range.getValue().replace("|", "%7C") + "&graph=" + graphNameEncoded
153 + "' title='Dump XML'>XML</a>");
154 writeln(" <a href='?format=txt&period="
155 + range.getValue().replace("|", "%7C") + "&graph=" + graphNameEncoded
156 + "' title='Dump TXT'>TXT</a>");
157 writeln("</div></td></tr></table>");
158 writeln("</div>");
159 }
160 if (request != null && request.getStackTrace() != null) {
161 writeln("<blockquote><blockquote><b>Stack-trace</b><br/><font size='-1'>");
162 writeStackTrace(request);
163 writeln("</font></blockquote></blockquote>");
164 }
165 }
166
167 private void writeStackTrace(CounterRequest request) throws IOException {
168 for (final String element : request.getStackTrace().split("[\n\r]")) {
169 if (!element.isEmpty()) {
170
171 writeDirectly(HtmlSourceReport.htmlEncodeStackTraceElementAndTabs(element));
172 writeDirectly("<br/>\n");
173 }
174 }
175 }
176
177 private boolean isGraphDisplayed(Collector collector, CounterRequest request)
178 throws IOException {
179 return request == null || getCounterByRequestId(request) != null
180 && HtmlCounterReport.isRequestGraphDisplayed(getCounterByRequestId(request))
181
182
183 && collector.getJRobin(request.getId()) != null;
184 }
185
186 private void writeSqlRequestExplainPlan(Collector collector, CollectorServer collectorServer,
187 CounterRequest sqlRequest) throws IOException {
188 try {
189 final String explainPlan;
190 if (collectorServer == null) {
191 explainPlan = DatabaseInformations.explainPlanFor(sqlRequest.getName());
192 } else {
193 explainPlan = collectorServer.collectSqlRequestExplainPlan(
194 collector.getApplication(), sqlRequest.getName());
195 }
196
197
198 if (explainPlan != null) {
199 writeln("<b>#Plan_d_execution#</b>");
200 writeln("<div class='explainPlan'>");
201 writeDirectly(explainPlan.replace(" ", " ").replace("\n", "<br/>"));
202 writeln("</div><hr/>");
203 }
204 } catch (final Exception e) {
205 writeln("<b>#Plan_d_execution#</b> ");
206 writeln(e.toString());
207 writeln("<br/>");
208 }
209 }
210
211 void writeRequestUsages(Collector collector, String requestId) throws IOException {
212 assert requestId != null;
213 counters = collector.getRangeCounters(range);
214 CounterRequest myRequest = null;
215 final List<CounterRequest> requests = new ArrayList<>();
216 for (final Counter counter : counters) {
217 for (final CounterRequest request : counter.getOrderedRequests()) {
218 if (myRequest == null && request.getId().equals(requestId)) {
219 myRequest = request;
220 }
221 if (request.containsChildRequest(requestId)) {
222 requests.add(request);
223 }
224 }
225 }
226 writeRequestUsages(myRequest, requests);
227 }
228
229 private void writeRequestUsages(CounterRequest myRequest, List<CounterRequest> requests)
230 throws IOException {
231 writeln("<br/><b>#Utilisations_de#</b>");
232 if (myRequest != null) {
233 writeDirectly(htmlEncodeRequestName(myRequest.getId(), myRequest.getName()));
234 }
235 writeln("<br/><br/>");
236 if (requests.isEmpty()) {
237 writeln("#Aucune_requete#");
238 return;
239 }
240 final boolean someUsagesDisplayed = getUsagesDisplayed(requests);
241 final HtmlTable table = new HtmlTable();
242 table.beginTable(getString("Utilisations_de"));
243 write("<th>#Requete#</th>");
244 if (someUsagesDisplayed) {
245 write("<th class='noPrint'>#Chercher_utilisations#</th>");
246 }
247 for (final CounterRequest request : requests) {
248 table.nextRow();
249 writeUsedRequest(request, someUsagesDisplayed);
250 }
251 table.endTable();
252 }
253
254 private void writeUsedRequest(CounterRequest request, boolean someUsageDisplayed)
255 throws IOException {
256 writeln(" <td>");
257 writeCounterIcon(request);
258 writeRequestGraph(request.getId(), request.getName());
259 if (someUsageDisplayed) {
260 writeln("</td><td align='center' class='noPrint'>");
261 if (doesRequestDisplayUsages(request)) {
262 writeln("<a href='?part=usages&graph=" + request.getId() + "'>");
263 writeln("<img src='?resource=find.png' alt='#Chercher_utilisations#' title='#Chercher_utilisations#'/></a>");
264 } else {
265 writeln(" ");
266 }
267 }
268 writeln("</td>");
269 }
270
271 private boolean getUsagesDisplayed(List<CounterRequest> requests) {
272 for (final CounterRequest request : requests) {
273 if (doesRequestDisplayUsages(request)) {
274 return true;
275 }
276 }
277 return false;
278 }
279
280 private void writeRequestRumData(CounterRequest request) throws IOException {
281 final CounterRequestRumData rumData = request.getRumData();
282 final DecimalFormat percentUsFormat = new DecimalFormat("0.00",
283 DecimalFormatSymbols.getInstance(Locale.US));
284 final DecimalFormat percentLocaleFormat = I18N.createPercentFormat();
285 final int networkTimeMean = rumData.getNetworkTimeMean();
286 final int serverMean = request.getMean();
287 final int domProcessingMean = rumData.getDomProcessingMean();
288 final int pageRenderingMean = rumData.getPageRenderingMean();
289 final int total = networkTimeMean + serverMean + domProcessingMean + pageRenderingMean;
290 final double networkPercent = 100d * networkTimeMean / total;
291 final double serverPercent = 100d * serverMean / total;
292 final double domProcessingPercent = 100d * domProcessingMean / total;
293 final double pageRenderingPercent = 100d * pageRenderingMean / total;
294 writeln("<br/><table class='rumData' summary=''><tr>");
295 writeln("<td class='rumDataNetwork tooltip' data-width-percent='"
296 + percentUsFormat.format(networkPercent) + "'><em>#Network#: "
297 + integerFormat.format(networkTimeMean) + " ms ("
298 + percentLocaleFormat.format(networkPercent) + ")</em>#Network#</td>");
299 writeln("<td class='rumDataServer tooltip' data-width-percent='"
300 + percentUsFormat.format(serverPercent) + "'><em>#Server#: "
301 + integerFormat.format(serverMean) + " ms ("
302 + percentLocaleFormat.format(serverPercent) + "%)</em>#Server#</td>");
303 writeln("<td class='rumDataDomProcessing tooltip' data-width-percent='"
304 + percentUsFormat.format(domProcessingPercent) + "'><em>#DOM_processing#:"
305 + integerFormat.format(domProcessingMean) + " ms ("
306 + percentLocaleFormat.format(domProcessingPercent)
307 + "%)</em>#DOM_processing#</td>");
308 writeln("<td class='rumDataPageRendering tooltip' data-width-percent='"
309 + percentUsFormat.format(pageRenderingPercent) + "'><em>#Page_rendering#:"
310 + integerFormat.format(pageRenderingMean) + " ms ("
311 + percentLocaleFormat.format(pageRenderingPercent)
312 + "%)</em>#Page_rendering#</td>");
313 writeln("</tr></table>");
314 }
315
316 private void writeRequest(CounterRequest request) throws IOException {
317 final Map<String, Long> childRequests = request.getChildRequestsExecutionsByRequestId();
318 writeln(" <br/>");
319 final HtmlTable table = new HtmlTable();
320 table.beginTable(getString("Drill_down"));
321 writeln("<th>#Requete#</th>");
322 final boolean hasChildren = !childRequests.isEmpty();
323 if (hasChildren) {
324 writeln("<th class='sorttable_numeric'>#Hits_par_requete#</th>");
325 }
326 writeln("<th class='sorttable_numeric'>#Temps_moyen#</th><th class='sorttable_numeric'>#Temps_max#</th>");
327 writeln("<th class='sorttable_numeric'>#Ecart_type#</th><th class='sorttable_numeric'>#Temps_cpu_moyen#</th>");
328 final boolean allocatedKBytesDisplayed = request.getAllocatedKBytesMean() >= 0;
329 if (allocatedKBytesDisplayed) {
330 writeln("<th class='sorttable_numeric'>#Ko_alloues_moyens#</th>");
331 }
332 writeln("<th class='sorttable_numeric'>#erreur_systeme#</th>");
333 final Counter parentCounter = getCounterByRequestId(request);
334 final boolean allChildHitsDisplayed = parentCounter != null
335 && parentCounter.getChildCounterName() != null && request.hasChildHits();
336 if (allChildHitsDisplayed) {
337 final String childCounterName = parentCounter.getChildCounterName();
338 writeln("<th class='sorttable_numeric'>"
339 + getFormattedString("hits_fils_moyens", childCounterName));
340 writeln("</th><th class='sorttable_numeric'>"
341 + getFormattedString("temps_fils_moyen", childCounterName) + "</th>");
342 }
343 table.nextRow();
344 write("<td class='wrappedText'>");
345 writeCounterIcon(request);
346 writeDirectly(htmlEncodeRequestName(request.getId(), request.getName()));
347 if (hasChildren) {
348 writeln("</td><td> ");
349 }
350 writeRequestValues(request, allChildHitsDisplayed, allocatedKBytesDisplayed);
351 writeln("</td> ");
352
353 if (hasChildren) {
354 writeChildRequests(request, childRequests, allChildHitsDisplayed,
355 allocatedKBytesDisplayed, table);
356 }
357 table.endTable();
358 if (doesRequestDisplayUsages(request)) {
359 writeln("<div align='right' class='noPrint'>");
360 writeln("<a href='?part=usages&graph=" + request.getId() + "'>");
361 writeln("<img src='?resource=find.png' alt='#Chercher_utilisations#' ");
362 writeln("title='#Chercher_utilisations#'/> #Chercher_utilisations#</a></div>");
363 } else {
364 writeln("<br/>");
365 }
366 }
367
368 private boolean doesRequestDisplayUsages(CounterRequest request) {
369 final Counter parentCounter = getCounterByRequestId(request);
370 return parentCounter != null && !parentCounter.isErrorCounter()
371 && !Counter.HTTP_COUNTER_NAME.equals(parentCounter.getName());
372 }
373
374 private void writeChildRequests(CounterRequest request, Map<String, Long> childRequests,
375 boolean allChildHitsDisplayed, boolean allocatedKBytesDisplayed, HtmlTable table)
376 throws IOException {
377 for (final Map.Entry<String, Long> entry : childRequests.entrySet()) {
378 final CounterRequest childRequest = requestsById.get(entry.getKey());
379 if (childRequest != null) {
380 table.nextRow();
381 final Long nbExecutions = entry.getValue();
382 final float executionsByRequest = (float) nbExecutions / request.getHits();
383 writeChildRequest(childRequest, executionsByRequest, allChildHitsDisplayed,
384 allocatedKBytesDisplayed);
385 }
386 }
387 }
388
389 private void writeChildRequest(CounterRequest childRequest, float executionsByRequest,
390 boolean allChildHitsDisplayed, boolean allocatedKBytesDisplayed) throws IOException {
391 writeln("<td>");
392 writeln("<div class='wrappedText childRequest'>");
393 writeCounterIcon(childRequest);
394 writeRequestGraph(childRequest.getId(), childRequest.getName());
395 writeln("</div></td><td align='right'>");
396 write(nbExecutionsFormat.format(executionsByRequest));
397 writeRequestValues(childRequest, allChildHitsDisplayed, allocatedKBytesDisplayed);
398 writeln("</td>");
399 }
400
401 private void writeRequestValues(CounterRequest request, boolean allChildHitsDisplayed,
402 boolean allocatedKBytesDisplayed) throws IOException {
403 final String nextColumn = "</td><td align='right'>";
404 writeln(nextColumn);
405 writeln(integerFormat.format(request.getMean()));
406 writeln(nextColumn);
407 writeln(integerFormat.format(request.getMaximum()));
408 writeln(nextColumn);
409 writeln(integerFormat.format(request.getStandardDeviation()));
410 writeln(nextColumn);
411 final String nbsp = " ";
412 if (request.getCpuTimeMean() >= 0) {
413 writeln(integerFormat.format(request.getCpuTimeMean()));
414 } else {
415 writeln(nbsp);
416 }
417 if (allocatedKBytesDisplayed) {
418 writeln(nextColumn);
419 if (request.getAllocatedKBytesMean() >= 0) {
420 writeln(integerFormat.format(request.getAllocatedKBytesMean()));
421 } else {
422 writeln(nbsp);
423 }
424 }
425 writeln(nextColumn);
426 writeln(systemErrorFormat.format(request.getSystemErrorPercentage()));
427 if (allChildHitsDisplayed) {
428 writeln(nextColumn);
429 final boolean childHitsDisplayed = request.hasChildHits();
430 if (childHitsDisplayed) {
431 writeln(integerFormat.format(request.getChildHitsMean()));
432 } else {
433 writeln(nbsp);
434 }
435 writeln(nextColumn);
436 if (childHitsDisplayed) {
437 writeln(integerFormat.format(request.getChildDurationsMean()));
438 } else {
439 writeln(nbsp);
440 }
441 }
442 }
443
444 private void writeCounterIcon(CounterRequest request) throws IOException {
445 final Counter parentCounter = getCounterByRequestId(request);
446 if (parentCounter != null && parentCounter.getIconName() != null) {
447 writeln("<img src='?resource=" + parentCounter.getIconName() + "' alt='"
448 + parentCounter.getName() + "' width='16' height='16' /> ");
449 }
450 }
451
452 private Map<String, CounterRequest> mapAllRequestsById() {
453 final Map<String, CounterRequest> result = new HashMap<>();
454 for (final Counter counter : counters) {
455 for (final CounterRequest request : counter.getRequests()) {
456 result.put(request.getId(), request);
457 }
458 }
459 return result;
460 }
461
462 private Counter getCounterByRequestId(CounterRequest request) {
463 final String requestId = request.getId();
464 for (final Counter counter : counters) {
465 if (counter.isRequestIdFromThisCounter(requestId)) {
466 return counter;
467 }
468 }
469 return null;
470 }
471 }
472