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