Update metrics from test runner

This is a reland of https://flutter-review.googlesource.com/c/recipes/+/17343
Changes:
1) addressed null result data case: https://github.com/flutter/flutter/pull/88749
2) separate gcs and cocoon token
3) add token util as a module so that engine and devicelab drone can share
4) inject task_name (new) - following https://github.com/flutter/flutter/pull/89004

Change-Id: Ia27a135ed2f4ed792756fb67476779e5916edad6
Bug: https://github.com/flutter/flutter/issues/88484
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/17442
Reviewed-by: Casey Hillers <chillers@google.com>
Commit-Queue: Keyong Han <keyonghan@google.com>
diff --git a/recipe_modules/token_util/__init__.py b/recipe_modules/token_util/__init__.py
new file mode 100644
index 0000000..79334ab
--- /dev/null
+++ b/recipe_modules/token_util/__init__.py
@@ -0,0 +1,6 @@
+DEPS = [
+    'recipe_engine/file',
+    'recipe_engine/path',
+    'recipe_engine/service_account',
+    "recipe_engine/step",
+]
diff --git a/recipe_modules/token_util/api.py b/recipe_modules/token_util/api.py
new file mode 100644
index 0000000..7c9618f
--- /dev/null
+++ b/recipe_modules/token_util/api.py
@@ -0,0 +1,48 @@
+# Copyright 2020 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.
+
+from recipe_engine import recipe_api
+
+
+class TokenUtilApi(recipe_api.RecipeApi):
+  """Utilities to generate tokens for communicating data."""
+
+  def metric_center_token(self):
+    """Generate a token to interact with GCS.
+
+    Returns the path to the written token.
+    """
+    service_account = self.m.service_account.default()
+    metrics_center_access_token = service_account.get_access_token(
+        scopes=[
+            'https://www.googleapis.com/auth/cloud-platform',
+            'https://www.googleapis.com/auth/datastore'
+        ]
+    )
+    metrics_center_token_path = self.m.path.mkstemp()
+    self.m.file.write_text(
+        "write metric center token",
+        metrics_center_token_path,
+        metrics_center_access_token,
+        include_log=False
+    )
+    return metrics_center_token_path
+
+  def cocoon_token(self):
+    """Generate a token to interact with Cocoon backend APIs.
+
+    Returns the path to the written token.
+    """
+    service_account = self.m.service_account.default()
+    cocoon_access_token = service_account.get_access_token()
+
+    cocoon_access_token_path = self.m.path.mkstemp()
+    self.m.file.write_text(
+        "write cocoon token",
+        cocoon_access_token_path,
+        cocoon_access_token,
+        include_log=False
+    )
+    return cocoon_access_token_path
+
diff --git a/recipe_modules/token_util/examples/full.expected/basic.json b/recipe_modules/token_util/examples/full.expected/basic.json
new file mode 100644
index 0000000..89c9607
--- /dev/null
+++ b/recipe_modules/token_util/examples/full.expected/basic.json
@@ -0,0 +1,55 @@
+[
+  {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-scopes",
+      "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore",
+      "-lifetime",
+      "3m"
+    ],
+    "infra_step": true,
+    "name": "get access token for default account"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]/tmp_tmp_1"
+    ],
+    "infra_step": true,
+    "name": "write metric center token"
+  },
+  {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-lifetime",
+      "3m"
+    ],
+    "infra_step": true,
+    "name": "get access token for default account (2)"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]/tmp_tmp_2"
+    ],
+    "infra_step": true,
+    "name": "write cocoon token"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/token_util/examples/full.py b/recipe_modules/token_util/examples/full.py
new file mode 100644
index 0000000..26e7da1
--- /dev/null
+++ b/recipe_modules/token_util/examples/full.py
@@ -0,0 +1,19 @@
+# Copyright 2020 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.
+
+from recipe_engine.recipe_api import Property
+
+DEPS = [
+    'flutter/token_util',
+]
+
+
+def RunSteps(api):
+  api.token_util.metric_center_token()
+  api.token_util.cocoon_token()
+
+
+def GenTests(api):
+  yield api.test('basic')
+
diff --git a/recipes/devicelab/devicelab_drone.expected/basic.json b/recipes/devicelab/devicelab_drone.expected/basic.json
index 0116e98..d96a541 100644
--- a/recipes/devicelab/devicelab_drone.expected/basic.json
+++ b/recipes/devicelab/devicelab_drone.expected/basic.json
@@ -110,6 +110,17 @@
     ]
   },
   {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%ct",
+      "-n",
+      "1"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+    "name": "git commit time"
+  },
+  {
     "cmd": [],
     "name": "Initialize logs"
   },
