Merge pull request #4859 from ctiller/sceq

Subchannel Sharing  [The interop tests failures and Basic test failures are unrelated to this change. Merging]
diff --git a/grpc.def b/grpc.def
index d37e687..bd0bc85 100644
--- a/grpc.def
+++ b/grpc.def
@@ -99,6 +99,7 @@
     grpc_auth_context_set_peer_identity_property_name
     grpc_channel_credentials_release
     grpc_google_default_credentials_create
+    grpc_set_ssl_roots_override_callback
     grpc_ssl_credentials_create
     grpc_call_credentials_release
     grpc_composite_channel_credentials_create
diff --git a/include/grpc++/support/channel_arguments.h b/include/grpc++/support/channel_arguments.h
index a2960a7..4e530d4 100644
--- a/include/grpc++/support/channel_arguments.h
+++ b/include/grpc++/support/channel_arguments.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -51,7 +51,7 @@
 /// concrete setters are provided.
 class ChannelArguments {
  public:
-  ChannelArguments() {}
+  ChannelArguments();
   ~ChannelArguments() {}
 
   ChannelArguments(const ChannelArguments& other);
@@ -62,8 +62,8 @@
 
   void Swap(ChannelArguments& other);
 
-  /// Populates this instance with the arguments from \a channel_args. Does not
-  /// take ownership of \a channel_args.
+  /// Dump arguments in this instance to \a channel_args. Does not take
+  /// ownership of \a channel_args.
   ///
   /// Note that the underlying arguments are shared. Changes made to either \a
   /// channel_args or this instance would be reflected on both.
@@ -77,6 +77,9 @@
   /// Set the compression algorithm for the channel.
   void SetCompressionAlgorithm(grpc_compression_algorithm algorithm);
 
+  /// The given string will be sent at the front of the user agent string.
+  void SetUserAgentPrefix(const grpc::string& user_agent_prefix);
+
   // Generic channel argument setters. Only for advanced use cases.
   /// Set an integer argument \a value under \a key.
   void SetInt(const grpc::string& key, int value);
diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h
index 3de5cae..ef7205d 100644
--- a/include/grpc/grpc_security.h
+++ b/include/grpc/grpc_security.h
@@ -167,7 +167,8 @@
    before any ssl credentials are created to have the desired side effect.
    If GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment is set to a valid path, the
    callback will not be called. */
-void grpc_set_ssl_roots_override_callback(grpc_ssl_roots_override_callback cb);
+GRPCAPI void grpc_set_ssl_roots_override_callback(
+    grpc_ssl_roots_override_callback cb);
 
 /* Object that holds a private key / certificate chain pair in PEM format. */
 typedef struct {
diff --git a/src/cpp/client/create_channel.cc b/src/cpp/client/create_channel.cc
index fdaa28f..76a1b31 100644
--- a/src/cpp/client/create_channel.cc
+++ b/src/cpp/client/create_channel.cc
@@ -32,7 +32,6 @@
  */
 
 #include <memory>
-#include <sstream>
 
 #include <grpc++/channel.h>
 #include <grpc++/create_channel.h>
@@ -56,13 +55,8 @@
     const ChannelArguments& args) {
   internal::GrpcLibrary
       init_lib;  // We need to call init in case of a bad creds.
-  ChannelArguments cp_args = args;
-  std::ostringstream user_agent_prefix;
-  user_agent_prefix << "grpc-c++/" << grpc_version_string();
-  cp_args.SetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING,
-                    user_agent_prefix.str());
   return creds
-             ? creds->CreateChannel(target, cp_args)
+             ? creds->CreateChannel(target, args)
              : CreateChannelInternal("", grpc_lame_client_channel_create(
                                              NULL, GRPC_STATUS_INVALID_ARGUMENT,
                                              "Invalid credentials."));
diff --git a/src/cpp/common/channel_arguments.cc b/src/cpp/common/channel_arguments.cc
index a495118..989c105 100644
--- a/src/cpp/common/channel_arguments.cc
+++ b/src/cpp/common/channel_arguments.cc
@@ -30,14 +30,23 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  */
-
 #include <grpc++/support/channel_arguments.h>
 
+#include <sstream>
+
+#include <grpc/impl/codegen/grpc_types.h>
 #include <grpc/support/log.h>
 #include "src/core/channel/channel_args.h"
 
 namespace grpc {
 
+ChannelArguments::ChannelArguments() {
+  std::ostringstream user_agent_prefix;
+  user_agent_prefix << "grpc-c++/" << grpc_version_string();
+  // This will be ignored if used on the server side.
+  SetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING, user_agent_prefix.str());
+}
+
 ChannelArguments::ChannelArguments(const ChannelArguments& other)
     : strings_(other.strings_) {
   args_.reserve(other.args_.size());
@@ -79,6 +88,31 @@
   SetInt(GRPC_COMPRESSION_ALGORITHM_ARG, algorithm);
 }
 
+// Note: a second call to this will add in front the result of the first call.
+// An example is calling this on a copy of ChannelArguments which already has a
+// prefix. The user can build up a prefix string by calling this multiple times,
+// each with more significant identifier.
+void ChannelArguments::SetUserAgentPrefix(
+    const grpc::string& user_agent_prefix) {
+  if (user_agent_prefix.empty()) {
+    return;
+  }
+  bool replaced = false;
+  for (auto it = args_.begin(); it != args_.end(); ++it) {
+    const grpc_arg& arg = *it;
+    if (arg.type == GRPC_ARG_STRING &&
+        grpc::string(arg.key) == GRPC_ARG_PRIMARY_USER_AGENT_STRING) {
+      strings_.push_back(user_agent_prefix + " " + arg.value.string);
+      it->value.string = const_cast<char*>(strings_.back().c_str());
+      replaced = true;
+      break;
+    }
+  }
+  if (!replaced) {
+    SetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING, user_agent_prefix);
+  }
+}
+
 void ChannelArguments::SetInt(const grpc::string& key, int value) {
   grpc_arg arg;
   arg.type = GRPC_ARG_INTEGER;
diff --git a/src/python/grpcio/grpc/_cython/imports.generated.c b/src/python/grpcio/grpc/_cython/imports.generated.c
index 4aa41db..4b1860c 100644
--- a/src/python/grpcio/grpc/_cython/imports.generated.c
+++ b/src/python/grpcio/grpc/_cython/imports.generated.c
@@ -137,6 +137,7 @@
 grpc_auth_context_set_peer_identity_property_name_type grpc_auth_context_set_peer_identity_property_name_import;
 grpc_channel_credentials_release_type grpc_channel_credentials_release_import;
 grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
+grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
 grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 grpc_call_credentials_release_type grpc_call_credentials_release_import;
 grpc_composite_channel_credentials_create_type grpc_composite_channel_credentials_create_import;
@@ -401,6 +402,7 @@
   grpc_auth_context_set_peer_identity_property_name_import = (grpc_auth_context_set_peer_identity_property_name_type) GetProcAddress(library, "grpc_auth_context_set_peer_identity_property_name");
   grpc_channel_credentials_release_import = (grpc_channel_credentials_release_type) GetProcAddress(library, "grpc_channel_credentials_release");
   grpc_google_default_credentials_create_import = (grpc_google_default_credentials_create_type) GetProcAddress(library, "grpc_google_default_credentials_create");
+  grpc_set_ssl_roots_override_callback_import = (grpc_set_ssl_roots_override_callback_type) GetProcAddress(library, "grpc_set_ssl_roots_override_callback");
   grpc_ssl_credentials_create_import = (grpc_ssl_credentials_create_type) GetProcAddress(library, "grpc_ssl_credentials_create");
   grpc_call_credentials_release_import = (grpc_call_credentials_release_type) GetProcAddress(library, "grpc_call_credentials_release");
   grpc_composite_channel_credentials_create_import = (grpc_composite_channel_credentials_create_type) GetProcAddress(library, "grpc_composite_channel_credentials_create");
diff --git a/src/python/grpcio/grpc/_cython/imports.generated.h b/src/python/grpcio/grpc/_cython/imports.generated.h
index f5329f3..ca30742 100644
--- a/src/python/grpcio/grpc/_cython/imports.generated.h
+++ b/src/python/grpcio/grpc/_cython/imports.generated.h
@@ -361,6 +361,9 @@
 typedef grpc_channel_credentials *(*grpc_google_default_credentials_create_type)(void);
 extern grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
 #define grpc_google_default_credentials_create grpc_google_default_credentials_create_import
+typedef void(*grpc_set_ssl_roots_override_callback_type)(grpc_ssl_roots_override_callback cb);
+extern grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
+#define grpc_set_ssl_roots_override_callback grpc_set_ssl_roots_override_callback_import
 typedef grpc_channel_credentials *(*grpc_ssl_credentials_create_type)(const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, void *reserved);
 extern grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 #define grpc_ssl_credentials_create grpc_ssl_credentials_create_import
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.c b/src/ruby/ext/grpc/rb_grpc_imports.generated.c
index d4ddb73..1af34d9 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.c
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.c
@@ -137,6 +137,7 @@
 grpc_auth_context_set_peer_identity_property_name_type grpc_auth_context_set_peer_identity_property_name_import;
 grpc_channel_credentials_release_type grpc_channel_credentials_release_import;
 grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
+grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
 grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 grpc_call_credentials_release_type grpc_call_credentials_release_import;
 grpc_composite_channel_credentials_create_type grpc_composite_channel_credentials_create_import;
@@ -397,6 +398,7 @@
   grpc_auth_context_set_peer_identity_property_name_import = (grpc_auth_context_set_peer_identity_property_name_type) GetProcAddress(library, "grpc_auth_context_set_peer_identity_property_name");
   grpc_channel_credentials_release_import = (grpc_channel_credentials_release_type) GetProcAddress(library, "grpc_channel_credentials_release");
   grpc_google_default_credentials_create_import = (grpc_google_default_credentials_create_type) GetProcAddress(library, "grpc_google_default_credentials_create");
+  grpc_set_ssl_roots_override_callback_import = (grpc_set_ssl_roots_override_callback_type) GetProcAddress(library, "grpc_set_ssl_roots_override_callback");
   grpc_ssl_credentials_create_import = (grpc_ssl_credentials_create_type) GetProcAddress(library, "grpc_ssl_credentials_create");
   grpc_call_credentials_release_import = (grpc_call_credentials_release_type) GetProcAddress(library, "grpc_call_credentials_release");
   grpc_composite_channel_credentials_create_import = (grpc_composite_channel_credentials_create_type) GetProcAddress(library, "grpc_composite_channel_credentials_create");
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
index 618ae5e..b61c528 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
@@ -361,6 +361,9 @@
 typedef grpc_channel_credentials *(*grpc_google_default_credentials_create_type)(void);
 extern grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
 #define grpc_google_default_credentials_create grpc_google_default_credentials_create_import
+typedef void(*grpc_set_ssl_roots_override_callback_type)(grpc_ssl_roots_override_callback cb);
+extern grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
+#define grpc_set_ssl_roots_override_callback grpc_set_ssl_roots_override_callback_import
 typedef grpc_channel_credentials *(*grpc_ssl_credentials_create_type)(const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, void *reserved);
 extern grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 #define grpc_ssl_credentials_create grpc_ssl_credentials_create_import
diff --git a/test/cpp/common/channel_arguments_test.cc b/test/cpp/common/channel_arguments_test.cc
index e010d37..5ee63bf 100644
--- a/test/cpp/common/channel_arguments_test.cc
+++ b/test/cpp/common/channel_arguments_test.cc
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -45,90 +45,131 @@
                       grpc_channel_args* args) {
     channel_args.SetChannelArgs(args);
   }
