Add swarming task for nanobench upload

BUG=skia:5719
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2364693003

Review-Url: https://codereview.chromium.org/2364693003
diff --git a/infra/bots/recipes/swarm_trigger.expected/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot.json b/infra/bots/recipes/swarm_trigger.expected/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot.json
index a5a2dcc..94e3890 100644
--- a/infra/bots/recipes/swarm_trigger.expected/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot.json
+++ b/infra/bots/recipes/swarm_trigger.expected/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot.json
@@ -766,47 +766,13 @@
   },
   {
     "cmd": [
-      "git",
-      "show",
-      "HEAD",
-      "--format=%at",
-      "-s"
-    ],
-    "cwd": "[SLAVE_BUILD]/skia",
-    "name": "git show",
-    "stdout": "/path/to/tmp/"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport os, sys\nfrom common import chromium_utils # Error? See https://crbug.com/584783.\n\n\nif os.path.exists(sys.argv[1]):\n  chromium_utils.RemoveDirectory(sys.argv[1])\n",
-      "[SLAVE_BUILD]/perfdata/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/data"
-    ],
-    "env": {
-      "PYTHONPATH": "[SLAVE_BUILD]/skia/infra/bots/.recipe_deps/build/scripts"
-    },
-    "name": "rmtree data",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import os, sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@from common import chromium_utils # Error? See https://crbug.com/584783.@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@if os.path.exists(sys.argv[1]):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  chromium_utils.RemoveDirectory(sys.argv[1])@@@",
-      "@@@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",
-      "[SLAVE_BUILD]/perfdata/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/data",
+      "[SLAVE_BUILD]/swarming_temp_dir",
       "511"
     ],
-    "name": "makedirs perf_dir",
+    "name": "makedirs swarming tmp dir (3)",
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
@@ -825,25 +791,222 @@
       "python",
       "-u",
       "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "[SLAVE_BUILD]/swarming_temp_dir/outputs/perf_skia/0/perfdata/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/data/nanobench_abc123.json",
-      "[SLAVE_BUILD]/perfdata/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/data/nanobench_abc123_1408633190.json"
+      "{\n    \"args\": [\n        \"--isolate\", \n        \"[SLAVE_BUILD]/skia/infra/bots/upload_nano_results.isolate\", \n        \"--isolated\", \n        \"[SLAVE_BUILD]/swarming_temp_dir/skia-task-upload_nano_results_skia.isolated\", \n        \"--config-variable\", \n        \"OS\", \n        \"Linux\", \n        \"--blacklist\", \n        \".git\", \n        \"--blacklist\", \n        \"out\", \n        \"--blacklist\", \n        \"*.pyc\", \n        \"--blacklist\", \n        \".recipe_deps\", \n        \"--extra-variable\", \n        \"WORKDIR\", \n        \"[SLAVE_BUILD]\"\n    ], \n    \"dir\": \"[SLAVE_BUILD]\", \n    \"version\": 1\n}",
+      "[SLAVE_BUILD]/swarming_temp_dir/upload_nano_results_skia.isolated.gen.json"
     ],
