diff --git a/.gitignore b/.gitignore
index 5202b53ad2514a0f3407971bb5500c37973ab11a..3cae07ed12fb99adfecd0f135675fab3cf473803 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
 bins
+coverage
 deps
+*.gcno
+gens
 libs
 objs
 *.pyc
-
diff --git a/.gitmodules b/.gitmodules
index 9a287d9ee2d99b04edf66badcd3e000344a64f51..97b7197be323212a29615efbf14dc1e416fab6f9 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -9,10 +9,6 @@
 	path = third_party/protobuf
 	url = https://github.com/google/protobuf.git
 	branch = v3.0.0-alpha-1
-[submodule "third_party/libevent"]
-	path = third_party/libevent
-	url = https://github.com/libevent/libevent.git
-	branch = patches-2.0
 [submodule "third_party/gflags"]
 	path = third_party/gflags
 	url = https://code.google.com/p/gflags
diff --git a/INSTALL b/INSTALL
index 20e27c1b805499d880d7805631ea72f9f5884769..a9b0b58aa6e0db3888dd00985c4b0ca4b9c0907a 100644
--- a/INSTALL
+++ b/INSTALL
@@ -12,8 +12,7 @@ Note that the Makefile makes it much easier for you to compile from sources
 if you were to clone recursively our git repository.
 
 
-grpc core currently depends on zlib and OpenSSL 1.0.2beta3, and also requires
-libevent2 for the Linux port.
+grpc core currently depends on zlib and OpenSSL 1.0.2beta3.
 
 grpc++'s tests depends on protobuf 3.0.0, gtests and gflags.
 
@@ -46,7 +45,7 @@ and let the Makefile build them itself.
 You may also install the dependencies yourself, from the sources, or from
 your distribution's package manager.
 
-The development packages needed for grpc are libevent2 under Linux, and zlib.
+The only development package needed for grpc is zlib.
 The development packages needed for grpc++'s tests are gtests, and gflags.
 
 To the best of our knowledge, no distribution has an OpenSSL package that
diff --git a/Makefile b/Makefile
index a102746f4f14a3c9f6e6b77d8202675738f12de8..76e6407b880f1af536a799e383898b0d493d5c6f 100644
--- a/Makefile
+++ b/Makefile
@@ -49,6 +49,15 @@ CPPFLAGS_msan = -O1 -fsanitize=memory -fno-omit-frame-pointer
 LDFLAGS_msan = -fsanitize=memory
 DEFINES_msan = NDEBUG
 
+VALID_CONFIG_gcov = 1
+CC_gcov = gcc
+CXX_gcov = g++
+LD_gcov = gcc
+LDXX_gcov = g++
+CPPFLAGS_gcov = -O0 -fprofile-arcs -ftest-coverage
+LDFLAGS_gcov = -fprofile-arcs -ftest-coverage
+DEFINES_gcov = NDEBUG
+
 # General settings.
 # You may want to change these depending on your system.
 
@@ -487,9 +496,9 @@ shared_cxx: dep_cxx libs/$(CONFIG)/libgrpc++.$(SHARED_EXT)
 
 privatelibs: privatelibs_c privatelibs_cxx
 
