diff --git a/BUILD b/BUILD
index d52e3f6c081601d2e3ca20a7532cbcc76e1c515a..04dba30d7fb052b273adbc3aa279b32246e94fe4 100644
--- a/BUILD
+++ b/BUILD
@@ -993,6 +993,7 @@ grpc_cc_library(
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c",
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c",
+        "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c",
     ],
     hdrs = [
         "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d2418c5a5f55743c319567d16c0592709e984d9..a60786458b1202ebce8c38a22730675f755b283d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1131,6 +1131,7 @@ add_library(grpc
   src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c
   src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c
   src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c
   src/core/ext/filters/load_reporting/load_reporting.c
@@ -2006,6 +2007,7 @@ add_library(grpc_unsecure
   src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c
   src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c
+  src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c
   src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c
   src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c
   src/core/ext/filters/client_channel/resolver/fake/fake_resolver.c
diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md
new file mode 100644
index 0000000000000000000000000000000000000000..9d4213ebca734be81a38cf3c0ee9fa23f246ce52
--- /dev/null
+++ b/CODE-OF-CONDUCT.md
@@ -0,0 +1,3 @@
+## Community Code of Conduct
+
+gRPC follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
diff --git a/Makefile b/Makefile
index 5df68785b4005d8aacea38a5ccc9ab30f0c63517..75f75da32412fc305cf13128c4cce491cd6059cd 100644
--- a/Makefile
+++ b/Makefile
@@ -3114,6 +3114,7 @@ LIBGRPC_SRC = \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c \
     src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c \
     src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c \
     src/core/ext/filters/load_reporting/load_reporting.c \
@@ -3958,6 +3959,7 @@ LIBGRPC_UNSECURE_SRC = \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c \
     src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c \
     src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c \
     src/core/ext/filters/client_channel/resolver/fake/fake_resolver.c \
diff --git a/PATENTS b/PATENTS
deleted file mode 100644
index 619f9dbfe637f648ce4e57d94b7b23797b37a9b8..0000000000000000000000000000000000000000
--- a/PATENTS
+++ /dev/null
@@ -1,22 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the GRPC project.
-
-Google hereby grants to You a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this section)
-patent license to make, have made, use, offer to sell, sell, import,
-transfer and otherwise run, modify and propagate the contents of this
-implementation of GRPC, where such license applies only to those patent
-claims, both currently owned or controlled by Google and acquired in
-the future, licensable by Google that are necessarily infringed by this
-implementation of GRPC.  This grant does not include claims that would be
-infringed only as a consequence of further modification of this
-implementation.  If you or your agent or exclusive licensee institute or
-order or agree to the institution of patent litigation against any
-entity (including a cross-claim or counterclaim in a lawsuit) alleging
-that this implementation of GRPC or any code incorporated within this
-implementation of GRPC constitutes direct or contributory patent
-infringement, or inducement of patent infringement, then any patent
-rights granted to you under this License for this implementation of GRPC
-shall terminate as of the date such litigation is filed.
diff --git a/binding.gyp b/binding.gyp
index 9b1a56692a78f7524e7849efa504464ece8b5a44..d5bb27f6da06f9c9e0516b5ddb4af51252d89fea 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -862,6 +862,7 @@
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c',
         'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c',
+        'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c',
         'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c',
         'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c',
         'src/core/ext/filters/load_reporting/load_reporting.c',
diff --git a/build.yaml b/build.yaml
index 56d7907891456320f446ab69dff9e61e7ed7bb33..7ab1cee2630bc8228152c4a5951529116448778d 100644
--- a/build.yaml
+++ b/build.yaml
@@ -589,6 +589,7 @@ filegroups:
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c
   - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c
+  - src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c
   plugin: grpc_resolver_dns_ares
   uses:
   - grpc_base
diff --git a/config.m4 b/config.m4
index 886f7107f4aa9a72bfd97475d780530ffe9909db..0049b72d483d48dd4586773fa2456c897ee25178 100644
--- a/config.m4
+++ b/config.m4
@@ -309,6 +309,7 @@ if test "$PHP_GRPC" != "no"; then
     src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c \
     src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c \
+    src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c \
     src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c \
     src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c \
     src/core/ext/filters/load_reporting/load_reporting.c \
diff --git a/config.w32 b/config.w32
index 3e21815e820edd4ac2971e79376154ae2656b90f..919587dc586c264291b4719a208a1debe877c9f4 100644
--- a/config.w32
+++ b/config.w32
@@ -286,6 +286,7 @@ if (PHP_GRPC != "no") {
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\dns_resolver_ares.c " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_ev_driver_posix.c " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper.c " +
+    "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\c_ares\\grpc_ares_wrapper_fallback.c " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\dns\\native\\dns_resolver.c " +
     "src\\core\\ext\\filters\\client_channel\\resolver\\sockaddr\\sockaddr_resolver.c " +
     "src\\core\\ext\\filters\\load_reporting\\load_reporting.c " +
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index f1d2190c1511d1aaff8cedb98d72f9de60d2cf93..277f97944cfa78de8aeda3d328d2be74d939395c 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -685,6 +685,7 @@ Pod::Spec.new do |s|
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c',
                       'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c',
+                      'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c',
                       'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c',
                       'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c',
                       'src/core/ext/filters/load_reporting/load_reporting.c',
diff --git a/grpc.gemspec b/grpc.gemspec
index 31f72201a76898c2b38716dd5c5810efa7f8fe92..a4e22dfbdf116f87dd321c1051b79ce76b408de5 100755
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -616,6 +616,7 @@ Gem::Specification.new do |s|
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c )
+  s.files += %w( src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c )
   s.files += %w( src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c )
   s.files += %w( src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c )
   s.files += %w( src/core/ext/filters/load_reporting/load_reporting.c )
diff --git a/include/grpc++/impl/codegen/async_unary_call.h b/include/grpc++/impl/codegen/async_unary_call.h
index 66843d0da32eabae6116b81ce82d9a8a7651c78d..4aa4e25a9e9842930468e768965449d6a27e6309 100644
--- a/include/grpc++/impl/codegen/async_unary_call.h
+++ b/include/grpc++/impl/codegen/async_unary_call.h
@@ -139,7 +139,7 @@ class ClientAsyncResponseReader final
 
   // disable operator new
   static void* operator new(std::size_t size);
-  static void* operator new(std::size_t size, void* p) { return p; };
+  static void* operator new(std::size_t size, void* p) { return p; }
 
   SneakyCallOpSet<CallOpSendInitialMetadata, CallOpSendMessage,
                   CallOpClientSendClose>
diff --git a/include/grpc++/impl/codegen/completion_queue.h b/include/grpc++/impl/codegen/completion_queue.h
index 0fef12c0914a328a446c00ec205da7bbd008c5f0..ca757e2a9c5e9db41cdebaac367164cc5023ab27 100644
--- a/include/grpc++/impl/codegen/completion_queue.h
+++ b/include/grpc++/impl/codegen/completion_queue.h
@@ -167,7 +167,7 @@ class CompletionQueue : private GrpcLibraryCodegen {
   void RegisterAvalanching() {
     gpr_atm_no_barrier_fetch_add(&avalanches_in_flight_,
                                  static_cast<gpr_atm>(1));
-  };
+  }
   void CompleteAvalanching();
 
  protected:
diff --git a/package.xml b/package.xml
index 36e77699dc536e80e4b11ec1264a8956bde7d389..18b1d639b99209753295c92000204d20f65bf360 100644
--- a/package.xml
+++ b/package.xml
@@ -630,6 +630,7 @@
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c" role="src" />
+    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/load_reporting/load_reporting.c" role="src" />
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c
index ebdba2835f845dd89d385808d0e7c80c955667d2..92f060b4229a84e77aade0613a94fccded0e9401 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c
@@ -65,6 +65,8 @@ typedef struct {
   grpc_combiner *combiner;
   /** are we currently resolving? */
   bool resolving;
+  /** the pending resolving request */
+  grpc_ares_request *pending_request;
   /** which version of the result have we published? */
   int published_version;
   /** which version of the result is current? */
@@ -82,7 +84,7 @@ typedef struct {
   gpr_backoff backoff_state;
 
   /** currently resolving addresses */
-  grpc_resolved_addresses *addresses;
+  grpc_lb_addresses *lb_addresses;
 } ares_dns_resolver;
 
 static void dns_ares_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *r);
@@ -109,6 +111,9 @@ static void dns_ares_shutdown_locked(grpc_exec_ctx *exec_ctx,
   if (r->have_retry_timer) {
     grpc_timer_cancel(exec_ctx, &r->retry_timer);
   }
+  if (r->pending_request != NULL) {
+    grpc_cancel_ares_request(exec_ctx, r->pending_request);
+  }
   if (r->next_completion != NULL) {
     *r->target_result = NULL;
     grpc_closure_sched(
@@ -145,19 +150,11 @@ static void dns_ares_on_resolved_locked(grpc_exec_ctx *exec_ctx, void *arg,
   grpc_channel_args *result = NULL;
   GPR_ASSERT(r->resolving);
   r->resolving = false;
-  if (r->addresses != NULL) {
-    grpc_lb_addresses *addresses = grpc_lb_addresses_create(
-        r->addresses->naddrs, NULL /* user_data_vtable */);
-    for (size_t i = 0; i < r->addresses->naddrs; ++i) {
-      grpc_lb_addresses_set_address(
-          addresses, i, &r->addresses->addrs[i].addr,
-          r->addresses->addrs[i].len, false /* is_balancer */,
-          NULL /* balancer_name */, NULL /* user_data */);
-    }
-    grpc_arg new_arg = grpc_lb_addresses_create_channel_arg(addresses);
+  r->pending_request = NULL;
+  if (r->lb_addresses != NULL) {
+    grpc_arg new_arg = grpc_lb_addresses_create_channel_arg(r->lb_addresses);
     result = grpc_channel_args_copy_and_add(r->channel_args, &new_arg, 1);
-    grpc_resolved_addresses_destroy(r->addresses);
-    grpc_lb_addresses_destroy(exec_ctx, addresses);
+    grpc_lb_addresses_destroy(exec_ctx, r->lb_addresses);
   } else {
     const char *msg = grpc_error_string(error);
     gpr_log(GPR_DEBUG, "dns resolution failed: %s", msg);
@@ -209,10 +206,11 @@ static void dns_ares_start_resolving_locked(grpc_exec_ctx *exec_ctx,
   GRPC_RESOLVER_REF(&r->base, "dns-resolving");
   GPR_ASSERT(!r->resolving);
   r->resolving = true;
-  r->addresses = NULL;
-  grpc_dns_lookup_ares(exec_ctx, r->dns_server, r->name_to_resolve,
-                       r->default_port, r->interested_parties,
-                       &r->dns_ares_on_resolved_locked, &r->addresses);
+  r->lb_addresses = NULL;
+  r->pending_request = grpc_dns_lookup_ares(
+      exec_ctx, r->dns_server, r->name_to_resolve, r->default_port,
+      r->interested_parties, &r->dns_ares_on_resolved_locked, &r->lb_addresses,
+      true /* check_grpclb */);
 }
 
 static void dns_ares_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx,
@@ -222,6 +220,7 @@ static void dns_ares_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx,
     *r->target_result = r->resolved_result == NULL
                             ? NULL
                             : grpc_channel_args_copy(r->resolved_result);
+    gpr_log(GPR_DEBUG, "dns_ares_maybe_finish_next_locked");
     grpc_closure_sched(exec_ctx, r->next_completion, GRPC_ERROR_NONE);
     r->next_completion = NULL;
     r->published_version = r->resolved_version;
@@ -245,10 +244,10 @@ static void dns_ares_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *gr) {
 static grpc_resolver *dns_ares_create(grpc_exec_ctx *exec_ctx,
                                       grpc_resolver_args *args,
                                       const char *default_port) {
-  // Get name from args.
+  /* Get name from args. */
   const char *path = args->uri->path;
   if (path[0] == '/') ++path;
-  // Create resolver.
+  /* Create resolver. */
   ares_dns_resolver *r = gpr_zalloc(sizeof(ares_dns_resolver));
   grpc_resolver_init(&r->base, &dns_ares_resolver_vtable, args->combiner);
   if (0 != strcmp(args->uri->authority, "")) {
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
index ff3904e2520cfa9fbacb376f68c0dcc56219ad57..eb1a8ab011ced991838823bf9decab525eb5b064 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h
@@ -47,5 +47,9 @@ grpc_error *grpc_ares_ev_driver_create(grpc_ares_ev_driver **ev_driver,
    of ARES_ECANCELLED. */
 void grpc_ares_ev_driver_destroy(grpc_ares_ev_driver *ev_driver);
 
+/* Shutdown all the grpc_fds used by \a ev_driver */
+void grpc_ares_ev_driver_shutdown(grpc_exec_ctx *exec_ctx,
+                                  grpc_ares_ev_driver *ev_driver);
+
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_C_ARES_GRPC_ARES_EV_DRIVER_H \
           */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c
index 03ce3036162766eede2315bd32f75237de7db7b3..293e6b0252a6d47cbac48a1e3ef4d21ca00a07b1 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c
@@ -99,9 +99,12 @@ static void fd_node_destroy(grpc_exec_ctx *exec_ctx, fd_node *fdn) {
   GPR_ASSERT(!fdn->writable_registered);
   gpr_mu_destroy(&fdn->mu);
   grpc_pollset_set_del_fd(exec_ctx, fdn->ev_driver->pollset_set, fdn->grpc_fd);
-  grpc_fd_shutdown(exec_ctx, fdn->grpc_fd,
-                   GRPC_ERROR_CREATE_FROM_STATIC_STRING("fd node destroyed"));
-  grpc_fd_orphan(exec_ctx, fdn->grpc_fd, NULL, NULL, "c-ares query finished");
+  /* c-ares library has closed the fd inside grpc_fd. This fd may be picked up
+     immediately by another thread, and should not be closed by the following
+     grpc_fd_orphan. To prevent this fd from being closed by grpc_fd_orphan,
+     a fd pointer is provided. */
+  int fd;
+  grpc_fd_orphan(exec_ctx, fdn->grpc_fd, NULL, &fd, "c-ares query finished");
   gpr_free(fdn);
 }
 
@@ -140,6 +143,20 @@ void grpc_ares_ev_driver_destroy(grpc_ares_ev_driver *ev_driver) {
   grpc_ares_ev_driver_unref(ev_driver);
 }
 
+void grpc_ares_ev_driver_shutdown(grpc_exec_ctx *exec_ctx,
+                                  grpc_ares_ev_driver *ev_driver) {
+  gpr_mu_lock(&ev_driver->mu);
+  ev_driver->shutting_down = true;
+  fd_node *fn = ev_driver->fds;
+  while (fn != NULL) {
+    grpc_fd_shutdown(
+        exec_ctx, fn->grpc_fd,
+        GRPC_ERROR_CREATE_FROM_STATIC_STRING("grpc_ares_ev_driver_shutdown"));
+    fn = fn->next;
+  }
+  gpr_mu_unlock(&ev_driver->mu);
+}
+
 // Search fd in the fd_node list head. This is an O(n) search, the max possible
 // value of n is ARES_GETSOCK_MAXNUM (16). n is typically 1 - 2 in our tests.
 static fd_node *pop_fd_node(fd_node **head, int fd) {
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c
index 4ee07e170af520816289fde2629b160733dcb373..153287c30e0b576aad7b1f0012b9b3374fabcfd3 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c
@@ -33,6 +33,7 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
 #include <grpc/support/useful.h>
+#include <nameser.h>
 
 #include "src/core/ext/filters/client_channel/parse_address.h"
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h"
@@ -45,20 +46,14 @@
 static gpr_once g_basic_init = GPR_ONCE_INIT;
 static gpr_mu g_init_mu;
 
-typedef struct grpc_ares_request {
+struct grpc_ares_request {
   /** indicates the DNS server to use, if specified */
   struct ares_addr_port_node dns_server_addr;
   /** following members are set in grpc_resolve_address_ares_impl */
-  /** host to resolve, parsed from the name to resolve */
-  char *host;
-  /** port to fill in sockaddr_in, parsed from the name to resolve */
-  char *port;
-  /** default port to use */
-  char *default_port;
   /** closure to call when the request completes */
   grpc_closure *on_done;
   /** the pointer to receive the resolved addresses */
-  grpc_resolved_addresses **addrs_out;
+  grpc_lb_addresses **lb_addrs_out;
   /** the evernt driver used by this request */
   grpc_ares_ev_driver *ev_driver;
   /** number of ongoing queries */
@@ -70,7 +65,19 @@ typedef struct grpc_ares_request {
   bool success;
   /** the errors explaining the request failure, set in on_done_cb */
   grpc_error *error;
-} grpc_ares_request;
+};
+
+typedef struct grpc_ares_hostbyname_request {
+  /** following members are set in create_hostbyname_request */
+  /** the top-level request instance */
+  grpc_ares_request *parent_request;
+  /** host to resolve, parsed from the name to resolve */
+  char *host;
+  /** port to fill in sockaddr_in, parsed from the name to resolve */
+  uint16_t port;
+  /** is it a grpclb address */
+  bool is_balancer;
+} grpc_ares_hostbyname_request;
 
 static void do_basic_init(void) { gpr_mu_init(&g_init_mu); }
 
@@ -83,6 +90,10 @@ static uint16_t strhtons(const char *port) {
   return htons((unsigned short)atoi(port));
 }
 
+static void grpc_ares_request_ref(grpc_ares_request *r) {
+  gpr_ref(&r->pending_queries);
+}
+
 static void grpc_ares_request_unref(grpc_exec_ctx *exec_ctx,
                                     grpc_ares_request *r) {
   /* If there are no pending queries, invoke on_done callback and destroy the
@@ -103,67 +114,95 @@ static void grpc_ares_request_unref(grpc_exec_ctx *exec_ctx,
     }
     gpr_mu_destroy(&r->mu);
     grpc_ares_ev_driver_destroy(r->ev_driver);
-    gpr_free(r->host);
-    gpr_free(r->port);
-    gpr_free(r->default_port);
     gpr_free(r);
   }
 }
 
-static void on_done_cb(void *arg, int status, int timeouts,
-                       struct hostent *hostent) {
-  grpc_ares_request *r = (grpc_ares_request *)arg;
+static grpc_ares_hostbyname_request *create_hostbyname_request(
+    grpc_ares_request *parent_request, char *host, uint16_t port,
+    bool is_balancer) {
+  grpc_ares_hostbyname_request *hr =
+      gpr_zalloc(sizeof(grpc_ares_hostbyname_request));
+  hr->parent_request = parent_request;
+  hr->host = gpr_strdup(host);
+  hr->port = port;
+  hr->is_balancer = is_balancer;
+  grpc_ares_request_ref(parent_request);
+  return hr;
+}
+
+static void destroy_hostbyname_request(grpc_exec_ctx *exec_ctx,
+                                       grpc_ares_hostbyname_request *hr) {
+  grpc_ares_request_unref(exec_ctx, hr->parent_request);
+  gpr_free(hr->host);
+  gpr_free(hr);
+}
+
+static void on_hostbyname_done_cb(void *arg, int status, int timeouts,
+                                  struct hostent *hostent) {
+  grpc_ares_hostbyname_request *hr = (grpc_ares_hostbyname_request *)arg;
+  grpc_ares_request *r = hr->parent_request;
   gpr_mu_lock(&r->mu);
   if (status == ARES_SUCCESS) {
     GRPC_ERROR_UNREF(r->error);
     r->error = GRPC_ERROR_NONE;
     r->success = true;
-    grpc_resolved_addresses **addresses = r->addrs_out;
-    if (*addresses == NULL) {
-      *addresses = gpr_malloc(sizeof(grpc_resolved_addresses));
-      (*addresses)->naddrs = 0;
-      (*addresses)->addrs = NULL;
+    grpc_lb_addresses **lb_addresses = r->lb_addrs_out;
+    if (*lb_addresses == NULL) {
+      *lb_addresses = grpc_lb_addresses_create(0, NULL);
     }
-    size_t prev_naddr = (*addresses)->naddrs;
+    size_t prev_naddr = (*lb_addresses)->num_addresses;
     size_t i;
     for (i = 0; hostent->h_addr_list[i] != NULL; i++) {
     }
-    (*addresses)->naddrs += i;
-    (*addresses)->addrs =
-        gpr_realloc((*addresses)->addrs,
-                    sizeof(grpc_resolved_address) * (*addresses)->naddrs);
-    for (i = prev_naddr; i < (*addresses)->naddrs; i++) {
-      memset(&(*addresses)->addrs[i], 0, sizeof(grpc_resolved_address));
-      if (hostent->h_addrtype == AF_INET6) {
-        (*addresses)->addrs[i].len = sizeof(struct sockaddr_in6);
-        struct sockaddr_in6 *addr =
-            (struct sockaddr_in6 *)&(*addresses)->addrs[i].addr;
-        addr->sin6_family = (sa_family_t)hostent->h_addrtype;
-        addr->sin6_port = strhtons(r->port);
-
-        char output[INET6_ADDRSTRLEN];
-        memcpy(&addr->sin6_addr, hostent->h_addr_list[i - prev_naddr],
-               sizeof(struct in6_addr));
-        ares_inet_ntop(AF_INET6, &addr->sin6_addr, output, INET6_ADDRSTRLEN);
-        gpr_log(GPR_DEBUG,
-                "c-ares resolver gets a AF_INET6 result: \n"
-                "  addr: %s\n  port: %s\n  sin6_scope_id: %d\n",
-                output, r->port, addr->sin6_scope_id);
-      } else {
-        (*addresses)->addrs[i].len = sizeof(struct sockaddr_in);
-        struct sockaddr_in *addr =
-            (struct sockaddr_in *)&(*addresses)->addrs[i].addr;
-        memcpy(&addr->sin_addr, hostent->h_addr_list[i - prev_naddr],
-               sizeof(struct in_addr));
-        addr->sin_family = (sa_family_t)hostent->h_addrtype;
-        addr->sin_port = strhtons(r->port);
-
-        char output[INET_ADDRSTRLEN];
-        ares_inet_ntop(AF_INET, &addr->sin_addr, output, INET_ADDRSTRLEN);
-        gpr_log(GPR_DEBUG,
-                "c-ares resolver gets a AF_INET result: \n"
-                "  addr: %s\n  port: %s\n",
-                output, r->port);
+    (*lb_addresses)->num_addresses += i;
+    (*lb_addresses)->addresses =
+        gpr_realloc((*lb_addresses)->addresses,
+                    sizeof(grpc_lb_address) * (*lb_addresses)->num_addresses);
+    for (i = prev_naddr; i < (*lb_addresses)->num_addresses; i++) {
+      switch (hostent->h_addrtype) {
+        case AF_INET6: {
+          size_t addr_len = sizeof(struct sockaddr_in6);
+          struct sockaddr_in6 addr;
+          memset(&addr, 0, addr_len);
+          memcpy(&addr.sin6_addr, hostent->h_addr_list[i - prev_naddr],
+                 sizeof(struct in6_addr));
+          addr.sin6_family = (sa_family_t)hostent->h_addrtype;
+          addr.sin6_port = hr->port;
+          grpc_lb_addresses_set_address(
+              *lb_addresses, i, &addr, addr_len,
+              hr->is_balancer /* is_balancer */,
+              hr->is_balancer ? strdup(hr->host) : NULL /* balancer_name */,
+              NULL /* user_data */);
+          char output[INET6_ADDRSTRLEN];
+          ares_inet_ntop(AF_INET6, &addr.sin6_addr, output, INET6_ADDRSTRLEN);
+          gpr_log(GPR_DEBUG,
+                  "c-ares resolver gets a AF_INET6 result: \n"
+                  "  addr: %s\n  port: %d\n  sin6_scope_id: %d\n",
+                  output, ntohs(hr->port), addr.sin6_scope_id);
+          break;
+        }
+        case AF_INET: {
+          size_t addr_len = sizeof(struct sockaddr_in);
+          struct sockaddr_in addr;
+          memset(&addr, 0, addr_len);
+          memcpy(&addr.sin_addr, hostent->h_addr_list[i - prev_naddr],
+                 sizeof(struct in_addr));
+          addr.sin_family = (sa_family_t)hostent->h_addrtype;
+          addr.sin_port = hr->port;
+          grpc_lb_addresses_set_address(
+              *lb_addresses, i, &addr, addr_len,
+              hr->is_balancer /* is_balancer */,
+              hr->is_balancer ? strdup(hr->host) : NULL /* balancer_name */,
+              NULL /* user_data */);
+          char output[INET_ADDRSTRLEN];
+          ares_inet_ntop(AF_INET, &addr.sin_addr, output, INET_ADDRSTRLEN);
+          gpr_log(GPR_DEBUG,
+                  "c-ares resolver gets a AF_INET result: \n"
+                  "  addr: %s\n  port: %d\n",
+                  output, ntohs(hr->port));
+          break;
+        }
       }
     }
   } else if (!r->success) {
@@ -179,14 +218,58 @@ static void on_done_cb(void *arg, int status, int timeouts,
     }
   }
   gpr_mu_unlock(&r->mu);
-  grpc_ares_request_unref(NULL, r);
+  destroy_hostbyname_request(NULL, hr);
+}
+
+static void on_srv_query_done_cb(void *arg, int status, int timeouts,
+                                 unsigned char *abuf, int alen) {
+  grpc_ares_request *r = (grpc_ares_request *)arg;
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  gpr_log(GPR_DEBUG, "on_query_srv_done_cb");
+  if (status == ARES_SUCCESS) {
+    gpr_log(GPR_DEBUG, "on_query_srv_done_cb ARES_SUCCESS");
+    struct ares_srv_reply *reply;
+    const int parse_status = ares_parse_srv_reply(abuf, alen, &reply);
+    if (parse_status == ARES_SUCCESS) {
+      ares_channel *channel = grpc_ares_ev_driver_get_channel(r->ev_driver);
+      for (struct ares_srv_reply *srv_it = reply; srv_it != NULL;
+           srv_it = srv_it->next) {
+        if (grpc_ipv6_loopback_available()) {
+          grpc_ares_hostbyname_request *hr = create_hostbyname_request(
+              r, srv_it->host, srv_it->port, true /* is_balancer */);
+          ares_gethostbyname(*channel, hr->host, AF_INET6,
+                             on_hostbyname_done_cb, hr);
+        }
+        grpc_ares_hostbyname_request *hr = create_hostbyname_request(
+            r, srv_it->host, srv_it->port, true /* is_balancer */);
+        ares_gethostbyname(*channel, hr->host, AF_INET, on_hostbyname_done_cb,
+                           hr);
+        grpc_ares_ev_driver_start(&exec_ctx, r->ev_driver);
+      }
+    }
+    if (reply != NULL) {
+      ares_free_data(reply);
+    }
+  } else if (!r->success) {
+    char *error_msg;
+    gpr_asprintf(&error_msg, "C-ares status is not ARES_SUCCESS: %s",
+                 ares_strerror(status));
+    grpc_error *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
+    gpr_free(error_msg);
+    if (r->error == GRPC_ERROR_NONE) {
+      r->error = error;
+    } else {
+      r->error = grpc_error_add_child(error, r->error);
+    }
+  }
+  grpc_ares_request_unref(&exec_ctx, r);
+  grpc_exec_ctx_finish(&exec_ctx);
 }
 
-void grpc_dns_lookup_ares(grpc_exec_ctx *exec_ctx, const char *dns_server,
-                          const char *name, const char *default_port,
-                          grpc_pollset_set *interested_parties,
-                          grpc_closure *on_done,
-                          grpc_resolved_addresses **addrs) {
+static grpc_ares_request *grpc_dns_lookup_ares_impl(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *name,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **addrs, bool check_grpclb) {
   grpc_error *error = GRPC_ERROR_NONE;
   /* TODO(zyc): Enable tracing after #9603 is checked in */
   /* if (grpc_dns_trace) {
@@ -221,10 +304,7 @@ void grpc_dns_lookup_ares(grpc_exec_ctx *exec_ctx, const char *dns_server,
   gpr_mu_init(&r->mu);
   r->ev_driver = ev_driver;
   r->on_done = on_done;
-  r->addrs_out = addrs;
-  r->default_port = gpr_strdup(default_port);
-  r->port = port;
-  r->host = host;
+  r->lb_addrs_out = addrs;
   r->success = false;
   r->error = GRPC_ERROR_NONE;
   ares_channel *channel = grpc_ares_ev_driver_get_channel(r->ev_driver);
@@ -248,6 +328,7 @@ void grpc_dns_lookup_ares(grpc_exec_ctx *exec_ctx, const char *dns_server,
       error = grpc_error_set_str(
           GRPC_ERROR_CREATE_FROM_STATIC_STRING("cannot parse authority"),
           GRPC_ERROR_STR_TARGET_ADDRESS, grpc_slice_from_copied_string(name));
+      gpr_free(r);
       goto error_cleanup;
     }
     int status = ares_set_servers_ports(*channel, &r->dns_server_addr);
@@ -257,41 +338,55 @@ void grpc_dns_lookup_ares(grpc_exec_ctx *exec_ctx, const char *dns_server,
                    ares_strerror(status));
       error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
       gpr_free(error_msg);
+      gpr_free(r);
       goto error_cleanup;
     }
   }
   // An extra reference is put here to avoid destroying the request in
   // on_done_cb before calling grpc_ares_ev_driver_start.
-  gpr_ref_init(&r->pending_queries, 2);
+  gpr_ref_init(&r->pending_queries, 1);
   if (grpc_ipv6_loopback_available()) {
-    gpr_ref(&r->pending_queries);
-    ares_gethostbyname(*channel, r->host, AF_INET6, on_done_cb, r);
+    grpc_ares_hostbyname_request *hr = create_hostbyname_request(
+        r, host, strhtons(port), false /* is_balancer */);
+    ares_gethostbyname(*channel, hr->host, AF_INET6, on_hostbyname_done_cb, hr);
+  }
+  grpc_ares_hostbyname_request *hr = create_hostbyname_request(
+      r, host, strhtons(port), false /* is_balancer */);
+  ares_gethostbyname(*channel, hr->host, AF_INET, on_hostbyname_done_cb, hr);
+  if (check_grpclb) {
+    /* Query the SRV record */
+    grpc_ares_request_ref(r);
+    char *service_name;
+    gpr_asprintf(&service_name, "_grpclb._tcp.%s", host);
+    ares_query(*channel, service_name, ns_c_in, ns_t_srv, on_srv_query_done_cb,
+               r);
+    gpr_free(service_name);
   }
-  ares_gethostbyname(*channel, r->host, AF_INET, on_done_cb, r);
   /* TODO(zyc): Handle CNAME records here. */
   grpc_ares_ev_driver_start(exec_ctx, r->ev_driver);
   grpc_ares_request_unref(exec_ctx, r);
-  return;
+  gpr_free(host);
+  gpr_free(port);
+  return r;
 
 error_cleanup:
   grpc_closure_sched(exec_ctx, on_done, error);
   gpr_free(host);
   gpr_free(port);
+  return NULL;
 }
 
-void grpc_resolve_address_ares_impl(grpc_exec_ctx *exec_ctx, const char *name,
-                                    const char *default_port,
-                                    grpc_pollset_set *interested_parties,
-                                    grpc_closure *on_done,
-                                    grpc_resolved_addresses **addrs) {
-  grpc_dns_lookup_ares(exec_ctx, NULL /* dns_server */, name, default_port,
-                       interested_parties, on_done, addrs);
-}
+grpc_ares_request *(*grpc_dns_lookup_ares)(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *name,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **addrs,
+    bool check_grpclb) = grpc_dns_lookup_ares_impl;
 
-void (*grpc_resolve_address_ares)(
-    grpc_exec_ctx *exec_ctx, const char *name, const char *default_port,
-    grpc_pollset_set *interested_parties, grpc_closure *on_done,
-    grpc_resolved_addresses **addrs) = grpc_resolve_address_ares_impl;
+void grpc_cancel_ares_request(grpc_exec_ctx *exec_ctx, grpc_ares_request *r) {
+  if (grpc_dns_lookup_ares == grpc_dns_lookup_ares_impl) {
+    grpc_ares_ev_driver_shutdown(exec_ctx, r->ev_driver);
+  }
+}
 
 grpc_error *grpc_ares_init(void) {
   gpr_once_init(&g_basic_init, do_basic_init);
@@ -316,4 +411,66 @@ void grpc_ares_cleanup(void) {
   gpr_mu_unlock(&g_init_mu);
 }
 
+/*
+ * grpc_resolve_address_ares related structs and functions
+ */
+
+typedef struct grpc_resolve_address_ares_request {
+  /** the pointer to receive the resolved addresses */
+  grpc_resolved_addresses **addrs_out;
+  /** currently resolving lb addresses */
+  grpc_lb_addresses *lb_addrs;
+  /** closure to call when the resolve_address_ares request completes */
+  grpc_closure *on_resolve_address_done;
+  /** a closure wrapping on_dns_lookup_done_cb, which should be invoked when the
+      grpc_dns_lookup_ares operation is done. */
+  grpc_closure on_dns_lookup_done;
+} grpc_resolve_address_ares_request;
+
+static void on_dns_lookup_done_cb(grpc_exec_ctx *exec_ctx, void *arg,
+                                  grpc_error *error) {
+  grpc_resolve_address_ares_request *r =
+      (grpc_resolve_address_ares_request *)arg;
+  grpc_resolved_addresses **resolved_addresses = r->addrs_out;
+  if (r->lb_addrs == NULL || r->lb_addrs->num_addresses == 0) {
+    *resolved_addresses = NULL;
+  } else {
+    *resolved_addresses = gpr_zalloc(sizeof(grpc_resolved_addresses));
+    (*resolved_addresses)->naddrs = r->lb_addrs->num_addresses;
+    (*resolved_addresses)->addrs = gpr_zalloc(sizeof(grpc_resolved_address) *
+                                              (*resolved_addresses)->naddrs);
+    for (size_t i = 0; i < (*resolved_addresses)->naddrs; i++) {
+      GPR_ASSERT(!r->lb_addrs->addresses[i].is_balancer);
+      memcpy(&(*resolved_addresses)->addrs[i],
+             &r->lb_addrs->addresses[i].address, sizeof(grpc_resolved_address));
+    }
+  }
+  grpc_closure_sched(exec_ctx, r->on_resolve_address_done,
+                     GRPC_ERROR_REF(error));
+  grpc_lb_addresses_destroy(exec_ctx, r->lb_addrs);
+  gpr_free(r);
+}
+
+static void grpc_resolve_address_ares_impl(grpc_exec_ctx *exec_ctx,
+                                           const char *name,
+                                           const char *default_port,
+                                           grpc_pollset_set *interested_parties,
+                                           grpc_closure *on_done,
+                                           grpc_resolved_addresses **addrs) {
+  grpc_resolve_address_ares_request *r =
+      gpr_zalloc(sizeof(grpc_resolve_address_ares_request));
+  r->addrs_out = addrs;
+  r->on_resolve_address_done = on_done;
+  grpc_closure_init(&r->on_dns_lookup_done, on_dns_lookup_done_cb, r,
+                    grpc_schedule_on_exec_ctx);
+  grpc_dns_lookup_ares(exec_ctx, NULL /* dns_server */, name, default_port,
+                       interested_parties, &r->on_dns_lookup_done, &r->lb_addrs,
+                       false /* check_grpclb */);
+}
+
+void (*grpc_resolve_address_ares)(
+    grpc_exec_ctx *exec_ctx, const char *name, const char *default_port,
+    grpc_pollset_set *interested_parties, grpc_closure *on_done,
+    grpc_resolved_addresses **addrs) = grpc_resolve_address_ares_impl;
+
 #endif /* GRPC_ARES == 1 && !defined(GRPC_UV) */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
index 08efeea04a92ab0beb0992397ca8cfd7082ce329..5d2d6c993bd3ff8cdb16ebb6ea54f3682600dbc3 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h
@@ -19,11 +19,14 @@
 #ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_C_ARES_GRPC_ARES_WRAPPER_H
 #define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_DNS_C_ARES_GRPC_ARES_WRAPPER_H
 
+#include "src/core/ext/filters/client_channel/lb_policy_factory.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/iomgr/polling_entity.h"
 #include "src/core/lib/iomgr/resolve_address.h"
 
+typedef struct grpc_ares_request grpc_ares_request;
+
 /* Asynchronously resolve addr. Use \a default_port if a port isn't designated
    in addr, otherwise use the port in addr. grpc_ares_init() must be called at
    least once before this function. \a on_done may be called directly in this
@@ -36,11 +39,21 @@ extern void (*grpc_resolve_address_ares)(grpc_exec_ctx *exec_ctx,
                                          grpc_closure *on_done,
                                          grpc_resolved_addresses **addresses);
 
-void grpc_dns_lookup_ares(grpc_exec_ctx *exec_ctx, const char *dns_server,
-                          const char *addr, const char *default_port,
-                          grpc_pollset_set *interested_parties,
-                          grpc_closure *on_done,
-                          grpc_resolved_addresses **addresses);
+/* Asynchronously resolve addr. It will try to resolve grpclb SRV records in
+  addition to the normal address records. For normal address records, it uses
+  \a default_port if a port isn't designated in \a addr, otherwise it uses the
+  port in \a addr. grpc_ares_init() must be called at least once before this
+  function. \a on_done may be called directly in this function without being
+  scheduled with \a exec_ctx, it must not try to acquire locks that are being
+  held by the caller. */
+extern grpc_ares_request *(*grpc_dns_lookup_ares)(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *addr,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **addresses, bool check_grpclb);
+
+/* Cancel the pending grpc_ares_request \a request */
+void grpc_cancel_ares_request(grpc_exec_ctx *exec_ctx,
+                              grpc_ares_request *request);
 
 /* Initialize gRPC ares wrapper. Must be called at least once before
    grpc_resolve_address_ares(). */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c
new file mode 100644
index 0000000000000000000000000000000000000000..5c15e3bdf5ccdef67e9d9bc1da4b77997c564033
--- /dev/null
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c
@@ -0,0 +1,74 @@
+/*
+ *
+ * Copyright 2017, 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.
+ *
+ */
+
+#include <grpc/support/port_platform.h>
+#if GRPC_ARES != 1 || defined(GRPC_UV)
+
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+
+struct grpc_ares_request {
+  char val;
+};
+
+static grpc_ares_request *grpc_dns_lookup_ares_impl(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *name,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **addrs, bool check_grpclb) {
+  return NULL;
+}
+
+grpc_ares_request *(*grpc_dns_lookup_ares)(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *name,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **addrs,
+    bool check_grpclb) = grpc_dns_lookup_ares_impl;
+
+void grpc_cancel_ares_request(grpc_exec_ctx *exec_ctx, grpc_ares_request *r) {}
+
+grpc_error *grpc_ares_init(void) { return GRPC_ERROR_NONE; }
+
+void grpc_ares_cleanup(void) {}
+
+static void grpc_resolve_address_ares_impl(grpc_exec_ctx *exec_ctx,
+                                           const char *name,
+                                           const char *default_port,
+                                           grpc_pollset_set *interested_parties,
+                                           grpc_closure *on_done,
+                                           grpc_resolved_addresses **addrs) {}
+
+void (*grpc_resolve_address_ares)(
+    grpc_exec_ctx *exec_ctx, const char *name, const char *default_port,
+    grpc_pollset_set *interested_parties, grpc_closure *on_done,
+    grpc_resolved_addresses **addrs) = grpc_resolve_address_ares_impl;
+
+#endif /* GRPC_ARES != 1 || defined(GRPC_UV) */
diff --git a/src/core/lib/iomgr/ev_epollsig_linux.c b/src/core/lib/iomgr/ev_epollsig_linux.c
index 006262cd2756277d9e3cdb178ac87c5fc6faaccb..29a86f73ffb1a76c785ef722d68d833127eaa8e1 100644
--- a/src/core/lib/iomgr/ev_epollsig_linux.c
+++ b/src/core/lib/iomgr/ev_epollsig_linux.c
@@ -904,7 +904,10 @@ static void fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd,
        unhappy. */
     PI_UNREF(exec_ctx, unref_pi, "fd_orphan");
   }
-  GRPC_LOG_IF_ERROR("fd_orphan", GRPC_ERROR_REF(error));
+  if (error != GRPC_ERROR_NONE) {
+    const char *msg = grpc_error_string(error);
+    gpr_log(GPR_DEBUG, "fd_orphan: %s", msg);
+  }
   GRPC_ERROR_UNREF(error);
 }
 
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index f11d8835176902fb2b453f34a09167e100043b45..48782174a7c924b8768872ed8a3b9389df5a9696 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -285,6 +285,7 @@ CORE_SOURCE_FILES = [
   'src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c',
   'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c',
   'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c',
+  'src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c',
   'src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c',
   'src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c',
   'src/core/ext/filters/load_reporting/load_reporting.c',
diff --git a/test/core/client_channel/resolvers/dns_resolver_connectivity_test.c b/test/core/client_channel/resolvers/dns_resolver_connectivity_test.c
index 03753da88a15acba654040a9dded1db1a888deae..43dc7e9084b9f71f35f7cbbcb3503433416c5cef 100644
--- a/test/core/client_channel/resolvers/dns_resolver_connectivity_test.c
+++ b/test/core/client_channel/resolvers/dns_resolver_connectivity_test.c
@@ -21,7 +21,9 @@
 #include <grpc/grpc.h>
 #include <grpc/support/alloc.h>
 
+#include "src/core/ext/filters/client_channel/lb_policy_factory.h"
 #include "src/core/ext/filters/client_channel/resolver.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/iomgr/combiner.h"
@@ -55,6 +57,26 @@ static void my_resolve_address(grpc_exec_ctx *exec_ctx, const char *addr,
   grpc_closure_sched(exec_ctx, on_done, error);
 }
 
+static grpc_ares_request *my_dns_lookup_ares(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *addr,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **lb_addrs, bool check_grpclb) {
+  gpr_mu_lock(&g_mu);
+  GPR_ASSERT(0 == strcmp("test", addr));
+  grpc_error *error = GRPC_ERROR_NONE;
+  if (g_fail_resolution) {
+    g_fail_resolution = false;
+    gpr_mu_unlock(&g_mu);
+    error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Forced Failure");
+  } else {
+    gpr_mu_unlock(&g_mu);
+    *lb_addrs = grpc_lb_addresses_create(1, NULL);
+    grpc_lb_addresses_set_address(*lb_addrs, 0, NULL, 0, false, NULL, NULL);
+  }
+  grpc_closure_sched(exec_ctx, on_done, error);
+  return NULL;
+}
+
 static grpc_resolver *create_resolver(grpc_exec_ctx *exec_ctx,
                                       const char *name) {
   grpc_resolver_factory *factory = grpc_resolver_factory_lookup("dns");
@@ -124,6 +146,7 @@ int main(int argc, char **argv) {
   gpr_mu_init(&g_mu);
   g_combiner = grpc_combiner_create();
   grpc_resolve_address = my_resolve_address;
+  grpc_dns_lookup_ares = my_dns_lookup_ares;
   grpc_channel_args *result = (grpc_channel_args *)1;
 
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
diff --git a/test/core/client_channel/resolvers/dns_resolver_test.c b/test/core/client_channel/resolvers/dns_resolver_test.c
index e3451d0682734832c06c92813779c1c6da149db9..a14926f17307dd9efe07830f1e3097cc4bd2ce5f 100644
--- a/test/core/client_channel/resolvers/dns_resolver_test.c
+++ b/test/core/client_channel/resolvers/dns_resolver_test.c
@@ -20,6 +20,7 @@
 
 #include <grpc/support/log.h>
 
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "test/core/util/test_config.h"
@@ -73,7 +74,11 @@ int main(int argc, char **argv) {
   test_succeeds(dns, "dns:10.2.1.1");
   test_succeeds(dns, "dns:10.2.1.1:1234");
   test_succeeds(dns, "ipv4:www.google.com");
-  test_fails(dns, "ipv4://8.8.8.8/8.8.8.8:8888");
+  if (grpc_resolve_address == grpc_resolve_address_ares) {
+    test_succeeds(dns, "ipv4://8.8.8.8/8.8.8.8:8888");
+  } else {
+    test_fails(dns, "ipv4://8.8.8.8/8.8.8.8:8888");
+  }
 
   grpc_resolver_factory_unref(dns);
   {
diff --git a/test/core/end2end/fuzzers/api_fuzzer.c b/test/core/end2end/fuzzers/api_fuzzer.c
index 59c82837c721d524b3903c14c3778e872dfe7e46..1e9e7e6194322c941e8df5655f237dbf24eb8777 100644
--- a/test/core/end2end/fuzzers/api_fuzzer.c
+++ b/test/core/end2end/fuzzers/api_fuzzer.c
@@ -24,6 +24,8 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 
+#include "src/core/ext/filters/client_channel/lb_policy_factory.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/iomgr/executor.h"
@@ -363,6 +365,7 @@ typedef struct addr_req {
   char *addr;
   grpc_closure *on_done;
   grpc_resolved_addresses **addrs;
+  grpc_lb_addresses **lb_addrs;
 } addr_req;
 
 static void finish_resolve(grpc_exec_ctx *exec_ctx, void *arg,
@@ -370,11 +373,17 @@ static void finish_resolve(grpc_exec_ctx *exec_ctx, void *arg,
   addr_req *r = arg;
 
   if (error == GRPC_ERROR_NONE && 0 == strcmp(r->addr, "server")) {
-    grpc_resolved_addresses *addrs = gpr_malloc(sizeof(*addrs));
-    addrs->naddrs = 1;
-    addrs->addrs = gpr_malloc(sizeof(*addrs->addrs));
-    addrs->addrs[0].len = 0;
-    *r->addrs = addrs;
+    if (r->addrs != NULL) {
+      grpc_resolved_addresses *addrs = gpr_malloc(sizeof(*addrs));
+      addrs->naddrs = 1;
+      addrs->addrs = gpr_malloc(sizeof(*addrs->addrs));
+      addrs->addrs[0].len = 0;
+      *r->addrs = addrs;
+    } else if (r->lb_addrs != NULL) {
+      grpc_lb_addresses *lb_addrs = grpc_lb_addresses_create(1, NULL);
+      grpc_lb_addresses_set_address(lb_addrs, 0, NULL, 0, NULL, NULL, NULL);
+      *r->lb_addrs = lb_addrs;
+    }
     grpc_closure_sched(exec_ctx, r->on_done, GRPC_ERROR_NONE);
   } else {
     grpc_closure_sched(exec_ctx, r->on_done,
@@ -395,11 +404,29 @@ void my_resolve_address(grpc_exec_ctx *exec_ctx, const char *addr,
   r->addr = gpr_strdup(addr);
   r->on_done = on_done;
   r->addrs = addresses;
+  r->lb_addrs = NULL;
+  grpc_timer_init(
+      exec_ctx, &r->timer, gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+                                        gpr_time_from_seconds(1, GPR_TIMESPAN)),
+      grpc_closure_create(finish_resolve, r, grpc_schedule_on_exec_ctx),
+      gpr_now(GPR_CLOCK_MONOTONIC));
+}
+
+grpc_ares_request *my_dns_lookup_ares(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *addr,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **lb_addrs, bool check_grpclb) {
+  addr_req *r = gpr_malloc(sizeof(*r));
+  r->addr = gpr_strdup(addr);
+  r->on_done = on_done;
+  r->addrs = NULL;
+  r->lb_addrs = lb_addrs;
   grpc_timer_init(
       exec_ctx, &r->timer, gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
                                         gpr_time_from_seconds(1, GPR_TIMESPAN)),
       grpc_closure_create(finish_resolve, r, grpc_schedule_on_exec_ctx),
       gpr_now(GPR_CLOCK_MONOTONIC));
+  return NULL;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -716,6 +743,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
     grpc_exec_ctx_finish(&exec_ctx);
   }
   grpc_resolve_address = my_resolve_address;
+  grpc_dns_lookup_ares = my_dns_lookup_ares;
 
   GPR_ASSERT(g_channel == NULL);
   GPR_ASSERT(g_server == NULL);
diff --git a/test/core/end2end/goaway_server_test.c b/test/core/end2end/goaway_server_test.c
index 49378665a03593beff78517aeedb27ae127b4e80..91a377e6b9b3fe679ca4581822616479cc9ba31e 100644
--- a/test/core/end2end/goaway_server_test.c
+++ b/test/core/end2end/goaway_server_test.c
@@ -27,6 +27,8 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 #include <string.h>
+#include "src/core/ext/filters/client_channel/lb_policy_factory.h"
+#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/lib/iomgr/resolve_address.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 #include "test/core/end2end/cq_verifier.h"
@@ -43,6 +45,11 @@ static void (*iomgr_resolve_address)(grpc_exec_ctx *exec_ctx, const char *addr,
                                      grpc_closure *on_done,
                                      grpc_resolved_addresses **addresses);
 
+static grpc_ares_request *(*iomgr_dns_lookup_ares)(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *addr,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **addresses, bool check_grpclb);
+
 static void set_resolve_port(int port) {
   gpr_mu_lock(&g_mu);
   g_resolve_port = port;
@@ -80,6 +87,36 @@ static void my_resolve_address(grpc_exec_ctx *exec_ctx, const char *addr,
   grpc_closure_sched(exec_ctx, on_done, error);
 }
 
+static grpc_ares_request *my_dns_lookup_ares(
+    grpc_exec_ctx *exec_ctx, const char *dns_server, const char *addr,
+    const char *default_port, grpc_pollset_set *interested_parties,
+    grpc_closure *on_done, grpc_lb_addresses **lb_addrs, bool check_grpclb) {
+  if (0 != strcmp(addr, "test")) {
+    return iomgr_dns_lookup_ares(exec_ctx, dns_server, addr, default_port,
+                                 interested_parties, on_done, lb_addrs,
+                                 check_grpclb);
+  }
+
+  grpc_error *error = GRPC_ERROR_NONE;
+  gpr_mu_lock(&g_mu);
+  if (g_resolve_port < 0) {
+    gpr_mu_unlock(&g_mu);
+    error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Forced Failure");
+  } else {
+    *lb_addrs = grpc_lb_addresses_create(1, NULL);
+    struct sockaddr_in *sa = gpr_zalloc(sizeof(struct sockaddr_in));
+    sa->sin_family = AF_INET;
+    sa->sin_addr.s_addr = htonl(0x7f000001);
+    sa->sin_port = htons((uint16_t)g_resolve_port);
+    grpc_lb_addresses_set_address(*lb_addrs, 0, sa, sizeof(*sa), false, NULL,
+                                  NULL);
+    gpr_free(sa);
+    gpr_mu_unlock(&g_mu);
+  }
+  grpc_closure_sched(exec_ctx, on_done, error);
+  return NULL;
+}
+
 int main(int argc, char **argv) {
   grpc_completion_queue *cq;
   cq_verifier *cqv;
@@ -91,7 +128,9 @@ int main(int argc, char **argv) {
   gpr_mu_init(&g_mu);
   grpc_init();
   iomgr_resolve_address = grpc_resolve_address;
+  iomgr_dns_lookup_ares = grpc_dns_lookup_ares;
   grpc_resolve_address = my_resolve_address;
+  grpc_dns_lookup_ares = my_dns_lookup_ares;
 
   int was_cancelled1;
   int was_cancelled2;
diff --git a/test/core/util/port_server_client.c b/test/core/util/port_server_client.c
index 89e0aca79cd940c2c598b340171c64b902e5214f..e5a2ecd5171bbebdbf5ea9827988df1055403b0d 100644
--- a/test/core/util/port_server_client.c
+++ b/test/core/util/port_server_client.c
@@ -92,6 +92,7 @@ void grpc_free_port_using_server(int port) {
                                        grpc_schedule_on_exec_ctx),
                    &rsp);
   grpc_resource_quota_unref_internal(&exec_ctx, resource_quota);
+  grpc_exec_ctx_flush(&exec_ctx);
   gpr_mu_lock(pr.mu);
   while (!pr.done) {
     grpc_pollset_worker *worker = NULL;
@@ -224,6 +225,7 @@ int grpc_pick_port_using_server(void) {
       grpc_closure_create(got_port_from_server, &pr, grpc_schedule_on_exec_ctx),
       &pr.response);
   grpc_resource_quota_unref_internal(&exec_ctx, resource_quota);
+  grpc_exec_ctx_flush(&exec_ctx);
   gpr_mu_lock(pr.mu);
   while (pr.port == -1) {
     grpc_pollset_worker *worker = NULL;
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 70e332970fcb1b9ac822feb10f8c9131fc50b215..766c20f59b75f5b5628ddff8e4a9ec51cc27250b 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -948,6 +948,7 @@ src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c \
 src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h \
+src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c \
 src/core/ext/filters/client_channel/resolver/dns/native/README.md \
 src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c \
 src/core/ext/filters/client_channel/resolver/fake/fake_resolver.c \
diff --git a/tools/jenkins/run_performance.sh b/tools/jenkins/run_performance.sh
index fc61b3b90047954d35b01902713c098d9f5d32ed..177b076650b92611ab8917aa6fb7d81de3c187f8 100755
--- a/tools/jenkins/run_performance.sh
+++ b/tools/jenkins/run_performance.sh
@@ -23,4 +23,4 @@ BENCHMARKS_TO_RUN="bm_fullstack_unary_ping_pong bm_fullstack_streaming_ping_pong
 cd $(dirname $0)/../..
 
 tools/run_tests/start_port_server.py
-tools/profiling/microbenchmarks/bm_diff.py -d origin/$ghprbTargetBranch -b $BENCHMARKS_TO_RUN
+tools/profiling/microbenchmarks/bm_diff/bm_main.py -d origin/$ghprbTargetBranch -b $BENCHMARKS_TO_RUN
diff --git a/tools/profiling/microbenchmarks/README.md b/tools/profiling/microbenchmarks/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..035888ee188fbc97e4254b85c7a95708f0fca4d8
--- /dev/null
+++ b/tools/profiling/microbenchmarks/README.md
@@ -0,0 +1,4 @@
+Microbenchmarks
+====
+
+This directory contains helper scripts for the microbenchmark suites.
diff --git a/tools/profiling/microbenchmarks/bm_diff.py b/tools/profiling/microbenchmarks/bm_diff.py
deleted file mode 100755
index 3de5c663ce4604bbdfdc38cbc65e2e99f2d3eef0..0000000000000000000000000000000000000000
--- a/tools/profiling/microbenchmarks/bm_diff.py
+++ /dev/null
@@ -1,244 +0,0 @@
-#!/usr/bin/env python2.7
-# Copyright 2017 gRPC authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import sys
-import json
-import bm_json
-import tabulate
-import argparse
-from scipy import stats
-import subprocess
-import multiprocessing
-import collections
-import pipes
-import os
-sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..', '..', 'run_tests', 'python_utils'))
-import comment_on_pr
-import jobset
-import itertools
-import speedup
-import random
-import shutil
-import errno
-
-_INTERESTING = (
-  'cpu_time',
-  'real_time',
-  'locks_per_iteration',
-  'allocs_per_iteration',
-  'writes_per_iteration',
-  'atm_cas_per_iteration',
-  'atm_add_per_iteration',
-  'cli_transport_stalls_per_iteration',
-  'cli_stream_stalls_per_iteration',
-  'svr_transport_stalls_per_iteration',
-  'svr_stream_stalls_per_iteration'
-  'nows_per_iteration',
-)
-
-def changed_ratio(n, o):
-  if float(o) <= .0001: o = 0
-  if float(n) <= .0001: n = 0
-  if o == 0 and n == 0: return 0
-  if o == 0: return 100
-  return (float(n)-float(o))/float(o)
-
-def median(ary):
-  ary = sorted(ary)
-  n = len(ary)
-  if n%2 == 0:
-    return (ary[n/2] + ary[n/2+1]) / 2.0
-  else:
-    return ary[n/2]
-
-def min_change(pct):
-  return lambda n, o: abs(changed_ratio(n,o)) > pct/100.0
-
-_AVAILABLE_BENCHMARK_TESTS = ['bm_fullstack_unary_ping_pong',
-                              'bm_fullstack_streaming_ping_pong',
-                              'bm_fullstack_streaming_pump',
-                              'bm_closure',
-                              'bm_cq',
-                              'bm_call_create',
-                              'bm_error',
-                              'bm_chttp2_hpack',
-                              'bm_chttp2_transport',
-                              'bm_pollset',
-                              'bm_metadata',
-                              'bm_fullstack_trickle']
-
-argp = argparse.ArgumentParser(description='Perform diff on microbenchmarks')
-argp.add_argument('-t', '--track',
-                  choices=sorted(_INTERESTING),
-                  nargs='+',
-                  default=sorted(_INTERESTING),
-                  help='Which metrics to track')
-argp.add_argument('-b', '--benchmarks', nargs='+', choices=_AVAILABLE_BENCHMARK_TESTS, default=['bm_cq'])
-argp.add_argument('-d', '--diff_base', type=str)
-argp.add_argument('-r', '--repetitions', type=int, default=1)
-argp.add_argument('-l', '--loops', type=int, default=20)
-argp.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count())
-args = argp.parse_args()
-
-assert args.diff_base
-
-def avg(lst):
-  sum = 0.0
-  n = 0.0
-  for el in lst:
-    sum += el
-    n += 1
-  return sum / n
-
-def make_cmd(cfg):
-  return ['make'] + args.benchmarks + [
-      'CONFIG=%s' % cfg, '-j', '%d' % args.jobs]
-
-def build(dest):
-  shutil.rmtree('bm_diff_%s' % dest, ignore_errors=True)
-  subprocess.check_call(['git', 'submodule', 'update'])
-  try:
-    subprocess.check_call(make_cmd('opt'))
-    subprocess.check_call(make_cmd('counters'))
-  except subprocess.CalledProcessError, e:
-    subprocess.check_call(['make', 'clean'])
-    subprocess.check_call(make_cmd('opt'))
-    subprocess.check_call(make_cmd('counters'))
-  os.rename('bins', 'bm_diff_%s' % dest)
-
-def collect1(bm, cfg, ver, idx):
-  cmd = ['bm_diff_%s/%s/%s' % (ver, cfg, bm),
-         '--benchmark_out=%s.%s.%s.%d.json' % (bm, cfg, ver, idx),
-         '--benchmark_out_format=json',
-         '--benchmark_repetitions=%d' % (args.repetitions)
-         ]
-  return jobset.JobSpec(cmd, shortname='%s %s %s %d/%d' % (bm, cfg, ver, idx+1, args.loops),
-                             verbose_success=True, timeout_seconds=None)
-
-build('new')
-
-where_am_i = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).strip()
-subprocess.check_call(['git', 'checkout', args.diff_base])
-try:
-  build('old')
-finally:
-  subprocess.check_call(['git', 'checkout', where_am_i])
-  subprocess.check_call(['git', 'submodule', 'update'])
-
-jobs = []
-for loop in range(0, args.loops):
-  jobs.extend(x for x in itertools.chain(
-    (collect1(bm, 'opt', 'new', loop) for bm in args.benchmarks),
-    (collect1(bm, 'counters', 'new', loop) for bm in args.benchmarks),
-    (collect1(bm, 'opt', 'old', loop) for bm in args.benchmarks),
-    (collect1(bm, 'counters', 'old', loop) for bm in args.benchmarks),
-  ))
-random.shuffle(jobs, random.SystemRandom().random)
-
-jobset.run(jobs, maxjobs=args.jobs)
-
-class Benchmark:
-
-  def __init__(self):
-    self.samples = {
-      True: collections.defaultdict(list),
-      False: collections.defaultdict(list)
-    }
-    self.final = {}
-
-  def add_sample(self, data, new):
-    for f in args.track:
-      if f in data:
-        self.samples[new][f].append(float(data[f]))
-
-  def process(self):
-    for f in sorted(args.track):
-      new = self.samples[True][f]
-      old = self.samples[False][f]
-      if not new or not old: continue
-      mdn_diff = abs(median(new) - median(old))
-      print '%s: new=%r old=%r mdn_diff=%r' % (f, new, old, mdn_diff)
-      s = speedup.speedup(new, old)
-      if abs(s) > 3 and mdn_diff > 0.5:
-        self.final[f] = '%+d%%' % s
-    return self.final.keys()
-
-  def skip(self):
-    return not self.final
-
-  def row(self, flds):
-    return [self.final[f] if f in self.final else '' for f in flds]
-
-
-def eintr_be_gone(fn):
-  """Run fn until it doesn't stop because of EINTR"""
-  while True:
-    try:
-      return fn()
-    except IOError, e:
-      if e.errno != errno.EINTR:
-        raise
-
-
-def read_json(filename):
-  try:
-    with open(filename) as f: return json.loads(f.read())
-  except ValueError, e:
-    return None
-
-
-def finalize():
-  benchmarks = collections.defaultdict(Benchmark)
-
-  for bm in args.benchmarks:
-    for loop in range(0, args.loops):
-      js_new_ctr = read_json('%s.counters.new.%d.json' % (bm, loop))
-      js_new_opt = read_json('%s.opt.new.%d.json' % (bm, loop))
-      js_old_ctr = read_json('%s.counters.old.%d.json' % (bm, loop))
-      js_old_opt = read_json('%s.opt.old.%d.json' % (bm, loop))
-
-      if js_new_ctr:
-        for row in bm_json.expand_json(js_new_ctr, js_new_opt):
-          print row
-          name = row['cpp_name']
-          if name.endswith('_mean') or name.endswith('_stddev'): continue
-          benchmarks[name].add_sample(row, True)
-      if js_old_ctr:
-        for row in bm_json.expand_json(js_old_ctr, js_old_opt):
-          print row
-          name = row['cpp_name']
-          if name.endswith('_mean') or name.endswith('_stddev'): continue
-          benchmarks[name].add_sample(row, False)
-
-  really_interesting = set()
-  for name, bm in benchmarks.items():
-    print name
-    really_interesting.update(bm.process())
-  fields = [f for f in args.track if f in really_interesting]
-
-  headers = ['Benchmark'] + fields
-  rows = []
-  for name in sorted(benchmarks.keys()):
-    if benchmarks[name].skip(): continue
-    rows.append([name] + benchmarks[name].row(fields))
-  if rows:
-    text = 'Performance differences noted:\n' + tabulate.tabulate(rows, headers=headers, floatfmt='+.2f')
-  else:
-    text = 'No significant performance differences'
-  print text
-  comment_on_pr.comment_on_pr('```\n%s\n```' % text)
-
-
-eintr_be_gone(finalize)
diff --git a/tools/profiling/microbenchmarks/bm_diff/README.md b/tools/profiling/microbenchmarks/bm_diff/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..caa477022996a989ad886aa18f30ea10fcc6e5f6
--- /dev/null
+++ b/tools/profiling/microbenchmarks/bm_diff/README.md
@@ -0,0 +1,116 @@
+The bm_diff Family
+====
+
+This family of python scripts can be incredibly useful for fast iteration over
+different performance tweaks. The tools allow you to save performance data from
+a baseline commit, then quickly compare data from your working branch to that
+baseline data to see if you have made any performance wins.
+
+The tools operate with three concrete steps, which can be invoked separately,
+or all together via the driver script, bm_main.py. This readme will describe 
+the typical workflow for these scripts, then it will include sections on the
+details of every script for advanced usage.
+
+## Normal Workflow
+
+Let's say you are working on a performance optimization for grpc_error. You have
+made some significant changes and want to see some data. From your branch, run
+(ensure everything is committed first):
+
+`tools/profiling/microbenchmarks/bm_diff/bm_main.py -b bm_error -l 5 -d master`
+
+This will build the `bm_error` binary on your branch, and then it will checkout 
+master and build it there too. It will then run these benchmarks 5 times each. 
+Lastly it will compute the statistically significant performance differences 
+between the two branches. This should show the nice performance wins your 
+changes have made.
+
+If you have already invoked bm_main with `-d master`, you should instead use 
+`-o` for subsequent runs. This allows the script to skip re-building and 
+re-running the unchanged master branch. For example:
+
+`tools/profiling/microbenchmarks/bm_diff/bm_main.py -b bm_error -l 5 -o`
+
+This will only build and run `bm_error` on your branch. It will then compare
+the output to the saved runs from master.
+
+## Advanced Workflow
+
+If you have a deeper knowledge of these scripts, you can use them to do more
+fine tuned benchmark comparisons. For example, you could build, run, and save
+the benchmark output from two different base branches. Then you could diff both
+of these baselines against your working branch to see how the different metrics
+change. The rest of this doc goes over the details of what each of the
+individual modules accomplishes.
+
+## bm_build.py
+
+This scrips builds the benchmarks. It takes in a name parameter, and will
+store the binaries based on that. Both `opt` and `counter` configurations
+will be used. The `opt` is used to get cpu_time and real_time, and the
+`counters` build is used to track other metrics like allocs, atomic adds,
+etc etc etc.
+
+For example, if you were to invoke (we assume everything is run from the 
+root of the repo):
+
+`tools/profiling/microbenchmarks/bm_diff/bm_build.py -b bm_error -n baseline`
+
+then the microbenchmark binaries will show up under 
+`bm_diff_baseline/{opt,counters}/bm_error`
+
+## bm_run.py
+
+This script runs the benchmarks. It takes a name parameter that must match the
+name that was passed to `bm_build.py`. The script then runs the benchmark
+multiple times (default is 20, can be toggled via the loops parameter). The
+output is saved as `<benchmark name>.<config>.<name>.<loop idx>.json`
+
+For example, if you were to run:
+
+`tools/profiling/microbenchmarks/bm_diff/bm_run.py -b bm_error -b baseline -l 5`
+
+Then an example output file would be `bm_error.opt.baseline.0.json`
+
+## bm_diff.py
+
+This script takes in the output from two benchmark runs, computes the diff
+between them, and prints any significant improvements or regressions. It takes
+in two name parameters, old and new. These must have previously been built and
+run.
+
+For example, assuming you had already built and run a 'baseline' microbenchmark
+from master, and then you also built and ran a 'current' microbenchmark from
+the branch you were working on, you could invoke:
+
+`tools/profiling/microbenchmarks/bm_diff/bm_diff.py -b bm_error -o baseline -n current -l 5`
+
+This would output the percent difference between your branch and master.
+
+## bm_main.py
+
+This is the driver script. It uses the previous three modules and does
+everything for you. You pass in the benchmarks to be run, the number of loops,
+number of CPUs to use, and the commit to compare to. Then the script will:
+* Build the benchmarks at head, then checkout the branch to compare to and
+  build the benchmarks there
+* Run both sets of microbenchmarks
+* Run bm_diff.py to compare the two, outputs the difference.
+
+For example, one might run:
+
+`tools/profiling/microbenchmarks/bm_diff/bm_main.py -b bm_error -l 5 -d master`
+
+This would compare the current branch's error benchmarks to master.
+
+This script is invoked by our infrastructure on every PR to protect against
+regressions and demonstrate performance wins.
+
+However, if you are iterating over different performance tweaks quickly, it is
+unnecessary to build and run the baseline commit every time. That is why we
+provide a different flag in case you are sure that the baseline benchmark has
+already been built and run. In that case use the --old flag to pass in the name
+of the baseline. This will only build and run the current branch. For example:
+
+`tools/profiling/microbenchmarks/bm_diff/bm_main.py -b bm_error -l 5 -o old`
+
diff --git a/tools/profiling/microbenchmarks/bm_diff/bm_build.py b/tools/profiling/microbenchmarks/bm_diff/bm_build.py
new file mode 100755
index 0000000000000000000000000000000000000000..650ccdc2b21601d5a6a6545e4b145ddd40f0bb83
--- /dev/null
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_build.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Python utility to build opt and counters benchmarks """
+
+import bm_constants
+
+import argparse
+import subprocess
+import multiprocessing
+import os
+import shutil
+
+
+def _args():
+  argp = argparse.ArgumentParser(description='Builds microbenchmarks')
+  argp.add_argument(
+    '-b',
+    '--benchmarks',
+    nargs='+',
+    choices=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    default=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    help='Which benchmarks to build')
+  argp.add_argument(
+    '-j',
+    '--jobs',
+    type=int,
+    default=multiprocessing.cpu_count(),
+    help='How many CPUs to dedicate to this task')
+  argp.add_argument(
+    '-n',
+    '--name',
+    type=str,
+    help='Unique name of this build. To be used as a handle to pass to the other bm* scripts'
+  )
+  args = argp.parse_args()
+  assert args.name
+  return args
+
+
+def _make_cmd(cfg, benchmarks, jobs):
+  return ['make'] + benchmarks + ['CONFIG=%s' % cfg, '-j', '%d' % jobs]
+
+
+def build(name, benchmarks, jobs):
+  shutil.rmtree('bm_diff_%s' % name, ignore_errors=True)
+  subprocess.check_call(['git', 'submodule', 'update'])
+  try:
+    subprocess.check_call(_make_cmd('opt', benchmarks, jobs))
+    subprocess.check_call(_make_cmd('counters', benchmarks, jobs))
+  except subprocess.CalledProcessError, e:
+    subprocess.check_call(['make', 'clean'])
+    subprocess.check_call(_make_cmd('opt', benchmarks, jobs))
+    subprocess.check_call(_make_cmd('counters', benchmarks, jobs))
+  os.rename(
+    'bins',
+    'bm_diff_%s' % name,)
+
+
+if __name__ == '__main__':
+  args = _args()
+  build(args.name, args.benchmarks, args.jobs)
diff --git a/tools/profiling/microbenchmarks/bm_diff/bm_constants.py b/tools/profiling/microbenchmarks/bm_diff/bm_constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..2bbd987b20fcad226cac0a1587d0ea1ca63e2a34
--- /dev/null
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_constants.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Configurable constants for the bm_*.py family """
+
+_AVAILABLE_BENCHMARK_TESTS = [
+  'bm_fullstack_unary_ping_pong', 'bm_fullstack_streaming_ping_pong',
+  'bm_fullstack_streaming_pump', 'bm_closure', 'bm_cq', 'bm_call_create',
+  'bm_error', 'bm_chttp2_hpack', 'bm_chttp2_transport', 'bm_pollset',
+  'bm_metadata', 'bm_fullstack_trickle'
+]
+
+_INTERESTING = ('cpu_time', 'real_time', 'locks_per_iteration',
+        'allocs_per_iteration', 'writes_per_iteration',
+        'atm_cas_per_iteration', 'atm_add_per_iteration',
+        'nows_per_iteration',)
diff --git a/tools/profiling/microbenchmarks/bm_diff/bm_diff.py b/tools/profiling/microbenchmarks/bm_diff/bm_diff.py
new file mode 100755
index 0000000000000000000000000000000000000000..881e157ccd5fc60709896f67703b6b72dc33f484
--- /dev/null
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_diff.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" Computes the diff between two bm runs and outputs significant results """
+
+import bm_constants
+import bm_speedup
+
+import sys
+import os
+
+sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..'))
+import bm_json
+
+import json
+import tabulate
+import argparse
+import collections
+import subprocess
+
+verbose = False
+
+
+def _median(ary):
+  assert (len(ary))
+  ary = sorted(ary)
+  n = len(ary)
+  if n % 2 == 0:
+    return (ary[(n - 1) / 2] + ary[(n - 1) / 2 + 1]) / 2.0
+  else:
+    return ary[n / 2]
+
+
+def _args():
+  argp = argparse.ArgumentParser(
+    description='Perform diff on microbenchmarks')
+  argp.add_argument(
+    '-t',
+    '--track',
+    choices=sorted(bm_constants._INTERESTING),
+    nargs='+',
+    default=sorted(bm_constants._INTERESTING),
+    help='Which metrics to track')
+  argp.add_argument(
+    '-b',
+    '--benchmarks',
+    nargs='+',
+    choices=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    default=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    help='Which benchmarks to run')
+  argp.add_argument(
+    '-l',
+    '--loops',
+    type=int,
+    default=20,
+    help='Number of times to loops the benchmarks. Must match what was passed to bm_run.py'
+  )
+  argp.add_argument('-n', '--new', type=str, help='New benchmark name')
+  argp.add_argument('-o', '--old', type=str, help='Old benchmark name')
+  argp.add_argument(
+    '-v', '--verbose', type=bool, help='Print details of before/after')
+  args = argp.parse_args()
+  global verbose
+  if args.verbose: verbose = True
+  assert args.new
+  assert args.old
+  return args
+
+
+def _maybe_print(str):
+  if verbose: print str
+
+
+class Benchmark:
+
+  def __init__(self):
+    self.samples = {
+      True: collections.defaultdict(list),
+      False: collections.defaultdict(list)
+    }
+    self.final = {}
+
+  def add_sample(self, track, data, new):
+    for f in track:
+      if f in data:
+        self.samples[new][f].append(float(data[f]))
+
+  def process(self, track, new_name, old_name):
+    for f in sorted(track):
+      new = self.samples[True][f]
+      old = self.samples[False][f]
+      if not new or not old: continue
+      mdn_diff = abs(_median(new) - _median(old))
+      _maybe_print('%s: %s=%r %s=%r mdn_diff=%r' %
+             (f, new_name, new, old_name, old, mdn_diff))
+      s = bm_speedup.speedup(new, old)
+      if abs(s) > 3 and mdn_diff > 0.5:
+        self.final[f] = '%+d%%' % s
+    return self.final.keys()
+
+  def skip(self):
+    return not self.final
+
+  def row(self, flds):
+    return [self.final[f] if f in self.final else '' for f in flds]
+
+
+def _read_json(filename, badjson_files, nonexistant_files):
+  stripped = ".".join(filename.split(".")[:-2])
+  try:
+    with open(filename) as f:
+      return json.loads(f.read())
+  except IOError, e:
+    if stripped in nonexistant_files:
+      nonexistant_files[stripped] += 1
+    else:
+      nonexistant_files[stripped] = 1
+    return None
+  except ValueError, e:
+    if stripped in badjson_files:
+      badjson_files[stripped] += 1
+    else:
+      badjson_files[stripped] = 1
+    return None
+
+
+def diff(bms, loops, track, old, new):
+  benchmarks = collections.defaultdict(Benchmark)
+
+  badjson_files = {}
+  nonexistant_files = {}
+  for bm in bms:
+    for loop in range(0, loops):
+      for line in subprocess.check_output(
+        ['bm_diff_%s/opt/%s' % (old, bm),
+         '--benchmark_list_tests']).splitlines():
+        stripped_line = line.strip().replace("/", "_").replace(
+          "<", "_").replace(">", "_").replace(", ", "_")
+        js_new_ctr = _read_json('%s.%s.counters.%s.%d.json' %
+                    (bm, stripped_line, new, loop),
+                    badjson_files, nonexistant_files)
+        js_new_opt = _read_json('%s.%s.opt.%s.%d.json' %
+                    (bm, stripped_line, new, loop),
+                    badjson_files, nonexistant_files)
+        js_old_ctr = _read_json('%s.%s.counters.%s.%d.json' %
+                    (bm, stripped_line, old, loop),
+                    badjson_files, nonexistant_files)
+        js_old_opt = _read_json('%s.%s.opt.%s.%d.json' %
+                    (bm, stripped_line, old, loop),
+                    badjson_files, nonexistant_files)
+
+        if js_new_ctr:
+          for row in bm_json.expand_json(js_new_ctr, js_new_opt):
+            name = row['cpp_name']
+            if name.endswith('_mean') or name.endswith('_stddev'):
+              continue
+            benchmarks[name].add_sample(track, row, True)
+        if js_old_ctr:
+          for row in bm_json.expand_json(js_old_ctr, js_old_opt):
+            name = row['cpp_name']
+            if name.endswith('_mean') or name.endswith('_stddev'):
+              continue
+            benchmarks[name].add_sample(track, row, False)
+
+  really_interesting = set()
+  for name, bm in benchmarks.items():
+    _maybe_print(name)
+    really_interesting.update(bm.process(track, new, old))
+  fields = [f for f in track if f in really_interesting]
+
+  headers = ['Benchmark'] + fields
+  rows = []
+  for name in sorted(benchmarks.keys()):
+    if benchmarks[name].skip(): continue
+    rows.append([name] + benchmarks[name].row(fields))
+  note = 'Corrupt JSON data (indicates timeout or crash) = %s' % str(
+    badjson_files)
+  note += '\n\nMissing files (new benchmark) = %s' % str(nonexistant_files)
+  if rows:
+    return tabulate.tabulate(rows, headers=headers, floatfmt='+.2f'), note
+  else:
+    return None, note
+
+
+if __name__ == '__main__':
+  args = _args()
+  diff, note = diff(args.benchmarks, args.loops, args.track, args.old,
+            args.new)
+  print('%s\n%s' % (note, diff if diff else "No performance differences"))
diff --git a/tools/profiling/microbenchmarks/bm_diff/bm_main.py b/tools/profiling/microbenchmarks/bm_diff/bm_main.py
new file mode 100755
index 0000000000000000000000000000000000000000..0136c6aa57aa14e846bb18f80e1a15c3fd37dfb8
--- /dev/null
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_main.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Runs the entire bm_*.py pipeline, and possible comments on the PR """
+
+import bm_constants
+import bm_build
+import bm_run
+import bm_diff
+
+import sys
+import os
+import argparse
+import multiprocessing
+import subprocess
+
+sys.path.append(
+  os.path.join(
+    os.path.dirname(sys.argv[0]), '..', '..', 'run_tests', 'python_utils'))
+import comment_on_pr
+
+
+def _args():
+  argp = argparse.ArgumentParser(
+    description='Perform diff on microbenchmarks')
+  argp.add_argument(
+    '-t',
+    '--track',
+    choices=sorted(bm_constants._INTERESTING),
+    nargs='+',
+    default=sorted(bm_constants._INTERESTING),
+    help='Which metrics to track')
+  argp.add_argument(
+    '-b',
+    '--benchmarks',
+    nargs='+',
+    choices=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    default=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    help='Which benchmarks to run')
+  argp.add_argument(
+    '-d',
+    '--diff_base',
+    type=str,
+    help='Commit or branch to compare the current one to')
+  argp.add_argument(
+    '-o',
+    '--old',
+    default='old',
+    type=str,
+    help='Name of baseline run to compare to. Ususally just called "old"')
+  argp.add_argument(
+    '-r',
+    '--repetitions',
+    type=int,
+    default=1,
+    help='Number of repetitions to pass to the benchmarks')
+  argp.add_argument(
+    '-l',
+    '--loops',
+    type=int,
+    default=20,
+    help='Number of times to loops the benchmarks. More loops cuts down on noise'
+  )
+  argp.add_argument(
+    '-j',
+    '--jobs',
+    type=int,
+    default=multiprocessing.cpu_count(),
+    help='Number of CPUs to use')
+  args = argp.parse_args()
+  assert args.diff_base or args.old, "One of diff_base or old must be set!"
+  if args.loops < 3:
+    print "WARNING: This run will likely be noisy. Increase loops."
+  return args
+
+
+def eintr_be_gone(fn):
+  """Run fn until it doesn't stop because of EINTR"""
+
+  def inner(*args):
+    while True:
+      try:
+        return fn(*args)
+      except IOError, e:
+        if e.errno != errno.EINTR:
+          raise
+
+  return inner
+
+
+def main(args):
+
+  bm_build.build('new', args.benchmarks, args.jobs)
+
+  old = args.old
+  if args.diff_base:
+    old = 'old'
+    where_am_i = subprocess.check_output(
+      ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).strip()
+    subprocess.check_call(['git', 'checkout', args.diff_base])
+    try:
+      bm_build.build('old', args.benchmarks, args.jobs)
+    finally:
+      subprocess.check_call(['git', 'checkout', where_am_i])
+      subprocess.check_call(['git', 'submodule', 'update'])
+
+  bm_run.run('new', args.benchmarks, args.jobs, args.loops, args.repetitions)
+  bm_run.run(old, args.benchmarks, args.jobs, args.loops, args.repetitions)
+
+  diff, note = bm_diff.diff(args.benchmarks, args.loops, args.track, old,
+                'new')
+  if diff:
+    text = 'Performance differences noted:\n' + diff
+  else:
+    text = 'No significant performance differences'
+  print('%s\n%s' % (note, text))
+  comment_on_pr.comment_on_pr('```\n%s\n\n%s\n```' % (note, text))
+
+
+if __name__ == '__main__':
+  args = _args()
+  main(args)
diff --git a/tools/profiling/microbenchmarks/bm_diff/bm_run.py b/tools/profiling/microbenchmarks/bm_diff/bm_run.py
new file mode 100755
index 0000000000000000000000000000000000000000..3457af916b45ae9089612326c879416e30f79f91
--- /dev/null
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_run.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Python utility to run opt and counters benchmarks and save json output """
+
+import bm_constants
+
+import argparse
+import subprocess
+import multiprocessing
+import random
+import itertools
+import sys
+import os
+
+sys.path.append(
+  os.path.join(
+    os.path.dirname(sys.argv[0]), '..', '..', '..', 'run_tests',
+    'python_utils'))
+import jobset
+
+
+def _args():
+  argp = argparse.ArgumentParser(description='Runs microbenchmarks')
+  argp.add_argument(
+    '-b',
+    '--benchmarks',
+    nargs='+',
+    choices=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    default=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+    help='Benchmarks to run')
+  argp.add_argument(
+    '-j',
+    '--jobs',
+    type=int,
+    default=multiprocessing.cpu_count(),
+    help='Number of CPUs to use')
+  argp.add_argument(
+    '-n',
+    '--name',
+    type=str,
+    help='Unique name of the build to run. Needs to match the handle passed to bm_build.py'
+  )
+  argp.add_argument(
+    '-r',
+    '--repetitions',
+    type=int,
+    default=1,
+    help='Number of repetitions to pass to the benchmarks')
+  argp.add_argument(
+    '-l',
+    '--loops',
+    type=int,
+    default=20,
+    help='Number of times to loops the benchmarks. More loops cuts down on noise'
+  )
+  args = argp.parse_args()
+  assert args.name
+  if args.loops < 3:
+    print "WARNING: This run will likely be noisy. Increase loops to at least 3."
+  return args
+
+
+def _collect_bm_data(bm, cfg, name, reps, idx, loops):
+  jobs_list = []
+  for line in subprocess.check_output(
+    ['bm_diff_%s/%s/%s' % (name, cfg, bm),
+     '--benchmark_list_tests']).splitlines():
+    stripped_line = line.strip().replace("/", "_").replace(
+      "<", "_").replace(">", "_").replace(", ", "_")
+    cmd = [
+      'bm_diff_%s/%s/%s' % (name, cfg, bm), '--benchmark_filter=^%s$' %
+      line, '--benchmark_out=%s.%s.%s.%s.%d.json' %
+      (bm, stripped_line, cfg, name, idx), '--benchmark_out_format=json',
+      '--benchmark_repetitions=%d' % (reps)
+    ]
+    jobs_list.append(
+      jobset.JobSpec(
+        cmd,
+        shortname='%s %s %s %s %d/%d' % (bm, line, cfg, name, idx + 1,
+                         loops),
+        verbose_success=True,
+        timeout_seconds=60 * 2))
+  return jobs_list
+
+
+def run(name, benchmarks, jobs, loops, reps):
+  jobs_list = []
+  for loop in range(0, loops):
+    for bm in benchmarks:
+      jobs_list += _collect_bm_data(bm, 'opt', name, reps, loop, loops)
+      jobs_list += _collect_bm_data(bm, 'counters', name, reps, loop,
+                      loops)
+  random.shuffle(jobs_list, random.SystemRandom().random)
+  jobset.run(jobs_list, maxjobs=jobs)
+
+
+if __name__ == '__main__':
+  args = _args()
+  run(args.name, args.benchmarks, args.jobs, args.loops, args.repetitions)
diff --git a/tools/profiling/microbenchmarks/speedup.py b/tools/profiling/microbenchmarks/bm_diff/bm_speedup.py
old mode 100644
new mode 100755
similarity index 57%
rename from tools/profiling/microbenchmarks/speedup.py
rename to tools/profiling/microbenchmarks/bm_diff/bm_speedup.py
index 3f45cc38fbe85e988a1cee0761cea36f82ecd6c2..3d126efa62bd98fcf27d2a6a28a7f40217f70d15
--- a/tools/profiling/microbenchmarks/speedup.py
+++ b/tools/profiling/microbenchmarks/bm_diff/bm_speedup.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python2.7
+#
 # Copyright 2017 gRPC authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,13 +19,17 @@ import math
 
 _THRESHOLD = 1e-10
 
+
 def scale(a, mul):
-  return [x*mul for x in a]
+  return [x * mul for x in a]
+
 
 def cmp(a, b):
   return stats.ttest_ind(a, b)
 
+
 def speedup(new, old):
+  if (len(set(new))) == 1 and new == old: return 0
   s0, p0 = cmp(new, old)
   if math.isnan(p0): return 0
   if s0 == 0: return 0
@@ -31,7 +37,7 @@ def speedup(new, old):
   if s0 < 0:
     pct = 1
     while pct < 101:
-      sp, pp = cmp(new, scale(old, 1 - pct/100.0))
+      sp, pp = cmp(new, scale(old, 1 - pct / 100.0))
       if sp > 0: break
       if pp > _THRESHOLD: break
       pct += 1
@@ -39,14 +45,15 @@ def speedup(new, old):
   else:
     pct = 1
     while pct < 100000:
-      sp, pp = cmp(new, scale(old, 1 + pct/100.0))
+      sp, pp = cmp(new, scale(old, 1 + pct / 100.0))
       if sp < 0: break
       if pp > _THRESHOLD: break
       pct += 1
     return pct - 1
 
+
 if __name__ == "__main__":
-  new=[66034560.0, 126765693.0, 99074674.0, 98588433.0, 96731372.0, 110179725.0, 103802110.0, 101139800.0, 102357205.0, 99016353.0, 98840824.0, 99585632.0, 98791720.0, 96171521.0, 95327098.0, 95629704.0, 98209772.0, 99779411.0, 100182488.0, 98354192.0, 99644781.0, 98546709.0, 99019176.0, 99543014.0, 99077269.0, 98046601.0, 99319039.0, 98542572.0, 98886614.0, 72560968.0]
-  old=[60423464.0, 71249570.0, 73213089.0, 73200055.0, 72911768.0, 72347798.0, 72494672.0, 72756976.0, 72116565.0, 71541342.0, 73442538.0, 74817383.0, 73007780.0, 72499062.0, 72404945.0, 71843504.0, 73245405.0, 72778304.0, 74004519.0, 73694464.0, 72919931.0, 72955481.0, 71583857.0, 71350467.0, 71836817.0, 70064115.0, 70355345.0, 72516202.0, 71716777.0, 71532266.0]
+  new = [1.0, 1.0, 1.0, 1.0]
+  old = [2.0, 2.0, 2.0, 2.0]
   print speedup(new, old)
   print speedup(old, new)
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index bfcdc6060a45a2723101a9d8593d905ab97c0cee..16ff417a277ce57f467c12ab5db0425af39b15bb 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -8518,7 +8518,8 @@
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c", 
       "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c", 
-      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h", 
+      "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c"
     ], 
     "third_party": false, 
     "type": "filegroup"
diff --git a/tools/run_tests/run_microbenchmark.py b/tools/run_tests/run_microbenchmark.py
index 3eecd8eff422c7afd3a0fec2a9cc9502a69dc8d0..312abd59c48120f2c243a9128c515848215beb58 100755
--- a/tools/run_tests/run_microbenchmark.py
+++ b/tools/run_tests/run_microbenchmark.py
@@ -23,18 +23,8 @@ import argparse
 import python_utils.jobset as jobset
 import python_utils.start_port_server as start_port_server
 
-_AVAILABLE_BENCHMARK_TESTS = ['bm_fullstack_unary_ping_pong',
-                              'bm_fullstack_streaming_ping_pong',
-                              'bm_fullstack_streaming_pump',
-                              'bm_closure',
-                              'bm_cq',
-                              'bm_call_create',
-                              'bm_error',
-                              'bm_chttp2_hpack',
-                              'bm_chttp2_transport',
-                              'bm_pollset',
-                              'bm_metadata',
-                              'bm_fullstack_trickle']
+sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..', 'profiling', 'microbenchmarks', 'bm_diff'))
+import bm_constants
 
 flamegraph_dir = os.path.join(os.path.expanduser('~'), 'FlameGraph')
 
@@ -199,8 +189,8 @@ argp.add_argument('-c', '--collect',
                   default=sorted(collectors.keys()),
                   help='Which collectors should be run against each benchmark')
 argp.add_argument('-b', '--benchmarks',
-                  choices=_AVAILABLE_BENCHMARK_TESTS,
-                  default=_AVAILABLE_BENCHMARK_TESTS,
+                  choices=bm_constants._AVAILABLE_BENCHMARK_TESTS,
+                  default=bm_constants._AVAILABLE_BENCHMARK_TESTS,
                   nargs='+',
                   type=str,
                   help='Which microbenchmarks should be run')
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj
index 3728887edb192cf96fb4212cf95b8827a9669c6f..f9badbbd35340573806e69face8a1823ff079237 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj
@@ -967,6 +967,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper_fallback.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\native\dns_resolver.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\sockaddr\sockaddr_resolver.c">
diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
index ada4a37bbce44036f5870bc36ee3a714d66f1302..f3e997e4e0e5525107414fd68362379811289ece 100644
--- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters
@@ -673,6 +673,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper.c">
       <Filter>src\core\ext\filters\client_channel\resolver\dns\c_ares</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper_fallback.c">
+      <Filter>src\core\ext\filters\client_channel\resolver\dns\c_ares</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\native\dns_resolver.c">
       <Filter>src\core\ext\filters\client_channel\resolver\dns\native</Filter>
     </ClCompile>
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
index b252a01647d3c1162140559d883653555a76a978..3a865f3e81270893f0e35439e8f2db74f57a397b 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj
@@ -850,6 +850,8 @@
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper.c">
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper_fallback.c">
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\native\dns_resolver.c">
     </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\sockaddr\sockaddr_resolver.c">
diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
index c73870e5cc63ddd74de71bbbaa381656ef8ae592..0310ebc012ce79f8f0bc82cf5f59290809c884e4 100644
--- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters
@@ -550,6 +550,9 @@
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper.c">
       <Filter>src\core\ext\filters\client_channel\resolver\dns\c_ares</Filter>
     </ClCompile>
+    <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\c_ares\grpc_ares_wrapper_fallback.c">
+      <Filter>src\core\ext\filters\client_channel\resolver\dns\c_ares</Filter>
+    </ClCompile>
     <ClCompile Include="$(SolutionDir)\..\src\core\ext\filters\client_channel\resolver\dns\native\dns_resolver.c">
       <Filter>src\core\ext\filters\client_channel\resolver\dns\native</Filter>
     </ClCompile>