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;
|
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.CONTINUATION_FRAME_HEADER_LENGTH;
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_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;
|
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.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelPromise;
|
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()}
|
* When padding is needed it can be taken as a slice of this buffer. Users should call {@link ByteBuf#retain()}
|
||||||
* before using their slice.
|
* 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 final Http2HeadersEncoder headersEncoder;
|
||||||
private int maxFrameSize;
|
private int maxFrameSize;
|
||||||
|
@ -31,8 +31,23 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<!-- Skip tests by default; run only if -DskipTests=false is specified -->
|
<!-- Skip tests by default; run only if -DskipTests=false is specified -->
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
<epoll.arch>x86_64</epoll.arch>
|
||||||
</properties>
|
</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>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
@ -44,6 +59,17 @@
|
|||||||
<artifactId>netty-codec-http</artifactId>
|
<artifactId>netty-codec-http</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.openjdk.jmh</groupId>
|
<groupId>org.openjdk.jmh</groupId>
|
||||||
<artifactId>jmh-core</artifactId>
|
<artifactId>jmh-core</artifactId>
|
||||||
@ -79,6 +105,13 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>kr.motd.maven</groupId>
|
||||||
|
<artifactId>os-maven-plugin</artifactId>
|
||||||
|
<version>1.2.3.Final</version>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</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;
|
package io.netty.microbench.util;
|
||||||
|
|
||||||
import io.netty.util.ResourceLeakDetector;
|
|
||||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||||
import io.netty.util.internal.SystemPropertyUtil;
|
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.SynchronousQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
* Base class for all JMH benchmarks.
|
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
|
||||||
*/
|
|
||||||
@Warmup(iterations = AbstractMicrobenchmark.DEFAULT_WARMUP_ITERATIONS)
|
/**
|
||||||
@Measurement(iterations = AbstractMicrobenchmark.DEFAULT_MEASURE_ITERATIONS)
|
* Default implementation of the JMH microbenchmark adapter. There may be context switches introduced by this harness.
|
||||||
@Fork(AbstractMicrobenchmark.DEFAULT_FORKS)
|
*/
|
||||||
@State(Scope.Thread)
|
@Fork(AbstractMicrobenchmark.DEFAULT_FORKS)
|
||||||
public class AbstractMicrobenchmark {
|
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 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 static final class HarnessExecutor extends ThreadPoolExecutor {
|
||||||
public HarnessExecutor(int maxThreads, String prefix) {
|
public HarnessExecutor(int maxThreads, String prefix) {
|
||||||
@ -55,69 +52,22 @@ public class AbstractMicrobenchmark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final String[] JVM_ARGS = {
|
@Override
|
||||||
"-server", "-dsa", "-da", "-ea:io.netty...", "-Xms768m", "-Xmx768m",
|
protected String[] jvmArgs() {
|
||||||
"-XX:MaxDirectMemorySize=768m", "-XX:+AggressiveOpts", "-XX:+UseBiasedLocking",
|
return JVM_ARGS;
|
||||||
"-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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
protected ChainedOptionsBuilder newOptionsBuilder() throws Exception {
|
||||||
public void run() throws Exception {
|
ChainedOptionsBuilder runnerOptions = super.newOptionsBuilder();
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getForks() > 0) {
|
if (getForks() > 0) {
|
||||||
runnerOptions.forks(getForks());
|
runnerOptions.forks(getForks());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getReportDir() != null) {
|
return runnerOptions;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getForks() {
|
protected int getForks() {
|
||||||
return SystemPropertyUtil.getInt("forks", -1);
|
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