diff --git a/recipes/devicelab/devicelab_drone.expected/local-engine.json b/recipes/devicelab/devicelab_drone.expected/local-engine.json
index 9e45a30..c68ad6d 100644
--- a/recipes/devicelab/devicelab_drone.expected/local-engine.json
+++ b/recipes/devicelab/devicelab_drone.expected/local-engine.json
@@ -110,6 +110,17 @@
     ]
   },
   {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%ct",
+      "-n",
+      "1"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+    "name": "git commit time"
+  },
+  {
     "cmd": [],
     "name": "Initialize logs"
   },
@@ -751,6 +762,8 @@
     "cmd": [
       "luci-auth",
       "token",
+      "-scopes",
+      "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore",
       "-lifetime",
       "3m"
     ],
@@ -814,7 +827,79 @@
       ]
     },
     "infra_step": true,
-    "name": "Upload metrics.write token",
+    "name": "Upload metrics.write metric center token",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-lifetime",
+      "3m"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LOCAL_ENGINE": "[CLEANUP]/builder/src/out/host-release",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/builder/src/out/host-release/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.get access token for default account (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]/tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LOCAL_ENGINE": "[CLEANUP]/builder/src/out/host-release",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/builder/src/out/host-release/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.write cocoon token",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
@@ -833,26 +918,32 @@
       "--test-status",
       "Succeeded",
       "--service-account-token-file",
-      "[CLEANUP]/tmp_tmp_3"
+      "[CLEANUP]/tmp_tmp_4"
     ],
     "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
     "env": {
       "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
       "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
       "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "GCP_PROJECT": "flutter-infra",
       "LOCAL_ENGINE": "[CLEANUP]/builder/src/out/host-release",
       "LUCI_BRANCH": "",
       "LUCI_CI": "True",
       "LUCI_PR": "",
       "OS": "linux",
       "PUB_CACHE": "[START_DIR]/.pub-cache",
-      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+      "TOKEN_PATH": "[CLEANUP]/tmp_tmp_3"
     },
     "env_prefixes": {
       "PATH": [
         "[CLEANUP]/builder/src/out/host-release/dart-sdk/bin",
         "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
         "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython",
+        "[CLEANUP]/builder/src/out/host-release/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
diff --git a/recipes/devicelab/devicelab_drone.expected/no-task-name.json b/recipes/devicelab/devicelab_drone.expected/no-task-name.json
index 286b5f6..abdcd25 100644
--- a/recipes/devicelab/devicelab_drone.expected/no-task-name.json
+++ b/recipes/devicelab/devicelab_drone.expected/no-task-name.json
@@ -7,7 +7,7 @@
       "The recipe has crashed at point 'Uncaught exception'!",
       "",
       "Traceback (most recent call last):",
-      "  File \"RECIPE_REPO[flutter]/recipes/devicelab/devicelab_drone.py\", line 38, in RunSteps",
+      "  File \"RECIPE_REPO[flutter]/recipes/devicelab/devicelab_drone.py\", line 40, in RunSteps",
       "    raise ValueError('A task_name property is required')",
       "ValueError: A task_name property is required"
     ]
diff --git a/recipes/devicelab/devicelab_drone.expected/post-submit.json b/recipes/devicelab/devicelab_drone.expected/post-submit.json
index 4f91cd8..8f47b19 100644
--- a/recipes/devicelab/devicelab_drone.expected/post-submit.json
+++ b/recipes/devicelab/devicelab_drone.expected/post-submit.json
@@ -118,6 +118,17 @@
     ]
   },
   {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%ct",
+      "-n",
+      "1"
+    ],
+    "cwd": "[CLEANUP]\\tmp_tmp_1\\flutter sdk",
+    "name": "git commit time"
+  },
+  {
     "cmd": [],
     "name": "Initialize logs"
   },
