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:
scottmitch 2015-02-04 08:00:43 -05:00 committed by Scott Mitchell
parent 23f881b382
commit 2dda917f27
9 changed files with 1363 additions and 77 deletions

View File

@ -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;

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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 {
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}