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.pdf;
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.List;
24 import java.util.Map;
25
26 import com.lowagie.text.Chunk;
27 import com.lowagie.text.Document;
28 import com.lowagie.text.DocumentException;
29 import com.lowagie.text.Element;
30 import com.lowagie.text.Font;
31 import com.lowagie.text.FontFactory;
32 import com.lowagie.text.Image;
33 import com.lowagie.text.Paragraph;
34 import com.lowagie.text.Phrase;
35 import com.lowagie.text.pdf.PdfPTable;
36
37 import net.bull.javamelody.internal.common.I18N;
38 import net.bull.javamelody.internal.common.Parameters;
39 import net.bull.javamelody.internal.model.CacheInformations;
40 import net.bull.javamelody.internal.model.Collector;
41 import net.bull.javamelody.internal.model.Counter;
42 import net.bull.javamelody.internal.model.CounterRequestContext;
43 import net.bull.javamelody.internal.model.JCacheInformations;
44 import net.bull.javamelody.internal.model.JRobin;
45 import net.bull.javamelody.internal.model.JavaInformations;
46 import net.bull.javamelody.internal.model.JobInformations;
47 import net.bull.javamelody.internal.model.Period;
48 import net.bull.javamelody.internal.model.Range;
49
50 /**
51  * Rapport pdf principal.
52  * @author Emeric Vernat
53  */

