• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Protocol;
22 import com.squareup.okhttp.internal.SslContextBuilder;
23 import com.squareup.okhttp.mockwebserver.Dispatcher;
24 import com.squareup.okhttp.mockwebserver.MockResponse;
25 import com.squareup.okhttp.mockwebserver.MockWebServer;
26 import com.squareup.okhttp.mockwebserver.RecordedRequest;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Random;
35 import java.util.concurrent.TimeUnit;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38 import java.util.zip.GZIPOutputStream;
39 import 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  */
46 public 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 
main(String[] args)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")
run()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 
toString()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 
sleep(int millis)153   private void sleep(int millis) {
154     try {
155       Thread.sleep(millis);
156     } catch (InterruptedException ignored) {
157     }
158   }
159 
startServer()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 
newResponse()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 
randomString(int length)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}. */
gzip(byte[] 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