All Downloads are FREE. Search and download functionalities are using the official Maven repository.

patterntesting.runtime.monitor.ProfileStatistic Maven / Gradle / Ivy

Go to download

PatternTesting Runtime (patterntesting-rt) is the runtime component for the PatternTesting framework. It provides the annotations and base classes for the PatternTesting testing framework (e.g. patterntesting-check, patterntesting-concurrent or patterntesting-exception) but can be also used standalone for classpath monitoring or profiling. It uses AOP and AspectJ to perform this feat.

There is a newer version: 2.5.0
Show newest version
/*
 * $Id: ProfileStatistic.java,v 1.36 2016/01/06 20:46:27 oboehm Exp $
 *
 * Copyright (c) 2008 by Oliver Boehm
 *
 * 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 orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 22.12.2008 by oliver ([email protected])
 */
package patterntesting.runtime.monitor;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;

import org.aspectj.lang.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import patterntesting.annotation.check.runtime.MayReturnNull;
import patterntesting.runtime.annotation.DontProfileMe;
import patterntesting.runtime.jmx.MBeanHelper;
import patterntesting.runtime.util.Environment;
import patterntesting.runtime.util.SignatureHelper;

/**
 * This is constructed as a thin layer around com.jamonapi.MonitorFactory for
 * the needs of patterntesting. The reason for this layer is that sometimes you
 * want to minimize the use of other libraries. So this implementation
 * provides also an implementation if the JaMon library is missing.
 *
 * @author oliver
 * @version $Revision: 1.36 $
 * @see com.jamonapi.MonitorFactory
 * @since 22.12.2008
 */
public class ProfileStatistic extends Thread implements ProfileStatisticMBean {

    private static final ProfileStatistic INSTANCE;
    private static final Logger LOG = LoggerFactory.getLogger(ProfileStatistic.class);

    private ObjectName mbeanName = MBeanHelper.getAsObjectName(this.getClass());
    private final ProfileMonitorFactory factory;

    /** Is JaMon library available?. */
    private static final boolean JAMON_AVAILABLE;

    /**
     * ProfileStatistic *must* be initialized after isJamonAvailable attribute
     * is set. Otherwise you'll get a NullPointerException after MBean
     * registration.
     */
    static {
        JAMON_AVAILABLE = Environment.isJamonAvailable();
        INSTANCE = new ProfileStatistic("root");
    }

    /**
     * Gets the single instance of ProfileStatistic.
     *
     * @return single instance of ProfileStatistic
     */
    public static ProfileStatistic getInstance() {
        return INSTANCE;
    }

    /**
     * Instantiates a new profile statistic.
     *
     * @param rootLabel the root label
     */
    protected ProfileStatistic(final String rootLabel) {
        SimpleProfileMonitor rootMonitor = new SimpleProfileMonitor(rootLabel);
        MBeanHelper.registerMBean(this.mbeanName, this);
        factory = JAMON_AVAILABLE ? new JamonMonitorFactory(rootMonitor) : new SimpleProfileMonitorFactory(rootMonitor);
        factory.setMaxNumMonitors(100);
    }

    /**
     * Normally the {@link ProfileMonitor} is already registered as MBean. So
     * you don't need to register it manually. But if you want to register it
     * under your own name you can use this method here. This can be useful if
     * your application runs on an application server with several other
     * active applications.
     *
     * @param name e.g "my.company.ProfileMonitor"
     * @since 1.6
     */
    public static void registerAsMBean(final String name) {
        INSTANCE.registerMeAsMBean(name);
    }

    /**
     * Register me as MBean.
     *
     * @param name the name
     */
    protected void registerMeAsMBean(final String name) {
        this.registerMeAsMBean(MBeanHelper.getAsObjectName(name));
    }

    private void registerMeAsMBean(final ObjectName name) {
        MBeanHelper.unregisterMBean(this.mbeanName);
        MBeanHelper.registerMBean(name, this);
        LOG.info("{} no longer registered as MBean '{}' but as MBean '{}'.", this.getClass().getSimpleName(),
                this.mbeanName, name);
        this.mbeanName = name;
    }

    /**
     * You can register the instance as shutdown hook. If the VM is
     * terminated the profile values are logged and dumped to a CSV file in the
     * tmp directory.
     *
     * @see #logStatistic()
     * @see #dumpStatistic()
     */
    public static void addAsShutdownHook() {
        addAsShutdownHook(INSTANCE);
    }

    /**
     * Adds the given instance (hook) as shutdown hook.
     *
     * @param hook the hook
     */
    protected static void addAsShutdownHook(final ProfileStatistic hook) {
        Runtime.getRuntime().addShutdownHook(hook);
        if (LOG.isDebugEnabled()) {
            LOG.debug(hook + " registered as shutdown hook");
        }
    }

