diff --git a/CMakeLists.txt b/CMakeLists.txt
index 851aeb8401b81aec61be8799fb0a8f2f203e55aa..28d3b5152f4d25dfb3c46dbc9fe839d1fb1762c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -584,6 +584,9 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 add_dependencies(buildtests_cxx bm_chttp2_hpack)
 endif()
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
+add_dependencies(buildtests_cxx bm_chttp2_transport)
+endif()
+if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 add_dependencies(buildtests_cxx bm_closure)
 endif()
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
@@ -7791,6 +7794,45 @@ endif (gRPC_BUILD_TESTS)
 if (gRPC_BUILD_TESTS)
 if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
 
+add_executable(bm_chttp2_transport
+  test/cpp/microbenchmarks/bm_chttp2_transport.cc
+  third_party/googletest/src/gtest-all.cc
+)
+
+
+target_include_directories(bm_chttp2_transport
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${BORINGSSL_ROOT_DIR}/include
+  PRIVATE ${PROTOBUF_ROOT_DIR}/src
+  PRIVATE ${BENCHMARK_ROOT_DIR}/include
+  PRIVATE ${ZLIB_ROOT_DIR}
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib
+  PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/third_party/gflags/include
+  PRIVATE third_party/googletest/include
+  PRIVATE third_party/googletest
+  PRIVATE ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(bm_chttp2_transport
+  ${_gRPC_PROTOBUF_LIBRARIES}
+  ${_gRPC_ALLTARGETS_LIBRARIES}
+  grpc_benchmark
+  benchmark
+  grpc++_test_util
+  grpc_test_util
+  grpc++
+  grpc
+  gpr_test_util
+  gpr
+  ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+endif()
+endif (gRPC_BUILD_TESTS)
+if (gRPC_BUILD_TESTS)
+if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
+
 add_executable(bm_closure
   test/cpp/microbenchmarks/bm_closure.cc
   third_party/googletest/src/gtest-all.cc
diff --git a/Makefile b/Makefile
index d2104e973c8a93947bf186e534412644c74a8784..ac70d1a6be50339e436b1eddb9604f3a240b4a83 100644
--- a/Makefile
+++ b/Makefile
@@ -1051,6 +1051,7 @@ auth_property_iterator_test: $(BINDIR)/$(CONFIG)/auth_property_iterator_test
 bm_arena: $(BINDIR)/$(CONFIG)/bm_arena
 bm_call_create: $(BINDIR)/$(CONFIG)/bm_call_create
 bm_chttp2_hpack: $(BINDIR)/$(CONFIG)/bm_chttp2_hpack
+bm_chttp2_transport: $(BINDIR)/$(CONFIG)/bm_chttp2_transport
 bm_closure: $(BINDIR)/$(CONFIG)/bm_closure
 bm_cq: $(BINDIR)/$(CONFIG)/bm_cq
 bm_error: $(BINDIR)/$(CONFIG)/bm_error
@@ -1475,6 +1476,7 @@ buildtests_cxx: privatelibs_cxx \
   $(BINDIR)/$(CONFIG)/bm_arena \
   $(BINDIR)/$(CONFIG)/bm_call_create \
   $(BINDIR)/$(CONFIG)/bm_chttp2_hpack \
+  $(BINDIR)/$(CONFIG)/bm_chttp2_transport \
   $(BINDIR)/$(CONFIG)/bm_closure \
   $(BINDIR)/$(CONFIG)/bm_cq \
   $(BINDIR)/$(CONFIG)/bm_error \
@@ -1592,6 +1594,7 @@ buildtests_cxx: privatelibs_cxx \
   $(BINDIR)/$(CONFIG)/bm_arena \
   $(BINDIR)/$(CONFIG)/bm_call_create \
   $(BINDIR)/$(CONFIG)/bm_chttp2_hpack \
+  $(BINDIR)/$(CONFIG)/bm_chttp2_transport \
   $(BINDIR)/$(CONFIG)/bm_closure \
   $(BINDIR)/$(CONFIG)/bm_cq \
   $(BINDIR)/$(CONFIG)/bm_error \
@@ -1939,6 +1942,8 @@ test_cxx: buildtests_cxx
 	$(Q) $(BINDIR)/$(CONFIG)/bm_call_create || ( echo test bm_call_create failed ; exit 1 )
 	$(E) "[RUN]     Testing bm_chttp2_hpack"
 	$(Q) $(BINDIR)/$(CONFIG)/bm_chttp2_hpack || ( echo test bm_chttp2_hpack failed ; exit 1 )
+	$(E) "[RUN]     Testing bm_chttp2_transport"
+	$(Q) $(BINDIR)/$(CONFIG)/bm_chttp2_transport || ( echo test bm_chttp2_transport failed ; exit 1 )
 	$(E) "[RUN]     Testing bm_closure"
 	$(Q) $(BINDIR)/$(CONFIG)/bm_closure || ( echo test bm_closure failed ; exit 1 )
 	$(E) "[RUN]     Testing bm_cq"
@@ -12830,6 +12835,49 @@ endif
 endif
 
 
+BM_CHTTP2_TRANSPORT_SRC = \
+    test/cpp/microbenchmarks/bm_chttp2_transport.cc \
+
+BM_CHTTP2_TRANSPORT_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(BM_CHTTP2_TRANSPORT_SRC))))
+ifeq ($(NO_SECURE),true)
+
+# You can't build secure targets if you don't have OpenSSL.
+
+$(BINDIR)/$(CONFIG)/bm_chttp2_transport: openssl_dep_error
+
+else
+
+
+
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
+
+$(BINDIR)/$(CONFIG)/bm_chttp2_transport: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/bm_chttp2_transport: $(PROTOBUF_DEP) $(BM_CHTTP2_TRANSPORT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_benchmark.a $(LIBDIR)/$(CONFIG)/libbenchmark.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+	$(E) "[LD]      Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(LDXX) $(LDFLAGS) $(BM_CHTTP2_TRANSPORT_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_benchmark.a $(LIBDIR)/$(CONFIG)/libbenchmark.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/bm_chttp2_transport
+
+endif
+
+endif
+
+$(OBJDIR)/$(CONFIG)/test/cpp/microbenchmarks/bm_chttp2_transport.o:  $(LIBDIR)/$(CONFIG)/libgrpc_benchmark.a $(LIBDIR)/$(CONFIG)/libbenchmark.a $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
+
+deps_bm_chttp2_transport: $(BM_CHTTP2_TRANSPORT_OBJS:.o=.dep)
+
+ifneq ($(NO_SECURE),true)
+ifneq ($(NO_DEPS),true)
+-include $(BM_CHTTP2_TRANSPORT_OBJS:.o=.dep)
+endif
+endif
+
+
 BM_CLOSURE_SRC = \
     test/cpp/microbenchmarks/bm_closure.cc \
 
diff --git a/build.yaml b/build.yaml
index 80c9849ca4a197100b7cb77dd0aa8868fd503934..be31935cb01b05a98a3466910712fc0088b21d99 100644
--- a/build.yaml
+++ b/build.yaml
@@ -3128,6 +3128,26 @@ targets:
   - mac
   - linux
   - posix
+- name: bm_chttp2_transport
+  build: test
+  language: c++
+  src:
+  - test/cpp/microbenchmarks/bm_chttp2_transport.cc
+  deps:
+  - grpc_benchmark
+  - benchmark
+  - grpc++_test_util
+  - grpc_test_util
+  - grpc++
+  - grpc
+  - gpr_test_util
+  - gpr
+  args:
+  - --benchmark_min_time=0
+  platforms:
+  - mac
+  - linux
+  - posix
 - name: bm_closure
   build: test
   language: c++
diff --git a/include/grpc++/support/channel_arguments.h b/include/grpc++/support/channel_arguments.h
index 80a3bfda3d9364ecadd687fa1d0634e0fad340ec..61307d61942bd18ce3b0fdd5d6a8139b0530ca5b 100644
--- a/include/grpc++/support/channel_arguments.h
+++ b/include/grpc++/support/channel_arguments.h
@@ -117,10 +117,10 @@ class ChannelArguments {
 
   /// Return (by value) a c grpc_channel_args structure which points to
   /// arguments owned by this ChannelArguments instance
-  grpc_channel_args c_channel_args() {
+  grpc_channel_args c_channel_args() const {
     grpc_channel_args out;
     out.num_args = args_.size();
-    out.args = args_.empty() ? NULL : &args_[0];
+    out.args = args_.empty() ? NULL : const_cast<grpc_arg*>(&args_[0]);
     return out;
   }
 
diff --git a/src/core/lib/iomgr/closure.c b/src/core/lib/iomgr/closure.c
index 509c1ff95dd2304fe90b1f4e51cbba172d4f69fb..6633fb68ecf63b488e5976a98c5a4502bb80fef6 100644
--- a/src/core/lib/iomgr/closure.c
+++ b/src/core/lib/iomgr/closure.c
@@ -33,6 +33,7 @@
 
 #include "src/core/lib/iomgr/closure.h"
 
+#include <assert.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
@@ -124,6 +125,7 @@ void grpc_closure_run(grpc_exec_ctx *exec_ctx, grpc_closure *c,
                       grpc_error *error) {
   GPR_TIMER_BEGIN("grpc_closure_run", 0);
   if (c != NULL) {
+    assert(c->cb);
     c->scheduler->vtable->run(exec_ctx, c, error);
   } else {
     GRPC_ERROR_UNREF(error);
@@ -135,6 +137,7 @@ void grpc_closure_sched(grpc_exec_ctx *exec_ctx, grpc_closure *c,
                         grpc_error *error) {
   GPR_TIMER_BEGIN("grpc_closure_sched", 0);
   if (c != NULL) {
+    assert(c->cb);
     c->scheduler->vtable->sched(exec_ctx, c, error);
   } else {
     GRPC_ERROR_UNREF(error);
@@ -146,6 +149,7 @@ void grpc_closure_list_sched(grpc_exec_ctx *exec_ctx, grpc_closure_list *list) {
   grpc_closure *c = list->head;
   while (c != NULL) {
     grpc_closure *next = c->next_data.next;
+    assert(c->cb);
     c->scheduler->vtable->sched(exec_ctx, c, c->error_data.error);
     c = next;
   }
diff --git a/src/core/lib/iomgr/combiner.c b/src/core/lib/iomgr/combiner.c
index fa9966c3a6957b38d7fa47d983bc7783198cf8a5..2bc476bbef624a72aafd09049f58ffa30ef52a26 100644
--- a/src/core/lib/iomgr/combiner.c
+++ b/src/core/lib/iomgr/combiner.c
@@ -33,6 +33,7 @@
 
 #include "src/core/lib/iomgr/combiner.h"
 
+#include <assert.h>
 #include <string.h>
 
 #include <grpc/support/alloc.h>
@@ -216,6 +217,7 @@ static void combiner_exec(grpc_exec_ctx *exec_ctx, grpc_combiner *lock,
       GPR_DEBUG, "C:%p grpc_combiner_execute c=%p cov=%d last=%" PRIdPTR, lock,
       cl, covered_by_poller, last));
   GPR_ASSERT(last & STATE_UNORPHANED);  // ensure lock has not been destroyed
+  assert(cl->cb);
   cl->error_data.scratch =
       pack_error_data((error_data){error, covered_by_poller});
   if (covered_by_poller) {
diff --git a/test/cpp/microbenchmarks/bm_chttp2_transport.cc b/test/cpp/microbenchmarks/bm_chttp2_transport.cc
new file mode 100644
index 0000000000000000000000000000000000000000..254d57de20589938beb142d1cf9cef41e2bfd4cc
--- /dev/null
+++ b/test/cpp/microbenchmarks/bm_chttp2_transport.cc
@@ -0,0 +1,587 @@
+/*
+ *
+ * Copyright 2015, 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.
+ *
+ */
+
+/* Microbenchmarks around CHTTP2 transport operations */
+
+#include <grpc++/support/channel_arguments.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+#include <string.h>
+#include <memory>
+#include <queue>
+#include <sstream>
+extern "C" {
+#include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
+#include "src/core/ext/transport/chttp2/transport/internal.h"
+#include "src/core/lib/iomgr/resource_quota.h"
+#include "src/core/lib/slice/slice_internal.h"
+#include "src/core/lib/transport/static_metadata.h"
+}
+#include "test/cpp/microbenchmarks/helpers.h"
+#include "third_party/benchmark/include/benchmark/benchmark.h"
+
+auto &force_library_initialization = Library::get();
+
+////////////////////////////////////////////////////////////////////////////////
+// Helper classes
+//
+
+class DummyEndpoint : public grpc_endpoint {
+ public:
+  DummyEndpoint() {
+    static const grpc_endpoint_vtable my_vtable = {read,
+                                                   write,
+                                                   get_workqueue,
+                                                   add_to_pollset,
+                                                   add_to_pollset_set,
+                                                   shutdown,
+                                                   destroy,
+                                                   get_resource_user,
+                                                   get_peer,
+                                                   get_fd};
+    grpc_endpoint::vtable = &my_vtable;
+    ru_ = grpc_resource_user_create(Library::get().rq(), "dummy_endpoint");
+  }
+
+  void PushInput(grpc_exec_ctx *exec_ctx, grpc_slice slice) {
+    if (read_cb_ == nullptr) {
+      GPR_ASSERT(!have_slice_);
+      buffered_slice_ = slice;
+      have_slice_ = true;
+      return;
+    }
+    grpc_slice_buffer_add(slices_, slice);
+    grpc_closure_sched(exec_ctx, read_cb_, GRPC_ERROR_NONE);
+    read_cb_ = nullptr;
+  }
+
+ private:
+  grpc_resource_user *ru_;
+  grpc_closure *read_cb_ = nullptr;
+  grpc_slice_buffer *slices_ = nullptr;
+  bool have_slice_ = false;
+  grpc_slice buffered_slice_;
+
+  void QueueRead(grpc_exec_ctx *exec_ctx, grpc_slice_buffer *slices,
+                 grpc_closure *cb) {
+    GPR_ASSERT(read_cb_ == nullptr);
+    if (have_slice_) {
+      have_slice_ = false;
+      grpc_slice_buffer_add(slices, buffered_slice_);
+      grpc_closure_sched(exec_ctx, cb, GRPC_ERROR_NONE);
+      return;
+    }
+    read_cb_ = cb;
+    slices_ = slices;
+  }
+
+  static void read(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                   grpc_slice_buffer *slices, grpc_closure *cb) {
+    static_cast<DummyEndpoint *>(ep)->QueueRead(exec_ctx, slices, cb);
+  }
+
+  static void write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                    grpc_slice_buffer *slices, grpc_closure *cb) {
+    grpc_closure_sched(exec_ctx, cb, GRPC_ERROR_NONE);
+  }
+
+  static grpc_workqueue *get_workqueue(grpc_endpoint *ep) { return NULL; }
+
+  static void add_to_pollset(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                             grpc_pollset *pollset) {}
+
+  static void add_to_pollset_set(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                                 grpc_pollset_set *pollset) {}
+
+  static void shutdown(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                       grpc_error *why) {
+    grpc_resource_user_shutdown(exec_ctx,
+                                static_cast<DummyEndpoint *>(ep)->ru_);
+    grpc_closure_sched(exec_ctx, static_cast<DummyEndpoint *>(ep)->read_cb_,
+                       why);
+  }
+
+  static void destroy(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep) {
+    grpc_resource_user_unref(exec_ctx, static_cast<DummyEndpoint *>(ep)->ru_);
+    delete static_cast<DummyEndpoint *>(ep);
+  }
+
+  static grpc_resource_user *get_resource_user(grpc_endpoint *ep) {
+    return static_cast<DummyEndpoint *>(ep)->ru_;
+  }
+  static char *get_peer(grpc_endpoint *ep) { return gpr_strdup("test"); }
+  static int get_fd(grpc_endpoint *ep) { return 0; }
+};
+
+class Fixture {
+ public:
+  Fixture(const grpc::ChannelArguments &args, bool client) {
+    grpc_channel_args c_args = args.c_channel_args();
+    ep_ = new DummyEndpoint;
+    t_ = grpc_create_chttp2_transport(exec_ctx(), &c_args, ep_, client);
+    grpc_chttp2_transport_start_reading(exec_ctx(), t_, NULL);
+    FlushExecCtx();
+  }
+
+  void FlushExecCtx() { grpc_exec_ctx_flush(&exec_ctx_); }
+
+  ~Fixture() {
+    grpc_transport_destroy(&exec_ctx_, t_);
+    grpc_exec_ctx_finish(&exec_ctx_);
+  }
+
+  grpc_chttp2_transport *chttp2_transport() {
+    return reinterpret_cast<grpc_chttp2_transport *>(t_);
+  }
+  grpc_transport *transport() { return t_; }
+  grpc_exec_ctx *exec_ctx() { return &exec_ctx_; }
+
+  void PushInput(grpc_slice slice) { ep_->PushInput(exec_ctx(), slice); }
+
+ private:
+  DummyEndpoint *ep_;
+  grpc_exec_ctx exec_ctx_ = GRPC_EXEC_CTX_INIT;
+  grpc_transport *t_;
+};
+
+static void DoNothing(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {}
+
+class Stream {
+ public:
+  Stream(Fixture *f) : f_(f) {
+    GRPC_STREAM_REF_INIT(&refcount_, 1, DoNothing, nullptr, "test_stream");
+    stream_size_ = grpc_transport_stream_size(f->transport());
+    stream_ = gpr_malloc(stream_size_);
+    arena_ = gpr_arena_create(4096);
+  }
+
+  ~Stream() {
+    gpr_free(stream_);
+    gpr_arena_destroy(arena_);
+  }
+
+  void Init(benchmark::State &state) {
+    memset(stream_, 0, stream_size_);
+    if ((state.iterations() & 0xffff) == 0) {
+      gpr_arena_destroy(arena_);
+      arena_ = gpr_arena_create(4096);
+    }
+    grpc_transport_init_stream(f_->exec_ctx(), f_->transport(),
+                               static_cast<grpc_stream *>(stream_), &refcount_,
+                               NULL, arena_);
+  }
+
+  void DestroyThen(grpc_closure *closure) {
+    grpc_transport_destroy_stream(f_->exec_ctx(), f_->transport(),
+                                  static_cast<grpc_stream *>(stream_), closure);
+  }
+
+  void Op(grpc_transport_stream_op *op) {
+    grpc_transport_perform_stream_op(f_->exec_ctx(), f_->transport(),
+                                     static_cast<grpc_stream *>(stream_), op);
+  }
+
+  grpc_chttp2_stream *chttp2_stream() {
+    return static_cast<grpc_chttp2_stream *>(stream_);
+  }
+
+ private:
+  Fixture *f_;
+  grpc_stream_refcount refcount_;
+  gpr_arena *arena_;
+  size_t stream_size_;
+  void *stream_;
+};
+
+class Closure : public grpc_closure {
+ public:
+  virtual ~Closure() {}
+};
+
+template <class F>
+std::unique_ptr<Closure> MakeClosure(
+    F f, grpc_closure_scheduler *sched = grpc_schedule_on_exec_ctx) {
+  struct C : public Closure {
+    C(const F &f, grpc_closure_scheduler *sched) : f_(f) {
+      grpc_closure_init(this, Execute, this, sched);
+    }
+    F f_;
+    static void Execute(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
+      static_cast<C *>(arg)->f_(exec_ctx, error);
+    }
+  };
+  return std::unique_ptr<Closure>(new C(f, sched));
+}
+
+template <class F>
+grpc_closure *MakeOnceClosure(
+    F f, grpc_closure_scheduler *sched = grpc_schedule_on_exec_ctx) {
+  struct C : public grpc_closure {
+    C(const F &f) : f_(f) {}
+    F f_;
+    static void Execute(grpc_exec_ctx *exec_ctx, void *arg, grpc_error *error) {
+      static_cast<C *>(arg)->f_(exec_ctx, error);
+      delete static_cast<C *>(arg);
+    }
+  };
+  auto *c = new C{f};
+  return grpc_closure_init(c, C::Execute, c, sched);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Benchmarks
+//
+
+static void BM_StreamCreateDestroy(benchmark::State &state) {
+  TrackCounters track_counters;
+  Fixture f(grpc::ChannelArguments(), true);
+  Stream s(&f);
+  std::unique_ptr<Closure> next =
+      MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+        if (!state.KeepRunning()) return;
+        s.Init(state);
+        s.DestroyThen(next.get());
+      });
+  grpc_closure_run(f.exec_ctx(), next.get(), GRPC_ERROR_NONE);
+  f.FlushExecCtx();
+  track_counters.Finish(state);
+}
+BENCHMARK(BM_StreamCreateDestroy);
+
+class RepresentativeClientInitialMetadata {
+ public:
+  static std::vector<grpc_mdelem> GetElems(grpc_exec_ctx *exec_ctx) {
+    return {
+        GRPC_MDELEM_SCHEME_HTTP, GRPC_MDELEM_METHOD_POST,
+        grpc_mdelem_from_slices(
+            exec_ctx, GRPC_MDSTR_PATH,
+            grpc_slice_intern(grpc_slice_from_static_string("/foo/bar"))),
+        grpc_mdelem_from_slices(exec_ctx, GRPC_MDSTR_AUTHORITY,
+                                grpc_slice_intern(grpc_slice_from_static_string(
+                                    "foo.test.google.fr:1234"))),
+        GRPC_MDELEM_GRPC_ACCEPT_ENCODING_IDENTITY_COMMA_DEFLATE_COMMA_GZIP,
+        GRPC_MDELEM_TE_TRAILERS,
+        GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC,
+        grpc_mdelem_from_slices(
+            exec_ctx, GRPC_MDSTR_USER_AGENT,
+            grpc_slice_intern(grpc_slice_from_static_string(
+                "grpc-c/3.0.0-dev (linux; chttp2; green)")))};
+  }
+};
+
+template <class Metadata>
+static void BM_StreamCreateSendInitialMetadataDestroy(benchmark::State &state) {
+  TrackCounters track_counters;
+  Fixture f(grpc::ChannelArguments(), true);
+  Stream s(&f);
+  grpc_transport_stream_op op;
+  std::unique_ptr<Closure> start;
+  std::unique_ptr<Closure> done;
+
+  grpc_metadata_batch b;
+  grpc_metadata_batch_init(&b);
+  b.deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
+  std::vector<grpc_mdelem> elems = Metadata::GetElems(f.exec_ctx());
+  std::vector<grpc_linked_mdelem> storage(elems.size());
+  for (size_t i = 0; i < elems.size(); i++) {
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "addmd",
+        grpc_metadata_batch_add_tail(f.exec_ctx(), &b, &storage[i], elems[i])));
+  }
+
+  f.FlushExecCtx();
+  start = MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+    if (!state.KeepRunning()) return;
+    s.Init(state);
+    memset(&op, 0, sizeof(op));
+    op.on_complete = done.get();
+    op.send_initial_metadata = &b;
+    s.Op(&op);
+  });
+  done = MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+    memset(&op, 0, sizeof(op));
+    op.cancel_error = GRPC_ERROR_CANCELLED;
+    s.Op(&op);
+    s.DestroyThen(start.get());
+  });
+  grpc_closure_sched(f.exec_ctx(), start.get(), GRPC_ERROR_NONE);
+  f.FlushExecCtx();
+  grpc_metadata_batch_destroy(f.exec_ctx(), &b);
+  track_counters.Finish(state);
+}
+BENCHMARK_TEMPLATE(BM_StreamCreateSendInitialMetadataDestroy,
+                   RepresentativeClientInitialMetadata);
+
+static void BM_TransportEmptyOp(benchmark::State &state) {
+  TrackCounters track_counters;
+  Fixture f(grpc::ChannelArguments(), true);
+  Stream s(&f);
+  s.Init(state);
+  grpc_transport_stream_op op;
+  std::unique_ptr<Closure> c =
+      MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+        if (!state.KeepRunning()) return;
+        memset(&op, 0, sizeof(op));
+        op.on_complete = c.get();
+        s.Op(&op);
+      });
+  grpc_closure_sched(f.exec_ctx(), c.get(), GRPC_ERROR_NONE);
+  f.FlushExecCtx();
+  s.DestroyThen(
+      MakeOnceClosure([](grpc_exec_ctx *exec_ctx, grpc_error *error) {}));
+  f.FlushExecCtx();
+  track_counters.Finish(state);
+}
+BENCHMARK(BM_TransportEmptyOp);
+
+static void BM_TransportStreamSend(benchmark::State &state) {
+  TrackCounters track_counters;
+  Fixture f(grpc::ChannelArguments(), true);
+  Stream s(&f);
+  s.Init(state);
+  grpc_transport_stream_op op;
+  grpc_slice_buffer_stream send_stream;
+  grpc_slice_buffer send_buffer;
+  grpc_slice_buffer_init(&send_buffer);
+  grpc_slice_buffer_add(&send_buffer, gpr_slice_malloc(state.range(0)));
+  memset(GRPC_SLICE_START_PTR(send_buffer.slices[0]), 0,
+         GRPC_SLICE_LENGTH(send_buffer.slices[0]));
+
+  grpc_metadata_batch b;
+  grpc_metadata_batch_init(&b);
+  b.deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
+  std::vector<grpc_mdelem> elems =
+      RepresentativeClientInitialMetadata::GetElems(f.exec_ctx());
+  std::vector<grpc_linked_mdelem> storage(elems.size());
+  for (size_t i = 0; i < elems.size(); i++) {
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "addmd",
+        grpc_metadata_batch_add_tail(f.exec_ctx(), &b, &storage[i], elems[i])));
+  }
+
+  std::unique_ptr<Closure> c =
+      MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+        if (!state.KeepRunning()) return;
+        // force outgoing window to be yuge
+        s.chttp2_stream()->outgoing_window_delta = 1024 * 1024 * 1024;
+        f.chttp2_transport()->outgoing_window = 1024 * 1024 * 1024;
+        grpc_slice_buffer_stream_init(&send_stream, &send_buffer, 0);
+        memset(&op, 0, sizeof(op));
+        op.on_complete = c.get();
+        op.send_message = &send_stream.base;
+        s.Op(&op);
+      });
+
+  memset(&op, 0, sizeof(op));
+  op.send_initial_metadata = &b;
+  op.on_complete = c.get();
+  s.Op(&op);
+
+  f.FlushExecCtx();
+  memset(&op, 0, sizeof(op));
+  op.cancel_error = GRPC_ERROR_CANCELLED;
+  s.Op(&op);
+  s.DestroyThen(
+      MakeOnceClosure([](grpc_exec_ctx *exec_ctx, grpc_error *error) {}));
+  f.FlushExecCtx();
+  track_counters.Finish(state);
+  grpc_metadata_batch_destroy(f.exec_ctx(), &b);
+  grpc_slice_buffer_destroy(&send_buffer);
+}
+BENCHMARK(BM_TransportStreamSend)->Range(0, 128 * 1024 * 1024);
+
+#define SLICE_FROM_BUFFER(s) grpc_slice_from_static_buffer(s, sizeof(s) - 1)
+
+static grpc_slice CreateIncomingDataSlice(size_t length, size_t frame_size) {
+  std::queue<char> unframed;
+
+  unframed.push(static_cast<uint8_t>(0));
+  unframed.push(static_cast<uint8_t>(length >> 24));
+  unframed.push(static_cast<uint8_t>(length >> 16));
+  unframed.push(static_cast<uint8_t>(length >> 8));
+  unframed.push(static_cast<uint8_t>(length));
+  for (size_t i = 0; i < length; i++) {
+    unframed.push('a');
+  }
+
+  std::vector<char> framed;
+  while (unframed.size() > frame_size) {
+    // frame size
+    framed.push_back(static_cast<uint8_t>(frame_size >> 16));
+    framed.push_back(static_cast<uint8_t>(frame_size >> 8));
+    framed.push_back(static_cast<uint8_t>(frame_size));
+    // data frame
+    framed.push_back(0);
+    // no flags
+    framed.push_back(0);
+    // stream id
+    framed.push_back(0);
+    framed.push_back(0);
+    framed.push_back(0);
+    framed.push_back(1);
+    // frame data
+    for (size_t i = 0; i < frame_size; i++) {
+      framed.push_back(unframed.front());
+      unframed.pop();
+    }
+  }
+
+  // frame size
+  framed.push_back(static_cast<uint8_t>(unframed.size() >> 16));
+  framed.push_back(static_cast<uint8_t>(unframed.size() >> 8));
+  framed.push_back(static_cast<uint8_t>(unframed.size()));
+  // data frame
+  framed.push_back(0);
+  // no flags
+  framed.push_back(0);
+  // stream id
+  framed.push_back(0);
+  framed.push_back(0);
+  framed.push_back(0);
+  framed.push_back(1);
+  while (!unframed.empty()) {
+    framed.push_back(unframed.front());
+    unframed.pop();
+  }
+
+  return grpc_slice_from_copied_buffer(framed.data(), framed.size());
+}
+
+static void BM_TransportStreamRecv(benchmark::State &state) {
+  TrackCounters track_counters;
+  Fixture f(grpc::ChannelArguments(), true);
+  Stream s(&f);
+  s.Init(state);
+  grpc_transport_stream_op op;
+  grpc_byte_stream *recv_stream;
+  grpc_slice incoming_data = CreateIncomingDataSlice(state.range(0), 16384);
+
+  grpc_metadata_batch b;
+  grpc_metadata_batch_init(&b);
+  grpc_metadata_batch b_recv;
+  grpc_metadata_batch_init(&b_recv);
+  b.deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
+  std::vector<grpc_mdelem> elems =
+      RepresentativeClientInitialMetadata::GetElems(f.exec_ctx());
+  std::vector<grpc_linked_mdelem> storage(elems.size());
+  for (size_t i = 0; i < elems.size(); i++) {
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "addmd",
+        grpc_metadata_batch_add_tail(f.exec_ctx(), &b, &storage[i], elems[i])));
+  }
+
+  std::unique_ptr<Closure> do_nothing =
+      MakeClosure([](grpc_exec_ctx *exec_ctx, grpc_error *error) {});
+
+  uint32_t received;
+
+  std::unique_ptr<Closure> drain_start;
+  std::unique_ptr<Closure> drain;
+  std::unique_ptr<Closure> drain_continue;
+  grpc_slice recv_slice;
+
+  std::unique_ptr<Closure> c =
+      MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+        if (!state.KeepRunning()) return;
+        // force outgoing window to be yuge
+        s.chttp2_stream()->incoming_window_delta = 1024 * 1024 * 1024;
+        f.chttp2_transport()->incoming_window = 1024 * 1024 * 1024;
+        received = 0;
+        memset(&op, 0, sizeof(op));
+        op.on_complete = do_nothing.get();
+        op.recv_message = &recv_stream;
+        op.recv_message_ready = drain_start.get();
+        s.Op(&op);
+        f.PushInput(grpc_slice_ref(incoming_data));
+      });
+
+  drain_start = MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+    if (recv_stream == NULL) {
+      GPR_ASSERT(!state.KeepRunning());
+      return;
+    }
+    grpc_closure_run(exec_ctx, drain.get(), GRPC_ERROR_NONE);
+  });
+
+  drain = MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+    do {
+      if (received == recv_stream->length) {
+        grpc_byte_stream_destroy(exec_ctx, recv_stream);
+        grpc_closure_sched(exec_ctx, c.get(), GRPC_ERROR_NONE);
+        return;
+      }
+    } while (grpc_byte_stream_next(exec_ctx, recv_stream, &recv_slice,
+                                   recv_stream->length - received,
+                                   drain_continue.get()));
+  });
+
+  drain_continue = MakeClosure([&](grpc_exec_ctx *exec_ctx, grpc_error *error) {
+    received += GRPC_SLICE_LENGTH(recv_slice);
+    grpc_slice_unref_internal(exec_ctx, recv_slice);
+    grpc_closure_run(exec_ctx, drain.get(), GRPC_ERROR_NONE);
+  });
+
+  memset(&op, 0, sizeof(op));
+  op.send_initial_metadata = &b;
+  op.recv_initial_metadata = &b_recv;
+  op.on_complete = c.get();
+  s.Op(&op);
+  f.PushInput(SLICE_FROM_BUFFER(
+      "\x00\x00\x00\x04\x00\x00\x00\x00\x00"
+      // Generated using:
+      // tools/codegen/core/gen_header_frame.py <
+      // test/cpp/microbenchmarks/representative_server_initial_metadata.headers
+      "\x00\x00X\x01\x04\x00\x00\x00\x01"
+      "\x10\x07:status\x03"
+      "200"
+      "\x10\x0c"
+      "content-type\x10"
+      "application/grpc"
+      "\x10\x14grpc-accept-encoding\x15identity,deflate,gzip"));
+
+  f.FlushExecCtx();
+  memset(&op, 0, sizeof(op));
+  op.cancel_error = GRPC_ERROR_CANCELLED;
+  s.Op(&op);
+  s.DestroyThen(
+      MakeOnceClosure([](grpc_exec_ctx *exec_ctx, grpc_error *error) {}));
+  f.FlushExecCtx();
+  track_counters.Finish(state);
+  grpc_metadata_batch_destroy(f.exec_ctx(), &b);
+  grpc_metadata_batch_destroy(f.exec_ctx(), &b_recv);
+  grpc_slice_unref(incoming_data);
+}
+BENCHMARK(BM_TransportStreamRecv)->Range(0, 128 * 1024 * 1024);
+
+BENCHMARK_MAIN();
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index 9160b0d9d6720b704f6f3764a983694b14e14da2..2e272e654fa3c3624764f66c2ac5e848667966a2 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -2507,6 +2507,27 @@
     "third_party": false, 
     "type": "target"
   }, 
