diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore b/src/csharp/Grpc.IntegrationTesting.StressClient/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a382af2294f7a8b9d7789117aeeb80a0a56bc19b
--- /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 0000000000000000000000000000000000000000..d6eba74289ef70ae1b14354c134fedce21d1af96
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting.StressClient/Grpc.IntegrationTesting.StressClient.csproj
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{ADEBA147-80AE-4710-82E9-5B7F93690266}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Grpc.IntegrationTesting.StressClient</RootNamespace>
+    <AssemblyName>Grpc.IntegrationTesting.StressClient</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\ReleaseSigned</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <SignAssembly>True</SignAssembly>
+    <AssemblyOriginatorKeyFile>..\keys\Grpc.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="..\Grpc.Core\Version.cs">
+      <Link>Version.cs</Link>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\Grpc.Core\Grpc.Core.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>Grpc.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Grpc.IntegrationTesting\Grpc.IntegrationTesting.csproj">
+      <Project>{C61154BA-DD4A-4838-8420-0162A28925E0}</Project>
+      <Name>Grpc.IntegrationTesting</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ 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 0000000000000000000000000000000000000000..dffdf22fa5bf82751d037b8b32bb0023f4a517cd
--- /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 0000000000000000000000000000000000000000..e845bbfb9e66fe4e044e9d48407be453cf419d6a
--- /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 c16d0e5c5d05b5dc1a832dec5a1cfe99d44d0865..9685cf18377234eb2aa2417259ef50e20be852e8 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -113,6 +113,9 @@
     <Compile Include="GeneratedClientTest.cs" />
     <Compile Include="InterarrivalTimers.cs" />
     <Compile Include="NUnitMain.cs" />
