• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 The gRPC Authors
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 
17 package io.grpc.benchmarks.qps;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static io.grpc.benchmarks.Utils.HISTOGRAM_MAX_VALUE;
21 import static io.grpc.benchmarks.Utils.HISTOGRAM_PRECISION;
22 import static io.grpc.benchmarks.Utils.saveHistogram;
23 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.ADDRESS;
24 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.CLIENT_PAYLOAD;
25 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.DURATION;
26 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.FLOW_CONTROL_WINDOW;
27 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.SAVE_HISTOGRAM;
28 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.SERVER_PAYLOAD;
29 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TARGET_QPS;
30 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TESTCA;
31 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS;
32 import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TRANSPORT;
33 
34 import io.grpc.Channel;
35 import io.grpc.ManagedChannel;
36 import io.grpc.Status;
37 import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
38 import io.grpc.benchmarks.proto.Messages.SimpleRequest;
39 import io.grpc.benchmarks.proto.Messages.SimpleResponse;
40 import io.grpc.stub.StreamObserver;
41 import java.util.Random;
42 import java.util.concurrent.Callable;
43 import org.HdrHistogram.AtomicHistogram;
44 import org.HdrHistogram.Histogram;
45 
46 /**
47  * Tries to generate traffic that closely resembles user-generated RPC traffic. This is done using
48  * a Poisson Process to average at a target QPS and the delays between calls are randomized using
49  * an exponential variate.
50  *
51  * @see <a href="http://en.wikipedia.org/wiki/Poisson_process">Poisson Process</a>
52  * @see <a href="http://en.wikipedia.org/wiki/Exponential_distribution">Exponential Distribution</a>
53  */
54 public class OpenLoopClient {
55 
56   private final ClientConfiguration config;
57 
OpenLoopClient(ClientConfiguration config)58   public OpenLoopClient(ClientConfiguration config) {
59     this.config = config;
60   }
61 
62   /**
63    * Comment for checkstyle.
64    */
main(String... args)65   public static void main(String... args) throws Exception {
66     ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder(
67         ADDRESS, TARGET_QPS, CLIENT_PAYLOAD, SERVER_PAYLOAD, TLS,
68         TESTCA, TRANSPORT, DURATION, SAVE_HISTOGRAM, FLOW_CONTROL_WINDOW);
69     ClientConfiguration config;
70     try {
71       config = configBuilder.build(args);
72     } catch (Exception e) {
73       System.out.println(e.getMessage());
74       configBuilder.printUsage();
75       return;
76     }
77     OpenLoopClient client = new OpenLoopClient(config);
78     client.run();
79   }
80 
81   /**
82    * Start the open loop client.
83    */
run()84   public void run() throws Exception {
85     if (config == null) {
86       return;
87     }
88     config.channels = 1;
89     config.directExecutor = true;
90     ManagedChannel ch = config.newChannel();
91     SimpleRequest req = config.newRequest();
92     LoadGenerationWorker worker =
93         new LoadGenerationWorker(ch, req, config.targetQps, config.duration);
94     final long start = System.nanoTime();
95     Histogram histogram = worker.call();
96     final long end = System.nanoTime();
97     printStats(histogram, end - start);
98     if (config.histogramFile != null) {
99       saveHistogram(histogram, config.histogramFile);
100     }
101     ch.shutdown();
102   }
103 
printStats(Histogram histogram, long elapsedTime)104   private void printStats(Histogram histogram, long elapsedTime) {
105     long latency50 = histogram.getValueAtPercentile(50);
106     long latency90 = histogram.getValueAtPercentile(90);
107     long latency95 = histogram.getValueAtPercentile(95);
108     long latency99 = histogram.getValueAtPercentile(99);
109     long latency999 = histogram.getValueAtPercentile(99.9);
110     long latencyMax = histogram.getValueAtPercentile(100);
111     long queriesPerSecond = histogram.getTotalCount() * 1000000000L / elapsedTime;
112 
113     StringBuilder values = new StringBuilder();
114     values.append("Server Payload Size:            ").append(config.serverPayload).append('\n')
115           .append("Client Payload Size:            ").append(config.clientPayload).append('\n')
116           .append("50%ile Latency (in micros):     ").append(latency50).append('\n')
117           .append("90%ile Latency (in micros):     ").append(latency90).append('\n')
118           .append("95%ile Latency (in micros):     ").append(latency95).append('\n')
119           .append("99%ile Latency (in micros):     ").append(latency99).append('\n')
120           .append("99.9%ile Latency (in micros):   ").append(latency999).append('\n')
121           .append("Maximum Latency (in micros):    ").append(latencyMax).append('\n')
122           .append("Actual QPS:                     ").append(queriesPerSecond).append('\n')
123           .append("Target QPS:                     ").append(config.targetQps).append('\n');
124     System.out.println(values);
125   }
126 
127   static class LoadGenerationWorker implements Callable<Histogram> {
128     final Histogram histogram = new AtomicHistogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION);
129     final BenchmarkServiceGrpc.BenchmarkServiceStub stub;
130     final SimpleRequest request;
131     final Random rnd;
132     final int targetQps;
133     final long numRpcs;
134 
LoadGenerationWorker(Channel channel, SimpleRequest request, int targetQps, int duration)135     LoadGenerationWorker(Channel channel, SimpleRequest request, int targetQps, int duration) {
136       stub = BenchmarkServiceGrpc.newStub(checkNotNull(channel, "channel"));
137       this.request = checkNotNull(request, "request");
138       this.targetQps = targetQps;
139       numRpcs = (long) targetQps * duration;
140       rnd = new Random();
141     }
142 
143     /**
144      * Discuss waiting strategy between calls. Sleeping seems to be very inaccurate
145      * (see below). On the other hand calling System.nanoTime() a lot (especially from
146      * different threads seems to impact its accuracy
147      * http://shipilev.net/blog/2014/nanotrusting-nanotime/
148      * On my system the overhead of LockSupport.park(long) seems to average at ~55 micros.
149      * // Try to sleep for 1 nanosecond and measure how long it actually takes.
150      * long start = System.nanoTime();
151      * int i = 0;
152      * while (i < 10000) {
153      *   LockSupport.parkNanos(1);
154      *   i++;
155      * }
156      * long end = System.nanoTime();
157      * System.out.println((end - start) / 10000);
158      */
159     @Override
call()160     public Histogram call() throws Exception {
161       long now = System.nanoTime();
162       long nextRpc = now;
163       long i = 0;
164       while (i < numRpcs) {
165         now = System.nanoTime();
166         if (nextRpc - now <= 0) {
167           // TODO: Add option to print how far we have been off from the target delay in micros.
168           nextRpc += nextDelay(targetQps);
169           newRpc(stub);
170           i++;
171         }
172       }
173 
174       waitForRpcsToComplete(1);
175 
176       return histogram;
177     }
178 
newRpc(BenchmarkServiceGrpc.BenchmarkServiceStub stub)179     private void newRpc(BenchmarkServiceGrpc.BenchmarkServiceStub stub) {
180       stub.unaryCall(request, new StreamObserver<SimpleResponse>() {
181 
182         private final long start = System.nanoTime();
183 
184         @Override
185         public void onNext(SimpleResponse value) {
186         }
187 
188         @Override
189         public void onError(Throwable t) {
190           Status status = Status.fromThrowable(t);
191           System.err.println("Encountered an error in unaryCall. Status is " + status);
192           t.printStackTrace();
193         }
194 
195         @Override
196         public void onCompleted() {
197           final long end = System.nanoTime();
198           histogram.recordValue((end - start) / 1000);
199         }
200       });
201     }
202 
waitForRpcsToComplete(int duration)203     private void waitForRpcsToComplete(int duration) {
204       long now = System.nanoTime();
205       long end = now + duration * 1000 * 1000 * 1000;
206       while (histogram.getTotalCount() < numRpcs && end - now > 0) {
207         now = System.nanoTime();
208       }
209     }
210 
211     private static final double DELAY_EPSILON = Math.nextUp(0d);
212 
nextDelay(int targetQps)213     private long nextDelay(int targetQps) {
214       double seconds = -Math.log(Math.max(rnd.nextDouble(), DELAY_EPSILON)) / targetQps;
215       double nanos = seconds * 1000 * 1000 * 1000;
216       return Math.round(nanos);
217     }
218   }
219 }
220