-    "name": "perf_results"
+    "name": "Write upload_nano_results_skia.isolated.gen.json"
   },
   {
     "cmd": [
       "python",
       "-u",
-      "RECIPE_MODULE[skia::core]/resources/upload_bench_results.py",
-      "Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot",
-      "5",
-      "[SLAVE_BUILD]/perfdata/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/data",
-      "abc123",
-      "[SLAVE_BUILD]/skia/infra/bots/.recipe_deps/depot_tools/gsutil.py",
-      "500"
+      "RECIPE_MODULE[build::isolate]/resources/isolate.py",
+      "[SLAVE_BUILD]/swarming.client",
+      "batcharchive",
+      "--dump-json",
+      "/path/to/tmp/json",
+      "--isolate-server",
+      "https://isolateserver.appspot.com",
+      "--verbose",
+      "[SLAVE_BUILD]/swarming_temp_dir/upload_nano_results_skia.isolated.gen.json"
     ],
-    "cwd": "[SLAVE_BUILD]/skia",
-    "name": "Upload perf results"
+    "name": "isolate tests (3)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"upload_nano_results_skia\": \"[dummy hash for upload_nano_results_skia]\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@SET_BUILD_PROPERTY@swarm_hashes@{\"upload_nano_results_skia\": \"[dummy hash for upload_nano_results_skia]\"}@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\nwith open(sys.argv[1]) as f:\n  isolated = json.load(f)\nif not isolated.get('includes'):\n  isolated['includes'] = []\nfor h in sys.argv[2:]:\n  isolated['includes'].append(h)\nwith open(sys.argv[1], 'w') as f:\n  json.dump(isolated, f, sort_keys=True)\n",
+      "[SLAVE_BUILD]/swarming_temp_dir/skia-task-upload_nano_results_skia.isolated",
+      "abc123"
+    ],
+    "name": "add_isolated_input (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(sys.argv[1]) as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  isolated = json.load(f)@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not isolated.get('includes'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  isolated['includes'] = []@@@",
+      "@@@STEP_LOG_LINE@python.inline@for h in sys.argv[2:]:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  isolated['includes'].append(h)@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(sys.argv[1], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  json.dump(isolated, f, sort_keys=True)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[SLAVE_BUILD]/swarming.client/isolateserver.py",
+      "archive",
+      "--isolate-server",
+      "https://isolateserver.appspot.com",
+      "[SLAVE_BUILD]/swarming_temp_dir/skia-task-upload_nano_results_skia.isolated"
+    ],
+    "name": "upload new .isolated file for upload_nano_results_skia",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[SLAVE_BUILD]/swarming.client/swarming.py",
+      "trigger",
+      "--swarming",
+      "https://chromium-swarm.appspot.com",
+      "--isolate-server",
+      "https://isolateserver.appspot.com",
+      "--priority",
+      "90",
+      "--shards",
+      "1",
+      "--task-name",
+      "upload_nano_results_skia/Linux/def456/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/5",
+      "--dump-json",
+      "/path/to/tmp/json",
+      "--expiration",
+      "72000",
+      "--io-timeout",
+      "2400",
+      "--hard-timeout",
+      "14400",
+      "--dimension",
+      "cpu",
+      "x86-64-avx2",
+      "--dimension",
+      "gpu",
+      "none",
+      "--dimension",
+      "os",
+      "Linux",
+      "--dimension",
+      "pool",
+      "Skia",
+      "--tag",
+      "allow_milo:1",
+      "--tag",
+      "buildername:Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot",
+      "--tag",
+      "buildnumber:5",
+      "--tag",
+      "data:def456",
+      "--tag",
+      "master:client.skia",
+      "--tag",
+      "name:upload_nano_results_skia",
+      "--tag",
+      "os:Linux",
+      "--tag",
+      "revision:abc123",
+      "--tag",
+      "rietveld:https://codereview.chromium.org/500/#ps1",
+      "--tag",
+      "slavename:skiabot-linux-swarm-000",
+      "--tag",
+      "stepname:upload_nano_results_skia on Linux",
+      "--idempotent",
+      "def456",
+      "--",
+      "--workdir",
+      "../../..",
+      "upload_nano_results",
+      "rietveld=https://codereview.chromium.org",
+      "buildername=Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot",
+      "mastername=client.skia",
+      "buildnumber=5",
+      "slavename=skiabot-linux-swarm-000",
+      "reason=Triggered by Skia swarm_trigger Recipe",
+      "swarm_out_dir=${ISOLATED_OUTDIR}",
+      "patchset=1",
+      "issue=500",
+      "revision=abc123"
+    ],
+    "name": "[trigger] upload_nano_results_skia on Linux",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"base_task_name\": \"upload_nano_results_skia/Linux/def456/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/5\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"tasks\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"upload_nano_results_skia/Linux/def456/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/5\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"shard_index\": 0, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"task_id\": \"10000\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"view_url\": \"https://chromium-swarm.appspot.com/user/task/10000\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LINK@shard #0@https://chromium-swarm.appspot.com/user/task/10000@@@",
+      "@@@STEP_LINK@view steps on Milo@https://luci-milo.appspot.com/swarming/task/10000@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[SLAVE_BUILD]/swarming.client/swarming.py",
+      "collect",
+      "--swarming",
+      "https://chromium-swarm.appspot.com",
+      "--decorate",
+      "--print-status-updates",
+      "--json",
+      "{\"base_task_name\": \"upload_nano_results_skia/Linux/def456/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/5\", \"tasks\": {\"upload_nano_results_skia/Linux/def456/Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Trybot/5\": {\"shard_index\": 0, \"task_id\": \"10000\", \"view_url\": \"https://chromium-swarm.appspot.com/user/task/10000\"}}}",
+      "--task-summary-json",
+      "/path/to/tmp/json"
+    ],
+    "name": "upload_nano_results_skia on Linux",
+    "~followup_annotations": [
+      "@@@STEP_TEXT@swarming pending 71s@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"shards\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@    {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"abandoned_ts\": null, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"bot_id\": \"vm30\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"completed_ts\": \"2014-09-25T01:42:00.123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"created_ts\": \"2014-09-25T01:41:00.123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"durations\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@        5.7, @@@",
+      "@@@STEP_LOG_LINE@json.output@        31.5@@@",
+      "@@@STEP_LOG_LINE@json.output@      ], @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"exit_codes\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@        0, @@@",
+      "@@@STEP_LOG_LINE@json.output@        0@@@",
+      "@@@STEP_LOG_LINE@json.output@      ], @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"failure\": false, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"id\": \"148aa78d7aa0000\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"internal_failure\": false, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"isolated_out\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"isolated\": \"abc123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"isolatedserver\": \"https://isolateserver.appspot.com\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"namespace\": \"default-gzip\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"view_url\": \"blah\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"modified_ts\": \"2014-09-25 01:42:00\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"heartbeat-canary-2014-09-25_01:41:55-os=Windows\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"outputs\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"Heart beat succeeded on win32.\\n\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"Foo\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ], @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"started_ts\": \"2014-09-25T01:42:11.123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"state\": 112, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"try_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"user\": \"unknown\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  ]@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LINK@shard #0 isolated out@blah@@@",
+      "@@@STEP_LINK@view steps on Milo@https://luci-milo.appspot.com/swarming/task/148aa78d7aa0000@@@"
+    ]
   },
   {
     "name": "$result",
diff --git a/infra/bots/recipes/swarm_trigger.py b/infra/bots/recipes/swarm_trigger.py
index f086e77..d6272a1 100644
--- a/infra/bots/recipes/swarm_trigger.py
+++ b/infra/bots/recipes/swarm_trigger.py
@@ -65,6 +65,14 @@
 }
 
 
