Skip to content

Commit

Permalink
Use a separate, dumber mock server for benchmarks.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jchambers committed Apr 16, 2017
1 parent 35586ba commit 78ac95a
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -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<SimpleApnsPushNotification> pushNotifications;

Expand Down Expand Up @@ -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;
{
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <a href="https://github.com/jchambers">Jon Chambers</a>
*/
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<SocketChannel>() {

@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<Void> 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<Void> shutdown() {
return (this.allChannels != null) ? this.allChannels.close() : new SucceededFuture<Void>(GlobalEventExecutor.INSTANCE, null);
}
}
Original file line number Diff line number Diff line change
@@ -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<BenchmarkApnsServerHandler, BenchmarkApnsServerHandlerBuilder> {
@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 {
}
}

0 comments on commit 78ac95a

Please sign in to comment.