+
+  grpc::string GetDefaultUserAgentPrefix() {
+    std::ostringstream user_agent_prefix;
+    user_agent_prefix << "grpc-c++/" << grpc_version_string();
+    return user_agent_prefix.str();
+  }
+
+  void VerifyDefaultChannelArgs() {
+    grpc_channel_args args;
+    SetChannelArgs(channel_args_, &args);
+    EXPECT_EQ(static_cast<size_t>(1), args.num_args);
+    EXPECT_STREQ(GRPC_ARG_PRIMARY_USER_AGENT_STRING, args.args[0].key);
+    EXPECT_EQ(GetDefaultUserAgentPrefix(),
+              grpc::string(args.args[0].value.string));
+  }
+
+  bool HasArg(grpc_arg expected_arg) {
+    grpc_channel_args args;
+    SetChannelArgs(channel_args_, &args);
+    for (size_t i = 0; i < args.num_args; i++) {
+      const grpc_arg& arg = args.args[i];
+      if (arg.type == expected_arg.type &&
+          grpc::string(arg.key) == expected_arg.key) {
+        if (arg.type == GRPC_ARG_INTEGER) {
+          return arg.value.integer == expected_arg.value.integer;
+        } else if (arg.type == GRPC_ARG_STRING) {
+          return grpc::string(arg.value.string) == expected_arg.value.string;
+        } else if (arg.type == GRPC_ARG_POINTER) {
+          return arg.value.pointer.p == expected_arg.value.pointer.p &&
+                 arg.value.pointer.copy == expected_arg.value.pointer.copy &&
+                 arg.value.pointer.destroy ==
+                     expected_arg.value.pointer.destroy;
+        }
+      }
+    }
+    return false;
+  }
+  ChannelArguments channel_args_;
 };
 
 TEST_F(ChannelArgumentsTest, SetInt) {
-  grpc_channel_args args;
-  ChannelArguments channel_args;
-  // Empty arguments.
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(0), args.num_args);
+  VerifyDefaultChannelArgs();
+  grpc::string key0("key0");
+  grpc_arg arg0;
+  arg0.type = GRPC_ARG_INTEGER;
+  arg0.key = const_cast<char*>(key0.c_str());
+  arg0.value.integer = 0;
+  grpc::string key1("key1");
+  grpc_arg arg1;
+  arg1.type = GRPC_ARG_INTEGER;
+  arg1.key = const_cast<char*>(key1.c_str());
+  arg1.value.integer = 1;
 
