Move recipe content from modules back into recipes

At one point I moved the contents of the recipes into modules so that
they could be shared between repos. It turns out that we don't need
that, and it adds complexity.

Bug: skia:6473
Change-Id: I75a920b6a8474dcdd8b37ee9edd52aac801d1ab0
Reviewed-on: https://skia-review.googlesource.com/12622
Reviewed-by: Ravi Mistry <rmistry@google.com>
Commit-Queue: Eric Boren <borenet@google.com>
diff --git a/infra/bots/recipe_modules/compile/__init__.py b/infra/bots/recipe_modules/compile/__init__.py
deleted file mode 100644
index 0021aea..0000000
--- a/infra/bots/recipe_modules/compile/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-DEPS = [
-  'core',
-  'recipe_engine/json',
-  'recipe_engine/path',
-  'recipe_engine/platform',
-  'recipe_engine/properties',
-  'recipe_engine/python',
-  'recipe_engine/step',
-  'flavor',
-  'run',
-  'vars',
-]
diff --git a/infra/bots/recipe_modules/compile/api.py b/infra/bots/recipe_modules/compile/api.py
deleted file mode 100644
index 917b228..0000000
--- a/infra/bots/recipe_modules/compile/api.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Recipe module for Skia Swarming compile.
-
-
-from recipe_engine import recipe_api
-
-
-def build_targets_from_builder_dict(builder_dict):
-  """Return a list of targets to build, depending on the builder type."""
-  if builder_dict.get('extra_config') == 'iOS':
-    return ['iOSShell']
-  return ['most']
-
-
-def get_extra_env_vars(builder_dict):
-  env = {}
-  if builder_dict.get('compiler') == 'Clang':
-    env['CC'] = '/usr/bin/clang'
-    env['CXX'] = '/usr/bin/clang++'
-
-  # SKNX_NO_SIMD, SK_USE_DISCARDABLE_SCALEDIMAGECACHE, etc.
-  extra_config = builder_dict.get('extra_config', '')
-  if extra_config.startswith('SK') and extra_config.isupper():
-    env['CPPFLAGS'] = '-D' + extra_config
-
-  return env
-
-
-class CompileApi(recipe_api.RecipeApi):
-  def run(self):
-    self.m.core.setup()
-
-    env = get_extra_env_vars(self.m.vars.builder_cfg)
-    build_targets = build_targets_from_builder_dict(self.m.vars.builder_cfg)
-
-    try:
-      for target in build_targets:
-        with self.m.step.context({'env': env}):
-          self.m.flavor.compile(target)
-      self.m.run.copy_build_products(
-          self.m.flavor.out_dir,
-          self.m.vars.swarming_out_dir.join(
-              'out', self.m.vars.configuration))
-      self.m.flavor.copy_extra_build_products(self.m.vars.swarming_out_dir)
-    finally:
-      if 'Win' in self.m.vars.builder_cfg.get('os', ''):
-        self.m.python.inline(
-            name='cleanup',
-            program='''import psutil
-for p in psutil.process_iter():
-  try:
-    if p.name in ('mspdbsrv.exe', 'vctip.exe', 'cl.exe', 'link.exe'):
-      p.kill()
-  except psutil._error.AccessDenied:
-    pass
-''',
-            infra_step=True)
-
-    self.m.flavor.cleanup_steps()
-    self.m.run.check_failure()
diff --git a/infra/bots/recipe_modules/compile/example.py b/infra/bots/recipe_modules/compile/example.py
deleted file mode 100644
index 691be13..0000000
--- a/infra/bots/recipe_modules/compile/example.py
+++ /dev/null
@@ -1,245 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Example recipe w/ coverage.
-
-
-DEPS = [
-  'compile',
-  'recipe_engine/path',
-  'recipe_engine/platform',
-  'recipe_engine/properties',
-]
-
-
-TEST_BUILDERS = {
-  'client.skia.compile': {
-    'skiabot-linux-swarm-000': [
-      'Build-Mac-Clang-arm64-Debug-iOS',
-      'Build-Mac-Clang-mipsel-Debug-GN_Android',
-      'Build-Mac-Clang-x64-Release-iOS',
-      'Build-Mac-Clang-x86_64-Debug-CommandBuffer',
-      'Build-Mac-Clang-x86_64-Release-GN',
-      'Build-Ubuntu-Clang-arm-Release-Chromebook_C100p',
-      'Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot',
-      'Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs',
-      'Build-Ubuntu-Clang-arm64-Release-GN_Android',
-      'Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan',
-      'Build-Ubuntu-Clang-x86_64-Debug-ASAN',
-      'Build-Ubuntu-Clang-x86_64-Debug-GN',
-      'Build-Ubuntu-Clang-x86_64-Release-Mini',
-      'Build-Ubuntu-Clang-x86_64-Release-Vulkan',
-      'Build-Ubuntu-GCC-arm-Release-Chromecast',
-      'Build-Ubuntu-GCC-x86-Debug',
-      'Build-Ubuntu-GCC-x86_64-Debug-GN',
-      'Build-Ubuntu-GCC-x86_64-Debug-MSAN',
-      'Build-Ubuntu-GCC-x86_64-Debug-NoGPU',
-      'Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE',
-      'Build-Ubuntu-GCC-x86_64-Release-ANGLE',
-      'Build-Ubuntu-GCC-x86_64-Release-Fast',
-      'Build-Ubuntu-GCC-x86_64-Release-Flutter_Android',
-      'Build-Ubuntu-GCC-x86_64-Release-Mesa',
-      'Build-Ubuntu-GCC-x86_64-Release-PDFium',
-      'Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths',
-      'Build-Ubuntu-GCC-x86_64-Release-Shared',
-      'Build-Ubuntu-GCC-x86_64-Release-Valgrind',
-      'Build-Win-Clang-arm64-Release-GN_Android',
-      'Build-Win-MSVC-x86-Debug',
-      'Build-Win-MSVC-x86-Debug-ANGLE',
-      'Build-Win-MSVC-x86-Debug-Exceptions',
-      'Build-Win-MSVC-x86-Release-GDI',
-      'Build-Win-MSVC-x86-Release-GN',
-      'Build-Win-MSVC-x86_64-Release-Vulkan',
-    ],
-  },
-}
-
-
-def RunSteps(api):
-  api.compile.run()
-
-
-def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         repository='https://skia.googlesource.com/skia.git',
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-          )
-        )
-        if 'Win' in builder:
-          test += api.platform('win', 64)
-        elif 'Mac' in builder:
-          test += api.platform('mac', 64)
-        else:
-          test += api.platform('linux', 64)
-        if 'Trybot' in builder:
-          test += api.properties(issue=500,
-                                 patchset=1,
-                                 rietveld='https://codereview.chromium.org')
-
-        yield test
-
-  mastername = 'client.skia.compile'
-  slavename = 'skiabot-win-compile-000'
-  buildername = 'Build-Win-MSVC-x86-Debug'
-  yield (
-      api.test('big_issue_number') +
-      api.properties(buildername=buildername,
-                     mastername=mastername,
-                     slavename=slavename,
-                     buildnumber=5,
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]',
-                     rietveld='https://codereview.chromium.org',
-                     patchset=1,
-                     issue=2147533002L) +
-      api.path.exists(
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      ) +
-      api.platform('win', 64)
-  )
-
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=buildername + '-Trybot',
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          repository='https://skia.googlesource.com/skia.git',
-          revision='abc123',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=buildername + '-Trybot',
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.platform('win', 64)
-  )
-
-  yield (
-      api.test('buildbotless_trybot_rietveld') +
-      api.properties(
-          buildername=buildername,
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          repository='https://skia.googlesource.com/skia.git',
-          revision='abc123',
-          nobuildbot='True',
-          issue=500,
-          patchset=1,
-          patch_storage='rietveld',
-          rietveld='https://codereview.chromium.org') +
-      api.platform('win', 64)
-  )
-
-  yield (
-      api.test('buildbotless_trybot_gerrit') +
-      api.properties(
-          repository='https://skia.googlesource.com/skia.git',
-          buildername=buildername,
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          patch_issue=500,
-          patch_set=1,
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=buildername,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.platform('win', 64)
-  )
-
-  buildername = 'Build-Win-MSVC-x86_64-Release-Vulkan'
-  yield (
-      api.test('alternate_repo') +
-      api.properties(buildername=buildername,
-                     mastername=mastername,
-                     slavename=slavename,
-                     buildnumber=5,
-                     repository='https://skia.googlesource.com/other_repo.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.path.exists(
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      )
-    )
-
-  buildername = 'Build-Ubuntu-GCC-x86_64-Release-PDFium'
-  yield (
-      api.test('pdfium_trybot') +
-      api.properties(
-          repository='https://skia.googlesource.com/skia.git',
-          buildername=buildername,
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          patch_issue=500,
-          patch_set=1,
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=buildername,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.path.exists(
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      )
-  )
-
-  buildername = 'Build-Ubuntu-GCC-x86_64-Release-Flutter_Android'
-  yield (
-      api.test('flutter_trybot') +
-      api.properties(
-          repository='https://skia.googlesource.com/skia.git',
-          buildername=buildername,
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          patch_issue=500,
-          patch_set=1,
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=buildername,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.path.exists(
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      )
-  )
diff --git a/infra/bots/recipe_modules/perf/__init__.py b/infra/bots/recipe_modules/perf/__init__.py
deleted file mode 100644
index 97846ca..0000000
--- a/infra/bots/recipe_modules/perf/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-DEPS = [
-  'build/file',
-  'core',
-  'recipe_engine/json',
-  'recipe_engine/path',
-  'recipe_engine/platform',
-  'recipe_engine/properties',
-  'recipe_engine/raw_io',
-  'recipe_engine/step',
-  'recipe_engine/time',
-  'run',
-  'flavor',
-  'vars',
-]
diff --git a/infra/bots/recipe_modules/perf/api.py b/infra/bots/recipe_modules/perf/api.py
deleted file mode 100644
index 05732b0..0000000
--- a/infra/bots/recipe_modules/perf/api.py
+++ /dev/null
@@ -1,297 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Recipe module for Skia Swarming perf.
-
-
-import calendar
-
-from recipe_engine import recipe_api
-
-
-def nanobench_flags(bot):
-  args = ['--pre_log']
-
-  if 'GPU' in bot:
-    args.append('--images')
-    args.extend(['--gpuStatsDump', 'true'])
-
-  if 'Android' in bot and 'GPU' in bot:
-    args.extend(['--useThermalManager', '1,1,10,1000'])
-
-  args.extend(['--scales', '1.0', '1.1'])
-
-  if 'iOS' in bot:
-    args.extend(['--skps', 'ignore_skps'])
-
-  configs = ['8888', 'nonrendering', 'hwui' ]
-
-  if '-arm-' not in bot:
-    # For Android CPU tests, these take too long and cause the task to time out.
-    configs += [ 'f16', 'srgb' ]
-  if '-GCE-' in bot:
-    configs += [ '565' ]
-
-  gl_prefix = 'gl'
-  sample_count = '8'
-  if 'Android' in bot or 'iOS' in bot:
-    sample_count = '4'
-    # The NVIDIA_Shield has a regular OpenGL implementation. We bench that
-    # instead of ES.
-    if 'NVIDIA_Shield' not in bot:
-      gl_prefix = 'gles'
-    # The NP produces a long error stream when we run with MSAA.
-    # iOS crashes (skia:6399)
-    if 'NexusPlayer' in bot or 'iOS' in bot:
-      sample_count = ''
-  elif 'Intel' in bot:
-    sample_count = ''
-  elif 'ChromeOS' in bot:
-    gl_prefix = 'gles'
-
-  configs.append(gl_prefix)
-  if sample_count is not '':
-    configs.extend([gl_prefix + 'msaa' + sample_count,
-      gl_prefix + 'nvpr' + sample_count,
-      gl_prefix + 'nvprdit' + sample_count])
-
-  # We want to test both the OpenGL config and the GLES config on Linux Intel:
-  # GL is used by Chrome, GLES is used by ChromeOS.
-  if 'Intel' in bot and 'Ubuntu' in bot:
-    configs.append('gles')
-
-  # Bench instanced rendering on a limited number of platforms
-  inst_config = gl_prefix + 'inst'
-  if 'Nexus6' in bot:
-    configs.append(inst_config) # msaa inst isn't working yet on Adreno.
-  elif 'PixelC' in bot or 'NVIDIA_Shield' in bot or 'MacMini6.2' in bot:
-    configs.extend([inst_config, inst_config + sample_count])
-
-  if 'CommandBuffer' in bot:
-    configs = ['commandbuffer']
-  if 'Vulkan' in bot:
-    configs = ['vk']
-
-  if 'ANGLE' in bot:
-    # Test only ANGLE configs.
-    configs = ['angle_d3d11_es2']
-    if sample_count is not '':
-      configs.append('angle_d3d11_es2_msaa' + sample_count)
-
-  if 'ChromeOS' in bot:
-    # Just run GLES for now - maybe add gles_msaa4 in the future
-    configs = ['gles']
-
-  args.append('--config')
-  args.extend(configs)
-
-  if 'Valgrind' in bot:
-    # Don't care about Valgrind performance.
-    args.extend(['--loops',   '1'])
-    args.extend(['--samples', '1'])
-    # Ensure that the bot framework does not think we have timed out.
-    args.extend(['--keepAlive', 'true'])
-
-  match = []
-  if 'Android' in bot:
-    # Segfaults when run as GPU bench. Very large texture?
-    match.append('~blurroundrect')
-    match.append('~patch_grid')  # skia:2847
-    match.append('~desk_carsvg')
-  if 'NexusPlayer' in bot:
-    match.append('~desk_unicodetable')
-  if 'Nexus5' in bot:
-    match.append('~keymobi_shop_mobileweb_ebay_com.skp')  # skia:5178
-  if 'iOS' in bot:
-    match.append('~blurroundrect')
-    match.append('~patch_grid')  # skia:2847
-    match.append('~desk_carsvg')
-    match.append('~keymobi')
-    match.append('~path_hairline')
-    match.append('~GLInstancedArraysBench') # skia:4714
-  if 'IntelIris540' in bot and 'ANGLE' in bot:
-    match.append('~tile_image_filter_tiled_64')  # skia:6082
-  if 'Intel' in bot and 'Ubuntu' in bot and not 'Vulkan' in bot:
-    match.append('~native_image_to_raster_surface')  # skia:6401
-  if 'Vulkan' in bot and 'IntelIris540' in bot and 'Win' in bot:
-    # skia:6398
-    match.append('~GM_varied_text_clipped_lcd')
-    match.append('~GM_varied_text_ignorable_clip_lcd')
-    match.append('~Xfermode_DstATop_aa')
-    match.append('~Xfermode_SrcIn_aa')
-    match.append('~Xfermode_SrcOut_aa')
-    match.append('~Xfermode_Src_aa')
-    match.append('~fontscaler_lcd')
-    match.append('~rotated_rects_aa_alternating_transparent_and_opaque_src')
-    match.append('~rotated_rects_aa_changing_transparent_src')
-    match.append('~rotated_rects_aa_same_transparent_src')
-    match.append('~shadermask_LCD_FF')
-    match.append('~srcmode_rects_1')
-    match.append('~text_16_LCD_88')
-    match.append('~text_16_LCD_BK')
-    match.append('~text_16_LCD_FF')
-    match.append('~text_16_LCD_WT')
-  if 'Vulkan' in bot and 'NexusPlayer' in bot:
-    match.append('~hardstop') # skia:6037
-  if 'ANGLE' in bot and any('msaa' in x for x in configs):
-    match.append('~native_image_to_raster_surface')  # skia:6457
-
-  # We do not need or want to benchmark the decodes of incomplete images.
-  # In fact, in nanobench we assert that the full image decode succeeds.
-  match.append('~inc0.gif')
-  match.append('~inc1.gif')
-  match.append('~incInterlaced.gif')
-  match.append('~inc0.jpg')
-  match.append('~incGray.jpg')
-  match.append('~inc0.wbmp')
-  match.append('~inc1.wbmp')
-  match.append('~inc0.webp')
-  match.append('~inc1.webp')
-  match.append('~inc0.ico')
-  match.append('~inc1.ico')
-  match.append('~inc0.png')
-  match.append('~inc1.png')
-  match.append('~inc2.png')
-  match.append('~inc12.png')
-  match.append('~inc13.png')
-  match.append('~inc14.png')
-  match.append('~inc0.webp')
-  match.append('~inc1.webp')
-
-  if match:
-    args.append('--match')
-    args.extend(match)
-
-  return args
-
-
-def perf_steps(api):
-  """Run Skia benchmarks."""
-  if api.vars.upload_perf_results:
-    api.flavor.create_clean_device_dir(
-        api.flavor.device_dirs.perf_data_dir)
-
-  # Run nanobench.
-  properties = [
-    '--properties',
-    'gitHash',      api.vars.got_revision,
-    'build_number', api.vars.build_number,
-  ]
-  if api.vars.is_trybot:
-    properties.extend([
-      'issue',    api.vars.issue,
-      'patchset', api.vars.patchset,
-      'patch_storage', api.vars.patch_storage,
-    ])
-  if api.vars.no_buildbot:
-    properties.extend(['no_buildbot', 'True'])
-    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
-    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
-
-  target = 'nanobench'
-  args = [
-      target,
-      '--undefok',   # This helps branches that may not know new flags.
-      '-i',       api.flavor.device_dirs.resource_dir,
-      '--skps',   api.flavor.device_dirs.skp_dir,
-      '--images', api.flavor.device_path_join(
-          api.flavor.device_dirs.images_dir, 'nanobench'),
-  ]
-
-  # Do not run svgs on Valgrind.
-  if 'Valgrind' not in api.vars.builder_name:
-    if ('Vulkan' not in api.vars.builder_name or
-        'NexusPlayer' not in api.vars.builder_name):
-      args.extend(['--svgs',  api.flavor.device_dirs.svg_dir])
-
-  skip_flag = None
-  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
-    skip_flag = '--nogpu'
-  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
-    skip_flag = '--nocpu'
-  if skip_flag:
-    args.append(skip_flag)
-  args.extend(nanobench_flags(api.vars.builder_name))
-
-  if 'Chromecast' in api.vars.builder_cfg.get('os', ''):
-    # Due to limited disk space, run a watered down perf run on Chromecast.
-    args = [
-      target,
-       '-i', api.flavor.device_dirs.resource_dir,
-       '--images', api.flavor.device_path_join(
-            api.flavor.device_dirs.resource_dir, 'color_wheel.jpg'),
-       '--svgs',  api.flavor.device_dirs.svg_dir,
-    ]
-
-  if api.vars.upload_perf_results:
-    now = api.time.utcnow()
-    ts = int(calendar.timegm(now.utctimetuple()))
-    json_path = api.flavor.device_path_join(
-        api.flavor.device_dirs.perf_data_dir,
-        'nanobench_%s_%d.json' % (api.vars.got_revision, ts))
-    args.extend(['--outResultsFile', json_path])
-    args.extend(properties)
-
-    keys_blacklist = ['configuration', 'role', 'is_trybot']
-    args.append('--key')
-    for k in sorted(api.vars.builder_cfg.keys()):
-      if not k in keys_blacklist:
-        args.extend([k, api.vars.builder_cfg[k]])
-
-  env = api.step.get_from_context('env', {})
-  if 'Ubuntu16' in api.vars.builder_name:
-    # The vulkan in this asset name simply means that the graphics driver
-    # supports Vulkan. It is also the driver used for GL code.
-    dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_release')
-    if 'Debug' in api.vars.builder_name:
-      dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_debug')
-
-    if 'Vulkan' in api.vars.builder_name:
-      sdk_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'bin')
-      lib_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'lib')
-      env.update({
-        'PATH':'%%(PATH)s:%s' % sdk_path,
-        'LD_LIBRARY_PATH': '%s:%s' % (lib_path, dri_path),
-        'LIBGL_DRIVERS_PATH': dri_path,
-        'VK_ICD_FILENAMES':'%s' % dri_path.join('intel_icd.x86_64.json'),
-      })
-    else:
-      # Even the non-vulkan NUC jobs could benefit from the newer drivers.
-      env.update({
-        'LD_LIBRARY_PATH': dri_path,
-        'LIBGL_DRIVERS_PATH': dri_path,
-      })
-
-  # See skia:2789.
-  if '_AbandonGpuContext' in api.vars.builder_cfg.get('extra_config', ''):
-    args.extend(['--abandonGpuContext', '--nocpu'])
-
-  with api.step.context({'env': env}):
-    api.run(api.flavor.step, target, cmd=args,
-            abort_on_failure=False)
-
-  # Copy results to swarming out dir.
-  if api.vars.upload_perf_results:
-    api.file.makedirs('perf_dir', api.vars.perf_data_dir)
-    api.flavor.copy_directory_contents_to_host(
-        api.flavor.device_dirs.perf_data_dir,
-        api.vars.perf_data_dir)
-
-class PerfApi(recipe_api.RecipeApi):
-  def run(self):
-    self.m.core.setup()
-    env = self.m.step.get_from_context('env', {})
-    if 'iOS' in self.m.vars.builder_name:
-      env['IOS_BUNDLE_ID'] = 'com.google.nanobench'
-    with self.m.step.context({'env': env}):
-      try:
-        if 'Chromecast' in self.m.vars.builder_name:
-          self.m.flavor.install(resources=True, skps=True)
-        else:
-          self.m.flavor.install_everything()
-        perf_steps(self.m)
-      finally:
-        self.m.flavor.cleanup_steps()
-      self.m.run.check_failure()
diff --git a/infra/bots/recipe_modules/perf/example.py b/infra/bots/recipe_modules/perf/example.py
deleted file mode 100644
index 169a25c..0000000
--- a/infra/bots/recipe_modules/perf/example.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Example recipe w/ coverage.
-
-
-DEPS = [
-  'perf',
-  'recipe_engine/path',
-  'recipe_engine/platform',
-  'recipe_engine/properties',
-  'recipe_engine/raw_io',
-]
-
-
-TEST_BUILDERS = {
-  'client.skia': {
-    'skiabot-linux-swarm-000': [
-      ('Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug' +
-       '-GN_Android_Vulkan'),
-      'Perf-Android-Clang-Nexus10-CPU-Exynos5250-arm-Release-Android',
-      'Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android',
-      'Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android',
-      'Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android',
-      'Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android',
-      ('Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_'
-       'Android_Vulkan'),
-      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android',
-      'Perf-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Release',
-      'Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Debug',
-      'Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Release',
-      'Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN',
-      'Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
-      'Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN',
-      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE',
-      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
-      ('Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind' +
-       '_AbandonGpuContext'),
-      'Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan',
-      'Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release',
-      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug',
-      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release',
-      'Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE',
-      'Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan',
-      'Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
-      'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot',
-      'Perf-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release'
-    ],
-  },
-}
-
-
-def RunSteps(api):
-  api.perf.run()
-
-
-def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('skia'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skimage', 'VERSION'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skp', 'VERSION'),
-              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-          )
-        )
-        if 'Trybot' in builder:
-          test += api.properties(issue=500,
-                                 patchset=1,
-                                 rietveld='https://codereview.chromium.org')
-        if 'Win' in builder:
-          test += api.platform('win', 64)
-
-        if 'Chromecast' in builder:
-          test += api.step_data('read chromecast ip',
-                  stdout=api.raw_io.output('192.168.1.2:5555'))
-
-        if 'ChromeOS' in builder:
-          test += api.step_data('read chromeos ip',
-                  stdout=api.raw_io.output('{"user_ip":"foo@127.0.0.1"}'))
-
-        yield test
-
-  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
-  yield (
-    api.test('big_issue_number') +
-    api.properties(buildername=builder,
-                   mastername='client.skia.compile',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=5,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]',
-                   rietveld='https://codereview.chromium.org',
-                   patchset=1,
-                   issue=2147533002L) +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.platform('win', 64)
-  )
-
-  builder = ('Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind-'
-             'Trybot')
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
-
-  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
-  yield (
-      api.test('nobuildbot') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          revision='abc123',
-          path_config='kitchen',
-          nobuildbot='True',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.path.exists(
-          api.path['start_dir'].join('skia'),
-          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                       'skimage', 'VERSION'),
-          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                       'skp', 'VERSION'),
-          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                       'svg', 'VERSION'),
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      ) +
-      api.platform('win', 64) +
-      api.step_data('get swarming bot id',
-          stdout=api.raw_io.output('skia-bot-123')) +
-      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
-  )
-
-  builder = 'Perf-Android-Clang-NexusPlayer-CPU-SSE4-x86-Debug-GN_Android'
-  yield (
-    api.test('failed_push') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('push [START_DIR]/skia/resources/* '+
-                  '/sdcard/revenge_of_the_skiabot/resources', retcode=1)
-  )
diff --git a/infra/bots/recipe_modules/skpbench/__init__.py b/infra/bots/recipe_modules/skpbench/__init__.py
deleted file mode 100644
index f2a0bfe..0000000
--- a/infra/bots/recipe_modules/skpbench/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-DEPS = [
-  'build/file',
-  'core',
-  'recipe_engine/path',
-  'recipe_engine/properties',
-  'recipe_engine/python',
-  'recipe_engine/raw_io',
-  'recipe_engine/step',
-  'recipe_engine/time',
-  'run',
-  'flavor',
-  'vars',
-]
diff --git a/infra/bots/recipe_modules/skpbench/api.py b/infra/bots/recipe_modules/skpbench/api.py
deleted file mode 100644
index 5c2a696..0000000
--- a/infra/bots/recipe_modules/skpbench/api.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Recipe module for Skia Swarming skpbench.
-
-
-import calendar
-
-from recipe_engine import recipe_api
-
-
-def _run(api, title, *cmd, **kwargs):
-  with api.step.context({'cwd': api.vars.skia_dir}):
-    return api.run(api.step, title, cmd=list(cmd), **kwargs)
-
-
-def _adb(api, title, *cmd, **kwargs):
-  if 'infra_step' not in kwargs:
-    kwargs['infra_step'] = True
-  return _run(api, title, 'adb', *cmd, **kwargs)
-
-
-def skpbench_steps(api):
-  """benchmark Skia using skpbench."""
-  app = api.vars.skia_out.join(api.vars.configuration, 'skpbench')
-  _adb(api, 'push skpbench', 'push', app, api.vars.android_bin_dir)
-
-  skpbench_dir = api.vars.slave_dir.join('skia', 'tools', 'skpbench')
-  table = api.path.join(api.vars.swarming_out_dir, 'table')
-
-  config = 'gles,glesinst4'
-  if 'Vulkan' in api.vars.builder_name:
-    config = 'vk'
-
-  skpbench_args = [
-        api.path.join(api.vars.android_bin_dir, 'skpbench'),
-        api.path.join(api.vars.android_data_dir, 'skps'),
-        '--adb',
-        '--resultsfile', table,
-        '--config', config]
-
-  api.run(api.python, 'skpbench',
-      script=skpbench_dir.join('skpbench.py'),
-      args=skpbench_args)
-
-  skiaperf_args = [
-    table,
-    '--properties',
-    'gitHash',      api.vars.got_revision,
-    'build_number', api.vars.build_number,
-  ]
-
-  skiaperf_args.extend(['no_buildbot', 'True'])
-  skiaperf_args.extend(['swarming_bot_id', api.vars.swarming_bot_id])
-  skiaperf_args.extend(['swarming_task_id', api.vars.swarming_task_id])
-
-  now = api.time.utcnow()
-  ts = int(calendar.timegm(now.utctimetuple()))
-  api.file.makedirs('perf_dir', api.vars.perf_data_dir)
-  json_path = api.path.join(
-      api.vars.perf_data_dir,
-      'skpbench_%s_%d.json' % (api.vars.got_revision, ts))
-
-  skiaperf_args.extend([
-    '--outfile', json_path
-  ])
-
-  keys_blacklist = ['configuration', 'role', 'is_trybot']
-  skiaperf_args.append('--key')
-  for k in sorted(api.vars.builder_cfg.keys()):
-    if not k in keys_blacklist:
-      skiaperf_args.extend([k, api.vars.builder_cfg[k]])
-
-  api.run(api.python, 'Parse skpbench output into Perf json',
-      script=skpbench_dir.join('skiaperf.py'),
-      args=skiaperf_args)
-
-
-class SkpBenchApi(recipe_api.RecipeApi):
-  def run(self):
-    self.m.core.setup()
-    try:
-      self.m.flavor.install(skps=True)
-      skpbench_steps(self.m)
-    finally:
-      self.m.flavor.cleanup_steps()
-    self.m.run.check_failure()
diff --git a/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench.json b/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench.json
deleted file mode 100644
index 807b3d0..0000000
--- a/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench.json
+++ /dev/null
@@ -1,437 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[START_DIR]/tmp",
-      "511"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "adb",
-      "reboot"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "rebooting device"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport subprocess\nimport sys\nimport time\n\nkicks = 0\nwhile True:\n\n  times = 0\n  while times < 30:\n    print 'Waiting for the device to be connected and ready.'\n    try:\n      times += 1\n      output = subprocess.check_output(['adb', 'shell',\n                                        'getprop', 'sys.boot_completed'])\n      if '1' in output:\n        print 'Connected'\n        sys.exit(0)\n    except subprocess.CalledProcessError:\n      # no device connected/authorized yet\n      pass\n    time.sleep(5)\n  if kicks >= 3:\n    break\n  print 'Giving the device a \"kick\" by trying to reboot it.'\n  kicks += 1\n  print subprocess.check_output(['adb', 'reboot'])\n\nprint 'Timed out waiting for device'\nsys.exit(1)\n"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "wait for device",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@kicks = 0@@@",
-      "@@@STEP_LOG_LINE@python.inline@while True:@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@  times = 0@@@",
-      "@@@STEP_LOG_LINE@python.inline@  while times < 30:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Waiting for the device to be connected and ready.'@@@",
-      "@@@STEP_LOG_LINE@python.inline@    try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      times += 1@@@",
-      "@@@STEP_LOG_LINE@python.inline@      output = subprocess.check_output(['adb', 'shell',@@@",
-      "@@@STEP_LOG_LINE@python.inline@                                        'getprop', 'sys.boot_completed'])@@@",
-      "@@@STEP_LOG_LINE@python.inline@      if '1' in output:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        print 'Connected'@@@",
-      "@@@STEP_LOG_LINE@python.inline@        sys.exit(0)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    except subprocess.CalledProcessError:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      # no device connected/authorized yet@@@",
-      "@@@STEP_LOG_LINE@python.inline@      pass@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(5)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if kicks >= 3:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  print 'Giving the device a \"kick\" by trying to reboot it.'@@@",
-      "@@@STEP_LOG_LINE@python.inline@  kicks += 1@@@",
-      "@@@STEP_LOG_LINE@python.inline@  print subprocess.check_output(['adb', 'reboot'])@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@print 'Timed out waiting for device'@@@",
-      "@@@STEP_LOG_LINE@python.inline@sys.exit(1)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "mkdir",
-      "-p",
-      "/sdcard/revenge_of_the_skiabot/resources"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded SKP VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "cat",
-      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
-    "stdout": "/path/to/tmp/"
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "rm",
-      "-f",
-      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "rm",
-      "-rf",
-      "/sdcard/revenge_of_the_skiabot/skps"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "rm /sdcard/revenge_of_the_skiabot/skps"
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "mkdir",
-      "-p",
-      "/sdcard/revenge_of_the_skiabot/skps"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
-      "[START_DIR]/skp",
-      "/sdcard/revenge_of_the_skiabot/skps"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
-      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['adb', 'push',@@@",
-      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
-      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "adb",
-      "push",
-      "[START_DIR]/tmp/SKP_VERSION",
-      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "adb",
-      "push",
-      "[START_DIR]/out/Release/skpbench",
-      "/data/local/tmp/"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "push skpbench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[START_DIR]/skia/tools/skpbench/skpbench.py",
-      "/data/local/tmp/skpbench",
-      "/sdcard/revenge_of_the_skiabot/skps",
-      "--adb",
-      "--resultsfile",
-      "[CUSTOM_[SWARM_OUT_DIR]]/table",
-      "--config",
-      "gles,glesinst4"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "name": "skpbench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench/data",
-      "511"
-    ],
-    "name": "makedirs perf_dir",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[START_DIR]/skia/tools/skpbench/skiaperf.py",
-      "[CUSTOM_[SWARM_OUT_DIR]]/table",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "build_number",
-      "5",
-      "no_buildbot",
-      "True",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--outfile",
-      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench/data/skpbench_abc123_1337000001.json",
-      "--key",
-      "arch",
-      "arm64",
-      "compiler",
-      "Clang",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "TegraX1",
-      "extra_config",
-      "GN_Android_Skpbench",
-      "model",
-      "PixelC",
-      "os",
-      "Android"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "name": "Parse skpbench output into Perf json"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
-      "[START_DIR]/out/Release"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "dump log",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@out = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output(['adb', 'logcat', '-d'])@@@",
-      "@@@STEP_LOG_LINE@python.inline@for line in log.split('\\n'):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  tokens = line.split()@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':@@@",
-      "@@@STEP_LOG_LINE@python.inline@    addr, path = tokens[-2:]@@@",
-      "@@@STEP_LOG_LINE@python.inline@    local = os.path.join(out, os.path.basename(path))@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if os.path.exists(local):@@@",
-      "@@@STEP_LOG_LINE@python.inline@      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])@@@",
-      "@@@STEP_LOG_LINE@python.inline@      line = line.replace(addr, addr + ' ' + sym.strip())@@@",
-      "@@@STEP_LOG_LINE@python.inline@  print line@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "adb",
-      "kill-server"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "kill adb server"
-  },
-  {
-    "name": "$result",
-    "recipe_result": null,
-    "status_code": 0
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/skpbench/example.py b/infra/bots/recipe_modules/skpbench/example.py
deleted file mode 100644
index 4acf916..0000000
--- a/infra/bots/recipe_modules/skpbench/example.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Example recipe w/ coverage.
-
-
-DEPS = [
-  'recipe_engine/path',
-  'recipe_engine/properties',
-  'recipe_engine/raw_io',
-  'skpbench',
-]
-
-
-TEST_BUILDERS = {
-  'client.skia': {
-    'skiabot-linux-swarm-000': [
-      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench',
-      ('Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-' +
-      'GN_Android_Vulkan_Skpbench'),
-    ],
-  },
-}
-
-
-def RunSteps(api):
-  api.skpbench.run()
-
-
-def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('skia'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skp', 'VERSION'),
-          ) +
-          api.step_data('get swarming bot id',
-              stdout=api.raw_io.output('skia-bot-123')) +
-          api.step_data('get swarming task id',
-              stdout=api.raw_io.output('123456'))
-        )
-
-        yield test
diff --git a/infra/bots/recipe_modules/sktest/__init__.py b/infra/bots/recipe_modules/sktest/__init__.py
deleted file mode 100644
index c97fd57..0000000
--- a/infra/bots/recipe_modules/sktest/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-DEPS = [
-  'build/file',
-  'core',
-  'recipe_engine/json',
-  'recipe_engine/path',
-  'recipe_engine/platform',
-  'recipe_engine/properties',
-  'recipe_engine/python',
-  'recipe_engine/raw_io',
-  'recipe_engine/step',
-  'flavor',
-  'run',
-  'vars',
-]
diff --git a/infra/bots/recipe_modules/sktest/api.py b/infra/bots/recipe_modules/sktest/api.py
deleted file mode 100644
index 69e30cd..0000000
--- a/infra/bots/recipe_modules/sktest/api.py
+++ /dev/null
@@ -1,696 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Recipe module for Skia Swarming test.
-
-
-from recipe_engine import recipe_api
-
-
-def dm_flags(bot):
-  args = []
-
-  # This enables non-deterministic random seeding of the GPU FP optimization
-  # test. Limit testing until we're sure it's not going to cause an
-  # avalanche of problems.
-  if 'Ubuntu' in bot or 'Win' in bot or 'Mac' in bot or 'iOS' in bot:
-    args.append('--randomProcessorTest')
-
-  # 32-bit desktop bots tend to run out of memory, because they have relatively
-  # far more cores than RAM (e.g. 32 cores, 3G RAM).  Hold them back a bit.
-  if '-x86-' in bot and not 'NexusPlayer' in bot:
-    args.extend('--threads 4'.split(' '))
-
-  # Avoid issues with dynamically exceeding resource cache limits.
-  if 'Test' in bot and 'DISCARDABLE' in bot:
-    args.extend('--threads 0'.split(' '))
-
-  # These are the canonical configs that we would ideally run on all bots. We
-  # may opt out or substitute some below for specific bots
-  configs = ['8888', 'srgb', 'pdf']
-  # Add in either gles or gl configs to the canonical set based on OS
-  sample_count = '8'
-  gl_prefix = 'gl'
-  if 'Android' in bot or 'iOS' in bot:
-    sample_count = '4'
-    # We want to test the OpenGL config not the GLES config on the Shield
-    if 'NVIDIA_Shield' not in bot:
-      gl_prefix = 'gles'
-  elif 'Intel' in bot:
-    sample_count = ''
-  elif 'ChromeOS' in bot:
-    gl_prefix = 'gles'
-
-  configs.extend([gl_prefix, gl_prefix + 'dft', gl_prefix + 'srgb'])
-  if sample_count is not '':
-    configs.append(gl_prefix + 'msaa' + sample_count)
-
-  # The NP produces a long error stream when we run with MSAA. The Tegra3 just
-  # doesn't support it.
-  if ('NexusPlayer' in bot or
-      'Tegra3'      in bot or
-      # We aren't interested in fixing msaa bugs on iPad4.
-      'iPad4' in bot or
-      # skia:5792
-      'iHD530'       in bot or
-      'IntelIris540' in bot):
-    configs = [x for x in configs if 'msaa' not in x]
-
-  # The NP produces different images for dft on every run.
-  if 'NexusPlayer' in bot:
-    configs = [x for x in configs if 'dft' not in x]
-
-  # Runs out of memory on Android bots.  Everyone else seems fine.
-  if 'Android' in bot:
-    configs.remove('pdf')
-
-  if '-GCE-' in bot:
-    configs.extend(['565'])
-    configs.extend(['f16'])
-    configs.extend(['sp-8888', '2ndpic-8888'])   # Test niche uses of SkPicture.
-    configs.extend(['lite-8888'])                # Experimental display list.
-    configs.extend(['gbr-8888'])
-
-  if '-TSAN' not in bot and sample_count is not '':
-    if ('TegraK1'  in bot or
-        'TegraX1'  in bot or
-        'GTX550Ti' in bot or
-        'GTX660'   in bot or
-        'GT610'    in bot):
-      configs.append(gl_prefix + 'nvprdit' + sample_count)
-
-  # We want to test both the OpenGL config and the GLES config on Linux Intel:
-  # GL is used by Chrome, GLES is used by ChromeOS.
-  if 'Intel' in bot and 'Ubuntu' in bot:
-    configs.extend(['gles', 'glesdft', 'glessrgb'])
-
-  # NP is running out of RAM when we run all these modes.  skia:3255
-  if 'NexusPlayer' not in bot:
-    configs.extend(mode + '-8888' for mode in
-                   ['serialize', 'tiles_rt', 'pic'])
-
-  # Test instanced rendering on a limited number of platforms
-  if 'Nexus6' in bot:
-    configs.append(gl_prefix + 'inst') # inst msaa isn't working yet on Adreno.
-  elif 'NVIDIA_Shield' in bot or 'PixelC' in bot:
-    # Multisampled instanced configs use nvpr so we substitute inst msaa
-    # configs for nvpr msaa configs.
-    old = gl_prefix + 'nvpr'
-    new = gl_prefix + 'inst'
-    configs = [x.replace(old, new) for x in configs]
-    # We also test non-msaa instanced.
-    configs.append(new)
-  elif 'MacMini6.2' in bot and sample_count is not '':
-    configs.extend([gl_prefix + 'inst', gl_prefix + 'inst' + sample_count])
-
-  # CommandBuffer bot *only* runs the command_buffer config.
-  if 'CommandBuffer' in bot:
-    configs = ['commandbuffer']
-
-  # ANGLE bot *only* runs the angle configs
-  if 'ANGLE' in bot:
-    configs = ['angle_d3d11_es2',
-               'angle_d3d9_es2',
-               'angle_gl_es2']
-    if sample_count is not '':
-      configs.append('angle_d3d11_es2_msaa' + sample_count)
-
-  # Vulkan bot *only* runs the vk config.
-  if 'Vulkan' in bot:
-    configs = ['vk']
-
-  if 'ChromeOS' in bot:
-    # Just run GLES for now - maybe add gles_msaa4 in the future
-    configs = ['gles']
-
-  args.append('--config')
-  args.extend(configs)
-
-  # Run tests, gms, and image decoding tests everywhere.
-  args.extend('--src tests gm image colorImage svg'.split(' '))
-  if 'Vulkan' in bot and 'NexusPlayer' in bot:
-    args.remove('svg')
-    args.remove('image')
-
-  blacklisted = []
-  def blacklist(quad):
-    config, src, options, name = quad.split(' ') if type(quad) is str else quad
-    if config == '_' or config in configs:
-      blacklisted.extend([config, src, options, name])
-
-  # TODO: ???
-  blacklist('f16 _ _ dstreadshuffle')
-  blacklist('glsrgb image _ _')
-  blacklist('glessrgb image _ _')
-
-  # Decoder tests are now performing gamma correct decodes.  This means
-  # that, when viewing the results, we need to perform a gamma correct
-  # encode to PNG.  Therefore, we run the image tests in srgb mode instead
-  # of 8888.
-  blacklist('8888 image _ _')
-
-  # Not any point to running these.
-  blacklist('gbr-8888 image _ _')
-  blacklist('gbr-8888 colorImage _ _')
-
-  if 'Valgrind' in bot:
-    # These take 18+ hours to run.
-    blacklist('pdf gm _ fontmgr_iter')
-    blacklist('pdf _ _ PANO_20121023_214540.jpg')
-    blacklist('pdf skp _ worldjournal')
-    blacklist('pdf skp _ desk_baidu.skp')
-    blacklist('pdf skp _ desk_wikipedia.skp')
-    blacklist('_ svg _ _')
-
-  if 'iOS' in bot:
-    blacklist(gl_prefix + ' skp _ _')
-
-  if 'Mac' in bot or 'iOS' in bot:
-    # CG fails on questionable bmps
-    blacklist('_ image gen_platf rgba32abf.bmp')
-    blacklist('_ image gen_platf rgb24prof.bmp')
-    blacklist('_ image gen_platf rgb24lprof.bmp')
-    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
-
-    # CG has unpredictable behavior on this questionable gif
-    # It's probably using uninitialized memory
-    blacklist('_ image gen_platf frame_larger_than_image.gif')
-
-    # CG has unpredictable behavior on incomplete pngs
-    # skbug.com/5774
-    blacklist('_ image gen_platf inc0.png')
-    blacklist('_ image gen_platf inc1.png')
-    blacklist('_ image gen_platf inc2.png')
-    blacklist('_ image gen_platf inc3.png')
-    blacklist('_ image gen_platf inc4.png')
-    blacklist('_ image gen_platf inc5.png')
-    blacklist('_ image gen_platf inc6.png')
-    blacklist('_ image gen_platf inc7.png')
-    blacklist('_ image gen_platf inc8.png')
-    blacklist('_ image gen_platf inc9.png')
-    blacklist('_ image gen_platf inc10.png')
-    blacklist('_ image gen_platf inc11.png')
-    blacklist('_ image gen_platf inc12.png')
-    blacklist('_ image gen_platf inc13.png')
-    blacklist('_ image gen_platf inc14.png')
-
-  # WIC fails on questionable bmps and arithmetic jpegs
-  if 'Win' in bot:
-    blacklist('_ image gen_platf rle8-height-negative.bmp')
-    blacklist('_ image gen_platf rle4-height-negative.bmp')
-    blacklist('_ image gen_platf pal8os2v2.bmp')
-    blacklist('_ image gen_platf pal8os2v2-16.bmp')
-    blacklist('_ image gen_platf rgba32abf.bmp')
-    blacklist('_ image gen_platf rgb24prof.bmp')
-    blacklist('_ image gen_platf rgb24lprof.bmp')
-    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf testimgari.jpg')
-    if 'x86_64' in bot and 'CPU' in bot:
-      # This GM triggers a SkSmallAllocator assert.
-      blacklist('_ gm _ composeshader_bitmap')
-
-  if 'Android' in bot or 'iOS' in bot:
-    # This test crashes the N9 (perhaps because of large malloc/frees). It also
-    # is fairly slow and not platform-specific. So we just disable it on all of
-    # Android and iOS. skia:5438
-    blacklist('_ test _ GrShape')
-
-  if 'Win8' in bot:
-    # bungeman: "Doesn't work on Windows anyway, produces unstable GMs with
-    # 'Unexpected error' from DirectWrite"
-    blacklist('_ gm _ fontscalerdistortable')
-    # skia:5636
-    blacklist('_ svg _ Nebraska-StateSeal.svg')
-
-  # skia:4095
-  bad_serialize_gms = ['bleed_image',
-                       'c_gms',
-                       'colortype',
-                       'colortype_xfermodes',
-                       'drawfilter',
-                       'fontmgr_bounds_0.75_0',
-                       'fontmgr_bounds_1_-0.25',
-                       'fontmgr_bounds',
-                       'fontmgr_match',
-                       'fontmgr_iter',
-                       'imagemasksubset']
-
-  # skia:5589
-  bad_serialize_gms.extend(['bitmapfilters',
-                            'bitmapshaders',
-                            'bleed',
-                            'bleed_alpha_bmp',
-                            'bleed_alpha_bmp_shader',
-                            'convex_poly_clip',
-                            'extractalpha',
-                            'filterbitmap_checkerboard_32_32_g8',
-                            'filterbitmap_image_mandrill_64',
-                            'shadows',
-                            'simpleaaclip_aaclip'])
-  # skia:5595
-  bad_serialize_gms.extend(['composeshader_bitmap',
-                            'scaled_tilemodes_npot',
-                            'scaled_tilemodes'])
-
-  # skia:5778
-  bad_serialize_gms.append('typefacerendering_pfaMac')
-  # skia:5942
-  bad_serialize_gms.append('parsedpaths')
-
-  # these use a custom image generator which doesn't serialize
-  bad_serialize_gms.append('ImageGeneratorExternal_rect')
-  bad_serialize_gms.append('ImageGeneratorExternal_shader')
-
-  # skia:6189
-  bad_serialize_gms.append('shadow_utils')
-
-  for test in bad_serialize_gms:
-    blacklist(['serialize-8888', 'gm', '_', test])
-
-  if 'Mac' not in bot:
-    for test in ['bleed_alpha_image', 'bleed_alpha_image_shader']:
-      blacklist(['serialize-8888', 'gm', '_', test])
-  # It looks like we skip these only for out-of-memory concerns.
-  if 'Win' in bot or 'Android' in bot:
-    for test in ['verylargebitmap', 'verylarge_picture_image']:
-      blacklist(['serialize-8888', 'gm', '_', test])
-
-  # skia:4769
-  for test in ['drawfilter']:
-    blacklist([    'sp-8888', 'gm', '_', test])
-    blacklist([   'pic-8888', 'gm', '_', test])
-    blacklist(['2ndpic-8888', 'gm', '_', test])
-    blacklist([  'lite-8888', 'gm', '_', test])
-  # skia:4703
-  for test in ['image-cacherator-from-picture',
-               'image-cacherator-from-raster',
-               'image-cacherator-from-ctable']:
-    blacklist([       'sp-8888', 'gm', '_', test])
-    blacklist([      'pic-8888', 'gm', '_', test])
-    blacklist([   '2ndpic-8888', 'gm', '_', test])
-    blacklist(['serialize-8888', 'gm', '_', test])
-
-  # GM that requires raster-backed canvas
-  for test in ['gamut', 'complexclip4_bw', 'complexclip4_aa']:
-    blacklist([       'sp-8888', 'gm', '_', test])
-    blacklist([      'pic-8888', 'gm', '_', test])
-    blacklist([     'lite-8888', 'gm', '_', test])
-    blacklist([   '2ndpic-8888', 'gm', '_', test])
-    blacklist(['serialize-8888', 'gm', '_', test])
-
-  # GM that not support tiles_rt
-  for test in ['complexclip4_bw', 'complexclip4_aa']:
-    blacklist([ 'tiles_rt-8888', 'gm', '_', test])
-
-  # Extensions for RAW images
-  r = ["arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw",
-       "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW"]
-
-  # skbug.com/4888
-  # Blacklist RAW images (and a few large PNGs) on GPU bots
-  # until we can resolve failures.
-  # Also blacklisted on 32-bit Win2k8 for F16 OOM errors.
-  if 'GPU' in bot or ('Win2k8' in bot and 'x86-' in bot):
-    blacklist('_ image _ interlaced1.png')
-    blacklist('_ image _ interlaced2.png')
-    blacklist('_ image _ interlaced3.png')
-    for raw_ext in r:
-      blacklist('_ image _ .%s' % raw_ext)
-
-  # Large image that overwhelms older Mac bots
-  if 'MacMini4.1-GPU' in bot:
-    blacklist('_ image _ abnormal.wbmp')
-    blacklist([gl_prefix + 'msaa' + sample_count, 'gm', '_', 'blurcircles'])
-
-  if 'IntelHD405' in bot and 'Ubuntu16' in bot:
-    # skia:6331
-    blacklist(['glmsaa8',   'image', 'gen_codec_gpu', 'abnormal.wbmp'])
-    blacklist(['glesmsaa4', 'image', 'gen_codec_gpu', 'abnormal.wbmp'])
-
-  if 'Nexus5' in bot:
-    # skia:5876
-    blacklist(['_', 'gm', '_', 'encode-platform'])
-
-  if 'AndroidOne-GPU' in bot:  # skia:4697, skia:4704, skia:4694, skia:4705
-    blacklist(['_',            'gm', '_', 'bigblurs'])
-    blacklist(['_',            'gm', '_', 'bleed'])
-    blacklist(['_',            'gm', '_', 'bleed_alpha_bmp'])
-    blacklist(['_',            'gm', '_', 'bleed_alpha_bmp_shader'])
-    blacklist(['_',            'gm', '_', 'bleed_alpha_image'])
-    blacklist(['_',            'gm', '_', 'bleed_alpha_image_shader'])
-    blacklist(['_',            'gm', '_', 'bleed_image'])
-    blacklist(['_',            'gm', '_', 'dropshadowimagefilter'])
-    blacklist(['_',            'gm', '_', 'filterfastbounds'])
-    blacklist([gl_prefix,      'gm', '_', 'imageblurtiled'])
-    blacklist(['_',            'gm', '_', 'imagefiltersclipped'])
-    blacklist(['_',            'gm', '_', 'imagefiltersscaled'])
-    blacklist(['_',            'gm', '_', 'imageresizetiled'])
-    blacklist(['_',            'gm', '_', 'matrixconvolution'])
-    blacklist(['_',            'gm', '_', 'strokedlines'])
-    if sample_count is not '':
-      gl_msaa_config = gl_prefix + 'msaa' + sample_count
-      blacklist([gl_msaa_config, 'gm', '_', 'imageblurtiled'])
-      blacklist([gl_msaa_config, 'gm', '_', 'imagefiltersbase'])
-
-  match = []
-  if 'Valgrind' in bot: # skia:3021
-    match.append('~Threaded')
-
-  if 'AndroidOne' in bot:  # skia:4711
-    match.append('~WritePixels')
-
-  if 'NexusPlayer' in bot:
-    match.append('~ResourceCache')
-
-  if 'Nexus10' in bot:
-    match.append('~CopySurface') # skia:5509
-    match.append('~SRGBReadWritePixels') # skia:6097
-
-  if 'GalaxyJ5' in bot:
-    match.append('~SRGBReadWritePixels') # skia:6097
-
-  if 'GalaxyS6' in bot:
-    match.append('~SpecialImage') # skia:6338
-
-  if 'GalaxyS7_G930A' in bot:
-    match.append('~WritePixels') # skia:6427
-
-  if 'ANGLE' in bot and 'Debug' in bot:
-    match.append('~GLPrograms') # skia:4717
-
-  if 'MSAN' in bot:
-    match.extend(['~Once', '~Shared'])  # Not sure what's up with these tests.
-
-  if 'TSAN' in bot:
-    match.extend(['~ReadWriteAlpha'])   # Flaky on TSAN-covered on nvidia bots.
-    match.extend(['~RGBA4444TextureTest',  # Flakier than they are important.
-                  '~RGB565TextureTest'])
-
-  if 'Vulkan' in bot and 'Adreno' in bot:
-    # skia:5777
-    match.extend(['~XfermodeImageFilterCroppedInput',
-                  '~GrTextureStripAtlasFlush',
-                  '~CopySurface'])
-
-  if 'Vulkan' in bot and 'NexusPlayer' in bot:
-    match.extend(['~hardstop_gradient', # skia:6037
-                  '~gradients_dup_color_stops',  # skia:6037
-                  '~gradients_no_texture$', # skia:6132
-                  '~tilemodes', # skia:6132
-                  '~shadertext$', # skia:6132
-                  '~bitmapfilters', # skia:6132
-                  '~GrContextFactory_abandon']) #skia:6209
-
-  if 'Vulkan' in bot and 'IntelIris540' in bot and 'Ubuntu' in bot:
-    match.extend(['~VkHeapTests']) # skia:6245
-
-  if 'Vulkan' in bot and 'IntelIris540' in bot and 'Win' in bot:
-    # skia:6398
-    blacklist(['vk', 'gm', '_', 'aarectmodes'])
-    blacklist(['vk', 'gm', '_', 'aaxfermodes'])
-    blacklist(['vk', 'gm', '_', 'arithmode'])
-    blacklist(['vk', 'gm', '_', 'composeshader_bitmap'])
-    blacklist(['vk', 'gm', '_', 'composeshader_bitmap2'])
-    blacklist(['vk', 'gm', '_', 'dftextCOLR'])
-    blacklist(['vk', 'gm', '_', 'drawregionmodes'])
-    blacklist(['vk', 'gm', '_', 'filterfastbounds'])
-    blacklist(['vk', 'gm', '_', 'fontcache'])
-    blacklist(['vk', 'gm', '_', 'fontmgr_iterWin10'])
-    blacklist(['vk', 'gm', '_', 'fontmgr_iter_factoryWin10'])
-    blacklist(['vk', 'gm', '_', 'fontmgr_matchWin10'])
-    blacklist(['vk', 'gm', '_', 'fontscalerWin'])
-    blacklist(['vk', 'gm', '_', 'fontscalerdistortable'])
-    blacklist(['vk', 'gm', '_', 'gammagradienttext'])
-    blacklist(['vk', 'gm', '_', 'gammatextWin'])
-    blacklist(['vk', 'gm', '_', 'gradtext'])
-    blacklist(['vk', 'gm', '_', 'hairmodes'])
-    blacklist(['vk', 'gm', '_', 'imagefilters_xfermodes'])
-    blacklist(['vk', 'gm', '_', 'imagefiltersclipped'])
-    blacklist(['vk', 'gm', '_', 'imagefiltersgraph'])
-    blacklist(['vk', 'gm', '_', 'imagefiltersscaled'])
-    blacklist(['vk', 'gm', '_', 'imagefiltersstroked'])
-    blacklist(['vk', 'gm', '_', 'imagefilterstransformed'])
-    blacklist(['vk', 'gm', '_', 'imageresizetiled'])
-    blacklist(['vk', 'gm', '_', 'lcdblendmodes'])
-    blacklist(['vk', 'gm', '_', 'lcdoverlap'])
-    blacklist(['vk', 'gm', '_', 'lcdtextWin'])
-    blacklist(['vk', 'gm', '_', 'lcdtextsize'])
-    blacklist(['vk', 'gm', '_', 'matriximagefilter'])
-    blacklist(['vk', 'gm', '_', 'mixedtextblobsCOLR'])
-    blacklist(['vk', 'gm', '_', 'pictureimagefilter'])
-    blacklist(['vk', 'gm', '_', 'resizeimagefilter'])
-    blacklist(['vk', 'gm', '_', 'rotate_imagefilter'])
-    blacklist(['vk', 'gm', '_', 'savelayer_lcdtext'])
-    blacklist(['vk', 'gm', '_', 'srcmode'])
-    blacklist(['vk', 'gm', '_', 'surfaceprops'])
-    blacklist(['vk', 'gm', '_', 'textblobgeometrychange'])
-    blacklist(['vk', 'gm', '_', 'textbloblooper'])
-    blacklist(['vk', 'gm', '_', 'textblobmixedsizes'])
-    blacklist(['vk', 'gm', '_', 'textblobmixedsizes_df'])
-    blacklist(['vk', 'gm', '_', 'textblobrandomfont'])
-    blacklist(['vk', 'gm', '_', 'textfilter_color'])
-    blacklist(['vk', 'gm', '_', 'textfilter_image'])
-    blacklist(['vk', 'gm', '_', 'typefacerenderingWin'])
-    blacklist(['vk', 'gm', '_', 'varied_text_clipped_lcd'])
-    blacklist(['vk', 'gm', '_', 'varied_text_ignorable_clip_lcd'])
-    blacklist(['vk', 'gm', '_', 'xfermodeimagefilter'])
-    match.append('~ApplyGamma')
-    match.append('~ComposedImageFilterBounds_Gpu')
-    match.append('~ImageFilterFailAffectsTransparentBlack_Gpu')
-    match.append('~ImageFilterZeroBlurSigma_Gpu')
-    match.append('~ImageNewShader_GPU')
-    match.append('~NewTextureFromPixmap')
-    match.append('~ReadPixels_Gpu')
-    match.append('~ReadPixels_Texture')
-    match.append('~ReadWriteAlpha')
-    match.append('~SRGBReadWritePixels')
-    match.append('~SpecialImage_DeferredGpu')
-    match.append('~SpecialImage_Gpu')
-    match.append('~WritePixels_Gpu')
-    match.append('~XfermodeImageFilterCroppedInput_Gpu')
-
-  if 'IntelIris540' in bot and 'ANGLE' in bot:
-    match.append('~IntTexture') # skia:6086
-    blacklist(['_', 'gm', '_', 'discard']) # skia:6141
-    # skia:6103
-    for config in ['angle_d3d9_es2', 'angle_d3d11_es2', 'angle_gl_es2']:
-      blacklist([config, 'gm', '_', 'multipicturedraw_invpathclip_simple'])
-      blacklist([config, 'gm', '_', 'multipicturedraw_noclip_simple'])
-      blacklist([config, 'gm', '_', 'multipicturedraw_pathclip_simple'])
-      blacklist([config, 'gm', '_', 'multipicturedraw_rectclip_simple'])
-      blacklist([config, 'gm', '_', 'multipicturedraw_rrectclip_simple'])
-
-  if 'IntelBayTrail' in bot and 'Ubuntu' in bot:
-    match.append('~ImageStorageLoad') # skia:6358
-
-  if blacklisted:
-    args.append('--blacklist')
-    args.extend(blacklisted)
-
-  if match:
-    args.append('--match')
-    args.extend(match)
-
-  # These bots run out of memory running RAW codec tests. Do not run them in
-  # parallel
-  if ('NexusPlayer' in bot or 'Nexus5' in bot or 'Nexus9' in bot
-      or 'Win8-MSVC-ShuttleB' in bot):
-    args.append('--noRAW_threading')
-
-  return args
-
-
-def key_params(api):
-  """Build a unique key from the builder name (as a list).
-
-  E.g.  arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6
-  """
-  # Don't bother to include role, which is always Test.
-  # TryBots are uploaded elsewhere so they can use the same key.
-  blacklist = ['role', 'is_trybot']
-
-  flat = []
-  for k in sorted(api.vars.builder_cfg.keys()):
-    if k not in blacklist:
-      flat.append(k)
-      flat.append(api.vars.builder_cfg[k])
-  return flat
-
-
-def test_steps(api):
-  """Run the DM test."""
-  use_hash_file = False
-  if api.vars.upload_dm_results:
-    # This must run before we write anything into
-    # api.flavor.device_dirs.dm_dir or we may end up deleting our
-    # output on machines where they're the same.
-    api.flavor.create_clean_host_dir(api.vars.dm_dir)
-    host_dm_dir = str(api.vars.dm_dir)
-    device_dm_dir = str(api.flavor.device_dirs.dm_dir)
-    if host_dm_dir != device_dm_dir:
-      api.flavor.create_clean_device_dir(device_dm_dir)
-
-    # Obtain the list of already-generated hashes.
-    hash_filename = 'uninteresting_hashes.txt'
-
-    # Ensure that the tmp_dir exists.
-    api.run.run_once(api.file.makedirs,
-                           'tmp_dir',
-                           api.vars.tmp_dir,
-                           infra_step=True)
-
-    host_hashes_file = api.vars.tmp_dir.join(hash_filename)
-    hashes_file = api.flavor.device_path_join(
-        api.flavor.device_dirs.tmp_dir, hash_filename)
-    api.run(
-        api.python.inline,
-        'get uninteresting hashes',
-        program="""
-        import contextlib
-        import math
-        import socket
-        import sys
-        import time
-        import urllib2
-
-        HASHES_URL = 'https://gold.skia.org/_/hashes'
-        RETRIES = 5
-        TIMEOUT = 60
-        WAIT_BASE = 15
-
-        socket.setdefaulttimeout(TIMEOUT)
-        for retry in range(RETRIES):
-          try:
-            with contextlib.closing(
-                urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:
-              hashes = w.read()
-              with open(sys.argv[1], 'w') as f:
-                f.write(hashes)
-                break
-          except Exception as e:
-            print 'Failed to get uninteresting hashes from %s:' % HASHES_URL
-            print e
-            if retry == RETRIES:
-              raise
-            waittime = WAIT_BASE * math.pow(2, retry)
-            print 'Retry in %d seconds.' % waittime
-            time.sleep(waittime)
-        """,
-        args=[host_hashes_file],
-        abort_on_failure=False,
-        fail_build_on_failure=False,
-        infra_step=True)
-
-    if api.path.exists(host_hashes_file):
-      api.flavor.copy_file_to_device(host_hashes_file, hashes_file)
-      use_hash_file = True
-
-  # Run DM.
-  properties = [
-    'gitHash',      api.vars.got_revision,
-    'master',       api.vars.master_name,
-    'builder',      api.vars.builder_name,
-    'build_number', api.vars.build_number,
-  ]
-  if api.vars.is_trybot:
-    properties.extend([
-      'issue',         api.vars.issue,
-      'patchset',      api.vars.patchset,
-      'patch_storage', api.vars.patch_storage,
-    ])
-  if api.vars.no_buildbot:
-    properties.extend(['no_buildbot', 'True'])
-    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
-    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
-
-  args = [
-    'dm',
-    '--undefok',   # This helps branches that may not know new flags.
-    '--resourcePath', api.flavor.device_dirs.resource_dir,
-    '--skps', api.flavor.device_dirs.skp_dir,
-    '--images', api.flavor.device_path_join(
-        api.flavor.device_dirs.images_dir, 'dm'),
-    '--colorImages', api.flavor.device_path_join(
-        api.flavor.device_dirs.images_dir, 'colorspace'),
-    '--nameByHash',
-    '--properties'
-  ] + properties
-
-  args.extend(['--svgs', api.flavor.device_dirs.svg_dir])
-
-  args.append('--key')
-  args.extend(key_params(api))
-  if use_hash_file:
-    args.extend(['--uninterestingHashesFile', hashes_file])
-  if api.vars.upload_dm_results:
-    args.extend(['--writePath', api.flavor.device_dirs.dm_dir])
-
-  skip_flag = None
-  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
-    skip_flag = '--nogpu'
-  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
-    skip_flag = '--nocpu'
-  if skip_flag:
-    args.append(skip_flag)
-  args.extend(dm_flags(api.vars.builder_name))
-
-  env = api.step.get_from_context('env', {})
-  if 'Ubuntu16' in api.vars.builder_name:
-    # The vulkan in this asset name simply means that the graphics driver
-    # supports Vulkan. It is also the driver used for GL code.
-    dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_release')
-    if 'Debug' in api.vars.builder_name:
-      dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_debug')
-
-    if 'Vulkan' in api.vars.builder_name:
-      sdk_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'bin')
-      lib_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'lib')
-      env.update({
-        'PATH':'%%(PATH)s:%s' % sdk_path,
-        'LD_LIBRARY_PATH': '%s:%s' % (lib_path, dri_path),
-        'LIBGL_DRIVERS_PATH': dri_path,
-        'VK_ICD_FILENAMES':'%s' % dri_path.join('intel_icd.x86_64.json'),
-      })
-    else:
-      # Even the non-vulkan NUC jobs could benefit from the newer drivers.
-      env.update({
-        'LD_LIBRARY_PATH': dri_path,
-        'LIBGL_DRIVERS_PATH': dri_path,
-      })
-
-  # See skia:2789.
-  if '_AbandonGpuContext' in api.vars.builder_cfg.get('extra_config', ''):
-    args.append('--abandonGpuContext')
-  if '_PreAbandonGpuContext' in api.vars.builder_cfg.get('extra_config', ''):
-    args.append('--preAbandonGpuContext')
-
-  with api.step.context({'env': env}):
-    api.run(api.flavor.step, 'dm', cmd=args, abort_on_failure=False)
-
-  if api.vars.upload_dm_results:
-    # Copy images and JSON to host machine if needed.
-    api.flavor.copy_directory_contents_to_host(
-        api.flavor.device_dirs.dm_dir, api.vars.dm_dir)
-
-
-class TestApi(recipe_api.RecipeApi):
-  def run(self):
-    self.m.core.setup()
-    env = self.m.step.get_from_context('env', {})
-    if 'iOS' in self.m.vars.builder_name:
-      env['IOS_BUNDLE_ID'] = 'com.google.dm'
-    with self.m.step.context({'env': env}):
-      try:
-        self.m.flavor.install_everything()
-        test_steps(self.m)
-      finally:
-        self.m.flavor.cleanup_steps()
-      self.m.run.check_failure()
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
deleted file mode 100644
index 3fc53d9..0000000
--- a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
+++ /dev/null
@@ -1,548 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded SKP VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded SVG VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[build::file]/resources/fileutil.py",
-      "rmtree",
-      "[CUSTOM_[SWARM_OUT_DIR]]/dm"
-    ],
-    "env": {
-      "PYTHONPATH": "[START_DIR]/skia/infra/bots/.recipe_deps/build/scripts"
-    },
-    "infra_step": true,
-    "name": "rmtree dm"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[CUSTOM_[SWARM_OUT_DIR]]/dm",
-      "511"
-    ],
-    "infra_step": true,
-    "name": "makedirs dm",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[START_DIR]/tmp",
-      "511"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = 'https://gold.skia.org/_/hashes'\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[1], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = 'https://gold.skia.org/_/hashes'@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[1], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[skia::flavor]/resources/symbolize_stack_trace.py",
-      "[START_DIR]",
-      "catchsegv",
-      "[START_DIR]/out/Debug/dm",
-      "--undefok",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "master",
-      "client.skia",
-      "builder",
-      "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug",
-      "build_number",
-      "5",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86",
-      "compiler",
-      "GCC",
-      "configuration",
-      "Debug",
-      "cpu_or_gpu",
-      "CPU",
-      "cpu_or_gpu_value",
-      "AVX2",
-      "model",
-      "GCE",
-      "os",
-      "Ubuntu",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[CUSTOM_[SWARM_OUT_DIR]]/dm",
-      "--nogpu",
-      "--randomProcessorTest",
-      "--threads",
-      "4",
-      "--config",
-      "8888",
-      "srgb",
-      "pdf",
-      "gl",
-      "gldft",
-      "glsrgb",
-      "glmsaa8",
-      "565",
-      "f16",
-      "sp-8888",
-      "2ndpic-8888",
-      "lite-8888",
-      "gbr-8888",
-      "serialize-8888",
-      "tiles_rt-8888",
-      "pic-8888",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "svg",
-      "--blacklist",
-      "f16",
-      "_",
-      "_",
-      "dstreadshuffle",
-      "glsrgb",
-      "image",
-      "_",
-      "_",
-      "8888",
-      "image",
-      "_",
-      "_",
-      "gbr-8888",
-      "image",
-      "_",
-      "_",
-      "gbr-8888",
-      "colorImage",
-      "_",
-      "_",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bleed_image",
-      "serialize-8888",
-      "gm",
-      "_",
-      "c_gms",
-      "serialize-8888",
-      "gm",
-      "_",
-      "colortype",
-      "serialize-8888",
-      "gm",
-      "_",
-      "colortype_xfermodes",
-      "serialize-8888",
-      "gm",
-      "_",
-      "drawfilter",
-      "serialize-8888",
-      "gm",
-      "_",
-      "fontmgr_bounds_0.75_0",
-      "serialize-8888",
-      "gm",
-      "_",
-      "fontmgr_bounds_1_-0.25",
-      "serialize-8888",
-      "gm",
-      "_",
-      "fontmgr_bounds",
-      "serialize-8888",
-      "gm",
-      "_",
-      "fontmgr_match",
-      "serialize-8888",
-      "gm",
-      "_",
-      "fontmgr_iter",
-      "serialize-8888",
-      "gm",
-      "_",
-      "imagemasksubset",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bitmapfilters",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bitmapshaders",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bleed",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bleed_alpha_bmp",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bleed_alpha_bmp_shader",
-      "serialize-8888",
-      "gm",
-      "_",
-      "convex_poly_clip",
-      "serialize-8888",
-      "gm",
-      "_",
-      "extractalpha",
-      "serialize-8888",
-      "gm",
-      "_",
-      "filterbitmap_checkerboard_32_32_g8",
-      "serialize-8888",
-      "gm",
-      "_",
-      "filterbitmap_image_mandrill_64",
-      "serialize-8888",
-      "gm",
-      "_",
-      "shadows",
-      "serialize-8888",
-      "gm",
-      "_",
-      "simpleaaclip_aaclip",
-      "serialize-8888",
-      "gm",
-      "_",
-      "composeshader_bitmap",
-      "serialize-8888",
-      "gm",
-      "_",
-      "scaled_tilemodes_npot",
-      "serialize-8888",
-      "gm",
-      "_",
-      "scaled_tilemodes",
-      "serialize-8888",
-      "gm",
-      "_",
-      "typefacerendering_pfaMac",
-      "serialize-8888",
-      "gm",
-      "_",
-      "parsedpaths",
-      "serialize-8888",
-      "gm",
-      "_",
-      "ImageGeneratorExternal_rect",
-      "serialize-8888",
-      "gm",
-      "_",
-      "ImageGeneratorExternal_shader",
-      "serialize-8888",
-      "gm",
-      "_",
-      "shadow_utils",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bleed_alpha_image",
-      "serialize-8888",
-      "gm",
-      "_",
-      "bleed_alpha_image_shader",
-      "sp-8888",
-      "gm",
-      "_",
-      "drawfilter",
-      "pic-8888",
-      "gm",
-      "_",
-      "drawfilter",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "drawfilter",
-      "lite-8888",
-      "gm",
-      "_",
-      "drawfilter",
-      "sp-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-picture",
-      "pic-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-picture",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-picture",
-      "serialize-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-picture",
-      "sp-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-raster",
-      "pic-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-raster",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-raster",
-      "serialize-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-raster",
-      "sp-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-ctable",
-      "pic-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-ctable",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-ctable",
-      "serialize-8888",
-      "gm",
-      "_",
-      "image-cacherator-from-ctable",
-      "sp-8888",
-      "gm",
-      "_",
-      "gamut",
-      "pic-8888",
-      "gm",
-      "_",
-      "gamut",
-      "lite-8888",
-      "gm",
-      "_",
-      "gamut",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "gamut",
-      "serialize-8888",
-      "gm",
-      "_",
-      "gamut",
-      "sp-8888",
-      "gm",
-      "_",
-      "complexclip4_bw",
-      "pic-8888",
-      "gm",
-      "_",
-      "complexclip4_bw",
-      "lite-8888",
-      "gm",
-      "_",
-      "complexclip4_bw",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "complexclip4_bw",
-      "serialize-8888",
-      "gm",
-      "_",
-      "complexclip4_bw",
-      "sp-8888",
-      "gm",
-      "_",
-      "complexclip4_aa",
-      "pic-8888",
-      "gm",
-      "_",
-      "complexclip4_aa",
-      "lite-8888",
-      "gm",
-      "_",
-      "complexclip4_aa",
-      "2ndpic-8888",
-      "gm",
-      "_",
-      "complexclip4_aa",
-      "serialize-8888",
-      "gm",
-      "_",
-      "complexclip4_aa",
-      "tiles_rt-8888",
-      "gm",
-      "_",
-      "complexclip4_bw",
-      "tiles_rt-8888",
-      "gm",
-      "_",
-      "complexclip4_aa"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "name": "symbolized dm"
-  },
-  {
-    "name": "$result",
-    "recipe_result": null,
-    "status_code": 0
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/sktest/example.expected/failed_push.json b/infra/bots/recipe_modules/sktest/example.expected/failed_push.json
deleted file mode 100644
index 9b12457..0000000
--- a/infra/bots/recipe_modules/sktest/example.expected/failed_push.json
+++ /dev/null
@@ -1,192 +0,0 @@
-[
-  {
-    "cmd": [
-      "adb",
-      "reboot"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "rebooting device"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport subprocess\nimport sys\nimport time\n\nkicks = 0\nwhile True:\n\n  times = 0\n  while times < 30:\n    print 'Waiting for the device to be connected and ready.'\n    try:\n      times += 1\n      output = subprocess.check_output(['adb', 'shell',\n                                        'getprop', 'sys.boot_completed'])\n      if '1' in output:\n        print 'Connected'\n        sys.exit(0)\n    except subprocess.CalledProcessError:\n      # no device connected/authorized yet\n      pass\n    time.sleep(5)\n  if kicks >= 3:\n    break\n  print 'Giving the device a \"kick\" by trying to reboot it.'\n  kicks += 1\n  print subprocess.check_output(['adb', 'reboot'])\n\nprint 'Timed out waiting for device'\nsys.exit(1)\n"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "wait for device",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@kicks = 0@@@",
-      "@@@STEP_LOG_LINE@python.inline@while True:@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@  times = 0@@@",
-      "@@@STEP_LOG_LINE@python.inline@  while times < 30:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Waiting for the device to be connected and ready.'@@@",
-      "@@@STEP_LOG_LINE@python.inline@    try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      times += 1@@@",
-      "@@@STEP_LOG_LINE@python.inline@      output = subprocess.check_output(['adb', 'shell',@@@",
-      "@@@STEP_LOG_LINE@python.inline@                                        'getprop', 'sys.boot_completed'])@@@",
-      "@@@STEP_LOG_LINE@python.inline@      if '1' in output:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        print 'Connected'@@@",
-      "@@@STEP_LOG_LINE@python.inline@        sys.exit(0)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    except subprocess.CalledProcessError:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      # no device connected/authorized yet@@@",
-      "@@@STEP_LOG_LINE@python.inline@      pass@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(5)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if kicks >= 3:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  print 'Giving the device a \"kick\" by trying to reboot it.'@@@",
-      "@@@STEP_LOG_LINE@python.inline@  kicks += 1@@@",
-      "@@@STEP_LOG_LINE@python.inline@  print subprocess.check_output(['adb', 'reboot'])@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@print 'Timed out waiting for device'@@@",
-      "@@@STEP_LOG_LINE@python.inline@sys.exit(1)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "mkdir",
-      "-p",
-      "/sdcard/revenge_of_the_skiabot/resources"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
-      "[START_DIR]/skia/resources",
-      "/sdcard/revenge_of_the_skiabot/resources"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
-    "~followup_annotations": [
-      "step returned non-zero exit code: 1",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
-      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['adb', 'push',@@@",
-      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
-      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
-      "@@@STEP_LOG_END@python.inline@@@",
-      "@@@STEP_EXCEPTION@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
-      "[START_DIR]/out/Debug"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "dump log",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@out = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output(['adb', 'logcat', '-d'])@@@",
-      "@@@STEP_LOG_LINE@python.inline@for line in log.split('\\n'):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  tokens = line.split()@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':@@@",
-      "@@@STEP_LOG_LINE@python.inline@    addr, path = tokens[-2:]@@@",
-      "@@@STEP_LOG_LINE@python.inline@    local = os.path.join(out, os.path.basename(path))@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if os.path.exists(local):@@@",
-      "@@@STEP_LOG_LINE@python.inline@      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])@@@",
-      "@@@STEP_LOG_LINE@python.inline@      line = line.replace(addr, addr + ' ' + sym.strip())@@@",
-      "@@@STEP_LOG_LINE@python.inline@  print line@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "adb",
-      "shell",
-      "reboot",
-      "-p"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "shut down device to quarantine bot"
-  },
-  {
-    "cmd": [
-      "adb",
-      "kill-server"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "kill adb server"
-  },
-  {
-    "name": "$result",
-    "reason": "Infra Failure: Step('push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources') returned 1",
-    "recipe_result": null,
-    "status_code": 1
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/sktest/example.py b/infra/bots/recipe_modules/sktest/example.py
deleted file mode 100644
index c8899b1..0000000
--- a/infra/bots/recipe_modules/sktest/example.py
+++ /dev/null
@@ -1,272 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Example recipe w/ coverage.
-
-
-DEPS = [
-  'recipe_engine/path',
-  'recipe_engine/platform',
-  'recipe_engine/properties',
-  'recipe_engine/raw_io',
-  'sktest',
-]
-
-
-TEST_BUILDERS = {
-  'client.skia': {
-    'skiabot-linux-swarm-000': [
-      'Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android',
-      'Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android',
-      'Test-Android-Clang-GalaxyJ5-GPU-Adreno306-arm-Release-Android',
-      'Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-Android',
-      'Test-Android-Clang-GalaxyS7_G930A-GPU-Adreno530-arm64-Debug-Android',
-      'Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android',
-      'Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android',
-      'Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android',
-      'Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android',
-      'Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan',
-      'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android',
-      'Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android',
-      ('Test-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-'
-       'GN_Android_Vulkan'),
-      'Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android',
-      'Test-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Debug',
-      'Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug',
-      'Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug',
-      'Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN',
-      'Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
-      ('Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind' +
-       '_AbandonGpuContext'),
-      ('Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind' +
-       '_PreAbandonGpuContext'),
-      'Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan',
-      'Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release',
-      'Test-Ubuntu16-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug',
-      'Test-Ubuntu16-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug',
-      'Test-Win10-MSVC-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-Vulkan',
-      'Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE',
-      'Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan',
-      'Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan',
-      'Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan',
-      'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot',
-      'Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
-      'Test-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release',
-      ('Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_' +
-        'SCALEDIMAGECACHE'),
-    ],
-  },
-}
-
-
-def RunSteps(api):
-  api.sktest.run()
-
-
-def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('skia'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skimage', 'VERSION'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skp', 'VERSION'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'svg', 'VERSION'),
-              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-          )
-        )
-        if 'Trybot' in builder:
-          test += api.properties(issue=500,
-                                 patchset=1,
-                                 rietveld='https://codereview.chromium.org')
-        if 'Win' in builder:
-          test += api.platform('win', 64)
-
-        if 'ChromeOS' in builder:
-          test += api.step_data('read chromeos ip',
-                  stdout=api.raw_io.output('{"user_ip":"foo@127.0.0.1"}'))
-
-
-        yield test
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
-  yield (
-    api.test('failed_dm') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('symbolized dm', retcode=1)
-  )
-
-  builder = 'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android'
-  yield (
-    api.test('failed_get_hashes') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('get uninteresting hashes', retcode=1)
-  )
-
-  builder = 'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot'
-  yield (
-    api.test('big_issue_number') +
-    api.properties(buildername=builder,
-                     mastername='client.skia.compile',
-                     slavename='skiabot-linux-swarm-000',
-                     buildnumber=5,
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]',
-                     rietveld='https://codereview.chromium.org',
-                     patchset=1,
-                     issue=2147533002L) +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.platform('win', 64)
-  )
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug-Trybot'
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
-
-  yield (
-      api.test('nobuildbot') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.step_data('get swarming bot id',
-          stdout=api.raw_io.output('skia-bot-123')) +
-      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
-  )
-
-  builder = 'Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Debug-GN_Android'
-  yield (
-    api.test('failed_push') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('push [START_DIR]/skia/resources/* '+
-                  '/sdcard/revenge_of_the_skiabot/resources', retcode=1)
-  )
-
-  builder = 'Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Debug-Android'
-  yield (
-    api.test('failed_pull') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('dm', retcode=1) +
-    api.step_data('pull /sdcard/revenge_of_the_skiabot/dm_out '+
-                  '[CUSTOM_[SWARM_OUT_DIR]]/dm', retcode=1)
-  )
diff --git a/infra/bots/recipe_modules/upload_dm_results/__init__.py b/infra/bots/recipe_modules/upload_dm_results/__init__.py
deleted file mode 100644
index df2e005..0000000
--- a/infra/bots/recipe_modules/upload_dm_results/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-DEPS = [
-  'build/file',
-  'recipe_engine/json',
-  'recipe_engine/path',
-  'recipe_engine/properties',
-  'recipe_engine/shutil',
-  'recipe_engine/step',
-  'recipe_engine/time',
-]
diff --git a/infra/bots/recipe_modules/upload_dm_results/api.py b/infra/bots/recipe_modules/upload_dm_results/api.py
deleted file mode 100644
index 3005f3e..0000000
--- a/infra/bots/recipe_modules/upload_dm_results/api.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Recipe for uploading DM results.
-
-
-
-import calendar
-
-from recipe_engine import recipe_api
-
-
-DM_JSON = 'dm.json'
-UPLOAD_ATTEMPTS = 5
-VERBOSE_LOG = 'verbose.log'
-
-
-class UploadDmResultsApi(recipe_api.RecipeApi):
-  def cp(self, name, src, dst, extra_args=None):
-    cmd = ['gsutil', 'cp']
-    if extra_args:
-      cmd.extend(extra_args)
-    cmd.extend([src, dst])
-
-    name = 'upload %s' % name
-    for i in xrange(UPLOAD_ATTEMPTS):
-      step_name = name
-      if i > 0:
-        step_name += ' (attempt %d)' % (i+1)
-      try:
-        self.m.step(step_name, cmd=cmd)
-        break
-      except self.m.step.StepFailure:
-        if i == UPLOAD_ATTEMPTS - 1:
-          raise
-
-  def run(self):
-    builder_name = self.m.properties['buildername']
-    revision = self.m.properties['revision']
-
-    results_dir = self.m.path['start_dir'].join('dm')
-
-    # Move dm.json and verbose.log to their own directory.
-    json_file = results_dir.join(DM_JSON)
-    log_file = results_dir.join(VERBOSE_LOG)
-    tmp_dir = self.m.path['start_dir'].join('tmp_upload')
-    self.m.shutil.makedirs('tmp dir', tmp_dir, infra_step=True)
-    self.m.shutil.copy('copy dm.json', json_file, tmp_dir)
-    self.m.shutil.copy('copy verbose.log', log_file, tmp_dir)
-    self.m.shutil.remove('rm old dm.json', json_file)
-    self.m.shutil.remove('rm old verbose.log', log_file)
-
-    # Upload the images.
-    image_dest_path = 'gs://%s/dm-images-v1' % self.m.properties['gs_bucket']
-    files_to_upload = self.m.file.glob(
-        'find images',
-        results_dir.join('*'),
-        test_data=[results_dir.join('someimage.png')],
-        infra_step=True)
-    if len(files_to_upload) > 0:
-      self.cp('images', results_dir.join('*'), image_dest_path)
-
-    # Upload the JSON summary and verbose.log.
-    now = self.m.time.utcnow()
-    summary_dest_path = '/'.join([
-        'dm-json-v1',
-        str(now.year ).zfill(4),
-        str(now.month).zfill(2),
-        str(now.day  ).zfill(2),
-        str(now.hour ).zfill(2),
-        revision,
-        builder_name,
-        str(int(calendar.timegm(now.utctimetuple())))])
-
-    # Trybot results are further siloed by issue/patchset.
-    issue = str(self.m.properties.get('issue', ''))
-    patchset = str(self.m.properties.get('patchset', ''))
-    if self.m.properties.get('patch_storage', '') == 'gerrit':
-      issue = str(self.m.properties['patch_issue'])
-      patchset = str(self.m.properties['patch_set'])
-    if issue and patchset:
-      summary_dest_path = '/'.join((
-          'trybot', summary_dest_path, issue, patchset))
-
-    summary_dest_path = 'gs://%s/%s' % (self.m.properties['gs_bucket'],
-                                        summary_dest_path)
-
-    self.cp('JSON and logs', tmp_dir.join('*'), summary_dest_path,
-       ['-z', 'json,log'])
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.expected/normal_bot.json b/infra/bots/recipe_modules/upload_dm_results/example.expected/normal_bot.json
deleted file mode 100644
index 7c90e99..0000000
--- a/infra/bots/recipe_modules/upload_dm_results/example.expected/normal_bot.json
+++ /dev/null
@@ -1,113 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[START_DIR]/tmp_upload",
-      "511"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp dir",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/dm/dm.json",
-      "[START_DIR]/tmp_upload"
-    ],
-    "name": "copy dm.json"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/dm/verbose.log",
-      "[START_DIR]/tmp_upload"
-    ],
-    "name": "copy verbose.log"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os\nimport sys\nos.remove(sys.argv[1])\n",
-      "[START_DIR]/dm/dm.json"
-    ],
-    "name": "rm old dm.json",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@os.remove(sys.argv[1])@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os\nimport sys\nos.remove(sys.argv[1])\n",
-      "[START_DIR]/dm/verbose.log"
-    ],
-    "name": "rm old verbose.log",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@os.remove(sys.argv[1])@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport glob\nimport sys\nwith open(sys.argv[1], 'w') as f:\n  f.write('\\n'.join(glob.glob(sys.argv[2])))\n",
-      "/path/to/tmp/",
-      "[START_DIR]/dm/*"
-    ],
-    "infra_step": true,
-    "name": "find images"
-  },
-  {
-    "cmd": [
-      "gsutil",
-      "cp",
-      "[START_DIR]/dm/*",
-      "gs://skia-infra-gm/dm-images-v1"
-    ],
-    "name": "upload images"
-  },
-  {
-    "cmd": [
-      "gsutil",
-      "cp",
-      "-z",
-      "json,log",
-      "[START_DIR]/tmp_upload/*",
-      "gs://skia-infra-gm/dm-json-v1/2012/05/14/12/abc123/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug/1337000001"
-    ],
-    "name": "upload JSON and logs"
-  },
-  {
-    "name": "$result",
-    "recipe_result": null,
-    "status_code": 0
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.py b/infra/bots/recipe_modules/upload_dm_results/example.py
deleted file mode 100644
index 5e332bf..0000000
--- a/infra/bots/recipe_modules/upload_dm_results/example.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Example recipe w/ coverage.
-
-
-DEPS = [
-  'upload_dm_results',
-  'recipe_engine/properties',
-]
-
-
-def RunSteps(api):
-  api.upload_dm_results.run()
-
-
-def GenTests(api):
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
-  yield (
-    api.test('normal_bot') +
-    api.properties(buildername=builder,
-                   gs_bucket='skia-infra-gm',
-                   revision='abc123',
-                   path_config='kitchen')
-  )
-
-  yield (
-    api.test('failed_once') +
-    api.properties(buildername=builder,
-                   gs_bucket='skia-infra-gm',
-                   revision='abc123',
-                   path_config='kitchen') +
-    api.step_data('upload images', retcode=1)
-  )
-
-  yield (
-    api.test('failed_all') +
-    api.properties(buildername=builder,
-                   gs_bucket='skia-infra-gm',
-                   revision='abc123',
-                   path_config='kitchen') +
-    api.step_data('upload images', retcode=1) +
-    api.step_data('upload images (attempt 2)', retcode=1) +
-    api.step_data('upload images (attempt 3)', retcode=1) +
-    api.step_data('upload images (attempt 4)', retcode=1) +
-    api.step_data('upload images (attempt 5)', retcode=1)
-  )
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
-  yield (
-    api.test('trybot') +
-    api.properties(buildername=builder,
-                   gs_bucket='skia-infra-gm',
-                   revision='abc123',
-                   path_config='kitchen',
-                   issue='12345',
-                   patchset='1002')
-  )
-
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          gs_bucket='skia-infra-gm',
-          revision='abc123',
-          path_config='kitchen',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
diff --git a/infra/bots/recipe_modules/upload_nano_results/__init__.py b/infra/bots/recipe_modules/upload_nano_results/__init__.py
deleted file mode 100644
index eac65b7..0000000
--- a/infra/bots/recipe_modules/upload_nano_results/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-DEPS = [
-  'build/file',
-  'recipe_engine/path',
-  'recipe_engine/properties',
-  'recipe_engine/step',
-  'recipe_engine/time',
-]
diff --git a/infra/bots/recipe_modules/upload_nano_results/api.py b/infra/bots/recipe_modules/upload_nano_results/api.py
deleted file mode 100644
index aabfdc6..0000000
--- a/infra/bots/recipe_modules/upload_nano_results/api.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Recipe for uploading nanobench results.
-
-
-from recipe_engine import recipe_api
-
-
-class UploadNanoResultsApi(recipe_api.RecipeApi):
-  def run(self):
-    # Upload the nanobench resuls.
-    builder_name = self.m.properties['buildername']
-
-    now = self.m.time.utcnow()
-    src_path = self.m.path['start_dir'].join(
-        'perfdata', builder_name, 'data')
-    with self.m.step.context({'cwd': src_path}):
-      results = self.m.file.glob(
-          'find results',
-          src_path.join('*.json'),
-          test_data=[src_path.join('nanobench_abc123.json')],
-          infra_step=True)
-    if len(results) != 1:  # pragma: nocover
-      raise Exception('Unable to find nanobench or skpbench JSON file!')
-
-    src = results[0]
-    basename = self.m.path.basename(src)
-    gs_path = '/'.join((
-        'nano-json-v1', str(now.year).zfill(4),
-        str(now.month).zfill(2), str(now.day).zfill(2), str(now.hour).zfill(2),
-        builder_name))
-
-    issue = str(self.m.properties.get('issue', ''))
-    patchset = str(self.m.properties.get('patchset', ''))
-    if self.m.properties.get('patch_storage', '') == 'gerrit':
-      issue = str(self.m.properties['patch_issue'])
-      patchset = str(self.m.properties['patch_set'])
-    if issue and patchset:
-      gs_path = '/'.join(('trybot', gs_path, issue, patchset))
-
-    dst = '/'.join((
-        'gs://%s' % self.m.properties['gs_bucket'], gs_path, basename))
-
-    self.m.step(
-        'upload',
-        cmd=['gsutil', 'cp', '-z', 'json', src, dst],
-        infra_step=True)
diff --git a/infra/bots/recipe_modules/upload_nano_results/example.expected/normal_bot.json b/infra/bots/recipe_modules/upload_nano_results/example.expected/normal_bot.json
deleted file mode 100644
index cc175d7..0000000
--- a/infra/bots/recipe_modules/upload_nano_results/example.expected/normal_bot.json
+++ /dev/null
@@ -1,31 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport glob\nimport sys\nwith open(sys.argv[1], 'w') as f:\n  f.write('\\n'.join(glob.glob(sys.argv[2])))\n",
-      "/path/to/tmp/",
-      "[START_DIR]/perfdata/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug/data/*.json"
-    ],
-    "cwd": "[START_DIR]/perfdata/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug/data",
-    "infra_step": true,
-    "name": "find results"
-  },
-  {
-    "cmd": [
-      "gsutil",
-      "cp",
-      "-z",
-      "json",
-      "[START_DIR]/perfdata/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug/data/nanobench_abc123.json",
-      "gs://skia-perf/nano-json-v1/2012/05/14/12/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug/nanobench_abc123.json"
-    ],
-    "infra_step": true,
-    "name": "upload"
-  },
-  {
-    "name": "$result",
-    "recipe_result": null,
-    "status_code": 0
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/upload_nano_results/example.py b/infra/bots/recipe_modules/upload_nano_results/example.py
deleted file mode 100644
index 7978fd5..0000000
--- a/infra/bots/recipe_modules/upload_nano_results/example.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# Example recipe w/ coverage.
-
-
-DEPS = [
-  'recipe_engine/properties',
-  'upload_nano_results',
-]
-
-
-def RunSteps(api):
-  api.upload_nano_results.run()
-
-
-def GenTests(api):
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
-  yield (
-    api.test('normal_bot') +
-    api.properties(buildername=builder,
-                   gs_bucket='skia-perf',
-                   revision='abc123',
-                   path_config='kitchen')
-  )
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
-  yield (
-    api.test('trybot') +
-    api.properties(buildername=builder,
-                   gs_bucket='skia-perf',
-                   revision='abc123',
-                   path_config='kitchen',
-                   issue='12345',
-                   patchset='1002')
-  )
-
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          gs_bucket='skia-perf',
-          revision='abc123',
-          path_config='kitchen',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
diff --git a/infra/bots/recipes/compile.expected/Build-Mac-Clang-Arm7-Release.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-Arm7-Release.json
deleted file mode 100644
index 321b444..0000000
--- a/infra/bots/recipes/compile.expected/Build-Mac-Clang-Arm7-Release.json
+++ /dev/null
@@ -1,211 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[CUSTOM_/_B_WORK]",
-      "511"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec",
-      "cache_dir = '[CUSTOM_/_B_CACHE]'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"skia\": \"got_revision\"}",
-      "--git-cache-dir",
-      "[CUSTOM_/_B_CACHE]",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123",
-      "--output_manifest"
-    ],
-    "cwd": "[CUSTOM_/_B_WORK]",
-    "env": {
-      "GIT_HTTP_LOW_SPEED_LIMIT": "1000",
-      "GIT_HTTP_LOW_SPEED_TIME": "300",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[CUSTOM_/_B_WORK]/skia/bin/fetch-gn"
-    ],
-    "cwd": "[CUSTOM_/_B_WORK]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS"
-    },
-    "infra_step": true,
-    "name": "fetch-gn"
-  },
-  {
-    "cmd": [
-      "[CUSTOM_/_B_WORK]/skia/bin/gn",
-      "gen",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS/Release",
-      "--args=cc=\"clang\" cxx=\"clang++\" is_debug=false target_cpu=\"Arm7\" target_os=\"ios\""
-    ],
-    "cwd": "[CUSTOM_/_B_WORK]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS"
-    },
-    "name": "gn gen"
-  },
-  {
-    "cmd": [
-      "ninja",
-      "-C",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS/Release"
-    ],
-    "cwd": "[CUSTOM_/_B_WORK]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS"
-    },
-    "name": "ninja"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[CUSTOM_/_B_WORK]/skia/gn/package_ios.py",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS/Release/dm"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS"
-    },
-    "infra_step": true,
-    "name": "package dm"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[CUSTOM_/_B_WORK]/skia/gn/package_ios.py",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS/Release/nanobench"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS"
-    },
-    "infra_step": true,
-    "name": "package nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'nanobench', 'nanobench.exe', 'skpbench', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'lib/*.so', 'iOSShell.app', 'iOSShell.ipa', 'visualbench', 'visualbench.exe', 'vulkan-1.dll']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-Arm7-Release-iOS/Release",
-      "[CUSTOM_[SWARM_OUT_DIR]]/out/Release"
-    ],
-    "infra_step": true,
-    "name": "copy build products",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
-      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'nanobench', 'nanobench.exe', 'skpbench', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'lib/*.so', 'iOSShell.app', 'iOSShell.ipa', 'visualbench', 'visualbench.exe', 'vulkan-1.dll']@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
-      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
-      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
-      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    shutil.move(f, dst_path)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "name": "$result",
-    "recipe_result": null,
-    "status_code": 0
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-arm64-Debug-iOS.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-arm64-Debug-iOS.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-arm64-Debug-iOS.json
rename to infra/bots/recipes/compile.expected/Build-Mac-Clang-arm64-Debug-iOS.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json
rename to infra/bots/recipes/compile.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x64-Release-iOS.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x64-Release-iOS.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x64-Release-iOS.json
rename to infra/bots/recipes/compile.expected/Build-Mac-Clang-x64-Release-iOS.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
rename to infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Release-GN.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Release-GN.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Release-GN.json
rename to infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Release-GN.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm-Release-Chromebook_C100p.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm-Release-Chromebook_C100p.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm-Release-Chromebook_C100p.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm-Release-Chromebook_C100p.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Release-Mini.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Release-Mini.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Release-Mini.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Release-Mini.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Release-Vulkan.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Release-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Release-Vulkan.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-Clang-x86_64-Release-Vulkan.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-arm-Release-Chromecast.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-arm-Release-Chromecast.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-arm-Release-Chromecast.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-arm-Release-Chromecast.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86-Debug.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86-Debug.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86-Debug.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Flutter_Android.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Flutter_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Flutter_Android.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Flutter_Android.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Shared.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Shared.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Shared.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Shared.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json b/infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json
rename to infra/bots/recipes/compile.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-Clang-arm64-Release-GN_Android.json b/infra/bots/recipes/compile.expected/Build-Win-Clang-arm64-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-Clang-arm64-Release-GN_Android.json
rename to infra/bots/recipes/compile.expected/Build-Win-Clang-arm64-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-ANGLE.json b/infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
rename to infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-Exceptions.json b/infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
rename to infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug.json b/infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug.json
rename to infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Debug.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GDI.json b/infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Release-GDI.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GDI.json
rename to infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Release-GDI.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GN.json b/infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Release-GN.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GN.json
rename to infra/bots/recipes/compile.expected/Build-Win-MSVC-x86-Release-GN.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json b/infra/bots/recipes/compile.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
rename to infra/bots/recipes/compile.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/alternate_repo.json b/infra/bots/recipes/compile.expected/alternate_repo.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/alternate_repo.json
rename to infra/bots/recipes/compile.expected/alternate_repo.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/big_issue_number.json b/infra/bots/recipes/compile.expected/big_issue_number.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/big_issue_number.json
rename to infra/bots/recipes/compile.expected/big_issue_number.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_gerrit.json b/infra/bots/recipes/compile.expected/buildbotless_trybot_gerrit.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_gerrit.json
rename to infra/bots/recipes/compile.expected/buildbotless_trybot_gerrit.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_rietveld.json b/infra/bots/recipes/compile.expected/buildbotless_trybot_rietveld.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_rietveld.json
rename to infra/bots/recipes/compile.expected/buildbotless_trybot_rietveld.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/flutter_trybot.json b/infra/bots/recipes/compile.expected/flutter_trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/flutter_trybot.json
rename to infra/bots/recipes/compile.expected/flutter_trybot.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/pdfium_trybot.json b/infra/bots/recipes/compile.expected/pdfium_trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/pdfium_trybot.json
rename to infra/bots/recipes/compile.expected/pdfium_trybot.json
diff --git a/infra/bots/recipe_modules/compile/example.expected/recipe_with_gerrit_patch.json b/infra/bots/recipes/compile.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipe_modules/compile/example.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipes/compile.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipes/compile.py b/infra/bots/recipes/compile.py
index cbf21f4..3eedb86 100644
--- a/infra/bots/recipes/compile.py
+++ b/infra/bots/recipes/compile.py
@@ -7,29 +7,295 @@
 
 
 DEPS = [
+  'core',
+  'recipe_engine/json',
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/properties',
-  'compile',
+  'recipe_engine/python',
+  'recipe_engine/step',
+  'flavor',
+  'run',
+  'vars',
 ]
 
 
+def build_targets_from_builder_dict(builder_dict):
+  """Return a list of targets to build, depending on the builder type."""
+  if builder_dict.get('extra_config') == 'iOS':
+    return ['iOSShell']
+  return ['most']
+
+
+def get_extra_env_vars(builder_dict):
+  env = {}
+  if builder_dict.get('compiler') == 'Clang':
+    env['CC'] = '/usr/bin/clang'
+    env['CXX'] = '/usr/bin/clang++'
+
+  # SKNX_NO_SIMD, SK_USE_DISCARDABLE_SCALEDIMAGECACHE, etc.
+  extra_config = builder_dict.get('extra_config', '')
+  if extra_config.startswith('SK') and extra_config.isupper():
+    env['CPPFLAGS'] = '-D' + extra_config
+
+  return env
+
+
 def RunSteps(api):
-  api.compile.run()
+  api.core.setup()
+
+  env = get_extra_env_vars(api.vars.builder_cfg)
+  build_targets = build_targets_from_builder_dict(api.vars.builder_cfg)
+
+  try:
+    for target in build_targets:
+      with api.step.context({'env': env}):
+        api.flavor.compile(target)
+    api.run.copy_build_products(
+        api.flavor.out_dir,
+        api.vars.swarming_out_dir.join(
+            'out', api.vars.configuration))
+    api.flavor.copy_extra_build_products(api.vars.swarming_out_dir)
+  finally:
+    if 'Win' in api.vars.builder_cfg.get('os', ''):
+      api.python.inline(
+          name='cleanup',
+          program='''import psutil
+for p in psutil.process_iter():
+  try:
+    if p.name in ('mspdbsrv.exe', 'vctip.exe', 'cl.exe', 'link.exe'):
+      p.kill()
+  except psutil._error.AccessDenied:
+    pass
+''',
+          infra_step=True)
+
+  api.flavor.cleanup_steps()
+  api.run.check_failure()
+
+
+TEST_BUILDERS = {
+  'client.skia.compile': {
+    'skiabot-linux-swarm-000': [
+      'Build-Mac-Clang-arm64-Debug-iOS',
+      'Build-Mac-Clang-mipsel-Debug-GN_Android',
+      'Build-Mac-Clang-x64-Release-iOS',
+      'Build-Mac-Clang-x86_64-Debug-CommandBuffer',
+      'Build-Mac-Clang-x86_64-Release-GN',
+      'Build-Ubuntu-Clang-arm-Release-Chromebook_C100p',
+      'Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot',
+      'Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs',
+      'Build-Ubuntu-Clang-arm64-Release-GN_Android',
+      'Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan',
+      'Build-Ubuntu-Clang-x86_64-Debug-ASAN',
+      'Build-Ubuntu-Clang-x86_64-Debug-GN',
+      'Build-Ubuntu-Clang-x86_64-Release-Mini',
+      'Build-Ubuntu-Clang-x86_64-Release-Vulkan',
+      'Build-Ubuntu-GCC-arm-Release-Chromecast',
+      'Build-Ubuntu-GCC-x86-Debug',
+      'Build-Ubuntu-GCC-x86_64-Debug-GN',
+      'Build-Ubuntu-GCC-x86_64-Debug-MSAN',
+      'Build-Ubuntu-GCC-x86_64-Debug-NoGPU',
+      'Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE',
+      'Build-Ubuntu-GCC-x86_64-Release-ANGLE',
+      'Build-Ubuntu-GCC-x86_64-Release-Fast',
+      'Build-Ubuntu-GCC-x86_64-Release-Flutter_Android',
+      'Build-Ubuntu-GCC-x86_64-Release-Mesa',
+      'Build-Ubuntu-GCC-x86_64-Release-PDFium',
+      'Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths',
+      'Build-Ubuntu-GCC-x86_64-Release-Shared',
+      'Build-Ubuntu-GCC-x86_64-Release-Valgrind',
+      'Build-Win-Clang-arm64-Release-GN_Android',
+      'Build-Win-MSVC-x86-Debug',
+      'Build-Win-MSVC-x86-Debug-ANGLE',
+      'Build-Win-MSVC-x86-Debug-Exceptions',
+      'Build-Win-MSVC-x86-Release-GDI',
+      'Build-Win-MSVC-x86-Release-GN',
+      'Build-Win-MSVC-x86_64-Release-Vulkan',
+    ],
+  },
+}
 
 
 def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         repository='https://skia.googlesource.com/skia.git',
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+          )
+        )
+        if 'Win' in builder:
+          test += api.platform('win', 64)
+        elif 'Mac' in builder:
+          test += api.platform('mac', 64)
+        else:
+          test += api.platform('linux', 64)
+        if 'Trybot' in builder:
+          test += api.properties(issue=500,
+                                 patchset=1,
+                                 rietveld='https://codereview.chromium.org')
+
+        yield test
+
+  mastername = 'client.skia.compile'
+  slavename = 'skiabot-win-compile-000'
+  buildername = 'Build-Win-MSVC-x86-Debug'
   yield (
-    api.test('Build-Mac-Clang-Arm7-Release') +
-    api.properties(buildername='Build-Mac-Clang-Arm7-Release-iOS',
-                   mastername='fake-master',
-                   slavename='fake-slave',
-                   buildnumber=5,
-                   repository='https://skia.googlesource.com/skia.git',
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      api.test('big_issue_number') +
+      api.properties(buildername=buildername,
+                     mastername=mastername,
+                     slavename=slavename,
+                     buildnumber=5,
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     rietveld='https://codereview.chromium.org',
+                     patchset=1,
+                     issue=2147533002L) +
+      api.path.exists(
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      ) +
+      api.platform('win', 64)
+  )
+
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=buildername + '-Trybot',
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          repository='https://skia.googlesource.com/skia.git',
+          revision='abc123',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=buildername + '-Trybot',
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.platform('win', 64)
+  )
+
+  yield (
+      api.test('buildbotless_trybot_rietveld') +
+      api.properties(
+          buildername=buildername,
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          repository='https://skia.googlesource.com/skia.git',
+          revision='abc123',
+          nobuildbot='True',
+          issue=500,
+          patchset=1,
+          patch_storage='rietveld',
+          rietveld='https://codereview.chromium.org') +
+      api.platform('win', 64)
+  )
+
+  yield (
+      api.test('buildbotless_trybot_gerrit') +
+      api.properties(
+          repository='https://skia.googlesource.com/skia.git',
+          buildername=buildername,
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          patch_issue=500,
+          patch_set=1,
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=buildername,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.platform('win', 64)
+  )
+
+  buildername = 'Build-Win-MSVC-x86_64-Release-Vulkan'
+  yield (
+      api.test('alternate_repo') +
+      api.properties(buildername=buildername,
+                     mastername=mastername,
+                     slavename=slavename,
+                     buildnumber=5,
+                     repository='https://skia.googlesource.com/other_repo.git',
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]') +
+      api.path.exists(
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      )
     )
+
+  buildername = 'Build-Ubuntu-GCC-x86_64-Release-PDFium'
+  yield (
+      api.test('pdfium_trybot') +
+      api.properties(
+          repository='https://skia.googlesource.com/skia.git',
+          buildername=buildername,
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          patch_issue=500,
+          patch_set=1,
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=buildername,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.path.exists(
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      )
+  )
+
+  buildername = 'Build-Ubuntu-GCC-x86_64-Release-Flutter_Android'
+  yield (
+      api.test('flutter_trybot') +
+      api.properties(
+          repository='https://skia.googlesource.com/skia.git',
+          buildername=buildername,
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          patch_issue=500,
+          patch_set=1,
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=buildername,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.path.exists(
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      )
   )
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus10-CPU-Exynos5250-arm-Release-Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus10-CPU-Exynos5250-arm-Release-Android.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus10-CPU-Exynos5250-arm-Release-Android.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus10-CPU-Exynos5250-arm-Release-Android.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json
rename to infra/bots/recipes/perf.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Release.json b/infra/bots/recipes/perf.expected/Perf-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Release.json
rename to infra/bots/recipes/perf.expected/Perf-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Release.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Debug.json b/infra/bots/recipes/perf.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Debug.json
rename to infra/bots/recipes/perf.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Debug.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Release.json b/infra/bots/recipes/perf.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Release.json
rename to infra/bots/recipes/perf.expected/Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Release.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json
rename to infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
rename to infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json
rename to infra/bots/recipes/perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json
diff --git a/infra/bots/recipes/perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release.json
deleted file mode 100644
index b46c436..0000000
--- a/infra/bots/recipes/perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release.json
+++ /dev/null
@@ -1,215 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded SKP VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get downloaded SVG VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[build::file]/resources/fileutil.py",
-      "rmtree",
-      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data"
-    ],
-    "env": {
-      "PYTHONPATH": "[START_DIR]/skia/infra/bots/.recipe_deps/build/scripts"
-    },
-    "infra_step": true,
-    "name": "rmtree data"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data",
-      "511"
-    ],
-    "infra_step": true,
-    "name": "makedirs data",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[skia::flavor]/resources/symbolize_stack_trace.py",
-      "[START_DIR]",
-      "catchsegv",
-      "[START_DIR]/out/Release/nanobench",
-      "--undefok",
-      "-i",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/nanobench",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--nogpu",
-      "--pre_log",
-      "--scales",
-      "1.0",
-      "1.1",
-      "--config",
-      "8888",
-      "nonrendering",
-      "hwui",
-      "f16",
-      "srgb",
-      "565",
-      "gl",
-      "glmsaa8",
-      "glnvpr8",
-      "glnvprdit8",
-      "--match",
-      "~inc0.gif",
-      "~inc1.gif",
-      "~incInterlaced.gif",
-      "~inc0.jpg",
-      "~incGray.jpg",
-      "~inc0.wbmp",
-      "~inc1.wbmp",
-      "~inc0.webp",
-      "~inc1.webp",
-      "~inc0.ico",
-      "~inc1.ico",
-      "~inc0.png",
-      "~inc1.png",
-      "~inc2.png",
-      "~inc12.png",
-      "~inc13.png",
-      "~inc14.png",
-      "~inc0.webp",
-      "~inc1.webp",
-      "--outResultsFile",
-      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data/nanobench_abc123_1337000001.json",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "build_number",
-      "5",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "cpu_or_gpu",
-      "CPU",
-      "cpu_or_gpu_value",
-      "AVX2",
-      "model",
-      "GCE",
-      "os",
-      "Ubuntu"
-    ],
-    "cwd": "[START_DIR]/skia",
-    "env": {
-      "BUILDTYPE": "Release",
-      "CHROME_HEADLESS": "1",
-      "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "name": "symbolized nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
-      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data",
-      "511"
-    ],
-    "name": "makedirs perf_dir",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
-      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
-      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
-      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
-      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "name": "$result",
-    "recipe_result": null,
-    "status_code": 0
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json
rename to infra/bots/recipes/perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
rename to infra/bots/recipes/perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json
rename to infra/bots/recipes/perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
rename to infra/bots/recipes/perf.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json
rename to infra/bots/recipes/perf.expected/Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json b/infra/bots/recipes/perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json
rename to infra/bots/recipes/perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json b/infra/bots/recipes/perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json
rename to infra/bots/recipes/perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json b/infra/bots/recipes/perf.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json
rename to infra/bots/recipes/perf.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan.json
rename to infra/bots/recipes/perf.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json b/infra/bots/recipes/perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
rename to infra/bots/recipes/perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json b/infra/bots/recipes/perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json
rename to infra/bots/recipes/perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json b/infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/Perf-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json
rename to infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/big_issue_number.json b/infra/bots/recipes/perf.expected/big_issue_number.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/big_issue_number.json
rename to infra/bots/recipes/perf.expected/big_issue_number.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/failed_push.json b/infra/bots/recipes/perf.expected/failed_push.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/failed_push.json
rename to infra/bots/recipes/perf.expected/failed_push.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/nobuildbot.json b/infra/bots/recipes/perf.expected/nobuildbot.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/nobuildbot.json
rename to infra/bots/recipes/perf.expected/nobuildbot.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/recipe_with_gerrit_patch.json b/infra/bots/recipes/perf.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipe_modules/perf/example.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipes/perf.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipes/perf.py b/infra/bots/recipes/perf.py
index b6b876f..8a65616 100644
--- a/infra/bots/recipes/perf.py
+++ b/infra/bots/recipes/perf.py
@@ -6,35 +6,487 @@
 # Recipe module for Skia Swarming perf.
 
 
