1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.web.html;
19
20 import java.io.IOException;
21 import java.io.PrintWriter;
22 import java.io.StringWriter;
23 import java.io.Writer;
24 import java.net.URLEncoder;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33
34 import net.bull.javamelody.Parameter;
35 import net.bull.javamelody.SessionListener;
36 import net.bull.javamelody.internal.common.I18N;
37 import net.bull.javamelody.internal.common.LOG;
38 import net.bull.javamelody.internal.common.Parameters;
39 import net.bull.javamelody.internal.model.Action;
40 import net.bull.javamelody.internal.model.CacheInformations;
41 import net.bull.javamelody.internal.model.Collector;
42 import net.bull.javamelody.internal.model.CollectorServer;
43 import net.bull.javamelody.internal.model.Counter;
44 import net.bull.javamelody.internal.model.CounterRequestContext;
45 import net.bull.javamelody.internal.model.HsErrPid;
46 import net.bull.javamelody.internal.model.JCacheInformations;
47 import net.bull.javamelody.internal.model.JRobin;
48 import net.bull.javamelody.internal.model.JavaInformations;
49 import net.bull.javamelody.internal.model.JobInformations;
50 import net.bull.javamelody.internal.model.Period;
51 import net.bull.javamelody.internal.model.Range;
52 import net.bull.javamelody.internal.model.ThreadInformations;
53 import net.bull.javamelody.internal.model.UpdateChecker;
54 import net.bull.javamelody.internal.model.VirtualMachine;
55
56 /**
57  * Rapport html principal.
58  * @author Emeric Vernat
59  */

