From fdc242e65e0fad549762f0668ca060995994fc50 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 14 Dec 2018 11:05:06 +0100 Subject: [PATCH] #97 - Remove Microbenchmark runner in favor of mp911de/microbenchmark-runner. --- benchmark/pom.xml | 10 + benchmark/support/pom.xml | 5 + .../common/AbstractMicrobenchmark.java | 9 +- .../common/HttpResultsWriter.java | 29 +- .../microbenchmark/common/JmhSupport.java | 312 ----------- .../microbenchmark/common/Microbenchmark.java | 498 ------------------ .../MicrobenchmarkResultsWriterFactory.java | 41 ++ .../common/MongoResultsWriter.java | 29 +- .../microbenchmark/common/ResultsWriter.java | 67 --- .../jmh.mbr.core.ResultsWriterFactory | 1 + 10 files changed, 104 insertions(+), 897 deletions(-) delete mode 100644 benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/JmhSupport.java delete mode 100644 benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/Microbenchmark.java create mode 100644 benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MicrobenchmarkResultsWriterFactory.java delete mode 100644 benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/ResultsWriter.java create mode 100644 benchmark/support/src/main/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory diff --git a/benchmark/pom.xml b/benchmark/pom.xml index 00f55cd..18e95bf 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -45,6 +45,12 @@ ${project.version} + + com.github.mp911de.microbenchmark-runner + microbenchmark-runner-junit4 + 0.1.0.RELEASE + + @@ -119,6 +125,10 @@ spring-libs-snapshot https://repo.spring.io/libs-snapshot + + jitpack.io + https://jitpack.io + diff --git a/benchmark/support/pom.xml b/benchmark/support/pom.xml index 5227f64..b6252d2 100644 --- a/benchmark/support/pom.xml +++ b/benchmark/support/pom.xml @@ -21,6 +21,11 @@ spring-data-mongodb + + com.github.mp911de.microbenchmark-runner + microbenchmark-runner-junit4 + + diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java index 1173690..70ae2a5 100644 --- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java +++ b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java @@ -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 { diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/HttpResultsWriter.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/HttpResultsWriter.java index 8ac0718..ba5834b 100644 --- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/HttpResultsWriter.java +++ b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/HttpResultsWriter.java @@ -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 results) { + public void write(OutputFormat output, Collection results) { if (CollectionUtils.isEmpty(results)) { return; } + try { + doWrite(results); + } catch (IOException e) { + output.println("Failed to write results: " + e.toString()); + } + } + + private void doWrite(Collection results) throws IOException { + StandardEnvironment env = new StandardEnvironment(); String projectVersion = env.getProperty("project.version", "unknown"); diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/JmhSupport.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/JmhSupport.java deleted file mode 100644 index 96cb77d..0000000 --- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/JmhSupport.java +++ /dev/null @@ -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.
- * 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 includes(Class testClass, Collection 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 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)); - } - } -} diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/Microbenchmark.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/Microbenchmark.java deleted file mode 100644 index 93c6f2a..0000000 --- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/Microbenchmark.java +++ /dev/null @@ -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 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 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 computeTestMethods() { - - List 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 children = new ArrayList<>(getFilteredChildren()); - List 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 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 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 methods, CacheFunction cache) throws Exception { - - Class jmhTestClass = getTestClass().getJavaClass(); - List 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 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 descriptionResolver; - private final OutputFormat delegate; - private final List log = new CopyOnWriteArrayList<>(); - - private volatile String lastKnownBenchmark; - private volatile boolean recordOutput; - - NotifyingOutputFormat(RunNotifier notifier, Function 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 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("")) { - 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 { - - private final Map methodMap = new ConcurrentHashMap<>(); - private final Collection methods; - private final Function describeFunction; - - CacheFunction(Collection methods, Function 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 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); - } - } - -} diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MicrobenchmarkResultsWriterFactory.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MicrobenchmarkResultsWriterFactory.java new file mode 100644 index 0000000..b3986bd --- /dev/null +++ b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MicrobenchmarkResultsWriterFactory.java @@ -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; + } +} diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MongoResultsWriter.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MongoResultsWriter.java index 3881711..9ce7e9c 100644 --- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MongoResultsWriter.java +++ b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/MongoResultsWriter.java @@ -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 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 results) { + private void doWrite(Collection 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(); diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/ResultsWriter.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/ResultsWriter.java deleted file mode 100644 index 3e14ac9..0000000 --- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/ResultsWriter.java +++ /dev/null @@ -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 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 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); - } -} diff --git a/benchmark/support/src/main/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory b/benchmark/support/src/main/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory new file mode 100644 index 0000000..eebba55 --- /dev/null +++ b/benchmark/support/src/main/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory @@ -0,0 +1 @@ +org.springframework.data.microbenchmark.common.MicrobenchmarkResultsWriterFactory