#97 - Remove Microbenchmark runner in favor of mp911de/microbenchmark-runner.

This commit is contained in:
Mark Paluch
2018-12-14 11:05:06 +01:00
parent b33d7dc386
commit fdc242e65e
10 changed files with 104 additions and 897 deletions

View File

@@ -45,6 +45,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
<artifactId>microbenchmark-runner-junit4</artifactId>
<version>0.1.0.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -119,6 +125,10 @@
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<pluginRepositories>

View File

@@ -21,6 +21,11 @@
<artifactId>spring-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
<artifactId>microbenchmark-runner-junit4</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.microbenchmark.common;
import jmh.mbr.junit4.Microbenchmark;
import org.junit.runner.RunWith;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
@@ -29,9 +31,10 @@ import org.openjdk.jmh.annotations.Warmup;
* @author Mark Paluch
* @see Microbenchmark
*/
@Warmup(iterations = JmhSupport.WARMUP_ITERATIONS)
@Measurement(iterations = JmhSupport.MEASUREMENT_ITERATIONS)
@Fork(JmhSupport.FORKS)
@Warmup(iterations = 5)
@Measurement(iterations = 10)
@Fork(value = 1, jvmArgs = { "-server", "-XX:+HeapDumpOnOutOfMemoryError", "-Xms1024m", "-Xmx1024m",
"-XX:MaxDirectMemorySize=1024m" })
@State(Scope.Thread)
@RunWith(Microbenchmark.class)
public abstract class AbstractMicrobenchmark {

View File

@@ -15,8 +15,10 @@
*/
package org.springframework.data.microbenchmark.common;
import lombok.SneakyThrows;
import jmh.mbr.core.ResultsWriter;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -26,30 +28,37 @@ import java.time.Duration;
import java.util.Collection;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.format.OutputFormat;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.CollectionUtils;
/**
* {@link ResultsWriter} implementation of {@link URLConnection}.
*
* @since 2.0
* {@link ResultsWriterOld} implementation of {@link URLConnection}.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
@RequiredArgsConstructor
class HttpResultsWriter implements ResultsWriter {
private final String url;
HttpResultsWriter(String url) {
this.url = url;
}
@Override
@SneakyThrows
public void write(Collection<RunResult> results) {
public void write(OutputFormat output, Collection<RunResult> results) {
if (CollectionUtils.isEmpty(results)) {
return;
}
try {
doWrite(results);
} catch (IOException e) {
output.println("Failed to write results: " + e.toString());
}
}
private void doWrite(Collection<RunResult> results) throws IOException {
StandardEnvironment env = new StandardEnvironment();
String projectVersion = env.getProperty("project.version", "unknown");

View File

@@ -1,312 +0,0 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.microbenchmark.common;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.junit.runners.model.FrameworkMethod;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
class JmhSupport {
static final int WARMUP_ITERATIONS = 5;
static final int MEASUREMENT_ITERATIONS = 10;
static final int FORKS = 1;
static final String[] JVM_ARGS = { "-server", "-XX:+HeapDumpOnOutOfMemoryError", "-Xms1024m", "-Xmx1024m",
"-XX:MaxDirectMemorySize=1024m" };
private final StandardEnvironment environment = new StandardEnvironment();
/**
* Get the regex for all benchmarks to be included in the run. By default every benchmark within classes matching the
* fqcn. <br />
* The {@literal benchmark} command line argument allows overriding the defaults using {@code #} as class / method
* name separator.
*
* @return never {@literal null}.
* @param name
* @param methods
*/
protected List<String> includes(Class<?> testClass, Collection<FrameworkMethod> methods) {
String tests = environment.getProperty("benchmark", String.class);
if (!StringUtils.hasText(tests)) {
return methods.stream()
.map(it -> Pattern.quote(it.getDeclaringClass().getName()) + "\\." + Pattern.quote(it.getName()) + "$")
.collect(Collectors.toList());
}
if (tests.contains(testClass.getName()) || tests.contains(testClass.getSimpleName())) {
if (!tests.contains("#")) {
return Collections.singletonList(".*" + tests + ".*");
}
String[] args = tests.split("#");
return Collections.singletonList(".*" + args[0] + "." + args[1]);
}
return Collections.emptyList();
}
/**
* Collect all options for the {@link Runner}.
*
* @return never {@literal null}.
* @throws Exception
*/
protected ChainedOptionsBuilder options(Class<?> jmhTestClass) throws Exception {
ChainedOptionsBuilder optionsBuilder = new OptionsBuilder().jvmArgs(jvmArgs());
optionsBuilder = warmup(optionsBuilder);
optionsBuilder = measure(optionsBuilder);
optionsBuilder = forks(optionsBuilder);
optionsBuilder = report(optionsBuilder, jmhTestClass);
return optionsBuilder;
}
/**
* JVM args to apply to {@link Runner} via its {@link org.openjdk.jmh.runner.options.Options}.
*
* @return {@link #JVM_ARGS} by default.
*/
protected String[] jvmArgs() {
String[] args = new String[JVM_ARGS.length];
System.arraycopy(JVM_ARGS, 0, args, 0, JVM_ARGS.length);
return args;
}
/**
* Read {@code warmupIterations} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected int getWarmupIterations() {
return environment.getProperty("warmupIterations", Integer.class, -1);
}
/**
* Read {@code measurementIterations} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected int getMeasurementIterations() {
return environment.getProperty("measurementIterations", Integer.class, -1);
}
/**
* Read {@code forks} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected int getForksCount() {
return environment.getProperty("forks", Integer.class, -1);
}
/**
* Read {@code benchmarkReportDir} property from {@link org.springframework.core.env.Environment}.
*
* @return {@literal null} if not set.
*/
protected String getReportDirectory() {
return environment.getProperty("benchmarkReportDir");
}
/**
* Read {@code measurementTime} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected long getMeasurementTime() {
return environment.getProperty("measurementTime", Long.class, -1L);
}
/**
* Read {@code warmupTime} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected long getWarmupTime() {
return environment.getProperty("warmupTime", Long.class, -1L);
}
/**
* {@code project.version_yyyy-MM-dd_ClassName.json} eg.
* {@literal 1.11.0.BUILD-SNAPSHOT_2017-03-07_MappingMongoConverterBenchmark.json}
*
* @return
*/
protected String reportFilename(Class<?> jmhTestClass) {
StringBuilder sb = new StringBuilder();
if (environment.containsProperty("project.version")) {
sb.append(environment.getProperty("project.version"));
sb.append("_");
}
sb.append(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
sb.append("_");
sb.append(org.springframework.util.ClassUtils.getShortName(jmhTestClass));
sb.append(".json");
return sb.toString();
}
/**
* Apply measurement options to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @see #getMeasurementIterations()
* @see #getMeasurementTime()
*/
private ChainedOptionsBuilder measure(ChainedOptionsBuilder optionsBuilder) {
int measurementIterations = getMeasurementIterations();
long measurementTime = getMeasurementTime();
if (measurementIterations > 0) {
optionsBuilder = optionsBuilder.measurementIterations(measurementIterations);
}
if (measurementTime > 0) {
optionsBuilder = optionsBuilder.measurementTime(TimeValue.seconds(measurementTime));
}
return optionsBuilder;
}
/**
* Apply warmup options to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @see #getWarmupIterations()
* @see #getWarmupTime()
*/
private ChainedOptionsBuilder warmup(ChainedOptionsBuilder optionsBuilder) {
int warmupIterations = getWarmupIterations();
long warmupTime = getWarmupTime();
if (warmupIterations > 0) {
optionsBuilder = optionsBuilder.warmupIterations(warmupIterations);
}
if (warmupTime > 0) {
optionsBuilder = optionsBuilder.warmupTime(TimeValue.seconds(warmupTime));
}
return optionsBuilder;
}
/**
* Apply forks option to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @see #getForksCount()
*/
private ChainedOptionsBuilder forks(ChainedOptionsBuilder optionsBuilder) {
int forks = getForksCount();
if (forks <= 0) {
return optionsBuilder;
}
return optionsBuilder.forks(forks);
}
/**
* Apply report option to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @throws IOException if report file cannot be created.
* @see #getReportDirectory()
*/
private ChainedOptionsBuilder report(ChainedOptionsBuilder optionsBuilder, Class<?> jmhTestClass) throws IOException {
String reportDir = getReportDirectory();
if (!StringUtils.hasText(reportDir)) {
return optionsBuilder;
}
String reportFilePath = reportDir + (reportDir.endsWith(File.separator) ? "" : File.separator) + reportFilename(jmhTestClass);
File file = ResourceUtils.getFile(reportFilePath);
if (file.exists()) {
file.delete();
} else {
file.getParentFile().mkdirs();
file.createNewFile();
}
optionsBuilder.resultFormat(ResultFormatType.JSON);
optionsBuilder.result(reportFilePath);
return optionsBuilder;
}
/**
* Publish results to an external system.
*
* @param results must not be {@literal null}.
*/
void publishResults(Collection<RunResult> results) {
if (CollectionUtils.isEmpty(results) || !environment.containsProperty("publishTo")) {
return;
}
String uri = environment.getProperty("publishTo");
try {
ResultsWriter.forUri(uri).write(results);
} catch (Exception e) {
System.err.println(String.format("Cannot save benchmark results to '%s'. Error was %s.", uri, e));
}
}
}

View File

@@ -1,498 +0,0 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.microbenchmark.common;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.infra.IterationParams;
import org.openjdk.jmh.results.BenchmarkResult;
import org.openjdk.jmh.results.IterationResult;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.Defaults;
import org.openjdk.jmh.runner.NoBenchmarksException;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.format.OutputFormat;
import org.openjdk.jmh.runner.format.OutputFormatFactory;
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.util.UnCloseablePrintStream;
import org.openjdk.jmh.util.Utils;
import org.springframework.util.StringUtils;
/**
* JMH Microbenchmark runner that turns methods annotated with {@link Benchmark} into runnable methods allowing
* execution through JUnit.
*
* @author Mark Paluch
*/
public class Microbenchmark extends BlockJUnit4ClassRunner {
private final Object childrenLock = new Object();
private final JmhSupport jmhRunner = new JmhSupport();
private Collection<FrameworkMethod> filteredChildren;
/**
* Creates a {@link Microbenchmark} to run {@link Class test class}.
*
* @param testClass
* @throws InitializationError if the test class is malformed.
*/
public Microbenchmark(Class<?> testClass) throws InitializationError {
super(testClass);
}
/**
* Ignore JUnit validation as we're using JMH here.
*
* @param errors
*/
@Override
protected void collectInitializationErrors(List<Throwable> errors) {}
/**
* JMH has no means to exclude benchmark methods.
*/
@Override
protected boolean isIgnored(FrameworkMethod child) {
return false;
}
/**
* Returns the methods that run tests. Default implementation returns all methods annotated with {@code @Test} on this
* class and superclasses that are not overridden.
*/
protected List<FrameworkMethod> computeTestMethods() {
List<FrameworkMethod> annotatedMethods = new ArrayList<>(getTestClass().getAnnotatedMethods(Benchmark.class));
annotatedMethods.sort(Comparator.comparing(Microbenchmark::getBenchmarkName));
return Collections.unmodifiableList(annotatedMethods);
}
/*
* (non-Javadoc)
* @see org.junit.runners.ParentRunner#classBlock(org.junit.runner.notification.RunNotifier)
*/
@Override
protected Statement classBlock(RunNotifier notifier) {
return childrenInvoker(notifier);
}
/*
* (non-Javadoc)
* @see org.junit.runners.ParentRunner#getDescription()
*/
@Override
public Description getDescription() {
Description description = Description.createSuiteDescription(getName(), getRunnerAnnotations());
getFilteredChildren().stream().map(this::describeChild).forEach(description::addChild);
return description;
}
/*
* (non-Javadoc)
* @see org.junit.runners.ParentRunner#filter(org.junit.runner.manipulation.Filter)
*/
public void filter(Filter filter) throws NoTestsRemainException {
synchronized (childrenLock) {
List<FrameworkMethod> children = new ArrayList<>(getFilteredChildren());
List<FrameworkMethod> filtered = children.stream().filter(it -> {
if (filter.shouldRun(describeChild(it))) {
try {
filter.apply(it);
return true;
} catch (NoTestsRemainException e) {
return false;
}
}
return false;
}).collect(Collectors.toList());
if (filtered.isEmpty()) {
throw new NoTestsRemainException();
}
filteredChildren = Collections.unmodifiableCollection(filtered);
}
}
/*
* (non-Javadoc)
* @see org.junit.runners.ParentRunner#sort(org.junit.runner.manipulation.Sorter)
*/
public void sort(Sorter sorter) {
synchronized (childrenLock) {
getFilteredChildren().forEach(sorter::apply);
List<FrameworkMethod> sortedChildren = new ArrayList<>(getFilteredChildren());
sortedChildren.sort((o1, o2) -> sorter.compare(describeChild(o1), describeChild(o2)));
filteredChildren = Collections.unmodifiableCollection(sortedChildren);
}
}
/**
* Run matching {@link org.openjdk.jmh.annotations.Benchmark} methods with options collected from
* {@link org.springframework.core.env.Environment}.
*/
@Override
protected Statement childrenInvoker(RunNotifier notifier) {
Collection<FrameworkMethod> methods = getFilteredChildren();
CacheFunction cache = new CacheFunction(methods, this::describeChild);
if (methods.isEmpty()) {
return new Statement() {
@Override
public void evaluate() {}
};
}
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
doRun(notifier, methods, cache);
} catch (NoBenchmarksException | NoTestsRemainException e) {
methods.forEach(it -> notifier.fireTestIgnored(describeChild(it)));
}
}
};
}
void doRun(RunNotifier notifier, Collection<FrameworkMethod> methods, CacheFunction cache) throws Exception {
Class<?> jmhTestClass = getTestClass().getJavaClass();
List<String> includes = jmhRunner.includes(jmhTestClass, methods);
if (includes.isEmpty()) {
throw new NoTestsRemainException();
}
ChainedOptionsBuilder optionsBuilder = jmhRunner.options(jmhTestClass);
includes.forEach(optionsBuilder::include);
Options options = optionsBuilder.build();
NotifyingOutputFormat notifyingOutputFormat = new NotifyingOutputFormat(notifier, cache,
createOutputFormat(options));
jmhRunner.publishResults(new Runner(options, notifyingOutputFormat).run());
}
private Collection<FrameworkMethod> getFilteredChildren() {
if (filteredChildren == null) {
synchronized (childrenLock) {
if (filteredChildren == null) {
filteredChildren = Collections.unmodifiableCollection(getChildren());
}
}
}
return filteredChildren;
}
private static OutputFormat createOutputFormat(Options options) {
// sadly required here as the check cannot be made before calling this method in constructor
if (options == null) {
throw new IllegalArgumentException("Options not allowed to be null.");
}
PrintStream out;
if (options.getOutput().hasValue()) {
try {
out = new PrintStream(options.getOutput().get());
} catch (FileNotFoundException ex) {
throw new IllegalStateException(ex);
}
} else {
// Protect the System.out from accidental closing
try {
out = new UnCloseablePrintStream(System.out, Utils.guessConsoleEncoding());
} catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
return OutputFormatFactory.createFormatInstance(out, options.verbosity().orElse(Defaults.VERBOSITY));
}
private static String getBenchmarkName(FrameworkMethod it) {
return it.getDeclaringClass().getName() + "." + it.getName();
}
/**
* {@link OutputFormat} that delegates to another {@link OutputFormat} and notifies {@link RunNotifier} about the
* progress.
*/
static class NotifyingOutputFormat implements OutputFormat {
private final RunNotifier notifier;
private final Function<String, Description> descriptionResolver;
private final OutputFormat delegate;
private final List<String> log = new CopyOnWriteArrayList<>();
private volatile String lastKnownBenchmark;
private volatile boolean recordOutput;
NotifyingOutputFormat(RunNotifier notifier, Function<String, Description> methods, OutputFormat delegate) {
this.notifier = notifier;
this.descriptionResolver = methods;
this.delegate = delegate;
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#iteration(org.openjdk.jmh.infra.BenchmarkParams, org.openjdk.jmh.infra.IterationParams, int)
*/
@Override
public void iteration(BenchmarkParams benchParams, IterationParams params, int iteration) {
delegate.iteration(benchParams, params, iteration);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#iterationResult(org.openjdk.jmh.infra.BenchmarkParams, org.openjdk.jmh.infra.IterationParams, int, org.openjdk.jmh.results.IterationResult)
*/
@Override
public void iterationResult(BenchmarkParams benchParams, IterationParams params, int iteration,
IterationResult data) {
delegate.iterationResult(benchParams, params, iteration, data);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#startBenchmark(org.openjdk.jmh.infra.BenchmarkParams)
*/
@Override
public void startBenchmark(BenchmarkParams benchParams) {
log.clear();
lastKnownBenchmark = benchParams.getBenchmark();
notifier.fireTestStarted(descriptionResolver.apply(benchParams.getBenchmark()));
delegate.startBenchmark(benchParams);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#endBenchmark(org.openjdk.jmh.results.BenchmarkResult)
*/
@Override
public void endBenchmark(BenchmarkResult result) {
recordOutput = false;
String lastKnownBenchmark = this.lastKnownBenchmark;
if (result != null) {
notifier.fireTestFinished(descriptionResolver.apply(result.getParams().getBenchmark()));
} else if (lastKnownBenchmark != null) {
String output = StringUtils.collectionToDelimitedString(log, System.getProperty("line.separator"));
notifier.fireTestFailure(
new Failure(descriptionResolver.apply(lastKnownBenchmark), new JmhRunnerException(output)));
}
log.clear();
delegate.endBenchmark(result);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#startRun()
*/
@Override
public void startRun() {
delegate.startRun();
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#endRun(java.util.Collection)
*/
@Override
public void endRun(Collection<RunResult> result) {
delegate.endRun(result);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#print(java.lang.String)
*/
@Override
public void print(String s) {
delegate.print(s);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#println(java.lang.String)
*/
@Override
public void println(String s) {
if (recordOutput && StringUtils.hasText(s)) {
log.add(s);
}
if (s.equals("<failure>")) {
recordOutput = true;
}
delegate.println(s);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#flush()
*/
@Override
public void flush() {
delegate.flush();
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#close()
*/
@Override
public void close() {
delegate.close();
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#verbosePrintln(java.lang.String)
*/
@Override
public void verbosePrintln(String s) {
delegate.verbosePrintln(s);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#write(int)
*/
@Override
public void write(int b) {
delegate.write(b);
}
/*
* (non-Javadoc)
* @see org.openjdk.jmh.runner.format.OutputFormat#write(byte[])
*/
@Override
public void write(byte[] b) throws IOException {
delegate.write(b);
}
}
/**
* Exception proxy without stack trace.
*/
static class JmhRunnerException extends RuntimeException {
private static final long serialVersionUID = -1385006784559013618L;
JmhRunnerException(String message) {
super(message);
}
/*
* (non-Javadoc)
* @see java.lang.Throwable#fillInStackTrace()
*/
@Override
public synchronized Throwable fillInStackTrace() {
return null;
}
}
/**
* Cache {@link Function} for benchmark names to {@link Description}.
*/
static class CacheFunction implements Function<String, Description> {
private final Map<String, FrameworkMethod> methodMap = new ConcurrentHashMap<>();
private final Collection<FrameworkMethod> methods;
private final Function<FrameworkMethod, Description> describeFunction;
CacheFunction(Collection<FrameworkMethod> methods, Function<FrameworkMethod, Description> describeFunction) {
this.methods = methods;
this.describeFunction = describeFunction;
}
/**
* Resolve a benchmark name (fqcn + "." + method name) to a {@link Description}.
*
* @param benchmarkName
* @return
*/
public Description apply(String benchmarkName) {
FrameworkMethod frameworkMethod = methodMap.computeIfAbsent(benchmarkName, key -> {
Optional<FrameworkMethod> method = methods.stream().filter(it -> getBenchmarkName(it).equals(key)).findFirst();
return method.orElseThrow(() -> new IllegalArgumentException(
String.format("Cannot resolve %s to a FrameworkMethod!", benchmarkName)));
});
return describeFunction.apply(frameworkMethod);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.data.microbenchmark.common;
import jmh.mbr.core.ResultsWriter;
import jmh.mbr.core.ResultsWriterFactory;
/**
* {@link ResultsWriterFactory} plugin via {@link java.util.ServiceLoader}.
*
* @author Mark Paluch
*/
public class MicrobenchmarkResultsWriterFactory implements ResultsWriterFactory {
@Override
public ResultsWriter forUri(String uri) {
if (uri.startsWith("http")) {
return new HttpResultsWriter(uri);
}
if (uri.startsWith("mongo")) {
return new MongoResultsWriter(uri);
}
return null;
}
}

View File

@@ -15,13 +15,18 @@
*/
package org.springframework.data.microbenchmark.common;
import jmh.mbr.core.ResultsWriter;
import lombok.RequiredArgsConstructor;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.bson.Document;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.format.OutputFormat;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -32,21 +37,31 @@ import com.mongodb.client.MongoDatabase;
import com.mongodb.util.JSON;
/**
* MongoDB specific {@link ResultsWriter} implementation.
* MongoDB specific {@link ResultsWriterOld} implementation.
*
* @author Christoph Strobl
* @since 2.0
* @author Mark Paluch
*/
@RequiredArgsConstructor
class MongoResultsWriter implements ResultsWriter {
private final String uri;
MongoResultsWriter(String uri) {
this.uri = uri;
@Override
public void write(OutputFormat output, Collection<RunResult> results) {
if (CollectionUtils.isEmpty(results)) {
return;
}
try {
doWrite(results);
} catch (RuntimeException e) {
output.println("Failed to write results: " + e.toString());
}
}
@Override
public void write(Collection<RunResult> results) {
private void doWrite(Collection<RunResult> results) {
Date now = new Date();
StandardEnvironment env = new StandardEnvironment();
@@ -89,7 +104,7 @@ class MongoResultsWriter implements ResultsWriter {
* @param doc
* @return
*/
private Document fixDocumentKeys(Document doc) {
private static Document fixDocumentKeys(Document doc) {
Document sanitized = new Document();

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.microbenchmark.common;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.results.format.ResultFormatFactory;
import org.openjdk.jmh.results.format.ResultFormatType;
/**
* @author Christoph Strobl
* @since 2.0
*/
interface ResultsWriter {
/**
* Write the {@link RunResult}s.
*
* @param results can be {@literal null}.
*/
void write(Collection<RunResult> results);
/**
* Get the uri specific {@link ResultsWriter}.
*
* @param uri must not be {@literal null}.
* @return
*/
static ResultsWriter forUri(String uri) {
return uri.startsWith("mongodb:") ? new MongoResultsWriter(uri) : new HttpResultsWriter(uri);
}
/**
* Convert {@link RunResult}s to JMH Json representation.
*
* @param results
* @return json string representation of results.
* @see org.openjdk.jmh.results.format.JSONResultFormat
*/
@SneakyThrows
static String jsonifyResults(Collection<RunResult> results) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ResultFormatFactory.getInstance(ResultFormatType.JSON, new PrintStream(baos, true, "UTF-8")).writeOut(results);
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1 @@
org.springframework.data.microbenchmark.common.MicrobenchmarkResultsWriterFactory