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