+import calendar
+
+
 DEPS = [
+  'build/file',
+  'core',
+  'recipe_engine/json',
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/properties',
   'recipe_engine/raw_io',
-  'perf',
+  'recipe_engine/step',
+  'recipe_engine/time',
+  'run',
+  'flavor',
+  'vars',
 ]
 
 
+def nanobench_flags(bot):
+  args = ['--pre_log']
+
+  if 'GPU' in bot:
+    args.append('--images')
+    args.extend(['--gpuStatsDump', 'true'])
+
+  if 'Android' in bot and 'GPU' in bot:
+    args.extend(['--useThermalManager', '1,1,10,1000'])
+
+  args.extend(['--scales', '1.0', '1.1'])
+
+  if 'iOS' in bot:
+    args.extend(['--skps', 'ignore_skps'])
+
+  configs = ['8888', 'nonrendering', 'hwui' ]
+
+  if '-arm-' not in bot:
+    # For Android CPU tests, these take too long and cause the task to time out.
+    configs += [ 'f16', 'srgb' ]
+  if '-GCE-' in bot:
+    configs += [ '565' ]
+
+  gl_prefix = 'gl'
+  sample_count = '8'
+  if 'Android' in bot or 'iOS' in bot:
+    sample_count = '4'
+    # The NVIDIA_Shield has a regular OpenGL implementation. We bench that
+    # instead of ES.
+    if 'NVIDIA_Shield' not in bot:
+      gl_prefix = 'gles'
+    # The NP produces a long error stream when we run with MSAA.
+    # iOS crashes (skia:6399)
+    if 'NexusPlayer' in bot or 'iOS' in bot:
+      sample_count = ''
+  elif 'Intel' in bot:
+    sample_count = ''
+  elif 'ChromeOS' in bot:
+    gl_prefix = 'gles'
+
+  configs.append(gl_prefix)
+  if sample_count is not '':
+    configs.extend([gl_prefix + 'msaa' + sample_count,
+      gl_prefix + 'nvpr' + sample_count,
+      gl_prefix + 'nvprdit' + sample_count])
+
+  # We want to test both the OpenGL config and the GLES config on Linux Intel:
+  # GL is used by Chrome, GLES is used by ChromeOS.
+  if 'Intel' in bot and 'Ubuntu' in bot:
+    configs.append('gles')
+
+  # Bench instanced rendering on a limited number of platforms
+  inst_config = gl_prefix + 'inst'
+  if 'Nexus6' in bot:
+    configs.append(inst_config) # msaa inst isn't working yet on Adreno.
+  elif 'PixelC' in bot or 'NVIDIA_Shield' in bot or 'MacMini6.2' in bot:
+    configs.extend([inst_config, inst_config + sample_count])
+
+  if 'CommandBuffer' in bot:
+    configs = ['commandbuffer']
+  if 'Vulkan' in bot:
+    configs = ['vk']
+
+  if 'ANGLE' in bot:
+    # Test only ANGLE configs.
+    configs = ['angle_d3d11_es2']
+    if sample_count is not '':
+      configs.append('angle_d3d11_es2_msaa' + sample_count)
+
+  if 'ChromeOS' in bot:
+    # Just run GLES for now - maybe add gles_msaa4 in the future
+    configs = ['gles']
+
+  args.append('--config')
+  args.extend(configs)
+
+  if 'Valgrind' in bot:
+    # Don't care about Valgrind performance.
+    args.extend(['--loops',   '1'])
+    args.extend(['--samples', '1'])
+    # Ensure that the bot framework does not think we have timed out.
+    args.extend(['--keepAlive', 'true'])
+
+  match = []
+  if 'Android' in bot:
+    # Segfaults when run as GPU bench. Very large texture?
+    match.append('~blurroundrect')
+    match.append('~patch_grid')  # skia:2847
+    match.append('~desk_carsvg')
+  if 'NexusPlayer' in bot:
+    match.append('~desk_unicodetable')
+  if 'Nexus5' in bot:
+    match.append('~keymobi_shop_mobileweb_ebay_com.skp')  # skia:5178
+  if 'iOS' in bot:
+    match.append('~blurroundrect')
+    match.append('~patch_grid')  # skia:2847
+    match.append('~desk_carsvg')
+    match.append('~keymobi')
+    match.append('~path_hairline')
+    match.append('~GLInstancedArraysBench') # skia:4714
+  if 'IntelIris540' in bot and 'ANGLE' in bot:
+    match.append('~tile_image_filter_tiled_64')  # skia:6082
+  if 'Intel' in bot and 'Ubuntu' in bot and not 'Vulkan' in bot:
+    match.append('~native_image_to_raster_surface')  # skia:6401
+  if 'Vulkan' in bot and 'IntelIris540' in bot and 'Win' in bot:
+    # skia:6398
+    match.append('~GM_varied_text_clipped_lcd')
+    match.append('~GM_varied_text_ignorable_clip_lcd')
+    match.append('~Xfermode_DstATop_aa')
+    match.append('~Xfermode_SrcIn_aa')
+    match.append('~Xfermode_SrcOut_aa')
+    match.append('~Xfermode_Src_aa')
+    match.append('~fontscaler_lcd')
+    match.append('~rotated_rects_aa_alternating_transparent_and_opaque_src')
+    match.append('~rotated_rects_aa_changing_transparent_src')
+    match.append('~rotated_rects_aa_same_transparent_src')
+    match.append('~shadermask_LCD_FF')
+    match.append('~srcmode_rects_1')
+    match.append('~text_16_LCD_88')
+    match.append('~text_16_LCD_BK')
+    match.append('~text_16_LCD_FF')
+    match.append('~text_16_LCD_WT')
+  if 'Vulkan' in bot and 'NexusPlayer' in bot:
+    match.append('~hardstop') # skia:6037
+  if 'ANGLE' in bot and any('msaa' in x for x in configs):
+    match.append('~native_image_to_raster_surface')  # skia:6457
+
+  # We do not need or want to benchmark the decodes of incomplete images.
+  # In fact, in nanobench we assert that the full image decode succeeds.
+  match.append('~inc0.gif')
+  match.append('~inc1.gif')
+  match.append('~incInterlaced.gif')
+  match.append('~inc0.jpg')
+  match.append('~incGray.jpg')
+  match.append('~inc0.wbmp')
+  match.append('~inc1.wbmp')
+  match.append('~inc0.webp')
+  match.append('~inc1.webp')
+  match.append('~inc0.ico')
+  match.append('~inc1.ico')
+  match.append('~inc0.png')
+  match.append('~inc1.png')
+  match.append('~inc2.png')
+  match.append('~inc12.png')
+  match.append('~inc13.png')
+  match.append('~inc14.png')
+  match.append('~inc0.webp')
+  match.append('~inc1.webp')
+
+  if match:
+    args.append('--match')
+    args.extend(match)
+
+  return args
+
+
+def perf_steps(api):
+  """Run Skia benchmarks."""
+  if api.vars.upload_perf_results:
+    api.flavor.create_clean_device_dir(
+        api.flavor.device_dirs.perf_data_dir)
+
+  # Run nanobench.
+  properties = [
+    '--properties',
+    'gitHash',      api.vars.got_revision,
+    'build_number', api.vars.build_number,
+  ]
+  if api.vars.is_trybot:
+    properties.extend([
+      'issue',    api.vars.issue,
+      'patchset', api.vars.patchset,
+      'patch_storage', api.vars.patch_storage,
+    ])
+  if api.vars.no_buildbot:
+    properties.extend(['no_buildbot', 'True'])
+    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
+    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
+
+  target = 'nanobench'
+  args = [
+      target,
+      '--undefok',   # This helps branches that may not know new flags.
+      '-i',       api.flavor.device_dirs.resource_dir,
+      '--skps',   api.flavor.device_dirs.skp_dir,
+      '--images', api.flavor.device_path_join(
+          api.flavor.device_dirs.images_dir, 'nanobench'),
+  ]
+
+  # Do not run svgs on Valgrind.
+  if 'Valgrind' not in api.vars.builder_name:
+    if ('Vulkan' not in api.vars.builder_name or
+        'NexusPlayer' not in api.vars.builder_name):
+      args.extend(['--svgs',  api.flavor.device_dirs.svg_dir])
+
+  skip_flag = None
+  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
+    skip_flag = '--nogpu'
+  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
+    skip_flag = '--nocpu'
+  if skip_flag:
+    args.append(skip_flag)
+  args.extend(nanobench_flags(api.vars.builder_name))
+
+  if 'Chromecast' in api.vars.builder_cfg.get('os', ''):
+    # Due to limited disk space, run a watered down perf run on Chromecast.
+    args = [
+      target,
+       '-i', api.flavor.device_dirs.resource_dir,
+       '--images', api.flavor.device_path_join(
+            api.flavor.device_dirs.resource_dir, 'color_wheel.jpg'),
+       '--svgs',  api.flavor.device_dirs.svg_dir,
+    ]
+
+  if api.vars.upload_perf_results:
+    now = api.time.utcnow()
+    ts = int(calendar.timegm(now.utctimetuple()))
+    json_path = api.flavor.device_path_join(
+        api.flavor.device_dirs.perf_data_dir,
+        'nanobench_%s_%d.json' % (api.vars.got_revision, ts))
+    args.extend(['--outResultsFile', json_path])
+    args.extend(properties)
+
+    keys_blacklist = ['configuration', 'role', 'is_trybot']
+    args.append('--key')
+    for k in sorted(api.vars.builder_cfg.keys()):
+      if not k in keys_blacklist:
+        args.extend([k, api.vars.builder_cfg[k]])
+
+  env = api.step.get_from_context('env', {})
+  if 'Ubuntu16' in api.vars.builder_name:
+    # The vulkan in this asset name simply means that the graphics driver
+    # supports Vulkan. It is also the driver used for GL code.
+    dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_release')
+    if 'Debug' in api.vars.builder_name:
+      dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_debug')
+
+    if 'Vulkan' in api.vars.builder_name:
+      sdk_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'bin')
+      lib_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'lib')
+      env.update({
+        'PATH':'%%(PATH)s:%s' % sdk_path,
+        'LD_LIBRARY_PATH': '%s:%s' % (lib_path, dri_path),
+        'LIBGL_DRIVERS_PATH': dri_path,
+        'VK_ICD_FILENAMES':'%s' % dri_path.join('intel_icd.x86_64.json'),
+      })
+    else:
+      # Even the non-vulkan NUC jobs could benefit from the newer drivers.
+      env.update({
+        'LD_LIBRARY_PATH': dri_path,
+        'LIBGL_DRIVERS_PATH': dri_path,
+      })
+
+  # See skia:2789.
+  if '_AbandonGpuContext' in api.vars.builder_cfg.get('extra_config', ''):
+    args.extend(['--abandonGpuContext', '--nocpu'])
+
+  with api.step.context({'env': env}):
+    api.run(api.flavor.step, target, cmd=args,
+            abort_on_failure=False)
+
+  # Copy results to swarming out dir.
+  if api.vars.upload_perf_results:
+    api.file.makedirs('perf_dir', api.vars.perf_data_dir)
+    api.flavor.copy_directory_contents_to_host(
+        api.flavor.device_dirs.perf_data_dir,
+        api.vars.perf_data_dir)
+
+
 def RunSteps(api):