+UPLOAD_DIMENSIONS = {
+  'pool': 'Skia',
+  'os': 'Linux',
+  'cpu': 'x86-64-avx2',
+  'gpu': 'none',
+}
+
+
 def derive_compile_bot_name(api):
   builder_name = api.properties['buildername']
   builder_cfg = api.builder_name_schema.DictForBuilderName(builder_name)
@@ -184,7 +192,8 @@
 def trigger_task(api, task_name, builder, master, slave, buildnumber,
                  builder_cfg, got_revision, infrabots_dir, idempotent=False,
                  store_output=True, extra_isolate_hashes=None, expiration=None,
-                 hard_timeout=None, io_timeout=None, cipd_packages=None):
+                 hard_timeout=None, io_timeout=None, cipd_packages=None,
+                 recipe_name=None, isolate_file=None, dimensions=None):
   """Trigger the given bot to run as a Swarming task."""
   # TODO(borenet): We're using Swarming directly to run the recipe through
   # recipes.py. Once it's possible to track the state of a Buildbucket build,
@@ -212,19 +221,19 @@
 
   extra_args = [
       '--workdir', '../../..',
-      'swarm_%s' % task_name,
+      recipe_name or 'swarm_%s' % task_name,
   ]
   for k, v in properties.iteritems():
     extra_args.append('%s=%s' % (k, v))
 
   isolate_base_dir = api.path['slave_build']
