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..42851467569627701b0a09fef87f5737dfba6346
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting.StressClient/Program.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Grpc.IntegrationTesting.StressClient
+{
+    class MainClass
+    {
+        public static void Main(string[] args)
+        {
+            StressTestClient.Run(args);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.IntegrationTesting.StressClient/Properties/AssemblyInfo.cs b/src/csharp/Grpc.IntegrationTesting.StressClient/Properties/AssemblyInfo.cs
new file mode 100644
index 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..176e005d02930f609abfbd79fc2d90bc64065ae0 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -113,6 +113,7 @@
     <Compile Include="GeneratedClientTest.cs" />
     <Compile Include="InterarrivalTimers.cs" />
     <Compile Include="NUnitMain.cs" />
+    <Compile Include="StressTestClient.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs b/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b12b28b9a38e4b9ba2cf03ec33a03257590797b4
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/StressTestClient.cs
@@ -0,0 +1,296 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using CommandLine;
+using CommandLine.Text;
+using Grpc.Core;
+using Grpc.Core.Logging;
+using Grpc.Core.Utils;
+using Grpc.Testing;
+
+namespace Grpc.IntegrationTesting
+{
+    public class StressTestClient
+    {
+        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<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 
+        {
+            readonly Histogram histogram;
+            readonly WallClockStopwatch wallClockStopwatch = new WallClockStopwatch();
+
+            public MetricsServiceImpl(Histogram histogram)
+            {
+                this.histogram = histogram;
+            }
+
+            public override async Task GetAllGauges(EmptyMessage request, IServerStreamWriter<GaugeResponse> responseStream, ServerCallContext context)
+            {
+                var snapshot = histogram.GetSnapshot(true);
+                var elapsedSnapshot = wallClockStopwatch.GetElapsedSnapshot(true);
+
+                double qps = snapshot.Count / elapsedSnapshot.Seconds;
+
+                var response = new GaugeResponse
+                {
+                    Name = "csharp_overall_qps",
+                    DoubleValue = qps
+                };
+                await responseStream.WriteAsync(response);
+            }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln
index 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