-privatelibs_c: dep_c libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libend2end_fixture_chttp2_fake_security.a libs/$(CONFIG)/libend2end_fixture_chttp2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_with_oauth2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair_one_byte_at_a_time.a libs/$(CONFIG)/libend2end_test_cancel_after_accept.a libs/$(CONFIG)/libend2end_test_cancel_after_accept_and_writes_closed.a libs/$(CONFIG)/libend2end_test_cancel_after_invoke.a libs/$(CONFIG)/libend2end_test_cancel_before_invoke.a libs/$(CONFIG)/libend2end_test_cancel_in_a_vacuum.a libs/$(CONFIG)/libend2end_test_census_simple_request.a libs/$(CONFIG)/libend2end_test_disappearing_server.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_inflight_calls.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_tags.a libs/$(CONFIG)/libend2end_test_invoke_large_request.a libs/$(CONFIG)/libend2end_test_max_concurrent_streams.a libs/$(CONFIG)/libend2end_test_no_op.a libs/$(CONFIG)/libend2end_test_ping_pong_streaming.a libs/$(CONFIG)/libend2end_test_request_response_with_binary_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_trailing_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_simple_delayed_request.a libs/$(CONFIG)/libend2end_test_simple_request.a libs/$(CONFIG)/libend2end_test_thread_stress.a libs/$(CONFIG)/libend2end_test_writes_done_hangs_with_pending_read.a libs/$(CONFIG)/libend2end_certs.a
+privatelibs_c: dep_c libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libend2end_fixture_chttp2_fake_security.a libs/$(CONFIG)/libend2end_fixture_chttp2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_with_oauth2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair_one_byte_at_a_time.a libs/$(CONFIG)/libend2end_test_cancel_after_accept.a libs/$(CONFIG)/libend2end_test_cancel_after_accept_and_writes_closed.a libs/$(CONFIG)/libend2end_test_cancel_after_invoke.a libs/$(CONFIG)/libend2end_test_cancel_before_invoke.a libs/$(CONFIG)/libend2end_test_cancel_in_a_vacuum.a libs/$(CONFIG)/libend2end_test_census_simple_request.a libs/$(CONFIG)/libend2end_test_disappearing_server.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_inflight_calls.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_tags.a libs/$(CONFIG)/libend2end_test_invoke_large_request.a libs/$(CONFIG)/libend2end_test_max_concurrent_streams.a libs/$(CONFIG)/libend2end_test_no_op.a libs/$(CONFIG)/libend2end_test_ping_pong_streaming.a libs/$(CONFIG)/libend2end_test_request_response_with_binary_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_trailing_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_simple_delayed_request.a libs/$(CONFIG)/libend2end_test_simple_request.a libs/$(CONFIG)/libend2end_test_thread_stress.a libs/$(CONFIG)/libend2end_test_writes_done_hangs_with_pending_read.a libs/$(CONFIG)/libend2end_certs.a
 
-privatelibs_cxx: dep_cxx libs/$(CONFIG)/libgpr_test_util.a libs/$(CONFIG)/libgrpc_test_util.a libs/$(CONFIG)/libgrpc++_test_util.a libs/$(CONFIG)/libend2end_fixture_chttp2_fake_security.a libs/$(CONFIG)/libend2end_fixture_chttp2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_simple_ssl_with_oauth2_fullstack.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair.a libs/$(CONFIG)/libend2end_fixture_chttp2_socket_pair_one_byte_at_a_time.a libs/$(CONFIG)/libend2end_test_cancel_after_accept.a libs/$(CONFIG)/libend2end_test_cancel_after_accept_and_writes_closed.a libs/$(CONFIG)/libend2end_test_cancel_after_invoke.a libs/$(CONFIG)/libend2end_test_cancel_before_invoke.a libs/$(CONFIG)/libend2end_test_cancel_in_a_vacuum.a libs/$(CONFIG)/libend2end_test_census_simple_request.a libs/$(CONFIG)/libend2end_test_disappearing_server.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_inflight_calls.a libs/$(CONFIG)/libend2end_test_early_server_shutdown_finishes_tags.a libs/$(CONFIG)/libend2end_test_invoke_large_request.a libs/$(CONFIG)/libend2end_test_max_concurrent_streams.a libs/$(CONFIG)/libend2end_test_no_op.a libs/$(CONFIG)/libend2end_test_ping_pong_streaming.a libs/$(CONFIG)/libend2end_test_request_response_with_binary_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_payload.a libs/$(CONFIG)/libend2end_test_request_response_with_trailing_metadata_and_payload.a libs/$(CONFIG)/libend2end_test_simple_delayed_request.a libs/$(CONFIG)/libend2end_test_simple_request.a libs/$(CONFIG)/libend2end_test_thread_stress.a libs/$(CONFIG)/libend2end_test_writes_done_hangs_with_pending_read.a libs/$(CONFIG)/libend2end_certs.a
+privatelibs_cxx: dep_cxx libs/$(CONFIG)/libgrpc++_test_util.a
 
 buildtests: buildtests_c buildtests_cxx
 
diff --git a/include/grpc/support/port_platform.h b/include/grpc/support/port_platform.h
index 9c999b9c811e97b3908a02b305ef563f6482477a..05a5bbe1bc9e0160a287e0c5bd60978513560e42 100644
--- a/include/grpc/support/port_platform.h
+++ b/include/grpc/support/port_platform.h
@@ -45,15 +45,16 @@
 #if defined(_WIN64) || defined(WIN64)
 #define GPR_WIN32 1
 #define GPR_ARCH_64 1
+#define GPR_GETPID_IN_PROCESS_H 1
 #elif defined(_WIN32) || defined(WIN32)
 #define GPR_ARCH_32 1
 #define GPR_WIN32 1
+#define GPR_GETPID_IN_PROCESS_H 1
 #elif defined(ANDROID) || defined(__ANDROID__)
 #define GPR_ANDROID 1
 #define GPR_ARCH_32 1
 #define GPR_CPU_LINUX 1
 #define GPR_GCC_SYNC 1
-#define GPR_LIBEVENT 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
 #define GPR_POSIX_SOCKET 1
 #define GPR_POSIX_SOCKETADDR 1
@@ -61,10 +62,10 @@
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_GETPID_IN_UNISTD_H 1
 #elif defined(__linux__)
 #define GPR_CPU_LINUX 1
 #define GPR_GCC_ATOMIC 1
-#define GPR_LIBEVENT 1
 #define GPR_LINUX 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
 #define GPR_POSIX_SOCKET 1
@@ -72,6 +73,7 @@
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
 #else /* _LP64 */
@@ -80,7 +82,6 @@
 #elif defined(__APPLE__)
 #define GPR_CPU_POSIX 1
 #define GPR_GCC_ATOMIC 1
-#define GPR_LIBEVENT 1
 #define GPR_POSIX_LOG 1
 #define GPR_POSIX_MULTIPOLL_WITH_POLL 1
 #define GPR_POSIX_SOCKET 1
@@ -89,6 +90,7 @@
 #define GPR_POSIX_STRING 1
 #define GPR_POSIX_SYNC 1
 #define GPR_POSIX_TIME 1
+#define GPR_GETPID_IN_UNISTD_H 1
 #ifdef _LP64
 #define GPR_ARCH_64 1
 #else /* _LP64 */
diff --git a/src/core/support/log_posix.c b/src/core/support/log_posix.c
index 0420570a3ec66087335f5b9a0d16703796737d29..55a38b136d0de0e7cab567a5d98bbddd5115149d 100644
--- a/src/core/support/log_posix.c
+++ b/src/core/support/log_posix.c
@@ -31,21 +31,27 @@
  *
  */
 
-#define _POSIX_SOURCE
+
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+
 #define _GNU_SOURCE
 #include <grpc/support/port_platform.h>
 
 #if defined(GPR_POSIX_LOG)
 
+#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
+#include <stdio.h>
 #include <time.h>
 #include <pthread.h>
 
