com.google.caliper.maven.BenchmarkMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caliper.maven.BenchmarkMojo.java

Source

/*
 * Copyright (C) 2013 Anton Tychyna <anton.tychina@gmail.com>
 *
 * 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 com.google.caliper.maven;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import static com.google.common.base.Strings.isNullOrEmpty;

/**
 * Run Caliper benchmarks.
 */
@Mojo(name = "run", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class BenchmarkMojo extends AbstractMojo {
    private static final Joiner JOINER = Joiner.on(',');
    private static final String CALIPER_GROUP_ID = "com.google.caliper";
    private static final String CALIPER_ARTIFACT_ID = "caliper";
    private static final String ALLOCATION_INSTRUMENTER_CLASSNAME = "com.google.monitoring.runtime.instrumentation.AllocationInstrumenter";
    private static Predicate<Object> CALIPER_PREDICATE = new Predicate<Object>() {
        @Override
        public boolean apply(@Nullable Object o) {
            if (o instanceof Artifact) {
                Artifact a = (Artifact) o;
                if ((Artifact.SCOPE_COMPILE.equals(a.getScope()) || Artifact.SCOPE_RUNTIME.equals(a.getScope()))
                        && a.getGroupId().equals(CALIPER_GROUP_ID)
                        && a.getArtifactId().equals(CALIPER_ARTIFACT_ID)) {
                    return true;
                }
            }
            return false;
        }
    };
    private static Predicate<Object> ALLOCATION_PREDICATE = new Predicate<Object>() {
        @Override
        public boolean apply(@Nullable Object o) {
            if (o instanceof Artifact) {
                Artifact a = (Artifact) o;
                if ("jar".equals(a.getType())) {
                    try {
                        JarFile jarFile = null;
                        try {
                            jarFile = new JarFile(a.getFile());
                            Manifest manifest = jarFile.getManifest();
                            if ((manifest != null) && ALLOCATION_INSTRUMENTER_CLASSNAME
                                    .equals(manifest.getMainAttributes().getValue("Premain-Class"))) {
                                return true;
                            }
                        } finally {
                            if (jarFile != null) {
                                jarFile.close();
                            }
                        }
                    } catch (IOException e) {
                        // do nothing
                    }

                }
            }
            return false;
        }
    };

    @Component
    private MavenProject project;

    /**
     * Where to look for compiled benchmark classes.
     */
    @Parameter(defaultValue = "${project.build.outputDirectory}")
    protected File benchmarkClassesDirectory;

    /**
     * Maximum length of time allowed for a single trial. Use 0 to allow trials to run indefinitely.
     */
    @Parameter(property = "timeLimit")
    protected String timeLimit;

    /**
     * Instead of measuring, execute a single rep for each scenario.
     */
    @Parameter(property = "dryRun")
    protected boolean dryRun;

    /**
     * Fail build if benchmark throws an exception.
     */
    @Parameter(property = "failBuild")
    protected boolean failBuild;

    /**
     * Number of independent trials to peform per benchmark scenario.
     */
    @Parameter(property = "trials")
    protected Integer trials;

    /**
     * List of measuring instruments to use.
     */
    @Parameter(property = "instruments")
    protected List<String> instruments;

    /**
     * A user-friendly string used to identify the run.
     */
    @Parameter(property = "runName")
    protected String runName;

    /**
     * In addition to normal console output, display a raw feed of very detailed information.
     */
    @Parameter(property = "verbose")
    protected boolean verbose;

    /**
     * Location of Caliper's configuration file.
     */
    @Parameter(property = "caliperConfigFile")
    protected String caliperConfigFile;

    /**
     * Location of Caliper's configuration and data directory.
     */
    @Parameter(property = "caliperDirectory")
    protected String caliperDirectory;

    /**
     * Print the effective configuration that will be used by Caliper.
     */
    @Parameter(property = "printConfig")
    protected boolean printConfig;

    /**
     * List of VMs to test on.
     */
    @Parameter(property = "vms")
    protected List<String> vms;

    /**
     * Specifies a value for any property that could otherwise be specified in $HOME/.caliper/config.properties.
     */
    @Parameter
    protected Map<String, String> properties;

    /**
     * Specifies the values to inject into the 'param' field of the benchmark.
     */
    @Parameter
    protected Map<String, String> params;

    /**
     * Benchmarks to include in this run. By default all classes that begin or end with Benchmark are included.
     */
    @Parameter(property = "includes")
    protected List<String> includes;

    /**
     * Benchmarks to exclude from this run.
     */
    @Parameter(property = "excludes")
    protected List<String> excludes;

    /**
     * Run single benchmark specified by regexp.
     */
    @Parameter(property = "benchmark")
    protected String benchmark;

    /**
     * Java agent for allocation instrument. Plugin will look for agent on a classpath if not defined.
     */
    @Parameter(property = "allocationAgentJar")
    private String allocationAgentJar;

    protected List<String> getDefaultIncludes() {
        return Lists.newArrayList("**/*Benchmark.java", "**/Benchmark*.java");
    }

    @SuppressWarnings("unchecked")
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        checkCaliperDependency();
        ConsoleLogger console = new ConsoleLogger(System.out);
        console.info("");
        console.info("-------------------------------------------------------");
        console.info(" B E N C H M A R K S ");
        console.info("-------------------------------------------------------");
        ClassLoader benchmarkClassloader = getBenchmarkClassloader();
        Properties systemProperties = System.getProperties();
        String oldClassPath = systemProperties.getProperty("java.class.path");
        // set "java.class.path" system property as it's used by Caliper runner
        if (getLog().isDebugEnabled()) {
            getLog().debug("Using classpath: " + ClassLoaderUtils.getClassPathString(benchmarkClassloader));
        }
        systemProperties.setProperty("java.class.path", ClassLoaderUtils.getClassPathString(benchmarkClassloader));
        List<CaliperBenchmark> benchmarks = getBenchmarks(benchmarkClassloader);
        if (benchmarks.isEmpty()) {
            getLog().info("No benchmarks to run");
        }
        BenchmarkRunResult result = new BenchmarkRunResult();
        for (CaliperBenchmark benchmark : benchmarks) {
            console.info("\nRunning " + benchmark);
            try {
                String[] commandLineOptions = getCommandLineOptions();
                if (getLog().isDebugEnabled()) {
                    getLog().debug("Command line options: " + Joiner.on(' ').join(commandLineOptions));
                }
                benchmark.run(commandLineOptions);
                result.successes++;
            } catch (Exception e) {
                String exception = "Exception was thrown while running " + benchmark;
                if (failBuild) {
                    getLog().error(exception, e);
                    throw new MojoFailureException(exception);
                } else {
                    getLog().warn(exception, e);
                    result.failures++;
                }
            }
        }
        console.info("");
        console.info(result);
        // restore old class path
        systemProperties.setProperty("java.class.path", oldClassPath);
    }

    private void checkCaliperDependency() throws MojoExecutionException {
        // check Caliper library is available
        Optional caliper = Iterables.tryFind(project.getArtifacts(), CALIPER_PREDICATE);
        if (!caliper.isPresent()) {
            throw dependencyNotFound(CALIPER_GROUP_ID, CALIPER_ARTIFACT_ID);
        }
        getLog().debug("Using Caliper library " + caliper.get());

        // get java agent for allocation instrument, Caliper won't run without it
        if (isNullOrEmpty(allocationAgentJar)) {
            Optional allocation = Iterables.tryFind(project.getArtifacts(), ALLOCATION_PREDICATE);
            if (!allocation.isPresent()) {
                throw new IllegalArgumentException("Can't find allocation agent jar on the classpath");
            }
            Artifact a = (Artifact) allocation.get();
            allocationAgentJar = a.getFile().getAbsolutePath();
        }
        File agentJar = new File(allocationAgentJar);
        if (!agentJar.isFile() || !agentJar.canRead()) {
            throw new IllegalArgumentException(
                    "Can't read agent jar " + allocationAgentJar + " (check file exists and its permissions)");
        }
        getLog().debug("Using allocation library " + allocationAgentJar);
    }

    protected List<CaliperBenchmark> getBenchmarks(ClassLoader benchmarkClassloader) throws MojoExecutionException {
        BenchmarkDirectoryScanner scanner = new BenchmarkDirectoryScanner(benchmarkClassesDirectory);
        List<String> includes = Lists.newArrayList(getDefaultIncludes());
        if (this.includes != null) {
            includes.addAll(this.includes);
        }
        scanner.setIncludes(includes);
        if (excludes != null) {
            scanner.setExcludes(excludes);
        }
        if (benchmark != null) {
            scanner.setSpecificBenchmarks(ImmutableList.of(benchmark));
        }
        try {
            BenchmarkScanResult scan = scanner.scan();
            return scan.toBenchmarks(benchmarkClassloader);
        } catch (ClassNotFoundException e) {
            throw bug(e);
        }
    }

    @SuppressWarnings("unchecked")
    protected ClassLoader getBenchmarkClassloader() throws MojoExecutionException {
        try {
            Collection<String> urls = project.getTestClasspathElements();
            URL[] runtimeUrls = new URL[urls.size() + 1];
            int i = 0;
            for (String url : urls) {
                runtimeUrls[i++] = new File(url).toURI().toURL();
            }
            runtimeUrls[i] = getPathToPluginJar();
            return new BenchmarkClassLoader(runtimeUrls, Thread.currentThread().getContextClassLoader());
        } catch (MalformedURLException e) {
            throw bug(e);
        } catch (DependencyResolutionRequiredException e) {
            throw bug(e);
        } catch (IOException e) {
            throw bug(e);
        }
    }

    private URL getPathToPluginJar() throws IOException {
        URL url = getClass().getResource(getClass().getSimpleName() + ".class");
        if (!"jar".equalsIgnoreCase(url.getProtocol()))
            throw new IllegalArgumentException("caliper-maven-plugin classes are not in a jar file");
        JarURLConnection connection = (JarURLConnection) url.openConnection();
        return connection.getJarFileURL();
    }

    protected String[] getCommandLineOptions() {
        List<String> options = Lists.newArrayList();
        if (!isNullOrEmpty(timeLimit)) {
            options.add("-l" + timeLimit);
        }
        if (dryRun) {
            options.add("-n");
        }
        if (trials != null) {
            options.add("-t" + trials);
        }
        if (!isNullOrEmpty(caliperConfigFile)) {
            options.add("-c" + caliperConfigFile);
        }
        if (!isNullOrEmpty(caliperDirectory)) {
            options.add("--directory" + caliperDirectory);
        }
        if (printConfig) {
            options.add("-p");
        }
        if (vms != null && !vms.isEmpty()) {
            options.add("-m" + JOINER.join(vms));
        }
        if (instruments != null && !instruments.isEmpty()) {
            options.add("-i" + JOINER.join(instruments));
        }
        if (!isNullOrEmpty(runName)) {
            options.add("-r" + runName);
        }
        if (verbose) {
            options.add("-v");
        }
        if (properties != null && !properties.isEmpty()) {
            for (Map.Entry<String, String> e : properties.entrySet()) {
                options.add("-C" + e.getKey() + "=" + e.getValue());
            }
        }
        options.add("-Cinstrument.allocation.options.allocationAgentJar=" + allocationAgentJar);
        if (params != null && !params.isEmpty()) {
            for (Map.Entry<String, String> e : params.entrySet()) {
                options.add("-D" + e.getKey() + "=" + e.getValue());
            }
        }
        return options.toArray(new String[options.size()]);
    }

    private static MojoExecutionException bug(Exception e) throws MojoExecutionException {
        return new MojoExecutionException("Please report this problem to caliper-maven-plugin bug tracker", e);
    }

    private MojoExecutionException dependencyNotFound(String groupId, String artifactId)
            throws MojoExecutionException {
        throw new MojoExecutionException(groupId + ":" + artifactId + " dependency was not found in project \""
                + project.getName() + "\" in compile or runtime scopes");
    }
}