@@ -639,6 +650,8 @@
     "cmd": [
       "luci-auth",
       "token",
+      "-scopes",
+      "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore",
       "-lifetime",
       "3m"
     ],
@@ -698,22 +711,17 @@
       ]
     },
     "infra_step": true,
-    "name": "Upload metrics.write token",
+    "name": "Upload metrics.write metric center token",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
   },
   {
     "cmd": [
-      "dart",
-      "bin/test_runner.dart",
-      "upload-metrics",
-      "--test-flaky",
-      "True",
-      "--results-file",
-      "[CLEANUP]\\results_tmp_1\\results",
-      "--service-account-token-file",
-      "[CLEANUP]\\tmp_tmp_3"
+      "luci-auth",
+      "token",
+      "-lifetime",
+      "3m"
     ],
     "cwd": "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\dev\\devicelab",
     "env": {
@@ -735,6 +743,88 @@
       ]
     },
     "infra_step": true,
+    "name": "Upload metrics.get access token for default account (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]\\tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\dev\\devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]\\flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]\\flutter_logs_dir",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "win",
+      "PUB_CACHE": "[START_DIR]\\.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]\\tmp_tmp_1\\flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\bin",
+        "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\bin\\cache\\dart-sdk\\bin",
+        "[CLEANUP]\\tmp_tmp_2\\vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.write cocoon token",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "dart",
+      "bin/test_runner.dart",
+      "upload-metrics",
+      "--test-flaky",
+      "True",
+      "--results-file",
+      "[CLEANUP]\\results_tmp_1\\results",
+      "--commit-time",
+      "",
+      "--task-name",
+      "abc",
+      "--service-account-token-file",
+      "[CLEANUP]\\tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\dev\\devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]\\flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]\\flutter_logs_dir",
+      "GCP_PROJECT": "flutter-infra",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "win",
+      "PUB_CACHE": "[START_DIR]\\.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]\\tmp_tmp_1\\flutter sdk",
+      "TOKEN_PATH": "[CLEANUP]\\tmp_tmp_3"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\bin",
+        "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\bin\\cache\\dart-sdk\\bin",
+        "[CLEANUP]\\tmp_tmp_2\\vpython",
+        "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\bin",
+        "[CLEANUP]\\tmp_tmp_1\\flutter sdk\\bin\\cache\\dart-sdk\\bin",
+        "[CLEANUP]\\tmp_tmp_2\\vpython"
+      ]
+    },
+    "infra_step": true,
     "name": "Upload metrics.upload results",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
diff --git a/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json b/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json
index c2411ca..53bbd51 100644
--- a/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json
+++ b/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json
@@ -110,6 +110,17 @@
     ]
   },
   {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%ct",
+      "-n",
+      "1"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+    "name": "git commit time"
+  },
+  {
     "cmd": [],
     "name": "Initialize logs"
   },
@@ -649,6 +660,8 @@
     "cmd": [
       "luci-auth",
       "token",
+      "-scopes",
+      "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore",
       "-lifetime",
       "3m"
     ],
@@ -708,22 +721,17 @@
       ]
     },
     "infra_step": true,
-    "name": "Upload metrics.write token",
+    "name": "Upload metrics.write metric center token",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
   },
   {
     "cmd": [
-      "dart",
-      "bin/test_runner.dart",
-      "upload-metrics",
-      "--test-flaky",
-      "False",
-      "--results-file",
-      "[CLEANUP]/results_tmp_1/results",
-      "--service-account-token-file",
-      "[CLEANUP]/tmp_tmp_3"
+      "luci-auth",
+      "token",
+      "-lifetime",
+      "3m"
     ],
     "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
     "env": {
@@ -745,6 +753,88 @@
       ]
     },
     "infra_step": true,
