com.google.caliper.core.BenchmarkClassModel.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caliper.core.BenchmarkClassModel.java

Source

/*
 * Copyright (C) 2018 Google Inc.
 *
 * 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.core;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.auto.value.AutoValue;
import com.google.caliper.Param;
import com.google.caliper.api.VmOptions;
import com.google.caliper.util.InvalidCommandException;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * A simplified model of a benchmark class, containing no reflective references to the class and
 * only those things that the runner needs to know about the class in order to determine the
 * scenarios to be run.
 */
@AutoValue
public abstract class BenchmarkClassModel implements Serializable {
    // TODO(b/64154339): Don't use Java serialization for this
    private static final long serialVersionUID = 1;

    /**
     * Creates a model of the given benchmark class and does some validation of it and the given
     * user-provided values for the class' parameter fields.
     */
    public static BenchmarkClassModel create(Class<?> clazz) {
        if (!clazz.getSuperclass().equals(Object.class)) {
            throw new InvalidBenchmarkException(
                    "Class '%s' must not extend any class other than %s. Prefer composition.", clazz, Object.class);
        }

        if (Modifier.isAbstract(clazz.getModifiers())) {
            throw new InvalidBenchmarkException("Class '%s' is abstract", clazz);
        }

        BenchmarkClassModel.Builder builder = new AutoValue_BenchmarkClassModel.Builder().setName(clazz.getName())
                .setSimpleName(clazz.getSimpleName());
        for (Method method : clazz.getDeclaredMethods()) {
            builder.methodsBuilder().add(MethodModel.of(method));
        }
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Param.class)) {
                builder.parametersBuilder().put(field.getName(), Parameters.validateAndGetDefaults(field));
            }
        }
        VmOptions vmOptions = clazz.getAnnotation(VmOptions.class);
        if (vmOptions != null) {
            builder.vmOptionsBuilder().add(vmOptions.value());
        }
        return builder.build();
    }

    /**
     * Validates the given user-provided parameters against the parameter fields on the benchmark
     * class.
     */
    public static void validateUserParameters(Class<?> clazz, SetMultimap<String, String> userParameters) {
        for (String paramName : userParameters.keySet()) {
            try {
                Field field = clazz.getDeclaredField(paramName);
                Parameters.validate(field, userParameters.get(paramName));
            } catch (NoSuchFieldException e) {
                throw new InvalidCommandException("unrecognized parameter: " + paramName);
            } catch (InvalidBenchmarkException e) {
                // TODO(kevinb): this is weird.
                throw new InvalidCommandException(e.getMessage());
            }
        }
    }

    /** Returns the fully qualified name of the benchmark class. */
    public abstract String name();

    /** Returns the simple name of the benchmark class. */
    public abstract String simpleName();

    /** Returns the set of all methods declared on the class. */
    public abstract ImmutableSet<MethodModel> methods();

    /**
     * Returns the set of parameter fields for the class and their default values as a map from field
     * name to set of default values.
     */
    // NOTE: Not a multimap because parameters with no default values are legal, and multimap can't
    // include a key mapped to nothing.
    abstract ImmutableMap<String, ImmutableSet<String>> parameters();

    /**
     * Returns a multimap containing the full set of parameter values to use for the benchmark. For
     * parameters on the benchmark that have values in the given user-supplied parameters, the user's
     * specified values are used. For all other parameters, the default values specified in the
     * annotation or implied by the type are used.
     *
     * @throws IllegalArgumentException if a parameter for the benchmark has neither user-specified
     *     values nor default values
     */
    public final ImmutableSetMultimap<String, String> fillInDefaultParameterValues(
            ImmutableSetMultimap<String, String> userParameters) {
        ImmutableSetMultimap.Builder<String, String> combined = ImmutableSetMultimap.builder();

        // For user parameters, this'll actually be the same as parameters().keySet(), since any extras
        // given at the command line are treated as errors; for VM parameters this is not the case.
        for (String name : Sets.union(parameters().keySet(), userParameters.keySet())) {
            ImmutableSet<String> values = userParameters.containsKey(name) ? userParameters.get(name)
                    : parameters().get(name);
            combined.putAll(name, values);
            checkArgument(!values.isEmpty(), "ERROR: No default value provided for %s", name);
        }
        return combined.orderKeysBy(Ordering.natural()).build();
    }

    /** Returns the set of VM options specified with the {@code @VmOptions} annotation. */
    public abstract ImmutableSet<String> vmOptions();

    @Override
    public String toString() {
        return name();
    }

    /** Builder for {@link BenchmarkClassModel} instances. */
    @AutoValue.Builder
    public abstract static class Builder {

        /** Sets the name of the benchmark class. */
        abstract Builder setName(String className);

        /** Sets the simple name of the benchmark class. */
        abstract Builder setSimpleName(String simpleName);

        /** Returns a builder for adding to the set of methods on the class. */
        abstract ImmutableSet.Builder<MethodModel> methodsBuilder();

        /** Returns a builder for adding to the map of parameters for the class. */
        public abstract ImmutableMap.Builder<String, ImmutableSet<String>> parametersBuilder();

        /** Sets the VM options to use for benchmarking the class. */
        public abstract Builder setVmOptions(Iterable<String> vmOptions);

        /** Returns a builder for adding to the set of VM options for the class. */
        // NOTE: This also makes explicitly setting VM options optional, which is useful for tests.
        public abstract ImmutableSet.Builder<String> vmOptionsBuilder();

        /** Returns a new {@link BenchmarkClassModel} with all the previously set values. */
        public abstract BenchmarkClassModel build();
    }

    /** Model of a method on a benchmark class. */
    @AutoValue
    public abstract static class MethodModel implements Serializable {
        private static final long serialVersionUID = 1;

        /** Creates a new {@link MethodModel} representing the given method. */
        public static MethodModel of(Method method) {
            MethodModel.Builder builder = new AutoValue_BenchmarkClassModel_MethodModel.Builder()
                    .setName(method.getName()).setModifiers(method.getModifiers())
                    .setDeclaringClass(method.getDeclaringClass().getName());
            if (!method.getReturnType().equals(void.class)) {
                builder.setReturnType(method.getReturnType().getName());
            }

            for (Class<?> parameterType : method.getParameterTypes()) {
                builder.parameterTypesBuilder().add(parameterType.getName());
            }
            for (Class<?> exceptionType : method.getExceptionTypes()) {
                builder.exceptionTypesBuilder().add(exceptionType.getName());
            }
            for (Annotation annotation : method.getAnnotations()) {
                builder.annotationTypesBuilder().add(annotation.annotationType().getName());
            }

            return builder.build();
        }

        /** Returns the name of this method. */
        public abstract String name();

        /** Returns the modifiers for this method. */
        public abstract int modifiers();

        /** Returns the class that declared this method. */
        public abstract String declaringClass();

        /** Returns the method's return type, or absent for a {@code void} method. */
        public abstract Optional<String> returnType();

        /** Returns the method's parameter types. */
        public abstract ImmutableList<String> parameterTypes();

        /** Returns the types of exceptions the method declares that it may throw. */
        public abstract ImmutableSet<String> exceptionTypes();

        /** Returns the types of annotations that are present on the method. */
        public abstract ImmutableSet<String> annotationTypes();

        /**
         * Returns whether or not the set of {@link #annotationTypes()} contains an annotation with the
         * given type name.
         */
        public final boolean isAnnotationPresent(String annotationTypeName) {
            checkNotNull(annotationTypeName);
            return annotationTypes().contains(annotationTypeName);
        }

        /**
         * Returns whether or not the set of {@link #annotationTypes()} contains an annotation of the
         * given type.
         */
        public final boolean isAnnotationPresent(Class<?> annotationType) {
            return isAnnotationPresent(annotationType.getName());
        }

        /** Builder for {@link MethodModel} instances. */
        @AutoValue.Builder
        interface Builder {
            /** Sets the name of this method. */
            Builder setName(String name);

            /** Sets the modifiers for this method. */
            Builder setModifiers(int modifiers);

            /** Sets the class that declared this method. */
            Builder setDeclaringClass(String declaringClass);

            /** Sets the method's return type. */
            Builder setReturnType(String returnType);

            /** Returns a builder for adding the method's parameter types. */
            ImmutableList.Builder<String> parameterTypesBuilder();

            /**
             * Returns a builder for adding the types of exceptions the method declares that it may throw.
             */
            ImmutableSet.Builder<String> exceptionTypesBuilder();

            /** Returns a builder for adding the types of annotations that are present on the method. */
            ImmutableSet.Builder<String> annotationTypesBuilder();

            /** Builds the {@link MethodModel}. */
            MethodModel build();
        }
    }
}