-  dimensions = swarm_dimensions(builder_cfg)
+  dimensions = dimensions or swarm_dimensions(builder_cfg)
   isolate_blacklist = ['.git', 'out', '*.pyc', '.recipe_deps']
   isolate_vars = {
     'WORKDIR': api.path['slave_build'],
   }
 
-  isolate_file = '%s_skia.isolate' % task_name
+  isolate_file = isolate_file or '%s_skia.isolate' % task_name
   if 'Coverage' == builder_cfg.get('configuration'):
     isolate_file = 'coverage_skia.isolate'
   if 'RecreateSKPs' in builder:
@@ -467,40 +476,36 @@
       cipd_packages=cipd_packages)
 
 
-def perf_steps_collect(api, task, got_revision, is_trybot):
+def perf_steps_collect(api, task, builder_cfg, got_revision, infrabots_dir):
   """Wait for perf steps to finish and upload results."""
   # Wait for nanobench to finish, download the results.
   api.run.rmtree(task.task_output_dir)
-  api.swarming.collect_swarming_task(task)
+  if not api.vars.upload_perf_results:  # pragma: nocover
+    api.swarming.collect_swarming_task(task)
+    return
+
+  perf_hash = api.swarming.collect_swarming_task_isolate_hash(task)
 
   # Upload the results.
-  if api.vars.upload_perf_results:
-    perf_data_dir = api.path['slave_build'].join(
-        'perfdata', api.properties['buildername'], 'data')
-    git_timestamp = api.git.get_timestamp(test_data='1408633190',
-                                          infra_step=True)
-    api.run.rmtree(perf_data_dir)
-    api.file.makedirs('perf_dir', perf_data_dir, infra_step=True)
-    src_results_file = task.task_output_dir.join(
-        '0', 'perfdata', api.properties['buildername'], 'data',
-        'nanobench_%s.json' % got_revision)
-    dst_results_file = perf_data_dir.join(
-        'nanobench_%s_%s.json' % (got_revision, git_timestamp))
-    api.file.copy('perf_results', src_results_file, dst_results_file,
-                  infra_step=True)
+  task = trigger_task(
+      api,
+      'upload_nano_results',
+      api.properties['buildername'],
+      api.properties['mastername'],
+      api.properties['slavename'],
+      api.properties['buildnumber'],
+      builder_cfg,
+      got_revision,
+      infrabots_dir,
+      idempotent=True,
+      store_output=False,
+      cipd_packages=None,
+      extra_isolate_hashes=[perf_hash],
+      recipe_name='upload_nano_results',
+      isolate_file='upload_nano_results.isolate',
+      dimensions=UPLOAD_DIMENSIONS)
 
-    gsutil_path = api.path['slave_build'].join(
-        'skia', 'infra', 'bots', '.recipe_deps', 'depot_tools', 'gsutil.py')
-    upload_args = [api.properties['buildername'], api.properties['buildnumber'],
-                   perf_data_dir, got_revision, gsutil_path]
-    if is_trybot:
-      upload_args.append(get_issue_num(api))
-    api.python(
-        'Upload perf results',
-        script=api.core.resource('upload_bench_results.py'),
-        args=upload_args,
-        cwd=api.path['checkout'],
-        infra_step=True)
+  return api.swarming.collect_swarming_task(task)
 
 
 def test_steps_trigger(api, builder_cfg, got_revision, infrabots_dir,
@@ -742,8 +747,8 @@
                        got_revision, is_trybot, builder_cfg)
 
   if perf_task:
-    perf_steps_collect(api, perf_task,
-                       got_revision, is_trybot)
+    perf_steps_collect(api, perf_task, builder_cfg,
+                       got_revision, infrabots_dir)
 
 
 def test_for_bot(api, builder, mastername, slavename, testname=None):
@@ -777,6 +782,9 @@
     test += api.step_data(
         'upload new .isolated file for perf_skia',
         stdout=api.raw_io.output('def456 XYZ.isolated'))
