net.bull.javamelody.internal.web.pdf.PdfRuntimeDependenciesReport.java Source code

Java tutorial

Introduction

Here is the source code for net.bull.javamelody.internal.web.pdf.PdfRuntimeDependenciesReport.java

Source

/*
 * 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.internal.web.pdf;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;

import net.bull.javamelody.internal.model.Counter;
import net.bull.javamelody.internal.model.CounterRequest;

/**
 * Partie du rapport pdf pour les dpendances en excution.
 * @author Emeric Vernat
 */
class PdfRuntimeDependenciesReport extends PdfAbstractReport {
    private final Counter counter;
    private final Font warningCellFont = PdfFonts.WARNING_CELL.getFont();
    private final Font severeCellFont = PdfFonts.SEVERE_CELL.getFont();
    private final Font normalFont = PdfFonts.NORMAL.getFont();
    private final Font cellFont = PdfFonts.TABLE_CELL.getFont();
    private final Font boldCellFont = PdfFonts.BOLD_CELL.getFont();
    private PdfPTable currentTable;
    private List<String> calledBeans;
    private double standardDeviation;

    PdfRuntimeDependenciesReport(Counter counter, Document document) {
        super(document);
        assert counter != null;
        this.counter = counter;
    }

    /**
     * Retourne les dpendances entre classes selon les requtes de counter,
     * selon les mthodes rellement excuts et monitores sur la priode slectionne.<br/>
     * Le niveau de dpendance entre chaque classe est indiqu par le nombre de mthodes
     * diffrentes appeles depuis une classe vers une autre classe
     * (et non par le nombre d'excutions et non plus par le nombre d'appels de mthodes).<br/>
     * Une classe ne peut pas avoir de dpendance avec elle-mme.<br/>
     * Exemple:<br/><pre>
     * ex :
     * public class A {
     *    @Inject
     *    private B b;
     *
     *    public void a() {
     *       b.b1();
     *       for (int i = 0; i < 100; i++) {
     *          b.b2();
     *       }
     *       if (false) {
     *          b.b3();
     *       }
     *    }
     * }
     * </pre>
     * Si seulement les classes A et B sont monitores et si la mthode "a" est appele au moins
     * une fois, alors les "runtimeDependencies" contiendront une dpendance de A vers B.
     * Cette dpendance sera de 2 mthodes (une pour "b1" et une pour "b2").
     * @return Map
     */
    Map<String, Map<String, Integer>> getRuntimeDependencies() {
        final Map<String, Set<CounterRequest>> methodsCalledByCallerBeans = getMethodsCalledByCallerBeans();

        final Map<String, Map<String, Integer>> result = new HashMap<String, Map<String, Integer>>();
        for (final Map.Entry<String, Set<CounterRequest>> entry : methodsCalledByCallerBeans.entrySet()) {
            final String callerBean = entry.getKey();
            final Set<CounterRequest> childRequests = entry.getValue();
            final Map<String, Integer> nbMethodsCalledByCalledBean = new HashMap<String, Integer>();
            for (final CounterRequest childRequest : childRequests) {
                final String calledBean = getClassNameFromRequest(childRequest);
                if (callerBean.equals(calledBean)) {
                    // si le bean est lui mme, il peut s'appeler autant qu'il veut
                    // ce n'est pas une dpendance
                    continue;
                }
                Integer nbOfMethodCalls = nbMethodsCalledByCalledBean.get(calledBean);
                if (nbOfMethodCalls == null) {
                    nbOfMethodCalls = 1;
                } else {
                    // on compte le nombre de mthodes appeles et non le nombre d'excutions
                    nbOfMethodCalls = nbOfMethodCalls + 1;
                }
                nbMethodsCalledByCalledBean.put(calledBean, nbOfMethodCalls);
            }

            // methodsCalled peut tre vide soit parce que childRequestsByBeans est vide
            // (il n'y a pas d'appels de mthodes pour ce bean, que des requtes sql par exemple),
            // soit parce qu'il n'y a que des appels de mthodes vers le bean lui-mme
            if (!nbMethodsCalledByCalledBean.isEmpty()) {
                result.put(callerBean, nbMethodsCalledByCalledBean);
            }
        }

        return result;
    }

    private Map<String, Set<CounterRequest>> getMethodsCalledByCallerBeans() {
        assert counter.isBusinessFacadeCounter();
        final List<CounterRequest> requests = counter.getRequests();
        final Map<String, CounterRequest> requestsById = new HashMap<String, CounterRequest>();
        for (final CounterRequest request : requests) {
            requestsById.put(request.getId(), request);
        }

        // on compte le nombre de mthodes diffrentes appeles depuis un bean vers un autre bean
        // et non le nombre d'excutions et non plus le nombre d'appels de mthodes
        final Map<String, Set<CounterRequest>> methodsCalledByCallerBeans = new HashMap<String, Set<CounterRequest>>();
        for (final CounterRequest request : requests) {
            final Set<String> childRequestIds = request.getChildRequestsExecutionsByRequestId().keySet();
            if (childRequestIds.isEmpty()) {
                continue;
            }
            final String callerBean = getClassNameFromRequest(request);
            // c'est un Set donc on ne compte une mthode 'a' appele depuis une classe qu'une seule fois
            // mme si elle est appele deux fois depuis la mme classe
            // ou mme  un seul endroit mais avec des excutions dans une boucle
            Set<CounterRequest> childRequests = methodsCalledByCallerBeans.get(callerBean);
            if (childRequests == null) {
                childRequests = new HashSet<CounterRequest>();
                methodsCalledByCallerBeans.put(callerBean, childRequests);
            }
            for (final String childRequestId : childRequestIds) {
                // on ne regarde que les requtes du mme counter
                // (pas sql, et pas guice si on est dans spring)
                if (counter.isRequestIdFromThisCounter(childRequestId)) {
                    final CounterRequest childRequest = requestsById.get(childRequestId);
                    // il peut y avoir une non synchronisation temporaire entre la liste des requtes
                    // et les requtes filles
                    if (childRequest != null) {
                        childRequests.add(childRequest);
                    }
                }
            }
        }
        return methodsCalledByCallerBeans;
    }

