1 /* 2 * Copyright (C) 2014 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp.benchmarks; 17 18 import com.google.caliper.Param; 19 import com.google.caliper.model.ArbitraryMeasurement; 20 import com.google.caliper.runner.CaliperMain; 21 import com.squareup.okhttp.HttpUrl; 22 import com.squareup.okhttp.Protocol; 23 import com.squareup.okhttp.internal.SslContextBuilder; 24 import com.squareup.okhttp.mockwebserver.Dispatcher; 25 import com.squareup.okhttp.mockwebserver.MockResponse; 26 import com.squareup.okhttp.mockwebserver.MockWebServer; 27 import com.squareup.okhttp.mockwebserver.RecordedRequest; 28 import java.io.IOException; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.List; 32 import java.util.Random; 33 import java.util.concurrent.TimeUnit; 34 import java.util.logging.Level; 35 import java.util.logging.Logger; 36 import javax.net.ssl.SSLContext; 37 import okio.Buffer; 38 import okio.GzipSink; 39 40 /** 41 * This benchmark is fake, but may be useful for certain relative comparisons. 42 * It uses a local connection to a MockWebServer to measure how many identical 43 * requests per second can be carried over a fixed number of threads. 44 */ 45 public class Benchmark extends com.google.caliper.Benchmark { 46 private static final int NUM_REPORTS = 10; 47 private static final boolean VERBOSE = false; 48 49 private final Random random = new Random(0); 50 51 /** Which client to run.*/ 52 @Param 53 Client client; 54 55 /** How many concurrent requests to execute. */ 56 @Param({ "1", "10" }) 57 int concurrencyLevel; 58 59 /** How many requests to enqueue to await threads to execute them. */ 60 @Param({ "10" }) 61 int targetBacklog; 62 63 /** True to use TLS. */ 64 // TODO: compare different ciphers? 65 @Param 66 boolean tls; 67 68 /** True to use gzip content-encoding for the response body. */ 69 @Param 70 boolean gzip; 71 72 /** Don't combine chunked with SPDY_3 or HTTP_2; that's not allowed. */ 73 @Param 74 boolean chunked; 75 76 /** The size of the HTTP response body, in uncompressed bytes. */ 77 @Param({ "128", "1048576" }) 78 int bodyByteCount; 79 80 /** How many additional headers were included, beyond the built-in ones. */ 81 @Param({ "0", "20" }) 82 int headerCount; 83 84 /** Which ALPN protocols are in use. Only useful with TLS. */ 85 List<Protocol> protocols = Arrays.asList(Protocol.HTTP_1_1); 86 main(String[] args)87 public static void main(String[] args) { 88 List<String> allArgs = new ArrayList<>(); 89 allArgs.add("--instrument"); 90 allArgs.add("arbitrary"); 91 allArgs.addAll(Arrays.asList(args)); 92 93 CaliperMain.main(Benchmark.class, allArgs.toArray(new String[allArgs.size()])); 94 } 95 96 @ArbitraryMeasurement(description = "requests per second") run()97 public double run() throws Exception { 98 if (VERBOSE) System.out.println(toString()); 99 HttpClient httpClient = client.create(); 100 101 // Prepare the client & server 102 httpClient.prepare(this); 103 MockWebServer server = startServer(); 104 HttpUrl url = server.url("/"); 105 106 int requestCount = 0; 107 long reportStart = System.nanoTime(); 108 long reportPeriod = TimeUnit.SECONDS.toNanos(1); 109 int reports = 0; 110 double best = 0.0; 111 112 // Run until we've printed enough reports. 113 while (reports < NUM_REPORTS) { 114 // Print a report if we haven't recently. 115 long now = System.nanoTime(); 116 double reportDuration = now - reportStart; 117 if (reportDuration > reportPeriod) { 118 double requestsPerSecond = requestCount / reportDuration * TimeUnit.SECONDS.toNanos(1); 119 if (VERBOSE) { 120 System.out.println(String.format("Requests per second: %.1f", requestsPerSecond)); 121 } 122 best = Math.max(best, requestsPerSecond); 123 requestCount = 0; 124 reportStart = now; 125 reports++; 126 } 127 128 // Fill the job queue with work. 129 while (httpClient.acceptingJobs()) { 130 httpClient.enqueue(url); 131 requestCount++; 132 } 133 134 // The job queue is full. Take a break. 135 sleep(1); 136 } 137 138 return best; 139 } 140 toString()141 @Override public String toString() { 142 List<Object> modifiers = new ArrayList<>(); 143 if (tls) modifiers.add("tls"); 144 if (gzip) modifiers.add("gzip"); 145 if (chunked) modifiers.add("chunked"); 146 modifiers.addAll(protocols); 147 148 return String.format("%s %s\nbodyByteCount=%s headerCount=%s concurrencyLevel=%s", 149 client, modifiers, bodyByteCount, headerCount, concurrencyLevel); 150 } 151 sleep(int millis)152 private void sleep(int millis) { 153 try { 154 Thread.sleep(millis); 155 } catch (InterruptedException ignored) { 156 } 157 } 158 startServer()159 private MockWebServer startServer() throws IOException { 160 Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING); 161 MockWebServer server = new MockWebServer(); 162 163 if (tls) { 164 SSLContext sslContext = SslContextBuilder.localhost(); 165 server.useHttps(sslContext.getSocketFactory(), false); 166 server.setProtocols(protocols); 167 } 168 169 final MockResponse response = newResponse(); 170 server.setDispatcher(new Dispatcher() { 171 @Override public MockResponse dispatch(RecordedRequest request) { 172 return response; 173 } 174 }); 175 176 server.start(); 177 return server; 178 } 179 newResponse()180 private MockResponse newResponse() throws IOException { 181 byte[] bytes = new byte[bodyByteCount]; 182 random.nextBytes(bytes); 183 Buffer body = new Buffer().write(bytes); 184 185 MockResponse result = new MockResponse(); 186 187 if (gzip) { 188 Buffer gzipBody = new Buffer(); 189 GzipSink gzipSink = new GzipSink(gzipBody); 190 gzipSink.write(body, body.size()); 191 gzipSink.close(); 192 body = gzipBody; 193 result.addHeader("Content-Encoding: gzip"); 194 } 195 196 if (chunked) { 197 result.setChunkedBody(body, 1024); 198 } else { 199 result.setBody(body); 200 } 201 202 for (int i = 0; i < headerCount; i++) { 203 result.addHeader(randomString(12), randomString(20)); 204 } 205 206 return result; 207 } 208 randomString(int length)209 private String randomString(int length) { 210 String alphabet = "-abcdefghijklmnopqrstuvwxyz"; 211 char[] result = new char[length]; 212 for (int i = 0; i < length; i++) { 213 result[i] = alphabet.charAt(random.nextInt(alphabet.length())); 214 } 215 return new String(result); 216 } 217 } 218