Http2DefaultFrameWriter microbenchmark
Motivation: A microbenchmark will be useful to get a baseline for performance. Modifications: - Introduce a new microbenchmark which tests the Http2DefaultFrameWriter. - Allow benchmarks to run without thread context switching between JMH and Netty. Result: Microbenchmark exists to test performance.
This commit is contained in:
parent
23f881b382
commit
2dda917f27
@ -15,6 +15,9 @@
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.buffer.Unpooled.directBuffer;
|
||||
import static io.netty.buffer.Unpooled.unmodifiableBuffer;
|
||||
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.CONTINUATION_FRAME_HEADER_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
|
||||
@ -50,7 +53,6 @@ import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
|
||||
import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
@ -69,7 +71,8 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
|
||||
* When padding is needed it can be taken as a slice of this buffer. Users should call {@link ByteBuf#retain()}
|
||||
* before using their slice.
|
||||
*/
|
||||
private static final ByteBuf ZERO_BUFFER = Unpooled.buffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE);
|
||||
private static final ByteBuf ZERO_BUFFER = unmodifiableBuffer(
|
||||
unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)));
|
||||
|
||||
private final Http2HeadersEncoder headersEncoder;
|
||||
private int maxFrameSize;
|
||||
|
@ -31,8 +31,23 @@
|
||||
<properties>
|
||||
<!-- Skip tests by default; run only if -DskipTests=false is specified -->
|
||||
<skipTests>true</skipTests>
|
||||
<epoll.arch>x86_64</epoll.arch>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>linux</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>linux</family>
|
||||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<epoll.arch>${os.detected.arch}</epoll.arch>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
@ -44,6 +59,17 @@
|
||||
<artifactId>netty-codec-http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-codec-http2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<classifier>linux-${epoll.arch}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-core</artifactId>
|
||||
@ -79,6 +105,13 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.2.3.Final</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
|
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you 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 io.netty.microbench.channel;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelHandlerInvoker;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelProgressivePromise;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.ReferenceCounted;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public abstract class EmbeddedChannelWriteReleaseHandlerContext implements ChannelHandlerContext {
|
||||
private static final String HANDLER_NAME = "microbench-delegator-ctx";
|
||||
private final EventExecutor executor;
|
||||
private final Channel channel;
|
||||
private final ByteBufAllocator alloc;
|
||||
private final ChannelHandler handler;
|
||||
private SocketAddress localAddress;
|
||||
|
||||
public EmbeddedChannelWriteReleaseHandlerContext(ByteBufAllocator alloc, ChannelHandler handler) {
|
||||
this(alloc, handler, new EmbeddedChannel());
|
||||
}
|
||||
|
||||
public EmbeddedChannelWriteReleaseHandlerContext(ByteBufAllocator alloc, ChannelHandler handler,
|
||||
EmbeddedChannel channel) {
|
||||
this.alloc = checkNotNull(alloc, "alloc");
|
||||
this.channel = checkNotNull(channel, "channel");
|
||||
this.handler = checkNotNull(handler, "handler");
|
||||
this.executor = checkNotNull(channel.eventLoop(), "executor");
|
||||
}
|
||||
|
||||
protected abstract void handleException(Throwable t);
|
||||
|
||||
@Override
|
||||
public <T> Attribute<T> attr(AttributeKey<T> key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean hasAttr(AttributeKey<T> key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventExecutor executor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerInvoker invoker() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return HANDLER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler handler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemoved() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelRegistered() {
|
||||
try {
|
||||
handler().channelRegistered(this);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelUnregistered() {
|
||||
try {
|
||||
handler().channelUnregistered(this);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelActive() {
|
||||
try {
|
||||
handler().channelActive(this);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelInactive() {
|
||||
try {
|
||||
handler().channelInactive(this);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireExceptionCaught(Throwable cause) {
|
||||
try {
|
||||
handler().exceptionCaught(this, cause);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireUserEventTriggered(Object event) {
|
||||
try {
|
||||
handler().userEventTriggered(this, event);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelRead(Object msg) {
|
||||
try {
|
||||
handler().channelRead(this, msg);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelReadComplete() {
|
||||
try {
|
||||
handler().channelReadComplete(this);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelWritabilityChanged() {
|
||||
try {
|
||||
handler().channelWritabilityChanged(this);
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress localAddress) {
|
||||
return bind(localAddress, newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress) {
|
||||
return connect(remoteAddress, newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||
return connect(remoteAddress, localAddress, newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect() {
|
||||
return disconnect(newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close() {
|
||||
return close(newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister() {
|
||||
return deregister(newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||
try {
|
||||
channel().bind(localAddress, promise);
|
||||
this.localAddress = localAddress;
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
|
||||
try {
|
||||
channel().connect(remoteAddress, localAddress, promise);
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress,
|
||||
ChannelPromise promise) {
|
||||
try {
|
||||
channel().connect(remoteAddress, localAddress, promise);
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect(ChannelPromise promise) {
|
||||
try {
|
||||
channel().disconnect(promise);
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close(ChannelPromise promise) {
|
||||
try {
|
||||
channel().close(promise);
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister(ChannelPromise promise) {
|
||||
try {
|
||||
channel().deregister(promise);
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext read() {
|
||||
try {
|
||||
channel().read();
|
||||
} catch (Exception e) {
|
||||
handleException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object msg) {
|
||||
return write(msg, newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object msg, ChannelPromise promise) {
|
||||
try {
|
||||
if (msg instanceof ReferenceCounted) {
|
||||
((ReferenceCounted) msg).release();
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
channel().write(msg, promise);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
handleException(e);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext flush() {
|
||||
channel().flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
||||
return channel().writeAndFlush(msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object msg) {
|
||||
return writeAndFlush(msg, newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline pipeline() {
|
||||
return channel().pipeline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return alloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise newPromise() {
|
||||
return channel().newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelProgressivePromise newProgressivePromise() {
|
||||
return channel().newProgressivePromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newSucceededFuture() {
|
||||
return channel().newSucceededFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newFailedFuture(Throwable cause) {
|
||||
return channel().newFailedFuture(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise voidPromise() {
|
||||
return channel().voidPromise();
|
||||
}
|
||||
}
|
@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you 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 io.netty.microbench.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_FRAME_SIZE_UPPER_BOUND;
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.ServerChannel;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.oio.OioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.channel.socket.oio.OioServerSocketChannel;
|
||||
import io.netty.channel.socket.oio.OioSocketChannel;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Connection;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||
import io.netty.handler.codec.http2.Http2Connection;
|
||||
import io.netty.handler.codec.http2.Http2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.Http2FrameAdapter;
|
||||
import io.netty.handler.codec.http2.Http2FrameWriter;
|
||||
import io.netty.handler.codec.http2.Http2Headers;
|
||||
import io.netty.handler.codec.http2.Http2LocalFlowController;
|
||||
import io.netty.handler.codec.http2.Http2RemoteFlowController;
|
||||
import io.netty.microbench.channel.EmbeddedChannelWriteReleaseHandlerContext;
|
||||
import io.netty.microbench.util.AbstractSharedExecutorMicrobenchmark;
|
||||
import io.netty.util.concurrent.Future;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class Http2FrameWriterBenchmark extends AbstractSharedExecutorMicrobenchmark {
|
||||
private static final EnvironmentParameters NIO_UNPOOLED_PARAMS =
|
||||
new NioEnvironmentParametersBase(UnpooledByteBufAllocator.DEFAULT);
|
||||
private static final EnvironmentParameters NIO_POOLED_PARAMS =
|
||||
new NioEnvironmentParametersBase(PooledByteBufAllocator.DEFAULT);
|
||||
private static final EnvironmentParameters EPOLL_UNPOOLED_PARAMS =
|
||||
new EpollEnvironmentParametersBase(UnpooledByteBufAllocator.DEFAULT);
|
||||
private static final EnvironmentParameters EPOLL_POOLED_PARAMS =
|
||||
new EpollEnvironmentParametersBase(PooledByteBufAllocator.DEFAULT);
|
||||
private static final EnvironmentParameters OIO_UNPOOLED_PARAMS =
|
||||
new OioEnvironmentParametersBase(UnpooledByteBufAllocator.DEFAULT);
|
||||
private static final EnvironmentParameters OIO_POOLED_PARAMS =
|
||||
new OioEnvironmentParametersBase(PooledByteBufAllocator.DEFAULT);
|
||||
|
||||
public static enum EnvironmentType {
|
||||
EMBEDDED_POOLED, EMBEDDED_UNPOOLED,
|
||||
NIO_POOLED, NIO_UNPOOLED,
|
||||
EPOLL_POOLED, EPOLL_UNPOOLED,
|
||||
OIO_POOLED, OIO_UNPOOLED;
|
||||
}
|
||||
|
||||
public static enum DataPayloadType {
|
||||
SMALL, MEDIUM, LARGE, JUMBO;
|
||||
}
|
||||
|
||||
private static final Map<EnvironmentType, Environment> ENVIRONMENTS = new HashMap<EnvironmentType, Environment>();
|
||||
private static final Map<DataPayloadType, BenchmarkTestPayload> PAYLOADS =
|
||||
new HashMap<DataPayloadType, BenchmarkTestPayload>();
|
||||
|
||||
static {
|
||||
ENVIRONMENTS.put(EnvironmentType.OIO_POOLED, boostrapEnvWithTransport(OIO_POOLED_PARAMS));
|
||||
ENVIRONMENTS.put(EnvironmentType.OIO_UNPOOLED, boostrapEnvWithTransport(OIO_UNPOOLED_PARAMS));
|
||||
ENVIRONMENTS.put(EnvironmentType.NIO_POOLED, boostrapEnvWithTransport(NIO_POOLED_PARAMS));
|
||||
ENVIRONMENTS.put(EnvironmentType.NIO_UNPOOLED, boostrapEnvWithTransport(NIO_UNPOOLED_PARAMS));
|
||||
if (Epoll.isAvailable()) {
|
||||
ENVIRONMENTS.put(EnvironmentType.EPOLL_POOLED, boostrapEnvWithTransport(EPOLL_POOLED_PARAMS));
|
||||
ENVIRONMENTS.put(EnvironmentType.EPOLL_UNPOOLED, boostrapEnvWithTransport(EPOLL_UNPOOLED_PARAMS));
|
||||
}
|
||||
ENVIRONMENTS.put(EnvironmentType.EMBEDDED_POOLED, boostrapEmbeddedEnv(PooledByteBufAllocator.DEFAULT));
|
||||
ENVIRONMENTS.put(EnvironmentType.EMBEDDED_UNPOOLED, boostrapEmbeddedEnv(UnpooledByteBufAllocator.DEFAULT));
|
||||
PAYLOADS.put(DataPayloadType.SMALL, createPayload(DataPayloadType.SMALL));
|
||||
PAYLOADS.put(DataPayloadType.MEDIUM, createPayload(DataPayloadType.MEDIUM));
|
||||
PAYLOADS.put(DataPayloadType.LARGE, createPayload(DataPayloadType.LARGE));
|
||||
PAYLOADS.put(DataPayloadType.JUMBO, createPayload(DataPayloadType.JUMBO));
|
||||
}
|
||||
|
||||
@Param
|
||||
public EnvironmentType environmentType;
|
||||
|
||||
@Param
|
||||
public DataPayloadType dataType;
|
||||
|
||||
@Param({ "0", "255" })
|
||||
public int padding;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private BenchmarkTestPayload payload;
|
||||
|
||||
@AfterClass
|
||||
public static void teardown() {
|
||||
for (Environment env : ENVIRONMENTS.values()) {
|
||||
try {
|
||||
env.teardown();
|
||||
} catch (Exception e) {
|
||||
handleUnexpectedException(e);
|
||||
}
|
||||
}
|
||||
for (BenchmarkTestPayload payload : PAYLOADS.values()) {
|
||||
payload.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() {
|
||||
environment = ENVIRONMENTS.get(environmentType);
|
||||
if (environment == null) {
|
||||
throw new IllegalStateException("Environment type [" + environmentType + "] is not supported.");
|
||||
}
|
||||
AbstractSharedExecutorMicrobenchmark.executor(environment.eventLoop());
|
||||
payload = PAYLOADS.get(dataType);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void writeData() {
|
||||
ChannelHandlerContext context = environment.context();
|
||||
environment.writer().writeData(context, 3, payload.data().retain(), padding, true, context.voidPromise());
|
||||
context.flush();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void writeHeaders() {
|
||||
ChannelHandlerContext context = environment.context();
|
||||
environment.writer().writeHeaders(context, 3, payload.headers(), padding, true, context.voidPromise());
|
||||
context.flush();
|
||||
}
|
||||
|
||||
private static Http2Headers createHeaders(int numValues, int nameLength, int valueLength) {
|
||||
Http2Headers headers = new DefaultHttp2Headers();
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < numValues; ++i) {
|
||||
byte[] tmp = new byte[nameLength];
|
||||
r.nextBytes(tmp);
|
||||
AsciiString name = new AsciiString(tmp);
|
||||
tmp = new byte[valueLength];
|
||||
r.nextBytes(tmp);
|
||||
headers.add(name, new AsciiString(tmp));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private static ByteBuf createData(int length) {
|
||||
byte[] result = new byte[length];
|
||||
new Random().nextBytes(result);
|
||||
return Unpooled.wrappedBuffer(result);
|
||||
}
|
||||
|
||||
private static BenchmarkTestPayload createPayload(DataPayloadType type) {
|
||||
switch (type) {
|
||||
case SMALL:
|
||||
return new BenchmarkTestPayload(createData(256), createHeaders(5, 20, 20));
|
||||
case MEDIUM:
|
||||
return new BenchmarkTestPayload(createData(DEFAULT_MAX_FRAME_SIZE), createHeaders(20, 40, 40));
|
||||
case LARGE:
|
||||
return new BenchmarkTestPayload(createData(MAX_FRAME_SIZE_UPPER_BOUND), createHeaders(100, 100, 100));
|
||||
case JUMBO:
|
||||
return new BenchmarkTestPayload(createData(10 * MAX_FRAME_SIZE_UPPER_BOUND), createHeaders(300, 300, 300));
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BenchmarkTestPayload {
|
||||
private final ByteBuf data;
|
||||
private final Http2Headers headers;
|
||||
|
||||
public BenchmarkTestPayload(ByteBuf data, Http2Headers headers) {
|
||||
this.data = data;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ByteBuf data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public Http2Headers headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
data.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static Environment boostrapEnvWithTransport(final EnvironmentParameters params) {
|
||||
ServerBootstrap sb = new ServerBootstrap();
|
||||
Bootstrap cb = new Bootstrap();
|
||||
final TrasportEnvironment environment = new TrasportEnvironment(cb, sb);
|
||||
|
||||
EventLoopGroup serverEventLoopGroup = params.newEventLoopGroup();
|
||||
sb.group(serverEventLoopGroup, serverEventLoopGroup);
|
||||
sb.channel(params.serverChannelClass());
|
||||
sb.option(ChannelOption.ALLOCATOR, params.serverAllocator());
|
||||
sb.childOption(ChannelOption.ALLOCATOR, params.serverAllocator());
|
||||
sb.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
}
|
||||
});
|
||||
|
||||
cb.group(params.newEventLoopGroup());
|
||||
cb.channel(params.clientChannelClass());
|
||||
cb.option(ChannelOption.ALLOCATOR, params.clientAllocator());
|
||||
cb.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
Http2Connection connection = new DefaultHttp2Connection(false);
|
||||
Http2RemoteFlowController remoteFlowController = params.remoteFlowController();
|
||||
if (remoteFlowController != null) {
|
||||
connection.remote().flowController(params.remoteFlowController());
|
||||
}
|
||||
Http2LocalFlowController localFlowController = params.localFlowController();
|
||||
if (localFlowController != null) {
|
||||
connection.local().flowController(localFlowController);
|
||||
}
|
||||
environment.writer(new DefaultHttp2FrameWriter());
|
||||
Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(
|
||||
new DefaultHttp2ConnectionDecoder.Builder().connection(connection)
|
||||
.frameReader(new DefaultHttp2FrameReader()).listener(new Http2FrameAdapter()),
|
||||
new DefaultHttp2ConnectionEncoder.Builder().connection(connection).frameWriter(
|
||||
environment.writer()));
|
||||
p.addLast(connectionHandler);
|
||||
environment.context(p.lastContext());
|
||||
}
|
||||
});
|
||||
|
||||
environment.serverChannel(sb.bind(params.address()));
|
||||
params.address(environment.serverChannel().localAddress());
|
||||
environment.clientChannel(cb.connect(params.address()));
|
||||
return environment;
|
||||
}
|
||||
|
||||
private static Environment boostrapEmbeddedEnv(final ByteBufAllocator alloc) {
|
||||
final EmbeddedEnvironment env = new EmbeddedEnvironment(new DefaultHttp2FrameWriter());
|
||||
final Http2Connection connection = new DefaultHttp2Connection(false);
|
||||
final Http2ConnectionHandler connectionHandler = new Http2ConnectionHandler(
|
||||
new DefaultHttp2ConnectionDecoder.Builder().connection(connection)
|
||||
.frameReader(new DefaultHttp2FrameReader()).listener(new Http2FrameAdapter()),
|
||||
new DefaultHttp2ConnectionEncoder.Builder().connection(connection).frameWriter(env.writer()));
|
||||
env.context(new EmbeddedChannelWriteReleaseHandlerContext(alloc, connectionHandler) {
|
||||
@Override
|
||||
protected void handleException(Throwable t) {
|
||||
handleUnexpectedException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
private interface Environment {
|
||||
/**
|
||||
* Get the event loop that should be shared with JMH to execute the benchmark.
|
||||
*/
|
||||
EventLoop eventLoop();
|
||||
|
||||
/**
|
||||
* The context to use during the benchmark.
|
||||
*/
|
||||
ChannelHandlerContext context();
|
||||
|
||||
/**
|
||||
* The writer which will be subject to benchmarking.
|
||||
*/
|
||||
Http2FrameWriter writer();
|
||||
|
||||
/**
|
||||
* Do any cleanup after environment is no longer needed.
|
||||
*/
|
||||
void teardown() throws Exception;
|
||||
}
|
||||
|
||||
private interface EnvironmentParameters {
|
||||
EventLoopGroup newEventLoopGroup();
|
||||
|
||||
Class<? extends ServerChannel> serverChannelClass();
|
||||
|
||||
Class<? extends Channel> clientChannelClass();
|
||||
|
||||
ByteBufAllocator clientAllocator();
|
||||
|
||||
ByteBufAllocator serverAllocator();
|
||||
|
||||
SocketAddress address();
|
||||
|
||||
void address(SocketAddress address);
|
||||
|
||||
Http2RemoteFlowController remoteFlowController();
|
||||
|
||||
Http2LocalFlowController localFlowController();
|
||||
}
|
||||
|
||||
private abstract static class EnvironmentParametersBase implements EnvironmentParameters {
|
||||
private final ByteBufAllocator clientAlloc;
|
||||
private final ByteBufAllocator serverAlloc;
|
||||
private final Class<? extends Channel> clientChannelClass;
|
||||
private final Class<? extends ServerChannel> serverChannelClass;
|
||||
private final Http2RemoteFlowController remoteFlowController;
|
||||
private final Http2LocalFlowController localFlowController;
|
||||
private SocketAddress address;
|
||||
|
||||
EnvironmentParametersBase(ByteBufAllocator serverAlloc, ByteBufAllocator clientAlloc,
|
||||
Class<? extends ServerChannel> serverChannelClass, Class<? extends Channel> clientChannelClass) {
|
||||
this(serverAlloc, clientAlloc, serverChannelClass, clientChannelClass,
|
||||
NoopHttp2RemoteFlowController.INSTANCE, NoopHttp2LocalFlowController.INSTANCE);
|
||||
}
|
||||
|
||||
EnvironmentParametersBase(ByteBufAllocator serverAlloc, ByteBufAllocator clientAlloc,
|
||||
Class<? extends ServerChannel> serverChannelClass, Class<? extends Channel> clientChannelClass,
|
||||
Http2RemoteFlowController remoteFlowController, Http2LocalFlowController localFlowController) {
|
||||
this.serverAlloc = checkNotNull(serverAlloc, "serverAlloc");
|
||||
this.clientAlloc = checkNotNull(clientAlloc, "clientAlloc");
|
||||
this.clientChannelClass = checkNotNull(clientChannelClass, "clientChannelClass");
|
||||
this.serverChannelClass = checkNotNull(serverChannelClass, "serverChannelClass");
|
||||
this.remoteFlowController = remoteFlowController; // OK to be null
|
||||
this.localFlowController = localFlowController; // OK to be null
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress address() {
|
||||
if (address == null) {
|
||||
return new InetSocketAddress(0);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void address(SocketAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ServerChannel> serverChannelClass() {
|
||||
return serverChannelClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Channel> clientChannelClass() {
|
||||
return clientChannelClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator clientAllocator() {
|
||||
return clientAlloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator serverAllocator() {
|
||||
return serverAlloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2RemoteFlowController remoteFlowController() {
|
||||
return remoteFlowController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2LocalFlowController localFlowController() {
|
||||
return localFlowController;
|
||||
}
|
||||
};
|
||||
|
||||
private static class NioEnvironmentParametersBase extends EnvironmentParametersBase {
|
||||
NioEnvironmentParametersBase(ByteBufAllocator clientAlloc) {
|
||||
super(UnpooledByteBufAllocator.DEFAULT, clientAlloc, NioServerSocketChannel.class, NioSocketChannel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoopGroup newEventLoopGroup() {
|
||||
return new NioEventLoopGroup(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EpollEnvironmentParametersBase extends EnvironmentParametersBase {
|
||||
EpollEnvironmentParametersBase(ByteBufAllocator clientAlloc) {
|
||||
super(UnpooledByteBufAllocator.DEFAULT, clientAlloc,
|
||||
EpollServerSocketChannel.class, EpollSocketChannel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoopGroup newEventLoopGroup() {
|
||||
return new EpollEventLoopGroup(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OioEnvironmentParametersBase extends EnvironmentParametersBase {
|
||||
OioEnvironmentParametersBase(ByteBufAllocator clientAlloc) {
|
||||
super(UnpooledByteBufAllocator.DEFAULT, clientAlloc, OioServerSocketChannel.class, OioSocketChannel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoopGroup newEventLoopGroup() {
|
||||
return new OioEventLoopGroup(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TrasportEnvironment implements Environment {
|
||||
private final ServerBootstrap sb;
|
||||
private final Bootstrap cb;
|
||||
private Channel serverChannel;
|
||||
private Channel clientChannel;
|
||||
private ChannelHandlerContext clientContext;
|
||||
private Http2FrameWriter clientWriter;
|
||||
|
||||
public TrasportEnvironment(Bootstrap cb, ServerBootstrap sb) {
|
||||
this.sb = checkNotNull(sb, "sb");
|
||||
this.cb = checkNotNull(cb, "cb");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
// It is assumed the channel is registered to the event loop by the time this is called
|
||||
return clientChannel.eventLoop();
|
||||
}
|
||||
|
||||
public Channel serverChannel() {
|
||||
return serverChannel;
|
||||
}
|
||||
|
||||
public void serverChannel(ChannelFuture bindFuture) {
|
||||
// No need to sync or wait by default...local channel immediate executor
|
||||
serverChannel = checkNotNull(bindFuture, "bindFuture").channel();
|
||||
}
|
||||
|
||||
public void clientChannel(ChannelFuture connectFuture) {
|
||||
// No need to sync or wait by default...local channel immediate executor
|
||||
clientChannel = checkNotNull(connectFuture, "connectFuture").channel();
|
||||
}
|
||||
|
||||
public void context(ChannelHandlerContext context) {
|
||||
clientContext = checkNotNull(context, "context");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext context() {
|
||||
return clientContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() throws InterruptedException {
|
||||
if (clientChannel != null) {
|
||||
clientChannel.close();
|
||||
}
|
||||
if (serverChannel != null) {
|
||||
serverChannel.close();
|
||||
}
|
||||
Future<?> serverGroup = null;
|
||||
Future<?> serverChildGroup = null;
|
||||
Future<?> clientGroup = null;
|
||||
if (sb != null) {
|
||||
serverGroup = sb.group().shutdownGracefully(0, 0, MILLISECONDS);
|
||||
serverChildGroup = sb.childGroup().shutdownGracefully(0, 0, MILLISECONDS);
|
||||
}
|
||||
if (cb != null) {
|
||||
clientGroup = cb.group().shutdownGracefully(0, 0, MILLISECONDS);
|
||||
}
|
||||
if (sb != null) {
|
||||
serverGroup.sync();
|
||||
serverChildGroup.sync();
|
||||
}
|
||||
if (cb != null) {
|
||||
clientGroup.sync();
|
||||
}
|
||||
}
|
||||
|
||||
public void writer(Http2FrameWriter writer) {
|
||||
clientWriter = checkNotNull(writer, "writer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2FrameWriter writer() {
|
||||
return clientWriter;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EmbeddedEnvironment implements Environment {
|
||||
private final Http2FrameWriter writer;
|
||||
private ChannelHandlerContext context;
|
||||
private EventLoop eventLoop;
|
||||
|
||||
public EmbeddedEnvironment(Http2FrameWriter writer) {
|
||||
this.writer = checkNotNull(writer, "writer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
return eventLoop;
|
||||
}
|
||||
|
||||
public void context(EmbeddedChannelWriteReleaseHandlerContext context) {
|
||||
this.context = checkNotNull(context, "context");
|
||||
Channel channel = checkNotNull(context.channel(), "context.channel()");
|
||||
this.eventLoop = checkNotNull(channel.eventLoop(), "channel.eventLoop()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2FrameWriter writer() {
|
||||
return writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() throws Exception {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you 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 io.netty.microbench.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http2.Http2Exception;
|
||||
import io.netty.handler.codec.http2.Http2LocalFlowController;
|
||||
import io.netty.handler.codec.http2.Http2Stream;
|
||||
|
||||
public final class NoopHttp2LocalFlowController implements Http2LocalFlowController {
|
||||
public static final NoopHttp2LocalFlowController INSTANCE = new NoopHttp2LocalFlowController();
|
||||
|
||||
private NoopHttp2LocalFlowController() { }
|
||||
|
||||
@Override
|
||||
public void initialWindowSize(int newWindowSize) throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int initialWindowSize() {
|
||||
return MAX_INITIAL_WINDOW_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int windowSize(Http2Stream stream) {
|
||||
return MAX_INITIAL_WINDOW_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
||||
throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveFlowControlledFrame(ChannelHandlerContext ctx, Http2Stream stream, ByteBuf data,
|
||||
int padding, boolean endOfStream) throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeBytes(ChannelHandlerContext ctx, Http2Stream stream, int numBytes) throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int unconsumedBytes(Http2Stream stream) {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you 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 io.netty.microbench.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http2.Http2Exception;
|
||||
import io.netty.handler.codec.http2.Http2RemoteFlowController;
|
||||
import io.netty.handler.codec.http2.Http2Stream;
|
||||
|
||||
public final class NoopHttp2RemoteFlowController implements Http2RemoteFlowController {
|
||||
public static final NoopHttp2RemoteFlowController INSTANCE = new NoopHttp2RemoteFlowController();
|
||||
|
||||
private NoopHttp2RemoteFlowController() { }
|
||||
@Override
|
||||
public void initialWindowSize(int newWindowSize) throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int initialWindowSize() {
|
||||
return MAX_INITIAL_WINDOW_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int windowSize(Http2Stream stream) {
|
||||
return MAX_INITIAL_WINDOW_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
||||
throws Http2Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFlowControlled(ChannelHandlerContext ctx, Http2Stream stream, FlowControlled payload) {
|
||||
// Don't check size beforehand because Headers payload returns 0 all the time.
|
||||
do {
|
||||
payload.write(MAX_INITIAL_WINDOW_SIZE);
|
||||
} while (payload.size() > 0);
|
||||
}
|
||||
}
|
@ -15,37 +15,34 @@
|
||||
*/
|
||||
package io.netty.microbench.util;
|
||||
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import org.junit.Test;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
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 java.io.File;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Base class for all JMH benchmarks.
|
||||
*/
|
||||
@Warmup(iterations = AbstractMicrobenchmark.DEFAULT_WARMUP_ITERATIONS)
|
||||
@Measurement(iterations = AbstractMicrobenchmark.DEFAULT_MEASURE_ITERATIONS)
|
||||
@Fork(AbstractMicrobenchmark.DEFAULT_FORKS)
|
||||
@State(Scope.Thread)
|
||||
public class AbstractMicrobenchmark {
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
|
||||
|
||||
/**
|
||||
* Default implementation of the JMH microbenchmark adapter. There may be context switches introduced by this harness.
|
||||
*/
|
||||
@Fork(AbstractMicrobenchmark.DEFAULT_FORKS)
|
||||
public class AbstractMicrobenchmark extends AbstractMicrobenchmarkBase {
|
||||
|
||||
protected static final int DEFAULT_WARMUP_ITERATIONS = 10;
|
||||
protected static final int DEFAULT_MEASURE_ITERATIONS = 10;
|
||||
protected static final int DEFAULT_FORKS = 2;
|
||||
protected static final String[] JVM_ARGS;
|
||||
|
||||
static {
|
||||
final String[] customArgs = {
|
||||
"-Xms768m", "-Xmx768m", "-XX:MaxDirectMemorySize=768m", "-Dharness.executor=CUSTOM",
|
||||
"-Dharness.executor.class=io.netty.microbench.util.AbstractMicrobenchmark$HarnessExecutor" };
|
||||
|
||||
JVM_ARGS = new String[BASE_JVM_ARGS.length + customArgs.length];
|
||||
System.arraycopy(BASE_JVM_ARGS, 0, JVM_ARGS, 0, BASE_JVM_ARGS.length);
|
||||
System.arraycopy(customArgs, 0, JVM_ARGS, BASE_JVM_ARGS.length, customArgs.length);
|
||||
}
|
||||
|
||||
public static final class HarnessExecutor extends ThreadPoolExecutor {
|
||||
public HarnessExecutor(int maxThreads, String prefix) {
|
||||
@ -55,69 +52,22 @@ public class AbstractMicrobenchmark {
|
||||
}
|
||||
}
|
||||
|
||||
protected static final String[] JVM_ARGS = {
|
||||
"-server", "-dsa", "-da", "-ea:io.netty...", "-Xms768m", "-Xmx768m",
|
||||
"-XX:MaxDirectMemorySize=768m", "-XX:+AggressiveOpts", "-XX:+UseBiasedLocking",
|
||||
"-XX:+UseFastAccessorMethods", "-XX:+UseStringCache", "-XX:+OptimizeStringConcat",
|
||||
"-XX:+HeapDumpOnOutOfMemoryError", "-Dio.netty.noResourceLeakDetection",
|
||||
"-Dharness.executor=CUSTOM",
|
||||
"-Dharness.executor.class=io.netty.microbench.util.AbstractMicrobenchmark$HarnessExecutor"
|
||||
};
|
||||
|
||||
static {
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
|
||||
@Override
|
||||
protected String[] jvmArgs() {
|
||||
return JVM_ARGS;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run() throws Exception {
|
||||
String className = getClass().getSimpleName();
|
||||
|
||||
ChainedOptionsBuilder runnerOptions = new OptionsBuilder()
|
||||
.include(".*" + className + ".*")
|
||||
.jvmArgs(JVM_ARGS);
|
||||
|
||||
if (getWarmupIterations() > 0) {
|
||||
runnerOptions.warmupIterations(getWarmupIterations());
|
||||
}
|
||||
|
||||
if (getMeasureIterations() > 0) {
|
||||
runnerOptions.measurementIterations(getMeasureIterations());
|
||||
}
|
||||
protected ChainedOptionsBuilder newOptionsBuilder() throws Exception {
|
||||
ChainedOptionsBuilder runnerOptions = super.newOptionsBuilder();
|
||||
|
||||
if (getForks() > 0) {
|
||||
runnerOptions.forks(getForks());
|
||||
}
|
||||
|
||||
if (getReportDir() != null) {
|
||||
String filePath = getReportDir() + className + ".json";
|
||||
File file = new File(filePath);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
} else {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
}
|
||||
|
||||
runnerOptions.resultFormat(ResultFormatType.JSON);
|
||||
runnerOptions.result(filePath);
|
||||
}
|
||||
|
||||
new Runner(runnerOptions.build()).run();
|
||||
}
|
||||
|
||||
protected int getWarmupIterations() {
|
||||
return SystemPropertyUtil.getInt("warmupIterations", -1);
|
||||
}
|
||||
|
||||
protected int getMeasureIterations() {
|
||||
return SystemPropertyUtil.getInt("measureIterations", -1);
|
||||
return runnerOptions;
|
||||
}
|
||||
|
||||
protected int getForks() {
|
||||
return SystemPropertyUtil.getInt("forks", -1);
|
||||
}
|
||||
|
||||
protected String getReportDir() {
|
||||
return SystemPropertyUtil.get("perfReportDir");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you 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 io.netty.microbench.util;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Base class for all JMH benchmarks.
|
||||
*/
|
||||
@Warmup(iterations = AbstractMicrobenchmarkBase.DEFAULT_WARMUP_ITERATIONS)
|
||||
@Measurement(iterations = AbstractMicrobenchmarkBase.DEFAULT_MEASURE_ITERATIONS)
|
||||
@State(Scope.Thread)
|
||||
public abstract class AbstractMicrobenchmarkBase {
|
||||
protected static final int DEFAULT_WARMUP_ITERATIONS = 10;
|
||||
protected static final int DEFAULT_MEASURE_ITERATIONS = 10;
|
||||
protected static final String[] BASE_JVM_ARGS = {
|
||||
"-server", "-dsa", "-da", "-ea:io.netty...", "-XX:+AggressiveOpts", "-XX:+UseBiasedLocking",
|
||||
"-XX:+UseFastAccessorMethods", "-XX:+UseStringCache", "-XX:+OptimizeStringConcat",
|
||||
"-XX:+HeapDumpOnOutOfMemoryError", "-Dio.netty.noResourceLeakDetection"};
|
||||
|
||||
static {
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
|
||||
}
|
||||
|
||||
protected ChainedOptionsBuilder newOptionsBuilder() throws Exception {
|
||||
String className = getClass().getSimpleName();
|
||||
|
||||
ChainedOptionsBuilder runnerOptions = new OptionsBuilder()
|
||||
.include(".*" + className + ".*")
|
||||
.jvmArgs(jvmArgs());
|
||||
|
||||
if (getWarmupIterations() > 0) {
|
||||
runnerOptions.warmupIterations(getWarmupIterations());
|
||||
}
|
||||
|
||||
if (getMeasureIterations() > 0) {
|
||||
runnerOptions.measurementIterations(getMeasureIterations());
|
||||
}
|
||||
|
||||
if (getReportDir() != null) {
|
||||
String filePath = getReportDir() + className + ".json";
|
||||
File file = new File(filePath);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
} else {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
}
|
||||
|
||||
runnerOptions.resultFormat(ResultFormatType.JSON);
|
||||
runnerOptions.result(filePath);
|
||||
}
|
||||
|
||||
return runnerOptions;
|
||||
}
|
||||
|
||||
protected abstract String[] jvmArgs();
|
||||
|
||||
@Test
|
||||
public void run() throws Exception {
|
||||
new Runner(newOptionsBuilder().build()).run();
|
||||
}
|
||||
|
||||
protected int getWarmupIterations() {
|
||||
return SystemPropertyUtil.getInt("warmupIterations", -1);
|
||||
}
|
||||
|
||||
protected int getMeasureIterations() {
|
||||
return SystemPropertyUtil.getInt("measureIterations", -1);
|
||||
}
|
||||
|
||||
protected String getReportDir() {
|
||||
return SystemPropertyUtil.get("perfReportDir");
|
||||
}
|
||||
|
||||
public static void handleUnexpectedException(Throwable t) {
|
||||
assertNull(t);
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you 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 io.netty.microbench.util;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.concurrent.AbstractEventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ProgressivePromise;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
|
||||
/**
|
||||
* This harness facilitates the sharing of an executor between JMH and Netty and
|
||||
* thus avoid measuring context switching in microbenchmarks.
|
||||
*/
|
||||
@Fork(AbstractSharedExecutorMicrobenchmark.DEFAULT_FORKS)
|
||||
public class AbstractSharedExecutorMicrobenchmark extends AbstractMicrobenchmarkBase {
|
||||
|
||||
protected static final int DEFAULT_FORKS = 0; // Forks has to be 0 so tasks are run immediately by JMH
|
||||
protected static final String[] JVM_ARGS;
|
||||
|
||||
static {
|
||||
final String[] customArgs = {
|
||||
"-Xms2g", "-Xmx2g", "-XX:MaxDirectMemorySize=2g", "-Dharness.executor=CUSTOM",
|
||||
"-Dharness.executor.class=io.netty.microbench.util.AbstractExecutorMicrobenchmark$DelegateHarnessExecutor" };
|
||||
|
||||
JVM_ARGS = new String[BASE_JVM_ARGS.length + customArgs.length];
|
||||
System.arraycopy(BASE_JVM_ARGS, 0, JVM_ARGS, 0, BASE_JVM_ARGS.length);
|
||||
System.arraycopy(customArgs, 0, JVM_ARGS, BASE_JVM_ARGS.length, customArgs.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the executor (in the form of an {@link EventLoop}) which JMH will use.
|
||||
* <p>
|
||||
* This must be called before JMH requires an executor to execute objects.
|
||||
* @param eventLoop Used as an executor by JMH to run benchmarks.
|
||||
*/
|
||||
public static void executor(EventLoop eventLoop) {
|
||||
DelegateHarnessExecutor.executor(eventLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* This executor allows Netty and JMH to share a common executor.
|
||||
* This is achieved by using {@link DelegateHarnessExecutor#executor(EventLoop)}
|
||||
* with the {@link EventLoop} used by Netty.
|
||||
*/
|
||||
public static final class DelegateHarnessExecutor extends AbstractEventExecutor {
|
||||
private static EventLoop executor;
|
||||
public DelegateHarnessExecutor(int maxThreads, String prefix) {
|
||||
System.out.println("Using DelegateHarnessExecutor executor " + this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the executor (in the form of an {@link EventLoop}) which JMH will use.
|
||||
* <p>
|
||||
* This must be called before JMH requires an executor to execute objects.
|
||||
* @param eventLoop Used as an executor by JMH to run benchmarks.
|
||||
*/
|
||||
public static void executor(EventLoop service) {
|
||||
executor = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inEventLoop() {
|
||||
return executor.inEventLoop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inEventLoop(Thread thread) {
|
||||
return executor.inEventLoop(thread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
|
||||
return executor.shutdownGracefully(quietPeriod, timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> terminationFuture() {
|
||||
return executor.terminationFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void shutdown() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShuttingDown() {
|
||||
return executor.isShuttingDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return executor.isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return executor.isTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) {
|
||||
try {
|
||||
return executor.awaitTermination(timeout, unit);
|
||||
} catch (InterruptedException e) {
|
||||
handleUnexpectedException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
executor.execute(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Promise<V> newPromise() {
|
||||
return executor.newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> ProgressivePromise<V> newProgressivePromise() {
|
||||
return executor.newProgressivePromise();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] jvmArgs() {
|
||||
return JVM_ARGS;
|
||||
}
|
||||
|
||||
public static void handleUnexpectedException(Throwable t) {
|
||||
assertNull(t);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user