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