+    "name": "Upload metrics.get access token for default account (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]/tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.write cocoon token",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "dart",
+      "bin/test_runner.dart",
+      "upload-metrics",
+      "--test-flaky",
+      "False",
+      "--results-file",
+      "[CLEANUP]/results_tmp_1/results",
+      "--commit-time",
+      "",
+      "--task-name",
+      "abc",
+      "--service-account-token-file",
+      "[CLEANUP]/tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "GCP_PROJECT": "flutter-infra",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+      "TOKEN_PATH": "[CLEANUP]/tmp_tmp_3"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
     "name": "Upload metrics.upload results",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
diff --git a/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json b/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json
index b5f1d8d..6837a94 100644
--- a/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json
+++ b/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json
@@ -110,6 +110,17 @@
     ]
   },
   {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%ct",
+      "-n",
+      "1"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+    "name": "git commit time"
+  },
+  {
     "cmd": [],
     "name": "Initialize logs"
   },
@@ -649,6 +660,8 @@
     "cmd": [
       "luci-auth",
       "token",
+      "-scopes",
+      "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore",
       "-lifetime",
       "3m"
     ],
@@ -708,7 +721,75 @@
       ]
     },
     "infra_step": true,
-    "name": "Upload metrics.write token",
+    "name": "Upload metrics.write metric center token",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-lifetime",
+      "3m"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.get access token for default account (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]/tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.write cocoon token",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
@@ -727,24 +808,29 @@
       "--test-status",
       "Succeeded",
       "--service-account-token-file",
-      "[CLEANUP]/tmp_tmp_3"
+      "[CLEANUP]/tmp_tmp_4"
     ],
     "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
     "env": {
       "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
       "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
       "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "GCP_PROJECT": "flutter-infra",
       "LUCI_BRANCH": "",
       "LUCI_CI": "True",
       "LUCI_PR": "",
       "OS": "linux",
       "PUB_CACHE": "[START_DIR]/.pub-cache",
-      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+      "TOKEN_PATH": "[CLEANUP]/tmp_tmp_3"
     },
     "env_prefixes": {
       "PATH": [
         "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
         "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
diff --git a/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json b/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json
index 4da7978..5ac1e9e 100644
--- a/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json
+++ b/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json
@@ -110,6 +110,17 @@
     ]
   },
   {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%ct",
+      "-n",
+      "1"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+    "name": "git commit time"
+  },
+  {
     "cmd": [],
     "name": "Initialize logs"
   },
@@ -681,6 +692,8 @@
     "cmd": [
       "luci-auth",
       "token",
+      "-scopes",
+      "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore",
       "-lifetime",
       "3m"
     ],
@@ -740,7 +753,75 @@
       ]
     },
     "infra_step": true,
-    "name": "Upload metrics.write token",
+    "name": "Upload metrics.write metric center token",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-lifetime",
+      "3m"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.get access token for default account (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "extra.secret.token.should.not.be.logged",
+      "[CLEANUP]/tmp_tmp_4"
+    ],
+    "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
+    "env": {
+      "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
+      "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "LUCI_BRANCH": "",
+      "LUCI_CI": "True",
+      "LUCI_PR": "",
+      "OS": "linux",
+      "PUB_CACHE": "[START_DIR]/.pub-cache",
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython"
+      ]
+    },
+    "infra_step": true,
+    "name": "Upload metrics.write cocoon token",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
@@ -759,24 +840,29 @@
       "--test-status",
       "Succeeded",
       "--service-account-token-file",
