/*
* 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);
}
}