-  api.perf.run()
+  api.core.setup()
+  env = api.step.get_from_context('env', {})
+  if 'iOS' in api.vars.builder_name:
+    env['IOS_BUNDLE_ID'] = 'com.google.nanobench'
+  with api.step.context({'env': env}):
+    try:
+      if 'Chromecast' in api.vars.builder_name:
+        api.flavor.install(resources=True, skps=True)
+      else:
+        api.flavor.install_everything()
+      perf_steps(api)
+    finally:
+      api.flavor.cleanup_steps()
+    api.run.check_failure()
+
+
+TEST_BUILDERS = {
+  'client.skia': {
+    'skiabot-linux-swarm-000': [
+      ('Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug' +
+       '-GN_Android_Vulkan'),
+      'Perf-Android-Clang-Nexus10-CPU-Exynos5250-arm-Release-Android',
+      'Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android',
+      'Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android',
+      'Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android',
+      'Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android',
+      ('Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_'
+       'Android_Vulkan'),
+      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android',
+      'Perf-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Release',
+      'Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Debug',
+      'Perf-Chromecast-GCC-Chorizo-CPU-Cortex_A7-arm-Release',
+      'Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN',
+      'Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
+      'Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN',
+      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE',
+      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
+      ('Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind' +
+       '_AbandonGpuContext'),
+      'Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan',
+      'Perf-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release',
+      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug',
+      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release',
+      'Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE',
+      'Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan',
+      'Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
+      'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot',
+      'Perf-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release'
+    ],
+  },
+}
 
 
 def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('skia'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skimage', 'VERSION'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skp', 'VERSION'),
