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.text.DecimalFormat;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28
29 import com.lowagie.text.Chunk;
30 import com.lowagie.text.Document;
31 import com.lowagie.text.DocumentException;
32 import com.lowagie.text.Element;
33 import com.lowagie.text.Font;
34 import com.lowagie.text.FontFactory;
35 import com.lowagie.text.Image;
36 import com.lowagie.text.Paragraph;
37 import com.lowagie.text.Phrase;
38 import com.lowagie.text.pdf.PdfPCell;
39 import com.lowagie.text.pdf.PdfPTable;
40
41 import net.bull.javamelody.JdbcWrapper;
42 import net.bull.javamelody.internal.common.I18N;
43 import net.bull.javamelody.internal.model.Collector;
44 import net.bull.javamelody.internal.model.CollectorServer;
45 import net.bull.javamelody.internal.model.Counter;
46 import net.bull.javamelody.internal.model.CounterRequest;
47 import net.bull.javamelody.internal.model.CounterRequestRumData;
48 import net.bull.javamelody.internal.model.DatabaseInformations;
49 import net.bull.javamelody.internal.model.JRobin;
50 import net.bull.javamelody.internal.model.Range;
51 import net.bull.javamelody.internal.web.html.HtmlCounterReport;
52
53 /**
54  * Rapport pdf pour le détail d'une requête.
55  * @author Emeric Vernat
56  */

57 public class PdfRequestAndGraphDetailReport extends PdfAbstractTableReport {
58     private final Collector collector;
59     private final CollectorServer collectorServer;
60     private final Range range;
61     private final List<Counter> counters;
62     private final String graphName;
63     private final CounterRequest request;
64     private final Map<String, CounterRequest> requestsById;
65     private final PdfDocumentFactory pdfDocumentFactory;
66     private final DecimalFormat systemErrorFormat = I18N.createPercentFormat();
67     private final DecimalFormat nbExecutionsFormat = I18N.createPercentFormat();
68     private final DecimalFormat integerFormat = I18N.createIntegerFormat();
69     private final Font cellFont = PdfFonts.TABLE_CELL.getFont();
70     private final Font boldFont = PdfFonts.BOLD.getFont();
71     private final Font courierFont = FontFactory.getFont(FontFactory.COURIER, 5.5f, Font.NORMAL);
72
73     PdfRequestAndGraphDetailReport(Collector collector, CollectorServer collectorServer,
74             Range range, String graphName, PdfDocumentFactory pdfDocumentFactory, Document document)
75             throws IOException {
76         super(document);
77         assert collector != null;
78         assert range != null;
79         assert graphName != null;
80         assert pdfDocumentFactory != null;
81         this.collector = collector;
82         this.collectorServer = collectorServer;
83         this.range = range;
84         this.graphName = graphName;
85         this.counters = collector.getRangeCounters(range);
86         this.requestsById = mapAllRequestsById();
87         this.request = requestsById.get(graphName);
88         this.pdfDocumentFactory = pdfDocumentFactory;
89     }
90
91     private Map<String, CounterRequest> mapAllRequestsById() {
92         final Map<String, CounterRequest> result = new HashMap<>();
93         for (final Counter counter : counters) {
94             for (final CounterRequest aRequest : counter.getRequests()) {
95                 result.put(aRequest.getId(), aRequest);
96             }
97         }
98         return result;
99     }
100
101     @Override
102     void toPdf() throws DocumentException, IOException {
103         if (request != null) {
104             if (request.getRumData() != null && request.getRumData().getHits() != 0) {
105                 writeRequestRumData();
106             }
107
108             writeHeader();
109
110             writeRequests();
111
112             addTableToDocument();
113
114             if (JdbcWrapper.SINGLETON.getSqlCounter().isRequestIdFromThisCounter(request.getId())
115                     && !request.getName().toLowerCase(Locale.ENGLISH).startsWith("alter ")) {
116                 // inutile d'essayer d'avoir le plan d'exécution des requêtes sql
117                 // telles que "alter session set ..." (cf issue 152)
118                 writeSqlRequestExplainPlan();
119             }
120         }
121
122         if (isGraphDisplayed()) {
123             writeGraph();
124         }
125
126         if (request != null && request.getStackTrace() != null) {
127             final Paragraph paragraph = new Paragraph("\n", cellFont);
128             paragraph.setIndentationLeft(20);
129             paragraph.setIndentationRight(20);
130             paragraph.add(new Phrase("Stack-trace\n", boldFont));
131             paragraph.add(new Phrase(request.getStackTrace().replace("\t""        "), cellFont));
132             addToDocument(paragraph);
133         }
134     }
135
136     private void writeRequestRumData() throws DocumentException {
137         final CounterRequestRumData rumData = request.getRumData();
138         final DecimalFormat percentFormat = I18N.createPercentFormat();
139         final int networkTimeMean = rumData.getNetworkTimeMean();
140         final int serverMean = request.getMean();
141         final int domProcessingMean = rumData.getDomProcessingMean();
142         final int pageRenderingMean = rumData.getPageRenderingMean();
143         final int totalTime = networkTimeMean + serverMean + domProcessingMean + pageRenderingMean;
144         final double networkPercent = 100d * networkTimeMean / totalTime;
145         final double serverPercent = 100d * serverMean / totalTime;
146         final double domProcessingPercent = 100d * domProcessingMean / totalTime;
147         final double pageRenderingPercent = 100d * pageRenderingMean / totalTime;
148
149         final PdfPTable table = new PdfPTable(2);
150         table.setHorizontalAlignment(Element.ALIGN_LEFT);
151         table.setWidthPercentage(25);
152         table.getDefaultCell().setBorderWidth(0);
153         table.addCell(new Phrase(I18N.getString("Network"), cellFont));
154         table.addCell(new Phrase(integerFormat.format(networkTimeMean) + " ms ("
155                 + percentFormat.format(networkPercent) + "%)", cellFont));
156         table.addCell(new Phrase(I18N.getString("Server"), cellFont));
157         table.addCell(new Phrase(integerFormat.format(serverMean) + " ms ("
158                 + percentFormat.format(serverPercent) + "%)", cellFont));
159         table.addCell(new Phrase(I18N.getString("DOM_processing"), cellFont));
160         table.addCell(new Phrase(integerFormat.format(domProcessingMean) + " ms ("
161                 + percentFormat.format(domProcessingPercent) + "%)", cellFont));
162         table.addCell(new Phrase(I18N.getString("Page_rendering"), cellFont));
163         table.addCell(new Phrase(integerFormat.format(pageRenderingMean) + " ms ("
164                 + percentFormat.format(pageRenderingPercent) + "%)", cellFont));
165         addToDocument(table);
166         addToDocument(new Phrase("\n", cellFont));
167     }
168
169     private void writeHeader() throws DocumentException {
170         final List<String> headers = createHeaders();
171         final int[] relativeWidths = new int[headers.size()];
172         Arrays.fill(relativeWidths, 0, headers.size(), 1);
173         relativeWidths[0] = 8; // requête
174
175         initTable(headers, relativeWidths);
176     }
177
178     private List<String> createHeaders() {
179         final List<String> headers = new ArrayList<>();
180         headers.add(getString("Requete"));
181         final boolean hasChildren = !request.getChildRequestsExecutionsByRequestId().isEmpty();
182         if (hasChildren) {
183             headers.add(getString("Hits_par_requete"));
184         }
185         headers.add(getString("Temps_moyen"));
186         headers.add(getString("Temps_max"));
187         headers.add(getString("Ecart_type"));
188         headers.add(getString("Temps_cpu_moyen"));
189         if (isAllocatedKBytesDisplayed()) {
190             headers.add(getString("Ko_alloues_moyens"));
191         }
192         headers.add(getString("erreur_systeme"));
193         final Counter parentCounter = getCounterByRequestId(request);
194         final boolean allChildHitsDisplayed = parentCounter != null
195                 && parentCounter.getChildCounterName() != null && request.hasChildHits();
196         if (allChildHitsDisplayed) {
197             final String childCounterName = parentCounter.getChildCounterName();
198             headers.add(getFormattedString("hits_fils_moyens", childCounterName));
199             headers.add(getFormattedString("temps_fils_moyen", childCounterName));
200         }
201         return headers;
202     }
203
204     private void writeRequests() throws IOException, DocumentException {
205         final Map<String, Long> childRequests = request.getChildRequestsExecutionsByRequestId();
206         final boolean hasChildren = !childRequests.isEmpty();
207         final Counter parentCounter = getCounterByRequestId(request);
208         final boolean allChildHitsDisplayed = parentCounter != null
209                 && parentCounter.getChildCounterName() != null && request.hasChildHits();
210
211         final PdfPCell defaultCell = getDefaultCell();
212         defaultCell.setLeading(2, 1);
213         defaultCell.setPaddingTop(0);
214
215         nextRow();
216         writeRequest(request, -1, allChildHitsDisplayed);
217
218         if (hasChildren) {
219             writeChildRequests(childRequests, allChildHitsDisplayed);
220         }
221     }
222
223     private void writeChildRequests(Map<String, Long> childRequests, boolean allChildHitsDisplayed)
224             throws IOException, DocumentException {
225         for (final Map.Entry<String, Long> entry : childRequests.entrySet()) {
226             final CounterRequest childRequest = requestsById.get(entry.getKey());
227             if (childRequest != null) {
228                 nextRow();
229                 final Long nbExecutions = entry.getValue();
230                 final float executionsByRequest = (float) nbExecutions / request.getHits();
231                 writeRequest(childRequest, executionsByRequest, allChildHitsDisplayed);
232             }
233         }
234     }
235
236     private void writeRequest(CounterRequest childRequest, float executionsByRequest,
237             boolean allChildHitsDisplayed) throws IOException, DocumentException {
238         final PdfPCell defaultCell = getDefaultCell();
239         defaultCell.setHorizontalAlignment(Element.ALIGN_LEFT);
240         final Paragraph paragraph = new Paragraph(defaultCell.getLeading() + cellFont.getSize());
241         if (executionsByRequest != -1) {
242             paragraph.setIndentationLeft(5);
243         }
244         final Counter parentCounter = getCounterByRequestId(childRequest);
245         if (parentCounter != null && parentCounter.getIconName() != null) {
246             paragraph.add(new Chunk(getSmallImage(parentCounter.getIconName()), 0, -1));
247         }
248         paragraph.add(new Phrase(childRequest.getName(), cellFont));
249         final PdfPCell requestCell = new PdfPCell();
250         requestCell.addElement(paragraph);
251         requestCell.setGrayFill(defaultCell.getGrayFill());
252         requestCell.setPaddingTop(defaultCell.getPaddingTop());
253         addCell(requestCell);
254
255         defaultCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
256         if (executionsByRequest != -1) {
257             addCell(nbExecutionsFormat.format(executionsByRequest));
258         } else {
259             final boolean hasChildren = !request.getChildRequestsExecutionsByRequestId().isEmpty();
260             if (hasChildren) {
261                 addCell("");
262             }
263         }
264         writeRequestValues(childRequest, allChildHitsDisplayed);
265     }
266
267     private void writeRequestValues(CounterRequest aRequest, boolean allChildHitsDisplayed) {
268         final PdfPCell defaultCell = getDefaultCell();
269         defaultCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
270         addCell(integerFormat.format(aRequest.getMean()));
271         addCell(integerFormat.format(aRequest.getMaximum()));
272         addCell(integerFormat.format(aRequest.getStandardDeviation()));
273         if (aRequest.getCpuTimeMean() >= 0) {
274             addCell(integerFormat.format(aRequest.getCpuTimeMean()));
275         } else {
276             addCell("");
277         }
278         if (isAllocatedKBytesDisplayed()) {
279             if (aRequest.getAllocatedKBytesMean() >= 0) {
280                 addCell(integerFormat.format(aRequest.getAllocatedKBytesMean()));
281             } else {
282                 addCell("");
283             }
284         }
285         addCell(systemErrorFormat.format(aRequest.getSystemErrorPercentage()));
286         if (allChildHitsDisplayed) {
287             final boolean childHitsDisplayed = aRequest.hasChildHits();
288             if (childHitsDisplayed) {
289                 addCell(integerFormat.format(aRequest.getChildHitsMean()));
290             } else {
291                 addCell("");
292             }
293             if (childHitsDisplayed) {
294                 addCell(integerFormat.format(aRequest.getChildDurationsMean()));
295             } else {
296                 addCell("");
297             }
298         }
299     }
300
301     private boolean isAllocatedKBytesDisplayed() {
302         return request.getAllocatedKBytesMean() >= 0;
303     }
304
305     private void writeGraph() throws IOException, DocumentException {
306         final JRobin jrobin = collector.getJRobin(graphName);
307         if (jrobin != null) {
308             final byte[] img = jrobin.graph(range, 960, 400);
309             final Image image = Image.getInstance(img);
310             image.scalePercent(50);
311
312             final PdfPTable table = new PdfPTable(1);
313             table.setHorizontalAlignment(Element.ALIGN_CENTER);
314             table.setWidthPercentage(100);
315             table.getDefaultCell().setBorder(0);
316             table.addCell("\n");
317             table.addCell(image);
318             table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
319             table.addCell(new Phrase(getString("graph_units"), cellFont));
320             addToDocument(table);
321         } else {
322             // just in case request is null and collector.getJRobin(graphName) is null, we must write something in the document
323             addToDocument(new Phrase("\n", cellFont));
324         }
325     }
326
327     private void writeSqlRequestExplainPlan() throws DocumentException {
328         try {
329             final String explainPlan;
330             if (collectorServer == null) {
331                 explainPlan = DatabaseInformations.explainPlanFor(request.getName());
332             } else {
333                 explainPlan = collectorServer.collectSqlRequestExplainPlan(
334                         collector.getApplication(), request.getName());
335             }
336             if (explainPlan != null) {
337                 final Paragraph paragraph = new Paragraph("", cellFont);
338                 paragraph.add(new Phrase('\n' + getString("Plan_d_execution") + '\n', boldFont));
339                 paragraph.add(new Phrase(explainPlan, courierFont));
340                 addToDocument(paragraph);
341             }
342         } catch (final Exception e) {
343             final Paragraph paragraph = new Paragraph("", cellFont);
344             paragraph.add(new Phrase('\n' + getString("Plan_d_execution") + '\n', boldFont));
345             paragraph.add(new Phrase(e.toString(), cellFont));
346             addToDocument(paragraph);
347         }
348     }
349
350     private Counter getCounterByRequestId(CounterRequest aRequest) {
351         final String myRequestId = aRequest.getId();
352         for (final Counter counter : counters) {
353             if (counter.isRequestIdFromThisCounter(myRequestId)) {
354                 return counter;
355             }
356         }
357         return null;
358     }
359
360     private boolean isGraphDisplayed() throws IOException {
361         return request == null || getCounterByRequestId(request) != null
362                 && HtmlCounterReport.isRequestGraphDisplayed(getCounterByRequestId(request))
363                 // on vérifie aussi que l'instance de jrobin existe pour faire le graph,
364                 // notamment si les statistiques ont été réinitialisées, ce qui vide les instances de jrobin
365                 && collector.getJRobin(request.getId()) != null;
366     }
367
368     private Image getSmallImage(String resourceFileName) throws DocumentException, IOException {
369         return pdfDocumentFactory.getSmallImage(resourceFileName);
370     }
371 }
372