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.Collections;
25 import java.util.List;
26
27 import com.lowagie.text.BadElementException;
28 import com.lowagie.text.Document;
29 import com.lowagie.text.DocumentException;
30 import com.lowagie.text.Element;
31 import com.lowagie.text.Font;
32 import com.lowagie.text.Image;
33 import com.lowagie.text.Paragraph;
34 import com.lowagie.text.Phrase;
35
36 import net.bull.javamelody.internal.common.I18N;
37 import net.bull.javamelody.internal.model.Collector;
38 import net.bull.javamelody.internal.model.Counter;
39 import net.bull.javamelody.internal.model.CounterRequest;
40 import net.bull.javamelody.internal.model.CounterRequestAggregation;
41 import net.bull.javamelody.internal.model.JRobin;
42 import net.bull.javamelody.internal.model.Range;
43
44 /**
45  * Partie du rapport pdf pour un compteur.
46  * @author Emeric Vernat
47  */

48 class PdfCounterReport extends PdfAbstractTableReport {
49     private final Collector collector;
50     private final Counter counter;
51     private final Range range;
52     private final boolean includeGraph;
53     private final CounterRequestAggregation counterRequestAggregation;
54     private final DecimalFormat systemErrorFormat = I18N.createPercentFormat();
55     private final DecimalFormat integerFormat = I18N.createIntegerFormat();
56     private final Font infoCellFont = PdfFonts.INFO_CELL.getFont();
57     private final Font warningCellFont = PdfFonts.WARNING_CELL.getFont();
58     private final Font severeCellFont = PdfFonts.SEVERE_CELL.getFont();
59     private final Font normalFont = PdfFonts.NORMAL.getFont();
60
61     PdfCounterReport(Collector collector, Counter counter, Range range, boolean includeGraph,
62             Document document) {
63         super(document);
64         assert collector != null;
65         assert counter != null;
66         assert range != null;
67         this.collector = collector;
68         this.counter = counter;
69         this.range = range;
70         this.includeGraph = includeGraph;
71         this.counterRequestAggregation = new CounterRequestAggregation(counter);
72     }
73
74     @Override
75     void toPdf() throws DocumentException, IOException {
76         final List<CounterRequest> requests = counterRequestAggregation.getRequests();
77         if (requests.isEmpty()) {
78             writeNoRequests();
79         } else if (isErrorAndNotJobCounter()) {
80             // il y a au moins une "request" d'erreur puisque la liste n'est pas vide
81             assert !requests.isEmpty();
82             final List<CounterRequest> summaryRequest = Collections.singletonList(requests.get(0));
83             writeRequests(counter.getChildCounterName(), summaryRequest);
84         } else {
85             // 1. synthèse
86             final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
87             final List<CounterRequest> summaryRequests = Arrays.asList(globalRequest,
88                     counterRequestAggregation.getWarningRequest(),
89                     counterRequestAggregation.getSevereRequest());
90             writeRequests(counter.getChildCounterName(), summaryRequests);
91         }
92     }
93
94     String getCounterName() {
95         return counter.getName();
96     }
97
98     String getCounterIconName() {
99         return counter.getIconName();
100     }
101
102     boolean isErrorCounter() {
103         return counter.isErrorCounter();
104     }
105
106     private boolean isJobCounter() {
107         return counter.isJobCounter();
108     }
109
110     private boolean isErrorAndNotJobCounter() {
111         return isErrorCounter() && !isJobCounter();
112     }
113
114     void writeRequestDetails() throws DocumentException, IOException {
115         // détails des requêtes
116         final List<CounterRequest> requests = counterRequestAggregation.getRequests();
117         if (requests.isEmpty()) {
118             writeNoRequests();
119         } else {
120             // on n'inclue pas pour l'instant les graphs d'évolution des requêtes
121             // pour des raisons de place et de volume
122             writeRequests(counter.getChildCounterName(), requests);
123         }
124     }
125
126     private void writeNoRequests() throws DocumentException {
127         final String msg;
128         if (isJobCounter()) {
129             msg = "Aucun_job";
130         } else if (isErrorCounter()) {
131             msg = "Aucune_erreur";
132         } else {
133             msg = "Aucune_requete";
134         }
135         addToDocument(new Phrase(getString(msg), normalFont));
136     }
137
138     void writeErrorDetails() throws DocumentException {
139         // détails des erreurs
140         new PdfCounterErrorReport(counter, getDocument()).toPdf();
141     }
142
143     void writeRequests(String childCounterName, List<CounterRequest> requestList)
144             throws DocumentException, IOException {
145         assert requestList != null;
146         writeHeader(childCounterName);
147
148         for (final CounterRequest request : requestList) {
149             nextRow();
150             writeRequest(request);
151         }
152         addTableToDocument();
153
154         // débit et liens
155         writeFooter();
156     }
157
158     private void writeHeader(String childCounterName) throws DocumentException {
159         final List<String> headers = createHeaders(childCounterName);
160         final int[] relativeWidths = new int[headers.size()];
161         Arrays.fill(relativeWidths, 0, headers.size(), 1);
162         relativeWidths[0] = 10; // requête
163         if (includeGraph) {
164             relativeWidths[0] = 8;
165             relativeWidths[1] = 2; // graph d'évolution
166         }
167
168         initTable(headers, relativeWidths);
169     }
170
171     private List<String> createHeaders(String childCounterName) {
172         final List<String> headers = new ArrayList<>();
173         headers.add(getRequestHeader());
174         if (includeGraph) {
175             headers.add(getString("Evolution"));
176         }
177         if (counterRequestAggregation.isTimesDisplayed()) {
178             headers.add(getString("temps_cumule"));
179             headers.add(getString("Hits"));
180             headers.add(getString("Temps_moyen"));
181             headers.add(getString("Temps_max"));
182             headers.add(getString("Ecart_type"));
183         } else {
184             headers.add(getString("Hits"));
185         }
186         if (counterRequestAggregation.isCpuTimesDisplayed()) {
187             headers.add(getString("temps_cpu_cumule"));
188             headers.add(getString("Temps_cpu_moyen"));
189         }
190         if (counterRequestAggregation.isAllocatedKBytesDisplayed()) {
191             headers.add(getString("Ko_alloues_moyens"));
192         }
193         if (!isErrorAndNotJobCounter()) {
194             headers.add(getString("erreur_systeme"));
195         }
196         if (counterRequestAggregation.isResponseSizeDisplayed()) {
197             headers.add(getString("Taille_moyenne"));
198         }
199         if (counterRequestAggregation.isChildHitsDisplayed()) {
200             headers.add(getFormattedString("hits_fils_moyens", childCounterName));
201             headers.add(getFormattedString("temps_fils_moyen", childCounterName));
202         }
203         return headers;
204     }
205
206     private String getRequestHeader() {
207         if (isJobCounter()) {
208             return getString("Job");
209         } else if (isErrorCounter()) {
210             return getString("Erreur");
211         } else {
212             return getString("Requete");
213         }
214     }
215
216     private void writeFooter() throws DocumentException {
217         final List<CounterRequest> requests = counterRequestAggregation.getRequests();
218         final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
219         // delta ni négatif ni à 0
220         final long deltaMillis = Math
221                 .max(System.currentTimeMillis() - counter.getStartDate().getTime(), 1);
222         final long hitsParMinute = 60 * 1000 * globalRequest.getHits() / deltaMillis;
223         final String key;
224         if (isJobCounter()) {
225             key = "nb_jobs";
226         } else if (isErrorCounter()) {
227             key = "nb_erreurs";
228         } else {
229             key = "nb_requetes";
230         }
231         final Paragraph footer = new Paragraph(getFormattedString(key,
232                 integerFormat.format(hitsParMinute), integerFormat.format(requests.size())),
233                 normalFont);
234         footer.setAlignment(Element.ALIGN_RIGHT);
235         addToDocument(footer);
236     }
237
238     private void writeRequest(CounterRequest request) throws BadElementException, IOException {
239         getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
240         addCell(getShortRequestName(request));
241         if (includeGraph) {
242             writeRequestGraph(request);
243         }
244         getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
245         final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
246         if (counterRequestAggregation.isTimesDisplayed()) {
247             addPercentageCell(request.getDurationsSum(), globalRequest.getDurationsSum());
248             addCell(integerFormat.format(request.getHits()));
249             final int mean = request.getMean();
250             addCell(new Phrase(integerFormat.format(mean), getSlaFont(mean)));
251             addCell(integerFormat.format(request.getMaximum()));
252             addCell(integerFormat.format(request.getStandardDeviation()));
253         } else {
254             addCell(integerFormat.format(request.getHits()));
255         }
256         if (counterRequestAggregation.isCpuTimesDisplayed()) {
257             addPercentageCell(request.getCpuTimeSum(), globalRequest.getCpuTimeSum());
258             final int cpuTimeMean = request.getCpuTimeMean();
259             addCell(new Phrase(integerFormat.format(cpuTimeMean), getSlaFont(cpuTimeMean)));
260         }
261         if (counterRequestAggregation.isAllocatedKBytesDisplayed()) {
262             final long allocatedKBytesMean = request.getAllocatedKBytesMean();
263             addCell(integerFormat.format(allocatedKBytesMean));
264         }
265         if (!isErrorAndNotJobCounter()) {
266             addCell(systemErrorFormat.format(request.getSystemErrorPercentage()));
267         }
268         if (counterRequestAggregation.isResponseSizeDisplayed()) {
269             addCell(integerFormat.format(request.getResponseSizeMean() / 1024L));
270         }
271         if (counterRequestAggregation.isChildHitsDisplayed()) {
272             addCell(integerFormat.format(request.getChildHitsMean()));
273             addCell(integerFormat.format(request.getChildDurationsMean()));
274         }
275     }
276
277     private String getShortRequestName(CounterRequest request) {
278         final String name = request.getName();
279         if (name.length() > 1000) {
280             // si la requête fait plus de 1000 caractères, on la coupe pour y voir quelque chose
281             return name.substring(0, 1000) + "...";
282         }
283         return name;
284     }
285
286     private void writeRequestGraph(CounterRequest request) throws BadElementException, IOException {
287         final JRobin jrobin = collector.getJRobin(request.getId());
288         if (jrobin == null) {
289             addCell("");
290         } else {
291             final byte[] img = jrobin.graph(range, 100, 50);
292             final Image image = Image.getInstance(img);
293             image.scalePercent(50);
294             addCell(image);
295         }
296     }
297
298     Font getSlaFont(int mean) {
299         final Font font;
300         if (mean < counterRequestAggregation.getWarningThreshold() || mean == 0) {
301             // si cette moyenne est < à la moyenne globale + 1 écart-type (paramétrable), c'est bien
302             font = infoCellFont;
303         } else if (mean < counterRequestAggregation.getSevereThreshold()) {
304             // sinon, si cette moyenne est < à la moyenne globale + 2 écart-types (paramétrable),
305             // attention à cette requête qui est plus longue que les autres
306             font = warningCellFont;
307         } else {
308             // sinon, (cette moyenne est > à la moyenne globale + 2 écart-types),
309             // cette requête est très longue par rapport aux autres ;
310             // il peut être opportun de l'optimiser si possible
311             font = severeCellFont;
312         }
313         return font;
314     }
315
316     private void addPercentageCell(long dividende, long diviseur) {
317         if (diviseur == 0) {
318             addCell("0");
319         } else {
320             addCell(integerFormat.format(100 * dividende / diviseur));
321         }
322     }
323 }
324