+              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+          )
+        )
+        if 'Trybot' in builder:
+          test += api.properties(issue=500,
+                                 patchset=1,
+                                 rietveld='https://codereview.chromium.org')
+        if 'Win' in builder:
+          test += api.platform('win', 64)
+
+        if 'Chromecast' in builder:
+          test += api.step_data('read chromecast ip',
+                  stdout=api.raw_io.output('192.168.1.2:5555'))
+
+        if 'ChromeOS' in builder:
+          test += api.step_data('read chromeos ip',
+                  stdout=api.raw_io.output('{"user_ip":"foo@127.0.0.1"}'))
+
+        yield test
+
+  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
   yield (
-    api.test('Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release') +
-    api.properties(buildername='Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release',
-                   mastername='fake-master',
-                   slavename='fake-slave',
+    api.test('big_issue_number') +
+    api.properties(buildername=builder,
+                   mastername='client.skia.compile',
+                   slavename='skiabot-linux-swarm-000',
                    buildnumber=5,
                    revision='abc123',
                    path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   rietveld='https://codereview.chromium.org',
+                   patchset=1,
+                   issue=2147533002L) +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.platform('win', 64)
+  )
+
+  builder = ('Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind-'
+             'Trybot')
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
+
+  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
+  yield (
+      api.test('nobuildbot') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          revision='abc123',
+          path_config='kitchen',
+          nobuildbot='True',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.path.exists(
+          api.path['start_dir'].join('skia'),
+          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                       'skimage', 'VERSION'),
+          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                       'skp', 'VERSION'),
+          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                       'svg', 'VERSION'),
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      ) +
+      api.platform('win', 64) +
+      api.step_data('get swarming bot id',
+          stdout=api.raw_io.output('skia-bot-123')) +
+      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
+  )
+
+  builder = 'Perf-Android-Clang-NexusPlayer-CPU-SSE4-x86-Debug-GN_Android'
+  yield (
+    api.test('failed_push') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
                    swarm_out_dir='[SWARM_OUT_DIR]') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                   'skimage', 'VERSION'),