-  grpc::string key("key0");
-  channel_args.SetInt(key, 0);
+  grpc::string arg_key0(key0);
+  channel_args_.SetInt(arg_key0, arg0.value.integer);
   // Clear key early to make sure channel_args takes a copy
-  key = "";
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(1), args.num_args);
-  EXPECT_EQ(GRPC_ARG_INTEGER, args.args[0].type);
-  EXPECT_STREQ("key0", args.args[0].key);
-  EXPECT_EQ(0, args.args[0].value.integer);
+  arg_key0.clear();
+  EXPECT_TRUE(HasArg(arg0));
 
-  key = "key1";
-  channel_args.SetInt(key, 1);
-  key = "";
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(2), args.num_args);
-  // We do not enforce order on the arguments.
-  for (size_t i = 0; i < args.num_args; i++) {
-    EXPECT_EQ(GRPC_ARG_INTEGER, args.args[i].type);
-    if (grpc::string(args.args[i].key) == "key0") {
-      EXPECT_EQ(0, args.args[i].value.integer);
-    } else if (grpc::string(args.args[i].key) == "key1") {
-      EXPECT_EQ(1, args.args[i].value.integer);
-    }
-  }
+  grpc::string arg_key1(key1);
+  channel_args_.SetInt(arg_key1, arg1.value.integer);
+  arg_key1.clear();
+  EXPECT_TRUE(HasArg(arg0));
+  EXPECT_TRUE(HasArg(arg1));
 }
 
 TEST_F(ChannelArgumentsTest, SetString) {
-  grpc_channel_args args;
-  ChannelArguments channel_args;
-  // Empty arguments.
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(0), args.num_args);
+  VerifyDefaultChannelArgs();
+  grpc::string key0("key0");
+  grpc::string val0("val0");
+  grpc_arg arg0;
+  arg0.type = GRPC_ARG_STRING;
+  arg0.key = const_cast<char*>(key0.c_str());
+  arg0.value.string = const_cast<char*>(val0.c_str());
+  grpc::string key1("key1");
+  grpc::string val1("val1");
+  grpc_arg arg1;
+  arg1.type = GRPC_ARG_STRING;
+  arg1.key = const_cast<char*>(key1.c_str());
+  arg1.value.string = const_cast<char*>(val1.c_str());
 
