package perf.backend;
import com.netflix.numerus.NumerusRollingNumber;
import com.netflix.numerus.NumerusRollingPercentile;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.reactivex.netty.RxNetty;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import rx.Observable;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.netflix.numerus.NumerusProperty.Factory.asProperty;
public class StartMockService {
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));
public static void main(String[] args) {
int port = 8989;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
System.out.println("Starting mock service on port " + port + "...");
startMonitoring();
RxNetty.<ByteBuf, ByteBuf>newHttpServerBuilder(port, (request, response) -> {
try {
long startTime = System.currentTimeMillis();
counter.increment(CounterEvent.REQUESTS);
return handleRequest(request, response)
.doOnCompleted(() -> {
counter.increment(CounterEvent.SUCCESS);
latency.addValue((int)(System.currentTimeMillis() - startTime));
})
.doOnError(t -> counter.increment(CounterEvent.NETTY_ERROR));
} catch (Throwable e) {
System.err.println("Server => Error [" + request.getPath() + "] => " + e);
response.setStatus(HttpResponseStatus.BAD_REQUEST);
counter.increment(CounterEvent.HTTP_ERROR);
return response.writeStringAndFlush("Error 500: Bad Request\n" + e.getMessage() + '\n');
}
}).build().startAndWait();
}
protected static Observable<Void> writeError(HttpServerRequest<?> request, HttpServerResponse<?> response, String message) {
System.err.println("Server => Error [" + request.getPath() + "] => " + message);
response.setStatus(HttpResponseStatus.BAD_REQUEST);
counter.increment(CounterEvent.HTTP_ERROR);
return response.writeStringAndFlush("Error 500: " + message + '\n');
}
protected static int getParameter(HttpServerRequest<?> request, String key, int defaultValue) {
List<String> v = request.getQueryParameters().get(key);
if (v == null || v.size() != 1) {
return defaultValue;
} else {
return Integer.parseInt(String.valueOf(v.get(0)));
}
}
private static Observable<Void> handleRequest(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
if (request.getUri().startsWith("/hello")) {
return response.writeStringAndFlush("Hello world!");
}
List<String> _id = request.getQueryParameters().get("id");
if (_id == null || _id.size() != 1) {
return writeError(request, response,
"Please provide a numerical 'id' value. It can be a random number (uuid). Received => "
+ _id);
}
long id = Long.parseLong(String.valueOf(_id.get(0)));
int delay = getParameter(request, "delay", 50); // default to 50ms server-side delay
int itemSize = getParameter(request, "itemSize", 128); // default to 128 bytes item size (assuming ascii text)
int numItems = getParameter(request, "numItems", 10); // default to 10 items in a list
// no more than 100 items
if (numItems < 1 || numItems > 100) {
return writeError(request, response, "Please choose a 'numItems' value from 1 to 100.");
}
// no larger than 50KB per item
if (itemSize < 1 || itemSize > 1024 * 50) {
return writeError(request, response, "Please choose an 'itemSize' value from 1 to 1024*50 (50KB).");
}
// no larger than 60 second delay
if (delay < 0 || delay > 60000) {
return writeError(request, response, "Please choose a 'delay' value from 0 to 60000 (60 seconds).");
}
response.setStatus(HttpResponseStatus.OK);
return MockResponse.generateJson(id, delay, itemSize, numItems)
.doOnNext(json -> counter.add(CounterEvent.BYTES, json.readableBytes()))
.flatMap(response::writeAndFlush)
.doOnTerminate(response::close);
}
private static void startMonitoring() {
Observable.interval(5, 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("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(" 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());
}).subscribe();
}
private static long getRollingSum(CounterEvent e) {
long s = counter.getRollingSum(e);
if (s > 0) {
s /= rollingSeconds;
}
return s;
}
}