    /**
     * We can't reset all ProfileMonitors - we must keep the empty
     * monitors with 0 hits to see which methods or constructors are
     * never called.
     */
    public void reset() {
        synchronized(ProfileStatistic.class) {
            List labels = new ArrayList();
            ProfileMonitor[] monitors = getMonitors();
            for (int i = 0; i < monitors.length; i++) {
                if (monitors[i].getHits() == 0) {
                    labels.add(monitors[i].getLabel());
                }
            }
            this.factory.addMonitors(labels);
        }
    }

    /**
     * Resets the root monitor.
     */
    protected void resetRootMonitor() {
        this.factory.reset();
    }

    /**
     * For each constructor and for each method of the given class a
     * ProfileMonitor is initialized. This is done to be able to find
     * constructors and methods which are are never used (i.e. their
     * hit count is zero).
     *
     * @param cl the given class
     */
    public void init(final Class cl) {
        if (LOG.isTraceEnabled()) {
            LOG.trace("initializing monitors for " + cl + "...");
        }
        INSTANCE.init(cl, cl.getMethods());
        INSTANCE.init(cl.getConstructors());
    }

    /**
     * Only methods of the given class but not the methods of the superclass
     * are initialized for profiling.
     *
     * @param cl
     * @param methods
     */
    private void init(final Class cl, final Method[] methods) {
        for (int i = 0; i < methods.length; i++) {
            Class declaring = methods[i].getDeclaringClass();
            if (cl.equals(declaring)) {
                init(methods[i]);
            } else if (LOG.isTraceEnabled()) {
                LOG.trace(methods[i] + " not defined in " + cl
                        + " -> no monitor initialized");
            }
        }
    }