+    test += api.step_data(
+        'upload new .isolated file for upload_nano_results_skia',
+        stdout=api.raw_io.output('def456 XYZ.isolated'))
   if 'Housekeeper' in builder and 'RecreateSKPs' not in builder:
     test += api.step_data(
         'upload new .isolated file for housekeeper_skia',
diff --git a/infra/bots/recipes/upload_nano_results.expected/normal_bot.json b/infra/bots/recipes/upload_nano_results.expected/normal_bot.json
new file mode 100644
index 0000000..0668361
--- /dev/null
+++ b/infra/bots/recipes/upload_nano_results.expected/normal_bot.json
@@ -0,0 +1,31 @@
+[
+  {
+    "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/",
+      "nanobench*.json"
+    ],
+    "cwd": "[CWD]/perfdata/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug/data",
+    "name": "find results"
+  },
+  {
+    "cmd": [
+      "gsutil",
+      "cp",
+      "-a",
+      "public-read",
+      "-z",
+      "json",
+      "[CWD]/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"
+    ],
+    "name": "upload"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/upload_nano_results.expected/trybot.json b/infra/bots/recipes/upload_nano_results.expected/trybot.json
new file mode 100644
index 0000000..0ad0fe4
--- /dev/null
+++ b/infra/bots/recipes/upload_nano_results.expected/trybot.json
@@ -0,0 +1,31 @@
+[
+  {
+    "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/",
+      "nanobench*.json"
+    ],
+    "cwd": "[CWD]/perfdata/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot/data",
+    "name": "find results"
+  },
+  {
+    "cmd": [
+      "gsutil",
+      "cp",
+      "-a",
+      "public-read",
+      "-z",
+      "json",
+      "[CWD]/perfdata/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot/data/nanobench_abc123.json",
+      "gs://skia-perf/trybot/nano-json-v1/2012/05/14/12/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot/12345/1002/nanobench_abc123.json"
+    ],
+    "name": "upload"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/upload_nano_results.py b/infra/bots/recipes/upload_nano_results.py
new file mode 100644
index 0000000..8cc39be
--- /dev/null
+++ b/infra/bots/recipes/upload_nano_results.py
@@ -0,0 +1,73 @@
+# 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.
+
+
+DEPS = [
+  'build/file',
+  'recipe_engine/path',
+  'recipe_engine/properties',
+  'recipe_engine/step',
+  'recipe_engine/time',
+]
+
+
+def RunSteps(api):
+  # Upload the nanobench resuls.
+  builder_name = api.properties['buildername']
+  issue = str(api.properties.get('issue'))
+  patchset = str(api.properties.get('patchset'))
+
+  now = api.time.utcnow()
+
+  src_path = api.path['cwd'].join(
+      'perfdata', builder_name, 'data')
+  results = api.file.glob(
+      'find results',
+      'nanobench*.json',
+      cwd=src_path,
+      test_data=['nanobench_abc123.json'],
+      infra_step=True)
+  if len(results) != 1:  # pragma: nocover
+    raise Exception('Unable to find nanobench JSON file!')
+
+  src = src_path.join(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))
+
+  if builder_name.endswith('-Trybot'):
+    if not (issue and patchset):  # pragma: nocover
+      raise Exception('issue and patchset properties are required for trybots.')
+    gs_path = '/'.join(('trybot', gs_path, issue, patchset))
+
+  dst = '/'.join(('gs://skia-perf', gs_path, basename))
+
+  api.step('upload',
+           cmd=['gsutil', 'cp', '-a', 'public-read', '-z', 'json', src, dst],
+           infra_step=True)
+
+
+def GenTests(api):
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
+  yield (
+    api.test('normal_bot') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen')
+  )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
+  yield (
+    api.test('trybot') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen',
+                   issue='12345',
+                   patchset='1002')
+  )
diff --git a/infra/bots/upload_nano_results.isolate b/infra/bots/upload_nano_results.isolate
new file mode 100644
index 0000000..767fa3a
--- /dev/null
+++ b/infra/bots/upload_nano_results.isolate
@@ -0,0 +1,10 @@
+{
+  'includes': [
+    'swarm_recipe.isolate',
+  ],
+  'variables': {
+    'files': [
+      '../../../.gclient',
+    ],
+  },
+}