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