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.Writer;
22 import java.text.DecimalFormat;
23 import java.text.MessageFormat;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Locale;
27
28 import net.bull.javamelody.internal.common.I18N;
29 import net.bull.javamelody.internal.common.Parameters;
30 import net.bull.javamelody.internal.model.JavaInformations;
31 import net.bull.javamelody.internal.model.MemoryInformations;
32 import net.bull.javamelody.internal.model.TomcatInformations;
33
34 /**
35  * Partie du rapport html pour les informations systèmes sur le serveur.
36  * @author Emeric Vernat
37  */

38 public class HtmlJavaInformationsReport extends HtmlAbstractReport {
39     private static final String[] OS = { "linux""windows""mac""solaris""hp""ibm", };
40     private static final String[] APPLICATION_SERVERS = { "tomcat""glassfish""jetty""oracle",
41             "bea""ibm""jboss""wildfly", };
42     // constantes pour l'affichage d'une barre avec pourcentage
43     private static final double MIN_VALUE = 0;
44     private static final double MAX_VALUE = 100;
45     private static final int PARTIAL_BLOCKS = 5;
46     private static final int FULL_BLOCKS = 10;
47     private static final double UNIT_SIZE = (MAX_VALUE - MIN_VALUE)
48             / (FULL_BLOCKS * PARTIAL_BLOCKS);
49
50     private final boolean noDatabase = Parameters.isNoDatabase();
51     private final DecimalFormat integerFormat = I18N.createIntegerFormat();
52     private final DecimalFormat decimalFormat = I18N.createPercentFormat();
53     private final List<JavaInformations> javaInformationsList;
54
55     HtmlJavaInformationsReport(List<JavaInformations> javaInformationsList, Writer writer) {
56         super(writer);
57         assert javaInformationsList != null && !javaInformationsList.isEmpty();
58
59         this.javaInformationsList = javaInformationsList;
60     }
61
62     @Override
63     void toHtml() throws IOException {
64         for (final JavaInformations javaInformations : javaInformationsList) {
65             writeSummary(javaInformations);
66         }
67         // sinon le tableau est décalé
68         if (!noDatabase) {
69             write("<br/><br/>");
70         }
71         final String br = "<br/>";
72         if (javaInformationsList.get(0).getSessionCount() >= 0) {
73             write(br);
74         }
75         if (javaInformationsList.get(0).getSystemLoadAverage() >= 0) {
76             // sinon le tableau est décalé vers la droite sous unix
77             write(br);
78         }
79         // pour l'alignement le nb de br doit correspondre au nb de lignes dans le résumé ci-dessus
80         writeln("<br/><br/><br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
81         writeShowHideLink("detailsJava""#Details#");
82         writeln("<br/><br/><br/>");
83         // div interne pour showHideLink
84         writeln("<div id='detailsJava' class='displayNone'><div>");
85         final boolean repeatHost = javaInformationsList.size() > 1;
86         for (final JavaInformations javaInformations : javaInformationsList) {
87             writeDetails(javaInformations, repeatHost);
88         }
89         writeln("</div></div>");
90     }
91
92     private void writeSummary(JavaInformations javaInformations) throws IOException {
93         final String lineEnd = "</td> </tr>";
94         final String columnAndLineEnd = "</td><td>" + lineEnd;
95         writeln("<table align='left' border='0' summary='#Informations_systemes#'>");
96         writeln("<tr><td>#Host#: </td><td><b>" + javaInformations.getHost() + "</b>" + lineEnd);
97         final MemoryInformations memoryInformations = javaInformations.getMemoryInformations();
98         final long usedMemory = memoryInformations.getUsedMemory();
99         final long maxMemory = memoryInformations.getMaxMemory();
100         write("<tr><td>#memoire_utilisee#: </td><td>");
101         writeGraph("usedMemory", integerFormat.format(usedMemory / 1024 / 1024));
102         writeln(" #Mo# / " + integerFormat.format(maxMemory / 1024 / 1024)
103                 + " #Mo#&nbsp;&nbsp;&nbsp;</td><td>");
104         writeln(toBarWithAlert(memoryInformations.getUsedMemoryPercentage(), "-Xmx"));
105         writeln(lineEnd);
106         if (javaInformations.getSessionCount() >= 0) {
107             write("<tr><td>#nb_sessions_http#: </td><td>");
108             writeGraph("httpSessions", integerFormat.format(javaInformations.getSessionCount()));
109             writeln(columnAndLineEnd);
110         }
111         write("<tr><td>#nb_threads_actifs#<br/>(#Requetes_http_en_cours#): </td><td>");
112         writeGraph("activeThreads", integerFormat.format(javaInformations.getActiveThreadCount()));
113         writeln(columnAndLineEnd);
114         if (!noDatabase) {
115             write("<tr><td>#nb_connexions_actives#: </td><td>");
116             writeGraph("activeConnections",
117                     integerFormat.format(javaInformations.getActiveConnectionCount()));
118             writeln(columnAndLineEnd);
119             final int usedConnectionCount = javaInformations.getUsedConnectionCount();
120             final int maxConnectionCount = javaInformations.getMaxConnectionCount();
121             write("<tr><td>#nb_connexions_utilisees#<br/>(#ouvertes#): </td><td>");
122             writeGraph("usedConnections", integerFormat.format(usedConnectionCount));
123             if (maxConnectionCount > 0) {
124                 writeln(" / " + integerFormat.format(maxConnectionCount)
125                         + "&nbsp;&nbsp;&nbsp;</td><td>");
126                 writeln(toBarWithAlert(javaInformations.getUsedConnectionPercentage(), null));
127             }
128             writeln(lineEnd);
129         }
130         if (javaInformations.getSystemLoadAverage() >= 0) {
131             write("<tr><td>#Charge_systeme#</td><td>");
132             writeGraph("systemLoad", decimalFormat.format(javaInformations.getSystemLoadAverage()));
133             writeln(columnAndLineEnd);
134         }
135         if (javaInformations.getSystemCpuLoad() >= 0) {
136             write("<tr><td>#systemCpuLoad#</td><td>");
137             writeGraph("systemCpuLoad", decimalFormat.format(javaInformations.getSystemCpuLoad()));
138             writeln("&nbsp;&nbsp;&nbsp;</td><td>");
139             writeln(toBarWithAlert(javaInformations.getSystemCpuLoad(), null));
140             writeln(lineEnd);
141         }
142         writeln("</table>");
143     }
144
145     private void writeDetails(JavaInformations javaInformations, boolean repeatHost)
146             throws IOException {
147         final String columnEnd = "</td></tr>";
148         writeln("<table align='left' border='0' summary='#Details_systeme#'>");
149         if (repeatHost) {
150             writeln("<tr><td>#Host#: </td><td><b>" + javaInformations.getHost() + "</b>"
151                     + columnEnd);
152         }
153         writeln("<tr><td>#OS#: </td><td>");
154         final String osIconName = getOSIconName(javaInformations.getOS());
155         if (osIconName != null) {
156             writeln("<img src='?resource=servers/" + osIconName + "' alt='#OS#'/>");
157         }
158         writeln(javaInformations.getOS() + " (" + javaInformations.getAvailableProcessors()
159                 + " #coeurs#)" + columnEnd);
160         writeln("<tr><td>#Java#: </td><td>" + javaInformations.getJavaVersion() + columnEnd);
161         write("<tr><td>#JVM#: </td><td>" + javaInformations.getJvmVersion());
162         if (javaInformations.getJvmVersion().contains("Client")) {
163             write("&nbsp;&nbsp;&nbsp;<img src='?resource=alert.png' alt=\"#Client_JVM#\" title=\"#Client_JVM#\"/>");
164         }
165         writeln(columnEnd);
166         writeln("<tr><td>#PID#: </td><td>" + javaInformations.getPID() + columnEnd);
167         final long unixOpenFileDescriptorCount = javaInformations.getUnixOpenFileDescriptorCount();
168         if (unixOpenFileDescriptorCount >= 0) {
169             final long unixMaxFileDescriptorCount = javaInformations
170                     .getUnixMaxFileDescriptorCount();
171             write("<tr><td>#nb_fichiers#</td><td>");
172             writeGraph("fileDescriptors", integerFormat.format(unixOpenFileDescriptorCount));
173             writeln(" / " + integerFormat.format(unixMaxFileDescriptorCount)
174                     + "&nbsp;&nbsp;&nbsp;");
175             writeln(toBarWithAlert(javaInformations.getUnixOpenFileDescriptorPercentage(), null));
176             writeln(columnEnd);
177         }
178         writeServerInfoAndContextPath(javaInformations);
179         writeln("<tr><td>#Demarrage#: </td><td>"
180                 + I18N.createDateAndTimeFormat().format(javaInformations.getStartDate())
181                 + columnEnd);
182
183         write("<tr><td valign='top'>#Arguments_JVM#: </td><td>");
184         // writeDirectly pour ne pas gérer de traductions si la donnée contient '#'
185         writeDirectly(htmlEncodeButNotSpace(javaInformations.getJvmArguments()) + columnEnd);
186         writeln("");
187
188         if (javaInformations.getSessionCount() >= 0) {
189             write("<tr><td>#httpSessionsMeanAge#: </td><td>");
190             writeGraph("httpSessionsMeanAge",
191                     integerFormat.format(javaInformations.getSessionMeanAgeInMinutes()));
192             writeln(columnEnd);
193         }
194
195         writeTomcatInformations(javaInformations.getTomcatInformationsList());
196
197         writeMemoryInformations(javaInformations.getMemoryInformations());
198
199         // on considère que l'espace libre sur le disque dur est celui sur la partition du répertoire temporaire
200         writeln("<tr><td>#Free_disk_space#: </td><td>"
201                 + integerFormat.format(javaInformations.getFreeDiskSpaceInTemp() / 1024 / 1024)
202                 + " #Mo# " + columnEnd);
203         writeln("<tr><td>#Usable_disk_space#: </td><td>"
204                 + integerFormat.format(javaInformations.getUsableDiskSpaceInTemp() / 1024 / 1024)
205                 + " #Mo# " + columnEnd);
206
207         writeDatabaseVersionAndDataSourceDetails(javaInformations);
208
209         writeln("<tr><td valign='top'><div class='noPrint'>#Dependencies#: </div></td><td>");
210         writeDependencies(javaInformations);
211         writeln(columnEnd);
212
213         writeln("</table>");
214     }
215
216     private void writeServerInfoAndContextPath(JavaInformations javaInformations)
217             throws IOException {
218         final String serverInfo = javaInformations.getServerInfo();
219         if (serverInfo != null) {
220             final String columnEnd = " </td></tr>";
221             writeln("<tr><td>#Serveur#: </td><td>");
222             final String applicationServerIconName = getApplicationServerIconName(serverInfo);
223             if (applicationServerIconName != null) {
224                 writeln("<img src='?resource=servers/" + applicationServerIconName
225                         + "' alt='#Serveur#'/>");
226             }
227             writeDirectly(serverInfo + columnEnd);
228             writeln("<tr><td>#Contexte_webapp#: </td><td>" + javaInformations.getContextPath()
229                     + columnEnd);
230         }
231     }
232
233     private void writeDatabaseVersionAndDataSourceDetails(JavaInformations javaInformations)
234             throws IOException {
235         final String columnEnd = "</td></tr>";
236         if (!noDatabase && javaInformations.getDataBaseVersion() != null) {
237             writeln("<tr><td valign='top'>#Base_de_donnees#: </td><td>");
238             // writeDirectly pour ne pas gérer de traductions si la donnée contient '#'
239             writeDirectly(htmlEncodeButNotSpace(javaInformations.getDataBaseVersion()));
240             writeln(columnEnd);
241         }
242         if (javaInformations.getDataSourceDetails() != null) {
243             writeln("<tr><td valign='top'>#DataSource_jdbc#: </td><td>");
244             // writeDirectly pour ne pas gérer de traductions si la donnée contient '#'
245             writeDirectly(htmlEncodeButNotSpace(javaInformations.getDataSourceDetails()));
246             writeDirectly(
247                     "<a href='http://commons.apache.org/proper/commons-dbcp/configuration.html'"
248                             + class='noPrint' target='_blank'>DataSource reference</a>");
249
250             // before commons dbcp v2, the documentation was:
251             // http://commons.apache.org/proper/commons-dbcp/api-1.4/index.html
252             writeln(columnEnd);
253         }
254     }
255
256     public static String getOSIconName(String os) {
257         final String tmp = os.toLowerCase(Locale.ENGLISH);
258         for (final String anOS : OS) {
259             if (tmp.contains(anOS)) {
260                 return anOS + ".png";
261             }
262         }
263         return null;
264     }
265
266     public static String getApplicationServerIconName(String appServer) {
267         final String tmp = appServer.toLowerCase(Locale.ENGLISH);
268         for (final String applicationServer : APPLICATION_SERVERS) {
269             if (tmp.contains(applicationServer)) {
270                 return applicationServer + ".png";
271             }
272         }
273         return null;
274     }
275
276     private void writeTomcatInformations(List<TomcatInformations> tomcatInformationsList)
277             throws IOException {
278         final List<TomcatInformations> list = new ArrayList<>();
279         for (final TomcatInformations tomcatInformations : tomcatInformationsList) {
280             if (tomcatInformations.getRequestCount() > 0) {
281                 list.add(tomcatInformations);
282             }
283         }
284         final boolean onlyOne = list.size() == 1;
285         for (final TomcatInformations tomcatInformations : list) {
286             writeDirectly("<tr><td valign='top'>Tomcat "
287                     + htmlEncodeButNotSpace(tomcatInformations.getName()) + ": </td><td>");
288             // rq: on n'affiche pas pour l'instant getCurrentThreadCount
289             final int currentThreadsBusy = tomcatInformations.getCurrentThreadsBusy();
290             writeln("#busyThreads# = ");
291             if (onlyOne) {
292                 writeGraph("tomcatBusyThreads", integerFormat.format(currentThreadsBusy));
293             } else {
294                 writeln(integerFormat.format(currentThreadsBusy));
295             }
296             writeln(" /  " + integerFormat.format(tomcatInformations.getMaxThreads()));
297             writeln("&nbsp;&nbsp;&nbsp;");
298             writeln(toBarWithAlert(100d * currentThreadsBusy / tomcatInformations.getMaxThreads(),
299                     null));
300             writeln("<br/>#bytesReceived# = ");
301             if (onlyOne) {
302                 writeGraph("tomcatBytesReceived",
303                         integerFormat.format(tomcatInformations.getBytesReceived()));
304             } else {
305                 writeln(integerFormat.format(tomcatInformations.getBytesReceived()));
306             }
307             writeln("<br/>#bytesSent# = ");
308             if (onlyOne) {
309                 writeGraph("tomcatBytesSent",
310                         integerFormat.format(tomcatInformations.getBytesSent()));
311             } else {
312                 writeln(integerFormat.format(tomcatInformations.getBytesSent()));
313             }
314             writeln("<br/>#requestCount# = ");
315             writeln(integerFormat.format(tomcatInformations.getRequestCount()));
316             writeln("<br/>#errorCount# = ");
317             writeln(integerFormat.format(tomcatInformations.getErrorCount()));
318             writeln("<br/>#processingTime# = ");
319             writeln(integerFormat.format(tomcatInformations.getProcessingTime()));
320             writeln("<br/>#maxProcessingTime# = ");
321             writeln(integerFormat.format(tomcatInformations.getMaxTime()));
322             writeln("</td> </tr>");
323         }
324     }
325
326     private void writeMemoryInformations(MemoryInformations memoryInformations) throws IOException {
327         final String columnEnd = "</td></tr>";
328         final String memoryDetails = memoryInformations.getMemoryDetails();
329         writeln("<tr><td valign='top'>#Gestion_memoire#: </td><td>"
330                 + htmlEncodeButNotSpace(memoryDetails).replace(" Mo"" #Mo#") + columnEnd);
331
332         final long usedPermGen = memoryInformations.getUsedPermGen();
333         if (usedPermGen > 0) {
334             // perm gen est à 0 sous jrockit
335             final long maxPermGen = memoryInformations.getMaxPermGen();
336             writeln("<tr><td>#Memoire_Perm_Gen#: </td><td>"
337                     + integerFormat.format(usedPermGen / 1024 / 1024) + " #Mo#");
338             if (maxPermGen > 0) {
339                 writeln(" / " + integerFormat.format(maxPermGen / 1024 / 1024)
340                         + " #Mo#&nbsp;&nbsp;&nbsp;");
341                 writeln(toBarWithAlert(memoryInformations.getUsedPermGenPercentage(),
342                         "-XX:MaxPermSize"));
343             }
344             writeln(columnEnd);
345         }
346     }
347
348     private void writeDependencies(JavaInformations javaInformations) throws IOException {
349         writeln("<a href='?part=dependencies' class='noPrint'>");
350         writeln("<img src='?resource=beans.png' width='14' height='14' alt='#Dependencies#'/> #Dependencies#</a>");
351         if (javaInformations.doesPomXmlExists() && Parameters.isSystemActionsEnabled()) {
352             writeln("&nbsp;&nbsp;&nbsp;");
353             writeln("<a href='?part=pom.xml' class='noPrint'>");
354             writeln("<img src='?resource=xml.png' width='14' height='14' alt=\"#pom.xml#\"/> #pom.xml#</a>");
355         }
356     }
357
358     private void writeGraph(String graph, String value) throws IOException {
359         if (javaInformationsList.size() > 1) {
360             write(value);
361             return;
362         }
363         final String id = "id" + graph;
364         // la classe tooltip est configurée dans la css de HtmlReport
365         write("<a class='tooltip replaceImage' href='?part=graph&amp;graph=");
366         write(graph);
367         write("' data-img-id='");
368         write(id);
369         write("' data-img-src='?graph=");
370         write(graph);
371         write("&amp;width=100&amp;height=50'>");
372         // avant mouseover on prend une image qui sera mise en cache
373         write("<em><img src='?resource=systeminfo.png' id='");
374         write(id);
375         write("' alt='graph'/></em>");
376         // writeDirectly pour ne pas gérer de traductions si le nom contient '#'
377         writeDirectly(value);
378         write("</a>");
379     }
380
381     // méthode inspirée de VisualScoreTag dans LambdaProbe/JStripe (Licence GPL)
382     static String toBar(double percentValue) { // NOPMD
383         final double myPercent = Math.max(Math.min(percentValue, 100d), 0d);
384         final StringBuilder sb = new StringBuilder();
385         final String body = "<img src=''?resource=bar/rb_{0}.gif'' alt=''+'' title=''"
386                 + I18N.createPercentFormat().format(myPercent) + "%'' />";
387         final int fullBlockCount = (int) Math.floor(myPercent / (UNIT_SIZE * PARTIAL_BLOCKS));
388         final int partialBlockIndex = (int) Math
389                 .floor((myPercent - fullBlockCount * UNIT_SIZE * PARTIAL_BLOCKS) / UNIT_SIZE);
390
391         sb.append(MessageFormat.format(body,
392                 fullBlockCount > 0 || partialBlockIndex > 0 ? "a" : "a0"));
393
394         final String fullBody = MessageFormat.format(body, PARTIAL_BLOCKS);
395         for (int i = 0; i < fullBlockCount; i++) {
396             sb.append(fullBody);
397         }
398
399         if (partialBlockIndex > 0) {
400             final String partialBody = MessageFormat.format(body, partialBlockIndex);
401             sb.append(partialBody);
402         }
403
404         final int emptyBlocks = FULL_BLOCKS - fullBlockCount - (partialBlockIndex > 0 ? 1 : 0);
405         final String emptyBody = MessageFormat.format(body, 0);
406         for (int i = 0; i < emptyBlocks; i++) {
407             sb.append(emptyBody);
408         }
409
410         sb.append(MessageFormat.format(body, fullBlockCount == FULL_BLOCKS ? "b" : "b0"));
411         return sb.toString();
412     }
413
414     static String toBarWithAlert(double percentValue, String configurationDetail) {
415         String result = toBar(percentValue);
416         if (percentValue >= JavaInformations.HIGH_USAGE_THRESHOLD_IN_PERCENTS) {
417             String message = getString("High_usage");
418             if (configurationDetail != null) {
419                 message += " (" + configurationDetail + ')'; // NOPMD
420             }
421             result += "&nbsp;&nbsp;&nbsp;<img src='?resource=alert.png' alt=\"" + message // NOPMD
422                     + "\" title=\"" + message + "\"/>";
423         }
424         return result;
425     }
426 }
427