    private static String getClassNameFromRequest(CounterRequest request) {
        final int lastIndexOf = request.getName().lastIndexOf('.');
        if (lastIndexOf != -1) {
            return request.getName().substring(0, lastIndexOf);
        }
        return request.getName();
    }

    private static double getStandardDeviation(Map<String, Map<String, Integer>> runtimeDependencies) {
        final List<Integer> values = new ArrayList<Integer>();
        int sum = 0;
        for (final Map<String, Integer> beanDependencies : runtimeDependencies.values()) {
            for (final Integer value : beanDependencies.values()) {
                values.add(value);
                sum += value;
            }
        }
        final int mean = sum / values.size();
        int square = 0;
        for (final Integer value : values) {
            final int diff = value - mean;
            square += diff * diff;
        }
        return Math.sqrt((double) square / values.size());
    }

    private List<String> getCalledBeans(Map<String, Map<String, Integer>> runtimeDependencies) {
        final Set<String> calledBeansSet = new HashSet<String>();
        for (final Map<String, Integer> values : runtimeDependencies.values()) {
            calledBeansSet.addAll(values.keySet());
        }
        final List<String> result = new ArrayList<String>(calledBeansSet);
        Collections.sort(result);
        return result;
    }

    @Override
    void toPdf() throws DocumentException {
        final Map<String, Map<String, Integer>> runtimeDependencies = getRuntimeDependencies();
        if (runtimeDependencies.isEmpty()) {
            addToDocument(new Phrase(getString("Aucune_dependance"), normalFont));
            return;
        }
        addToDocument(new Phrase(getString("runtime_dependencies_desc"), normalFont));
        this.standardDeviation = getStandardDeviation(runtimeDependencies);
        this.calledBeans = getCalledBeans(runtimeDependencies);

        writeHeader();

        final List<String> callerBeans = new ArrayList<String>(runtimeDependencies.keySet());
        Collections.sort(callerBeans);
        final PdfPCell defaultCell = getDefaultCell();
        boolean odd = false;
        for (final String callerBean : callerBeans) {
            if (odd) {
                defaultCell.setGrayFill(0.97f);
            } else {
                defaultCell.setGrayFill(1);
            }
            odd = !odd; // NOPMD
            final Map<String, Integer> beanDependencies = runtimeDependencies.get(callerBean);
            writeBeanDependencies(callerBean, beanDependencies);
        }
        addToDocument(currentTable);
    }

    private void writeBeanDependencies(String callerBean, Map<String, Integer> beanDependencies) {
        getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
        addCell(callerBean, cellFont);
        getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
        for (final String calledBean : calledBeans) {
            final Integer dependency = beanDependencies.get(calledBean);
            if (dependency == null) {
                addCell("", cellFont);
            } else {
                final String s = dependency.toString();
                if (dependency > standardDeviation * 2) {
                    addCell(s, severeCellFont);
                } else if (dependency > standardDeviation) {
                    addCell(s, warningCellFont);
                } else {
                    addCell(s, cellFont);
                }
            }
        }
    }

    private void writeHeader() throws DocumentException {
        final List<String> headers = new ArrayList<String>();
        headers.add("Beans");
        headers.addAll(calledBeans);
        final int[] relativeWidths = new int[headers.size()];
        Arrays.fill(relativeWidths, 0, headers.size(), 1);
        relativeWidths[0] = 4;

        final PdfPTable table = new PdfPTable(headers.size());
        table.setWidthPercentage(100);
        table.setWidths(relativeWidths);
        table.setHeaderRows(1);
        final PdfPCell defaultCell = table.getDefaultCell();
        defaultCell.setGrayFill(0.9f);
        defaultCell.setHorizontalAlignment(Element.ALIGN_CENTER);
        defaultCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        defaultCell.setPaddingLeft(0);
        defaultCell.setPaddingRight(0);
        for (final String header : headers) {
            table.addCell(new Phrase(header, boldCellFont));
            // pas la premire entte de colonne
            defaultCell.setRotation(90);
        }
        defaultCell.setRotation(0);
        defaultCell.setPaddingLeft(2);
        defaultCell.setPaddingRight(2);
        currentTable = table;
    }

    private PdfPCell getDefaultCell() {
        return currentTable.getDefaultCell();
    }

    private void addCell(String string, Font font) {
        currentTable.addCell(new Phrase(string, font));
    }
}