+  {
+    "deps": [
+      "benchmark", 
+      "gpr", 
+      "gpr_test_util", 
+      "grpc", 
+      "grpc++", 
+      "grpc++_test_util", 
+      "grpc_benchmark", 
+      "grpc_test_util"
+    ], 
+    "headers": [], 
+    "is_filegroup": false, 
+    "language": "c++", 
+    "name": "bm_chttp2_transport", 
+    "src": [
+      "test/cpp/microbenchmarks/bm_chttp2_transport.cc"
+    ], 
+    "third_party": false, 
+    "type": "target"
+  }, 
   {
     "deps": [
       "benchmark", 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index 1af05162b4f6495a596cdcad32c81836d7d8f63c..2dc5198ca88e2358864b7058e2d5ebd22533e2db 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -2695,6 +2695,28 @@
       "posix"
     ]
   }, 
+  {
+    "args": [
+      "--benchmark_min_time=0"
+    ], 
+    "ci_platforms": [
+      "linux", 
+      "mac", 
+      "posix"
+    ], 
+    "cpu_cost": 1.0, 
+    "exclude_configs": [], 
+    "exclude_iomgrs": [], 
+    "flaky": false, 
+    "gtest": false, 
+    "language": "c++", 
+    "name": "bm_chttp2_transport", 
+    "platforms": [
+      "linux", 
+      "mac", 
+      "posix"
+    ]
+  }, 
   {
     "args": [
       "--benchmark_min_time=0"
@@ -117505,6 +117527,7 @@
     "language": "c", 
     "name": "hpack_parser_fuzzer_test_one_entry", 
     "platforms": [
+      "mac", 
       "linux"
     ], 
     "uses_polling": false