+    <Compile Include="StressTestClient.cs" />
+    <Compile Include="Metrics.cs" />
+    <Compile Include="MetricsGrpc.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.IntegrationTesting/Metrics.cs b/src/csharp/Grpc.IntegrationTesting/Metrics.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3163949d32b99c87dc4c13acc1e3858aa0188d83
--- /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 {
+
+  /// <summary>Holder for reflection information generated from src/proto/grpc/testing/metrics.proto</summary>
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public static partial class MetricsReflection {
+
+    #region Descriptor
+    /// <summary>File descriptor for src/proto/grpc/testing/metrics.proto</summary>
+    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
+  /// <summary>
+  ///  Reponse message containing the gauge name and value
+  /// </summary>
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class GaugeResponse : pb::IMessage<GaugeResponse> {
+    private static readonly pb::MessageParser<GaugeResponse> _parser = new pb::MessageParser<GaugeResponse>(() => new GaugeResponse());
+    public static pb::MessageParser<GaugeResponse> 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);
+    }
+
+    /// <summary>Field number for the "name" field.</summary>
+    public const int NameFieldNumber = 1;
+    private string name_ = "";
+    public string Name {
+      get { return name_; }
+      set {
+        name_ = pb::Preconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "long_value" field.</summary>
+    public const int LongValueFieldNumber = 2;
+    public long LongValue {
+      get { return valueCase_ == ValueOneofCase.LongValue ? (long) value_ : 0L; }
+      set {
+        value_ = value;
+        valueCase_ = ValueOneofCase.LongValue;
+      }
+    }
+
+    /// <summary>Field number for the "double_value" field.</summary>
+    public const int DoubleValueFieldNumber = 3;
+    public double DoubleValue {
+      get { return valueCase_ == ValueOneofCase.DoubleValue ? (double) value_ : 0D; }
+      set {
+        value_ = value;
+        valueCase_ = ValueOneofCase.DoubleValue;
+      }
+    }
+
+    /// <summary>Field number for the "string_value" field.</summary>
+    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_;
+    /// <summary>Enum of possible cases for the "value" oneof.</summary>
+    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;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  ///  Request message containing the gauge name
+  /// </summary>
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class GaugeRequest : pb::IMessage<GaugeRequest> {
+    private static readonly pb::MessageParser<GaugeRequest> _parser = new pb::MessageParser<GaugeRequest>(() => new GaugeRequest());
+    public static pb::MessageParser<GaugeRequest> 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);
+    }
+
+    /// <summary>Field number for the "name" field.</summary>
+    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<EmptyMessage> {
+    private static readonly pb::MessageParser<EmptyMessage> _parser = new pb::MessageParser<EmptyMessage>(() => new EmptyMessage());
+    public static pb::MessageParser<EmptyMessage> 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 0000000000000000000000000000000000000000..cc01ae91a145e3862b9d8025c26ad1d01d0dc65f
--- /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<global::Grpc.Testing.EmptyMessage> __Marshaller_EmptyMessage = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.EmptyMessage.Parser.ParseFrom);
+    static readonly Marshaller<global::Grpc.Testing.GaugeResponse> __Marshaller_GaugeResponse = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.GaugeResponse.Parser.ParseFrom);
+    static readonly Marshaller<global::Grpc.Testing.GaugeRequest> __Marshaller_GaugeRequest = Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Grpc.Testing.GaugeRequest.Parser.ParseFrom);
+
+    static readonly Method<global::Grpc.Testing.EmptyMessage, global::Grpc.Testing.GaugeResponse> __Method_GetAllGauges = new Method<global::Grpc.Testing.EmptyMessage, global::Grpc.Testing.GaugeResponse>(
+        MethodType.ServerStreaming,
+        __ServiceName,
+        "GetAllGauges",
+        __Marshaller_EmptyMessage,
+        __Marshaller_GaugeResponse);
+
+    static readonly Method<global::Grpc.Testing.GaugeRequest, global::Grpc.Testing.GaugeResponse> __Method_GetGauge = new Method<global::Grpc.Testing.GaugeRequest, global::Grpc.Testing.GaugeResponse>(
+        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<global::Grpc.Testing.GaugeResponse> GetAllGauges(global::Grpc.Testing.EmptyMessage request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncServerStreamingCall<global::Grpc.Testing.GaugeResponse> 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<global::Grpc.Testing.GaugeResponse> GetGaugeAsync(global::Grpc.Testing.GaugeRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncUnaryCall<global::Grpc.Testing.GaugeResponse> 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<global::Grpc.Testing.GaugeResponse> responseStream, ServerCallContext context);
+      Task<global::Grpc.Testing.GaugeResponse> 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<global::Grpc.Testing.GaugeResponse> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Grpc.Testing.GaugeResponse> GetGauge(global::Grpc.Testing.GaugeRequest request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
+    // client stub
+    public class MetricsServiceClient : ClientBase<MetricsServiceClient>, IMetricsServiceClient
+    {
+      public MetricsServiceClient(Channel channel) : base(channel)
+      {
+      }
+      public MetricsServiceClient(CallInvoker callInvoker) : base(callInvoker)
+      {
+      }
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected MetricsServiceClient() : base()
+      {
+      }
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected MetricsServiceClient(ClientBaseConfiguration configuration) : base(configuration)
+      {
+      }
+
+      public virtual AsyncServerStreamingCall<global::Grpc.Testing.GaugeResponse> 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<global::Grpc.Testing.GaugeResponse> 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<global::Grpc.Testing.GaugeResponse> 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<global::Grpc.Testing.GaugeResponse> 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 0000000000000000000000000000000000000000..8db691cb048b1767542d18318c0705af4e414c25
--- /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<StressTestClient>();
+        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<string> serverAddresses;
+        Dictionary<string, int> 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<string> serverAddresses, Dictionary<string, int> 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<Task>();
+            var channels = new List<Channel>();
+            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<string, int> ParseWeightedTestCases(string weightedTestCases)
+        {
+            var result = new Dictionary<string, int>();
+            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<Tuple<int, string>> cumulativeSums;
+            readonly int weightSum;
+
+            public WeightedRandomGenerator(Dictionary<string, int> weightedItems)
+            {
+                cumulativeSums = new List<Tuple<int, string>>();
+                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<GaugeResponse> 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<GaugeResponse> 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 8ff35e8c0d0193f35ad390314f467e16d8cec859..9be36c0caa44aeb999b57ce39aa18ee89b60f82a 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 9ac770b79d774e491143127e9fdbb384106aee31..79488e02a53b4f19ebbebd0f54cb906fe220ea15 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