54 public class PdfCoreReport extends PdfAbstractReport {
55     public static final int SMALL_GRAPH_WIDTH = 200;
56     public static final int SMALL_GRAPH_HEIGHT = 50;
57     public static final int LARGE_GRAPH_WIDTH = 960;
58     public static final int LARGE_GRAPH_HEIGHT = 370;
59     private final Collector collector;
60     private final List<JavaInformations> javaInformationsList;
61     private final Range range;
62     private Range counterRange;
63     private List<CounterRequestContext> currentRequests;
64     private final boolean collectorServer;
65     private final PdfDocumentFactory pdfDocumentFactory;
66     private final Font normalFont = PdfFonts.NORMAL.getFont();
67     private final Font cellFont = PdfFonts.TABLE_CELL.getFont();
68     private final Font boldFont = PdfFonts.BOLD.getFont();
69     private final long start = System.currentTimeMillis();
70     private Map<String, byte[]> smallGraphs;
71     private Map<String, byte[]> smallOtherGraphs;
72     private Map<String, byte[]> largeGraphs;
73
74     PdfCoreReport(Collector collector, boolean collectorServer,
75             List<JavaInformations> javaInformationsList, Range range,
76             PdfDocumentFactory pdfDocumentFactory, Document document) {
77         super(document);
78         assert collector != null;
79         assert javaInformationsList != null && !javaInformationsList.isEmpty();
80         assert range != null;
81         assert pdfDocumentFactory != null;
82
83         this.collector = collector;
84         this.collectorServer = collectorServer;
85         this.javaInformationsList = javaInformationsList;
86         this.range = range;
87         this.counterRange = range; // par défaut, c'est le même range
88         this.pdfDocumentFactory = pdfDocumentFactory;
89     }
90
91     // cette méthode est utilisée dans l'ihm Swing
92     void preInitGraphs(Map<String, byte[]> newSmallGraphs, Map<String, byte[]> newSmallOtherGraphs,
93             Map<String, byte[]> newLargeGraphs) {
94         this.smallGraphs = newSmallGraphs;
95         this.smallOtherGraphs = newSmallOtherGraphs;
96         this.largeGraphs = newLargeGraphs;
97     }
98
99     // cette méthode est utilisée dans l'ihm Swing
100     void setCounterRange(Range counterRange) {
101         this.counterRange = counterRange;
102     }
103
104     // cette méthode est utilisée dans l'ihm Swing
105     void setCurrentRequests(List<CounterRequestContext> currentRequests) {
106         this.currentRequests = currentRequests;
107     }
108
109     @Override
110     void toPdf() throws IOException, DocumentException {
111         addParagraph(buildSummary(), "systemmonitor.png");
112         writeGraphs(collector.getDisplayedCounterJRobins(), smallGraphs);
113
114         final List<Counter> counters = collector.getRangeCountersToBeDisplayed(counterRange);
115         final List<PdfCounterReport> pdfCounterReports = writeCounters(counters);
116
117         final List<PdfCounterRequestContextReport> pdfCounterRequestContextReports = new ArrayList<>();
118         if (!collectorServer) {
119             addParagraph(getString("Requetes_en_cours"), "hourglass.png");
120             // si on n'est pas sur le serveur de collecte il n'y a qu'un javaInformations
121             pdfCounterRequestContextReports.addAll(
122                     writeCurrentRequests(javaInformationsList.get(0), counters, pdfCounterReports));
123         }
124
125         addToDocument(new Phrase("\n", normalFont));
126         addParagraph(getString("Informations_systemes"), "systeminfo.png");
127         new PdfJavaInformationsReport(javaInformationsList, getDocument()).toPdf();
128
129         addParagraph(getString("Threads"), "threads.png");
130         writeThreads(false);
131
132         PdfCounterReport pdfJobCounterReport = null;
133         Counter rangeJobCounter = null;
134         if (isJobEnabled()) {
135             rangeJobCounter = collector.getRangeCounter(counterRange, Counter.JOB_COUNTER_NAME);
136             addToDocument(new Phrase("\n", normalFont));
137             addParagraph(getString("Jobs"), "jobs.png");
138             writeJobs(rangeJobCounter, false);
139             pdfJobCounterReport = writeCounter(rangeJobCounter);
140         }
141
142         if (isCacheEnabled() || isJCacheEnabled()) {
143             addToDocument(new Phrase("\n", normalFont));
144             addParagraph(getString("Caches"), "caches.png");
145             writeCaches(false);
146             writeJCaches(false);
147         }
148
149         newPage();
150         addParagraph(getString("Statistiques_detaillees"), "systemmonitor.png");
151         writeGraphs(collector.getDisplayedOtherJRobins(), smallOtherGraphs);
152         writeGraphDetails();
153
154         writeCountersDetails(pdfCounterReports);
155
156         if (!collectorServer) {
157             addParagraph(getString("Requetes_en_cours_detaillees"), "hourglass.png");
158             // si on n'est pas sur le serveur de collecte il n'y a qu'un javaInformations
159             writeCurrentRequestsDetails(pdfCounterRequestContextReports);
160         }
161
162         addParagraph(getString("Informations_systemes_detaillees"), "systeminfo.png");
163         new PdfJavaInformationsReport(javaInformationsList, getDocument())
164                 .writeInformationsDetails();
165
166         addParagraph(getString("Threads_detailles"), "threads.png");
167         writeThreads(true);
168
169         if (isJobEnabled()) {
170             addToDocument(new Phrase("\n", normalFont));
171             addParagraph(getString("Jobs_detailles"), "jobs.png");
172             writeJobs(rangeJobCounter, true);
173             writeCounterDetails(pdfJobCounterReport);
174         }
175
176         if (isCacheEnabled() || isJCacheEnabled()) {
177             addToDocument(new Phrase("\n", normalFont));
178             addParagraph(getString("Caches_detailles"), "caches.png");
179             writeCaches(true);
180             writeJCaches(true);
181         }
182
183         writeDurationAndOverhead();
184     }
185
186     private String buildSummary() {
187         final String tmp;
188         if (range.getPeriod() == Period.TOUT) {
189             final String startDate = I18N.createDateAndTimeFormat()
190                     .format(collector.getCounters().get(0).getStartDate());
191             tmp = getFormattedString("Statistiques""JavaMelody", I18N.getCurrentDateAndTime(),
192                     startDate, collector.getApplication());
193         } else {
194             tmp = getFormattedString("Statistiques_sans_depuis""JavaMelody",
195                     I18N.getCurrentDateAndTime(), collector.getApplication());
196         }
197         if (javaInformationsList.get(0).getContextDisplayName() != null) {
198             return tmp + " (" + javaInformationsList.get(0).getContextDisplayName() + ')';
199         }
200         return tmp;
201     }
202
203     private void writeGraphs(Collection<JRobin> jrobins, Map<String, byte[]> mySmallGraphs)
204             throws IOException, DocumentException {
205         if (collector.isStopped()) {
206             // pas de graphs, ils seraient en erreur sans timer
207             // mais un message d'avertissement à la place
208             final String message = getString("collect_server_misusage");
209             final Paragraph jrobinParagraph = new Paragraph(message, PdfFonts.BOLD.getFont());
210             jrobinParagraph.setAlignment(Element.ALIGN_CENTER);
211             addToDocument(jrobinParagraph);
212             return;
213         }
214         if (collector.isStorageUsedByMultipleInstances()) {
215             final String message = getString("storage_used_by_multiple_instances") + "\n\n";
216             final Paragraph jrobinParagraph = new Paragraph(message, PdfFonts.BOLD.getFont());
217             jrobinParagraph.setAlignment(Element.ALIGN_CENTER);
218             addToDocument(jrobinParagraph);
219         }
220         final Paragraph jrobinParagraph = new Paragraph("",
221                 FontFactory.getFont(FontFactory.HELVETICA, 9f, Font.NORMAL));
222         jrobinParagraph.setAlignment(Element.ALIGN_CENTER);
223         jrobinParagraph.add(new Phrase("\n\n\n\n"));
224         final Collection<byte[]> graphs;
225         if (mySmallGraphs != null) {
226             // si les graphiques ont été préinitialisés (en Swing) alors on les utilise
227             graphs = mySmallGraphs.values();
228         } else {
229             if (jrobins.isEmpty()) {
230                 return;
231             }
232             graphs = new ArrayList<>(jrobins.size());
233             for (final JRobin jrobin : jrobins) {
234                 graphs.add(jrobin.graph(range, SMALL_GRAPH_WIDTH, SMALL_GRAPH_HEIGHT));
235             }
236         }
237         int i = 0;
238         for (final byte[] graph : graphs) {
239             if (i % 3 == 0 && i != 0) {
240                 // un retour après httpSessions et avant activeThreads pour l'alignement
241                 jrobinParagraph.add(new Phrase("\n\n\n\n\n"));
242             }
243             final Image image = Image.getInstance(graph);
244             image.scalePercent(50);
245             jrobinParagraph.add(new Phrase(new Chunk(image, 0, 0)));
246             jrobinParagraph.add(new Phrase(" "));
247             i++;
248         }
249         jrobinParagraph.add(new Phrase("\n"));
250         addToDocument(jrobinParagraph);
251     }
252
253     private void writeGraphDetails() throws IOException, DocumentException {
254         if (collector.isStopped()) {
255             return;
256         }
257         final PdfPTable jrobinTable = new PdfPTable(1);
258         jrobinTable.setHorizontalAlignment(Element.ALIGN_CENTER);
259         jrobinTable.setWidthPercentage(100);
260         jrobinTable.getDefaultCell().setBorder(0);
261         if (largeGraphs != null) {
262             // si les graphiques ont été préinitialisés (en Swing) alors on les utilise
263             for (final byte[] imageData : largeGraphs.values()) {
264                 final Image image = Image.getInstance(imageData);
265                 jrobinTable.addCell(image);
266             }
267         } else {
268             final Collection<JRobin> counterJRobins = collector.getDisplayedCounterJRobins();
269             if (counterJRobins.isEmpty()) {
270                 return;
271             }
272             for (final JRobin jrobin : counterJRobins) {
273                 // la hauteur de l'image est prévue pour qu'il n'y ait pas de graph seul sur une page
274                 final Image image = Image
275                         .getInstance(jrobin.graph(range, LARGE_GRAPH_WIDTH, LARGE_GRAPH_HEIGHT));
276                 jrobinTable.addCell(image);
277             }
278         }
279         newPage();
280         addToDocument(jrobinTable);
281         newPage();
282     }
283
284     private List<PdfCounterReport> writeCounters(List<Counter> counters)
285             throws IOException, DocumentException {
286         final List<PdfCounterReport> pdfCounterReports = new ArrayList<>();
287         for (final Counter counter : counters) {
288             pdfCounterReports.add(writeCounter(counter));
289         }
290         return pdfCounterReports;
291     }
292
293     private PdfCounterReport writeCounter(Counter counter) throws DocumentException, IOException {
294         final String counterLabel = getString(counter.getName() + "Label");
295         addParagraph(addRangeLabel(getFormattedString("Statistiques_compteur", counterLabel)),
296                 counter.getIconName());
297         final PdfCounterReport pdfCounterReport = new PdfCounterReport(collector, counter, range,
298                 false, getDocument());
299         pdfCounterReport.toPdf();
300         return pdfCounterReport;
301     }
302
303     private String addRangeLabel(String s) {
304         return s + " - " + range.getLabel() + ' ' + getString("depuis_minuit");
305     }
306
307     private void writeCountersDetails(List<PdfCounterReport> pdfCounterReports)
308             throws DocumentException, IOException {
309         for (final PdfCounterReport pdfCounterReport : pdfCounterReports) {
310             writeCounterDetails(pdfCounterReport);
311         }
312     }
313
314     private void writeCounterDetails(PdfCounterReport pdfCounterReport)
315             throws DocumentException, IOException {
316         final String counterLabel = getString(pdfCounterReport.getCounterName() + "Label");
317         addParagraph(
318                 addRangeLabel(getFormattedString("Statistiques_compteur_detaillees", counterLabel)),
319                 pdfCounterReport.getCounterIconName());
320         pdfCounterReport.writeRequestDetails();
321         if (pdfCounterReport.isErrorCounter()) {
322             addParagraph(addRangeLabel(getString(pdfCounterReport.getCounterName() + "ErrorLabel")),
323                     pdfCounterReport.getCounterIconName());
324             pdfCounterReport.writeErrorDetails();
325         }
326     }
327
328     private List<PdfCounterRequestContextReport> writeCurrentRequests(
329             JavaInformations javaInformations, List<Counter> counters,
330             List<PdfCounterReport> pdfCounterReports) throws IOException, DocumentException {
331         final List<PdfCounterRequestContextReport> pdfCounterRequestContextReports = new ArrayList<>();
332         final List<CounterRequestContext> rootCurrentContexts;
333         if (currentRequests == null) {
334             rootCurrentContexts = collector.getRootCurrentContexts(counters);
335         } else {
336             rootCurrentContexts = currentRequests;
337         }
338         if (rootCurrentContexts.isEmpty()) {
339             addToDocument(new Phrase(getString("Aucune_requete_en_cours"), normalFont));
340         } else {
341             final PdfCounterRequestContextReport pdfCounterRequestContextReport = new PdfCounterRequestContextReport(
342                     rootCurrentContexts, pdfCounterReports,
343                     javaInformations.getThreadInformationsList(),
344                     javaInformations.isStackTraceEnabled(), pdfDocumentFactory, getDocument());
345             pdfCounterRequestContextReport.toPdf();
346             pdfCounterRequestContextReports.add(pdfCounterRequestContextReport);
347         }
348         return pdfCounterRequestContextReports;
349     }
350
351     private void writeCurrentRequestsDetails(
352             List<PdfCounterRequestContextReport> pdfCounterRequestContextReports)
353             throws IOException, DocumentException {
354         for (final PdfCounterRequestContextReport pdfCounterRequestContextReport : pdfCounterRequestContextReports) {
355             pdfCounterRequestContextReport.writeContextDetails();
356         }
357         if (pdfCounterRequestContextReports.isEmpty()) {
358             addToDocument(new Phrase(getString("Aucune_requete_en_cours"), normalFont));
359         }
360     }
361
362     private void writeThreads(boolean includeDetails) throws DocumentException, IOException {
363         String eol = "";
364         for (final JavaInformations javaInformations : javaInformationsList) {
365             addToDocument(new Phrase(eol, normalFont));
366             final PdfThreadInformationsReport pdfThreadInformationsReport = new PdfThreadInformationsReport(
367                     javaInformations.getThreadInformationsList(),
368                     javaInformations.isStackTraceEnabled(), pdfDocumentFactory, getDocument());
369             pdfThreadInformationsReport.writeIntro(javaInformations);
370             pdfThreadInformationsReport.writeDeadlocks();
371
372             if (includeDetails) {
373                 pdfThreadInformationsReport.toPdf();
374             }
375             eol = "\n";
376         }
377     }
378
379     private void writeCaches(boolean includeDetails) throws DocumentException {
380         String eol = "";
381         for (final JavaInformations javaInformations : javaInformationsList) {
382             if (!javaInformations.isCacheEnabled()) {
383                 continue;
384             }
385             final List<CacheInformations> cacheInformationsList = javaInformations
386                     .getCacheInformationsList();
387             final String msg = getFormattedString("caches_sur", cacheInformationsList.size(),
388                     javaInformations.getHost(), javaInformations.getCurrentlyExecutingJobCount());
389             addToDocument(new Phrase(eol + msg, boldFont));
390
391             if (includeDetails) {
392                 new PdfCacheInformationsReport(cacheInformationsList, getDocument()).toPdf();
393             }
394             eol = "\n";
395         }
396     }
397
398     private boolean isCacheEnabled() {
399         for (final JavaInformations javaInformations : javaInformationsList) {
400             if (javaInformations.isCacheEnabled()) {
401                 return true;
402             }
403         }
404         return false;
405     }
406
407     private void writeJCaches(boolean includeDetails) throws DocumentException {
408         String eol = "";
409         if (isCacheEnabled()) {
410             // new line in case there are both cache and jcache
411             eol = "\n";
412         }
413         for (final JavaInformations javaInformations : javaInformationsList) {
414             if (!javaInformations.isJCacheEnabled()) {
415                 continue;
416             }
417             final List<JCacheInformations> jcacheInformationsList = javaInformations
418                     .getJCacheInformationsList();
419             final String msg = getFormattedString("caches_sur", jcacheInformationsList.size(),
420                     javaInformations.getHost());
421             addToDocument(new Phrase(eol + msg, boldFont));
422
423             if (includeDetails) {
424                 new PdfJCacheInformationsReport(jcacheInformationsList, getDocument()).toPdf();
425             }
426             eol = "\n";
427         }
428     }
429
430     private boolean isJCacheEnabled() {
431         for (final JavaInformations javaInformations : javaInformationsList) {
432             if (javaInformations.isJCacheEnabled()) {
433                 return true;
434             }
435         }
436         return false;
437     }
438
439     private void writeJobs(Counter rangeJobCounter, boolean includeDetails)
440             throws DocumentException, IOException {
441         String eol = "";
442         for (final JavaInformations javaInformations : javaInformationsList) {
443             if (!javaInformations.isJobEnabled()) {
444                 continue;
445             }
446             final List<JobInformations> jobInformationsList = javaInformations
447                     .getJobInformationsList();
448             final String msg = getFormattedString("jobs_sur", jobInformationsList.size(),
449                     javaInformations.getHost(), javaInformations.getCurrentlyExecutingJobCount());
450             addToDocument(new Phrase(eol + msg, boldFont));
451
452             if (includeDetails) {
453                 new PdfJobInformationsReport(jobInformationsList, rangeJobCounter, getDocument())
454                         .toPdf();
455             }
456             eol = "\n";
457         }
458     }
459
460     private boolean isJobEnabled() {
461         for (final JavaInformations javaInformations : javaInformationsList) {
462             if (javaInformations.isJobEnabled()) {
463                 return true;
464             }
465         }
466         return false;
467     }
468
469     private void writeDurationAndOverhead() throws DocumentException {
470         final long displayDuration = System.currentTimeMillis() - start;
471         final String tmp = "\n\n" + getString("temps_derniere_collecte") + ": "
472                 + collector.getLastCollectDuration() + ' ' + getString("ms") + '\n'
473                 + getString("temps_affichage") + ": " + displayDuration + ' ' + getString("ms")
474                 + '\n' + getString("Estimation_overhead_memoire") + ": < "
475                 + (collector.getEstimatedMemorySize() / 1024 / 1024 + 1) + ' ' + getString("Mo")
476                 + '\n' + getString("Usage_disque") + ": "
477                 + (collector.getDiskUsage() / 1024 / 1024 + 1) + ' ' + getString("Mo");
478         final String string;
479         if (Parameters.JAVAMELODY_VERSION != null) {
480             string = tmp + "\n\n" + "JavaMelody " + Parameters.JAVAMELODY_VERSION;
481         } else {
482             string = tmp;
483         }
484         addToDocument(new Phrase(string, cellFont));
485     }
486
487     private void addParagraph(String paragraphTitle, String iconName)
488             throws DocumentException, IOException {
489         addToDocument(pdfDocumentFactory.createParagraphElement(paragraphTitle, iconName));
490     }
491 }
492