/* * Copyright 2008-2017 by Emeric Vernat * * This file is part of Java Melody. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.bull.javamelody; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import com.lowagie.text.Chunk; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.Font; import com.lowagie.text.FontFactory; import com.lowagie.text.Image; import com.lowagie.text.Paragraph; import com.lowagie.text.Phrase; import com.lowagie.text.pdf.PdfPCell; import com.lowagie.text.pdf.PdfPTable; /** * Rapport pdf pour le détail d'une requête. * @author Emeric Vernat */ public class PdfRequestAndGraphDetailReport extends PdfAbstractTableReport { private final Collector collector; private final CollectorServer collectorServer; private final Range range; private final List<Counter> counters; private final String graphName; private final CounterRequest request; private final Map<String, CounterRequest> requestsById; private final PdfDocumentFactory pdfDocumentFactory; private final DecimalFormat systemErrorFormat = I18N.createPercentFormat(); private final DecimalFormat nbExecutionsFormat = I18N.createPercentFormat(); private final DecimalFormat integerFormat = I18N.createIntegerFormat(); private final Font cellFont = PdfFonts.TABLE_CELL.getFont(); private final Font boldFont = PdfFonts.BOLD.getFont(); private final Font courierFont = FontFactory.getFont(FontFactory.COURIER, 5.5f, Font.NORMAL); PdfRequestAndGraphDetailReport(Collector collector, CollectorServer collectorServer, Range range, String graphName, PdfDocumentFactory pdfDocumentFactory, Document document) throws IOException { super(document); assert collector != null; assert range != null; assert graphName != null; assert pdfDocumentFactory != null; this.collector = collector; this.collectorServer = collectorServer; this.range = range; this.graphName = graphName; this.counters = collector.getRangeCounters(range); this.requestsById = mapAllRequestsById(); this.request = requestsById.get(graphName); this.pdfDocumentFactory = pdfDocumentFactory; } private Map<String, CounterRequest> mapAllRequestsById() { final Map<String, CounterRequest> result = new HashMap<String, CounterRequest>(); for (final Counter counter : counters) { for (final CounterRequest aRequest : counter.getRequests()) { result.put(aRequest.getId(), aRequest); } } return result; } @Override void toPdf() throws DocumentException, IOException { if (request != null) { if (request.getRumData() != null && request.getRumData().getHits() != 0) { writeRequestRumData(); } writeHeader(); writeRequests(); addTableToDocument(); if (JdbcWrapper.SINGLETON.getSqlCounter().isRequestIdFromThisCounter(request.getId()) && !request.getName().toLowerCase(Locale.ENGLISH).startsWith("alter ")) { // inutile d'essayer d'avoir le plan d'exécution des requêtes sql // telles que "alter session set ..." (cf issue 152) writeSqlRequestExplainPlan(); } } if (isGraphDisplayed()) { writeGraph(); } if (request != null && request.getStackTrace() != null) { final Paragraph paragraph = new Paragraph("\n", cellFont); paragraph.setIndentationLeft(20); paragraph.setIndentationRight(20); paragraph.add(new Phrase("Stack-trace\n", boldFont)); paragraph.add(new Phrase(request.getStackTrace().replace("\t", " "), cellFont)); addToDocument(paragraph); } } private void writeRequestRumData() throws DocumentException { final CounterRequestRumData rumData = request.getRumData(); final DecimalFormat percentFormat = I18N.createPercentFormat(); final int networkTimeMean = rumData.getNetworkTimeMean(); final int serverMean = request.getMean(); final int domProcessingMean = rumData.getDomProcessingMean(); final int pageRenderingMean = rumData.getPageRenderingMean(); final int totalTime = networkTimeMean + serverMean + domProcessingMean + pageRenderingMean; final double networkPercent = 100d * networkTimeMean / totalTime; final double serverPercent = 100d * serverMean / totalTime; final double domProcessingPercent = 100d * domProcessingMean / totalTime; final double pageRenderingPercent = 100d * pageRenderingMean / totalTime; final PdfPTable table = new PdfPTable(2); table.setHorizontalAlignment(Element.ALIGN_LEFT); table.setWidthPercentage(25); table.getDefaultCell().setBorderWidth(0); table.addCell(new Phrase(I18N.getString("Network"), cellFont)); table.addCell(new Phrase(integerFormat.format(networkTimeMean) + " ms (" + percentFormat.format(networkPercent) + "%)", cellFont)); table.addCell(new Phrase(I18N.getString("Server"), cellFont)); table.addCell(new Phrase(integerFormat.format(serverMean) + " ms (" + percentFormat.format(serverPercent) + "%)", cellFont)); table.addCell(new Phrase(I18N.getString("DOM_processing"), cellFont)); table.addCell(new Phrase(integerFormat.format(domProcessingMean) + " ms (" + percentFormat.format(domProcessingPercent) + "%)", cellFont)); table.addCell(new Phrase(I18N.getString("Page_rendering"), cellFont)); table.addCell(new Phrase(integerFormat.format(pageRenderingMean) + " ms (" + percentFormat.format(pageRenderingPercent) + "%)", cellFont)); addToDocument(table); addToDocument(new Phrase("\n", cellFont)); } private void writeHeader() throws DocumentException { final List<String> headers = createHeaders(); final int[] relativeWidths = new int[headers.size()]; Arrays.fill(relativeWidths, 0, headers.size(), 1); relativeWidths[0] = 8; // requête initTable(headers, relativeWidths); } private List<String> createHeaders() { final List<String> headers = new ArrayList<String>(); headers.add(getString("Requete")); final boolean hasChildren = !request.getChildRequestsExecutionsByRequestId().isEmpty(); if (hasChildren) { headers.add(getString("Hits_par_requete")); } headers.add(getString("Temps_moyen")); headers.add(getString("Temps_max")); headers.add(getString("Ecart_type")); headers.add(getString("Temps_cpu_moyen")); headers.add(getString("erreur_systeme")); final Counter parentCounter = getCounterByRequestId(request); final boolean allChildHitsDisplayed = parentCounter != null && parentCounter.getChildCounterName() != null && request.hasChildHits(); if (allChildHitsDisplayed) { final String childCounterName = parentCounter.getChildCounterName(); headers.add(getFormattedString("hits_fils_moyens", childCounterName)); headers.add(getFormattedString("temps_fils_moyen", childCounterName)); } return headers; } private void writeRequests() throws IOException, DocumentException { final Map<String, Long> childRequests = request.getChildRequestsExecutionsByRequestId(); final boolean hasChildren = !childRequests.isEmpty(); final Counter parentCounter = getCounterByRequestId(request); final boolean allChildHitsDisplayed = parentCounter != null && parentCounter.getChildCounterName() != null && request.hasChildHits(); final PdfPCell defaultCell = getDefaultCell(); defaultCell.setLeading(2, 1); defaultCell.setPaddingTop(0); nextRow(); writeRequest(request, -1, allChildHitsDisplayed); if (hasChildren) { writeChildRequests(childRequests, allChildHitsDisplayed); } } private void writeChildRequests(Map<String, Long> childRequests, boolean allChildHitsDisplayed) throws IOException, DocumentException { for (final Map.Entry<String, Long> entry : childRequests.entrySet()) { final CounterRequest childRequest = requestsById.get(entry.getKey()); if (childRequest != null) { nextRow(); final Long nbExecutions = entry.getValue(); final float executionsByRequest = (float) nbExecutions / request.getHits(); writeRequest(childRequest, executionsByRequest, allChildHitsDisplayed); } } } private void writeRequest(CounterRequest childRequest, float executionsByRequest, boolean allChildHitsDisplayed) throws IOException, DocumentException { final PdfPCell defaultCell = getDefaultCell(); defaultCell.setHorizontalAlignment(Element.ALIGN_LEFT); final Paragraph paragraph = new Paragraph(defaultCell.getLeading() + cellFont.getSize()); if (executionsByRequest != -1) { paragraph.setIndentationLeft(5); } final Counter parentCounter = getCounterByRequestId(childRequest); if (parentCounter != null && parentCounter.getIconName() != null) { paragraph.add(new Chunk(getSmallImage(parentCounter.getIconName()), 0, -1)); } paragraph.add(new Phrase(childRequest.getName(), cellFont)); final PdfPCell requestCell = new PdfPCell(); requestCell.addElement(paragraph); requestCell.setGrayFill(defaultCell.getGrayFill()); requestCell.setPaddingTop(defaultCell.getPaddingTop()); addCell(requestCell); defaultCell.setHorizontalAlignment(Element.ALIGN_RIGHT); if (executionsByRequest != -1) { addCell(nbExecutionsFormat.format(executionsByRequest)); } else { addCell(""); } writeRequestValues(childRequest, allChildHitsDisplayed); } private void writeRequestValues(CounterRequest aRequest, boolean allChildHitsDisplayed) { final PdfPCell defaultCell = getDefaultCell(); defaultCell.setHorizontalAlignment(Element.ALIGN_RIGHT); addCell(integerFormat.format(aRequest.getMean())); addCell(integerFormat.format(aRequest.getMaximum())); addCell(integerFormat.format(aRequest.getStandardDeviation())); if (aRequest.getCpuTimeMean() >= 0) { addCell(integerFormat.format(aRequest.getCpuTimeMean())); } else { addCell(""); } addCell(systemErrorFormat.format(aRequest.getSystemErrorPercentage())); if (allChildHitsDisplayed) { final boolean childHitsDisplayed = aRequest.hasChildHits(); if (childHitsDisplayed) { addCell(integerFormat.format(aRequest.getChildHitsMean())); } else { addCell(""); } if (childHitsDisplayed) { addCell(integerFormat.format(aRequest.getChildDurationsMean())); } else { addCell(""); } } } private void writeGraph() throws IOException, DocumentException { final JRobin jrobin = collector.getJRobin(graphName); if (jrobin != null) { final byte[] img = jrobin.graph(range, 960, 400); final Image image = Image.getInstance(img); image.scalePercent(50); final PdfPTable table = new PdfPTable(1); table.setHorizontalAlignment(Element.ALIGN_CENTER); table.setWidthPercentage(100); table.getDefaultCell().setBorder(0); table.addCell("\n"); table.addCell(image); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT); table.addCell(new Phrase(getString("graph_units"), cellFont)); addToDocument(table); } else { // just in case request is null and collector.getJRobin(graphName) is null, we must write something in the document addToDocument(new Phrase("\n", cellFont)); } } private void writeSqlRequestExplainPlan() throws DocumentException { try { final String explainPlan; if (collectorServer == null) { explainPlan = DatabaseInformations.explainPlanFor(request.getName()); } else { explainPlan = collectorServer.collectSqlRequestExplainPlan( collector.getApplication(), request.getName()); } if (explainPlan != null) { final Paragraph paragraph = new Paragraph("", cellFont); paragraph.add(new Phrase('\n' + getString("Plan_d_execution") + '\n', boldFont)); paragraph.add(new Phrase(explainPlan, courierFont)); addToDocument(paragraph); } } catch (final Exception e) { final Paragraph paragraph = new Paragraph("", cellFont); paragraph.add(new Phrase('\n' + getString("Plan_d_execution") + '\n', boldFont)); paragraph.add(new Phrase(e.toString(), cellFont)); addToDocument(paragraph); } } private Counter getCounterByRequestId(CounterRequest aRequest) { final String myRequestId = aRequest.getId(); for (final Counter counter : counters) { if (counter.isRequestIdFromThisCounter(myRequestId)) { return counter; } } return null; } private boolean isGraphDisplayed() throws IOException { return request == null || getCounterByRequestId(request) != null && HtmlCounterReport.isRequestGraphDisplayed(getCounterByRequestId(request)) // on vérifie aussi que l'instance de jrobin existe pour faire le graph, // notamment si les statistiques ont été réinitialisées, ce qui vide les instances de jrobin && collector.getJRobin(request.getId()) != null; } private Image getSmallImage(String resourceFileName) throws DocumentException, IOException { return pdfDocumentFactory.getSmallImage(resourceFileName); } }