+                                     'skimage', 'VERSION'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                   'skp', 'VERSION'),
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
         api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    )
+    ) +
+    api.step_data('push [START_DIR]/skia/resources/* '+
+                  '/sdcard/revenge_of_the_skiabot/resources', retcode=1)
   )
diff --git a/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json b/infra/bots/recipes/skpbench.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json
similarity index 100%
rename from infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json
rename to infra/bots/recipes/skpbench.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json
diff --git a/infra/bots/recipes/skpbench.py b/infra/bots/recipes/skpbench.py
index 4561f61..5dd107f 100644
--- a/infra/bots/recipes/skpbench.py
+++ b/infra/bots/recipes/skpbench.py
@@ -3,39 +3,137 @@
 # found in the LICENSE file.
 
 
-# Recipe module for Skia Swarming skpbench.
+# Recipe for Skia skpbench.
+
+
+import calendar
 
 
 DEPS = [
+  'build/file',
+  'core',
   'recipe_engine/path',
   'recipe_engine/properties',
+  'recipe_engine/python',
   'recipe_engine/raw_io',
-  'skpbench',
+  'recipe_engine/step',
+  'recipe_engine/time',
+  'run',
+  'flavor',
+  'vars',
 ]
 
 
+def _run(api, title, *cmd, **kwargs):
+  with api.step.context({'cwd': api.vars.skia_dir}):
+    return api.run(api.step, title, cmd=list(cmd), **kwargs)
+
+
+def _adb(api, title, *cmd, **kwargs):
+  if 'infra_step' not in kwargs:
+    kwargs['infra_step'] = True
+  return _run(api, title, 'adb', *cmd, **kwargs)
+
+
+def skpbench_steps(api):
+  """benchmark Skia using skpbench."""
+  app = api.vars.skia_out.join(api.vars.configuration, 'skpbench')
+  _adb(api, 'push skpbench', 'push', app, api.vars.android_bin_dir)
+
+  skpbench_dir = api.vars.slave_dir.join('skia', 'tools', 'skpbench')
+  table = api.path.join(api.vars.swarming_out_dir, 'table')
+
+  config = 'gles,glesinst4'
+  if 'Vulkan' in api.vars.builder_name:
+    config = 'vk'
+
+  skpbench_args = [
+        api.path.join(api.vars.android_bin_dir, 'skpbench'),
+        api.path.join(api.vars.android_data_dir, 'skps'),
+        '--adb',
+        '--resultsfile', table,
+        '--config', config]
+
+  api.run(api.python, 'skpbench',
+      script=skpbench_dir.join('skpbench.py'),
+      args=skpbench_args)
+
+  skiaperf_args = [
+    table,
+    '--properties',
+    'gitHash',      api.vars.got_revision,
+    'build_number', api.vars.build_number,
+  ]
+
+  skiaperf_args.extend(['no_buildbot', 'True'])
+  skiaperf_args.extend(['swarming_bot_id', api.vars.swarming_bot_id])
+  skiaperf_args.extend(['swarming_task_id', api.vars.swarming_task_id])
+
+  now = api.time.utcnow()
+  ts = int(calendar.timegm(now.utctimetuple()))
+  api.file.makedirs('perf_dir', api.vars.perf_data_dir)
+  json_path = api.path.join(
+      api.vars.perf_data_dir,
+      'skpbench_%s_%d.json' % (api.vars.got_revision, ts))
+
+  skiaperf_args.extend([
+    '--outfile', json_path
+  ])
+
+  keys_blacklist = ['configuration', 'role', 'is_trybot']
+  skiaperf_args.append('--key')
+  for k in sorted(api.vars.builder_cfg.keys()):
+    if not k in keys_blacklist:
+      skiaperf_args.extend([k, api.vars.builder_cfg[k]])
+
+  api.run(api.python, 'Parse skpbench output into Perf json',
+      script=skpbench_dir.join('skiaperf.py'),
+      args=skiaperf_args)
+
+
 def RunSteps(api):
-  api.skpbench.run()
+  api.core.setup()
+  try:
+    api.flavor.install(skps=True)
+    skpbench_steps(api)
+  finally:
+    api.flavor.cleanup_steps()
+  api.run.check_failure()
+
+
+TEST_BUILDERS = {
+  'client.skia': {
+    'skiabot-linux-swarm-000': [
+      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench',
+      ('Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-' +
+      'GN_Android_Vulkan_Skpbench'),
+    ],
+  },
+}
 
 
 def GenTests(api):
-  b = 'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench'
-  yield (
-    api.test(b) +
-    api.properties(buildername=b,
-                   mastername='fake-master',
-                   slavename='fake-slave',
-                   buildnumber=5,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                   'skp', 'VERSION'),
-    ) +
-    api.step_data('get swarming bot id',
-        stdout=api.raw_io.output('skia-bot-123')) +
-    api.step_data('get swarming task id',
-        stdout=api.raw_io.output('123456'))
-  )
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('skia'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skp', 'VERSION'),
+          ) +
+          api.step_data('get swarming bot id',
+              stdout=api.raw_io.output('skia-bot-123')) +
+          api.step_data('get swarming task id',
+              stdout=api.raw_io.output('123456'))
+        )
+
+        yield test
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyJ5-GPU-Adreno306-arm-Release-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyJ5-GPU-Adreno306-arm-Release-Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyJ5-GPU-Adreno306-arm-Release-Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyJ5-GPU-Adreno306-arm-Release-Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyS7_G930A-GPU-Adreno530-arm64-Debug-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930A-GPU-Adreno530-arm64-Debug-Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyS7_G930A-GPU-Adreno530-arm64-Debug-Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930A-GPU-Adreno530-arm64-Debug-Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android_Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json
rename to infra/bots/recipes/test.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Debug.json b/infra/bots/recipes/test.expected/Test-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Debug.json
rename to infra/bots/recipes/test.expected/Test-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Debug.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json
rename to infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json
rename to infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
rename to infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
index 898d178..3fc53d9 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
@@ -196,7 +196,7 @@
       "gitHash",
       "abc123",
       "master",
-      "fake-master",
+      "client.skia",
       "builder",
       "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug",
       "build_number",
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_AbandonGpuContext.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_PreAbandonGpuContext.json b/infra/bots/recipes/test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_PreAbandonGpuContext.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_PreAbandonGpuContext.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind_PreAbandonGpuContext.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json b/infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json b/infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug.json b/infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug.json b/infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu16-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug.json
rename to infra/bots/recipes/test.expected/Test-Ubuntu16-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-MSVC-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Win10-MSVC-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json b/infra/bots/recipes/test.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json
rename to infra/bots/recipes/test.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json
rename to infra/bots/recipes/test.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json b/infra/bots/recipes/test.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json
rename to infra/bots/recipes/test.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json b/infra/bots/recipes/test.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
rename to infra/bots/recipes/test.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json b/infra/bots/recipes/test.expected/Test-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/Test-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json
rename to infra/bots/recipes/test.expected/Test-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/big_issue_number.json b/infra/bots/recipes/test.expected/big_issue_number.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/big_issue_number.json
rename to infra/bots/recipes/test.expected/big_issue_number.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/failed_dm.json b/infra/bots/recipes/test.expected/failed_dm.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/failed_dm.json
rename to infra/bots/recipes/test.expected/failed_dm.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/failed_get_hashes.json b/infra/bots/recipes/test.expected/failed_get_hashes.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/failed_get_hashes.json
rename to infra/bots/recipes/test.expected/failed_get_hashes.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/failed_pull.json b/infra/bots/recipes/test.expected/failed_pull.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/failed_pull.json
rename to infra/bots/recipes/test.expected/failed_pull.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/failed_push.json b/infra/bots/recipes/test.expected/failed_push.json
similarity index 100%
copy from infra/bots/recipe_modules/perf/example.expected/failed_push.json
copy to infra/bots/recipes/test.expected/failed_push.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/nobuildbot.json b/infra/bots/recipes/test.expected/nobuildbot.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/nobuildbot.json
rename to infra/bots/recipes/test.expected/nobuildbot.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/recipe_with_gerrit_patch.json b/infra/bots/recipes/test.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipe_modules/sktest/example.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipes/test.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index 2e101ae..ece37ba 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -7,36 +7,955 @@
 
 
 DEPS = [
+  'build/file',
+  'core',
+  'recipe_engine/json',
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/properties',
+  'recipe_engine/python',
   'recipe_engine/raw_io',
-  'sktest',
+  'recipe_engine/step',
+  'flavor',
+  'run',
+  'vars',
 ]
 
 
