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.4.0
Show newest version
/*
 * $Id: ProfileStatistic.java,v 1.23 2014/05/03 20:02:39 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.*;
import java.lang.management.ManagementFactory;
import java.lang.reflect.*;
import java.util.*;

import javax.management.*;
import javax.management.openmbean.*;

import org.aspectj.lang.Signature;
import org.slf4j.*;

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

/**
 * 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.
 *
 * @see com.jamonapi.MonitorFactory
 * @author oliver
 * @since 22.12.2008
 * @version $Revision: 1.23 $
 */
public class ProfileStatistic extends Thread implements ProfileStatisticMBean {

    private static final ProfileStatistic instance;
    private static final Logger log = LoggerFactory.getLogger(ProfileStatistic.class);

    private final ObjectName mbeanName;
    private final SimpleProfileMonitor rootMonitor;

    /** Is JaMon library available?. */
    protected static final boolean jamonAvailable;

    /**
     * ProfileStatistic *must* be initialized after isJamonAvailable attribute
     * is set. Otherwise you'll get a NullPointerException after MBean
     * registration.
     */
    static {
        jamonAvailable = 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) {
        this.rootMonitor = new SimpleProfileMonitor(rootLabel);
        this.mbeanName = this.registerAsMBean();
    }

    private ObjectName registerAsMBean() {
        ObjectName name = createObjectName();
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        try {
            server.registerMBean(this, name);
        } catch (InstanceAlreadyExistsException ex) {
            log.info(getMBeanName() + " was already registered.", ex);
        } catch (MBeanRegistrationException ex) {
            log.info(getMBeanName() + " cannot be registered.", ex);
        } catch (NotCompliantMBeanException ex) {
            log.info(getMBeanName() + " is not a compliant MBean.", ex);
        }
        log.info("{} successful registered as MBean", name);
        return name;
    }

    private ObjectName createObjectName() {
        String name = MBeanHelper.getMBeanName(this);
        ObjectName objectName = null;
        try {
            objectName = new ObjectName(name);
        } catch (MalformedObjectNameException e) {
            log.info("can't create object name + '" + name + "'");
        }
        return objectName;
    }

    /**
     * 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());
                }
            }
            if (jamonAvailable) {
                JamonMonitorFactory.addMonitors(labels);
            } else {
                rootMonitor.addChildren(labels);
            }
        }
    }

    /**
     * Resets the root monitor.
     */
    protected void resetRootMonitor() {
        if (jamonAvailable) {
            JamonMonitorFactory.reset(this.rootMonitor);
        } else {
            this.rootMonitor.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) {
            if (log.isTraceEnabled()) {
                log.trace("@DontProfileMe " + ctor + " is ignored");
            }
            return;
        }
        Signature sig = SignatureHelper.getAsSignature(ctor);
        ProfileMonitor mon = getMonitor(sig);
        if (log.isTraceEnabled()) {
            log.trace(mon + " initialized");
        }
    }

    /**
     * Gets the MBean name of the registered {@link ProfileStatistic} bean.
     *
     * @return the MBean name
     */
    public ObjectName getMBeanName() {
        return this.mbeanName;
    }



    /////   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() {
        dumpStatistic();
    }

    /**
     * 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 = null;
        if (jamonAvailable) {
            mon = JamonMonitorFactory.getMonitor(sig, rootMonitor);
//            mon = JamonMonitorFactory.getMonitor(sig);
        } else {
            SimpleProfileMonitor parent = this.getSimpleProfileMonitor(sig);
            mon = new SimpleProfileMonitor(sig, parent);
        }
        mon.start();
        return mon;
    }

    private synchronized ProfileMonitor getMonitor(final Signature sig) {
        if (jamonAvailable) {
            return JamonMonitorFactory.getMonitor(sig, this.rootMonitor);
        } else {
            return getSimpleProfileMonitor(sig);
        }
    }

    private SimpleProfileMonitor getSimpleProfileMonitor(final Signature sig) {
        return this.getSimpleProfileMonitor(SignatureHelper.getAsString(sig));
    }

    private SimpleProfileMonitor getSimpleProfileMonitor(final String sig) {
        SimpleProfileMonitor monitor = rootMonitor.getMonitor(sig);
        if (monitor == null) {
            monitor = new SimpleProfileMonitor(sig, rootMonitor);
        }
        return monitor;
    }

    private ProfileMonitor[] getMonitors() {
        if (jamonAvailable) {
            return JamonMonitorFactory.getMonitors(this.rootMonitor);
        } else {
            return rootMonitor.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.rootMonitor;
    }

    /**
     * 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", "Active", "AvgActive", "MaxActive",
                    "FirstAccess", "LastAccess" };
            String[] itemDescriptions = { "method name", "time unit (e.g. ms)",
                    "number of hits", "average time", "total time",
                    "minimal time", "maximal time", "active threads",
                    "average number of threads", "maximal number of threads",
                    "first access", "last access" };
            OpenType[] itemTypes = { SimpleType.STRING, SimpleType.STRING,
                    SimpleType.INTEGER, SimpleType.DOUBLE, SimpleType.DOUBLE,
                    SimpleType.DOUBLE, SimpleType.DOUBLE, SimpleType.DOUBLE,
                    SimpleType.DOUBLE, SimpleType.DOUBLE, SimpleType.DATE,
                    SimpleType.DATE };
            CompositeType rowType = new CompositeType("propertyType",
                    "property entry", itemNames, itemDescriptions, itemTypes);
            TabularDataSupport data = MBeanHelper.createTabularDataSupport(rowType, itemNames);
            ProfileMonitor[] monitors = getSortedMonitors();
            if (monitors == null) {
                log.warn("can't find monitors");
                return null;
            }
            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());
                map.put("Active", monitors[i].getActive());
                map.put("AvgActive", monitors[i].getAvgActive());
                map.put("MaxActive", monitors[i].getMaxActive());
                map.put("FirstAccess", monitors[i].getFirstAccess());
                map.put("LastAccess", monitors[i].getLastAccess());
                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.
     *
     * @see ProfileStatisticMBean#dumpStatistic()
     */
    public void dumpStatistic() {
        try {
            File dumpFile = File.createTempFile(this.getClass().getSimpleName(), ".csv");
            dumpStatisticTo(dumpFile);
        } catch (IOException ioe) {
            log.warn("Cannot dump statistic.", ioe);
        }
    }

    /**
     * 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 - 2024 Weber Informatics LLC | Privacy Policy