From 6169d5f7b002a68daa3eda36798cec34de11d57f Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Thu, 31 Mar 2016 07:46:18 -0700 Subject: Update copyrights --- src/csharp/generate_proto_csharp.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/csharp/generate_proto_csharp.sh') diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh index 23e0540253..9ac770b79d 100755 --- a/src/csharp/generate_proto_csharp.sh +++ b/src/csharp/generate_proto_csharp.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright 2015-2016, Google Inc. +# Copyright 2015, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without -- cgit v1.2.3 From 445a82bfae3a3b221d5c55183edfca7802c8476c Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Thu, 21 Apr 2016 13:18:26 -0700 Subject: Add C# stress test client --- .../.gitignore | 3 + .../Grpc.IntegrationTesting.StressClient.csproj | 60 +++++ .../Program.cs | 12 + .../Properties/AssemblyInfo.cs | 11 + .../Grpc.IntegrationTesting.csproj | 1 + .../Grpc.IntegrationTesting/StressTestClient.cs | 296 +++++++++++++++++++++ src/csharp/Grpc.sln | 8 + src/csharp/generate_proto_csharp.sh | 2 +- 8 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore create mode 100644 src/csharp/Grpc.IntegrationTesting.StressClient/Grpc.IntegrationTesting.StressClient.csproj create mode 100644 src/csharp/Grpc.IntegrationTesting.StressClient/Program.cs create mode 100644 src/csharp/Grpc.IntegrationTesting.StressClient/Properties/AssemblyInfo.cs create mode 100644 src/csharp/Grpc.IntegrationTesting/StressTestClient.cs (limited to 'src/csharp/generate_proto_csharp.sh') diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore b/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore new file mode 100644 index 0000000000..a382af2294 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore @@ -0,0 +1,3 @@ +bin +obj + diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/Grpc.IntegrationTesting.StressClient.csproj b/src/csharp/Grpc.IntegrationTesting.StressClient/Grpc.IntegrationTesting.StressClient.csproj new file mode 100644 index 0000000000..d6eba74289 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.StressClient/Grpc.IntegrationTesting.StressClient.csproj @@ -0,0 +1,60 @@ + + + + Debug + AnyCPU + {ADEBA147-80AE-4710-82E9-5B7F93690266} + Exe + Grpc.IntegrationTesting.StressClient + Grpc.IntegrationTesting.StressClient + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + AnyCPU + + + pdbonly + true + bin\Release + prompt + 4 + AnyCPU + + + pdbonly + true + bin\ReleaseSigned + prompt + 4 + True + ..\keys\Grpc.snk + + + + + + + Version.cs + + + + + + + + {CCC4440E-49F7-4790-B0AF-FEABB0837AE7} + Grpc.Core + + + {C61154BA-DD4A-4838-8420-0162A28925E0} + Grpc.IntegrationTesting + + + \ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/Program.cs b/src/csharp/Grpc.IntegrationTesting.StressClient/Program.cs new file mode 100644 index 0000000000..4285146756 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.StressClient/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace Grpc.IntegrationTesting.StressClient +{ + class MainClass + { + public static void Main(string[] args) + { + StressTestClient.Run(args); + } + } +} diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/Properties/AssemblyInfo.cs b/src/csharp/Grpc.IntegrationTesting.StressClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e845bbfb9e --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.StressClient/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("Grpc.IntegrationTesting.StressClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Google Inc. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj index c16d0e5c5d..176e005d02 100644 --- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj +++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj @@ -113,6 +113,7 @@ + diff --git a/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs b/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs new file mode 100644 index 0000000000..b12b28b9a3 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs @@ -0,0 +1,296 @@ +#region Copyright notice and license + +// Copyright 2015-2016, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using CommandLine; +using CommandLine.Text; +using Grpc.Core; +using Grpc.Core.Logging; +using Grpc.Core.Utils; +using Grpc.Testing; + +namespace Grpc.IntegrationTesting +{ + public class StressTestClient + { + static readonly ILogger Logger = GrpcEnvironment.Logger.ForType(); + const double SecondsToNanos = 1e9; + + private class ClientOptions + { + [Option("server_addresses", DefaultValue = "localhost:8080")] + public string ServerAddresses { get; set; } + + [Option("test_cases", DefaultValue = "large_unary:100")] + public string TestCases { get; set; } + + [Option("test_duration_secs", DefaultValue = -1)] + public int TestDurationSecs { get; set; } + + [Option("num_channels_per_server", DefaultValue = 1)] + public int NumChannelsPerServer { get; set; } + + [Option("num_stubs_per_channel", DefaultValue = 1)] + public int NumStubsPerChannel { get; set; } + + [Option("metrics_port", DefaultValue = 8081)] + public int MetricsPort { get; set; } + + [HelpOption] + public string GetUsage() + { + var help = new HelpText + { + Heading = "gRPC C# stress test client", + AddDashesToOption = true + }; + help.AddPreOptionsLine("Usage:"); + help.AddOptions(this); + return help; + } + } + + ClientOptions options; + List serverAddresses; + Dictionary weightedTestCases; + WeightedRandomGenerator testCaseGenerator; + + // cancellation will be emitted once test_duration_secs has elapsed. + CancellationTokenSource finishedTokenSource = new CancellationTokenSource(); + Histogram histogram = new Histogram(0.01, 60 * SecondsToNanos); + + private StressTestClient(ClientOptions options, List serverAddresses, Dictionary weightedTestCases) + { + this.options = options; + this.serverAddresses = serverAddresses; + this.weightedTestCases = weightedTestCases; + this.testCaseGenerator = new WeightedRandomGenerator(this.weightedTestCases); + } + + public static void Run(string[] args) + { + var options = new ClientOptions(); + if (!Parser.Default.ParseArguments(args, options)) + { + Environment.Exit(1); + } + + GrpcPreconditions.CheckArgument(options.NumChannelsPerServer > 0); + GrpcPreconditions.CheckArgument(options.NumStubsPerChannel > 0); + + var serverAddresses = options.ServerAddresses.Split(','); + GrpcPreconditions.CheckArgument(serverAddresses.Length > 0, "You need to provide at least one server address"); + + var testCases = ParseWeightedTestCases(options.TestCases); + GrpcPreconditions.CheckArgument(testCases.Count > 0, "You need to provide at least one test case"); + + var interopClient = new StressTestClient(options, serverAddresses.ToList(), testCases); + interopClient.Run().Wait(); + } + + async Task Run() + { + var metricsServer = new Server() + { + Services = { MetricsService.BindService(new MetricsServiceImpl(histogram)) }, + Ports = { { "[::]", options.MetricsPort, ServerCredentials.Insecure } } + }; + metricsServer.Start(); + + if (options.TestDurationSecs >= 0) + { + finishedTokenSource.CancelAfter(TimeSpan.FromSeconds(options.TestDurationSecs)); + } + + var tasks = new List(); + var channels = new List(); + foreach (var serverAddress in serverAddresses) + { + for (int i = 0; i < options.NumChannelsPerServer; i++) + { + var channel = new Channel(serverAddress, ChannelCredentials.Insecure); + channels.Add(channel); + for (int j = 0; j < options.NumStubsPerChannel; j++) + { + var client = TestService.NewClient(channel); + var task = Task.Factory.StartNew(() => RunBodyAsync(client).GetAwaiter().GetResult(), + TaskCreationOptions.LongRunning); + tasks.Add(task); + } + } + } + await Task.WhenAll(tasks); + + foreach (var channel in channels) + { + await channel.ShutdownAsync(); + } + + await metricsServer.ShutdownAsync(); + } + + async Task RunBodyAsync(TestService.TestServiceClient client) + { + Logger.Info("Starting stress test client thread."); + while (!finishedTokenSource.Token.IsCancellationRequested) + { + var testCase = testCaseGenerator.GetNext(); + + var stopwatch = Stopwatch.StartNew(); + + await RunTestCaseAsync(client, testCase); + + stopwatch.Stop(); + histogram.AddObservation(stopwatch.Elapsed.TotalSeconds * SecondsToNanos); + } + Logger.Info("Stress test client thread finished."); + } + + async Task RunTestCaseAsync(TestService.TestServiceClient client, string testCase) + { + switch (testCase) + { + case "empty_unary": + InteropClient.RunEmptyUnary(client); + break; + case "large_unary": + InteropClient.RunLargeUnary(client); + break; + case "client_streaming": + await InteropClient.RunClientStreamingAsync(client); + break; + case "server_streaming": + await InteropClient.RunServerStreamingAsync(client); + break; + case "ping_pong": + await InteropClient.RunPingPongAsync(client); + break; + case "empty_stream": + await InteropClient.RunEmptyStreamAsync(client); + break; + case "cancel_after_begin": + await InteropClient.RunCancelAfterBeginAsync(client); + break; + case "cancel_after_first_response": + await InteropClient.RunCancelAfterFirstResponseAsync(client); + break; + case "timeout_on_sleeping_server": + await InteropClient.RunTimeoutOnSleepingServerAsync(client); + break; + case "custom_metadata": + await InteropClient.RunCustomMetadataAsync(client); + break; + case "status_code_and_message": + await InteropClient.RunStatusCodeAndMessageAsync(client); + break; + default: + throw new ArgumentException("Unsupported test case " + testCase); + } + } + + static Dictionary ParseWeightedTestCases(string weightedTestCases) + { + var result = new Dictionary(); + foreach (var weightedTestCase in weightedTestCases.Split(',')) + { + var parts = weightedTestCase.Split(new char[] {':'}, 2); + GrpcPreconditions.CheckArgument(parts.Length == 2, "Malformed test_cases option."); + result.Add(parts[0], int.Parse(parts[1])); + } + return result; + } + + class WeightedRandomGenerator + { + readonly Random random = new Random(); + readonly List> cumulativeSums; + readonly int weightSum; + + public WeightedRandomGenerator(Dictionary weightedItems) + { + cumulativeSums = new List>(); + weightSum = 0; + foreach (var entry in weightedItems) + { + weightSum += entry.Value; + cumulativeSums.Add(Tuple.Create(weightSum, entry.Key)); + } + } + + public string GetNext() + { + int rand = random.Next(weightSum); + foreach (var entry in cumulativeSums) + { + if (rand < entry.Item1) + { + return entry.Item2; + } + } + throw new InvalidOperationException("GetNext() failed."); + } + } + + class MetricsServiceImpl : MetricsService.MetricsServiceBase + { + readonly Histogram histogram; + readonly WallClockStopwatch wallClockStopwatch = new WallClockStopwatch(); + + public MetricsServiceImpl(Histogram histogram) + { + this.histogram = histogram; + } + + public override async Task GetAllGauges(EmptyMessage request, IServerStreamWriter responseStream, ServerCallContext context) + { + var snapshot = histogram.GetSnapshot(true); + var elapsedSnapshot = wallClockStopwatch.GetElapsedSnapshot(true); + + double qps = snapshot.Count / elapsedSnapshot.Seconds; + + var response = new GaugeResponse + { + Name = "csharp_overall_qps", + DoubleValue = qps + }; + await responseStream.WriteAsync(response); + } + } + } +} diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln index 8ff35e8c0d..9be36c0caa 100644 --- a/src/csharp/Grpc.sln +++ b/src/csharp/Grpc.sln @@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc.HealthCheck.Tests", "G EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc.IntegrationTesting.QpsWorker", "Grpc.IntegrationTesting.QpsWorker\Grpc.IntegrationTesting.QpsWorker.csproj", "{B82B7DFE-7F7B-40EF-B3D6-064FF2B01294}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc.IntegrationTesting.StressClient", "Grpc.IntegrationTesting.StressClient\Grpc.IntegrationTesting.StressClient.csproj", "{ADEBA147-80AE-4710-82E9-5B7F93690266}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,12 @@ Global {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|Any CPU.Build.0 = Release|Any CPU {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU + {ADEBA147-80AE-4710-82E9-5B7F93690266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADEBA147-80AE-4710-82E9-5B7F93690266}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADEBA147-80AE-4710-82E9-5B7F93690266}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADEBA147-80AE-4710-82E9-5B7F93690266}.Release|Any CPU.Build.0 = Release|Any CPU + {ADEBA147-80AE-4710-82E9-5B7F93690266}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU + {ADEBA147-80AE-4710-82E9-5B7F93690266}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh index 9ac770b79d..79488e02a5 100755 --- a/src/csharp/generate_proto_csharp.sh +++ b/src/csharp/generate_proto_csharp.sh @@ -45,4 +45,4 @@ $PROTOC --plugin=$PLUGIN --csharp_out=$HEALTHCHECK_DIR --grpc_out=$HEALTHCHECK_D -I src/proto/grpc/health/v1 src/proto/grpc/health/v1/health.proto $PROTOC --plugin=$PLUGIN --csharp_out=$TESTING_DIR --grpc_out=$TESTING_DIR \ - -I . src/proto/grpc/testing/{control,empty,messages,payloads,services,stats,test}.proto + -I . src/proto/grpc/testing/{control,empty,messages,metrics,payloads,services,stats,test}.proto -- cgit v1.2.3