Home | History | Annotate | Download | only in benchmarks
      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 
     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