+def dm_flags(bot):
+  args = []
+
+  # This enables non-deterministic random seeding of the GPU FP optimization
+  # test. Limit testing until we're sure it's not going to cause an
+  # avalanche of problems.
+  if 'Ubuntu' in bot or 'Win' in bot or 'Mac' in bot or 'iOS' in bot:
+    args.append('--randomProcessorTest')
+
+  # 32-bit desktop bots tend to run out of memory, because they have relatively
+  # far more cores than RAM (e.g. 32 cores, 3G RAM).  Hold them back a bit.
+  if '-x86-' in bot and not 'NexusPlayer' in bot:
+    args.extend('--threads 4'.split(' '))
+
+  # Avoid issues with dynamically exceeding resource cache limits.
+  if 'Test' in bot and 'DISCARDABLE' in bot:
+    args.extend('--threads 0'.split(' '))
+
+  # These are the canonical configs that we would ideally run on all bots. We
+  # may opt out or substitute some below for specific bots
+  configs = ['8888', 'srgb', 'pdf']
+  # Add in either gles or gl configs to the canonical set based on OS
+  sample_count = '8'
+  gl_prefix = 'gl'
+  if 'Android' in bot or 'iOS' in bot:
+    sample_count = '4'
+    # We want to test the OpenGL config not the GLES config on the Shield
+    if 'NVIDIA_Shield' not in bot:
+      gl_prefix = 'gles'
+  elif 'Intel' in bot:
+    sample_count = ''
+  elif 'ChromeOS' in bot:
+    gl_prefix = 'gles'
+
+  configs.extend([gl_prefix, gl_prefix + 'dft', gl_prefix + 'srgb'])
+  if sample_count is not '':
+    configs.append(gl_prefix + 'msaa' + sample_count)
+
+  # The NP produces a long error stream when we run with MSAA. The Tegra3 just
+  # doesn't support it.
+  if ('NexusPlayer' in bot or
+      'Tegra3'      in bot or
+      # We aren't interested in fixing msaa bugs on iPad4.
+      'iPad4' in bot or
+      # skia:5792
+      'iHD530'       in bot or
+      'IntelIris540' in bot):
+    configs = [x for x in configs if 'msaa' not in x]
+
+  # The NP produces different images for dft on every run.
+  if 'NexusPlayer' in bot:
+    configs = [x for x in configs if 'dft' not in x]
+
+  # Runs out of memory on Android bots.  Everyone else seems fine.
+  if 'Android' in bot:
+    configs.remove('pdf')
+
+  if '-GCE-' in bot:
+    configs.extend(['565'])
+    configs.extend(['f16'])
+    configs.extend(['sp-8888', '2ndpic-8888'])   # Test niche uses of SkPicture.
+    configs.extend(['lite-8888'])                # Experimental display list.
+    configs.extend(['gbr-8888'])
+
+  if '-TSAN' not in bot and sample_count is not '':
+    if ('TegraK1'  in bot or
+        'TegraX1'  in bot or
+        'GTX550Ti' in bot or
+        'GTX660'   in bot or
+        'GT610'    in bot):
+      configs.append(gl_prefix + 'nvprdit' + sample_count)
+
+  # We want to test both the OpenGL config and the GLES config on Linux Intel:
+  # GL is used by Chrome, GLES is used by ChromeOS.
+  if 'Intel' in bot and 'Ubuntu' in bot:
+    configs.extend(['gles', 'glesdft', 'glessrgb'])
+
+  # NP is running out of RAM when we run all these modes.  skia:3255
+  if 'NexusPlayer' not in bot:
+    configs.extend(mode + '-8888' for mode in
+                   ['serialize', 'tiles_rt', 'pic'])
+
+  # Test instanced rendering on a limited number of platforms
+  if 'Nexus6' in bot:
+    configs.append(gl_prefix + 'inst') # inst msaa isn't working yet on Adreno.
+  elif 'NVIDIA_Shield' in bot or 'PixelC' in bot:
+    # Multisampled instanced configs use nvpr so we substitute inst msaa
+    # configs for nvpr msaa configs.
+    old = gl_prefix + 'nvpr'
+    new = gl_prefix + 'inst'
+    configs = [x.replace(old, new) for x in configs]
+    # We also test non-msaa instanced.
+    configs.append(new)
+  elif 'MacMini6.2' in bot and sample_count is not '':
+    configs.extend([gl_prefix + 'inst', gl_prefix + 'inst' + sample_count])
+
+  # CommandBuffer bot *only* runs the command_buffer config.
+  if 'CommandBuffer' in bot:
+    configs = ['commandbuffer']
+
+  # ANGLE bot *only* runs the angle configs
+  if 'ANGLE' in bot:
+    configs = ['angle_d3d11_es2',
+               'angle_d3d9_es2',
+               'angle_gl_es2']
+    if sample_count is not '':
+      configs.append('angle_d3d11_es2_msaa' + sample_count)
+
+  # Vulkan bot *only* runs the vk config.
+  if 'Vulkan' in bot:
+    configs = ['vk']
+
+  if 'ChromeOS' in bot:
+    # Just run GLES for now - maybe add gles_msaa4 in the future
+    configs = ['gles']
+
+  args.append('--config')
+  args.extend(configs)
+
+  # Run tests, gms, and image decoding tests everywhere.
+  args.extend('--src tests gm image colorImage svg'.split(' '))
+  if 'Vulkan' in bot and 'NexusPlayer' in bot:
+    args.remove('svg')
+    args.remove('image')
+
+  blacklisted = []
+  def blacklist(quad):
+    config, src, options, name = quad.split(' ') if type(quad) is str else quad
+    if config == '_' or config in configs:
+      blacklisted.extend([config, src, options, name])
+
+  # TODO: ???
+  blacklist('f16 _ _ dstreadshuffle')
+  blacklist('glsrgb image _ _')
+  blacklist('glessrgb image _ _')
+
+  # Decoder tests are now performing gamma correct decodes.  This means
+  # that, when viewing the results, we need to perform a gamma correct
+  # encode to PNG.  Therefore, we run the image tests in srgb mode instead
+  # of 8888.
+  blacklist('8888 image _ _')
+
+  # Not any point to running these.
+  blacklist('gbr-8888 image _ _')
+  blacklist('gbr-8888 colorImage _ _')
+
+  if 'Valgrind' in bot:
+    # These take 18+ hours to run.
+    blacklist('pdf gm _ fontmgr_iter')
+    blacklist('pdf _ _ PANO_20121023_214540.jpg')
+    blacklist('pdf skp _ worldjournal')
+    blacklist('pdf skp _ desk_baidu.skp')
+    blacklist('pdf skp _ desk_wikipedia.skp')
+    blacklist('_ svg _ _')
+
+  if 'iOS' in bot:
+    blacklist(gl_prefix + ' skp _ _')
+
+  if 'Mac' in bot or 'iOS' in bot:
+    # CG fails on questionable bmps
+    blacklist('_ image gen_platf rgba32abf.bmp')
+    blacklist('_ image gen_platf rgb24prof.bmp')
+    blacklist('_ image gen_platf rgb24lprof.bmp')
+    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
+
+    # CG has unpredictable behavior on this questionable gif
+    # It's probably using uninitialized memory
+    blacklist('_ image gen_platf frame_larger_than_image.gif')
+
+    # CG has unpredictable behavior on incomplete pngs
+    # skbug.com/5774
+    blacklist('_ image gen_platf inc0.png')
+    blacklist('_ image gen_platf inc1.png')
+    blacklist('_ image gen_platf inc2.png')
+    blacklist('_ image gen_platf inc3.png')
+    blacklist('_ image gen_platf inc4.png')
+    blacklist('_ image gen_platf inc5.png')
+    blacklist('_ image gen_platf inc6.png')
+    blacklist('_ image gen_platf inc7.png')
+    blacklist('_ image gen_platf inc8.png')
+    blacklist('_ image gen_platf inc9.png')
+    blacklist('_ image gen_platf inc10.png')
+    blacklist('_ image gen_platf inc11.png')
+    blacklist('_ image gen_platf inc12.png')
+    blacklist('_ image gen_platf inc13.png')
+    blacklist('_ image gen_platf inc14.png')
+
+  # WIC fails on questionable bmps and arithmetic jpegs
+  if 'Win' in bot:
+    blacklist('_ image gen_platf rle8-height-negative.bmp')
+    blacklist('_ image gen_platf rle4-height-negative.bmp')
+    blacklist('_ image gen_platf pal8os2v2.bmp')
+    blacklist('_ image gen_platf pal8os2v2-16.bmp')
+    blacklist('_ image gen_platf rgba32abf.bmp')
+    blacklist('_ image gen_platf rgb24prof.bmp')
+    blacklist('_ image gen_platf rgb24lprof.bmp')
+    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf testimgari.jpg')
+    if 'x86_64' in bot and 'CPU' in bot:
+      # This GM triggers a SkSmallAllocator assert.
+      blacklist('_ gm _ composeshader_bitmap')
+
+  if 'Android' in bot or 'iOS' in bot:
+    # This test crashes the N9 (perhaps because of large malloc/frees). It also
+    # is fairly slow and not platform-specific. So we just disable it on all of
+    # Android and iOS. skia:5438
+    blacklist('_ test _ GrShape')
+
+  if 'Win8' in bot:
+    # bungeman: "Doesn't work on Windows anyway, produces unstable GMs with
+    # 'Unexpected error' from DirectWrite"
+    blacklist('_ gm _ fontscalerdistortable')
+    # skia:5636
+    blacklist('_ svg _ Nebraska-StateSeal.svg')
+
+  # skia:4095
+  bad_serialize_gms = ['bleed_image',
+                       'c_gms',
+                       'colortype',
+                       'colortype_xfermodes',
+                       'drawfilter',
+                       'fontmgr_bounds_0.75_0',
+                       'fontmgr_bounds_1_-0.25',
+                       'fontmgr_bounds',
+                       'fontmgr_match',
+                       'fontmgr_iter',
+                       'imagemasksubset']
+
+  # skia:5589
+  bad_serialize_gms.extend(['bitmapfilters',
+                            'bitmapshaders',
+                            'bleed',
+                            'bleed_alpha_bmp',
+                            'bleed_alpha_bmp_shader',
+                            'convex_poly_clip',
+                            'extractalpha',
+                            'filterbitmap_checkerboard_32_32_g8',
+                            'filterbitmap_image_mandrill_64',
+                            'shadows',
+                            'simpleaaclip_aaclip'])
+  # skia:5595
+  bad_serialize_gms.extend(['composeshader_bitmap',
+                            'scaled_tilemodes_npot',
+                            'scaled_tilemodes'])
+
+  # skia:5778
+  bad_serialize_gms.append('typefacerendering_pfaMac')
+  # skia:5942
+  bad_serialize_gms.append('parsedpaths')
+
+  # these use a custom image generator which doesn't serialize
+  bad_serialize_gms.append('ImageGeneratorExternal_rect')
+  bad_serialize_gms.append('ImageGeneratorExternal_shader')
+
+  # skia:6189
+  bad_serialize_gms.append('shadow_utils')
+
+  for test in bad_serialize_gms:
+    blacklist(['serialize-8888', 'gm', '_', test])
+
+  if 'Mac' not in bot:
+    for test in ['bleed_alpha_image', 'bleed_alpha_image_shader']:
+      blacklist(['serialize-8888', 'gm', '_', test])
+  # It looks like we skip these only for out-of-memory concerns.
+  if 'Win' in bot or 'Android' in bot:
+    for test in ['verylargebitmap', 'verylarge_picture_image']:
+      blacklist(['serialize-8888', 'gm', '_', test])
+
+  # skia:4769
+  for test in ['drawfilter']:
+    blacklist([    'sp-8888', 'gm', '_', test])
+    blacklist([   'pic-8888', 'gm', '_', test])
+    blacklist(['2ndpic-8888', 'gm', '_', test])
+    blacklist([  'lite-8888', 'gm', '_', test])
+  # skia:4703
+  for test in ['image-cacherator-from-picture',
+               'image-cacherator-from-raster',
+               'image-cacherator-from-ctable']:
+    blacklist([       'sp-8888', 'gm', '_', test])
+    blacklist([      'pic-8888', 'gm', '_', test])
+    blacklist([   '2ndpic-8888', 'gm', '_', test])
+    blacklist(['serialize-8888', 'gm', '_', test])
+
+  # GM that requires raster-backed canvas
+  for test in ['gamut', 'complexclip4_bw', 'complexclip4_aa']:
+    blacklist([       'sp-8888', 'gm', '_', test])
+    blacklist([      'pic-8888', 'gm', '_', test])
+    blacklist([     'lite-8888', 'gm', '_', test])
+    blacklist([   '2ndpic-8888', 'gm', '_', test])
+    blacklist(['serialize-8888', 'gm', '_', test])
+
+  # GM that not support tiles_rt
+  for test in ['complexclip4_bw', 'complexclip4_aa']:
+    blacklist([ 'tiles_rt-8888', 'gm', '_', test])
+
+  # Extensions for RAW images
+  r = ["arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw",
+       "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW"]
+
+  # skbug.com/4888
+  # Blacklist RAW images (and a few large PNGs) on GPU bots
+  # until we can resolve failures.
+  # Also blacklisted on 32-bit Win2k8 for F16 OOM errors.
+  if 'GPU' in bot or ('Win2k8' in bot and 'x86-' in bot):
+    blacklist('_ image _ interlaced1.png')
+    blacklist('_ image _ interlaced2.png')
+    blacklist('_ image _ interlaced3.png')
+    for raw_ext in r:
+      blacklist('_ image _ .%s' % raw_ext)
+
+  # Large image that overwhelms older Mac bots
+  if 'MacMini4.1-GPU' in bot:
+    blacklist('_ image _ abnormal.wbmp')
+    blacklist([gl_prefix + 'msaa' + sample_count, 'gm', '_', 'blurcircles'])
+
+  if 'IntelHD405' in bot and 'Ubuntu16' in bot:
+    # skia:6331
+    blacklist(['glmsaa8',   'image', 'gen_codec_gpu', 'abnormal.wbmp'])
+    blacklist(['glesmsaa4', 'image', 'gen_codec_gpu', 'abnormal.wbmp'])
+
+  if 'Nexus5' in bot:
+    # skia:5876
+    blacklist(['_', 'gm', '_', 'encode-platform'])
+
+  if 'AndroidOne-GPU' in bot:  # skia:4697, skia:4704, skia:4694, skia:4705
+    blacklist(['_',            'gm', '_', 'bigblurs'])
+    blacklist(['_',            'gm', '_', 'bleed'])
+    blacklist(['_',            'gm', '_', 'bleed_alpha_bmp'])
+    blacklist(['_',            'gm', '_', 'bleed_alpha_bmp_shader'])
+    blacklist(['_',            'gm', '_', 'bleed_alpha_image'])
+    blacklist(['_',            'gm', '_', 'bleed_alpha_image_shader'])
+    blacklist(['_',            'gm', '_', 'bleed_image'])
+    blacklist(['_',            'gm', '_', 'dropshadowimagefilter'])
+    blacklist(['_',            'gm', '_', 'filterfastbounds'])
+    blacklist([gl_prefix,      'gm', '_', 'imageblurtiled'])
+    blacklist(['_',            'gm', '_', 'imagefiltersclipped'])
+    blacklist(['_',            'gm', '_', 'imagefiltersscaled'])
+    blacklist(['_',            'gm', '_', 'imageresizetiled'])
+    blacklist(['_',            'gm', '_', 'matrixconvolution'])
+    blacklist(['_',            'gm', '_', 'strokedlines'])
+    if sample_count is not '':
+      gl_msaa_config = gl_prefix + 'msaa' + sample_count
+      blacklist([gl_msaa_config, 'gm', '_', 'imageblurtiled'])
+      blacklist([gl_msaa_config, 'gm', '_', 'imagefiltersbase'])
+
+  match = []
+  if 'Valgrind' in bot: # skia:3021
+    match.append('~Threaded')
+
+  if 'AndroidOne' in bot:  # skia:4711
+    match.append('~WritePixels')
+
+  if 'NexusPlayer' in bot:
+    match.append('~ResourceCache')
+
+  if 'Nexus10' in bot:
+    match.append('~CopySurface') # skia:5509
+    match.append('~SRGBReadWritePixels') # skia:6097
+
+  if 'GalaxyJ5' in bot:
+    match.append('~SRGBReadWritePixels') # skia:6097
+
+  if 'GalaxyS6' in bot:
+    match.append('~SpecialImage') # skia:6338
+
+  if 'GalaxyS7_G930A' in bot:
+    match.append('~WritePixels') # skia:6427
+
+  if 'ANGLE' in bot and 'Debug' in bot:
+    match.append('~GLPrograms') # skia:4717
+
+  if 'MSAN' in bot:
+    match.extend(['~Once', '~Shared'])  # Not sure what's up with these tests.
+
+  if 'TSAN' in bot:
+    match.extend(['~ReadWriteAlpha'])   # Flaky on TSAN-covered on nvidia bots.
+    match.extend(['~RGBA4444TextureTest',  # Flakier than they are important.
+                  '~RGB565TextureTest'])
+
+  if 'Vulkan' in bot and 'Adreno' in bot:
+    # skia:5777
+    match.extend(['~XfermodeImageFilterCroppedInput',
+                  '~GrTextureStripAtlasFlush',
+                  '~CopySurface'])
+
+  if 'Vulkan' in bot and 'NexusPlayer' in bot:
+    match.extend(['~hardstop_gradient', # skia:6037
+                  '~gradients_dup_color_stops',  # skia:6037
+                  '~gradients_no_texture$', # skia:6132
+                  '~tilemodes', # skia:6132
+                  '~shadertext$', # skia:6132
+                  '~bitmapfilters', # skia:6132
+                  '~GrContextFactory_abandon']) #skia:6209
+
+  if 'Vulkan' in bot and 'IntelIris540' in bot and 'Ubuntu' in bot:
+    match.extend(['~VkHeapTests']) # skia:6245
+
+  if 'Vulkan' in bot and 'IntelIris540' in bot and 'Win' in bot:
+    # skia:6398
+    blacklist(['vk', 'gm', '_', 'aarectmodes'])
+    blacklist(['vk', 'gm', '_', 'aaxfermodes'])
+    blacklist(['vk', 'gm', '_', 'arithmode'])
+    blacklist(['vk', 'gm', '_', 'composeshader_bitmap'])
+    blacklist(['vk', 'gm', '_', 'composeshader_bitmap2'])
+    blacklist(['vk', 'gm', '_', 'dftextCOLR'])
+    blacklist(['vk', 'gm', '_', 'drawregionmodes'])
+    blacklist(['vk', 'gm', '_', 'filterfastbounds'])
+    blacklist(['vk', 'gm', '_', 'fontcache'])
+    blacklist(['vk', 'gm', '_', 'fontmgr_iterWin10'])
+    blacklist(['vk', 'gm', '_', 'fontmgr_iter_factoryWin10'])
+    blacklist(['vk', 'gm', '_', 'fontmgr_matchWin10'])
+    blacklist(['vk', 'gm', '_', 'fontscalerWin'])
+    blacklist(['vk', 'gm', '_', 'fontscalerdistortable'])
+    blacklist(['vk', 'gm', '_', 'gammagradienttext'])
+    blacklist(['vk', 'gm', '_', 'gammatextWin'])
+    blacklist(['vk', 'gm', '_', 'gradtext'])
+    blacklist(['vk', 'gm', '_', 'hairmodes'])
+    blacklist(['vk', 'gm', '_', 'imagefilters_xfermodes'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersclipped'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersgraph'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersscaled'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersstroked'])
+    blacklist(['vk', 'gm', '_', 'imagefilterstransformed'])
+    blacklist(['vk', 'gm', '_', 'imageresizetiled'])
+    blacklist(['vk', 'gm', '_', 'lcdblendmodes'])
+    blacklist(['vk', 'gm', '_', 'lcdoverlap'])
+    blacklist(['vk', 'gm', '_', 'lcdtextWin'])
+    blacklist(['vk', 'gm', '_', 'lcdtextsize'])
+    blacklist(['vk', 'gm', '_', 'matriximagefilter'])
+    blacklist(['vk', 'gm', '_', 'mixedtextblobsCOLR'])
+    blacklist(['vk', 'gm', '_', 'pictureimagefilter'])
+    blacklist(['vk', 'gm', '_', 'resizeimagefilter'])
+    blacklist(['vk', 'gm', '_', 'rotate_imagefilter'])
+    blacklist(['vk', 'gm', '_', 'savelayer_lcdtext'])
+    blacklist(['vk', 'gm', '_', 'srcmode'])
+    blacklist(['vk', 'gm', '_', 'surfaceprops'])
+    blacklist(['vk', 'gm', '_', 'textblobgeometrychange'])
+    blacklist(['vk', 'gm', '_', 'textbloblooper'])
+    blacklist(['vk', 'gm', '_', 'textblobmixedsizes'])
+    blacklist(['vk', 'gm', '_', 'textblobmixedsizes_df'])
+    blacklist(['vk', 'gm', '_', 'textblobrandomfont'])
+    blacklist(['vk', 'gm', '_', 'textfilter_color'])
+    blacklist(['vk', 'gm', '_', 'textfilter_image'])
+    blacklist(['vk', 'gm', '_', 'typefacerenderingWin'])
+    blacklist(['vk', 'gm', '_', 'varied_text_clipped_lcd'])
+    blacklist(['vk', 'gm', '_', 'varied_text_ignorable_clip_lcd'])
+    blacklist(['vk', 'gm', '_', 'xfermodeimagefilter'])
+    match.append('~ApplyGamma')
+    match.append('~ComposedImageFilterBounds_Gpu')
+    match.append('~ImageFilterFailAffectsTransparentBlack_Gpu')
+    match.append('~ImageFilterZeroBlurSigma_Gpu')
+    match.append('~ImageNewShader_GPU')
+    match.append('~NewTextureFromPixmap')
+    match.append('~ReadPixels_Gpu')
+    match.append('~ReadPixels_Texture')
+    match.append('~ReadWriteAlpha')
+    match.append('~SRGBReadWritePixels')
+    match.append('~SpecialImage_DeferredGpu')
+    match.append('~SpecialImage_Gpu')
+    match.append('~WritePixels_Gpu')
+    match.append('~XfermodeImageFilterCroppedInput_Gpu')
+
+  if 'IntelIris540' in bot and 'ANGLE' in bot:
+    match.append('~IntTexture') # skia:6086
+    blacklist(['_', 'gm', '_', 'discard']) # skia:6141
+    # skia:6103
+    for config in ['angle_d3d9_es2', 'angle_d3d11_es2', 'angle_gl_es2']:
+      blacklist([config, 'gm', '_', 'multipicturedraw_invpathclip_simple'])
+      blacklist([config, 'gm', '_', 'multipicturedraw_noclip_simple'])
+      blacklist([config, 'gm', '_', 'multipicturedraw_pathclip_simple'])
+      blacklist([config, 'gm', '_', 'multipicturedraw_rectclip_simple'])
+      blacklist([config, 'gm', '_', 'multipicturedraw_rrectclip_simple'])
+
+  if 'IntelBayTrail' in bot and 'Ubuntu' in bot:
+    match.append('~ImageStorageLoad') # skia:6358
+
+  if blacklisted:
+    args.append('--blacklist')
+    args.extend(blacklisted)
+
+  if match:
+    args.append('--match')
+    args.extend(match)
+
+  # These bots run out of memory running RAW codec tests. Do not run them in
+  # parallel
+  if ('NexusPlayer' in bot or 'Nexus5' in bot or 'Nexus9' in bot
+      or 'Win8-MSVC-ShuttleB' in bot):
+    args.append('--noRAW_threading')
+
+  return args
+
+
+def key_params(api):
+  """Build a unique key from the builder name (as a list).
+
+  E.g.  arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6
+  """
+  # Don't bother to include role, which is always Test.
+  # TryBots are uploaded elsewhere so they can use the same key.
+  blacklist = ['role', 'is_trybot']
+
+  flat = []
+  for k in sorted(api.vars.builder_cfg.keys()):
+    if k not in blacklist:
+      flat.append(k)
+      flat.append(api.vars.builder_cfg[k])
+  return flat
+
+
+def test_steps(api):
+  """Run the DM test."""
+  use_hash_file = False
+  if api.vars.upload_dm_results:
+    # This must run before we write anything into
+    # api.flavor.device_dirs.dm_dir or we may end up deleting our
+    # output on machines where they're the same.
+    api.flavor.create_clean_host_dir(api.vars.dm_dir)
+    host_dm_dir = str(api.vars.dm_dir)
+    device_dm_dir = str(api.flavor.device_dirs.dm_dir)
+    if host_dm_dir != device_dm_dir:
+      api.flavor.create_clean_device_dir(device_dm_dir)
+
+    # Obtain the list of already-generated hashes.
+    hash_filename = 'uninteresting_hashes.txt'
+
+    # Ensure that the tmp_dir exists.
+    api.run.run_once(api.file.makedirs,
+                           'tmp_dir',
+                           api.vars.tmp_dir,
+                           infra_step=True)
+
+    host_hashes_file = api.vars.tmp_dir.join(hash_filename)
+    hashes_file = api.flavor.device_path_join(
+        api.flavor.device_dirs.tmp_dir, hash_filename)
+    api.run(
+        api.python.inline,
+        'get uninteresting hashes',
+        program="""
+        import contextlib
+        import math
+        import socket
+        import sys
+        import time
+        import urllib2
+
+        HASHES_URL = 'https://gold.skia.org/_/hashes'
+        RETRIES = 5
+        TIMEOUT = 60
+        WAIT_BASE = 15
+
+        socket.setdefaulttimeout(TIMEOUT)
+        for retry in range(RETRIES):
+          try:
+            with contextlib.closing(
+                urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:
+              hashes = w.read()
+              with open(sys.argv[1], 'w') as f:
+                f.write(hashes)
+                break
+          except Exception as e:
+            print 'Failed to get uninteresting hashes from %s:' % HASHES_URL
+            print e
+            if retry == RETRIES:
+              raise
+            waittime = WAIT_BASE * math.pow(2, retry)
+            print 'Retry in %d seconds.' % waittime
+            time.sleep(waittime)
+        """,
+        args=[host_hashes_file],
+        abort_on_failure=False,
+        fail_build_on_failure=False,
+        infra_step=True)
+
+    if api.path.exists(host_hashes_file):
+      api.flavor.copy_file_to_device(host_hashes_file, hashes_file)
+      use_hash_file = True
+
+  # Run DM.
+  properties = [
+    'gitHash',      api.vars.got_revision,
+    'master',       api.vars.master_name,
+    'builder',      api.vars.builder_name,
+    'build_number', api.vars.build_number,
+  ]
+  if api.vars.is_trybot:
+    properties.extend([
+      'issue',         api.vars.issue,
+      'patchset',      api.vars.patchset,
+      'patch_storage', api.vars.patch_storage,
+    ])
+  if api.vars.no_buildbot:
+    properties.extend(['no_buildbot', 'True'])
+    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
+    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
+
+  args = [
+    'dm',
+    '--undefok',   # This helps branches that may not know new flags.
+    '--resourcePath', api.flavor.device_dirs.resource_dir,
+    '--skps', api.flavor.device_dirs.skp_dir,
+    '--images', api.flavor.device_path_join(
+        api.flavor.device_dirs.images_dir, 'dm'),
+    '--colorImages', api.flavor.device_path_join(
+        api.flavor.device_dirs.images_dir, 'colorspace'),
+    '--nameByHash',
+    '--properties'
+  ] + properties
+
+  args.extend(['--svgs', api.flavor.device_dirs.svg_dir])
+
+  args.append('--key')
+  args.extend(key_params(api))
+  if use_hash_file:
+    args.extend(['--uninterestingHashesFile', hashes_file])
+  if api.vars.upload_dm_results:
+    args.extend(['--writePath', api.flavor.device_dirs.dm_dir])
+
+  skip_flag = None
+  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
+    skip_flag = '--nogpu'
+  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
+    skip_flag = '--nocpu'
+  if skip_flag:
+    args.append(skip_flag)
+  args.extend(dm_flags(api.vars.builder_name))
+
+  env = api.step.get_from_context('env', {})
+  if 'Ubuntu16' in api.vars.builder_name:
+    # The vulkan in this asset name simply means that the graphics driver
+    # supports Vulkan. It is also the driver used for GL code.
+    dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_release')
+    if 'Debug' in api.vars.builder_name:
+      dri_path = api.vars.slave_dir.join('linux_vulkan_intel_driver_debug')
+
+    if 'Vulkan' in api.vars.builder_name:
+      sdk_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'bin')
+      lib_path = api.vars.slave_dir.join('linux_vulkan_sdk', 'lib')
+      env.update({
+        'PATH':'%%(PATH)s:%s' % sdk_path,
+        'LD_LIBRARY_PATH': '%s:%s' % (lib_path, dri_path),
+        'LIBGL_DRIVERS_PATH': dri_path,
+        'VK_ICD_FILENAMES':'%s' % dri_path.join('intel_icd.x86_64.json'),
+      })
+    else:
+      # Even the non-vulkan NUC jobs could benefit from the newer drivers.
+      env.update({
+        'LD_LIBRARY_PATH': dri_path,
+        'LIBGL_DRIVERS_PATH': dri_path,
+      })
+
+  # See skia:2789.
+  if '_AbandonGpuContext' in api.vars.builder_cfg.get('extra_config', ''):
+    args.append('--abandonGpuContext')
+  if '_PreAbandonGpuContext' in api.vars.builder_cfg.get('extra_config', ''):
+    args.append('--preAbandonGpuContext')
+
+  with api.step.context({'env': env}):
+    api.run(api.flavor.step, 'dm', cmd=args, abort_on_failure=False)
+
+  if api.vars.upload_dm_results:
+    # Copy images and JSON to host machine if needed.
+    api.flavor.copy_directory_contents_to_host(
+        api.flavor.device_dirs.dm_dir, api.vars.dm_dir)
+
+
 def RunSteps(api):
