diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore b/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore
new file mode 100644
index 00000000000..a382af2294f
--- /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 00000000000..d6eba74289e
--- /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 00000000000..dffdf22fa5b
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting.StressClient/Program.cs
@@ -0,0 +1,45 @@
+#region Copyright notice and license
+
+// Copyright 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;
+
+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 00000000000..e845bbfb9e6
--- /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 c16d0e5c5d0..9685cf18377 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -113,6 +113,9 @@
+
+
+
diff --git a/src/csharp/Grpc.IntegrationTesting/Metrics.cs b/src/csharp/Grpc.IntegrationTesting/Metrics.cs
new file mode 100644
index 00000000000..3163949d32b
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/Metrics.cs
@@ -0,0 +1,452 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: src/proto/grpc/testing/metrics.proto
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+namespace Grpc.Testing {
+
+ /// Holder for reflection information generated from src/proto/grpc/testing/metrics.proto
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public static partial class MetricsReflection {
+
+ #region Descriptor
+ /// File descriptor for src/proto/grpc/testing/metrics.proto
+ public static pbr::FileDescriptor Descriptor {
+ get { return descriptor; }
+ }
+ private static pbr::FileDescriptor descriptor;
+
+ static MetricsReflection() {
+ byte[] descriptorData = global::System.Convert.FromBase64String(
+ string.Concat(
+ "CiRzcmMvcHJvdG8vZ3JwYy90ZXN0aW5nL21ldHJpY3MucHJvdG8SDGdycGMu",
+ "dGVzdGluZyJsCg1HYXVnZVJlc3BvbnNlEgwKBG5hbWUYASABKAkSFAoKbG9u",
+ "Z192YWx1ZRgCIAEoA0gAEhYKDGRvdWJsZV92YWx1ZRgDIAEoAUgAEhYKDHN0",
+ "cmluZ192YWx1ZRgEIAEoCUgAQgcKBXZhbHVlIhwKDEdhdWdlUmVxdWVzdBIM",
+ "CgRuYW1lGAEgASgJIg4KDEVtcHR5TWVzc2FnZTKgAQoOTWV0cmljc1NlcnZp",
+ "Y2USSQoMR2V0QWxsR2F1Z2VzEhouZ3JwYy50ZXN0aW5nLkVtcHR5TWVzc2Fn",
+ "ZRobLmdycGMudGVzdGluZy5HYXVnZVJlc3BvbnNlMAESQwoIR2V0R2F1Z2US",
+ "Gi5ncnBjLnRlc3RpbmcuR2F1Z2VSZXF1ZXN0GhsuZ3JwYy50ZXN0aW5nLkdh",
+ "dWdlUmVzcG9uc2ViBnByb3RvMw=="));
+ descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+ new pbr::FileDescriptor[] { },
+ new pbr::GeneratedCodeInfo(null, new pbr::GeneratedCodeInfo[] {
+ new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.GaugeResponse), global::Grpc.Testing.GaugeResponse.Parser, new[]{ "Name", "LongValue", "DoubleValue", "StringValue" }, new[]{ "Value" }, null, null),
+ new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.GaugeRequest), global::Grpc.Testing.GaugeRequest.Parser, new[]{ "Name" }, null, null, null),
+ new pbr::GeneratedCodeInfo(typeof(global::Grpc.Testing.EmptyMessage), global::Grpc.Testing.EmptyMessage.Parser, null, null, null, null)
+ }));
+ }
+ #endregion
+
+ }
+ #region Messages
+ ///
+ /// Reponse message containing the gauge name and value
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public sealed partial class GaugeResponse : pb::IMessage {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GaugeResponse());
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Grpc.Testing.MetricsReflection.Descriptor.MessageTypes[0]; }
+ }
+
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ public GaugeResponse() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ public GaugeResponse(GaugeResponse other) : this() {
+ name_ = other.name_;
+ switch (other.ValueCase) {
+ case ValueOneofCase.LongValue:
+ LongValue = other.LongValue;
+ break;
+ case ValueOneofCase.DoubleValue:
+ DoubleValue = other.DoubleValue;
+ break;
+ case ValueOneofCase.StringValue:
+ StringValue = other.StringValue;
+ break;
+ }
+
+ }
+
+ public GaugeResponse Clone() {
+ return new GaugeResponse(this);
+ }
+
+ /// Field number for the "name" field.
+ public const int NameFieldNumber = 1;
+ private string name_ = "";
+ public string Name {
+ get { return name_; }
+ set {
+ name_ = pb::Preconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "long_value" field.
+ public const int LongValueFieldNumber = 2;
+ public long LongValue {
+ get { return valueCase_ == ValueOneofCase.LongValue ? (long) value_ : 0L; }
+ set {
+ value_ = value;
+ valueCase_ = ValueOneofCase.LongValue;
+ }
+ }
+
+ /// Field number for the "double_value" field.
+ public const int DoubleValueFieldNumber = 3;
+ public double DoubleValue {
+ get { return valueCase_ == ValueOneofCase.DoubleValue ? (double) value_ : 0D; }
+ set {
+ value_ = value;
+ valueCase_ = ValueOneofCase.DoubleValue;
+ }
+ }
+
+ /// Field number for the "string_value" field.
+ public const int StringValueFieldNumber = 4;
+ public string StringValue {
+ get { return valueCase_ == ValueOneofCase.StringValue ? (string) value_ : ""; }
+ set {
+ value_ = pb::Preconditions.CheckNotNull(value, "value");
+ valueCase_ = ValueOneofCase.StringValue;
+ }
+ }
+
+ private object value_;
+ /// Enum of possible cases for the "value" oneof.
+ public enum ValueOneofCase {
+ None = 0,
+ LongValue = 2,
+ DoubleValue = 3,
+ StringValue = 4,
+ }
+ private ValueOneofCase valueCase_ = ValueOneofCase.None;
+ public ValueOneofCase ValueCase {
+ get { return valueCase_; }
+ }
+
+ public void ClearValue() {
+ valueCase_ = ValueOneofCase.None;
+ value_ = null;
+ }
+
+ public override bool Equals(object other) {
+ return Equals(other as GaugeResponse);
+ }
+
+ public bool Equals(GaugeResponse other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Name != other.Name) return false;
+ if (LongValue != other.LongValue) return false;
+ if (DoubleValue != other.DoubleValue) return false;
+ if (StringValue != other.StringValue) return false;
+ if (ValueCase != other.ValueCase) return false;
+ return true;
+ }
+
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Name.Length != 0) hash ^= Name.GetHashCode();
+ if (valueCase_ == ValueOneofCase.LongValue) hash ^= LongValue.GetHashCode();
+ if (valueCase_ == ValueOneofCase.DoubleValue) hash ^= DoubleValue.GetHashCode();
+ if (valueCase_ == ValueOneofCase.StringValue) hash ^= StringValue.GetHashCode();
+ hash ^= (int) valueCase_;
+ return hash;
+ }
+
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ public void WriteTo(pb::CodedOutputStream output) {
+ if (Name.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Name);
+ }
+ if (valueCase_ == ValueOneofCase.LongValue) {
+ output.WriteRawTag(16);
+ output.WriteInt64(LongValue);
+ }
+ if (valueCase_ == ValueOneofCase.DoubleValue) {
+ output.WriteRawTag(25);
+ output.WriteDouble(DoubleValue);
+ }
+ if (valueCase_ == ValueOneofCase.StringValue) {
+ output.WriteRawTag(34);
+ output.WriteString(StringValue);
+ }
+ }
+
+ public int CalculateSize() {
+ int size = 0;
+ if (Name.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
+ }
+ if (valueCase_ == ValueOneofCase.LongValue) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(LongValue);
+ }
+ if (valueCase_ == ValueOneofCase.DoubleValue) {
+ size += 1 + 8;
+ }
+ if (valueCase_ == ValueOneofCase.StringValue) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(StringValue);
+ }
+ return size;
+ }
+
+ public void MergeFrom(GaugeResponse other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Name.Length != 0) {
+ Name = other.Name;
+ }
+ switch (other.ValueCase) {
+ case ValueOneofCase.LongValue:
+ LongValue = other.LongValue;
+ break;
+ case ValueOneofCase.DoubleValue:
+ DoubleValue = other.DoubleValue;
+ break;
+ case ValueOneofCase.StringValue:
+ StringValue = other.StringValue;
+ break;
+ }
+
+ }
+
+ public void MergeFrom(pb::CodedInputStream input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ input.SkipLastField();
+ break;
+ case 10: {
+ Name = input.ReadString();
+ break;
+ }
+ case 16: {
+ LongValue = input.ReadInt64();
+ break;
+ }
+ case 25: {
+ DoubleValue = input.ReadDouble();
+ break;
+ }
+ case 34: {
+ StringValue = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+
+ }
+
+ ///
+ /// Request message containing the gauge name
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public sealed partial class GaugeRequest : pb::IMessage {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GaugeRequest());
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Grpc.Testing.MetricsReflection.Descriptor.MessageTypes[1]; }
+ }
+
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ public GaugeRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ public GaugeRequest(GaugeRequest other) : this() {
+ name_ = other.name_;
+ }
+
+ public GaugeRequest Clone() {
+ return new GaugeRequest(this);
+ }
+
+ /// Field number for the "name" field.
+ public const int NameFieldNumber = 1;
+ private string name_ = "";
+ public string Name {
+ get { return name_; }
+ set {
+ name_ = pb::Preconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ public override bool Equals(object other) {
+ return Equals(other as GaugeRequest);
+ }
+
+ public bool Equals(GaugeRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Name != other.Name) return false;
+ return true;
+ }
+
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Name.Length != 0) hash ^= Name.GetHashCode();
+ return hash;
+ }
+
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ public void WriteTo(pb::CodedOutputStream output) {
+ if (Name.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Name);
+ }
+ }
+
+ public int CalculateSize() {
+ int size = 0;
+ if (Name.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
+ }
+ return size;
+ }
+
+ public void MergeFrom(GaugeRequest other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Name.Length != 0) {
+ Name = other.Name;
+ }
+ }
+
+ public void MergeFrom(pb::CodedInputStream input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ input.SkipLastField();
+ break;
+ case 10: {
+ Name = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public sealed partial class EmptyMessage : pb::IMessage {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EmptyMessage());
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Grpc.Testing.MetricsReflection.Descriptor.MessageTypes[2]; }
+ }
+
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ public EmptyMessage() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ public EmptyMessage(EmptyMessage other) : this() {
+ }
+
+ public EmptyMessage Clone() {
+ return new EmptyMessage(this);
+ }
+
+ public override bool Equals(object other) {
+ return Equals(other as EmptyMessage);
+ }
+
+ public bool Equals(EmptyMessage other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ return true;
+ }
+
+ public override int GetHashCode() {
+ int hash = 1;
+ return hash;
+ }
+
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ public void WriteTo(pb::CodedOutputStream output) {
+ }
+
+ public int CalculateSize() {
+ int size = 0;
+ return size;
+ }
+
+ public void MergeFrom(EmptyMessage other) {
+ if (other == null) {
+ return;
+ }
+ }
+
+ public void MergeFrom(pb::CodedInputStream input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ switch(tag) {
+ default:
+ input.SkipLastField();
+ break;
+ }
+ }
+ }
+
+ }
+
+ #endregion
+
+}
+
+#endregion Designer generated code
diff --git a/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs b/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs
new file mode 100644
index 00000000000..cc01ae91a14
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/MetricsGrpc.cs
@@ -0,0 +1,146 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: src/proto/grpc/testing/metrics.proto
+#region Designer generated code
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+
+namespace Grpc.Testing {
+ public static class MetricsService
+ {
+ static readonly string __ServiceName = "grpc.testing.MetricsService";
+
+ static readonly Marshaller __Marshaller_EmptyMessage = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.EmptyMessage.Parser.ParseFrom);
+ static readonly Marshaller __Marshaller_GaugeResponse = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.GaugeResponse.Parser.ParseFrom);
+ static readonly Marshaller __Marshaller_GaugeRequest = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.GaugeRequest.Parser.ParseFrom);
+
+ static readonly Method __Method_GetAllGauges = new Method(
+ MethodType.ServerStreaming,
+ __ServiceName,
+ "GetAllGauges",
+ __Marshaller_EmptyMessage,
+ __Marshaller_GaugeResponse);
+
+ static readonly Method __Method_GetGauge = new Method(
+ MethodType.Unary,
+ __ServiceName,
+ "GetGauge",
+ __Marshaller_GaugeRequest,
+ __Marshaller_GaugeResponse);
+
+ // service descriptor
+ public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
+ {
+ get { return global::Grpc.Testing.MetricsReflection.Descriptor.Services[0]; }
+ }
+
+ // client interface
+ [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
+ public interface IMetricsServiceClient
+ {
+ AsyncServerStreamingCall GetAllGauges(global::Grpc.Testing.EmptyMessage request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+ AsyncServerStreamingCall GetAllGauges(global::Grpc.Testing.EmptyMessage request, CallOptions options);
+ global::Grpc.Testing.GaugeResponse GetGauge(global::Grpc.Testing.GaugeRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+ global::Grpc.Testing.GaugeResponse GetGauge(global::Grpc.Testing.GaugeRequest request, CallOptions options);
+ AsyncUnaryCall GetGaugeAsync(global::Grpc.Testing.GaugeRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+ AsyncUnaryCall GetGaugeAsync(global::Grpc.Testing.GaugeRequest request, CallOptions options);
+ }
+
+ // server-side interface
+ [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
+ public interface IMetricsService
+ {
+ Task GetAllGauges(global::Grpc.Testing.EmptyMessage request, IServerStreamWriter responseStream, ServerCallContext context);
+ Task GetGauge(global::Grpc.Testing.GaugeRequest request, ServerCallContext context);
+ }
+
+ // server-side abstract class
+ public abstract class MetricsServiceBase
+ {
+ public virtual Task GetAllGauges(global::Grpc.Testing.EmptyMessage request, IServerStreamWriter responseStream, ServerCallContext context)
+ {
+ throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+ }
+
+ public virtual Task GetGauge(global::Grpc.Testing.GaugeRequest request, ServerCallContext context)
+ {
+ throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+ }
+
+ }
+
+ // client stub
+ public class MetricsServiceClient : ClientBase, IMetricsServiceClient
+ {
+ public MetricsServiceClient(Channel channel) : base(channel)
+ {
+ }
+ public MetricsServiceClient(CallInvoker callInvoker) : base(callInvoker)
+ {
+ }
+ ///Protected parameterless constructor to allow creation of test doubles.
+ protected MetricsServiceClient() : base()
+ {
+ }
+ ///Protected constructor to allow creation of configured clients.
+ protected MetricsServiceClient(ClientBaseConfiguration configuration) : base(configuration)
+ {
+ }
+
+ public virtual AsyncServerStreamingCall GetAllGauges(global::Grpc.Testing.EmptyMessage request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return GetAllGauges(request, new CallOptions(headers, deadline, cancellationToken));
+ }
+ public virtual AsyncServerStreamingCall GetAllGauges(global::Grpc.Testing.EmptyMessage request, CallOptions options)
+ {
+ return CallInvoker.AsyncServerStreamingCall(__Method_GetAllGauges, null, options, request);
+ }
+ public virtual global::Grpc.Testing.GaugeResponse GetGauge(global::Grpc.Testing.GaugeRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return GetGauge(request, new CallOptions(headers, deadline, cancellationToken));
+ }
+ public virtual global::Grpc.Testing.GaugeResponse GetGauge(global::Grpc.Testing.GaugeRequest request, CallOptions options)
+ {
+ return CallInvoker.BlockingUnaryCall(__Method_GetGauge, null, options, request);
+ }
+ public virtual AsyncUnaryCall GetGaugeAsync(global::Grpc.Testing.GaugeRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return GetGaugeAsync(request, new CallOptions(headers, deadline, cancellationToken));
+ }
+ public virtual AsyncUnaryCall GetGaugeAsync(global::Grpc.Testing.GaugeRequest request, CallOptions options)
+ {
+ return CallInvoker.AsyncUnaryCall(__Method_GetGauge, null, options, request);
+ }
+ protected override MetricsServiceClient NewInstance(ClientBaseConfiguration configuration)
+ {
+ return new MetricsServiceClient(configuration);
+ }
+ }
+
+ // creates service definition that can be registered with a server
+ public static ServerServiceDefinition BindService(IMetricsService serviceImpl)
+ {
+ return ServerServiceDefinition.CreateBuilder(__ServiceName)
+ .AddMethod(__Method_GetAllGauges, serviceImpl.GetAllGauges)
+ .AddMethod(__Method_GetGauge, serviceImpl.GetGauge).Build();
+ }
+
+ // creates service definition that can be registered with a server
+ public static ServerServiceDefinition BindService(MetricsServiceBase serviceImpl)
+ {
+ return ServerServiceDefinition.CreateBuilder(__ServiceName)
+ .AddMethod(__Method_GetAllGauges, serviceImpl.GetAllGauges)
+ .AddMethod(__Method_GetGauge, serviceImpl.GetGauge).Build();
+ }
+
+ // creates a new client
+ public static MetricsServiceClient NewClient(Channel channel)
+ {
+ return new MetricsServiceClient(channel);
+ }
+
+ }
+}
+#endregion
diff --git a/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs b/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs
new file mode 100644
index 00000000000..8db691cb048
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs
@@ -0,0 +1,318 @@
+#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
+ {
+ const string GaugeName = "csharp_overall_qps";
+
+ readonly Histogram histogram;
+ readonly WallClockStopwatch wallClockStopwatch = new WallClockStopwatch();
+
+ public MetricsServiceImpl(Histogram histogram)
+ {
+ this.histogram = histogram;
+ }
+
+ public override Task GetGauge(GaugeRequest request, ServerCallContext context)
+ {
+ if (request.Name == GaugeName)
+ {
+ long qps = GetQpsAndReset();
+
+ return Task.FromResult(new GaugeResponse
+ {
+ Name = GaugeName,
+ LongValue = qps
+ });
+ }
+ throw new RpcException(new Status(StatusCode.InvalidArgument, "Gauge does not exist"));
+ }
+
+ public override async Task GetAllGauges(EmptyMessage request, IServerStreamWriter responseStream, ServerCallContext context)
+ {
+ long qps = GetQpsAndReset();
+
+ var response = new GaugeResponse
+ {
+ Name = GaugeName,
+ LongValue = qps
+ };
+ await responseStream.WriteAsync(response);
+ }
+
+ long GetQpsAndReset()
+ {
+ var snapshot = histogram.GetSnapshot(true);
+ var elapsedSnapshot = wallClockStopwatch.GetElapsedSnapshot(true);
+
+ return (long) (snapshot.Count / elapsedSnapshot.Seconds);
+ }
+ }
+ }
+}
diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln
index 8ff35e8c0d0..9be36c0caa4 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 9ac770b79d7..79488e02a53 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