60 class HtmlCoreReport extends HtmlAbstractReport {
61     private static final int MAX_CURRENT_REQUESTS_DISPLAYED_IN_MAIN_REPORT = 500;
62     private static final int MAX_THREADS_DISPLAYED_IN_MAIN_REPORT = 500;
63     private static final String SEPARATOR = "     ";
64     private static final String END_DIV = "</div>";
65     private final Collector collector;
66     private final List<JavaInformations> javaInformationsList;
67     private final Range range;
68     private final CollectorServer collectorServer;
69     private final long start = System.currentTimeMillis();
70     private final Map<String, String> menuTextsByAnchorName = new LinkedHashMap<>();
71
72     HtmlCoreReport(Collector collector, CollectorServer collectorServer,
73             List<JavaInformations> javaInformationsList, Range range, Writer writer) {
74         super(writer);
75         assert collector != null;
76         assert javaInformationsList != null && !javaInformationsList.isEmpty();
77         assert range != null;
78
79         this.collector = collector;
80         this.collectorServer = collectorServer;
81         this.javaInformationsList = javaInformationsList;
82         this.range = range;
83     }
84
85     @Override
86     void toHtml() throws IOException {
87         toHtml(nullnull);
88     }
89
90     void toHtml(String message, String anchorNameForRedirect) throws IOException {
91         writeAlerts();
92         if (collectorServer != null) {
93             writeApplicationsLinks();
94         }
95
96         writeln("<h3 class='chapterTitle'><img src='?resource=systemmonitor.png' alt='#Stats#'/>");
97         writeAnchor("top", I18N.getString("Stats"));
98         writeSummary();
99         writeln("</h3>");
100         write("<a class='donate' href='https://github.com/javamelody/javamelody/wiki/Donate'>");
101         writeln("<img class='noPrint' src='?resource=donate.gif' alt='Donate' /></a>");
102         writeln("<div align='center'>");
103         writeRefreshAndPeriodLinks(nullnull);
104         writeGraphs();
105         writeln(END_DIV);
106
107         final List<Counter> counters = collector.getRangeCountersToBeDisplayed(range);
108         final Map<String, HtmlCounterReport> counterReportsByCounterName = writeCounters(counters);
109
110         if (collectorServer == null) {
111             writeln("<h3 class='chapterTitle'><img src='?resource=hourglass.png' alt='#Requetes_en_cours#'/>");
112             writeAnchor("currentRequests", I18N.getString("Requetes_en_cours"));
113             writeln("#Requetes_en_cours#</h3>");
114             // si on n'est pas sur le serveur de collecte il n'y a qu'un javaInformations
115             writeCurrentRequests(javaInformationsList.get(0), counters,
116                     counterReportsByCounterName);
117         }
118
119         writeln("<h3 class='chapterTitle'><img src='?resource=systeminfo.png' alt='#Informations_systemes#'/>");
120         writeAnchor("systeminfo", I18N.getString("Informations_systemes"));
121         writeln("#Informations_systemes#</h3>");
122         if (collectorServer != null) {
123             writeln("<div align='center' class='noPrint'><a href='?part=currentRequests'>");
124             writeln("<img src='?resource=hourglass.png' width='20' height='20' alt=\"#Voir_requetes_en_cours#\" /> #Voir_requetes_en_cours#</a>");
125             writeln(END_DIV);
126             writeln("<br/>");
127         }
128         if (Parameters.isSystemActionsEnabled()) {
129             writeSystemActionsLinks();
130         }
131
132         new HtmlJavaInformationsReport(javaInformationsList, getWriter()).toHtml();
133
134         writeln("<h3 class='chapterTitle'><img src='?resource=threads.png' alt='#Threads#'/>");
135         writeAnchor("threads", I18N.getString("Threads"));
136         writeln("#Threads#</h3>");
137         writeThreads();
138
139         if (isJobEnabled()) {
140             writeln("<h3 class='chapterTitle'><img src='?resource=jobs.png' alt='#Jobs#'/>");
141             writeAnchor("jobs", I18N.getString("Jobs"));
142             writeln("#Jobs#</h3>");
143             writeJobs(collector.getRangeCounter(range, Counter.JOB_COUNTER_NAME));
144         }
145
146         if (isCacheEnabled()) {
147             writeln("<h3 class='chapterTitle'><img src='?resource=caches.png' alt='#Caches#'/>");
148             writeAnchor("caches", I18N.getString("Caches"));
149             writeln("#Caches#</h3>");
150             writeCaches();
151         }
152         if (isJCacheEnabled()) {
153             writeln("<h3 class='chapterTitle'><img src='?resource=caches.png' alt='#Caches#'/>");
154             writeAnchor("caches", I18N.getString("Caches"));
155             writeln("#Caches#</h3>");
156             writeJCaches();
157         }
158         //        else if (JavaInformations.STACK_TRACES_ENABLED) {
159         //            // pour que les tooltips des stack traces s'affichent dans le scroll
160         //            writeln("<br/><br/><br/><br/>");
161         //        }
162
163         writeMenu();
164
165         writeMessageIfNotNull(message, null, anchorNameForRedirect);
166         writeDurationAndOverhead();
167     }
168
169     private void writeAlerts() throws IOException {
170         final String newJavamelodyVersion = UpdateChecker.getNewJavamelodyVersion();
171         if (newJavamelodyVersion != null) {
172             writeln("<div class='newVersion' align='center'>");
173             writeln("<img src='?resource=alert.png' alt='alert'/>");
174             writeDirectly(I18N.getFormattedString("version_alert", newJavamelodyVersion,
175                     Parameters.JAVAMELODY_VERSION));
176             writeln("</div>");
177         }
178         final Throwable lastCollectorException = collector.getLastCollectorException();
179         if (lastCollectorException != null) {
180             writeln("<div class='alert'>");
181             writeln("<img src='?resource=alert.png' alt='alert'/>");
182             writeDirectly(htmlEncodeButNotSpace(lastCollectorException.toString()));
183             writeln("</div>");
184             writeShowHideLink("detailsLastCollectorException""#Details#");
185             writeln("<div id='detailsLastCollectorException' class='displayNone'><div>");
186             final StringWriter stackTraceWriter = new StringWriter(200);
187             lastCollectorException.printStackTrace(new PrintWriter(stackTraceWriter));
188             for (final String stackTraceElement : stackTraceWriter.toString().split("[\n\r]")) {
189                 if (!stackTraceElement.isEmpty()) {
190                     // writeDirectly pour ne pas gérer de traductions car les liens contiennent '#'
191                     writeDirectly(
192                             HtmlSourceReport.htmlEncodeStackTraceElementAndTabs(stackTraceElement));
193                     writeDirectly("<br/>\n");
194                 }
195             }
196             writeln("</div></div>");
197         }
198         if (collectorServer != null
199                 && collectorServer.isApplicationDataAvailable(collector.getApplication())
200                 && javaInformationsList.size() < collectorServer
201                         .getUrlsByApplication(collector.getApplication()).size()) {
202             writeln("<div class='alert'>");
203             writeln("<img src='?resource=alert.png' alt='alert'/> #some_node_unavailable#");
204             writeln("</div><br/>");
205         }
206     }
207
208     private void writeSummary() throws IOException {
209         final String javaMelodyUrl = "<a href='https://github.com/javamelody/javamelody/wiki' target='_blank'>JavaMelody</a>";
210         if (range.getPeriod() == Period.TOUT) {
211             final String startDate = I18N.createDateAndTimeFormat()
212                     .format(collector.getCounters().get(0).getStartDate());
213             writeDirectly(getFormattedString("Statistiques", javaMelodyUrl,
214                     I18N.getCurrentDateAndTime(), startDate, collector.getApplication()));
215         } else {
216             writeDirectly(getFormattedString("Statistiques_sans_depuis", javaMelodyUrl,
217                     I18N.getCurrentDateAndTime(), collector.getApplication()));
218         }
219         final String contextDisplayName = javaInformationsList.get(0).getContextDisplayName();
220         final String webappVersion = javaInformationsList.get(0).getWebappVersion();
221         if (contextDisplayName != null) {
222             writeDirectly(" (");
223             writeDirectly(htmlEncodeButNotSpace(contextDisplayName));
224             if (webappVersion != null) {
225                 writeDirectly(", " + htmlEncodeButNotSpace(webappVersion));
226             }
227             writeDirectly(")");
228         } else if (webappVersion != null) {
229             writeDirectly(" (");
230             writeDirectly(htmlEncodeButNotSpace(webappVersion));
231             writeDirectly(")");
232         }
233         writeln("");
234     }
235
236     private void writeAnchor(String anchorName, String menuText) throws IOException {
237         write("<a name='" + anchorName + "'></a>");
238         menuTextsByAnchorName.put(anchorName, menuText);
239     }
240
241     private void writeMenu() throws IOException {
242         writeln("<div class='noPrint'> ");
243         writeln("<div id='menuBox' class='menuHide'>");
244         writeln("  <ul id='menuTab'><li><a class='menuBoxToggle'><img id='menuToggle' src='?resource=menu.png' alt='menu' /></a></li></ul>");
245         writeln("  <div id='menuLinks'><div id='menuDeco'>");
246         for (final Map.Entry<String, String> entry : menuTextsByAnchorName.entrySet()) {
247             final String anchorName = entry.getKey();
248             final String menuText = entry.getValue();
249             writeDirectly("    <div class='menuButton'><a href='#" + anchorName + "'>" + menuText
250                     + "</a></div>");
251             writeln("");
252         }
253         final String customReports = Parameter.CUSTOM_REPORTS.getValue();
254         if (customReports != null) {
255             for (final String customReport : customReports.split(",")) {
256                 final String customReportName = customReport.trim();
257                 final String customReportPath = Parameters
258                         .getParameterValueByName(customReportName);
259                 if (customReportPath == null) {
260                     LOG.debug("Parameter not defined in web.xml for custom report: "
261                             + customReportName);
262                     continue;
263                 }
264                 writeDirectly("    <div class='menuButton'><a href='?report="
265                         + URLEncoder.encode(customReportName, "UTF-8") + "'>"
266                         + htmlEncode(customReportName) + "</a></div>");
267                 writeln("");
268             }
269         }
270         if (SessionListener.getCurrentSession() != null) {
271             writeDirectly("    <div class='menuButton'><a href='?action=logout"
272                     + getCsrfTokenUrlPart() + "'>" + I18N.getString("logout") + "</a></div>");
273             writeln("");
274         }
275         writeln("  </div></div>");
276         writeln("</div></div>");
277     }
278
279     private Map<String, HtmlCounterReport> writeCounters(List<Counter> counters)
280             throws IOException {
281         final Map<String, HtmlCounterReport> counterReportsByCounterName = new HashMap<>();
282         for (final Counter counter : counters) {
283             final HtmlCounterReport htmlCounterReport = writeCounter(counter);
284             counterReportsByCounterName.put(counter.getName(), htmlCounterReport);
285         }
286
287         if (range.getPeriod() == Period.TOUT && counterReportsByCounterName.size() > 1) {
288             writeln("<div align='right'>");
289             writeln("<a href='?action=clear_counter&amp;counter=all" + getCsrfTokenUrlPart()
290                     + "' title='#Vider_toutes_stats#'");
291             writeln("class='confirm noPrint' data-confirm='"
292                     + htmlEncodeButNotSpaceAndNewLine(getString("confirm_vider_toutes_stats"))
293                     + "'>#Reinitialiser_toutes_stats#</a>");
294             writeln(END_DIV);
295         }
296
297         return counterReportsByCounterName;
298     }
299
300     private HtmlCounterReport writeCounter(Counter counter) throws IOException {
301         writeCounterTitle(counter);
302         final HtmlCounterReport htmlCounterReport = new HtmlCounterReport(counter, range,
303                 getWriter());
304         htmlCounterReport.toHtml();
305         return htmlCounterReport;
306     }
307
308     private void writeCounterTitle(Counter counter) throws IOException {
309         writeln("<h3 class='chapterTitle'><img src='?resource=" + counter.getIconName() + "' alt='"
310                 + counter.getName() + "'/>");
311         writeAnchor(counter.getName(),
312                 I18N.getString("Stats") + ' ' + counter.getName().toLowerCase(Locale.ENGLISH));
313         final String counterLabel = getString(counter.getName() + "Label");
314         write(getFormattedString("Statistiques_compteur", counterLabel));
315         write(" - " + range.getLabel());
316         if (range.getPeriod() != Period.TOUT) {
317             write(" #depuis_minuit#");
318         }
319         writeln("</h3>");
320     }
321
322     static void writeAddAndRemoveApplicationLinks(String currentApplication,
323             Collection<String> applications, Writer writer) throws IOException {
324         new HtmlForms(writer).writeAddAndRemoveApplicationLinks(currentApplication, applications);
325     }
326
327     void writeMessageIfNotNull(String message, String partToRedirectTo,
328             String anchorNameForRedirect) throws IOException {
329         if (message != null) {
330             final String href;
331             if (partToRedirectTo == null) {
332                 href = anchorNameForRedirect == null ? "?" : "?#" + anchorNameForRedirect;
333             } else {
334                 href = "?part=" + partToRedirectTo;
335             }
336             writeDirectly("<span class='alertAndRedirect' data-alert='"
337                     + htmlEncodeButNotSpaceAndNewLine(message) + "' data-href='" + href
338                     + "'></span>");
339         }
340     }
341
342     private void writeGraphs() throws IOException {
343         if (collector.isStopped()) {
344             // pas de graphs, ils seraient en erreur sans timer
345             // mais un message d'avertissement à la place
346             writeln("<div align='center' class='severe'><br/><br/>");
347             writeln("#collect_server_misusage#");
348             writeln("</div>");
349             return;
350         }
351         if (collector.isStorageUsedByMultipleInstances()) {
352             writeln("<div align='center' class='severe'><br/><br/>");
353             writeln("#storage_used_by_multiple_instances#");
354             writeln("</div><br/>");
355         }
356
357         writeGraphs(collector.getDisplayedCounterJRobins(), false);
358         final Collection<JRobin> otherJRobins = collector.getDisplayedOtherJRobins();
359         if (!otherJRobins.isEmpty()) {
360             writeln("<div align='right'>");
361             writeShowHideLink("detailsGraphs""#Autres_courbes#");
362             writeln(END_DIV);
363             writeln("<div id='detailsGraphs' class='displayNone'><div>");
364             writeGraphs(otherJRobins, true);
365             writeln("</div></div>");
366         }
367     }
368
369     private void writeGraphs(Collection<JRobin> jrobins, boolean lazyGraphs) throws IOException {
370         int i = 0;
371         for (final JRobin jrobin : jrobins) {
372             final String jrobinName = jrobin.getName();
373             write("<a href='?part=graph&amp;graph=" + jrobinName + "'><img class='synthese' ");
374             if (lazyGraphs) {
375                 write("data-src");
376             } else {
377                 write("src");
378             }
379             writeln("='?width=200&amp;height=" + JRobin.SMALL_HEIGHT + "&amp;graph=" + jrobinName
380                     + "' alt=\"" + jrobin.getLabel() + "\" title=\"" + jrobin.getLabel()
381                     + "\"/></a>");
382             i++;
383             if (i % 3 == 0) {
384                 // un <br/> après httpSessions et avant activeThreads pour l'alignement
385                 writeln("<br/>");
386             }
387         }
388     }
389
390     private void writeCurrentRequests(JavaInformations javaInformations, List<Counter> counters,
391             Map<String, HtmlCounterReport> counterReportsByCounterName) throws IOException {
392         final List<ThreadInformations> threadInformationsList = javaInformations
393                 .getThreadInformationsList();
394         final boolean stackTraceEnabled = javaInformations.isStackTraceEnabled();
395         final List<CounterRequestContext> rootCurrentContexts = collector
396                 .getRootCurrentContexts(counters);
397         writeCurrentRequests(threadInformationsList, rootCurrentContexts, stackTraceEnabled,
398                 MAX_CURRENT_REQUESTS_DISPLAYED_IN_MAIN_REPORT, false, counterReportsByCounterName);
399     }
400
401     void writeAllCurrentRequestsAsPart(
402             Map<JavaInformations, List<CounterRequestContext>> currentRequests) throws IOException {
403         writeln("<div  class='noPrint'>");
404         writeln("<a class='back' href=''><img src='?resource=action_back.png' alt='#Retour#'/> #Retour#</a>");
405         writeln(SEPARATOR);
406         writeln("<a href='?part=currentRequests'><img src='?resource=action_refresh.png' alt='#Actualiser#'/> #Actualiser#</a>");
407         if (isPdfEnabled()) {
408             writeln(SEPARATOR);
409             write("<a href='?part=currentRequests&amp;format=pdf' title='#afficher_PDF#'>");
410             write("<img src='?resource=pdf.png' alt='#PDF#'/> #PDF#</a>");
411         }
412         writeln("</div><br/>");
413         for (final Map.Entry<JavaInformations, List<CounterRequestContext>> entry : currentRequests
414                 .entrySet()) {
415             final JavaInformations javaInformations = entry.getKey();
416             final List<CounterRequestContext> rootCurrentContexts = entry.getValue();
417
418             final List<ThreadInformations> threadInformationsList = javaInformations
419                     .getThreadInformationsList();
420             final boolean stackTraceEnabled = javaInformations.isStackTraceEnabled();
421             writeCurrentRequests(threadInformationsList, rootCurrentContexts, stackTraceEnabled,
422                     Integer.MAX_VALUE, truenull);
423             write("<br/><br/>");
424         }
425     }
426
427     private void writeCurrentRequests(List<ThreadInformations> threadInformationsList,
428             List<CounterRequestContext> rootCurrentContexts, boolean stackTraceEnabled,
429             int maxContextsDisplayed, boolean onlyTitleAndDetails,
430             Map<String, HtmlCounterReport> counterReportsByCounterName) throws IOException {
431         // counterReportsByCounterName peut être null
432         final HtmlCounterRequestContextReport htmlCounterRequestContextReport = new HtmlCounterRequestContextReport(
433                 rootCurrentContexts, counterReportsByCounterName, threadInformationsList,
434                 stackTraceEnabled, maxContextsDisplayed, getWriter());
435         if (onlyTitleAndDetails) {
436             htmlCounterRequestContextReport.writeTitleAndDetails();
437         } else {
438             htmlCounterRequestContextReport.toHtml();
439         }
440     }
441
442     void writeAllThreadsAsPart() throws IOException {
443         final String separator = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
444         writeln("<div class='noPrint'>");
445         writeln("<a class='back' href=''><img src='?resource=action_back.png' alt='#Retour#'/> #Retour#</a>");
446         writeln(separator);
447         writeln("<a href='?part=threads'><img src='?resource=action_refresh.png' alt='#Actualiser#'/> #Actualiser#</a>");
448         if (isPdfEnabled()) {
449             writeln("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
450             write("<a href='?part=threads&amp;format=pdf' title='#afficher_PDF#'>");
451             write("<img src='?resource=pdf.png' alt='#PDF#'/> #PDF#</a>");
452         }
453         for (final JavaInformations javaInformations : javaInformationsList) {
454             if (javaInformations.isStackTraceEnabled()) {
455                 writeln(separator);
456                 writeln("<a href='?part=threadsDump'><img src='?resource=text.png' alt='#Dump_threads_en_texte#'/>&nbsp;#Dump_threads_en_texte#</a>");
457                 break;
458             }
459         }
460
461         writeln("</div> <br/>");
462         writeTitle("threads.png", getString("Threads"));
463         write(" <br/>");
464
465         for (final JavaInformations javaInformations : javaInformationsList) {
466             write(" <b>");
467             writeDirectly(getFormattedString("Threads_sur", javaInformations.getHost()));
468             write(": </b>");
469             writeln(getFormattedString("thread_count", javaInformations.getThreadCount(),
470                     javaInformations.getPeakThreadCount(),
471                     javaInformations.getTotalStartedThreadCount()));
472             final HtmlThreadInformationsReport htmlThreadInformationsReport = new HtmlThreadInformationsReport(
473                     javaInformations.getThreadInformationsList(),
474                     javaInformations.isStackTraceEnabled(), getWriter());
475             htmlThreadInformationsReport.writeDeadlocks();
476             writeln("<br/><br/>");
477             htmlThreadInformationsReport.toHtml();
478         }
479     }
480
481     void writeThreadsDump() throws IOException {
482         writeDirectly(I18N.getCurrentDateAndTime());
483         writeDirectly("\n\n");
484         for (final JavaInformations javaInformations : javaInformationsList) {
485             writeDirectly("===== " + getFormattedString("Threads_sur", javaInformations.getHost())
486                     + " =====");
487             writeDirectly("\n\n");
488             final HtmlThreadInformationsReport htmlThreadInformationsReport = new HtmlThreadInformationsReport(
489                     javaInformations.getThreadInformationsList(),
490                     javaInformations.isStackTraceEnabled(), getWriter());
491             htmlThreadInformationsReport.writeThreadsDump();
492         }
493     }
494
495     private void writeThreads() throws IOException {
496         int i = 0;
497         for (final JavaInformations javaInformations : javaInformationsList) {
498             write("<b>");
499             writeDirectly(getFormattedString("Threads_sur", javaInformations.getHost()));
500             write(": </b>");
501             writeln(getFormattedString("thread_count", javaInformations.getThreadCount(),
502                     javaInformations.getPeakThreadCount(),
503                     javaInformations.getTotalStartedThreadCount()));
504             writeln(SEPARATOR);
505             final List<ThreadInformations> threadInformationsList = javaInformations
506                     .getThreadInformationsList();
507             final HtmlThreadInformationsReport htmlThreadInformationsReport = new HtmlThreadInformationsReport(
508                     threadInformationsList, javaInformations.isStackTraceEnabled(), getWriter());
509             if (threadInformationsList.size() <= MAX_THREADS_DISPLAYED_IN_MAIN_REPORT) {
510                 final String id = "threads_" + i;
511                 writeShowHideLink(id, "#Details#");
512                 htmlThreadInformationsReport.writeDeadlocks();
513                 writeln("<br/><br/><div id='" + id + "' class='displayNone'>");
514                 htmlThreadInformationsReport.toHtml();
515
516                 writeln("<div align='right' class='noPrint'><br/>");
517                 if (javaInformations.isStackTraceEnabled()) {
518                     writeln("<a href='?part=threadsDump'><img src='?resource=text.png' alt='#Dump_threads_en_texte#'/>&nbsp;#Dump_threads_en_texte#</a>");
519                 }
520                 writeln(SEPARATOR);
521                 writeln("<a href='?part=threads'><img src='?resource=threads.png' alt='#Threads#' width='16' height='16'/>&nbsp;#Voir_dans_une_nouvelle_page#</a>");
522                 writeln("</div>");
523
524                 writeln("</div><br/>");
525             } else {
526                 // le nombre de threads dépasse le maximum pour être affiché dans le rapport
527                 // principal, donc on affiche seulement un lien vers la page à part
528                 writeln("<a href='?part=threads' class='noPrint'>#Details#</a><br/>");
529             }
530             i++;
531         }
532     }
533
534     void writeCounterSummaryPerClass(String counterName, String requestId) throws IOException {
535         final Counter counter = collector.getRangeCounter(range, counterName);
536         writeln("<div class='noPrint'>");
537         writeln("<a class='back' href=''><img src='?resource=action_back.png' alt='#Retour#'/> #Retour#</a>");
538         writeln(SEPARATOR);
539         final String hrefStart = "<a href='?part=counterSummaryPerClass&amp;counter="
540                 + counter.getName()
541                 + (requestId == null ? "" : "&amp;graph=" + urlEncode(requestId));
542         writeln(hrefStart + "'>");
543         writeln("<img src='?resource=action_refresh.png' alt='#Actualiser#'/> #Actualiser#</a>");
544
545         if (isPdfEnabled()) {
546             writeln(SEPARATOR);
547             write(hrefStart);
548             writeln("&amp;format=pdf' title='#afficher_PDF#'>");
549             write("<img src='?resource=pdf.png' alt='#PDF#'/> #PDF#</a>");
550         }
551         writeln("</div>");
552
553         writeCounterTitle(counter);
554         final HtmlCounterReport htmlCounterReport = new HtmlCounterReport(counter, range,
555                 getWriter());
556         htmlCounterReport.writeRequestsAggregatedOrFilteredByClassName(requestId);
557     }
558
559     private boolean isGcEnabled() {
560         return Action.GC_ENABLED || collectorServer != null;
561     }
562
563     private boolean isHeapHistoEnabled() {
564         return collectorServer != null || VirtualMachine.isEnabled();
565     }
566
567     private boolean isSamplingEnabled() {
568         return collectorServer != null || collector.getSamplingProfiler() != null;
569     }
570
571     private boolean isDatabaseEnabled() {
572         return !Parameters.isNoDatabase()
573                 && javaInformationsList.get(0).getDataBaseVersion() != null
574                 && !javaInformationsList.get(0).getDataBaseVersion().contains("Exception");
575     }
576
577     private boolean doesWebXmlExists() {
578         return javaInformationsList.get(0).doesWebXmlExists();
579     }
580
581     private boolean isSessionsEnabled() {
582         return javaInformationsList.get(0).getSessionCount() >= 0;
583     }
584
585     private boolean isSpringBeansEnabled() {
586         return javaInformationsList.get(0).isSpringBeansEnabled();
587     }
588
589     private boolean isCacheEnabled() {
590         for (final JavaInformations javaInformations : javaInformationsList) {
591             if (javaInformations.isCacheEnabled()) {
592                 return true;
593             }
594         }
595         return false;
596     }
597
598     private boolean isJCacheEnabled() {
599         for (final JavaInformations javaInformations : javaInformationsList) {
600             if (javaInformations.isJCacheEnabled()) {
601                 return true;
602             }
603         }
604         return false;
605     }
606
607     private boolean isJobEnabled() {
608         for (final JavaInformations javaInformations : javaInformationsList) {
609             if (javaInformations.isJobEnabled()) {
610                 return true;
611             }
612         }
613         return false;
614     }
615
616     List<HsErrPid> getHsErrPidList() {
617         return HsErrPid.getHsErrPidList(javaInformationsList);
618     }
619
620     private void writeCaches() throws IOException {
621         int i = 0;
622         for (final JavaInformations javaInformations : javaInformationsList) {
623             if (!javaInformations.isCacheEnabled()) {
624                 continue;
625             }
626             final List<CacheInformations> cacheInformationsList = javaInformations
627                     .getCacheInformationsList();
628             writeln("<b>");
629             writeln(getFormattedString("caches_sur", cacheInformationsList.size(),
630                     javaInformations.getHost()));
631             writeln("</b>");
632             writeln(SEPARATOR);
633             final String id = "caches_" + i;
634             writeShowHideLink(id, "#Details#");
635             writeln("<br/><br/><div id='" + id + "' class='displayNone'><div>");
636             new HtmlCacheInformationsReport(cacheInformationsList, getWriter()).toHtml();
637             writeln("</div></div><br/>");
638             i++;
639         }
640     }
641
642     private void writeJCaches() throws IOException {
643         int i = 0;
644         for (final JavaInformations javaInformations : javaInformationsList) {
645             if (!javaInformations.isJCacheEnabled()) {
646                 continue;
647             }
648             final List<JCacheInformations> jcacheInformationsList = javaInformations
649                     .getJCacheInformationsList();
650             writeln("<b>");
651             writeln(getFormattedString("caches_sur", jcacheInformationsList.size(),
652                     javaInformations.getHost()));
653             writeln("</b>");
654             writeln(SEPARATOR);
655             final String id = "jcaches_" + i;
656             writeShowHideLink(id, "#Details#");
657             writeln("<br/><br/><div id='" + id + "' class='displayNone'><div>");
658             new HtmlJCacheInformationsReport(jcacheInformationsList, getWriter()).toHtml();
659             writeln("</div></div><br/>");
660             i++;
661         }
662     }
663
664     private void writeJobs(Counter rangeJobCounter) throws IOException {
665         int i = 0;
666         for (final JavaInformations javaInformations : javaInformationsList) {
667             if (!javaInformations.isJobEnabled()) {
668                 continue;
669             }
670             final List<JobInformations> jobInformationsList = javaInformations
671                     .getJobInformationsList();
672             writeln("<b>");
673             writeln(getFormattedString("jobs_sur", jobInformationsList.size(),
674                     javaInformations.getHost(), javaInformations.getCurrentlyExecutingJobCount()));
675             writeln("</b>");
676             writeln(SEPARATOR);
677             final String id = "job_" + i;
678             writeShowHideLink(id, "#Details#");
679             writeln("<br/><br/><div id='" + id + "' class='displayNone'><div>");
680             new HtmlJobInformationsReport(jobInformationsList, rangeJobCounter, getWriter())
681                     .toHtml();
682             writeln("</div></div><br/>");
683             i++;
684         }
685
686         writeCounter(rangeJobCounter);
687     }
688
689     // CHECKSTYLE:OFF
690     private void writeSystemActionsLinks() throws IOException { // NOPMD
691         // CHECKSTYLE:ON
692         writeln("<div align='center' class='noPrint'>");
693         final String separator = "&nbsp;&nbsp;&nbsp;&nbsp;";
694         if (isGcEnabled()) {
695             write("<a href='?action=gc" + getCsrfTokenUrlPart() + "' class='confirm' data-confirm='"
696                     + htmlEncodeButNotSpaceAndNewLine(getString("confirm_ramasse_miette")) + "'>");
697         } else {
698             write("<a href='?action=gc" + getCsrfTokenUrlPart()
699                     + "' class='alertAndStop' data-alert='"
700                     + htmlEncodeButNotSpaceAndNewLine(getString("ramasse_miette_desactive"))
701                     + "'>");
702         }
703         write("<img src='?resource=broom.png' width='20' height='20' alt='#ramasse_miette#' /> #ramasse_miette#</a>");
704         writeln(separator);
705         write("<a href='?action=heap_dump" + getCsrfTokenUrlPart()
706                 + "' class='confirm' data-confirm='"
707                 + htmlEncodeButNotSpaceAndNewLine(getString("confirm_heap_dump")) + "'>");
708         write("<img src='?resource=heapdump.png' width='20' height='20' alt=\"#heap_dump#\" /> #heap_dump#</a>");
709         writeln(separator);
710         if (isHeapHistoEnabled()) {
711             write("<a href='?part=heaphisto'>");
712             write("<img src='?resource=memory.png' width='20' height='20' alt=\"#heaphisto#\" /> #heaphisto#</a>");
713             writeln(separator);
714         }
715         if (isSessionsEnabled()) {
716             write("<a href='?action=invalidate_sessions" + getCsrfTokenUrlPart()
717                     + "' class='confirm' data-confirm='"
718                     + htmlEncodeButNotSpaceAndNewLine(getString("confirm_invalidate_sessions"))
719                     + "'>");
720             write("<img src='?resource=user-trash.png' width='18' height='18' alt=\"#invalidate_sessions#\" /> #invalidate_sessions#</a>");
721             writeln(separator);
722             write("<a href='?part=sessions'>");
723             writeln("<img src='?resource=system-users.png' width='20' height='20' alt=\"#sessions#\" /> #sessions#</a>");
724         }
725         if (isSamplingEnabled()) {
726             writeln(separator);
727             write("<a href='?part=hotspots'>");
728             writeln("<img src='?resource=clock.png' width='20' height='20' alt=\"#hotspots#\" /> #hotspots#</a>");
729         }
730
731         writeln("<br />");
732         if (doesWebXmlExists()) {
733             // on n'affiche le lien web.xml que si le fichier existe (pour api servlet 3.0 par ex)
734             writeln(separator);
735             write("<a href='?part=web.xml'>");
736             write("<img src='?resource=xml.png' width='20' height='20' alt=\"#web.xml#\" /> #web.xml#</a>");
737         }
738
739         writeln(separator);
740         write("<a href='?part=mbeans'>");
741         write("<img src='?resource=mbeans.png' width='20' height='20' alt=\"#MBeans#\" /> #MBeans#</a>");
742
743         writeln(separator);
744         write("<a href='?part=processes'>");
745         write("<img src='?resource=processes.png' width='20' height='20' alt=\"#processes#\" /> #processes#</a>");
746
747         final String serverInfo = javaInformationsList.get(0).getServerInfo();
748         if (serverInfo != null && !serverInfo.contains("Winstone")) {
749             // on n'affiche pas le lien JNDI si serveur Winstone car cela n'a pas d'intérêt
750             // pour Jenkins sous Winstone, et surtout car (Winstone)Context.listBindings
751             // renvoie une liste de NameClassPair au lieu d'une liste de Binding comme il le devrait
752             writeln(separator);
753             write("<a href='?part=jndi'>");
754             write("<img src='?resource=jndi.png' width='20' height='20' alt=\"#Arbre_JNDI#\" /> #Arbre_JNDI#</a>");
755         }
756
757         if (isSpringBeansEnabled()) {
758             writeln(separator);
759             write("<a href='?part=springBeans'>");
760             write("<img src='?resource=beans.png' width='20' height='20' alt=\"#Spring_beans#\" /> #Spring_beans#</a>");
761         }
762
763         if (isDatabaseEnabled()) {
764             writeln(separator);
765             write("<a href='?part=connections'>");
766             write("<img src='?resource=db.png' width='20' height='20' alt=\"#Connexions_jdbc_ouvertes#\" /> #Connexions_jdbc_ouvertes#</a>");
767
768             writeln(separator);
769             write("<a href='?part=database'>");
770             writeln("<img src='?resource=db.png' width='20' height='20' alt=\"#database#\" /> #database#</a>");
771         }
772
773         final int hsErrPidCount = getHsErrPidList().size();
774         if (hsErrPidCount > 0) {
775             writeln(separator);
776             write("<a href='?part=crashes'>");
777             write("<img src='?resource=alert.png' width='20' height='20' alt=\"#Crashes# ("
778                     + hsErrPidCount + ")\" /> #Crashes# (" + hsErrPidCount + ")</a>");
779         }
780
781         writeln("<br/></div>");
782     }
783
784     private void writeApplicationsLinks() throws IOException {
785         assert collectorServer != null;
786         writeln("<div align='center'>");
787         final Collection<String> applications = new ArrayList<>(
788                 Parameters.getCollectorUrlsByApplications().keySet());
789         applications.addAll(Parameters.getApplicationsByAggregationApplication().keySet());
790         if (applications.size() > 1
791                 || !collectorServer.getLastCollectExceptionsByApplication().isEmpty()) {
792             final boolean tabularList = applications.size() > 10;
793             if (tabularList) {
794                 writeln("<table summary='applications'><tr><td>");
795                 writeShowHideLink("chooseApplication""#Choix_application#");
796                 if (Parameters.getCollectorApplicationsFile().canWrite()) {
797                     writeAddAndRemoveApplicationLinks(collector.getApplication(), applications,
798                             getWriter());
799                 }
800                 writeln("<div id='chooseApplication' class='displayNone'><div>&nbsp;&nbsp;&nbsp;");
801                 writeApplicationsLinks(applications, tabularList);
802                 writeln("</div></div></td></tr></table>");
803             } else {
804                 writeln("&nbsp;&nbsp;&nbsp;#Choix_application# :&nbsp;&nbsp;&nbsp;");
805                 writeApplicationsLinks(applications, tabularList);
806                 if (Parameters.getCollectorApplicationsFile().canWrite()) {
807                     writeAddAndRemoveApplicationLinks(collector.getApplication(), applications,
808                             getWriter());
809                 }
810             }
811         } else if (Parameters.getCollectorApplicationsFile().canWrite()) {
812             writeAddAndRemoveApplicationLinks(collector.getApplication(), applications,
813                     getWriter());
814         }
815         writeln(END_DIV);
816     }
817
818     private void writeApplicationsLinks(Collection<String> applications, boolean tabularList)
819             throws IOException {
820         final Map<String, Throwable> lastCollectExceptionsByApplication = collectorServer
821                 .getLastCollectExceptionsByApplication();
822         if (tabularList) {
823             writeln("<table><tr><td>");
824         }
825         final int nbColumns = 5;
826         int i = 0;
827         for (final String application : applications) {
828             final Throwable lastCollectException = lastCollectExceptionsByApplication
829                     .get(application);
830             writeln("<a href='?application=" + application + "' class='tooltip'>");
831             if (lastCollectException == null) {
832                 writeln("<img src='?resource=bullets/green.png' alt='#Application_disponible#'/>");
833                 writeln("<em class='applicationStatus'>");
834                 writeln("#Application_disponible#");
835             } else {
836                 writeln("<img src='?resource=bullets/red.png' alt='#Application_indisponible#'/>");
837                 writeln("<em class='applicationStatus'>");
838                 writeln("#Application_indisponible#:<br/>");
839                 writeDirectly(htmlEncode(lastCollectException.toString()));
840                 writeDirectly("<br/>");
841                 for (final StackTraceElement stackTraceElement : lastCollectException
842                         .getStackTrace()) {
843                     writeDirectly(htmlEncode(stackTraceElement.toString()));
844                     writeDirectly("<br/>");
845                 }
846             }
847             writeln("</em>");
848             writeln(application + "</a>");
849             i++;
850             if (tabularList) {
851                 if (i % nbColumns == 0) {
852                     writeln("</td></tr><tr><td>");
853                 } else {
854                     writeln("</td><td>");
855                 }
856             } else {
857                 writeln("&nbsp;&nbsp;&nbsp;");
858             }
859         }
860         if (tabularList) {
861             writeln("</td></tr></table>");
862         }
863     }
864
865     void writeRefreshAndPeriodLinks(String graphName, String part) throws IOException {
866         writeln("<div class='noPrint'>");
867         final String separator = "&nbsp;&nbsp;&nbsp;&nbsp;";
868         final String graphParameter = "&amp;graph=";
869         if (graphName == null) {
870             write("<a href='?' title='#Rafraichir#'>");
871         } else {
872             write("<a class='back' href=''><img src='?resource=action_back.png' alt='#Retour#'/> #Retour#</a>");
873             writeln(separator);
874             writeln("<a href='?'><img src='?resource=action_home.png' alt='#Page_principale#'/> #Page_principale#</a>");
875             writeln(separator);
876             write("<a href='?part=" + part + graphParameter + urlEncode(graphName)
877                     + "' title='#Rafraichir#'>");
878         }
879         write("<img src='?resource=action_refresh.png' alt='#Actualiser#'/> #Actualiser#</a>");
880         if (isPdfEnabled() && !"usages".equals(part)) {
881             writeln(separator);
882             if (graphName == null) {
883                 write("<a href='?format=pdf' title='#afficher_PDF#'>");
884             } else {
885                 write("<a href='?part=" + part + graphParameter + urlEncode(graphName)
886                         + "&amp;format=pdf' title='#afficher_PDF#'>");
887             }
888             write("<img src='?resource=pdf.png' alt='#PDF#'/> #PDF#</a>");
889         }
890         writeln(separator);
891         write("<a href='?resource=#help_url#' target='_blank'");
892         write(" title=\"#Afficher_aide_en_ligne#\"><img src='?resource=action_help.png' alt='#Aide_en_ligne#'/> #Aide_en_ligne#</a>");
893         writeln(separator);
894         write("<a href='?part=jnlp'");
895         write(" title=\"#RDA#\"><img src='?resource=systemmonitor.png' width='16' height='16' alt='#RDA#'/> #Desktop#</a>");
896         writeln(separator);
897         writeln("#Choix_periode# :&nbsp;");
898         // On affiche des liens vers les périodes.
899         // Rq : il n'y a pas de période ni de graph sur la dernière heure puisque
900         // si la résolution des données est de 5 min, on ne verra alors presque rien
901         for (final Period myPeriod : Period.values()) {
902             if (graphName == null) {
903                 write("<a href='?period=" + myPeriod.getCode() + "' ");
904             } else {
905                 write("<a href='?part=" + part + graphParameter + urlEncode(graphName)
906                         + "&amp;period=" + myPeriod.getCode() + "' ");
907             }
908             write("title='" + getFormattedString("Choisir_periode", myPeriod.getLinkLabel())
909                     + "'>");
910             write("<img src='?resource=" + myPeriod.getIconName() + "' alt='"
911                     + myPeriod.getLinkLabel() + "' /> ");
912             writeln(myPeriod.getLinkLabel() + "</a>&nbsp;");
913         }
914         final HtmlForms htmlForms = new HtmlForms(getWriter());
915         final Map<String, Date> datesByWebappVersions = collector.getDatesByWebappVersions();
916         htmlForms.writeCustomPeriodLinks(datesByWebappVersions, range, graphName, part);
917
918         writeln(END_DIV);
919     }
920
921     private void writeDurationAndOverhead() throws IOException {
922         final long displayDuration = System.currentTimeMillis() - start;
923         writeln("<a name='bottom'></a>");
924         writeln("<br/><div class='durationAndOverHead'>");
925         writeln("#temps_derniere_collecte#: " + collector.getLastCollectDuration() + " #ms#<br/>");
926         writeln("#temps_affichage#: " + displayDuration + " #ms#<br/>");
927         writeln("#Estimation_overhead_memoire#: < "
928                 + (collector.getEstimatedMemorySize() / 1024 / 1024 + 1) + " #Mo#");
929         writeln("<br/>#Usage_disque#: " + (collector.getDiskUsage() / 1024 / 1024 + 1) + " #Mo#");
930         if (Parameters.isSystemActionsEnabled()) {
931             writeln("&nbsp;&nbsp;&nbsp;<a href='?action=purge_obsolete_files"
932                     + getCsrfTokenUrlPart() + "' class='noPrint'>");
933             writeln("<img width='14' height='14' src='?resource=user-trash.png' alt='#Purger_les_fichiers_obsoletes#' title='#Purger_les_fichiers_obsoletes#'/></a>");
934         }
935         if (Parameters.JAVAMELODY_VERSION != null) {
936             writeln("<br/><br/>JavaMelody " + Parameters.JAVAMELODY_VERSION);
937         }
938         if (collectorServer == null) {
939             writeln("<br/>");
940             writeShowHideLink("debuggingLogs""Debugging logs");
941             writeln("<br/><br/>");
942             writeln("<div id='debuggingLogs' class='displayNone'>");
943             final List<String> debuggingLogs = LOG.getDebuggingLogs();
944             if (debuggingLogs.size() >= LOG.MAX_DEBUGGING_LOGS_COUNT) {
945                 writeln("<div class='severe'>Only the last " + LOG.MAX_DEBUGGING_LOGS_COUNT
946                         + " messages are displayed</div>");
947             }
948             for (final String msg : debuggingLogs) {
949                 writeDirectly(
950                         htmlEncodeButNotSpace(msg).replace("\t""&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"));
951                 writeln("<br/>");
952             }
953             writeln(END_DIV);
954         }
955         writeln(END_DIV);
956     }
957 }
958