-  api.sktest.run()
+  api.core.setup()
+  env = api.step.get_from_context('env', {})
+  if 'iOS' in api.vars.builder_name:
+    env['IOS_BUNDLE_ID'] = 'com.google.dm'
+  with api.step.context({'env': env}):
+    try:
+      api.flavor.install_everything()
+      test_steps(api)
+    finally:
+      api.flavor.cleanup_steps()
+    api.run.check_failure()
+
+
+TEST_BUILDERS = {
+  'client.skia': {
+    'skiabot-linux-swarm-000': [
+      'Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android',
+      'Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android',
+      'Test-Android-Clang-GalaxyJ5-GPU-Adreno306-arm-Release-Android',
+      'Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-Android',
+      'Test-Android-Clang-GalaxyS7_G930A-GPU-Adreno530-arm64-Debug-Android',
+      'Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android',
+      'Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android',
+      'Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android',
+      'Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android',
+      'Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan',
+      'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android',
+      'Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android',
+      ('Test-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-'
+       'GN_Android_Vulkan'),
+      'Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android',
+      'Test-ChromeOS-Clang-Chromebook_C100p-GPU-MaliT764-arm-Debug',
+      'Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug',
+      'Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug',
+      'Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN',
+      'Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
+      ('Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind' +
+       '_AbandonGpuContext'),
+      ('Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind' +
+       '_PreAbandonGpuContext'),
+      'Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan',
+      'Test-Ubuntu16-Clang-NUC-GPU-IntelIris540-x86_64-Release',
+      'Test-Ubuntu16-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug',
+      'Test-Ubuntu16-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug',
+      'Test-Win10-MSVC-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-Vulkan',
+      'Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE',
+      'Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan',
+      'Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan',
+      'Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan',
+      'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot',
+      'Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
+      'Test-iOS-Clang-iPadMini4-GPU-GX6450-arm-Release',
+      ('Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_' +
+        'SCALEDIMAGECACHE'),
+    ],
+  },
+}
 
 
 def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('skia'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skimage', 'VERSION'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skp', 'VERSION'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'svg', 'VERSION'),
+              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+          )
+        )
+        if 'Trybot' in builder:
+          test += api.properties(issue=500,
+                                 patchset=1,
+                                 rietveld='https://codereview.chromium.org')
+        if 'Win' in builder:
+          test += api.platform('win', 64)
+
+        if 'ChromeOS' in builder:
+          test += api.step_data('read chromeos ip',
+                  stdout=api.raw_io.output('{"user_ip":"foo@127.0.0.1"}'))
+
+
+        yield test
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
   yield (
-    api.test('Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug') +
-    api.properties(buildername='Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug',
-                   mastername='fake-master',
-                   slavename='fake-slave',
-                   buildnumber=5,
+    api.test('failed_dm') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
                    revision='abc123',
                    path_config='kitchen',
                    swarm_out_dir='[SWARM_OUT_DIR]') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                   'skimage', 'VERSION'),
+                                     'skimage', 'VERSION'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                   'skp', 'VERSION'),
+                                     'skp', 'VERSION'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                   'svg', 'VERSION'),
+                                     'svg', 'VERSION'),
         api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    )
+    ) +
+    api.step_data('symbolized dm', retcode=1)
+  )
+
+  builder = 'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android'
+  yield (
+    api.test('failed_get_hashes') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.step_data('get uninteresting hashes', retcode=1)
+  )
+
+  builder = 'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot'
+  yield (
+    api.test('big_issue_number') +
+    api.properties(buildername=builder,
+                     mastername='client.skia.compile',
+                     slavename='skiabot-linux-swarm-000',
+                     buildnumber=5,
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     rietveld='https://codereview.chromium.org',
+                     patchset=1,
+                     issue=2147533002L) +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.platform('win', 64)
+  )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug-Trybot'
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
+
+  yield (
+      api.test('nobuildbot') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.step_data('get swarming bot id',
+          stdout=api.raw_io.output('skia-bot-123')) +
+      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
+  )
+
+  builder = 'Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Debug-GN_Android'
+  yield (
+    api.test('failed_push') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.step_data('push [START_DIR]/skia/resources/* '+
+                  '/sdcard/revenge_of_the_skiabot/resources', retcode=1)
+  )
+
+  builder = 'Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Debug-Android'
+  yield (
+    api.test('failed_pull') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.step_data('dm', retcode=1) +
+    api.step_data('pull /sdcard/revenge_of_the_skiabot/dm_out '+
+                  '[CUSTOM_[SWARM_OUT_DIR]]/dm', retcode=1)
   )
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.expected/failed_all.json b/infra/bots/recipes/upload_dm_results.expected/failed_all.json
similarity index 100%
rename from infra/bots/recipe_modules/upload_dm_results/example.expected/failed_all.json
rename to infra/bots/recipes/upload_dm_results.expected/failed_all.json
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.expected/failed_once.json b/infra/bots/recipes/upload_dm_results.expected/failed_once.json
similarity index 100%
rename from infra/bots/recipe_modules/upload_dm_results/example.expected/failed_once.json
rename to infra/bots/recipes/upload_dm_results.expected/failed_once.json
diff --git a/infra/bots/recipes/upload_dm_results.expected/upload.json b/infra/bots/recipes/upload_dm_results.expected/normal_bot.json
similarity index 100%
rename from infra/bots/recipes/upload_dm_results.expected/upload.json
rename to infra/bots/recipes/upload_dm_results.expected/normal_bot.json
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.expected/recipe_with_gerrit_patch.json b/infra/bots/recipes/upload_dm_results.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipe_modules/upload_dm_results/example.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipes/upload_dm_results.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.expected/trybot.json b/infra/bots/recipes/upload_dm_results.expected/trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/upload_dm_results/example.expected/trybot.json
rename to infra/bots/recipes/upload_dm_results.expected/trybot.json
diff --git a/infra/bots/recipes/upload_dm_results.py b/infra/bots/recipes/upload_dm_results.py
index c161225..0995656 100644
--- a/infra/bots/recipes/upload_dm_results.py
+++ b/infra/bots/recipes/upload_dm_results.py
@@ -6,21 +6,153 @@
 # Recipe for uploading DM results.
 
 
+import calendar
+
+
 DEPS = [
+  'build/file',
+  'recipe_engine/json',
+  'recipe_engine/path',
   'recipe_engine/properties',
-  'upload_dm_results',
+  'recipe_engine/shutil',
+  'recipe_engine/step',
+  'recipe_engine/time',
 ]
 
 
+DM_JSON = 'dm.json'
+UPLOAD_ATTEMPTS = 5
+VERBOSE_LOG = 'verbose.log'
+
+
+def cp(api, name, src, dst, extra_args=None):
+  cmd = ['gsutil', 'cp']
+  if extra_args:
+    cmd.extend(extra_args)
+  cmd.extend([src, dst])
+
+  name = 'upload %s' % name
+  for i in xrange(UPLOAD_ATTEMPTS):
+    step_name = name
+    if i > 0:
+      step_name += ' (attempt %d)' % (i+1)
+    try:
+      api.step(step_name, cmd=cmd)
+      break
+    except api.step.StepFailure:
+      if i == UPLOAD_ATTEMPTS - 1:
+        raise
+
+
 def RunSteps(api):
-  api.upload_dm_results.run()
+  builder_name = api.properties['buildername']
+  revision = api.properties['revision']
+
+  results_dir = api.path['start_dir'].join('dm')
+
+  # Move dm.json and verbose.log to their own directory.
+  json_file = results_dir.join(DM_JSON)
+  log_file = results_dir.join(VERBOSE_LOG)
+  tmp_dir = api.path['start_dir'].join('tmp_upload')
+  api.shutil.makedirs('tmp dir', tmp_dir, infra_step=True)
+  api.shutil.copy('copy dm.json', json_file, tmp_dir)
+  api.shutil.copy('copy verbose.log', log_file, tmp_dir)
+  api.shutil.remove('rm old dm.json', json_file)
+  api.shutil.remove('rm old verbose.log', log_file)
+
+  # Upload the images.
+  image_dest_path = 'gs://%s/dm-images-v1' % api.properties['gs_bucket']
+  files_to_upload = api.file.glob(
+      'find images',
+      results_dir.join('*'),
+      test_data=[results_dir.join('someimage.png')],
+      infra_step=True)
+  if len(files_to_upload) > 0:
+    cp(api, 'images', results_dir.join('*'), image_dest_path)
+
+  # Upload the JSON summary and verbose.log.
+  now = api.time.utcnow()
+  summary_dest_path = '/'.join([
+      'dm-json-v1',
+      str(now.year ).zfill(4),
+      str(now.month).zfill(2),
+      str(now.day  ).zfill(2),
+      str(now.hour ).zfill(2),
+      revision,
+      builder_name,
+      str(int(calendar.timegm(now.utctimetuple())))])
+
+  # Trybot results are further siloed by issue/patchset.
+  issue = str(api.properties.get('issue', ''))
+  patchset = str(api.properties.get('patchset', ''))
+  if api.properties.get('patch_storage', '') == 'gerrit':
+    issue = str(api.properties['patch_issue'])
+    patchset = str(api.properties['patch_set'])
+  if issue and patchset:
+    summary_dest_path = '/'.join((
+        'trybot', summary_dest_path, issue, patchset))
+
+  summary_dest_path = 'gs://%s/%s' % (api.properties['gs_bucket'],
+                                      summary_dest_path)
+
+  cp(api, 'JSON and logs', tmp_dir.join('*'), summary_dest_path,
+     ['-z', 'json,log'])
 
 
 def GenTests(api):
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
   yield (
-    api.test('upload') +
-    api.properties(buildername='Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
+    api.test('normal_bot') +
+    api.properties(buildername=builder,
                    gs_bucket='skia-infra-gm',
                    revision='abc123',
                    path_config='kitchen')
   )
+
+  yield (
+    api.test('failed_once') +
+    api.properties(buildername=builder,
+                   gs_bucket='skia-infra-gm',
+                   revision='abc123',
+                   path_config='kitchen') +
+    api.step_data('upload images', retcode=1)
+  )
+
+  yield (
+    api.test('failed_all') +
+    api.properties(buildername=builder,
+                   gs_bucket='skia-infra-gm',
+                   revision='abc123',
+                   path_config='kitchen') +
+    api.step_data('upload images', retcode=1) +
+    api.step_data('upload images (attempt 2)', retcode=1) +
+    api.step_data('upload images (attempt 3)', retcode=1) +
+    api.step_data('upload images (attempt 4)', retcode=1) +
+    api.step_data('upload images (attempt 5)', retcode=1)
+  )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
+  yield (
+    api.test('trybot') +
+    api.properties(buildername=builder,
+                   gs_bucket='skia-infra-gm',
+                   revision='abc123',
+                   path_config='kitchen',
+                   issue='12345',
+                   patchset='1002')
+  )
+
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          gs_bucket='skia-infra-gm',
+          revision='abc123',
+          path_config='kitchen',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
diff --git a/infra/bots/recipes/upload_nano_results.expected/upload.json b/infra/bots/recipes/upload_nano_results.expected/normal_bot.json
similarity index 100%
rename from infra/bots/recipes/upload_nano_results.expected/upload.json
rename to infra/bots/recipes/upload_nano_results.expected/normal_bot.json
diff --git a/infra/bots/recipe_modules/upload_nano_results/example.expected/recipe_with_gerrit_patch.json b/infra/bots/recipes/upload_nano_results.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipe_modules/upload_nano_results/example.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipes/upload_nano_results.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipe_modules/upload_nano_results/example.expected/trybot.json b/infra/bots/recipes/upload_nano_results.expected/trybot.json
similarity index 100%
rename from infra/bots/recipe_modules/upload_nano_results/example.expected/trybot.json
rename to infra/bots/recipes/upload_nano_results.expected/trybot.json
diff --git a/infra/bots/recipes/upload_nano_results.py b/infra/bots/recipes/upload_nano_results.py
index 4929efa..ee0fc51 100644
--- a/infra/bots/recipes/upload_nano_results.py
+++ b/infra/bots/recipes/upload_nano_results.py
@@ -7,20 +7,86 @@
 
 
 DEPS = [
+  'build/file',
+  'recipe_engine/path',
   'recipe_engine/properties',
-  'upload_nano_results',
+  'recipe_engine/step',
+  'recipe_engine/time',
 ]
 
 
 def RunSteps(api):
-  api.upload_nano_results.run()
+  # Upload the nanobench resuls.
+  builder_name = api.properties['buildername']
+
+  now = api.time.utcnow()
+  src_path = api.path['start_dir'].join(
+      'perfdata', builder_name, 'data')
+  with api.step.context({'cwd': src_path}):
+    results = api.file.glob(
+        'find results',
+        src_path.join('*.json'),
+        test_data=[src_path.join('nanobench_abc123.json')],
+        infra_step=True)
+  if len(results) != 1:  # pragma: nocover
+    raise Exception('Unable to find nanobench or skpbench JSON file!')
+
+  src = results[0]
+  basename = api.path.basename(src)
+  gs_path = '/'.join((
+      'nano-json-v1', str(now.year).zfill(4),
+      str(now.month).zfill(2), str(now.day).zfill(2), str(now.hour).zfill(2),
+      builder_name))
+
+  issue = str(api.properties.get('issue', ''))
+  patchset = str(api.properties.get('patchset', ''))
+  if api.properties.get('patch_storage', '') == 'gerrit':
+    issue = str(api.properties['patch_issue'])
+    patchset = str(api.properties['patch_set'])
+  if issue and patchset:
+    gs_path = '/'.join(('trybot', gs_path, issue, patchset))
+
+  dst = '/'.join((
+      'gs://%s' % api.properties['gs_bucket'], gs_path, basename))
+
+  api.step(
+      'upload',
+      cmd=['gsutil', 'cp', '-z', 'json', src, dst],
+      infra_step=True)
 
 
 def GenTests(api):
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
   yield (
-    api.test('upload') +
-    api.properties(buildername='Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
+    api.test('normal_bot') +
+    api.properties(buildername=builder,
                    gs_bucket='skia-perf',
                    revision='abc123',
                    path_config='kitchen')
   )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
+  yield (
+    api.test('trybot') +
+    api.properties(buildername=builder,
+                   gs_bucket='skia-perf',
+                   revision='abc123',
+                   path_config='kitchen',
+                   issue='12345',
+                   patchset='1002')
+  )
+
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          gs_bucket='skia-perf',
+          revision='abc123',
+          path_config='kitchen',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )