package perf.test.rxnetty;
import com.netflix.numerus.NumerusRollingNumber;
import com.netflix.numerus.NumerusRollingPercentile;
import io.netty.buffer.ByteBuf;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.reactivex.netty.RxNetty;
import io.reactivex.netty.channel.SingleNioLoopProvider;
import io.reactivex.netty.client.PoolExhaustedException;
import io.reactivex.netty.metrics.Clock;
import perf.test.utils.JsonParseException;
import rx.Observable;
import java.io.IOException;
import java.net.SocketException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import static com.netflix.numerus.NumerusProperty.Factory.asProperty;
public final class StartServer {
static final int rollingSeconds = 5;
static final NumerusRollingNumber counter = new NumerusRollingNumber(CounterEvent.SUCCESS,
asProperty( rollingSeconds * 1000), asProperty(10));
static final NumerusRollingPercentile latency = new NumerusRollingPercentile(asProperty(rollingSeconds * 1000),
asProperty(10),
asProperty(1000), asProperty(Boolean.TRUE));
private static TestRouteBasic route;
private static TestRouteHello routeHello;
public static void main(String[] args) {
Clock.disableSystemTimeCalls();
int eventLoops = Runtime.getRuntime().availableProcessors();
int port = 8888;
String backendHost = "127.0.0.1";
int backendPort = 8989;
if (args.length == 0) {
// use defaults
} else if (args.length == 4) {
eventLoops = Integer.parseInt(args[0]);
port = Integer.parseInt(args[1]);
backendHost = args[2];
backendPort = Integer.parseInt(args[3]);
} else {
System.err.println(
"Execute with either no argument (for defaults) or 4 arguments: EVENTLOOPS, PORT, BACKEND_HOST, BACKEND_PORT");
System.exit(-1);
}
System.out.println(String.format("Using eventloops: %d port: %d backend host: %s backend port: %d", eventLoops,
port, backendHost, backendPort));
route = new TestRouteBasic(backendHost, backendPort);
routeHello = new TestRouteHello();
SingleNioLoopProvider provider = new SingleNioLoopProvider(eventLoops);
RxNetty.useEventLoopProvider(provider);
System.out.println("Starting service on port " + port + " with backend at " + backendHost + ':' + backendPort + " ...");
startMonitoring();
RxNetty.<ByteBuf, ByteBuf>newHttpServerBuilder(port, (request, response) -> {
try {
if (request.getUri().startsWith("/hello")) {
return routeHello.handle(request, response);
}
long startTime = System.currentTimeMillis();
counter.increment(CounterEvent.REQUESTS);
return route.handle(request, response)
.doOnCompleted(() -> {
counter.increment(CounterEvent.SUCCESS);
latency.addValue((int)(System.currentTimeMillis() - startTime));
})
.onErrorResumeNext(t -> {
if (t instanceof PoolExhaustedException) {
counter.increment(CounterEvent.CLIENT_POOL_EXHAUSTION);
} else if (t instanceof SocketException) {
counter.increment(CounterEvent.SOCKET_EXCEPTION);
} else if (t instanceof IOException) {
counter.increment(CounterEvent.IO_EXCEPTION);
} else if (t instanceof CancellationException) {
counter.increment(CounterEvent.CANCELLATION_EXCEPTION);
} else if (t instanceof JsonParseException) {
counter.increment(CounterEvent.PARSING_EXCEPTION);
} else {
counter.increment(CounterEvent.NETTY_ERROR);
}
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
response.writeString("");
return response.close(false);
});
} catch (Throwable e) {
System.err.println("Server => Error [" + request.getPath() + "] => " + e);
counter.increment(CounterEvent.NETTY_ERROR);
response.setStatus(HttpResponseStatus.BAD_REQUEST);
return response.writeStringAndFlush("Error 400: Bad Request\n" + e.getMessage() + '\n');
}
}).eventLoops(new NioEventLoopGroup(1), provider.globalServerEventLoop()).build()
.withErrorHandler(throwable -> Observable.empty())
.withErrorResponseGenerator((response, error) -> System.err.println("Error: " + error.getMessage()))
.startAndWait();
}
private static void startMonitoring() {
int interval = 5;
Observable.interval(interval, TimeUnit.SECONDS).doOnNext(l -> {
long totalRequestsInLastWindow = getRollingSum(CounterEvent.REQUESTS);
if (totalRequestsInLastWindow <= 0) {
return; // Don't print anything if there weren't any requests coming.
}
StringBuilder msg = new StringBuilder();
msg.append("########################################################################################").append(
'\n');
msg.append("Time since start (seconds): " + l * interval).append('\n');
msg.append("########################################################################################").append(
'\n');
msg.append("Total => ");
msg.append(" Requests: ").append(counter.getCumulativeSum(CounterEvent.REQUESTS));
msg.append(" Success: ").append(counter.getCumulativeSum(CounterEvent.SUCCESS));
msg.append(" Error: ").append(counter.getCumulativeSum(CounterEvent.HTTP_ERROR));
msg.append(" Netty Error: ").append(counter.getCumulativeSum(CounterEvent.NETTY_ERROR));
msg.append(" Client Pool Exhausted: ").append(counter.getCumulativeSum(CounterEvent.CLIENT_POOL_EXHAUSTION));
msg.append(" Socket Exception: ").append(counter.getCumulativeSum(CounterEvent.SOCKET_EXCEPTION));
msg.append(" I/O Exception: ").append(counter.getCumulativeSum(CounterEvent.IO_EXCEPTION));
msg.append(" Cancellation Exception: ").append(counter.getCumulativeSum(CounterEvent.CANCELLATION_EXCEPTION));
msg.append(" Parsing Exception: ").append(counter.getCumulativeSum(CounterEvent.PARSING_EXCEPTION));
msg.append(" Bytes: ").append(counter.getCumulativeSum(CounterEvent.BYTES) / 1024).append("kb");
msg.append(" \n Rolling =>");
msg.append(" Requests: ").append(getRollingSum(CounterEvent.REQUESTS)).append("/s");
msg.append(" Success: ").append(getRollingSum(CounterEvent.SUCCESS)).append("/s");
msg.append(" Error: ").append(getRollingSum(CounterEvent.HTTP_ERROR)).append("/s");
msg.append(" Netty Error: ").append(getRollingSum(CounterEvent.NETTY_ERROR)).append("/s");
msg.append(" Bytes: ").append(getRollingSum(CounterEvent.BYTES) / 1024).append("kb/s");
msg.append(" \n Latency (ms) => 50th: ").append(latency.getPercentile(50.0)).append(
" 90th: ").append(latency.getPercentile(90.0));
msg.append(" 99th: ").append(latency.getPercentile(99.0)).append(" 100th: ").append(latency.getPercentile(
100.0));
System.out.println(msg.toString());
StringBuilder n = new StringBuilder();
ConnectionPoolMetricListener stats = route.getStats();
n.append(" Netty => Used: ").append(stats.getInUseCount());
n.append(" Idle: ").append(stats.getIdleCount());
n.append(" Total Conns: ").append(stats.getTotalConnections());
n.append(" AcqReq: ").append(stats.getPendingAcquire());
n.append(" RelReq: ").append(stats.getPendingRelease());
System.out.println(n.toString());
}).subscribe();
}
private static long getRollingSum(CounterEvent e) {
long s = counter.getRollingSum(e);
if (s > 0) {
s /= rollingSeconds;
}
return s;
}
}