-  grpc::string key("key0");
-  grpc::string val("val0");
-  channel_args.SetString(key, val);
+  grpc::string key(key0);
+  grpc::string val(val0);
+  channel_args_.SetString(key, val);
   // Clear key/val early to make sure channel_args takes a copy
   key = "";
   val = "";
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(1), args.num_args);
-  EXPECT_EQ(GRPC_ARG_STRING, args.args[0].type);
-  EXPECT_STREQ("key0", args.args[0].key);
-  EXPECT_STREQ("val0", args.args[0].value.string);
+  EXPECT_TRUE(HasArg(arg0));
 
-  key = "key1";
-  val = "val1";
-  channel_args.SetString(key, val);
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(2), args.num_args);
-  // We do not enforce order on the arguments.
-  for (size_t i = 0; i < args.num_args; i++) {
-    EXPECT_EQ(GRPC_ARG_STRING, args.args[i].type);
-    if (grpc::string(args.args[i].key) == "key0") {
-      EXPECT_STREQ("val0", args.args[i].value.string);
-    } else if (grpc::string(args.args[i].key) == "key1") {
-      EXPECT_STREQ("val1", args.args[i].value.string);
-    }
-  }
+  key = key1;
+  val = val1;
+  channel_args_.SetString(key, val);
+  // Clear key/val early to make sure channel_args takes a copy
+  key = "";
+  val = "";
+  EXPECT_TRUE(HasArg(arg0));
+  EXPECT_TRUE(HasArg(arg1));
 }
 
 TEST_F(ChannelArgumentsTest, SetPointer) {
-  grpc_channel_args args;
-  ChannelArguments channel_args;
-  // Empty arguments.
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(0), args.num_args);
+  VerifyDefaultChannelArgs();
+  grpc::string key0("key0");
+  grpc_arg arg0;
+  arg0.type = GRPC_ARG_POINTER;
+  arg0.key = const_cast<char*>(key0.c_str());
+  arg0.value.pointer.p = &key0;
+  arg0.value.pointer.copy = nullptr;
+  arg0.value.pointer.destroy = nullptr;
 
