blob: a0eb468c5bb2d2e012ba9e9c4fd2864c7ed47f45 [file] [log] [blame]
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -07001#region Copyright notice and license
2
Craig Tiller6169d5f2016-03-31 07:46:18 -07003// Copyright 2015, Google Inc.
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -07004// All rights reserved.
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10// * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12// * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16// * Neither the name of Google Inc. nor the names of its
17// contributors may be used to endorse or promote products derived from
18// this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32#endregion
33
34using System;
Jan Tattermusch09c5e052016-05-18 22:24:27 -070035using System.Collections.Concurrent;
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -070036using System.Collections.Generic;
37using System.Diagnostics;
38using System.IO;
39using System.Linq;
40using System.Text.RegularExpressions;
41using System.Threading;
42using System.Threading.Tasks;
43using Google.Protobuf;
44using Grpc.Core;
Jan Tattermusch09c5e052016-05-18 22:24:27 -070045using Grpc.Core.Internal;
Jan Tattermusch3d6644a2016-03-21 09:46:15 -070046using Grpc.Core.Logging;
Jan Tattermusch09c5e052016-05-18 22:24:27 -070047using Grpc.Core.Profiling;
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -070048using Grpc.Core.Utils;
49using NUnit.Framework;
50using Grpc.Testing;
51
52namespace Grpc.IntegrationTesting
53{
54 /// <summary>
55 /// Helper methods to start client runners for performance testing.
56 /// </summary>
Jan Tattermusch3d6644a2016-03-21 09:46:15 -070057 public class ClientRunners
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -070058 {
Jan Tattermusch3d6644a2016-03-21 09:46:15 -070059 static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<ClientRunners>();
60
Jan Tattermusch09c5e052016-05-18 22:24:27 -070061 // Profilers to use for clients.
62 static readonly BlockingCollection<BasicProfiler> profilers = new BlockingCollection<BasicProfiler>();
63
64 internal static void AddProfiler(BasicProfiler profiler)
65 {
66 GrpcPreconditions.CheckNotNull(profiler);
67 profilers.Add(profiler);
68 }
69
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -070070 /// <summary>
71 /// Creates a started client runner.
72 /// </summary>
73 public static IClientRunner CreateStarted(ClientConfig config)
74 {
Jan Tattermusch3d6644a2016-03-21 09:46:15 -070075 Logger.Debug("ClientConfig: {0}", config);
Jan Tattermusch3d6644a2016-03-21 09:46:15 -070076
Jan Tattermusch3d6644a2016-03-21 09:46:15 -070077 if (config.AsyncClientThreads != 0)
78 {
79 Logger.Warning("ClientConfig.AsyncClientThreads is not supported for C#. Ignoring the value");
80 }
81 if (config.CoreLimit != 0)
82 {
83 Logger.Warning("ClientConfig.CoreLimit is not supported for C#. Ignoring the value");
84 }
85 if (config.CoreList.Count > 0)
86 {
87 Logger.Warning("ClientConfig.CoreList is not supported for C#. Ignoring the value");
88 }
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -070089
Jan Tattermusche81adf32016-04-04 15:59:50 -070090 var channels = CreateChannels(config.ClientChannels, config.ServerTargets, config.SecurityParams);
91
92 return new ClientRunnerImpl(channels,
93 config.ClientType,
94 config.RpcType,
95 config.OutstandingRpcsPerChannel,
96 config.LoadParams,
97 config.PayloadConfig,
Jan Tattermusch09c5e052016-05-18 22:24:27 -070098 config.HistogramParams,
99 () => GetNextProfiler());
Jan Tattermusche81adf32016-04-04 15:59:50 -0700100 }
101
102 private static List<Channel> CreateChannels(int clientChannels, IEnumerable<string> serverTargets, SecurityParams securityParams)
103 {
104 GrpcPreconditions.CheckArgument(clientChannels > 0, "clientChannels needs to be at least 1.");
105 GrpcPreconditions.CheckArgument(serverTargets.Count() > 0, "at least one serverTarget needs to be specified.");
106
107 var credentials = securityParams != null ? TestCredentials.CreateSslCredentials() : ChannelCredentials.Insecure;
Jan Tattermusch3d6644a2016-03-21 09:46:15 -0700108 List<ChannelOption> channelOptions = null;
Jan Tattermusche81adf32016-04-04 15:59:50 -0700109 if (securityParams != null && securityParams.ServerHostOverride != "")
Jan Tattermusch3d6644a2016-03-21 09:46:15 -0700110 {
111 channelOptions = new List<ChannelOption>
112 {
Jan Tattermusche81adf32016-04-04 15:59:50 -0700113 new ChannelOption(ChannelOptions.SslTargetNameOverride, securityParams.ServerHostOverride)
Jan Tattermusch3d6644a2016-03-21 09:46:15 -0700114 };
115 }
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700116
Jan Tattermusche81adf32016-04-04 15:59:50 -0700117 var result = new List<Channel>();
118 for (int i = 0; i < clientChannels; i++)
119 {
120 var target = serverTargets.ElementAt(i % serverTargets.Count());
121 var channel = new Channel(target, credentials, channelOptions);
122 result.Add(channel);
123 }
124 return result;
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700125 }
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700126
127 private static BasicProfiler GetNextProfiler()
128 {
129 BasicProfiler result = null;
130 profilers.TryTake(out result);
131 return result;
132 }
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700133 }
134
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700135 internal class ClientRunnerImpl : IClientRunner
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700136 {
Jan Tattermusch18ce9d62015-11-18 15:41:10 -0800137 const double SecondsToNanos = 1e9;
138
Jan Tattermusche81adf32016-04-04 15:59:50 -0700139 readonly List<Channel> channels;
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700140 readonly ClientType clientType;
141 readonly RpcType rpcType;
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700142 readonly PayloadConfig payloadConfig;
Jan Tattermuschd7079b22017-05-16 19:55:57 +0200143 readonly Lazy<byte[]> cachedByteBufferRequest;
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200144 readonly ThreadLocal<Histogram> threadLocalHistogram;
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700145
Jan Tattermusche81adf32016-04-04 15:59:50 -0700146 readonly List<Task> runnerTasks;
147 readonly CancellationTokenSource stoppedCts = new CancellationTokenSource();
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700148 readonly WallClockStopwatch wallClockStopwatch = new WallClockStopwatch();
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700149 readonly AtomicCounter statsResetCount = new AtomicCounter();
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700150
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700151 public ClientRunnerImpl(List<Channel> channels, ClientType clientType, RpcType rpcType, int outstandingRpcsPerChannel, LoadParams loadParams, PayloadConfig payloadConfig, HistogramParams histogramParams, Func<BasicProfiler> profilerFactory)
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700152 {
Jan Tattermusche81adf32016-04-04 15:59:50 -0700153 GrpcPreconditions.CheckArgument(outstandingRpcsPerChannel > 0, "outstandingRpcsPerChannel");
Jan Tattermusch8c839e82016-04-12 15:12:08 -0700154 GrpcPreconditions.CheckNotNull(histogramParams, "histogramParams");
Jan Tattermusche81adf32016-04-04 15:59:50 -0700155 this.channels = new List<Channel>(channels);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700156 this.clientType = clientType;
157 this.rpcType = rpcType;
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700158 this.payloadConfig = payloadConfig;
Jan Tattermuschd7079b22017-05-16 19:55:57 +0200159 this.cachedByteBufferRequest = new Lazy<byte[]>(() => new byte[payloadConfig.BytebufParams.ReqSize]);
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200160 this.threadLocalHistogram = new ThreadLocal<Histogram>(() => new Histogram(histogramParams.Resolution, histogramParams.MaxPossible), true);
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700161
Jan Tattermusche81adf32016-04-04 15:59:50 -0700162 this.runnerTasks = new List<Task>();
163 foreach (var channel in this.channels)
164 {
165 for (int i = 0; i < outstandingRpcsPerChannel; i++)
166 {
167 var timer = CreateTimer(loadParams, 1.0 / this.channels.Count / outstandingRpcsPerChannel);
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700168 var optionalProfiler = profilerFactory();
169 this.runnerTasks.Add(RunClientAsync(channel, timer, optionalProfiler));
Jan Tattermusche81adf32016-04-04 15:59:50 -0700170 }
171 }
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700172 }
173
174 public ClientStats GetStats(bool reset)
175 {
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200176 var histogramData = new HistogramData();
177 foreach (var hist in threadLocalHistogram.Values)
178 {
179 hist.GetSnapshot(histogramData, reset);
180 }
181
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700182 var secondsElapsed = wallClockStopwatch.GetElapsedSnapshot(reset).TotalSeconds;
183
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700184 if (reset)
185 {
186 statsResetCount.Increment();
187 }
188
Jan Tattermusch8251fdd2017-06-01 19:26:12 +0200189 GrpcEnvironment.Logger.Info("[ClientRunnerImpl.GetStats] GC collection counts: gen0 {0}, gen1 {1}, gen2 {2}, (histogram reset count:{3}, seconds since reset: {4})",
190 GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2), statsResetCount.Count, secondsElapsed);
Jan Tattermusch01ce2362017-04-25 20:53:51 +0200191
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700192 // TODO: populate user time and system time
193 return new ClientStats
194 {
195 Latencies = histogramData,
196 TimeElapsed = secondsElapsed,
197 TimeUser = 0,
198 TimeSystem = 0
199 };
200 }
201
202 public async Task StopAsync()
203 {
204 stoppedCts.Cancel();
Jan Tattermusche81adf32016-04-04 15:59:50 -0700205 foreach (var runnerTask in runnerTasks)
206 {
207 await runnerTask;
208 }
209 foreach (var channel in channels)
210 {
211 await channel.ShutdownAsync();
212 }
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700213 }
214
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700215 private void RunUnary(Channel channel, IInterarrivalTimer timer, BasicProfiler optionalProfiler)
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700216 {
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700217 if (optionalProfiler != null)
218 {
219 Profilers.SetForCurrentThread(optionalProfiler);
220 }
221
222 bool profilerReset = false;
223
Jan Tattermuschf41ebc32016-06-22 12:47:14 -0700224 var client = new BenchmarkService.BenchmarkServiceClient(channel);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700225 var request = CreateSimpleRequest();
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700226 var stopwatch = new Stopwatch();
227
228 while (!stoppedCts.Token.IsCancellationRequested)
229 {
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700230 // after the first stats reset, also reset the profiler.
231 if (optionalProfiler != null && !profilerReset && statsResetCount.Count > 0)
232 {
233 optionalProfiler.Reset();
234 profilerReset = true;
235 }
236
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700237 stopwatch.Restart();
238 client.UnaryCall(request);
239 stopwatch.Stop();
240
Jan Tattermusch18ce9d62015-11-18 15:41:10 -0800241 // spec requires data point in nanoseconds.
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200242 threadLocalHistogram.Value.AddObservation(stopwatch.Elapsed.TotalSeconds * SecondsToNanos);
Jan Tattermusche81adf32016-04-04 15:59:50 -0700243
244 timer.WaitForNext();
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700245 }
246 }
247
Jan Tattermusche81adf32016-04-04 15:59:50 -0700248 private async Task RunUnaryAsync(Channel channel, IInterarrivalTimer timer)
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700249 {
Jan Tattermuschf41ebc32016-06-22 12:47:14 -0700250 var client = new BenchmarkService.BenchmarkServiceClient(channel);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700251 var request = CreateSimpleRequest();
252 var stopwatch = new Stopwatch();
253
254 while (!stoppedCts.Token.IsCancellationRequested)
255 {
256 stopwatch.Restart();
257 await client.UnaryCallAsync(request);
258 stopwatch.Stop();
259
260 // spec requires data point in nanoseconds.
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200261 threadLocalHistogram.Value.AddObservation(stopwatch.Elapsed.TotalSeconds * SecondsToNanos);
Jan Tattermusche81adf32016-04-04 15:59:50 -0700262
263 await timer.WaitForNextAsync();
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700264 }
265 }
266
Jan Tattermusche81adf32016-04-04 15:59:50 -0700267 private async Task RunStreamingPingPongAsync(Channel channel, IInterarrivalTimer timer)
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700268 {
Jan Tattermuschf41ebc32016-06-22 12:47:14 -0700269 var client = new BenchmarkService.BenchmarkServiceClient(channel);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700270 var request = CreateSimpleRequest();
271 var stopwatch = new Stopwatch();
272
273 using (var call = client.StreamingCall())
274 {
275 while (!stoppedCts.Token.IsCancellationRequested)
276 {
277 stopwatch.Restart();
278 await call.RequestStream.WriteAsync(request);
279 await call.ResponseStream.MoveNext();
280 stopwatch.Stop();
281
282 // spec requires data point in nanoseconds.
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200283 threadLocalHistogram.Value.AddObservation(stopwatch.Elapsed.TotalSeconds * SecondsToNanos);
Jan Tattermusche81adf32016-04-04 15:59:50 -0700284
285 await timer.WaitForNextAsync();
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700286 }
287
288 // finish the streaming call
289 await call.RequestStream.CompleteAsync();
290 Assert.IsFalse(await call.ResponseStream.MoveNext());
291 }
292 }
293
Jan Tattermusche81adf32016-04-04 15:59:50 -0700294 private async Task RunGenericStreamingAsync(Channel channel, IInterarrivalTimer timer)
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700295 {
Jan Tattermuschd7079b22017-05-16 19:55:57 +0200296 var request = cachedByteBufferRequest.Value;
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700297 var stopwatch = new Stopwatch();
298
Jan Tattermusch253769e2016-03-21 16:25:59 -0700299 var callDetails = new CallInvocationDetails<byte[], byte[]>(channel, GenericService.StreamingCallMethod, new CallOptions());
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700300
301 using (var call = Calls.AsyncDuplexStreamingCall(callDetails))
302 {
303 while (!stoppedCts.Token.IsCancellationRequested)
304 {
305 stopwatch.Restart();
306 await call.RequestStream.WriteAsync(request);
307 await call.ResponseStream.MoveNext();
308 stopwatch.Stop();
309
310 // spec requires data point in nanoseconds.
Jan Tattermusch19e3d3b2017-05-17 11:05:06 +0200311 threadLocalHistogram.Value.AddObservation(stopwatch.Elapsed.TotalSeconds * SecondsToNanos);
Jan Tattermusche81adf32016-04-04 15:59:50 -0700312
313 await timer.WaitForNextAsync();
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700314 }
315
316 // finish the streaming call
317 await call.RequestStream.CompleteAsync();
318 Assert.IsFalse(await call.ResponseStream.MoveNext());
319 }
320 }
321
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700322 private Task RunClientAsync(Channel channel, IInterarrivalTimer timer, BasicProfiler optionalProfiler)
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700323 {
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700324 if (payloadConfig.PayloadCase == PayloadConfig.PayloadOneofCase.BytebufParams)
325 {
Jan Tattermusch87ba2942016-05-16 17:18:00 -0700326 GrpcPreconditions.CheckArgument(clientType == ClientType.AsyncClient, "Generic client only supports async API");
327 GrpcPreconditions.CheckArgument(rpcType == RpcType.Streaming, "Generic client only supports streaming calls");
Jan Tattermusche458d842016-05-09 14:21:52 -0700328 return RunGenericStreamingAsync(channel, timer);
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700329 }
330
331 GrpcPreconditions.CheckNotNull(payloadConfig.SimpleParams);
Jan Tattermusch87ba2942016-05-16 17:18:00 -0700332 if (clientType == ClientType.SyncClient)
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700333 {
Jan Tattermusch87ba2942016-05-16 17:18:00 -0700334 GrpcPreconditions.CheckArgument(rpcType == RpcType.Unary, "Sync client can only be used for Unary calls in C#");
Jan Tattermusche458d842016-05-09 14:21:52 -0700335 // create a dedicated thread for the synchronous client
Jan Tattermusch09c5e052016-05-18 22:24:27 -0700336 return Task.Factory.StartNew(() => RunUnary(channel, timer, optionalProfiler), TaskCreationOptions.LongRunning);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700337 }
Jan Tattermusch87ba2942016-05-16 17:18:00 -0700338 else if (clientType == ClientType.AsyncClient)
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700339 {
340 switch (rpcType)
341 {
Jan Tattermusch87ba2942016-05-16 17:18:00 -0700342 case RpcType.Unary:
Jan Tattermusche458d842016-05-09 14:21:52 -0700343 return RunUnaryAsync(channel, timer);
Jan Tattermusch87ba2942016-05-16 17:18:00 -0700344 case RpcType.Streaming:
Jan Tattermusche458d842016-05-09 14:21:52 -0700345 return RunStreamingPingPongAsync(channel, timer);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700346 }
347 }
348 throw new ArgumentException("Unsupported configuration.");
349 }
350
351 private SimpleRequest CreateSimpleRequest()
352 {
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700353 GrpcPreconditions.CheckNotNull(payloadConfig.SimpleParams);
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700354 return new SimpleRequest
355 {
Jan Tattermusche45ca5f2016-03-21 14:59:08 -0700356 Payload = CreateZerosPayload(payloadConfig.SimpleParams.ReqSize),
357 ResponseSize = payloadConfig.SimpleParams.RespSize
Jan Tattermusche26e2e52016-03-21 13:52:50 -0700358 };
359 }
360
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700361 private static Payload CreateZerosPayload(int size)
362 {
363 return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
364 }
Jan Tattermusche81adf32016-04-04 15:59:50 -0700365
366 private static IInterarrivalTimer CreateTimer(LoadParams loadParams, double loadMultiplier)
367 {
368 switch (loadParams.LoadCase)
369 {
370 case LoadParams.LoadOneofCase.ClosedLoop:
371 return new ClosedLoopInterarrivalTimer();
372 case LoadParams.LoadOneofCase.Poisson:
373 return new PoissonInterarrivalTimer(loadParams.Poisson.OfferedLoad * loadMultiplier);
374 default:
375 throw new ArgumentException("Unknown load type");
376 }
377 }
Jan Tattermuschd0c1bfa2015-10-22 19:14:57 -0700378 }
379}