blob: 04b7f7fbff7e62782ba9dc8d0784ec048a028d92 [file] [log] [blame]
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.benchmarks;
import com.google.caliper.Param;
import com.google.caliper.model.ArbitraryMeasurement;
import com.google.caliper.runner.CaliperMain;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.mockwebserver.Dispatcher;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import okio.Buffer;
import okio.GzipSink;
/**
* This benchmark is fake, but may be useful for certain relative comparisons.
* It uses a local connection to a MockWebServer to measure how many identical
* requests per second can be carried over a fixed number of threads.
*/
public class Benchmark extends com.google.caliper.Benchmark {
private static final int NUM_REPORTS = 10;
private static final boolean VERBOSE = false;
private final Random random = new Random(0);
/** Which client to run.*/
@Param
Client client;
/** How many concurrent requests to execute. */
@Param({ "1", "10" })
int concurrencyLevel;
/** How many requests to enqueue to await threads to execute them. */
@Param({ "10" })
int targetBacklog;
/** True to use TLS. */
// TODO: compare different ciphers?
@Param
boolean tls;
/** True to use gzip content-encoding for the response body. */
@Param
boolean gzip;
/** Don't combine chunked with SPDY_3 or HTTP_2; that's not allowed. */
@Param
boolean chunked;
/** The size of the HTTP response body, in uncompressed bytes. */
@Param({ "128", "1048576" })
int bodyByteCount;
/** How many additional headers were included, beyond the built-in ones. */
@Param({ "0", "20" })
int headerCount;
/** Which ALPN protocols are in use. Only useful with TLS. */
List<Protocol> protocols = Arrays.asList(Protocol.HTTP_1_1);
public static void main(String[] args) {
List<String> allArgs = new ArrayList<>();
allArgs.add("--instrument");
allArgs.add("arbitrary");
allArgs.addAll(Arrays.asList(args));
CaliperMain.main(Benchmark.class, allArgs.toArray(new String[allArgs.size()]));
}
@ArbitraryMeasurement(description = "requests per second")
public double run() throws Exception {
if (VERBOSE) System.out.println(toString());
HttpClient httpClient = client.create();
// Prepare the client & server
httpClient.prepare(this);
MockWebServer server = startServer();
HttpUrl url = server.url("/");
int requestCount = 0;
long reportStart = System.nanoTime();
long reportPeriod = TimeUnit.SECONDS.toNanos(1);
int reports = 0;
double best = 0.0;
// Run until we've printed enough reports.
while (reports < NUM_REPORTS) {
// Print a report if we haven't recently.
long now = System.nanoTime();
double reportDuration = now - reportStart;
if (reportDuration > reportPeriod) {
double requestsPerSecond = requestCount / reportDuration * TimeUnit.SECONDS.toNanos(1);
if (VERBOSE) {
System.out.println(String.format("Requests per second: %.1f", requestsPerSecond));
}
best = Math.max(best, requestsPerSecond);
requestCount = 0;
reportStart = now;
reports++;
}
// Fill the job queue with work.
while (httpClient.acceptingJobs()) {
httpClient.enqueue(url);
requestCount++;
}
// The job queue is full. Take a break.
sleep(1);
}
return best;
}
@Override public String toString() {
List<Object> modifiers = new ArrayList<>();
if (tls) modifiers.add("tls");
if (gzip) modifiers.add("gzip");
if (chunked) modifiers.add("chunked");
modifiers.addAll(protocols);
return String.format("%s %s\nbodyByteCount=%s headerCount=%s concurrencyLevel=%s",
client, modifiers, bodyByteCount, headerCount, concurrencyLevel);
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ignored) {
}
}
private MockWebServer startServer() throws IOException {
Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING);
MockWebServer server = new MockWebServer();
if (tls) {
SSLContext sslContext = SslContextBuilder.localhost();
server.useHttps(sslContext.getSocketFactory(), false);
server.setProtocols(protocols);
}
final MockResponse response = newResponse();
server.setDispatcher(new Dispatcher() {
@Override public MockResponse dispatch(RecordedRequest request) {
return response;
}
});
server.start();
return server;
}
private MockResponse newResponse() throws IOException {
byte[] bytes = new byte[bodyByteCount];
random.nextBytes(bytes);
Buffer body = new Buffer().write(bytes);
MockResponse result = new MockResponse();
if (gzip) {
Buffer gzipBody = new Buffer();
GzipSink gzipSink = new GzipSink(gzipBody);
gzipSink.write(body, body.size());
gzipSink.close();
body = gzipBody;
result.addHeader("Content-Encoding: gzip");
}
if (chunked) {
result.setChunkedBody(body, 1024);
} else {
result.setBody(body);
}
for (int i = 0; i < headerCount; i++) {
result.addHeader(randomString(12), randomString(20));
}
return result;
}
private String randomString(int length) {
String alphabet = "-abcdefghijklmnopqrstuvwxyz";
char[] result = new char[length];
for (int i = 0; i < length; i++) {
result[i] = alphabet.charAt(random.nextInt(alphabet.length()));
}
return new String(result);
}
}