-  grpc::string key("key0");
-  channel_args.SetPointer(key, &key);
-  SetChannelArgs(channel_args, &args);
-  EXPECT_EQ(static_cast<size_t>(1), args.num_args);
-  EXPECT_EQ(GRPC_ARG_POINTER, args.args[0].type);
-  EXPECT_STREQ("key0", args.args[0].key);
-  EXPECT_EQ(&key, args.args[0].value.pointer.p);
+  grpc::string key(key0);
+  channel_args_.SetPointer(key, arg0.value.pointer.p);
+  EXPECT_TRUE(HasArg(arg0));
+}
+
+TEST_F(ChannelArgumentsTest, SetUserAgentPrefix) {
+  VerifyDefaultChannelArgs();
+  grpc::string prefix("prefix");
+  grpc::string whole_prefix = prefix + " " + GetDefaultUserAgentPrefix();
+  grpc_arg arg0;
+  arg0.type = GRPC_ARG_STRING;
+  arg0.key = const_cast<char*>(GRPC_ARG_PRIMARY_USER_AGENT_STRING);
+  arg0.value.string = const_cast<char*>(whole_prefix.c_str());
+
+  channel_args_.SetUserAgentPrefix(prefix);
+  EXPECT_TRUE(HasArg(arg0));
 }
 
 }  // namespace testing
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index 65da71b..c852384 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -252,6 +252,9 @@
       args.SetSslTargetNameOverride("foo.test.google.fr");
       channel_creds = SslCredentials(ssl_opts);
     }
+    if (!user_agent_prefix_.empty()) {
+      args.SetUserAgentPrefix(user_agent_prefix_);
+    }
     args.SetString(GRPC_ARG_SECONDARY_USER_AGENT_STRING, "end2end_test");
     channel_ = CreateCustomChannel(server_address_.str(), channel_creds, args);
   }
@@ -285,6 +288,7 @@
   TestServiceImpl service_;
   TestServiceImpl special_service_;
   TestServiceImplDupPkg dup_pkg_service_;
+  grpc::string user_agent_prefix_;
 };
 
 static void SendRpc(grpc::testing::EchoTestService::Stub* stub, int num_rpcs,
@@ -601,6 +605,25 @@
   TestBidiStreamServerCancel(CANCEL_AFTER_PROCESSING, 5);
 }
 
+TEST_P(End2endTest, SimpleRpcWithCustomeUserAgentPrefix) {
+  user_agent_prefix_ = "custom_prefix";
+  ResetStub();
+  EchoRequest request;
+  EchoResponse response;
+  request.set_message("Hello hello hello hello");
+  request.mutable_param()->set_echo_metadata(true);
+
+  ClientContext context;
+  Status s = stub_->Echo(&context, request, &response);
+  EXPECT_EQ(response.message(), request.message());
+  EXPECT_TRUE(s.ok());
+  const auto& trailing_metadata = context.GetServerTrailingMetadata();
+  auto iter = trailing_metadata.find("user-agent");
+  EXPECT_TRUE(iter != trailing_metadata.end());
+  grpc::string expected_prefix = user_agent_prefix_ + " grpc-c++/";
+  EXPECT_TRUE(iter->second.starts_with(expected_prefix));
+}
+
 TEST_P(End2endTest, MultipleRpcsWithVariedBinaryMetadataValue) {
   ResetStub();
   std::vector<std::thread*> threads;
diff --git a/test/cpp/qps/qps-sweep.sh b/test/cpp/qps/qps-sweep.sh
index 539da1d..7a35788 100755
--- a/test/cpp/qps/qps-sweep.sh
+++ b/test/cpp/qps/qps-sweep.sh
@@ -37,9 +37,26 @@
 
 bins=`find . .. ../.. ../../.. -name bins | head -1`
 
+# Print out each command that gets executed
 set -x
 
+#
+# Specify parameters used in some of the tests
+#
+
+# big is the size in bytes of large messages (0 is the size otherwise)
 big=65536
+
+# wide is the number of client channels in multi-channel tests (1 otherwise)
+wide=64
+
+# deep is the number of RPCs outstanding on a channel in non-ping-pong tests
+# (the value used is 1 otherwise)
+deep=100
+
+# half is half the count of worker processes, used in the crossbar scenario
+# that uses equal clients and servers. The other scenarios use only 1 server
+# and either 1 client or N-1 clients as appropriate
 half=`echo $QPS_WORKERS | awk -F, '{print int(NF/2)}'`
 
 for secure in true false; do
@@ -52,30 +69,40 @@
 
   # Scenario 2: generic async streaming "unconstrained" (QPS)
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
-    --client_channels=64 --bbuf_req_size=0 --bbuf_resp_size=0 \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
+    --client_channels=$wide --bbuf_req_size=0 --bbuf_resp_size=0 \
     --async_client_threads=0 --async_server_threads=0 --secure_test=$secure \
-    --num_servers=1 --num_clients=0
+    --num_servers=1 --num_clients=0 |& tee /tmp/qps-test.$$
 
   # Scenario 2b: QPS with a single server core
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
-    --client_channels=64 --bbuf_req_size=0 --bbuf_resp_size=0 \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
+    --client_channels=$wide --bbuf_req_size=0 --bbuf_resp_size=0 \
     --async_client_threads=0 --async_server_threads=0 --secure_test=$secure \
     --num_servers=1 --num_clients=0 --server_core_limit=1
 
   # Scenario 2c: protobuf-based QPS
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_SERVER --outstanding_rpcs_per_channel=100 \
-    --client_channels=64 --simple_req_size=0 --simple_resp_size=0 \
+    --server_type=ASYNC_SERVER --outstanding_rpcs_per_channel=$deep \
+    --client_channels=$wide --simple_req_size=0 --simple_resp_size=0 \
     --async_client_threads=0 --async_server_threads=0 --secure_test=$secure \
     --num_servers=1 --num_clients=0
 
-  # Scenario 3: Latency at near-peak load (TBD)
+  # Scenario 3: Latency at sub-peak load (all clients equally loaded)
+  for loadfactor in 0.2 0.5 0.7; do
+    "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
+      --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
+      --client_channels=$wide --bbuf_req_size=0 --bbuf_resp_size=0 \
+      --async_client_threads=0 --async_server_threads=0 --secure_test=$secure \
+      --num_servers=1 --num_clients=0 --poisson_load=`awk -v lf=$loadfactor \
+      '$5 == "QPS:" {print int(lf * $6); exit}' /tmp/qps-test.$$`
+  done
+
+  rm /tmp/qps-test.$$
 
   # Scenario 4: Single-channel bidirectional throughput test (like TCP_STREAM).
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
     --client_channels=1 --bbuf_req_size=$big --bbuf_resp_size=$big \
     --async_client_threads=1 --async_server_threads=1 --secure_test=$secure \
     --num_servers=1 --num_clients=1
@@ -108,35 +135,35 @@
 
   # Scenario 9: Crossbar QPS test
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
-    --client_channels=64 --bbuf_req_size=0 --bbuf_resp_size=0 \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
+    --client_channels=$wide --bbuf_req_size=0 --bbuf_resp_size=0 \
     --async_client_threads=0 --async_server_threads=0 --secure_test=$secure \
     --num_servers=$half --num_clients=0
 
   # Scenario 10: Multi-channel bidir throughput test
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
-    --client_channels=64 --bbuf_req_size=$big --bbuf_resp_size=$big \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=1 \
+    --client_channels=$wide --bbuf_req_size=$big --bbuf_resp_size=$big \
     --async_client_threads=0 --async_server_threads=0 --secure_test=$secure \
     --num_servers=1 --num_clients=1
 
   # Scenario 11: Single-channel request throughput test
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
     --client_channels=1 --bbuf_req_size=$big --bbuf_resp_size=0 \
     --async_client_threads=1 --async_server_threads=1 --secure_test=$secure \
     --num_servers=1 --num_clients=1
 
   # Scenario 12: Single-channel response throughput test
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=100 \
+    --server_type=ASYNC_GENERIC_SERVER --outstanding_rpcs_per_channel=$deep \
     --client_channels=1 --bbuf_req_size=0 --bbuf_resp_size=$big \
     --async_client_threads=1 --async_server_threads=1 --secure_test=$secure \
     --num_servers=1 --num_clients=1
 
   # Scenario 13: Single-channel bidirectional protobuf throughput test
   "$bins"/opt/qps_driver --rpc_type=STREAMING --client_type=ASYNC_CLIENT \
-    --server_type=ASYNC_SERVER --outstanding_rpcs_per_channel=100 \
+    --server_type=ASYNC_SERVER --outstanding_rpcs_per_channel=$deep \
     --client_channels=1 --simple_req_size=$big --simple_resp_size=$big \
     --async_client_threads=1 --async_server_threads=1 --secure_test=$secure \
     --num_servers=1 --num_clients=1