-static long gettid() { return pthread_self(); }
+static gpr_intptr gettid() { return (gpr_intptr)pthread_self(); }
 
 void gpr_log(const char *file, int line, gpr_log_severity severity,
              const char *format, ...) {
@@ -55,7 +61,7 @@ void gpr_log(const char *file, int line, gpr_log_severity severity,
   int ret;
   va_list args;
   va_start(args, format);
-  ret = vsnprintf(buf, format, args);
+  ret = vsnprintf(buf, sizeof(buf), format, args);
   va_end(args);
   if (ret < 0) {
     message = NULL;
@@ -64,7 +70,7 @@ void gpr_log(const char *file, int line, gpr_log_severity severity,
   } else {
     message = allocated = gpr_malloc(ret + 1);
     va_start(args, format);
-    vsnprintf(message, format, args);
+    vsnprintf(message, ret, format, args);
     va_end(args);
   }
   gpr_log_message(file, line, severity, message);
@@ -91,7 +97,7 @@ void gpr_default_log(gpr_log_func_args *args) {
     strcpy(time_buffer, "error:strftime");
   }
 
-  fprintf(stderr, "%s%s.%09d %7ld %s:%d] %s\n",
+  fprintf(stderr, "%s%s.%09d %7tu %s:%d] %s\n",
           gpr_log_severity_string(args->severity), time_buffer,
           (int)(now.tv_nsec), gettid(), display_file, args->line,
           args->message);
diff --git a/src/core/support/log_win32.c b/src/core/support/log_win32.c
index ae5f23a90dc9f406596b39c527ada1b91c3b38e5..dc8c1d0785ad9e7afdcf0333fc5901c29a7561c1 100644
--- a/src/core/support/log_win32.c
+++ b/src/core/support/log_win32.c
@@ -36,12 +36,13 @@
 #ifdef GPR_WIN32
 
 #include <grpc/support/log.h>
+#include <grpc/support/alloc.h>
 #include <stdio.h>
 #include <stdarg.h>
 
 void gpr_log(const char *file, int line, gpr_log_severity severity,
-             const char *message) {
-  const char *message = NULL;
+             const char *format, ...) {
+  char *message = NULL;
   va_list args;
   int ret;
 
@@ -53,7 +54,7 @@ void gpr_log(const char *file, int line, gpr_log_severity severity,
     message = NULL;
   } else {
     /* Allocate a new buffer, with space for the NUL terminator. */
-    strp_buflen = (size_t)ret + 1;
+    size_t strp_buflen = (size_t)ret + 1;
     message = gpr_malloc(strp_buflen);
 
     /* Print to the buffer. */
@@ -73,7 +74,7 @@ void gpr_log(const char *file, int line, gpr_log_severity severity,
 
 /* Simple starter implementation */
 void gpr_default_log(gpr_log_func_args *args) {
-  fprintf(stderr, "%s %s:%d: %s\n", gpr_log_severity_string(severity),
+  fprintf(stderr, "%s %s:%d: %s\n", gpr_log_severity_string(args->severity),
           args->file, args->line, args->message);
 }
 
diff --git a/src/core/support/time.c b/src/core/support/time.c
index 0e88c65be0c43341b13a0eb89bca2080aca6457a..97243318fdaa5265a117f71c9e0a3212c5e7cc84 100644
--- a/src/core/support/time.c
+++ b/src/core/support/time.c
@@ -259,7 +259,7 @@ gpr_int32 gpr_time_to_millis(gpr_timespec t) {
   } else if (t.tv_sec <= -2147483) {
     /* TODO(ctiller): correct handling here (it's so far in the past do we
        care?) */
-    return -2147483648;
+    return -2147483647;
   } else {
     return t.tv_sec * GPR_MS_PER_SEC + t.tv_nsec / GPR_NS_PER_MS;
   }
diff --git a/src/node/server.js b/src/node/server.js
index 7f3e0259a0b9f71b9c2987aa1e110138af2c9c0d..2704c68f17c96dea95f45ddbc7ea5a25ab6123a4 100644
--- a/src/node/server.js
+++ b/src/node/server.js
@@ -73,6 +73,7 @@ function GrpcServerStream(call, options) {
    * @param {Error} err The error object
    */
   function setStatus(err) {
+    console.log('Server setting status to', err);
     var code = grpc.status.INTERNAL;
     var details = 'Unknown Error';
 
diff --git a/src/node/surface_client.js b/src/node/surface_client.js
index 9c40b0a3a00a8d0ccaff805e7bcd247a295286d8..acd22089ce1c717e6066d0609d02b2ce2ba353a3 100644
--- a/src/node/surface_client.js
+++ b/src/node/surface_client.js
@@ -178,7 +178,7 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
   /**
    * Make a unary request with this method on the given channel with the given
    * argument, callback, etc.
-   * @param {client.Channel} channel The channel on which to make the request
+   * @this {SurfaceClient} Client object. Must have a channel member.
    * @param {*} argument The argument to the call. Should be serializable with
    *     serialize
    * @param {function(?Error, value=)} callback The callback to for when the
@@ -189,8 +189,8 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
    *     Defaults to infinite future
    * @return {EventEmitter} An event emitter for stream related events
    */
-  function makeUnaryRequest(channel, argument, callback, metadata, deadline) {
-    var stream = client.makeRequest(channel, method, metadata, deadline);
+  function makeUnaryRequest(argument, callback, metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
     var emitter = new EventEmitter();
     forwardEvent(stream, emitter, 'status');
     forwardEvent(stream, emitter, 'metadata');
@@ -220,7 +220,7 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
   /**
    * Make a client stream request with this method on the given channel with the
    * given callback, etc.
-   * @param {client.Channel} channel The channel on which to make the request
+   * @this {SurfaceClient} Client object. Must have a channel member.
    * @param {function(?Error, value=)} callback The callback to for when the
    *     response is received
    * @param {array=} metadata Array of metadata key/value pairs to add to the
@@ -229,8 +229,8 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
    *     Defaults to infinite future
    * @return {EventEmitter} An event emitter for stream related events
    */
-  function makeClientStreamRequest(channel, callback, metadata, deadline) {
-    var stream = client.makeRequest(channel, method, metadata, deadline);
+  function makeClientStreamRequest(callback, metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
     var obj_stream = new ClientWritableObjectStream(stream, serialize, {});
     stream.on('data', function forwardData(chunk) {
       try {
@@ -256,7 +256,7 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
   /**
    * Make a server stream request with this method on the given channel with the
    * given argument, etc.
-   * @param {client.Channel} channel The channel on which to make the request
+   * @this {SurfaceClient} Client object. Must have a channel member.
    * @param {*} argument The argument to the call. Should be serializable with
    *     serialize
    * @param {array=} metadata Array of metadata key/value pairs to add to the
@@ -265,8 +265,8 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
    *     Defaults to infinite future
    * @return {EventEmitter} An event emitter for stream related events
    */
-  function makeServerStreamRequest(channel, argument, metadata, deadline) {
-    var stream = client.makeRequest(channel, method, metadata, deadline);
+  function makeServerStreamRequest(argument, metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
     var obj_stream = new ClientReadableObjectStream(stream, deserialize, {});
     stream.write(serialize(argument));
     stream.end();
@@ -287,15 +287,15 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
 function makeBidiStreamRequestFunction(method, serialize, deserialize) {
   /**
    * Make a bidirectional stream request with this method on the given channel.
-   * @param {client.Channel} channel The channel on which to make the request
+   * @this {SurfaceClient} Client object. Must have a channel member.
    * @param {array=} metadata Array of metadata key/value pairs to add to the
    *     call
    * @param {(number|Date)=} deadline The deadline for processing this request.
    *     Defaults to infinite future
    * @return {EventEmitter} An event emitter for stream related events
    */
-  function makeBidiStreamRequest(channel, metadata, deadline) {
-    var stream = client.makeRequest(channel, method, metadata, deadline);
+  function makeBidiStreamRequest(metadata, deadline) {
+    var stream = client.makeRequest(this.channel, method, metadata, deadline);
     var obj_stream = new ClientBidiObjectStream(stream,
                                                 serialize,
                                                 deserialize,
@@ -306,29 +306,63 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
 }
 
 /**
- * See docs for makeUnaryRequestFunction
+ * Map with short names for each of the requester maker functions. Used in
+ * makeClientConstructor
  */
-exports.makeUnaryRequestFunction = makeUnaryRequestFunction;
+var requester_makers = {
+  unary: makeUnaryRequestFunction,
+  server_stream: makeServerStreamRequestFunction,
+  client_stream: makeClientStreamRequestFunction,
+  bidi: makeBidiStreamRequestFunction
+}
 
 /**
- * See docs for makeClientStreamRequestFunction
+ * Creates a constructor for clients with a service defined by the methods
+ * object. The methods object has string keys and values of this form:
+ * {serialize: function, deserialize: function, client_stream: bool,
+ *  server_stream: bool}
+ * @param {!Object<string, Object>} methods Method descriptor for each method
+ *     the client should expose
+ * @param {string} prefix The prefix to prepend to each method name
+ * @return {function(string, Object)} New client constructor
  */
-exports.makeClientStreamRequestFunction = makeClientStreamRequestFunction;
+function makeClientConstructor(methods, prefix) {
+  /**
+   * Create a client with the given methods
+   * @constructor
+   * @param {string} address The address of the server to connect to
+   * @param {Object} options Options to pass to the underlying channel
+   */
+  function SurfaceClient(address, options) {
+    this.channel = new client.Channel(address, options);
+  }
 
-/**
- * See docs for makeServerStreamRequestFunction
- */
-exports.makeServerStreamRequestFunction = makeServerStreamRequestFunction;
+  _.each(methods, function(method, name) {
+    var method_type;
+    if (method.client_stream) {
+      if (method.server_stream) {
+        method_type = 'bidi';
+      } else {
+        method_type = 'client_stream';
+      }
+    } else {
+      if (method.server_stream) {
+        method_type = 'server_stream';
+      } else {
+        method_type = 'unary';
+      }
+    }
+    SurfaceClient.prototype[name] = requester_makers[method_type](
+        prefix + name,
+        method.serialize,
+        method.deserialize);
+  });
 
-/**
- * See docs for makeBidiStreamRequestFunction
- */
-exports.makeBidiStreamRequestFunction = makeBidiStreamRequestFunction;
+  return SurfaceClient;
+}
+
+exports.makeClientConstructor = makeClientConstructor;
 
-/**
- * See docs for client.Channel
- */
-exports.Channel = client.Channel;
 /**
  * See docs for client.status
  */
diff --git a/src/node/test/math_client_test.js b/src/node/test/math_client_test.js
index f3697aca98e66d0bc2a41fa95bd6481a0aba2339..5b34a228ad3556bbec2ada419a6ddda14e1d92e9 100644
--- a/src/node/test/math_client_test.js
+++ b/src/node/test/math_client_test.js
@@ -32,13 +32,14 @@
  */
 
 var assert = require('assert');
-var client = require('../surface_client.js');
 var ProtoBuf = require('protobufjs');
 var port_picker = require('../port_picker');
 
 var builder = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
 var math = builder.build('math');
 
+var client = require('../surface_client.js');
+var makeConstructor = client.makeClientConstructor;
 /**
  * Get a function that deserializes a specific type of protobuf.
  * @param {function()} cls The constructor of the message type to deserialize
@@ -56,78 +57,60 @@ function deserializeCls(cls) {
 }
 
 /**
- * Serialize an object to a buffer
- * @param {*} arg The object to serialize
- * @return {Buffer} The serialized object
+ * Get a function that serializes objects to a buffer by protobuf class.
+ * @param {function()} Cls The constructor of the message type to serialize
+ * @return {function(Cls):Buffer} The serialization function
  */
-function serialize(arg) {
-  return new Buffer(arg.encode().toBuffer());
+function serializeCls(Cls) {
+  /**
+   * Serialize an object to a Buffer
+   * @param {Object} arg The object to serialize
+   * @return {Buffer} The serialized object
+   */
+  return function serialize(arg) {
+    return new Buffer(new Cls(arg).encode().toBuffer());
+  };
 }
 
-/**
- * Sends a Div request on the channel.
- * @param {client.Channel} channel The channel on which to make the request
- * @param {DivArg} argument The argument to the call. Should be serializable
- *     with serialize
- * @param {function(?Error, value=)} The callback to for when the response is
- *     received
- * @param {array=} Array of metadata key/value pairs to add to the call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- *     Defaults to infinite future
- * @return {EventEmitter} An event emitter for stream related events
- */
-var div = client.makeUnaryRequestFunction(
-    '/Math/Div',
-    serialize,
-    deserializeCls(math.DivReply));
-
-/**
- * Sends a Fib request on the channel.
- * @param {client.Channel} channel The channel on which to make the request
- * @param {*} argument The argument to the call. Should be serializable with
- *     serialize
- * @param {array=} Array of metadata key/value pairs to add to the call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- *     Defaults to infinite future
- * @return {EventEmitter} An event emitter for stream related events
- */
-var fib = client.makeServerStreamRequestFunction(
-    '/Math/Fib',
-    serialize,
-    deserializeCls(math.Num));
-
-/**
- * Sends a Sum request on the channel.
- * @param {client.Channel} channel The channel on which to make the request
- * @param {function(?Error, value=)} The callback to for when the response is
- *     received
- * @param {array=} Array of metadata key/value pairs to add to the call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- *     Defaults to infinite future
- * @return {EventEmitter} An event emitter for stream related events
- */
-var sum = client.makeClientStreamRequestFunction(
-    '/Math/Sum',
-    serialize,
-    deserializeCls(math.Num));
-
-/**
- * Sends a DivMany request on the channel.
- * @param {client.Channel} channel The channel on which to make the request
- * @param {array=} Array of metadata key/value pairs to add to the call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- *     Defaults to infinite future
- * @return {EventEmitter} An event emitter for stream related events
- */
-var divMany = client.makeBidiStreamRequestFunction(
-    '/Math/DivMany',
-    serialize,
-    deserializeCls(math.DivReply));
+/* This function call creates a client constructor for clients that expose the
+ * four specified methods. This specifies how to serialize messages that the
+ * client sends and deserialize messages that the server sends, and whether the
+ * client or the server will send a stream of messages, for each method. This
+ * also specifies a prefix that will be added to method names when sending them
+ * on the wire. This function call and all of the preceding code in this file
+ * are intended to approximate what the generated code will look like for the
+ * math client */
+var MathClient = makeConstructor({
+  Div: {
+    serialize: serializeCls(math.DivArgs),
+    deserialize: deserializeCls(math.DivReply),
+    client_stream: false,
+    server_stream: false
+  },
+  Fib: {
+    serialize: serializeCls(math.FibArgs),
+    deserialize: deserializeCls(math.Num),
+    client_stream: false,
+    server_stream: true
+  },
+  Sum: {
+    serialize: serializeCls(math.Num),
+    deserialize: deserializeCls(math.Num),
+    client_stream: true,
+    server_stream: false
+  },
+  DivMany: {
+    serialize: serializeCls(math.DivArgs),
+    deserialize: deserializeCls(math.DivReply),
+    client_stream: true,
+    server_stream: true
+  }
+}, '/Math/');
 
 /**
  * Channel to use to make requests to a running server.
  */
-var channel;
+var math_client;
 
 /**
  * Server to test against
@@ -139,7 +122,7 @@ describe('Math client', function() {
   before(function(done) {
     port_picker.nextAvailablePort(function(port) {
       server.bind(port).listen();
-      channel = new client.Channel(port);
+      math_client = new MathClient(port);
       done();
     });
   });
@@ -147,11 +130,11 @@ describe('Math client', function() {
     server.shutdown();
   });
   it('should handle a single request', function(done) {
-    var arg = new math.DivArgs({dividend: 7, divisor: 4});
-    var call = div(channel, arg, function handleDivResult(err, value) {
+    var arg = {dividend: 7, divisor: 4};
+    var call = math_client.Div(arg, function handleDivResult(err, value) {
       assert.ifError(err);
-      assert.equal(value.get('quotient'), 1);
-      assert.equal(value.get('remainder'), 3);
+      assert.equal(value.quotient, 1);
+      assert.equal(value.remainder, 3);
     });
     call.on('status', function checkStatus(status) {
       assert.strictEqual(status.code, client.status.OK);
@@ -159,12 +142,11 @@ describe('Math client', function() {
     });
   });
   it('should handle a server streaming request', function(done) {
-    var arg = new math.FibArgs({limit: 7});
-    var call = fib(channel, arg);
+    var call = math_client.Fib({limit: 7});
     var expected_results = [1, 1, 2, 3, 5, 8, 13];
     var next_expected = 0;
     call.on('data', function checkResponse(value) {
-      assert.equal(value.get('num'), expected_results[next_expected]);
+      assert.equal(value.num, expected_results[next_expected]);
       next_expected += 1;
     });
     call.on('status', function checkStatus(status) {
@@ -173,12 +155,12 @@ describe('Math client', function() {
     });
   });
   it('should handle a client streaming request', function(done) {
-    var call = sum(channel, function handleSumResult(err, value) {
+    var call = math_client.Sum(function handleSumResult(err, value) {
       assert.ifError(err);
-      assert.equal(value.get('num'), 21);
+      assert.equal(value.num, 21);
     });
     for (var i = 0; i < 7; i++) {
-      call.write(new math.Num({'num': i}));
+      call.write({'num': i});
     }
     call.end();
     call.on('status', function checkStatus(status) {
@@ -188,17 +170,17 @@ describe('Math client', function() {
   });
   it('should handle a bidirectional streaming request', function(done) {
     function checkResponse(index, value) {
-      assert.equal(value.get('quotient'), index);
-      assert.equal(value.get('remainder'), 1);
+      assert.equal(value.quotient, index);
+      assert.equal(value.remainder, 1);
     }
-    var call = divMany(channel);
+    var call = math_client.DivMany();
     var response_index = 0;
     call.on('data', function(value) {
       checkResponse(response_index, value);
       response_index += 1;
     });
     for (var i = 0; i < 7; i++) {
-      call.write(new math.DivArgs({dividend: 2 * i + 1, divisor: 2}));
+      call.write({dividend: 2 * i + 1, divisor: 2});
     }
     call.end();
     call.on('status', function checkStatus(status) {
diff --git a/templates/Makefile.template b/templates/Makefile.template
index d0ebb1024086943baf997db77432e64bd2ba3f4c..256380c2ddc1d51a1a1d501fb0364297d0171c0e 100644
--- a/templates/Makefile.template
+++ b/templates/Makefile.template
@@ -66,6 +66,15 @@ CPPFLAGS_msan = -O1 -fsanitize=memory -fno-omit-frame-pointer
 LDFLAGS_msan = -fsanitize=memory
 DEFINES_msan = NDEBUG
 
+VALID_CONFIG_gcov = 1
+CC_gcov = gcc
+CXX_gcov = g++
+LD_gcov = gcc
+LDXX_gcov = g++
+CPPFLAGS_gcov = -O0 -fprofile-arcs -ftest-coverage
+LDFLAGS_gcov = -fprofile-arcs -ftest-coverage
+DEFINES_gcov = NDEBUG
+
 # General settings.
 # You may want to change these depending on your system.
 
@@ -329,7 +338,7 @@ privatelibs: privatelibs_c privatelibs_cxx
 
 privatelibs_c: dep_c\
 % for lib in libs:
-% if lib.build == 'private':
+% if lib.build == 'private' and not lib.get('c++', False):
  libs/$(CONFIG)/lib${lib.name}.a\
 % endif
 % endfor
@@ -337,7 +346,7 @@ privatelibs_c: dep_c\
 
 privatelibs_cxx: dep_cxx\
 % for lib in libs:
-% if lib.build == 'private':
+% if lib.build == 'private' and lib.get('c++', False):
  libs/$(CONFIG)/lib${lib.name}.a\
 % endif
 % endfor
diff --git a/templates/vsprojects/vs2013/build_and_run_tests.bat.template b/templates/vsprojects/vs2013/build_and_run_tests.bat.template
index 8679bee3fc3966d89c8ecae747463042cc3e5549..4a15e01c522225fa2a0b71be68a953feb97d74ed 100644
--- a/templates/vsprojects/vs2013/build_and_run_tests.bat.template
+++ b/templates/vsprojects/vs2013/build_and_run_tests.bat.template
@@ -13,7 +13,7 @@
 
 @rem Build the library dependencies first
 MSBuild.exe gpr.vcxproj /p:Configuration=Debug
-MSBuild.exe grpc_test_util.vcxproj /p:Configuration=Debug
+MSBuild.exe gpr_test_util.vcxproj /p:Configuration=Debug
 
 mkdir ${test_bin_dir}
 
diff --git a/test/build/event2.c b/test/build/event2.c
deleted file mode 100644
index f632b1e43e6369936485abca9c90b3de982434dd..0000000000000000000000000000000000000000
--- a/test/build/event2.c
+++ /dev/null
@@ -1,8 +0,0 @@
-/* This is only a compilation test, to see if we have libevent installed. */
-
-#include <event2/event.h>
-
-int main() {
-  event_base_new();
-  return 0;
-}
diff --git a/test/core/util/test_config.c b/test/core/util/test_config.c
index 1a8f1a5f4968ec2b1d8178f785e7550af087486c..94245d85bcc79fffea83ec8b7d4a37a996f285ca 100644
--- a/test/core/util/test_config.c
+++ b/test/core/util/test_config.c
@@ -33,14 +33,28 @@
 
 #include "test/core/util/test_config.h"
 
+#include <grpc/support/port_platform.h>
 #include <stdlib.h>
 #include <signal.h>
+
+#if GPR_GETPID_IN_UNISTD_H
 #include <unistd.h>
+static int seed() {
+  return getpid();
+}
+#endif
+
+#if GPR_GETPID_IN_PROCESS_H
+#include <process.h>
+static int seed(void) {
+  return _getpid();
+}
+#endif
 
 void grpc_test_init(int argc, char **argv) {
   /* disable SIGPIPE */
   signal(SIGPIPE, SIG_IGN);
   /* seed rng with pid, so we don't end up with the same random numbers as a
      concurrently running test binary */
-  srand(getpid());
+  srand(seed());
 }
diff --git a/third_party/libevent b/third_party/libevent
deleted file mode 160000
index f7d92c63928a1460f3d99b9bc418bd3b686a0dca..0000000000000000000000000000000000000000
--- a/third_party/libevent
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f7d92c63928a1460f3d99b9bc418bd3b686a0dca
diff --git a/tools/dockerfile/grpc_base/Dockerfile b/tools/dockerfile/grpc_base/Dockerfile
index 76e585a7d0df4893eeda7cc9cd7c1119b9b50d11..45be17259355ac5230e7506d19b7d7bcfbf84ac2 100644
--- a/tools/dockerfile/grpc_base/Dockerfile
+++ b/tools/dockerfile/grpc_base/Dockerfile
@@ -13,7 +13,6 @@ RUN apt-get update && apt-get install -y \
   libc6 \
   libc6-dbg \
   libc6-dev \
-  libevent-dev \
   libtool \
   make \
   strace \
diff --git a/tools/run_tests/run_lcov.sh b/tools/run_tests/run_lcov.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6f22b0e8a4110389edecf92801448a80c1281a03
--- /dev/null
+++ b/tools/run_tests/run_lcov.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -ex
+
+out=`realpath ${1:-coverage}`
+
+root=`realpath $(dirname $0)/../..`
+tmp=`mktemp`
+cd $root
+tools/run_tests/run_tests.py -c gcov
+lcov --capture --directory . --output-file $tmp
+genhtml $tmp --output-directory $out
+rm $tmp
+if which xdg-open > /dev/null
+then
+  xdg-open file://$out/index.html
+fi
+
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 6ab80d90453e5893bacadbf21a6944faecee0f16..bb25b38e57052e10eeda76b196500ae41499c293 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -36,6 +36,7 @@ _CONFIGS = {
   'tsan': SimpleConfig('tsan'),
   'msan': SimpleConfig('msan'),
   'asan': SimpleConfig('asan'),
+  'gcov': SimpleConfig('gcov'),
   'valgrind': ValgrindConfig('dbg'),
   }
 
diff --git a/vsprojects/vs2013/build_and_run_tests.bat b/vsprojects/vs2013/build_and_run_tests.bat
index 06e9776d862c77f79068b37cdbd509a68b22ad64..3e36dcf6002060f35747d06e050f707d57d0db6a 100644
--- a/vsprojects/vs2013/build_and_run_tests.bat
+++ b/vsprojects/vs2013/build_and_run_tests.bat
@@ -5,7 +5,7 @@
 
 @rem Build the library dependencies first
 MSBuild.exe gpr.vcxproj /p:Configuration=Debug
-MSBuild.exe grpc_test_util.vcxproj /p:Configuration=Debug
+MSBuild.exe gpr_test_util.vcxproj /p:Configuration=Debug
 
 mkdir test_bin