    private void init(final Method method) {
        if (method.getAnnotation(DontProfileMe.class) != null) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("@DontProfileMe " + method + " is ignored");
            }
            return;
        }
        Signature sig = SignatureHelper.getAsSignature(method);
        ProfileMonitor mon = getMonitor(sig);
        if (LOG.isTraceEnabled()) {
            LOG.trace(mon + " initialized");
        }
    }

    private void init(final Constructor[] ctors) {
        for (int i = 0; i < ctors.length; i++) {
            init(ctors[i]);
        }
    }

    private void init(final Constructor ctor) {
        if (ctor.getAnnotation(DontProfileMe.class) != null) {
            LOG.trace("@DontProfileMe {} is ignored", ctor);
            return;
        }
        Signature sig = SignatureHelper.getAsSignature(ctor);
        ProfileMonitor mon = getMonitor(sig);
        LOG.trace("{} initialized", mon);
    }

    /**
     * Gets the MBean name of the registered {@link ProfileStatistic} bean.
     *
     * @return the MBean name
     */
    public ObjectName getMBeanName() {
        return this.mbeanName;
    }
    
    /**
     * Here you can set the maximal size of the statistic entries.
     *
     * @param size the new max size
     * @since 1.6
     */
    public void setMaxSize(final int size) {
        factory.setMaxNumMonitors(size);
    }
    
    /**
     * Gets the max size.
     *
     * @return the max size
     * @since 1.6
     */
    public int getMaxSize() {
    	return factory.getMaxNumMonitors();
    }



    /////   business logic (measurement, statistics and more)   ///////////////

    /**
     * This method is called when the PerformanceMonitor is registered as
     * shutdown hook.
     *
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {
        try {
            dumpStatistic();
        } catch (IOException ioe) {
            LOG.warn("Cannot dump statistic to temporary file:", ioe);
        }
    }

    /**
     * Start.
     *
     * @param sig the sig
     *
     * @return the profile monitor
     */
    public static ProfileMonitor start(final Signature sig) {
        return INSTANCE.startProfileMonitorFor(sig);
    }

    /**
     * Start profile monitor for the given signature.
     *
     * @param sig the signature
     * @return the profile monitor
     */
    public ProfileMonitor startProfileMonitorFor(final Signature sig) {
        return this.startProfileMonitorFor(SignatureHelper.getAsString(sig));
    }

    /**
     * Start profile monitor for the given signature.
     *
     * @param sig the signature
     * @return the profile monitor
     */
    public ProfileMonitor startProfileMonitorFor(final String sig) {
    	ProfileMonitor mon = factory.getMonitor(sig);
        mon.start();
        return mon;
    }

    private synchronized ProfileMonitor getMonitor(final Signature sig) {
    	return factory.getMonitor(SignatureHelper.getAsString(sig));
    }

    /**
     * Gets the monitors (unsorted).
     *
     * @return the monitors
     */
    public ProfileMonitor[] getMonitors() {
    	return factory.getMonitors();
    }

    /**
     * Gets the sorted monitors.
     *
     * @return monitors sorted after total time (descending order)
     */
    protected final ProfileMonitor[] getSortedMonitors() {
        ProfileMonitor[] monitors = getMonitors();
        Arrays.sort(monitors);
        return monitors;
    }

    private ProfileMonitor getMaxHitsMonitor() {
        ProfileMonitor[] monitors = getMonitors();
        ProfileMonitor max = new SimpleProfileMonitor();
        for (int i = 0; i < monitors.length; i++) {
            if (monitors[i].getHits() >= max.getHits()) {
                max = monitors[i];
            }
        }
        return max;
    }

    /**
     * Gets the max hits.
     *
     * @return the max hits
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxHits()
     */
    public int getMaxHits() {
        return getMaxHitsMonitor().getHits();
    }

    /**
     * Gets the max hits label.
     *
     * @return the max hits label
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxHitsLabel()
     */
    public String getMaxHitsLabel() {
        return getMaxHitsMonitor().getLabel();
    }

    /**
     * Gets the max hits statistic.
     *
     * @return the max hits statistic
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxHitsStatistic()
     */
    public String getMaxHitsStatistic() {
        return getMaxHitsMonitor().toShortString();
    }

    private ProfileMonitor getMaxTotalMonitor() {
        ProfileMonitor[] monitors = getMonitors();
        ProfileMonitor max = new SimpleProfileMonitor();
        for (int i = 0; i < monitors.length; i++) {
            if (monitors[i].getTotal() >= max.getTotal()) {
                max = monitors[i];
            }
        }
        return max;
    }

    /**
     * Gets the max total.
     *
     * @return the max total
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxTotal()
     */
    public double getMaxTotal() {
        return getMaxTotalMonitor().getTotal();
    }

    /**
     * Gets the max total label.
     *
     * @return the max total label
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxTotalLabel()
     */
    public String getMaxTotalLabel() {
        return getMaxTotalMonitor().getLabel();
    }

    /**
     * Gets the max total statistic.
     *
     * @return the max total statistic
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxTotalStatistic()
     */
    public String getMaxTotalStatistic() {
        return getMaxTotalMonitor().toShortString();
    }

    private ProfileMonitor getMaxAvgMonitor() {
        ProfileMonitor[] monitors = getMonitors();
        ProfileMonitor max = monitors[0];
        double maxValue = 0.0;
        for (int i = 0; i < monitors.length; i++) {
            double value = monitors[i].getAvg();
            if (!Double.isNaN(value) && (value > maxValue)) {
                maxValue = value;
                max = monitors[i];
            }
        }
        return max;
    }

    /**
     * Gets the root monitor.
     *
     * @return the root monitor
     */
    protected ProfileMonitor getRootMonitor() {
        return this.factory.getRootMonitor();
    }

    /**
     * Gets the max avg.
     *
     * @return the max avg
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxAvg()
     */
    public double getMaxAvg() {
        return getMaxAvgMonitor().getAvg();
    }

    /**
     * Gets the max avg label.
     *
     * @return the max avg label
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxAvgLabel()
     */
    public String getMaxAvgLabel() {
        return getMaxAvgMonitor().getLabel();
    }

    /**
     * Gets the max avg statistic.
     *
     * @return the max avg statistic
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxAvgStatistic()
     */
    public String getMaxAvgStatistic() {
        return getMaxAvgMonitor().toShortString();
    }

    private ProfileMonitor getMaxMaxMonitor() {
        ProfileMonitor[] monitors = getMonitors();
        ProfileMonitor max = new SimpleProfileMonitor();
        for (int i = 0; i < monitors.length; i++) {
            if (monitors[i].getMax() >= max.getMax()) {
                max = monitors[i];
            }
        }
        return max;
    }

    /**
     * Gets the max max.
     *
     * @return the max max
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxMax()
     */
    public double getMaxMax() {
        return getMaxMaxMonitor().getMax();
    }

    /**
     * Gets the max max label.
     *
     * @return the max max label
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxMaxLabel()
     */
    public String getMaxMaxLabel() {
        return getMaxMaxMonitor().getLabel();
    }

    /**
     * Gets the max max statistic.
     *
     * @return the max max statistic
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxMaxStatistic()
     */
    public String getMaxMaxStatistic() {
        return getMaxMaxMonitor().toShortString();
    }

    /**
     * Gets the statistics.
     *
     * @return the statistics
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getStatistics()
     */
    @SuppressWarnings("rawtypes")
    public TabularData getStatistics() {
        try {
            String[] itemNames = { "Label", "Units", "Hits", "Avg", "Total",
                    "Min", "Max" };
            String[] itemDescriptions = { "method name", "time unit (e.g. ms)",
                    "number of hits", "average time", "total time",
                    "minimal time", "maximal time" };
            OpenType[] itemTypes = { SimpleType.STRING, SimpleType.STRING,
                    SimpleType.INTEGER, SimpleType.DOUBLE, SimpleType.DOUBLE,
                    SimpleType.DOUBLE, SimpleType.DOUBLE };
            CompositeType rowType = new CompositeType("propertyType",
                    "property entry", itemNames, itemDescriptions, itemTypes);
            TabularDataSupport data = MBeanHelper.createTabularDataSupport(rowType, itemNames);
            ProfileMonitor[] monitors = getSortedMonitors();
            for (int i = 0; i < monitors.length; i++) {
                Map map = new HashMap();
                map.put("Label", monitors[i].getLabel());
                map.put("Units", monitors[i].getUnits());
                map.put("Hits", monitors[i].getHits());
                map.put("Avg", monitors[i].getAvg());
                map.put("Total", monitors[i].getTotal());
                map.put("Min", monitors[i].getMin());
                map.put("Max", monitors[i].getMax());
                CompositeDataSupport compData = new CompositeDataSupport(
                        rowType, map);
                data.put(compData);
            }
            return data;
        } catch (OpenDataException e) {
            LOG.error("can't create TabularData for log settings", e);
            return null;
        }
    }

    /**
     * Log statistic.
     *
     * @see patterntesting.runtime.monitor.ProfileStatisticMBean#logStatistic()
     */
    public void logStatistic() {
        LOG.info("----- Profile Statistic -----");
        ProfileMonitor[] monitors = getSortedMonitors();
        for (ProfileMonitor profMon : monitors) {
            LOG.info("{}", profMon);
        }
    }

    /**
     * Dump statistic to a file in the temporary directory. Since 1.4.2 the
     * filename is no longer "profile###.csv", but begins with the classname.
     * The reason is located in the subclass SqlStatistic - now you can see
     * which CSV file belongs to which statistic.
     *
     * @return the name of the dump file
     * @throws IOException Signals that an I/O exception has occurred.
     * @see ProfileStatisticMBean#dumpStatistic()
     */
    public File dumpStatistic() throws IOException {
        File dumpFile = File.createTempFile(this.getClass().getSimpleName(), ".csv");
        dumpStatisticTo(dumpFile);
        return dumpFile;
    }

    /**
     * This operation dumps the statistic to the given file.
     *
     * @param filename the file name
     * @throws IOException Signals that an I/O exception has occurred.
     * @see ProfileStatisticMBean#dumpStatistic(String)
     * @since 1.5
     */
    public void dumpStatistic(final String filename) throws IOException {
        this.dumpStatisticTo(new File(filename));
    }

    /**
     * Dump statistic to the given file.
     *
     * @param dumpFile the dump file
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public void dumpStatisticTo(final File dumpFile) throws IOException {
        ProfileMonitor[] monitors = getSortedMonitors();
        if (monitors.length == 0) {
            LOG.debug("no profiling data available");
            return;
        }
        BufferedWriter writer = new BufferedWriter(new FileWriter(dumpFile));
        writer.write(monitors[0].toCsvHeadline());
        writer.newLine();
        for (ProfileMonitor profMon : monitors) {
            writer.write(profMon.toCsvString());
            writer.newLine();
        }
        writer.close();
        LOG.info("profiling data dumped to " + dumpFile);
    }

    /**
     * Do you want to look for the monitor of a given method? Use this
     * method here.
     *
     * @param clazz the clazz
     * @param method the method name, including parameter
     * e.g. "getProfileMonitor(Class,String)"
     *
     * @return monitor of the given class or null
     */
    @MayReturnNull
    public ProfileMonitor getProfileMonitor(final Class clazz, final String method) {
        return this.getProfileMonitor(clazz.getName() + "." + method);
    }

    /**
     * Do you want to look for the monitor of a given method? Use this method
     * here.
     *
     * @param signature e.g. "hello.World(String[])"
     *
     * @return monitor for the given signature or null
     * @since 1.4.2
     */
    @MayReturnNull
    public ProfileMonitor getProfileMonitor(final Signature signature) {
        return this.getProfileMonitor(SignatureHelper.getAsString(signature));
    }

    /**
     * Do you want to look for the monitor of a given method? Use this
     * method here.
     *
     * @param signature e.g. "hello.World(String[])"
     *
     * @return monitor for the given signature or null
     */
    @MayReturnNull
    public ProfileMonitor getProfileMonitor(final String signature) {
        for (ProfileMonitor profMon : this.getMonitors()) {
            if (signature.equals(profMon.getLabel())) {
                return profMon;
            }
        }
        LOG.trace("no ProfileMonitor for " + signature + " found");
        return null;
    }

    /**
     * To string.
     *
     * @return the string
     * @see java.lang.Thread#toString()
     */
    @Override
    public String toString() {
        return this.mbeanName.toString();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy