JMH is a great tool to write microbenchmarks but when you need to integrate it with CDI it can be a bit tricky or you need to break the “common” concept of shade JMH really loves.
However OpenEJB ApplicationComposer provides a built-in solution to this issue pretty neat.
Note on used version: this blog post has been written using OpenEJB 4.7.2.
The idea there is to use ApplicationComposers to handle the application description and lifecycle in a JMH benchmark scoped state.
Basic wiring looks like:
@State(Scope.Benchmark) public class MyEEBench { private final ApplicationComposers applicationComposers = new ApplicationComposers(MyEEBench.class); @Setup public void setup() throws Exception { applicationComposers.before(this); } @TearDown public void tearDown() throws Exception { applicationComposers.after(); } // ... }
Then you can describe your application as usual with ApplicationComposer using @Module in MyEEBench:
@State(Scope.Benchmark) public class MyEEBench { // ... @Module @Classes(cdi = true, innerClassesAsBean = true) public WebApp app() { return new WebApp(); } // ... }
And also get injections in the state:
@State(Scope.Benchmark) public class MyEEBench { // ... @Inject private BeanManager bm; @Resource private DataSource ds; // ... }
And finally you can write your benchmark using the injected beans/resources/services:
@State(Scope.Benchmark) public class MyEEBench { // ... @Benchmark public void beanManagerFireEvent() { bm.fireEvent(new Evt()); } // ... }
Put all together it looks like:
import org.apache.openejb.jee.WebApp; import org.apache.openejb.testing.ApplicationComposers; import org.apache.openejb.testing.Classes; import org.apache.openejb.testing.Module; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Event; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import java.util.logging.Logger; @State(Scope.Benchmark) public class CdiEvent { @Inject private BeanManager bm; @Inject private Event<Evt> evt; private final ApplicationComposers applicationComposers = new ApplicationComposers(CdiEvent.class); @Setup public void setup() throws Exception { applicationComposers.before(this); } @TearDown public void tearDown() throws Exception { applicationComposers.after(); } @Benchmark public void beanManagerFireEvent() { bm.fireEvent(new Evt("-")); } @Benchmark public void eventFire() { evt.fire(new Evt("-")); } @Module @Classes(cdi = true, innerClassesAsBean = true) public WebApp app() { return new WebApp(); } public static class Evt { private final String evt; public Evt(String evt) { this.evt = evt; } public String getEvt() { return evt; } } @ApplicationScoped public static class See { public void listen(@Observes final Evt e) { // no-op } } public static void main(final String[] args) throws RunnerException { final Options opt = new OptionsBuilder() .include(CdiEvent.class.getSimpleName()) .forks(1) .warmupIterations(5) .measurementIterations(5) .threads(30) .build(); new Runner(opt).run(); } }
Finally easier than expected isn’t it? 😉
No more reason to not microbenchmark and validate any part of your framework now :).
Pingback: Java Weekly 40/15: JMH and CDI, MVC, Microservice Truths