-      "[CLEANUP]/tmp_tmp_3"
+      "[CLEANUP]/tmp_tmp_4"
     ],
     "cwd": "[CLEANUP]/tmp_tmp_1/flutter sdk/dev/devicelab",
     "env": {
       "DEPOT_TOOLS": "RECIPE_REPO[depot_tools]",
       "FLUTTER_LOGS_DIR": "[CLEANUP]/flutter_logs_dir",
       "FLUTTER_TEST_OUTPUTS_DIR": "[CLEANUP]/flutter_logs_dir",
+      "GCP_PROJECT": "flutter-infra",
       "LUCI_BRANCH": "",
       "LUCI_CI": "True",
       "LUCI_PR": "",
       "OS": "linux",
       "PUB_CACHE": "[START_DIR]/.pub-cache",
-      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk"
+      "SDK_CHECKOUT_PATH": "[CLEANUP]/tmp_tmp_1/flutter sdk",
+      "TOKEN_PATH": "[CLEANUP]/tmp_tmp_3"
     },
     "env_prefixes": {
       "PATH": [
         "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
         "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
+        "[CLEANUP]/tmp_tmp_2/vpython",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin",
+        "[CLEANUP]/tmp_tmp_1/flutter sdk/bin/cache/dart-sdk/bin",
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
diff --git a/recipes/devicelab/devicelab_drone.py b/recipes/devicelab/devicelab_drone.py
index fb6147c..cbdaf7f 100644
--- a/recipes/devicelab/devicelab_drone.py
+++ b/recipes/devicelab/devicelab_drone.py
@@ -5,6 +5,7 @@
 from recipe_engine.recipe_api import Property
 
 DEPS = [
+    'fuchsia/git',
     'flutter/bucket_util',
     'flutter/devicelab_osx_sdk',
     'flutter/flutter_deps',
@@ -14,6 +15,7 @@
     'flutter/osx_sdk',
     'flutter/retry',
     'flutter/test_utils',
+    'flutter/token_util',
     'recipe_engine/buildbucket',
     'recipe_engine/cas',
     'recipe_engine/context',
@@ -46,6 +48,15 @@
       api.properties.get('git_url'),
       api.properties.get('git_ref'),
   )
+  with api.context(cwd=flutter_path):
+    commit_time = api.git(
+        'git commit time',
+        'log',
+        '--pretty=format:%ct',
+        '-n',
+        '1',
+        stdout=api.raw_io.output()
+    ).stdout.rstrip()
   env, env_prefixes = api.repo_util.flutter_environment(flutter_path)
   api.logs_util.initialize_logs_collection(env)
   with api.step.nest('Dependencies'):
@@ -117,8 +128,8 @@
           check_flaky(api)
   with api.context(env=env, env_prefixes=env_prefixes, cwd=devicelab_path):
     uploadResults(
-        api, results_path, test_status == 'flaky', git_branch,
-        api.properties.get('buildername')
+        api, env, env_prefixes, results_path, test_status == 'flaky',
+        git_branch, api.properties.get('buildername'), commit_time, task_name
     )
     uploadMetricsToCas(api, results_path)
 
@@ -178,7 +189,8 @@
   """
   supported_branches = ['master']
   if api.runtime.is_experimental or api.properties.get(
-      'git_url') or 'staging' in builder_name or git_branch not in supported_branches:
+      'git_url'
+  ) or 'staging' in builder_name or git_branch not in supported_branches:
     return True
   else:
     return False
@@ -186,11 +198,15 @@
 
 def uploadResults(
     api,
+    env,
+    env_prefixes,
     results_path,
     is_test_flaky,
     git_branch,
     builder_name,
-    test_status='Succeeded'
+    commit_time,
+    task_name,
+    test_status='Succeeded',
 ):
   """Upload DeviceLab test results to Cocoon.
 
@@ -210,18 +226,22 @@
         '--test-status', test_status
     ])
   else:
-    runner_params.extend(['--results-file', results_path])
+    runner_params.extend([
+        '--results-file', results_path, '--commit-time', commit_time,
+        '--task-name', task_name
+    ])
+
   with api.step.nest('Upload metrics'):
