diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py
index 7e1ec366f96edea0f6c2cb1dc13cf387db5de576..b7bcb7b02c11a031af7af22c2091eb382bd75988 100755
--- a/tools/run_tests/jobset.py
+++ b/tools/run_tests/jobset.py
@@ -8,7 +8,7 @@ import tempfile
 import time
 
 
-_MAX_JOBS = 16 * multiprocessing.cpu_count()
+_MAX_JOBS = 3
 
 
 def shuffle_iteratable(it):
@@ -108,6 +108,7 @@ class Jobset(object):
     self._check_cancelled = check_cancelled
     self._cancelled = False
     self._failures = 0
+    self._completed = 0
 
   def start(self, cmdline):
     """Start a job. Return True on success, False on failure."""
@@ -128,9 +129,11 @@ class Jobset(object):
         if st == _FAILURE: self._failures += 1
         dead.add(job)
       for job in dead:
+        self._completed += 1
         self._running.remove(job)
       if dead: return
-      message('WAITING', '%d jobs left' % len(self._running))
+      message('WAITING', '%d jobs running, %d complete' % (
+          len(self._running), self._completed))
       time.sleep(0.1)
 
   def cancelled(self):
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index c9aedc1679e234d50f7a05f7f008edd568bc79a1..2bd2e6d830cbf9aaa5d2b163004d8d59cc0609be 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -11,14 +11,42 @@ import time
 import jobset
 import watch_dirs
 
-# flags required for make for each configuration
-_CONFIGS = ['dbg', 'opt', 'tsan', 'msan', 'asan']
+
+# SimpleConfig: just compile with CONFIG=config, and run the binary to test
+class SimpleConfig(object):
+  def __init__(self, config):
+    self.build_config = config
+
+  def run_command(self, binary):
+    return [binary]
+
+
+# ValgrindConfig: compile with some CONFIG=config, but use valgrind to run
+class ValgrindConfig(object):
+  def __init__(self, config):
+    self.build_config = config
+
+  def run_command(self, binary):
+    return ['valgrind', binary]
+
+
+# different configurations we can run under
+_CONFIGS = {
+  'dbg': SimpleConfig('dbg'),
+  'opt': SimpleConfig('opt'),
+  'tsan': SimpleConfig('tsan'),
+  'msan': SimpleConfig('msan'),
+  'asan': SimpleConfig('asan'),
+  'valgrind': ValgrindConfig('dbg'),
+  }
+
+
 _DEFAULT = ['dbg', 'opt']
 
 # parse command line
 argp = argparse.ArgumentParser(description='Run grpc tests.')
 argp.add_argument('-c', '--config',
-                  choices=['all'] + _CONFIGS,
+                  choices=['all'] + sorted(_CONFIGS.keys()),
                   nargs='+',
                   default=_DEFAULT)
 argp.add_argument('-t', '--test-filter', nargs='*', default=['*'])
@@ -30,10 +58,11 @@ argp.add_argument('-f', '--forever',
 args = argp.parse_args()
 
 # grab config
-configs = [cfg
-           for cfg in itertools.chain.from_iterable(
-               _CONFIGS if x == 'all' else [x]
-               for x in args.config)]
+run_configs = set(_CONFIGS[cfg]
+                  for cfg in itertools.chain.from_iterable(
+                      _CONFIGS.iterkeys() if x == 'all' else [x]
+                      for x in args.config))
+build_configs = set(cfg.build_config for cfg in run_configs)
 filters = args.test_filter
 runs_per_test = args.runs_per_test
 forever = args.forever
@@ -44,20 +73,21 @@ def _build_and_run(check_cancelled):
   # build latest, sharing cpu between the various makes
   if not jobset.run(
       (['make',
-        '-j', '%d' % max(multiprocessing.cpu_count() / len(configs), 1),
+        '-j', '%d' % (multiprocessing.cpu_count() + 1),
         'buildtests_c',
         'CONFIG=%s' % cfg]
-       for cfg in configs), check_cancelled):
+       for cfg in build_configs), check_cancelled):
     sys.exit(1)
 
   # run all the tests
-  jobset.run(([x]
-              for x in itertools.chain.from_iterable(
-                  itertools.chain.from_iterable(itertools.repeat(
-                      glob.glob('bins/%s/%s_test' % (config, filt)),
-                      runs_per_test))
-                  for config in configs
-                  for filt in filters)), check_cancelled)
+  jobset.run((
+      config.run_command(x)
+      for config in run_configs
+      for filt in filters
+      for x in itertools.chain.from_iterable(itertools.repeat(
+          glob.glob('bins/%s/%s_test' % (
+              config.build_config, filt)),
+          runs_per_test))), check_cancelled)
 
 
 if forever: