From 78ac95a084ff086d111514434f2c872b107b552c Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Sun, 16 Apr 2017 13:43:25 -0400 Subject: [PATCH] Use a separate, dumber mock server for benchmarks. This has two good outcomes: 1. Because we've removed a lot of logic, it's less likely that we'll be inadvertently benchmarking the server instead of the client. 2. We don't have to worry about performance as much for the mock server we use for testing; this frees us to write code that is clearer and simpler (and ideally more likely to be correct), but maybe a little slower. --- .../pushy/apns/ApnsClientBenchmark.java | 44 +++----- .../pushy/apns/BenchmarkApnsServer.java | 102 ++++++++++++++++++ .../apns/BenchmarkApnsServerHandler.java | 92 ++++++++++++++++ 3 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServer.java create mode 100644 benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServerHandler.java diff --git a/benchmark/src/main/java/com/relayrides/pushy/apns/ApnsClientBenchmark.java b/benchmark/src/main/java/com/relayrides/pushy/apns/ApnsClientBenchmark.java index 9a902f02c..57f62c547 100644 --- a/benchmark/src/main/java/com/relayrides/pushy/apns/ApnsClientBenchmark.java +++ b/benchmark/src/main/java/com/relayrides/pushy/apns/ApnsClientBenchmark.java @@ -1,43 +1,29 @@ package com.relayrides.pushy.apns; +import com.relayrides.pushy.apns.util.ApnsPayloadBuilder; +import com.relayrides.pushy.apns.util.SimpleApnsPushNotification; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.apache.commons.lang3.RandomStringUtils; +import org.openjdk.jmh.annotations.*; + import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; -import org.apache.commons.lang3.RandomStringUtils; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; - -import com.relayrides.pushy.apns.util.ApnsPayloadBuilder; -import com.relayrides.pushy.apns.util.SimpleApnsPushNotification; - -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - @State(Scope.Thread) public class ApnsClientBenchmark { - private EventLoopGroup eventLoopGroup; + private NioEventLoopGroup eventLoopGroup; private ApnsClient client; - private MockApnsServer server; + private BenchmarkApnsServer server; private List pushNotifications; @@ -65,12 +51,10 @@ public void setUp() throws Exception { .setTrustedServerCertificateChain(ApnsClientBenchmark.class.getResourceAsStream(CA_CERTIFICATE_FILENAME)) .setEventLoopGroup(this.eventLoopGroup); - final MockApnsServerBuilder serverBuilder = new MockApnsServerBuilder() - .setServerCredentials(ApnsClientBenchmark.class.getResourceAsStream(SERVER_CERTIFICATES_FILENAME), ApnsClientBenchmark.class.getResourceAsStream(SERVER_KEY_FILENAME), null) - .setEventLoopGroup(this.eventLoopGroup); - this.client = clientBuilder.build(); - this.server = serverBuilder.build(); + this.server = new BenchmarkApnsServer(ApnsClientBenchmark.class.getResourceAsStream(SERVER_CERTIFICATES_FILENAME), + ApnsClientBenchmark.class.getResourceAsStream(SERVER_KEY_FILENAME), + this.eventLoopGroup); final KeyPair keyPair; { @@ -83,10 +67,8 @@ public void setUp() throws Exception { } this.client.registerSigningKey((ECPrivateKey) keyPair.getPrivate(), TEAM_ID, KEY_ID, TOPIC); - this.server.registerPublicKey((ECPublicKey) keyPair.getPublic(), TEAM_ID, KEY_ID, TOPIC); final String token = generateRandomToken(); - this.server.registerDeviceTokenForTopic(TOPIC, token, null); this.pushNotifications = new ArrayList<>(this.notificationCount); diff --git a/benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServer.java b/benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServer.java new file mode 100644 index 000000000..afbb09db8 --- /dev/null +++ b/benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServer.java @@ -0,0 +1,102 @@ +package com.relayrides.pushy.apns; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http2.Http2SecurityUtil; +import io.netty.handler.codec.http2.Http2Settings; +import io.netty.handler.ssl.*; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.SucceededFuture; + +import javax.net.ssl.SSLException; +import java.io.InputStream; + +/** + * A simple benchmark server that sends a "looks good!" response to any and all requests. + * + * @author Jon Chambers + */ +class BenchmarkApnsServer { + + private final ServerBootstrap bootstrap; + private ChannelGroup allChannels; + + private static final int MAX_CONCURRENT_STREAMS = 500; + + public BenchmarkApnsServer(final InputStream certificateChainInputStream, final InputStream privateKeyPkcs8InputStream, final NioEventLoopGroup eventLoopGroup) throws SSLException { + final SslContext sslContext; + { + final SslProvider sslProvider; + + if (OpenSsl.isAvailable()) { + if (OpenSsl.isAlpnSupported()) { + sslProvider = SslProvider.OPENSSL; + } else { + sslProvider = SslProvider.JDK; + } + } else { + sslProvider = SslProvider.JDK; + } + + sslContext = SslContextBuilder.forServer(certificateChainInputStream, privateKeyPkcs8InputStream, null) + .sslProvider(sslProvider) + .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) + .clientAuth(ClientAuth.OPTIONAL) + .applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2)) + .build(); + } + + this.bootstrap = new ServerBootstrap(); + this.bootstrap.group(eventLoopGroup); + + this.bootstrap.channel(NioServerSocketChannel.class); + this.bootstrap.childHandler(new ChannelInitializer() { + + @Override + protected void initChannel(final SocketChannel channel) throws Exception { + final SslHandler sslHandler = sslContext.newHandler(channel.alloc()); + channel.pipeline().addLast(sslHandler); + channel.pipeline().addLast(new ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_1_1) { + + @Override + protected void configurePipeline(final ChannelHandlerContext context, final String protocol) throws Exception { + if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { + context.pipeline().addLast(new BenchmarkApnsServerHandler.BenchmarkApnsServerHandlerBuilder() + .initialSettings(new Http2Settings().maxConcurrentStreams(MAX_CONCURRENT_STREAMS)) + .build()); + + BenchmarkApnsServer.this.allChannels.add(context.channel()); + } else { + throw new IllegalStateException("Unexpected protocol: " + protocol); + } + } + }); + } + }); + } + + public Future start(final int port) { + final ChannelFuture channelFuture = this.bootstrap.bind(port); + + this.allChannels = new DefaultChannelGroup(channelFuture.channel().eventLoop(), true); + this.allChannels.add(channelFuture.channel()); + + return channelFuture; + } + + public Future shutdown() { + return (this.allChannels != null) ? this.allChannels.close() : new SucceededFuture(GlobalEventExecutor.INSTANCE, null); + } +} diff --git a/benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServerHandler.java b/benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServerHandler.java new file mode 100644 index 000000000..5cabd9e10 --- /dev/null +++ b/benchmark/src/main/java/com/relayrides/pushy/apns/BenchmarkApnsServerHandler.java @@ -0,0 +1,92 @@ +package com.relayrides.pushy.apns; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http2.*; + +class BenchmarkApnsServerHandler extends Http2ConnectionHandler implements Http2FrameListener { + + private static final Http2Headers SUCCESS_HEADERS = new DefaultHttp2Headers() + .status(HttpResponseStatus.OK.codeAsText()); + + public static final class BenchmarkApnsServerHandlerBuilder extends AbstractHttp2ConnectionHandlerBuilder { + @Override + public BenchmarkApnsServerHandlerBuilder initialSettings(final Http2Settings initialSettings) { + return super.initialSettings(initialSettings); + } + + @Override + public BenchmarkApnsServerHandler build(final Http2ConnectionDecoder decoder, final Http2ConnectionEncoder encoder, final Http2Settings initialSettings) { + final BenchmarkApnsServerHandler handler = new BenchmarkApnsServerHandler(decoder, encoder, initialSettings); + this.frameListener(handler); + return handler; + } + + @Override + public BenchmarkApnsServerHandler build() { + return super.build(); + } + } + + protected BenchmarkApnsServerHandler(final Http2ConnectionDecoder decoder, final Http2ConnectionEncoder encoder, final Http2Settings initialSettings) { + super(decoder, encoder, initialSettings); + } + + @Override + public int onDataRead(final ChannelHandlerContext context, final int streamId, final ByteBuf data, final int padding, final boolean endOfStream) throws Http2Exception { + if (endOfStream) { + this.encoder().writeHeaders(context, streamId, SUCCESS_HEADERS, 0, true, context.channel().newPromise()); + } + + return data.readableBytes() + padding; + } + + @Override + public void onHeadersRead(final ChannelHandlerContext ctx, final int streamId, final Http2Headers headers, final int padding, final boolean endOfStream) throws Http2Exception { + } + + @Override + public void onHeadersRead(final ChannelHandlerContext ctx, final int streamId, final Http2Headers headers, final int streamDependency, final short weight, final boolean exclusive, final int padding, final boolean endOfStream) throws Http2Exception { + } + + @Override + public void onPriorityRead(final ChannelHandlerContext ctx, final int streamId, final int streamDependency, final short weight, final boolean exclusive) throws Http2Exception { + } + + @Override + public void onRstStreamRead(final ChannelHandlerContext ctx, final int streamId, final long errorCode) throws Http2Exception { + } + + @Override + public void onSettingsAckRead(final ChannelHandlerContext ctx) throws Http2Exception { + } + + @Override + public void onSettingsRead(final ChannelHandlerContext ctx, final Http2Settings settings) throws Http2Exception { + } + + @Override + public void onPingRead(final ChannelHandlerContext ctx, final ByteBuf data) throws Http2Exception { + } + + @Override + public void onPingAckRead(final ChannelHandlerContext ctx, final ByteBuf data) throws Http2Exception { + } + + @Override + public void onPushPromiseRead(final ChannelHandlerContext ctx, final int streamId, final int promisedStreamId, final Http2Headers headers, final int padding) throws Http2Exception { + } + + @Override + public void onGoAwayRead(final ChannelHandlerContext ctx, final int lastStreamId, final long errorCode, final ByteBuf debugData) throws Http2Exception { + } + + @Override + public void onWindowUpdateRead(final ChannelHandlerContext ctx, final int streamId, final int windowSizeIncrement) throws Http2Exception { + } + + @Override + public void onUnknownFrame(final ChannelHandlerContext ctx, final byte frameType, final int streamId, final Http2Flags flags, final ByteBuf payload) throws Http2Exception { + } +}