-    service_account = api.service_account.default()
-    access_token = service_account.get_access_token()
-    access_token_path = api.path.mkstemp()
-    api.file.write_text(
-        "write token", access_token_path, access_token, include_log=False
-    )
-    runner_params.extend(['--service-account-token-file', access_token_path])
+    env['TOKEN_PATH'] = api.token_util.metric_center_token()
+    env['GCP_PROJECT'] = 'flutter-infra'
+    runner_params.extend([
+        '--service-account-token-file',
+        api.token_util.cocoon_token()
+    ])
     upload_command = ['dart', 'bin/test_runner.dart', 'upload-metrics']
     upload_command.extend(runner_params)
-    api.step('upload results', upload_command, infra_step=True)
+    with api.context(env=env, env_prefixes=env_prefixes):
+      api.step('upload results', upload_command, infra_step=True)
 
 
 def uploadMetricsToCas(api, results_path):
@@ -267,9 +287,7 @@
           task_name='abc',
           dependencies=[{'dependency': 'xcode'}]
       ), api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
-      api.buildbucket.ci_build(
-          git_ref='refs/heads/master',
-      ),
+      api.buildbucket.ci_build(git_ref='refs/heads/master',),
       api.step_data(
           'run abc',
           stdout=api.raw_io.output_text('#flaky\nthis is a flaky\nflaky: true'),
@@ -283,9 +301,7 @@
           task_name='abc',
           dependencies=[{'dependency': 'xcode'}]
       ),
-      api.buildbucket.ci_build(
-          git_ref='refs/heads/master',
-      ),
+      api.buildbucket.ci_build(git_ref='refs/heads/master',),
       api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
   )
   yield api.test(
@@ -299,9 +315,7 @@
           stdout=api.raw_io.output_text('#flaky\nthis is a flaky\nflaky: true'),
           retcode=0
       ),
-      api.buildbucket.ci_build(
-          git_ref='refs/heads/master',
-      ),
+      api.buildbucket.ci_build(git_ref='refs/heads/master',),
       api.platform.name('win'),
   )
   yield api.test(
@@ -313,9 +327,7 @@
           upload_metrics=True,
           upload_metrics_to_cas=True,
       ), api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
-      api.buildbucket.ci_build(
-          git_ref='refs/heads/master',
-      )
+      api.buildbucket.ci_build(git_ref='refs/heads/master',)
   )
   yield api.test(
       "local-engine",
diff --git a/recipes/engine/engine_metrics.expected/basic.json b/recipes/engine/engine_metrics.expected/basic.json
index 26c461d..1706ded 100644
--- a/recipes/engine/engine_metrics.expected/basic.json
+++ b/recipes/engine/engine_metrics.expected/basic.json
@@ -704,7 +704,7 @@
       "[CLEANUP]/tmp_tmp_1"
     ],
     "infra_step": true,
-    "name": "write token"
+    "name": "write metric center token"
   },
   {
     "cmd": [
diff --git a/recipes/engine/engine_metrics.py b/recipes/engine/engine_metrics.py
index 494155f..9d7e263 100644
--- a/recipes/engine/engine_metrics.py
+++ b/recipes/engine/engine_metrics.py
@@ -11,6 +11,7 @@
     'flutter/build_util',
     'flutter/os_utils',
     'flutter/repo_util',
+    'flutter/token_util',
     'fuchsia/goma',
     'recipe_engine/context',
     'recipe_engine/file',
@@ -61,18 +62,7 @@
       'flutter', 'testing', 'benchmark', 'upload_metrics.sh'
   )
 
-  service_account = api.service_account.default()
-  access_token = service_account.get_access_token(
-      scopes=[
-          'https://www.googleapis.com/auth/cloud-platform',
-          'https://www.googleapis.com/auth/datastore'
-      ]
-  )
-  access_token_path = api.path.mkstemp()
-  api.file.write_text(
-      'write token', access_token_path, access_token, include_log=False
-  )
-  env['TOKEN_PATH'] = access_token_path
+  env['TOKEN_PATH'] = api.token_util.metric_center_token()
   env['GCP_PROJECT'] = 'flutter-cirrus'
   with api.context(env=env, env_prefixes=env_prefixes, cwd=benchmark_path):
     api.step('Upload metrics', ['bash', script_path])