Merge "Update catapult to latest version (91735e2e6)" am: 481ecea837

Original change: https://android-review.googlesource.com/c/platform/external/chromium-trace/+/1567220

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I9004291607bc9b0e7f80e252ce2e9ef9271eaad1
diff --git a/UPSTREAM_REVISION b/UPSTREAM_REVISION
index 2997d94..ab641bd 100644
--- a/UPSTREAM_REVISION
+++ b/UPSTREAM_REVISION
@@ -1 +1 @@
-c4d3ff474abec0ece4c42552b25aea9b54ecc047
+91735e2e6775c098eb32840a8903e5a9111fad77
diff --git a/catapult/common/node_runner/node_runner/node_binaries.json b/catapult/common/node_runner/node_runner/node_binaries.json
index d5e1cc5..89a73ee 100644
--- a/catapult/common/node_runner/node_runner/node_binaries.json
+++ b/catapult/common/node_runner/node_runner/node_binaries.json
@@ -9,19 +9,19 @@
           "cloud_storage_hash": "27ad092b0ce59d2da32090a00f717f0c31e65240",
           "download_path": "bin/node/node-linux64.zip",
           "path_within_archive": "node-v10.14.1-linux-x64/bin/node",
-          "version_in_cs": "6.7.0"
+          "version_in_cs": "10.14.1"
         },
         "mac_x86_64": {
-          "cloud_storage_hash": "1af7c221e530165af8a6ab8ff7ccb1f2dd54036d",
+          "cloud_storage_hash": "1bd87abe88492d52b366e97ae9404f082b34fd15",
           "download_path": "bin/node/node-mac64.zip",
-          "path_within_archive": "node-v6.7.0-darwin-x64/bin/node",
-          "version_in_cs": "6.7.0"
+          "path_within_archive": "node-v10.14.1-darwin-x64/bin/node",
+          "version_in_cs": "10.14.1"
         },
         "win_AMD64": {
-          "cloud_storage_hash": "23f21bfb2edf874a8b6bdb6c1acb408bc7edeced",
+          "cloud_storage_hash": "f1162c6343dc6a3a1acecf76966d4c2acd17fc46",
           "download_path": "bin/node/node-win64.zip",
-          "path_within_archive": "node-v6.7.0-win-x64/node.exe",
-          "version_in_cs": "6.7.0"
+          "path_within_archive": "node-v10.14.1-win-x64/node.exe",
+          "version_in_cs": "10.14.1"
         }
       }
     },
@@ -30,22 +30,22 @@
       "cloud_storage_bucket": "chromium-telemetry",
       "file_info": {
         "linux_x86_64": {
-          "cloud_storage_hash": "5750e968975e7f5ab8cb694f5e92a34a890e129d",
+          "cloud_storage_hash": "830b8f6761bf82a945b57facfb49925b579c471c",
           "download_path": "bin/node/npm-linux64.zip",
-          "path_within_archive": "node-v6.7.0-linux-x64/lib/node_modules/npm/bin/npm-cli.js",
-          "version_in_cs": "6.7.0"
+          "path_within_archive": "node-v10.14.1-linux-x64/lib/node_modules/npm/bin/npm-cli.js",
+          "version_in_cs": "10.14.1"
         },
         "mac_x86_64": {
-          "cloud_storage_hash": "1af7c221e530165af8a6ab8ff7ccb1f2dd54036d",
+          "cloud_storage_hash": "1bd87abe88492d52b366e97ae9404f082b34fd15",
           "download_path": "bin/node/npm-mac64.zip",
-          "path_within_archive": "node-v6.7.0-darwin-x64/lib/node_modules/npm/bin/npm-cli.js",
-          "version_in_cs": "6.7.0"
+          "path_within_archive": "node-v10.14.1-darwin-x64/lib/node_modules/npm/bin/npm-cli.js",
+          "version_in_cs": "10.14.1"
         },
         "win_AMD64": {
-          "cloud_storage_hash": "23f21bfb2edf874a8b6bdb6c1acb408bc7edeced",
+          "cloud_storage_hash": "f1162c6343dc6a3a1acecf76966d4c2acd17fc46",
           "download_path": "bin/node/npm-win64.zip",
-          "path_within_archive": "node-v6.7.0-win-x64\\node_modules\\npm\\bin\\npm-cli.js",
-          "version_in_cs": "6.7.0"
+          "path_within_archive": "node-v10.14.1-win-x64\\node_modules\\npm\\bin\\npm-cli.js",
+          "version_in_cs": "10.14.1"
         }
       }
     }
diff --git a/catapult/common/node_runner/node_runner/package-lock.json b/catapult/common/node_runner/node_runner/package-lock.json
index 683cae9..2e8ef2a 100644
--- a/catapult/common/node_runner/node_runner/package-lock.json
+++ b/catapult/common/node_runner/node_runner/package-lock.json
@@ -4,6 +4,157 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+      "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+      "requires": {
+        "@babel/highlight": "^7.10.4"
+      }
+    },
+    "@babel/generator": {
+      "version": "7.11.4",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.4.tgz",
+      "integrity": "sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g==",
+      "requires": {
+        "@babel/types": "^7.11.0",
+        "jsesc": "^2.5.1",
+        "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "2.5.2",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+          "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "@babel/helper-function-name": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
+      "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.10.4",
+        "@babel/template": "^7.10.4",
+        "@babel/types": "^7.10.4"
+      }
+    },
+    "@babel/helper-get-function-arity": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
+      "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
+      "requires": {
+        "@babel/types": "^7.10.4"
+      }
+    },
+    "@babel/helper-split-export-declaration": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
+      "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
+      "requires": {
+        "@babel/types": "^7.11.0"
+      }
+    },
+    "@babel/helper-validator-identifier": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+      "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
+    },
+    "@babel/highlight": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+      "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.10.4",
+        "chalk": "^2.0.0",
+        "js-tokens": "^4.0.0"
+      },
+      "dependencies": {
+        "js-tokens": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+          "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+        }
+      }
+    },
+    "@babel/parser": {
+      "version": "7.11.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.4.tgz",
+      "integrity": "sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA=="
+    },
+    "@babel/template": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
+      "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
+      "requires": {
+        "@babel/code-frame": "^7.10.4",
+        "@babel/parser": "^7.10.4",
+        "@babel/types": "^7.10.4"
+      }
+    },
+    "@babel/traverse": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz",
+      "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==",
+      "requires": {
+        "@babel/code-frame": "^7.10.4",
+        "@babel/generator": "^7.11.0",
+        "@babel/helper-function-name": "^7.10.4",
+        "@babel/helper-split-export-declaration": "^7.11.0",
+        "@babel/parser": "^7.11.0",
+        "@babel/types": "^7.11.0",
+        "debug": "^4.1.0",
+        "globals": "^11.1.0",
+        "lodash": "^4.17.19"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "lodash": {
+          "version": "4.17.20",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+          "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "@babel/types": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
+      "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.10.4",
+        "lodash": "^4.17.19",
+        "to-fast-properties": "^2.0.0"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.20",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+          "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
+        },
+        "to-fast-properties": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+          "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
+        }
+      }
+    },
     "@chopsui/batch-iterator": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/@chopsui/batch-iterator/-/batch-iterator-0.1.0.tgz",
@@ -232,11 +383,83 @@
       "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
       "integrity": "sha1-jaXGUwkVZT86Hzj9XxAdjD+AecU="
     },
+    "@types/babel-generator": {
+      "version": "6.25.3",
+      "resolved": "https://registry.npmjs.org/@types/babel-generator/-/babel-generator-6.25.3.tgz",
+      "integrity": "sha512-pGgnuxVddKcYIc+VJkRDop7gxLhqclNKBdlsm/5Vp8d+37pQkkDK7fef8d9YYImRzw9xcojEPc18pUYnbxmjqA==",
+      "requires": {
+        "@types/babel-types": "*"
+      }
+    },
+    "@types/babel-traverse": {
+      "version": "6.25.5",
+      "resolved": "https://registry.npmjs.org/@types/babel-traverse/-/babel-traverse-6.25.5.tgz",
+      "integrity": "sha512-WrMbwmu+MWf8FiUMbmVOGkc7bHPzndUafn1CivMaBHthBBoo0VNIcYk1KV71UovYguhsNOwf3UF5oRmkkGOU3w==",
+      "requires": {
+        "@types/babel-types": "*"
+      }
+    },
+    "@types/babel-types": {
+      "version": "7.0.8",
+      "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.8.tgz",
+      "integrity": "sha512-jvu8g4LR7+p6ao30RhTREnEhHxmP4/R9D9/rOR/Kq14FztORty9SKgtOZUNZNMB9CXLxZ54EWu4dArUE8WdTsw=="
+    },
+    "@types/babylon": {
+      "version": "6.16.5",
+      "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz",
+      "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==",
+      "requires": {
+        "@types/babel-types": "*"
+      }
+    },
+    "@types/chai": {
+      "version": "4.2.12",
+      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz",
+      "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ=="
+    },
+    "@types/chai-subset": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz",
+      "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==",
+      "requires": {
+        "@types/chai": "*"
+      }
+    },
+    "@types/chalk": {
+      "version": "0.4.31",
+      "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz",
+      "integrity": "sha1-ox10JBprHtu5c8822XooloNKUfk="
+    },
     "@types/clone": {
       "version": "0.1.30",
       "resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz",
       "integrity": "sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ="
     },
+    "@types/cssbeautify": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@types/cssbeautify/-/cssbeautify-0.3.1.tgz",
+      "integrity": "sha1-jgvuj33suVIlDaDK6+BeMFkcF+8="
+    },
+    "@types/doctrine": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.1.tgz",
+      "integrity": "sha1-uZny2fe0PKvgoaLzm8IDvH3K2p0="
+    },
+    "@types/estree": {
+      "version": "0.0.45",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
+      "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g=="
+    },
+    "@types/is-windows": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/@types/is-windows/-/is-windows-0.2.0.tgz",
+      "integrity": "sha1-byTuSHMdMRaOpRBhDW3RXl/Jxv8="
+    },
+    "@types/minimatch": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+      "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
+    },
     "@types/node": {
       "version": "4.2.23",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-4.2.23.tgz",
@@ -257,6 +480,27 @@
         }
       }
     },
+    "@types/path-is-inside": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@types/path-is-inside/-/path-is-inside-1.0.0.tgz",
+      "integrity": "sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw=="
+    },
+    "@types/resolve": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.6.tgz",
+      "integrity": "sha512-g+Rg8uMWY76oYTyaL+m7ZcblqF/oj7pE6uEUyACluJx4zcop1Lk14qQiocdEkEVMDFm6DmKpxJhsER+ZuTwG3g==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/whatwg-url": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-6.4.0.tgz",
+      "integrity": "sha512-tonhlcbQ2eho09am6RHnHOgvtDfDYINd5rgxD+2YSkKENooVCFsWizJz139MQW/PV8FfClyKrNe9ZbdHrSCxGg==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@webassemblyjs/ast": {
       "version": "1.7.10",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.10.tgz",
@@ -632,6 +876,11 @@
       "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
       "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
     },
+    "array-back": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
+      "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q=="
+    },
     "array-find-index": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -1317,6 +1566,14 @@
         }
       }
     },
+    "cancel-token": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/cancel-token/-/cancel-token-0.1.1.tgz",
+      "integrity": "sha1-wYGXZ0uxyEwdaTPr8V2NWlznm08=",
+      "requires": {
+        "@types/node": "^4.0.30"
+      }
+    },
     "capture-stack-trace": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
@@ -1506,6 +1763,43 @@
       "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
       "integrity": "sha1-OeAF1Uav4B4B+cTKj6UPaGoBIF0="
     },
+    "command-line-args": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz",
+      "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==",
+      "requires": {
+        "array-back": "^3.0.1",
+        "find-replace": "^3.0.0",
+        "lodash.camelcase": "^4.3.0",
+        "typical": "^4.0.0"
+      }
+    },
+    "command-line-usage": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz",
+      "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==",
+      "requires": {
+        "array-back": "^2.0.0",
+        "chalk": "^2.4.1",
+        "table-layout": "^0.4.3",
+        "typical": "^2.6.1"
+      },
+      "dependencies": {
+        "array-back": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
+          "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
+          "requires": {
+            "typical": "^2.6.1"
+          }
+        },
+        "typical": {
+          "version": "2.6.1",
+          "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+          "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
+        }
+      }
+    },
     "commander": {
       "version": "2.13.0",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
@@ -1725,6 +2019,11 @@
       "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
       "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
     },
+    "cssbeautify": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cssbeautify/-/cssbeautify-0.3.1.tgz",
+      "integrity": "sha1-Et0fc0A1wub6ymfcvc73TkKBE5c="
+    },
     "currently-unhandled": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -2829,6 +3128,14 @@
         "pkg-dir": "^2.0.0"
       }
     },
+    "find-replace": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
+      "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
+      "requires": {
+        "array-back": "^3.0.1"
+      }
+    },
     "find-up": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
@@ -2958,8 +3265,7 @@
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -2977,13 +3283,11 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -2996,18 +3300,15 @@
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -3110,8 +3411,7 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "ini": {
           "version": "1.3.5",
@@ -3121,7 +3421,6 @@
         "is-fullwidth-code-point": {
           "version": "1.0.0",
           "bundled": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -3134,20 +3433,17 @@
         "minimatch": {
           "version": "3.0.4",
           "bundled": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -3164,7 +3460,6 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -3237,8 +3532,7 @@
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -3248,7 +3542,6 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -3324,8 +3617,7 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -3355,7 +3647,6 @@
         "string-width": {
           "version": "1.0.2",
           "bundled": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -3373,7 +3664,6 @@
         "strip-ansi": {
           "version": "3.0.1",
           "bundled": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -3412,13 +3702,11 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         }
       }
     },
@@ -3808,6 +4096,11 @@
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
       "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
     },
+    "indent": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/indent/-/indent-0.0.2.tgz",
+      "integrity": "sha1-jHnwgBkFWbaHA0uEx676l9WpEdk="
+    },
     "indent-string": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
@@ -4342,6 +4635,11 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "jsonschema": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz",
+      "integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA=="
+    },
     "just-extend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
@@ -4544,11 +4842,26 @@
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
       "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40="
     },
+    "lodash.camelcase": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+      "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
+    },
     "lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
     },
+    "lodash.padend": {
+      "version": "4.6.1",
+      "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
+      "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4="
+    },
+    "lodash.sortby": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+      "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
+    },
     "log-symbols": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
@@ -4629,6 +4942,14 @@
         "yallist": "^2.1.2"
       }
     },
+    "magic-string": {
+      "version": "0.22.5",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
+      "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
+      "requires": {
+        "vlq": "^0.2.2"
+      }
+    },
     "make-dir": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
@@ -5458,6 +5779,169 @@
       "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
       "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c="
     },
+    "polymer-analyzer": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.2.4.tgz",
+      "integrity": "sha512-JmxUhMajTuC18tLXbTtu2+aN2x9bTX+4MvCD4IZKJ0rtAL8jWi1iRLfogpHJB4Ig9Dc8EEEuEYipLuzPFl3vqA==",
+      "requires": {
+        "@babel/generator": "^7.0.0-beta.42",
+        "@babel/traverse": "^7.0.0-beta.42",
+        "@babel/types": "^7.0.0-beta.42",
+        "@types/babel-generator": "^6.25.1",
+        "@types/babel-traverse": "^6.25.2",
+        "@types/babel-types": "^6.25.1",
+        "@types/babylon": "^6.16.2",
+        "@types/chai-subset": "^1.3.0",
+        "@types/chalk": "^0.4.30",
+        "@types/clone": "^0.1.30",
+        "@types/cssbeautify": "^0.3.1",
+        "@types/doctrine": "^0.0.1",
+        "@types/is-windows": "^0.2.0",
+        "@types/minimatch": "^3.0.1",
+        "@types/parse5": "^2.2.34",
+        "@types/path-is-inside": "^1.0.0",
+        "@types/resolve": "0.0.6",
+        "@types/whatwg-url": "^6.4.0",
+        "babylon": "^7.0.0-beta.42",
+        "cancel-token": "^0.1.1",
+        "chalk": "^1.1.3",
+        "clone": "^2.0.0",
+        "cssbeautify": "^0.3.1",
+        "doctrine": "^2.0.2",
+        "dom5": "^3.0.0",
+        "indent": "0.0.2",
+        "is-windows": "^1.0.2",
+        "jsonschema": "^1.1.0",
+        "minimatch": "^3.0.4",
+        "parse5": "^4.0.0",
+        "path-is-inside": "^1.0.2",
+        "resolve": "^1.5.0",
+        "shady-css-parser": "^0.1.0",
+        "stable": "^0.1.6",
+        "strip-indent": "^2.0.0",
+        "vscode-uri": "=1.0.6",
+        "whatwg-url": "^6.4.0"
+      },
+      "dependencies": {
+        "@types/babel-types": {
+          "version": "6.25.2",
+          "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-6.25.2.tgz",
+          "integrity": "sha512-+3bMuktcY4a70a0KZc8aPJlEOArPuAKQYHU5ErjkOqGJdx8xuEEVK6nWogqigBOJ8nKPxRpyCUDTCPmZ3bUxGA=="
+        },
+        "@types/parse5": {
+          "version": "2.2.34",
+          "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-2.2.34.tgz",
+          "integrity": "sha1-44cKEOgnNacg9i1x3NGDunjvOp0=",
+          "requires": {
+            "@types/node": "*"
+          }
+        },
+        "babylon": {
+          "version": "7.0.0-beta.47",
+          "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz",
+          "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ=="
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "clone": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+          "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
+        },
+        "dom5": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/dom5/-/dom5-3.0.1.tgz",
+          "integrity": "sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==",
+          "requires": {
+            "@types/parse5": "^2.2.34",
+            "clone": "^2.1.0",
+            "parse5": "^4.0.0"
+          }
+        },
+        "parse5": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+          "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        }
+      }
+    },
+    "polymer-bundler": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/polymer-bundler/-/polymer-bundler-4.0.10.tgz",
+      "integrity": "sha512-nwlN3LQlQDqbZ2sUH3394C/dHZUDHq8tpdS5HARvPDb0Q9IXWD+znOR1cr7wSjF0EZN4LiUH5hWyUoV4QSjhpQ==",
+      "requires": {
+        "@types/babel-generator": "^6.25.1",
+        "@types/babel-traverse": "^6.25.3",
+        "babel-generator": "^6.26.1",
+        "babel-traverse": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "clone": "^2.1.0",
+        "command-line-args": "^5.0.2",
+        "command-line-usage": "^5.0.5",
+        "dom5": "^3.0.0",
+        "espree": "^3.5.2",
+        "magic-string": "^0.22.4",
+        "mkdirp": "^0.5.1",
+        "parse5": "^4.0.0",
+        "polymer-analyzer": "^3.2.2",
+        "rollup": "^1.3.0",
+        "source-map": "^0.5.6",
+        "vscode-uri": "=1.0.6"
+      },
+      "dependencies": {
+        "@types/parse5": {
+          "version": "2.2.34",
+          "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-2.2.34.tgz",
+          "integrity": "sha1-44cKEOgnNacg9i1x3NGDunjvOp0=",
+          "requires": {
+            "@types/node": "*"
+          }
+        },
+        "clone": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+          "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
+        },
+        "dom5": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/dom5/-/dom5-3.0.1.tgz",
+          "integrity": "sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==",
+          "requires": {
+            "@types/parse5": "^2.2.34",
+            "clone": "^2.1.0",
+            "parse5": "^4.0.0"
+          }
+        },
+        "parse5": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+          "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -5745,6 +6229,11 @@
         "strip-indent": "^2.0.0"
       }
     },
+    "reduce-flatten": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz",
+      "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc="
+    },
     "redux": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
@@ -5896,6 +6385,23 @@
         "inherits": "^2.0.1"
       }
     },
+    "rollup": {
+      "version": "1.32.1",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz",
+      "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==",
+      "requires": {
+        "@types/estree": "*",
+        "@types/node": "*",
+        "acorn": "^7.1.0"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "7.4.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+          "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w=="
+        }
+      }
+    },
     "run-async": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
@@ -6033,6 +6539,11 @@
         "safe-buffer": "^5.0.1"
       }
     },
+    "shady-css-parser": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/shady-css-parser/-/shady-css-parser-0.1.0.tgz",
+      "integrity": "sha512-irfJUUkEuDlNHKZNAp2r7zOyMlmbfVJ+kWSfjlCYYUx/7dJnANLCyTzQZsuxy5NJkvtNwSxY5Gj8MOlqXUQPyA=="
+    },
     "shebang-command": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -6325,6 +6836,11 @@
         "safe-buffer": "^5.1.1"
       }
     },
+    "stable": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+      "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w=="
+    },
     "static-extend": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -6471,6 +6987,33 @@
         "string-width": "^2.1.1"
       }
     },
+    "table-layout": {
+      "version": "0.4.5",
+      "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
+      "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==",
+      "requires": {
+        "array-back": "^2.0.0",
+        "deep-extend": "~0.6.0",
+        "lodash.padend": "^4.6.1",
+        "typical": "^2.6.1",
+        "wordwrapjs": "^3.0.0"
+      },
+      "dependencies": {
+        "array-back": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
+          "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
+          "requires": {
+            "typical": "^2.6.1"
+          }
+        },
+        "typical": {
+          "version": "2.6.1",
+          "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+          "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
+        }
+      }
+    },
     "tapable": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz",
@@ -6587,6 +7130,14 @@
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
       "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM="
     },
+    "tr46": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+      "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
     "trim-newlines": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz",
@@ -6634,6 +7185,11 @@
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
       "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
     },
+    "typical": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
+      "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw=="
+    },
     "uglify-es": {
       "version": "3.3.9",
       "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
@@ -6880,6 +7436,11 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "vlq": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
+      "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow=="
+    },
     "vm-browserify": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
@@ -6893,6 +7454,11 @@
       "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
       "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
     },
+    "vscode-uri": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.6.tgz",
+      "integrity": "sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww=="
+    },
     "vulcanize": {
       "version": "1.16.0",
       "resolved": "https://registry.npmjs.org/vulcanize/-/vulcanize-1.16.0.tgz",
@@ -6923,6 +7489,11 @@
         "defaults": "^1.0.3"
       }
     },
+    "webidl-conversions": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+      "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
+    },
     "webpack": {
       "version": "4.23.1",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.1.tgz",
@@ -7067,6 +7638,16 @@
         "source-map": "~0.6.1"
       }
     },
+    "whatwg-url": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
+      "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
+      "requires": {
+        "lodash.sortby": "^4.7.0",
+        "tr46": "^1.0.1",
+        "webidl-conversions": "^4.0.2"
+      }
+    },
     "which": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -7088,6 +7669,22 @@
       "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
       "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
     },
+    "wordwrapjs": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
+      "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==",
+      "requires": {
+        "reduce-flatten": "^1.0.1",
+        "typical": "^2.6.1"
+      },
+      "dependencies": {
+        "typical": {
+          "version": "2.6.1",
+          "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
+          "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
+        }
+      }
+    },
     "worker-farm": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz",
diff --git a/catapult/common/py_utils/py_utils/chrome_binaries.json b/catapult/common/py_utils/py_utils/chrome_binaries.json
index badb364..03f0afd 100644
--- a/catapult/common/py_utils/py_utils/chrome_binaries.json
+++ b/catapult/common/py_utils/py_utils/chrome_binaries.json
@@ -12,16 +12,16 @@
           "version_in_cs": "85.0.4169.0"
         },
         "win_AMD64": {
-          "cloud_storage_hash": "c0b02f47afb6bacdd8d12eda6f03488583404b60",
+          "cloud_storage_hash": "121271e6e917886bd2e5b539dd643e74a3aae395",
           "download_path": "bin\\reference_build\\chrome-win64-clang.zip",
           "path_within_archive": "chrome-win64-clang\\chrome.exe",
-          "version_in_cs": "85.0.4169.0"
+          "version_in_cs": "86.0.4240.111"
         },
         "win_x86": {
-          "cloud_storage_hash": "5879958eda4edc5b0eaffeda4c7369a07937af73",
+          "cloud_storage_hash": "83e9d11b28d01c10bbcccb7b882288b192b5e756",
           "download_path": "bin\\reference_build\\chrome-win32-clang.zip",
           "path_within_archive": "chrome-win32-clang\\chrome.exe",
-          "version_in_cs": "85.0.4169.0"
+          "version_in_cs": "86.0.4240.111"
         }
       }
     },
@@ -169,4 +169,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/catapult/common/py_utils/py_utils/cloud_storage.py b/catapult/common/py_utils/py_utils/cloud_storage.py
index a359a06..919add6 100644
--- a/catapult/common/py_utils/py_utils/cloud_storage.py
+++ b/catapult/common/py_utils/py_utils/cloud_storage.py
@@ -192,10 +192,33 @@
   return disable_cloud_storage_env_val != '1'
 
 
-def List(bucket):
-  query = 'gs://%s/' % bucket
-  stdout = _RunCommand(['ls', query])
-  return [url[len(query):] for url in stdout.splitlines()]
+def List(bucket, prefix=None):
+  """Returns all paths matching the given prefix in bucket.
+
+  Returned paths are relative to the bucket root.
+  If path is given, 'gsutil ls gs://<bucket>/<path>' will be executed, otherwise
+  'gsutil ls gs://<bucket>' will be executed.
+
+  For more details, see:
+  https://cloud.google.com/storage/docs/gsutil/commands/ls#directory-by-directory,-flat,-and-recursive-listings
+
+  Args:
+    bucket: Name of cloud storage bucket to look at.
+    prefix: Path within the bucket to filter to.
+
+  Returns:
+    A list of files. All returned path are relative to the bucket root
+    directory. For example, List('my-bucket', path='foo/') will returns results
+    of the form ['/foo/123', '/foo/124', ...], as opposed to ['123', '124',
+    ...].
+  """
+  bucket_prefix = 'gs://%s' % bucket
+  if prefix is None:
+    full_path = bucket_prefix
+  else:
+    full_path = '%s/%s' % (bucket_prefix, prefix)
+  stdout = _RunCommand(['ls', full_path])
+  return [url[len(bucket_prefix):] for url in stdout.splitlines()]
 
 
 def ListDirs(bucket, path=''):
diff --git a/catapult/common/py_utils/py_utils/cloud_storage_unittest.py b/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
index 2e663a7..afd0394 100644
--- a/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
+++ b/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
@@ -171,6 +171,24 @@
       cloud_storage._RunCommand = orig_run_command
 
   @mock.patch('py_utils.cloud_storage._RunCommand')
+  def testListNoPrefix(self, mock_run_command):
+    mock_run_command.return_value = '\n'.join(['gs://bucket/foo-file.txt',
+                                               'gs://bucket/foo1/',
+                                               'gs://bucket/foo2/'])
+
+    self.assertEqual(cloud_storage.List('bucket'),
+                     ['/foo-file.txt', '/foo1/', '/foo2/'])
+
+  @mock.patch('py_utils.cloud_storage._RunCommand')
+  def testListWithPrefix(self, mock_run_command):
+    mock_run_command.return_value = '\n'.join(['gs://bucket/foo/foo-file.txt',
+                                               'gs://bucket/foo/foo1/',
+                                               'gs://bucket/foo/foo2/'])
+
+    self.assertEqual(cloud_storage.List('bucket', 'foo'),
+                     ['/foo/foo-file.txt', '/foo/foo1/', '/foo/foo2/'])
+
+  @mock.patch('py_utils.cloud_storage._RunCommand')
   def testListDirs(self, mock_run_command):
     mock_run_command.return_value = '\n'.join(['gs://bucket/foo-file.txt',
                                                '',
diff --git a/catapult/common/py_utils/py_utils/ts_proxy_server.py b/catapult/common/py_utils/py_utils/ts_proxy_server.py
index b71d143..0e168a5 100644
--- a/catapult/common/py_utils/py_utils/ts_proxy_server.py
+++ b/catapult/common/py_utils/py_utils/ts_proxy_server.py
@@ -196,11 +196,13 @@
     try:
       py_utils.WaitFor(lambda: self._proc.poll() is not None, 10)
     except py_utils.TimeoutException:
-      try:
-        # Use a SIGNINT so that it can do graceful cleanup
-        self._proc.send_signal(signal.SIGINT)
-      except ValueError:
-        logging.warning('Unable to stop ts_proxy_server gracefully.\n')
+      # signal.SIGINT is not supported on Windows.
+      if not sys.platform.startswith('win'):
+        try:
+          # Use a SIGNINT so that it can do graceful cleanup
+          self._proc.send_signal(signal.SIGINT)
+        except ValueError:
+          logging.warning('Unable to stop ts_proxy_server gracefully.\n')
       self._proc.terminate()
     _, err = self._proc.communicate()
 
diff --git a/catapult/common/py_utils/py_utils/webpagereplay_go_server.py b/catapult/common/py_utils/py_utils/webpagereplay_go_server.py
index 950e8ad..95e4495 100644
--- a/catapult/common/py_utils/py_utils/webpagereplay_go_server.py
+++ b/catapult/common/py_utils/py_utils/webpagereplay_go_server.py
@@ -17,7 +17,6 @@
 from py_utils import atexit_with_log
 from py_utils import binary_manager
 
-
 _WPR_DIR = os.path.abspath(os.path.join(
     py_utils.GetCatapultDir(), 'web_page_replay_go'))
 
@@ -31,6 +30,8 @@
 
 RECORD = '--record'
 INJECT_SCRIPTS = '--inject_scripts='
+USE_LOCAL_WPR = '--use-local-wpr'
+DISABLE_FUZZY_URL_MATCHING = '--disable-fuzzy-url-matching'
 
 class ReplayError(Exception):
   """Catch-all exception for the module."""
@@ -102,16 +103,10 @@
     # subprocess.
     self._temp_log_file_path = None
 
-    # Assign the downloader func and binary_manager
-    downloader = None
-    if binary_downloader:
-      downloader = binary_downloader
-    else:
-      configs = [CHROME_BINARY_CONFIG, TELEMETRY_PROJECT_CONFIG]
-      downloader = binary_manager.BinaryManager(configs).FetchPath
-
+    self._downloader = binary_downloader
+    self._replay_options = replay_options
     self._cmd_line = self._GetCommandLine(
-        self._GetGoBinaryPath(downloader=downloader), http_port, https_port,
+        self._GetGoBinaryPath(replay_options), http_port, https_port,
         replay_options, archive_path)
 
     if RECORD in replay_options or 'record' in replay_options:
@@ -122,19 +117,57 @@
 
     self.replay_process = None
 
-  @classmethod
-  def _GetGoBinaryPath(cls, downloader):
-    if not cls._go_binary_path:
-      cls._go_binary_path = downloader(
+  def _GetDownloader(self):
+    """Gets the downloader used to download wpr_go binary from GCS."""
+    if ReplayServer._go_binary_path:
+      # If the _go_binary_path was already set, then no need to use downloader
+      # to download via binary_manager.
+      self._downloader = None
+    elif not self._downloader:
+      configs = [CHROME_BINARY_CONFIG, TELEMETRY_PROJECT_CONFIG]
+      self._downloader = binary_manager.BinaryManager(configs).FetchPath
+    return self._downloader
+
+  def _GetGoBinaryPath(self, replay_options):
+    """Gets the _go_binary_path if it already set, or downloads it."""
+    if USE_LOCAL_WPR in replay_options:
+      # Build WPR
+      go_folder = os.path.join(_WPR_DIR, 'src')
+      cur_cwd = os.getcwd()
+      os.chdir(go_folder)
+      try:
+        print subprocess.check_output(['go', 'build', os.path.join(go_folder, 'wpr.go')])
+      except subprocess.CalledProcessError:
+        exit(1)
+      os.chdir(cur_cwd)
+
+      return os.path.join(go_folder, 'wpr')
+
+    if not ReplayServer._go_binary_path:
+      downloader = self._GetDownloader()
+      if not downloader:
+        raise RuntimeError('downloader should not be None '
+                           'while _go_binary_path is None')
+      ReplayServer._go_binary_path = downloader(
           'wpr_go', py_utils.GetHostOsName(), py_utils.GetHostArchName())
-    return cls._go_binary_path
+    return ReplayServer._go_binary_path
 
   @classmethod
   def SetGoBinaryPath(cls, go_binary_path):
     """Overrides the _go_binary_path.
 
     This allows the server to use WPRGO files retrieved from somewhere
-    other than GCS, such as CIPD."""
+    other than GCS via binary_manager, such as test isolation.
+
+    For chromium project to use WPR, it is encourage to use test isolation,
+    and therefore should call SetGoBinaryPath to set _go_binary_path.
+
+    For Catapult/Telemetry project, the tradition is to download wpr_go
+    binary via binary_manager. So do not call SetGoBinaryPath.
+    """
+    if not os.path.exists(go_binary_path):
+      raise ValueError('SetGoBinaryPath could not set {} as it does not exist'
+                       .format(go_binary_path))
     cls._go_binary_path = go_binary_path
 
   @property
@@ -162,7 +195,8 @@
     """
     bad_options = []
     for option in options:
-      if option not in [RECORD, INJECT_SCRIPTS]:
+      if option not in [RECORD, INJECT_SCRIPTS,
+                        USE_LOCAL_WPR, DISABLE_FUZZY_URL_MATCHING]:
         bad_options.append(option)
     if len(bad_options) > 0:
       raise ValueError("Invalid replay options %s" % bad_options)
@@ -172,6 +206,8 @@
       cmd_line.append('record')
     else:
       cmd_line.append('replay')
+    if DISABLE_FUZZY_URL_MATCHING in options:
+      cmd_line.append('--disable_fuzzy_url_matching')
     key_file = os.path.join(_WPR_DIR, 'wpr_key.pem')
     cert_file = os.path.join(_WPR_DIR, 'wpr_cert.pem')
     inject_script = os.path.join(_WPR_DIR, 'deterministic.js')
@@ -347,13 +383,17 @@
   def _CleanUpTempLogFilePath(self, log_level):
     if not self._temp_log_file_path:
       return ''
-    if logging.getLogger('').isEnabledFor(log_level):
+    if logging.getLogger('').isEnabledFor(log_level) or USE_LOCAL_WPR in self._replay_options:
       with open(self._temp_log_file_path, 'r') as f:
         wpr_log_output = f.read()
-      logging.log(log_level, '\n'.join([
-          '************************** WPR LOG *****************************',
-          wpr_log_output,
-          '************************** END OF WPR LOG **********************']))
+      output = ('************************** WPR LOG *****************************\n' +
+                '\n'.join(wpr_log_output.split('\n')) +
+                '************************** END OF WPR LOG **********************')
+      if logging.getLogger('').isEnabledFor(log_level):
+        logging.log(log_level, output)
+      else:
+        print output
+
     os.remove(self._temp_log_file_path)
     self._temp_log_file_path = None
 
diff --git a/catapult/dependency_manager/dependency_manager/__init__.py b/catapult/dependency_manager/dependency_manager/__init__.py
index 84cca5a..3b18f06 100644
--- a/catapult/dependency_manager/dependency_manager/__init__.py
+++ b/catapult/dependency_manager/dependency_manager/__init__.py
@@ -15,7 +15,7 @@
 def _AddDirToPythonPath(*path_parts):
   path = os.path.abspath(os.path.join(*path_parts))
   if os.path.isdir(path) and path not in sys.path:
-    sys.path.append(path)
+    sys.path.insert(0, path)
 
 
 _AddDirToPythonPath(CATAPULT_PATH, 'common', 'py_utils')
diff --git a/catapult/devil/bin/generate_md_docs b/catapult/devil/bin/generate_md_docs
index 1cca44c..d1dbf06 100755
--- a/catapult/devil/bin/generate_md_docs
+++ b/catapult/devil/bin/generate_md_docs
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env vpython
 # 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.
diff --git a/catapult/devil/bin/run_py_devicetests b/catapult/devil/bin/run_py_devicetests
index 6a6da18..9329f3a 100755
--- a/catapult/devil/bin/run_py_devicetests
+++ b/catapult/devil/bin/run_py_devicetests
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env vpython
 # 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.
diff --git a/catapult/devil/bin/run_py_tests b/catapult/devil/bin/run_py_tests
index 112451c..be245c6 100755
--- a/catapult/devil/bin/run_py_tests
+++ b/catapult/devil/bin/run_py_tests
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env vpython
 # 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.
diff --git a/catapult/devil/build/cipd.yaml b/catapult/devil/build/cipd.yaml
index fcb4db3..204400a 100644
--- a/catapult/devil/build/cipd.yaml
+++ b/catapult/devil/build/cipd.yaml
@@ -1,3 +1,9 @@
+# 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.
+
+# To create CIPD package run the following command.
+# cipd create --pkg-def cipd.yaml -ref latest
 package: chromium/third_party/catapult/devil/${platform}
 
 description: All of devil along with its dependencies in catapult.
@@ -11,4 +17,5 @@
   - dir: common/py_utils
   - dir: dependency_manager
   - dir: devil
+  - dir: third_party/gsutil
   - dir: third_party/zipfile
diff --git a/catapult/devil/devil/android/device_blacklist.py b/catapult/devil/devil/android/device_blacklist.py
deleted file mode 100644
index 7112204..0000000
--- a/catapult/devil/devil/android/device_blacklist.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright 2014 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 devil.android import device_denylist
-
-# TODO(crbug.com/1097306): Remove this (and this file) once existing uses
-# have switched to using device_denylist directly.
-Blacklist = device_denylist.Denylist
diff --git a/catapult/devil/devil/android/device_errors.py b/catapult/devil/devil/android/device_errors.py
index d145468..6e71087 100644
--- a/catapult/devil/devil/android/device_errors.py
+++ b/catapult/devil/devil/android/device_errors.py
@@ -14,6 +14,7 @@
      |    +-- FastbootCommandFailedError
      |    +-- DeviceVersionError
      |    +-- DeviceChargingError
+     |    +-- RootUserBuildError
      +-- CommandTimeoutError
      +-- DeviceUnreachableError
      +-- NoDevicesError
@@ -228,3 +229,11 @@
 
   def __init__(self, message, device_serial=None):
     super(DeviceChargingError, self).__init__(message, device_serial)
+
+
+class RootUserBuildError(CommandFailedError):
+  """Exception for being unable to root a device with "user" build."""
+
+  def __init__(self, message=None, device_serial=None):
+    super(RootUserBuildError, self).__init__(
+        message or 'Unable to root device with user build.', device_serial)
diff --git a/catapult/devil/devil/android/device_utils.py b/catapult/devil/devil/android/device_utils.py
index 0a041ed..7b7dad2 100644
--- a/catapult/devil/devil/android/device_utils.py
+++ b/catapult/devil/devil/android/device_utils.py
@@ -45,7 +45,8 @@
 from devil.utils import timeout_retry
 from devil.utils import zip_utils
 
-from py_utils import tempfile_ext
+with devil_env.SysPath(devil_env.PY_UTILS_PATH):
+  from py_utils import tempfile_ext
 
 try:
   from devil.utils import reset_usb
@@ -132,6 +133,7 @@
         'android.permission.MANAGE_ACCOUNTS',
         'android.permission.MODIFY_AUDIO_SETTINGS',
         'android.permission.NFC',
+        'android.permission.QUERY_ALL_PACKAGES',
         'android.permission.READ_SYNC_SETTINGS',
         'android.permission.READ_SYNC_STATS',
         'android.permission.RECEIVE_BOOT_COMPLETED',
@@ -277,6 +279,15 @@
 
 _GOOGLE_FEATURES_RE = re.compile(r'^\s*com\.google\.')
 
+_EMULATOR_RE = re.compile(r'^generic_.*$')
+
+# Regular expressions for determining if a package is installed using the
+# output of `dumpsys package`.
+# Matches lines like "Package [com.google.android.youtube] (c491050):".
+# or "Package [org.chromium.trichromelibrary_425300033] (e476383):"
+_DUMPSYS_PACKAGE_RE_STR =\
+    r'^\s*Package\s*\[%s(_(?P<version_code>\d*))?\]\s*\(\w*\):$'
+
 PS_COLUMNS = ('name', 'pid', 'ppid')
 ProcessInfo = collections.namedtuple('ProcessInfo', PS_COLUMNS)
 
@@ -556,22 +567,14 @@
       CommandTimeoutError on timeout.
       DeviceUnreachableError on missing device.
     """
-    try:
-      if self.build_type == 'eng':
-        # 'eng' builds have root enabled by default and the adb session cannot
-        # be unrooted.
-        return True
-      # Devices using the system-as-root partition layout appear to not have
-      # a /root directory. See http://bit.ly/37F34sx for more context.
-      if (self.build_system_root_image == 'true'
-          or self.build_version_sdk >= version_codes.Q
-          # This may be redundant with the checks above.
-          or self.product_name in _SPECIAL_ROOT_DEVICE_LIST):
-        return self.GetProp('service.adb.root') == '1'
-      self.RunShellCommand(['ls', '/root'], check_return=True)
+    if self.build_type == 'eng':
+      # 'eng' builds have root enabled by default and the adb session cannot
+      # be unrooted.
       return True
-    except device_errors.AdbCommandFailedError:
-      return False
+    # Check if uid is 0. Such behavior has remained unchanged since
+    # android 2.2.3 (https://bit.ly/2QQzg67)
+    output = self.RunShellCommand(['id'], single_line=True)
+    return output.startswith('uid=0(root)')
 
   def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT):
     """Checks whether 'su' is needed to access protected resources.
@@ -638,8 +641,7 @@
       self.adb.Root()
     except device_errors.AdbCommandFailedError as e:
       if self.IsUserBuild():
-        raise device_errors.CommandFailedError(
-            'Unable to root device with user build.', str(self))
+        raise device_errors.RootUserBuildError(device_serial=str(self))
       elif e.output and _WAIT_FOR_DEVICE_TIMEOUT_STR in e.output:
         # adb 1.0.41 added a call to wait-for-device *inside* root
         # with a timeout that can be too short in some cases.
@@ -768,21 +770,50 @@
     raise device_errors.CommandFailedError('Unable to fetch IMEI.')
 
   @decorators.WithTimeoutAndRetriesFromInstance()
-  def IsApplicationInstalled(self, package, timeout=None, retries=None):
+  def IsApplicationInstalled(
+      self, package, version_code=None, timeout=None, retries=None):
     """Determines whether a particular package is installed on the device.
 
     Args:
       package: Name of the package.
+      version_code: The version of the package to check for as an int, if
+          applicable. Only used for static shared libraries, otherwise ignored.
 
     Returns:
       True if the application is installed, False otherwise.
     """
-    # `pm list packages` allows matching substrings, but we want exact matches
-    # only.
-    matching_packages = self.RunShellCommand(
-        ['pm', 'list', 'packages', package], check_return=True)
-    desired_line = 'package:' + package
-    return desired_line in matching_packages
+    # `pm list packages` doesn't include the version code, so if it was
+    # provided, skip this since we can't guarantee that the installed
+    # version is the requested version.
+    if version_code is None:
+      # `pm list packages` allows matching substrings, but we want exact matches
+      # only.
+      matching_packages = self.RunShellCommand(
+          ['pm', 'list', 'packages', package], check_return=True)
+      desired_line = 'package:' + package
+      found_package = desired_line in matching_packages
+      if found_package:
+        return True
+
+    # Some packages do not properly show up via `pm list packages`, so fall back
+    # to checking via `dumpsys package`.
+    matcher = re.compile(_DUMPSYS_PACKAGE_RE_STR % package)
+    dumpsys_output = self.RunShellCommand(
+        ['dumpsys', 'package'], check_return=True, large_output=True)
+    for line in dumpsys_output:
+      match = matcher.match(line)
+      # We should have one of these cases:
+      # 1. The package is a regular app, in which case it will show up without
+      #    its version code in the line we're filtering for.
+      # 2. The package is a static shared library, in which case one or more
+      #    entries with the version code can show up, but not one without the
+      #    version code.
+      if match:
+        installed_version_code = match.groupdict().get('version_code')
+        if (installed_version_code is None
+            or installed_version_code == str(version_code)):
+          return True
+    return False
 
   @decorators.WithTimeoutAndRetriesFromInstance()
   def GetApplicationPaths(self, package, timeout=None, retries=None):
@@ -1062,6 +1093,10 @@
              retries=None):
     """Reboot the device.
 
+    Note if the device has the root privilege, it will likely lose it after the
+    reboot. When |block| is True, it will try to restore the root status if
+    applicable.
+
     Args:
       block: A boolean indicating if we should wait for the reboot to complete.
       wifi: A boolean indicating if we should wait for wifi to be enabled after
@@ -1081,11 +1116,15 @@
     def device_offline():
       return not self.IsOnline()
 
+    # Only check the root when block is True
+    should_restore_root = self.HasRoot() if block else False
     self.adb.Reboot()
     self.ClearCache()
     timeout_retry.WaitFor(device_offline, wait_period=1)
     if block:
       self.WaitUntilFullyBooted(wifi=wifi, decrypt=decrypt)
+      if should_restore_root:
+        self.EnableRoot()
 
   INSTALL_DEFAULT_TIMEOUT = 8 * _DEFAULT_TIMEOUT
   MODULES_SRC_DIRECTORY_PATH = '/data/local/tmp/modules'
@@ -1282,6 +1321,7 @@
         apks_to_install = apk_paths
 
     if device_apk_paths and apks_to_install and not reinstall:
+      logger.info('Uninstalling package %s', package_name)
       self.Uninstall(package_name)
 
     if apks_to_install:
@@ -1292,6 +1332,8 @@
       streaming = None
       if self.product_name in _NO_STREAMING_DEVICE_LIST:
         streaming = False
+      logger.info('Installing package %s using APKs %s',
+                  package_name, apks_to_install)
       if len(apks_to_install) > 1 or partial:
         self.adb.InstallMultiple(
             apks_to_install,
@@ -1306,10 +1348,20 @@
             streaming=streaming,
             allow_downgrade=allow_downgrade)
     else:
+      logger.info('Skipping installation of package %s', package_name)
       # Running adb install terminates running instances of the app, so to be
       # consistent, we explicitly terminate it when skipping the install.
       self.ForceStop(package_name)
 
+    # There have been cases of APKs not being detected after being explicitly
+    # installed, so perform a sanity check now and fail early if the
+    # installation somehow failed.
+    apk_version = apk.GetVersionCode()
+    if not self.IsApplicationInstalled(package_name, apk_version):
+      raise device_errors.CommandFailedError(
+          'Package %s with version %s not installed on device after explicit '
+          'install attempt.' % (package_name, apk_version))
+
     if (permissions is None
         and self.build_version_sdk >= version_codes.MARSHMALLOW):
       permissions = apk.GetPermissions()
@@ -2045,9 +2097,14 @@
   def _ComputeDeviceChecksumsForApks(self, package_name):
     ret = self._cache['package_apk_checksums'].get(package_name)
     if ret is None:
-      device_paths = self._GetApplicationPathsInternal(package_name)
-      file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
-      ret = set(file_to_checksums.values())
+      if self.PathExists('/data/data/' + package_name, as_root=True):
+        device_paths = self._GetApplicationPathsInternal(package_name)
+        file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
+        ret = set(file_to_checksums.values())
+      else:
+        logger.info('Cannot reuse package %s (data directory missing)',
+                    package_name)
+        ret = set()
       self._cache['package_apk_checksums'][package_name] = ret
     return ret
 
@@ -2720,11 +2777,16 @@
   @property
   def pixel_density(self):
     density = self.GetProp('ro.sf.lcd_density', cache=True)
-    if not density and self.adb.is_emulator:
+    if not density:
+      # It might be an emulator, try the qemu prop.
       density = self.GetProp('qemu.sf.lcd_density', cache=True)
     return int(density)
 
   @property
+  def is_emulator(self):
+    return _EMULATOR_RE.match(self.GetProp('ro.product.device', cache=True))
+
+  @property
   def build_description(self):
     """Returns the build description of the system.
 
@@ -3186,11 +3248,12 @@
         WebViewPackages: Dict of installed WebView providers, mapping "package
             name" to "reason it's valid/invalid."
 
-    It may return an empty dictionary if device does not
-    support the "dumpsys webviewupdate" command.
+    The returned dictionary may not include all of the above keys: this depends
+    on the support of the platform's underlying WebViewUpdateService. This may
+    return an empty dictionary on OS versions which do not support querying the
+    WebViewUpdateService.
 
     Raises:
-      CommandFailedError on failure.
       CommandTimeoutError on timeout.
       DeviceUnreachableError on missing device.
     """
@@ -3229,12 +3292,6 @@
         result['MinimumWebViewVersionCode'] = int(match.group(1))
     if webview_packages:
       result['WebViewPackages'] = webview_packages
-
-    missing_fields = set(['CurrentWebViewPackage', 'FallbackLogicEnabled']) - \
-                     set(result.keys())
-    if len(missing_fields) > 0:
-      raise device_errors.CommandFailedError(
-          '%s not found in dumpsys webviewupdate' % str(list(missing_fields)))
     return result
 
   @decorators.WithTimeoutAndRetriesFromInstance()
@@ -3552,9 +3609,6 @@
                      retries=1,
                      enable_usb_resets=False,
                      abis=None,
-                     # TODO(crbug.com/1097306): Remove this once clients have
-                     # stopped passing it.
-                     blacklist=None,
                      **kwargs):
     """Returns a list of DeviceUtils instances.
 
@@ -3607,10 +3661,6 @@
       if device_arg:
         device_arg = (device_arg, )
 
-    # TODO(crbug.com/1097306): Remove this once clients have switched.
-    if blacklist and not denylist:
-      denylist = blacklist
-
     denylisted_devices = denylist.Read() if denylist else []
 
     # adb looks for ANDROID_SERIAL, so support it as well.
diff --git a/catapult/devil/devil/android/device_utils_test.py b/catapult/devil/devil/android/device_utils_test.py
index 38e64a9..62313c5 100755
--- a/catapult/devil/devil/android/device_utils_test.py
+++ b/catapult/devil/devil/android/device_utils_test.py
@@ -31,8 +31,7 @@
 from devil.utils import cmd_helper
 from devil.utils import mock_calls
 
-with devil_env.SysPath(
-    os.path.join(devil_env.CATAPULT_ROOT_PATH, 'common', 'py_utils')):
+with devil_env.SysPath(os.path.join(devil_env.PY_UTILS_PATH)):
   from py_utils import tempfile_ext
 
 with devil_env.SysPath(devil_env.PYMOCK_PATH):
@@ -80,6 +79,7 @@
     self.perms = perms
     self.splits = splits if splits else []
     self.abis = [abis.ARM]
+    self.version_code = None
 
   def GetPackageName(self):
     return self.package_name
@@ -87,6 +87,9 @@
   def GetPermissions(self):
     return self.perms
 
+  def GetVersionCode(self):
+    return self.version_code
+
   def GetAbis(self):
     return self.abis
 
@@ -332,119 +335,21 @@
 
 class DeviceUtilsHasRootTest(DeviceUtilsTest):
   def testHasRoot_true(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='notasailfish')), (self.assertCall(
-                        self.call.adb.Shell('ls /root'), 'foo\n')):
+    with self.patch_call(self.call.device.build_type,
+                         return_value='userdebug'), (self.assertCall(
+                             self.call.adb.Shell('id'), 'uid=0(root)\n')):
       self.assertTrue(self.device.HasRoot())
 
-  def testhasRootSpecial_true(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='sailfish')), (self.assertCall(
-                        self.call.adb.Shell('getprop service.adb.root'),
-                        '1\n')):
-      self.assertTrue(self.device.HasRoot())
-
-  def testhasRootSpecialAosp_true(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='aosp_sailfish')), (self.assertCall(
-                        self.call.adb.Shell('getprop service.adb.root'),
-                        '1\n')):
-      self.assertTrue(self.device.HasRoot())
-
-  def testhasRootEngBuild_true(self):
+  def testHasRootEngBuild_true(self):
     with self.patch_call(self.call.device.build_type, return_value='eng'):
       self.assertTrue(self.device.HasRoot())
 
   def testHasRoot_false(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='notasailfish')), (self.assertCall(
-                        self.call.adb.Shell('ls /root'), self.ShellError())):
+    with self.patch_call(self.call.device.build_type,
+                         return_value='userdebug'), (self.assertCall(
+                             self.call.adb.Shell('id'), 'uid=2000(shell)\n')):
       self.assertFalse(self.device.HasRoot())
 
-  def testHasRootSpecial_false(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='sailfish')), (self.assertCall(
-                        self.call.adb.Shell('getprop service.adb.root'), '\n')):
-      self.assertFalse(self.device.HasRoot())
-
-  def testHasRootSpecialAosp_false(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='aosp_sailfish')), (self.assertCall(
-                        self.call.adb.Shell('getprop service.adb.root'), '\n')):
-      self.assertFalse(self.device.HasRoot())
-
-  def testHasRootSystemRootImage(self):
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='true')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.PIE)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='notasailfish')), (self.assertCall(
-                        self.call.adb.Shell('getprop service.adb.root'),
-                        '1\n')):
-      self.assertTrue(self.device.HasRoot())
-
-  def testHasRoot10(self):
-    # All devices on Android 10 / Q and above should use the system-as-root
-    # partition layout, though they may not have the property set.
-    with self.patch_call(
-        self.call.device.build_type,
-        return_value='userdebug'), (self.patch_call(
-            self.call.device.build_system_root_image,
-            return_value='')), (self.patch_call(
-                self.call.device.build_version_sdk,
-                return_value=version_codes.Q)), (self.patch_call(
-                    self.call.device.product_name,
-                    return_value='notasailfish')), (self.assertCall(
-                        self.call.adb.Shell('getprop service.adb.root'),
-                        '1\n')):
-      self.assertTrue(self.device.HasRoot())
-
 
 class DeviceUtilsEnableRootTest(DeviceUtilsTest):
   def testEnableRoot_succeeds(self):
@@ -530,21 +435,54 @@
       self.assertTrue(self.device.IsApplicationInstalled('some.installed.app'))
 
   def testIsApplicationInstalled_notInstalled(self):
-    with self.assertCalls((self.call.device.RunShellCommand(
-        ['pm', 'list', 'packages', 'not.installed.app'], check_return=True),
-                           '')):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['pm', 'list', 'packages', 'not.installed.app'], check_return=True),
+         ''),
+        (self.call.device.RunShellCommand(
+            ['dumpsys', 'package'], check_return=True, large_output=True), [])):
       self.assertFalse(self.device.IsApplicationInstalled('not.installed.app'))
 
   def testIsApplicationInstalled_substringMatch(self):
-    with self.assertCalls((self.call.device.RunShellCommand(
-        ['pm', 'list', 'packages', 'substring.of.package'], check_return=True),
-                           [
-                               'package:first.substring.of.package',
-                               'package:second.substring.of.package',
-                           ])):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['pm', 'list', 'packages', 'substring.of.package'],
+            check_return=True),
+         [
+             'package:first.substring.of.package',
+             'package:second.substring.of.package',
+         ]),
+        (self.call.device.RunShellCommand(
+            ['dumpsys', 'package'], check_return=True, large_output=True), [])):
       self.assertFalse(
           self.device.IsApplicationInstalled('substring.of.package'))
 
+  def testIsApplicationInstalled_dumpsysFallback(self):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['pm', 'list', 'packages', 'some.installed.app'],
+            check_return=True), []),
+        (self.call.device.RunShellCommand(
+            ['dumpsys', 'package'], check_return=True, large_output=True),
+         ['Package [some.installed.app] (a12345):'])):
+      self.assertTrue(self.device.IsApplicationInstalled('some.installed.app'))
+
+  def testIsApplicationInstalled_dumpsysFallbackVersioned(self):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['dumpsys', 'package'], check_return=True, large_output=True),
+         ['Package [some.installed.app_1234] (a12345):'])):
+      self.assertTrue(
+          self.device.IsApplicationInstalled('some.installed.app', 1234))
+
+  def testIsApplicationInstalled_dumpsysFallbackVersionNotNeeded(self):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['dumpsys', 'package'], check_return=True, large_output=True),
+         ['Package [some.installed.app] (a12345):'])):
+      self.assertTrue(
+          self.device.IsApplicationInstalled('some.installed.app', 1234))
+
 
 class DeviceUtilsGetApplicationPathsInternalTest(DeviceUtilsTest):
   def testGetApplicationPathsInternal_exists(self):
@@ -913,13 +851,24 @@
 
   def testReboot_blocking(self):
     with self.assertCalls(
+        (self.call.device.HasRoot(), False),
         self.call.adb.Reboot(), (self.call.device.IsOnline(), True),
         (self.call.device.IsOnline(), False),
         self.call.device.WaitUntilFullyBooted(wifi=False, decrypt=False)):
       self.device.Reboot(block=True)
 
+  def testReboot_blockingWithRoot(self):
+    with self.assertCalls(
+        (self.call.device.HasRoot(), True),
+        self.call.adb.Reboot(), (self.call.device.IsOnline(), True),
+        (self.call.device.IsOnline(), False),
+        self.call.device.WaitUntilFullyBooted(wifi=False, decrypt=False),
+        self.call.device.EnableRoot()):
+      self.device.Reboot(block=True)
+
   def testReboot_blockUntilWifi(self):
     with self.assertCalls(
+        (self.call.device.HasRoot(), False),
         self.call.adb.Reboot(), (self.call.device.IsOnline(), True),
         (self.call.device.IsOnline(), False),
         self.call.device.WaitUntilFullyBooted(wifi=True, decrypt=False)):
@@ -927,6 +876,7 @@
 
   def testReboot_blockUntilDecrypt(self):
     with self.assertCalls(
+        (self.call.device.HasRoot(), False),
         self.call.adb.Reboot(), (self.call.device.IsOnline(), True),
         (self.call.device.IsOnline(), False),
         self.call.device.WaitUntilFullyBooted(wifi=False, decrypt=True)):
@@ -950,6 +900,7 @@
                                 reinstall=False,
                                 streaming=None,
                                 allow_downgrade=False),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True),
           (self.call.device.GrantPermissions(TEST_PACKAGE, ['p1']), [])):
         self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
 
@@ -966,6 +917,7 @@
                                 reinstall=False,
                                 streaming=False,
                                 allow_downgrade=False),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True),
           (self.call.device.GrantPermissions(TEST_PACKAGE, ['p1']), [])):
         self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
 
@@ -981,7 +933,8 @@
           (self.call.adb.Install(TEST_APK_PATH,
                                  reinstall=False,
                                  streaming=None,
-                                 allow_downgrade=False))):
+                                 allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
 
   def testInstall_findPermissions(self):
@@ -997,6 +950,7 @@
                                  reinstall=False,
                                  streaming=None,
                                  allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True),
           (self.call.device.GrantPermissions(TEST_PACKAGE, ['p1']), [])):
         self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
 
@@ -1011,6 +965,7 @@
                                  reinstall=False,
                                  streaming=None,
                                  allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True),
           (self.call.device.GrantPermissions(TEST_PACKAGE, ['p1', 'p2']), [])):
         self.device.Install(
             DeviceUtilsInstallTest.mock_apk,
@@ -1024,7 +979,8 @@
         (self.call.device._GetApplicationPathsInternal(TEST_PACKAGE),
          ['/fake/data/app/test.package.apk']),
         (self.call.device._ComputeStaleApks(TEST_PACKAGE, [TEST_APK_PATH]),
-         ([], None)), (self.call.device.ForceStop(TEST_PACKAGE))):
+         ([], None)), (self.call.device.ForceStop(TEST_PACKAGE)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
       self.device.Install(
           DeviceUtilsInstallTest.mock_apk, retries=0, permissions=[])
 
@@ -1041,7 +997,8 @@
           self.call.adb.Install(TEST_APK_PATH,
                                 reinstall=False,
                                 streaming=None,
-                                allow_downgrade=False)):
+                                allow_downgrade=False),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.Install(
             DeviceUtilsInstallTest.mock_apk, retries=0, permissions=[])
 
@@ -1058,7 +1015,8 @@
           self.call.adb.Install(TEST_APK_PATH,
                                 reinstall=False,
                                 streaming=None,
-                                allow_downgrade=False)):
+                                allow_downgrade=False),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.Install(
             DeviceUtilsInstallTest.mock_apk, retries=0, permissions=[])
 
@@ -1075,7 +1033,8 @@
           self.call.adb.Install(TEST_APK_PATH,
                                 reinstall=True,
                                 streaming=None,
-                                allow_downgrade=False)):
+                                allow_downgrade=False),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.Install(
             DeviceUtilsInstallTest.mock_apk,
             reinstall=True,
@@ -1089,7 +1048,8 @@
         (self.call.device._GetApplicationPathsInternal(TEST_PACKAGE),
          ['/fake/data/app/test.package.apk']),
         (self.call.device._ComputeStaleApks(TEST_PACKAGE, [TEST_APK_PATH]),
-         ([], None)), (self.call.device.ForceStop(TEST_PACKAGE))):
+         ([], None)), (self.call.device.ForceStop(TEST_PACKAGE)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
       self.device.Install(
           DeviceUtilsInstallTest.mock_apk,
           reinstall=True,
@@ -1131,7 +1091,8 @@
           self.call.adb.Install(TEST_APK_PATH,
                                 reinstall=True,
                                 streaming=None,
-                                allow_downgrade=True)):
+                                allow_downgrade=True),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.Install(
             DeviceUtilsInstallTest.mock_apk,
             reinstall=True,
@@ -1164,10 +1125,32 @@
                                 reinstall=False,
                                 streaming=None,
                                 allow_downgrade=False),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True),
           (self.call.device.GrantPermissions(TEST_PACKAGE, None), [])):
         self.device.Install(
             mock_apk_with_fake, fake_modules=fake_modules, retries=0)
 
+  def testInstall_packageNotAvailableAfterInstall(self):
+    with self.patch_call(
+        self.call.device.product_name,
+        return_value='notflounder'), (self.patch_call(
+            self.call.device.build_version_sdk, return_value=23)), (
+                self.patch_call(self.call.device.IsApplicationInstalled,
+                                return_value=False)):
+      with self.assertCalls(
+          (self.call.device._FakeInstall(set(), None, 'test.package')),
+          (mock.call.os.path.exists(TEST_APK_PATH), True),
+          (self.call.device._GetApplicationPathsInternal(TEST_PACKAGE), []),
+          self.call.adb.Install(TEST_APK_PATH,
+                                reinstall=False,
+                                streaming=None,
+                                allow_downgrade=False)):
+        with self.assertRaisesRegexp(
+            device_errors.CommandFailedError,
+            'not installed on device after explicit install attempt'):
+          self.device.Install(
+              DeviceUtilsInstallTest.mock_apk, retries=0)
+
 
 class DeviceUtilsInstallSplitApkTest(DeviceUtilsTest):
 
@@ -1191,7 +1174,8 @@
               partial=None,
               reinstall=False,
               streaming=None,
-              allow_downgrade=False))):
+              allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.InstallSplitApk(
             'base.apk', ['split1.apk', 'split2.apk'], permissions=[], retries=0)
 
@@ -1212,7 +1196,8 @@
               partial=None,
               reinstall=False,
               streaming=False,
-              allow_downgrade=False))):
+              allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.InstallSplitApk(
             'base.apk', ['split1.apk', 'split2.apk'], permissions=[], retries=0)
 
@@ -1237,7 +1222,8 @@
                                          partial=TEST_PACKAGE,
                                          reinstall=True,
                                          streaming=None,
-                                         allow_downgrade=False))):
+                                         allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.InstallSplitApk(
             DeviceUtilsInstallSplitApkTest.mock_apk,
             ['split1.apk', 'split2.apk'],
@@ -1266,7 +1252,8 @@
                                          partial=TEST_PACKAGE,
                                          reinstall=True,
                                          streaming=None,
-                                         allow_downgrade=True))):
+                                         allow_downgrade=True)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.InstallSplitApk(
             DeviceUtilsInstallSplitApkTest.mock_apk,
             ['split1.apk', 'split2.apk'],
@@ -1311,7 +1298,8 @@
               partial=None,
               reinstall=False,
               streaming=None,
-              allow_downgrade=False))):
+              allow_downgrade=False)),
+          (self.call.device.IsApplicationInstalled(TEST_PACKAGE, None), True)):
         self.device.InstallSplitApk(
             DeviceUtilsInstallSplitApkTest.mock_apk,
             ['split1.apk', 'split2.apk'],
@@ -3132,8 +3120,8 @@
       with self.assertCall(
           self.call.adb.Shell('dumpsys webviewupdate'),
           'Fallback logic enabled: true'):
-        with self.assertRaises(device_errors.CommandFailedError):
-          self.device.GetWebViewUpdateServiceDump()
+        update = self.device.GetWebViewUpdateServiceDump()
+        self.assertEqual(True, update['FallbackLogicEnabled'])
 
   def testGetWebViewUpdateServiceDump_noop(self):
     with self.patch_call(
diff --git a/catapult/devil/devil/android/forwarder.py b/catapult/devil/devil/android/forwarder.py
index bf4c5ca..9b1f82e 100644
--- a/catapult/devil/devil/android/forwarder.py
+++ b/catapult/devil/devil/android/forwarder.py
@@ -435,14 +435,10 @@
         (exit_code, output) = cmd_helper.GetCmdStatusAndOutputWithTimeout(
             kill_cmd, Forwarder._TIMEOUT)
         if exit_code == -9:
-          # pkill can exit with -9, seemingly in cases where the process it's
-          # asked to kill dies sometime during pkill running. In this case,
-          # re-running should result in pkill succeeding.
-          logging.warning(
-              'pkilling host forwarder returned -9, retrying through strace. '
-              'Output: %s', output)
-          exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
-              ['strace', '-f', '-s', '256'] + kill_cmd, Forwarder._TIMEOUT)
+          # pkill can exit with -9, which indicates that it was killed. It's
+          # possible that the forwarder was still killed, though, which will
+          # be checked later.
+          logging.warning('pkilling host forwarder returned -9.')
         if exit_code in (0, 1):
           # pkill exits with a 0 if it was able to signal at least one process.
           # pkill exits with a 1 if it wasn't able to signal a process because
@@ -458,6 +454,7 @@
                        '\n  '.join(host_forwarder_lines))
         else:
           logger.error('No remaining host_forwarder processes?')
+          return
         _DumpHostLog()
         error_msg = textwrap.dedent("""\
             `{kill_cmd}` failed to kill host_forwarder.
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper.py b/catapult/devil/devil/android/sdk/adb_wrapper.py
index 8cd7313..71928d5 100644
--- a/catapult/devil/devil/android/sdk/adb_wrapper.py
+++ b/catapult/devil/devil/android/sdk/adb_wrapper.py
@@ -35,11 +35,13 @@
 ADB_HOST_KEYS_DIR = os.path.join(os.path.expanduser('~'), '.android')
 
 DEFAULT_TIMEOUT = 30
+DEFAULT_LONG_TIMEOUT = DEFAULT_TIMEOUT * 10
+DEFAULT_SUPER_LONG_TIMEOUT = DEFAULT_LONG_TIMEOUT * 2
 DEFAULT_RETRIES = 2
 
 _ADB_VERSION_RE = re.compile(r'Android Debug Bridge version (\d+\.\d+\.\d+)')
 _EMULATOR_RE = re.compile(r'^emulator-[0-9]+$')
-_DEVICE_NOT_FOUND_RE = re.compile(r"error: device '(?P<serial>.+)' not found")
+_DEVICE_NOT_FOUND_RE = re.compile(r"device '(?P<serial>.+)' not found")
 _READY_STATE = 'device'
 _VERITY_DISABLE_RE = re.compile(r'(V|v)erity (is )?(already )?disabled'
                                 r'|Successfully disabled verity')
@@ -303,7 +305,7 @@
     # inconsistent with error reporting so many command failures present
     # differently.
     if status != 0 or (check_error and output.startswith('error:')):
-      not_found_m = _DEVICE_NOT_FOUND_RE.match(output)
+      not_found_m = _DEVICE_NOT_FOUND_RE.search(output)
       device_waiting_m = _WAITING_FOR_DEVICE_RE.match(output)
       if (device_waiting_m is not None
           or (not_found_m is not None
@@ -476,7 +478,7 @@
            local,
            remote,
            sync=False,
-           timeout=60 * 5,
+           timeout=DEFAULT_SUPER_LONG_TIMEOUT,
            retries=DEFAULT_RETRIES):
     """Pushes a file from the host to the device.
 
@@ -549,7 +551,11 @@
 
     self._RunDeviceAdbCmd(push_cmd, timeout, retries)
 
-  def Pull(self, remote, local, timeout=60 * 5, retries=DEFAULT_RETRIES):
+  def Pull(self,
+           remote,
+           local,
+           timeout=DEFAULT_LONG_TIMEOUT,
+           retries=DEFAULT_RETRIES):
     """Pulls a file from the device to the host.
 
     Args:
@@ -832,7 +838,7 @@
               reinstall=False,
               sd_card=False,
               streaming=None,
-              timeout=60 * 2,
+              timeout=DEFAULT_LONG_TIMEOUT,
               retries=DEFAULT_RETRIES):
     """Install an apk on the device.
 
@@ -883,7 +889,7 @@
                       allow_downgrade=False,
                       partial=False,
                       streaming=None,
-                      timeout=60 * 2,
+                      timeout=DEFAULT_LONG_TIMEOUT,
                       retries=DEFAULT_RETRIES):
     """Install an apk with splits on the device.
 
@@ -1002,7 +1008,8 @@
     VerifyLocalFileExists(path)
     self._RunDeviceAdbCmd(['restore'] + [path], timeout, retries)
 
-  def WaitForDevice(self, timeout=60 * 5, retries=DEFAULT_RETRIES):
+  def WaitForDevice(self, timeout=DEFAULT_LONG_TIMEOUT,
+                    retries=DEFAULT_RETRIES):
     """Block until the device is online.
 
     Args:
@@ -1047,7 +1054,9 @@
     """Remounts the /system partition on the device read-write."""
     self._RunDeviceAdbCmd(['remount'], timeout, retries)
 
-  def Reboot(self, to_bootloader=False, timeout=60 * 5,
+  def Reboot(self,
+             to_bootloader=False,
+             timeout=DEFAULT_LONG_TIMEOUT,
              retries=DEFAULT_RETRIES):
     """Reboots the device.
 
@@ -1069,7 +1078,16 @@
       timeout: (optional) Timeout per try in seconds.
       retries: (optional) Number of retries to attempt.
     """
-    output = self._RunDeviceAdbCmd(['root'], timeout, retries)
+    try:
+      output = self._RunDeviceAdbCmd(['root'], timeout, retries)
+    except device_errors.AdbCommandFailedError as e:
+      # For some devices, root can occasionally fail with this error and kick
+      # the device into adb 'offline' mode. Assuming this is transient, try
+      # waiting for the device to come back up before re-raising the exception
+      # and proceeding with any retries.
+      if 'unable to connect for root: closed' in e.output:
+        self.WaitForDevice()
+      raise
     if 'cannot' in output:
       raise device_errors.AdbCommandFailedError(
           ['root'], output, device_serial=self._device_serial)
@@ -1115,6 +1133,7 @@
           ['enable-verity'], output, device_serial=self._device_serial)
     return output
 
+  # Deprecated use device_utils#is_emulator instead.
   @property
   def is_emulator(self):
     return _EMULATOR_RE.match(self._device_serial)
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper_test.py b/catapult/devil/devil/android/sdk/adb_wrapper_test.py
index f30ab15..1b3d584 100755
--- a/catapult/devil/devil/android/sdk/adb_wrapper_test.py
+++ b/catapult/devil/devil/android/sdk/adb_wrapper_test.py
@@ -68,3 +68,12 @@
     get_cmd_mock.return_value = (1, '- waiting for device - ')
     self.assertRaises(device_errors.DeviceUnreachableError, self.adb.Shell,
                       '/bin/true')
+
+  @mock.patch('devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout')
+  def testRootConnectionClosedFailure(self, get_cmd_mock):
+    get_cmd_mock.side_effect = [
+        (1, 'unable to connect for root: closed'),
+        (0, ''),
+    ]
+    self.assertRaises(device_errors.AdbCommandFailedError, self.adb.Root,
+                      retries=0)
diff --git a/catapult/devil/devil/android/sdk/bundletool.py b/catapult/devil/devil/android/sdk/bundletool.py
index 5c181c5..f210db1 100644
--- a/catapult/devil/devil/android/sdk/bundletool.py
+++ b/catapult/devil/devil/android/sdk/bundletool.py
@@ -9,7 +9,9 @@
 from devil import devil_env
 from devil.utils import cmd_helper
 from devil.utils import lazy
-from py_utils import tempfile_ext
+
+with devil_env.SysPath(devil_env.PY_UTILS_PATH):
+  from py_utils import tempfile_ext
 
 _bundletool_path = lazy.WeakConstant(lambda: devil_env.config.FetchPath(
     'bundletool'))
diff --git a/catapult/devil/devil/android/tools/device_monitor.py b/catapult/devil/devil/android/tools/device_monitor.py
index 6128dae..26e89a2 100755
--- a/catapult/devil/devil/android/tools/device_monitor.py
+++ b/catapult/devil/devil/android/tools/device_monitor.py
@@ -203,11 +203,6 @@
   parser = argparse.ArgumentParser(description='Launches the device monitor.')
   script_common.AddEnvironmentArguments(parser)
   parser.add_argument('--denylist-file', help='Path to device denylist file.')
-  # TODO(crbug.com/1097306): Remove this once chromium_android/api.py stops
-  # using it.
-  parser.add_argument('--blacklist-file',
-                      dest='denylist_file',
-                      help=argparse.SUPPRESS)
   args = parser.parse_args(argv)
 
   logger = logging.getLogger()
diff --git a/catapult/devil/devil/android/tools/device_recovery.py b/catapult/devil/devil/android/tools/device_recovery.py
index f25c5a2..10c5fe5 100755
--- a/catapult/devil/devil/android/tools/device_recovery.py
+++ b/catapult/devil/devil/android/tools/device_recovery.py
@@ -114,6 +114,7 @@
     return
 
   if should_reboot(device):
+    should_restore_root = device.HasRoot()
     try:
       device.WaitUntilFullyBooted(retries=0)
     except (device_errors.CommandTimeoutError, device_errors.CommandFailedError,
@@ -154,6 +155,8 @@
     try:
       device.WaitUntilFullyBooted(
           retries=0, timeout=device.REBOOT_DEFAULT_TIMEOUT)
+      if should_restore_root:
+        device.EnableRoot()
     except (device_errors.CommandFailedError,
             device_errors.DeviceUnreachableError):
       logger.exception('Failure while waiting for %s.', str(device))
@@ -230,8 +233,6 @@
   parser = argparse.ArgumentParser()
   logging_common.AddLoggingArguments(parser)
   script_common.AddEnvironmentArguments(parser)
-  # TODO(crbug.com/1097306): Remove this once callers switch to --denylist-file.
-  parser.add_argument('--blacklist-file', help=argparse.SUPPRESS)
   parser.add_argument('--denylist-file', help='Device denylist JSON file.')
   parser.add_argument(
       '--known-devices-file',
@@ -248,9 +249,6 @@
 
   denylist = (device_denylist.Denylist(args.denylist_file)
               if args.denylist_file else None)
-  # TODO(crbug.com/1097306): Remove this once callers switch to --denylist-file.
-  if not denylist and args.blacklist_file:
-    denylist = device_denylist.Denylist(args.blacklist_file)
 
   expected_devices = device_status.GetExpectedDevices(args.known_devices_files)
   usb_devices = set(lsusb.get_android_devices())
diff --git a/catapult/devil/devil/android/tools/device_status.py b/catapult/devil/devil/android/tools/device_status.py
index d0eb7df..40f714a 100755
--- a/catapult/devil/devil/android/tools/device_status.py
+++ b/catapult/devil/devil/android/tools/device_status.py
@@ -226,11 +226,6 @@
   parser.add_argument(
       '--json-output', help='Output JSON information into a specified file.')
   parser.add_argument('--denylist-file', help='Device denylist JSON file.')
-  # TODO(crbug.com/1097306): Remove this once //testing/scripts/host_info.py
-  # stops using it.
-  parser.add_argument('--blacklist-file',
-                      dest='denylist_file',
-                      help=argparse.SUPPRESS)
   parser.add_argument(
       '--known-devices-file',
       action='append',
diff --git a/catapult/devil/devil/android/tools/provision_devices.py b/catapult/devil/devil/android/tools/provision_devices.py
index 4b0f44a..9a324c4 100755
--- a/catapult/devil/devil/android/tools/provision_devices.py
+++ b/catapult/devil/devil/android/tools/provision_devices.py
@@ -625,9 +625,6 @@
       metavar='NUM',
       help='wait for the device to reach this minimum battery'
       ' level before trying to continue')
-  # TODO(crbug.com/1097306): Remove after updating clients.
-  parser.add_argument('--output-device-blacklist',
-                      help=argparse.SUPPRESS)
   parser.add_argument('--output-device-denylist',
                       help='Json file to output the device denylist.')
   parser.add_argument(
@@ -674,10 +671,6 @@
   logging_common.InitializeLogging(args)
   script_common.InitializeEnvironment(args)
 
-  output_device_denylist = args.output_device_denylist
-  if not output_device_denylist and args.output_device_blacklist:
-    output_device_denylist = args.output_device_blacklist
-
   try:
     return ProvisionDevices(
         args.devices,
@@ -691,7 +684,7 @@
         enable_java_debug=args.enable_java_debug,
         max_battery_temp=args.max_battery_temp,
         min_battery_level=args.min_battery_level,
-        output_device_denylist=output_device_denylist,
+        output_device_denylist=args.output_device_denylist,
         reboot_timeout=args.reboot_timeout,
         remove_system_webview=args.remove_system_webview,
         system_app_remove_list=args.system_app_remove_list,
diff --git a/catapult/devil/devil/android/tools/script_common.py b/catapult/devil/devil/android/tools/script_common.py
index ae2b7a8..de82366 100644
--- a/catapult/devil/devil/android/tools/script_common.py
+++ b/catapult/devil/devil/android/tools/script_common.py
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import argparse
 import os
 
 from devil import devil_env
@@ -66,22 +65,8 @@
       default=[],
       help='Serial number of the Android device to use. (default: use all)')
 
-  # TODO(crbug.com/1097306): Simplify this to an ungrouped --denylist-file
-  # once all uses of --blacklist-file / args.blacklist_file have been updated.
-  class DenylistAction(argparse.Action):
-
-    #override
-    def __call__(self, parser, namespace, values, option_string=None):
-      setattr(namespace, 'denylist_file', values)
-      setattr(namespace, 'blacklist_file', values)
-
-  denylist_group = parser.add_mutually_exclusive_group()
-  denylist_group.add_argument('--denylist-file',
-                              help='Device denylist JSON file.',
-                              action=DenylistAction)
-  denylist_group.add_argument('--blacklist-file',
-                              help=argparse.SUPPRESS,
-                              action=DenylistAction)
+  parser.add_argument('--denylist-file',
+                      help='Device denylist JSON file.')
 
 
 def GetDevices(requested_devices, denylist_file):
diff --git a/catapult/devil/devil/android/tools/system_app.py b/catapult/devil/devil/android/tools/system_app.py
index c2f058e..62bf5a5 100755
--- a/catapult/devil/devil/android/tools/system_app.py
+++ b/catapult/devil/devil/android/tools/system_app.py
@@ -34,8 +34,9 @@
 SPECIAL_SYSTEM_APP_LOCATIONS = {
     # Older versions of ArCore were installed in /data/app/ regardless of
     # whether they were system apps or not. Newer versions install in /system/
-    # if they are system apps, and in /data/app/ if they aren't.
-    'com.google.ar.core': ['/data/app/', '/system/'],
+    # if they are system apps, and in /data/app/ if they aren't. Some newer
+    # devices/OSes install in /product/app/ for system apps, as well.
+    'com.google.ar.core': ['/data/app/', '/system/', '/product/app/'],
     # On older versions of VrCore, the system app version is installed in
     # /system/ like normal. However, at some point, this moved to /data/.
     # So, we have to handle both cases. Like ArCore, this means we'll end up
diff --git a/catapult/devil/devil/android/tools/webview_app.py b/catapult/devil/devil/android/tools/webview_app.py
index 406de1c..5866a66 100755
--- a/catapult/devil/devil/android/tools/webview_app.py
+++ b/catapult/devil/devil/android/tools/webview_app.py
@@ -12,15 +12,11 @@
 import sys
 
 if __name__ == '__main__':
-  sys.path.append(
-      os.path.abspath(
-          os.path.join(os.path.dirname(__file__), '..', '..', '..')))
-  sys.path.append(
-      os.path.abspath(
-          os.path.join(
-              os.path.dirname(__file__), '..', '..', '..', '..', 'common',
-              'py_utils')))
+  _DEVIL_ROOT_DIR = os.path.abspath(
+      os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+  sys.path.append(_DEVIL_ROOT_DIR)
 
+from devil import devil_env
 from devil.android import apk_helper
 from devil.android import device_errors
 from devil.android.sdk import version_codes
@@ -29,7 +25,9 @@
 from devil.utils import cmd_helper
 from devil.utils import parallelizer
 from devil.utils import run_tests_helper
-from py_utils import tempfile_ext
+
+with devil_env.SysPath(devil_env.PY_UTILS_PATH):
+  from py_utils import tempfile_ext
 
 logger = logging.getLogger(__name__)
 
diff --git a/catapult/devil/devil/devil_dependencies.json b/catapult/devil/devil/devil_dependencies.json
index 8e15e35..03e8d98 100644
--- a/catapult/devil/devil/devil_dependencies.json
+++ b/catapult/devil/devil/devil_dependencies.json
@@ -16,7 +16,7 @@
       "cloud_storage_bucket": "chromium-telemetry",
       "file_info": {
         "linux2_x86_64": {
-          "cloud_storage_hash": "8bd43e3930f6eec643d5dc64cab9e5bb4ddf4909",
+          "cloud_storage_hash": "528d0e88a0d876b0549840f0797beffca4a6a796",
           "download_path": "../bin/deps/linux2/x86_64/bin/adb"
         }
       }
diff --git a/catapult/devil/devil/devil_env.py b/catapult/devil/devil/devil_env.py
index b39e01c..b61d1b0 100644
--- a/catapult/devil/devil/devil_env.py
+++ b/catapult/devil/devil/devil_env.py
@@ -15,6 +15,7 @@
     os.path.join(os.path.dirname(__file__), '..', '..'))
 DEPENDENCY_MANAGER_PATH = os.path.join(CATAPULT_ROOT_PATH, 'dependency_manager')
 PYMOCK_PATH = os.path.join(CATAPULT_ROOT_PATH, 'third_party', 'mock')
+PY_UTILS_PATH = os.path.join(CATAPULT_ROOT_PATH, 'common', 'py_utils')
 
 
 @contextlib.contextmanager
@@ -151,7 +152,9 @@
       devil_logger.propagate = False
       devil_logger.addHandler(handler)
 
-      import py_utils.cloud_storage
+      with SysPath(PY_UTILS_PATH):
+        import py_utils.cloud_storage
+
       lock_logger = py_utils.cloud_storage.logger
       lock_logger.setLevel(log_level)
       lock_logger.propagate = False
diff --git a/catapult/devil/devil/utils/update_dependencies.py b/catapult/devil/devil/utils/update_dependencies.py
index df1f88b..513c638 100755
--- a/catapult/devil/devil/utils/update_dependencies.py
+++ b/catapult/devil/devil/utils/update_dependencies.py
@@ -116,7 +116,7 @@
   remote_path = '%s_%s' % (dependency_name, dependency_sha1)
   gs_dest = 'gs://%s/%s/%s' % (bucket, folder, remote_path)
   ec, gsutil_output = cmd_helper.GetCmdStatusAndOutput(
-      ['gsutil', 'cp', local_path, gs_dest])
+      ['gsutil.py', 'cp', local_path, gs_dest])
   if ec:
     raise base_error.BaseError(
         'Failed to upload %s to %s: %s' % (remote_path, gs_dest, gsutil_output))
diff --git a/catapult/devil/devil/utils/zip_utils.py b/catapult/devil/devil/utils/zip_utils.py
index 35c5905..aac9641 100644
--- a/catapult/devil/devil/utils/zip_utils.py
+++ b/catapult/devil/devil/utils/zip_utils.py
@@ -9,18 +9,17 @@
 import sys
 import zipfile
 
-_DEVIL_ROOT_DIR = os.path.abspath(
-    os.path.join(os.path.dirname(__file__), '..', '..'))
 if __name__ == '__main__':
+  _DEVIL_ROOT_DIR = os.path.abspath(
+      os.path.join(os.path.dirname(__file__), '..', '..'))
   sys.path.append(_DEVIL_ROOT_DIR)
 
-_PY_UTILS_ROOT_DIR = os.path.abspath(
-    os.path.join(_DEVIL_ROOT_DIR, '..', 'common', 'py_utils'))
-sys.path.append(_PY_UTILS_ROOT_DIR)
-
+from devil import devil_env
 from devil import base_error
 from devil.utils import cmd_helper
-from py_utils import tempfile_ext
+
+with devil_env.SysPath(devil_env.PY_UTILS_PATH):
+  from py_utils import tempfile_ext
 
 logger = logging.getLogger(__name__)
 
diff --git a/catapult/devil/devil/utils/zip_utils_test.py b/catapult/devil/devil/utils/zip_utils_test.py
index 96a55f5..1afc3f4 100644
--- a/catapult/devil/devil/utils/zip_utils_test.py
+++ b/catapult/devil/devil/utils/zip_utils_test.py
@@ -6,8 +6,11 @@
 import unittest
 import zipfile
 
+from devil import devil_env
 from devil.utils import zip_utils
-from py_utils import tempfile_ext
+
+with devil_env.SysPath(devil_env.PY_UTILS_PATH):
+  from py_utils import tempfile_ext
 
 
 class WriteZipFileTest(unittest.TestCase):
diff --git a/catapult/systrace/systrace/systrace_trace_viewer.html b/catapult/systrace/systrace/systrace_trace_viewer.html
index 04e8089..473261d 100644
--- a/catapult/systrace/systrace/systrace_trace_viewer.html
+++ b/catapult/systrace/systrace/systrace_trace_viewer.html
@@ -2901,8 +2901,6 @@
       <div id="collapsing_controls"></div>
       <tr-ui-b-info-bar-group id="import-warnings">
       </tr-ui-b-info-bar-group>
-      <tr-ui-b-info-bar-group id="polyfill-warning">
-      </tr-ui-b-info-bar-group>
     </div>
     <middle-container>
       <slot></slot>
@@ -3468,7 +3466,22 @@
  * Do not edit directly.
  */
 
-'use strict';if(!window.CustomElements||window.CustomElements.hasNative){if(window.Polymer){throw new Error('Cannot proceed. Polymer already present.');}
+'use strict';const global=this.window||this.global;this.tr=(function(){if(global.tr)return global.tr;function exportPath(name){const parts=name.split('.');let cur=global;for(let part;parts.length&&(part=parts.shift());){if(part in cur){cur=cur[part];}else{cur=cur[part]={};}}
+return cur;}
+function isExported(name){const parts=name.split('.');let cur=global;for(let part;parts.length&&(part=parts.shift());){if(part in cur){cur=cur[part];}else{return false;}}
+return true;}
+function isDefined(name){const parts=name.split('.');let curObject=global;for(let i=0;i<parts.length;i++){const partName=parts[i];const nextObject=curObject[partName];if(nextObject===undefined)return false;curObject=nextObject;}
+return true;}
+let panicElement=undefined;const rawPanicMessages=[];function showPanicElementIfNeeded(){if(panicElement)return;const panicOverlay=document.createElement('div');panicOverlay.style.backgroundColor='white';panicOverlay.style.border='3px solid red';panicOverlay.style.boxSizing='border-box';panicOverlay.style.color='black';panicOverlay.style.display='flex';panicOverlay.style.height='100%';panicOverlay.style.left=0;panicOverlay.style.padding='8px';panicOverlay.style.position='fixed';panicOverlay.style.top=0;panicOverlay.style.webkitFlexDirection='column';panicOverlay.style.width='100%';panicElement=document.createElement('div');panicElement.style.webkitFlex='1 1 auto';panicElement.style.overflow='auto';panicOverlay.appendChild(panicElement);if(!document.body){setTimeout(function(){document.body.appendChild(panicOverlay);},150);}else{document.body.appendChild(panicOverlay);}}
+function showPanic(panicTitle,panicDetails){if(tr.isHeadless){if(panicDetails instanceof Error)throw panicDetails;throw new Error('Panic: '+panicTitle+':\n'+panicDetails);}
+if(panicDetails instanceof Error){panicDetails=panicDetails.stack;}
+showPanicElementIfNeeded();const panicMessageEl=document.createElement('div');panicMessageEl.innerHTML='<h2 id="message"></h2>'+'<pre id="details"></pre>';panicMessageEl.querySelector('#message').textContent=panicTitle;panicMessageEl.querySelector('#details').textContent=panicDetails;panicElement.appendChild(panicMessageEl);rawPanicMessages.push({title:panicTitle,details:panicDetails});}
+function hasPanic(){return rawPanicMessages.length!==0;}
+function getPanicText(){return rawPanicMessages.map(function(msg){return msg.title;}).join(', ');}
+function exportTo(namespace,fn){const obj=exportPath(namespace);const exports=fn();for(const propertyName in exports){const propertyDescriptor=Object.getOwnPropertyDescriptor(exports,propertyName);if(propertyDescriptor){Object.defineProperty(obj,propertyName,propertyDescriptor);}}}
+function initialize(){if(global.isVinn){tr.isVinn=true;}else if(global.process&&global.process.versions.node){tr.isNode=true;}else{tr.isVinn=false;tr.isNode=false;tr.doc=document;tr.isMac=/Mac/.test(navigator.platform);tr.isWindows=/Win/.test(navigator.platform);tr.isChromeOS=/CrOS/.test(navigator.userAgent);tr.isLinux=/Linux/.test(navigator.userAgent);}
+tr.isHeadless=tr.isVinn||tr.isNode;}
+return{initialize,exportTo,isExported,isDefined,showPanic,hasPanic,getPanicText,};})();tr.initialize();'use strict';if(!window.CustomElements||window.CustomElements.hasNative){if(window.Polymer){throw new Error('Cannot proceed. Polymer already present.');}
 window.Polymer={};window.Polymer.dom='shadow';}
 (function(){function resolve(){document.body.removeAttribute('unresolved');}
 if(window.WebComponents){addEventListener('WebComponentsReady',resolve);}else{if(document.readyState==='interactive'||document.readyState==='complete'){resolve();}else{addEventListener('DOMContentLoaded',resolve);}}}());window.Polymer={Settings:function(){var settings=window.Polymer||{};if(!settings.noUrlSettings){var parts=location.search.slice(1).split('&');for(var i=0,o;i<parts.length&&(o=parts[i]);i++){o=o.split('=');o[0]&&(settings[o[0]]=o[1]||true);}}
@@ -4062,22 +4075,7 @@
 this._instance=null;}},_showHideChildren:function(){var hidden=this.__hideTemplateChildren__||!this.if;if(this._instance){this._instance._showHideChildren(hidden);}},_forwardParentProp:function(prop,value){if(this._instance){this._instance.__setProperty(prop,value,true);}},_forwardParentPath:function(path,value){if(this._instance){this._instance._notifyPath(path,value,true);}}});Polymer({is:'dom-bind',properties:{notifyDomChange:{type:Boolean}},extends:'template',_template:null,created:function(){var self=this;Polymer.RenderStatus.whenReady(function(){if(document.readyState=='loading'){document.addEventListener('DOMContentLoaded',function(){self._markImportsReady();});}else{self._markImportsReady();}});},_ensureReady:function(){if(!this._readied){this._readySelf();}},_markImportsReady:function(){this._importsReady=true;this._ensureReady();},_registerFeatures:function(){this._prepConstructor();},_insertChildren:function(){var refNode;var parentNode=Polymer.dom(this).parentNode;if(parentNode.localName==this.is){refNode=parentNode;parentNode=Polymer.dom(parentNode).parentNode;}else{refNode=this;}
 Polymer.dom(parentNode).insertBefore(this.root,refNode);},_removeChildren:function(){if(this._children){for(var i=0;i<this._children.length;i++){this.root.appendChild(this._children[i]);}}},_initFeatures:function(){},_scopeElementClass:function(element,selector){if(this.dataHost){return this.dataHost._scopeElementClass(element,selector);}else{return selector;}},_configureInstanceProperties:function(){},_prepConfigure:function(){var config={};for(var prop in this._propertyEffects){config[prop]=this[prop];}
 var setupConfigure=this._setupConfigure;this._setupConfigure=function(){setupConfigure.call(this,config);};},attached:function(){if(this._importsReady){this.render();}},detached:function(){this._removeChildren();},render:function(){this._ensureReady();if(!this._children){this._template=this;this._prepAnnotations();this._prepEffects();this._prepBehaviors();this._prepConfigure();this._prepBindings();this._prepPropertyInfo();Polymer.Base._initFeatures.call(this);this._children=Polymer.TreeApi.arrayCopyChildNodes(this.root);}
-this._insertChildren();if(!Polymer.Settings.suppressTemplateNotifications||this.notifyDomChange){this.fire('dom-change');}}});'use strict';if(!window.CustomElements||window.CustomElements.hasNative){if(!Polymer.Settings.useNativeShadow){tr.showPanic('Polymer error','base should use native shadow when possible.');}}'use strict';const global=this.window||this.global;this.tr=(function(){if(global.tr)return global.tr;function exportPath(name){const parts=name.split('.');let cur=global;for(let part;parts.length&&(part=parts.shift());){if(part in cur){cur=cur[part];}else{cur=cur[part]={};}}
-return cur;}
-function isExported(name){const parts=name.split('.');let cur=global;for(let part;parts.length&&(part=parts.shift());){if(part in cur){cur=cur[part];}else{return false;}}
-return true;}
-function isDefined(name){const parts=name.split('.');let curObject=global;for(let i=0;i<parts.length;i++){const partName=parts[i];const nextObject=curObject[partName];if(nextObject===undefined)return false;curObject=nextObject;}
-return true;}
-let panicElement=undefined;const rawPanicMessages=[];function showPanicElementIfNeeded(){if(panicElement)return;const panicOverlay=document.createElement('div');panicOverlay.style.backgroundColor='white';panicOverlay.style.border='3px solid red';panicOverlay.style.boxSizing='border-box';panicOverlay.style.color='black';panicOverlay.style.display='flex';panicOverlay.style.height='100%';panicOverlay.style.left=0;panicOverlay.style.padding='8px';panicOverlay.style.position='fixed';panicOverlay.style.top=0;panicOverlay.style.webkitFlexDirection='column';panicOverlay.style.width='100%';panicElement=document.createElement('div');panicElement.style.webkitFlex='1 1 auto';panicElement.style.overflow='auto';panicOverlay.appendChild(panicElement);if(!document.body){setTimeout(function(){document.body.appendChild(panicOverlay);},150);}else{document.body.appendChild(panicOverlay);}}
-function showPanic(panicTitle,panicDetails){if(tr.isHeadless){if(panicDetails instanceof Error)throw panicDetails;throw new Error('Panic: '+panicTitle+':\n'+panicDetails);}
-if(panicDetails instanceof Error){panicDetails=panicDetails.stack;}
-showPanicElementIfNeeded();const panicMessageEl=document.createElement('div');panicMessageEl.innerHTML='<h2 id="message"></h2>'+'<pre id="details"></pre>';panicMessageEl.querySelector('#message').textContent=panicTitle;panicMessageEl.querySelector('#details').textContent=panicDetails;panicElement.appendChild(panicMessageEl);rawPanicMessages.push({title:panicTitle,details:panicDetails});}
-function hasPanic(){return rawPanicMessages.length!==0;}
-function getPanicText(){return rawPanicMessages.map(function(msg){return msg.title;}).join(', ');}
-function exportTo(namespace,fn){const obj=exportPath(namespace);const exports=fn();for(const propertyName in exports){const propertyDescriptor=Object.getOwnPropertyDescriptor(exports,propertyName);if(propertyDescriptor){Object.defineProperty(obj,propertyName,propertyDescriptor);}}}
-function initialize(){if(global.isVinn){tr.isVinn=true;}else if(global.process&&global.process.versions.node){tr.isNode=true;}else{tr.isVinn=false;tr.isNode=false;tr.doc=document;tr.isMac=/Mac/.test(navigator.platform);tr.isWindows=/Win/.test(navigator.platform);tr.isChromeOS=/CrOS/.test(navigator.userAgent);tr.isLinux=/Linux/.test(navigator.userAgent);}
-tr.isHeadless=tr.isVinn||tr.isNode;}
-return{initialize,exportTo,isExported,isDefined,showPanic,hasPanic,getPanicText,};})();tr.initialize();'use strict';tr.exportTo('tr.b',function(){function EventTarget(){}
+this._insertChildren();if(!Polymer.Settings.suppressTemplateNotifications||this.notifyDomChange){this.fire('dom-change');}}});'use strict';if(!window.CustomElements||window.CustomElements.hasNative){if(!Polymer.Settings.useNativeShadow){tr.showPanic('Polymer error','base should use native shadow when possible.');}}'use strict';tr.exportTo('tr.b',function(){function EventTarget(){}
 EventTarget.decorate=function(target){for(const k in EventTarget.prototype){if(k==='decorate')continue;const v=EventTarget.prototype[k];if(typeof v!=='function')continue;target[k]=v;}};EventTarget.prototype={addEventListener(type,handler){if(!this.listeners_){this.listeners_=Object.create(null);}
 if(!(type in this.listeners_)){this.listeners_[type]=[handler];}else{const handlers=this.listeners_[type];if(handlers.indexOf(handler)<0){handlers.push(handler);}}},removeEventListener(type,handler){if(!this.listeners_)return;if(type in this.listeners_){const handlers=this.listeners_[type];const index=handlers.indexOf(handler);if(index>=0){if(handlers.length===1){delete this.listeners_[type];}else{handlers.splice(index,1);}}}},dispatchEvent(event){if(!this.listeners_)return true;event.__defineGetter__('target',()=>this);const realPreventDefault=event.preventDefault;event.preventDefault=function(){realPreventDefault.call(this);this.rawReturnValue=false;};const type=event.type;let prevented=0;if(type in this.listeners_){const handlers=this.listeners_[type].concat();for(let i=0,handler;handler=handlers[i];i++){if(handler.handleEvent){prevented|=handler.handleEvent.call(handler,event)===false;}else{prevented|=handler.call(this,event)===false;}}}
 return!prevented&&event.rawReturnValue;},async dispatchAsync(event){if(!this.listeners_)return true;const listeners=this.listeners_[event.type];if(listeners===undefined)return;await Promise.all(listeners.slice().map(listener=>{if(listener.handleEvent){return listener.handleEvent.call(listener,event);}
@@ -4574,8 +4572,8 @@
 return process.findAllThreadsNamed('CrGpuMain').length>0;};ChromeGpuHelper.prototype={__proto__:tr.model.helpers.ChromeProcessHelper.prototype};return{ChromeGpuHelper,};});'use strict';tr.exportTo('tr.model.helpers',function(){const NET_CATEGORIES=new Set(['net','netlog','disabled-by-default-netlog','disabled-by-default-network']);class ChromeThreadHelper{constructor(thread){this.thread=thread;}
 getNetworkEvents(){const networkEvents=[];for(const slice of this.thread.asyncSliceGroup.slices){const categories=tr.b.getCategoryParts(slice.category);const isNetEvent=category=>NET_CATEGORIES.has(category);if(categories.filter(isNetEvent).length===0)continue;networkEvents.push(slice);}
 return networkEvents;}}
-return{ChromeThreadHelper,};});'use strict';tr.exportTo('tr.model.helpers',function(){const ChromeThreadHelper=tr.model.helpers.ChromeThreadHelper;function ChromeRendererHelper(modelHelper,process){tr.model.helpers.ChromeProcessHelper.call(this,modelHelper,process);this.mainThread_=process.findAtMostOneThreadNamed('CrRendererMain')||process.findAtMostOneThreadNamed('Chrome_InProcRendererThread');this.compositorThread_=process.findAtMostOneThreadNamed('Compositor');this.rasterWorkerThreads_=process.findAllThreadsMatching(function(t){if(t.name===undefined)return false;if(t.name.startsWith('CompositorTileWorker'))return true;if(t.name.startsWith('CompositorRasterWorker'))return true;return false;});this.dedicatedWorkerThreads_=process.findAllThreadsMatching(function(t){return t.name&&t.name.startsWith('DedicatedWorker');});this.foregroundWorkerThreads_=process.findAllThreadsMatching(function(t){return t.name&&t.name.startsWith('ThreadPoolForegroundWorker');});if(!process.name){process.name=ChromeRendererHelper.PROCESS_NAME;}}
-ChromeRendererHelper.PROCESS_NAME='Renderer';ChromeRendererHelper.isRenderProcess=function(process){if(process.findAtMostOneThreadNamed('CrRendererMain'))return true;if(process.findAtMostOneThreadNamed('Compositor'))return true;return false;};ChromeRendererHelper.isTracingProcess=function(process){return process.labels!==undefined&&process.labels.length===1&&process.labels[0]==='chrome://tracing';};ChromeRendererHelper.prototype={__proto__:tr.model.helpers.ChromeProcessHelper.prototype,get mainThread(){return this.mainThread_;},get compositorThread(){return this.compositorThread_;},get rasterWorkerThreads(){return this.rasterWorkerThreads_;},get dedicatedWorkerThreads(){return this.dedicatedWorkerThreads_;},get foregroundWorkerThreads(){return this.foregroundWorkerThreads_;},get isChromeTracingUI(){return ChromeRendererHelper.isTracingProcess(this.process);},};return{ChromeRendererHelper,};});'use strict';tr.exportTo('tr.model.um',function(){class Segment extends tr.model.TimedEvent{constructor(start,duration){super(start);this.duration=duration;this.expectations_=[];}
+return{ChromeThreadHelper,};});'use strict';tr.exportTo('tr.model.helpers',function(){const ChromeThreadHelper=tr.model.helpers.ChromeThreadHelper;function ChromeRendererHelper(modelHelper,process){tr.model.helpers.ChromeProcessHelper.call(this,modelHelper,process);this.mainThread_=process.findAtMostOneThreadNamed('CrRendererMain')||process.findAtMostOneThreadNamed('Chrome_InProcRendererThread');this.compositorThread_=process.findAtMostOneThreadNamed('Compositor');this.rasterWorkerThreads_=process.findAllThreadsMatching(function(t){if(t.name===undefined)return false;if(t.name.startsWith('CompositorTileWorker'))return true;if(t.name.startsWith('CompositorRasterWorker'))return true;return false;});this.dedicatedWorkerThreads_=process.findAllThreadsMatching(function(t){return t.name&&t.name.startsWith('DedicatedWorker');});this.serviceWorkerThreads_=process.findAllThreadsMatching(function(t){return t.name&&t.name.startsWith('ServiceWorker');});this.foregroundWorkerThreads_=process.findAllThreadsMatching(function(t){return t.name&&t.name.startsWith('ThreadPoolForegroundWorker');});if(!process.name){process.name=ChromeRendererHelper.PROCESS_NAME;}}
+ChromeRendererHelper.PROCESS_NAME='Renderer';ChromeRendererHelper.isRenderProcess=function(process){if(process.findAtMostOneThreadNamed('CrRendererMain'))return true;if(process.findAtMostOneThreadNamed('Compositor'))return true;return false;};ChromeRendererHelper.isTracingProcess=function(process){return process.labels!==undefined&&process.labels.length===1&&process.labels[0]==='chrome://tracing';};ChromeRendererHelper.prototype={__proto__:tr.model.helpers.ChromeProcessHelper.prototype,get mainThread(){return this.mainThread_;},get compositorThread(){return this.compositorThread_;},get rasterWorkerThreads(){return this.rasterWorkerThreads_;},get dedicatedWorkerThreads(){return this.dedicatedWorkerThreads_;},get serviceWorkerThreads(){return this.serviceWorkerThreads_;},get foregroundWorkerThreads(){return this.foregroundWorkerThreads_;},get isChromeTracingUI(){return ChromeRendererHelper.isTracingProcess(this.process);},};return{ChromeRendererHelper,};});'use strict';tr.exportTo('tr.model.um',function(){class Segment extends tr.model.TimedEvent{constructor(start,duration){super(start);this.duration=duration;this.expectations_=[];}
 get expectations(){return this.expectations_;}
 clone(){const clone=new Segment(this.start,this.duration);clone.expectations.push(...this.expectations);return clone;}
 addSegment(other){this.duration+=other.duration;this.expectations.push(...other.expectations);}}
@@ -4799,7 +4797,8 @@
 if(markers[0].domainId===domainId){throw new Error('A clock domain cannot sync with itself.');}
 markers.push(marker);this.onSyncCompleted_(markers[0],marker);},get completeSyncIds(){const completeSyncIds=[];for(const[syncId,markers]of this.markersBySyncId){if(markers.length===2)completeSyncIds.push(syncId);}
 return completeSyncIds;},get markersBySyncId(){return this.markersBySyncId_;},get domainsSeen(){return this.domainsSeen_;},getModelTimeTransformer(domainId){this.onDomainSeen_(domainId);if(!this.modelDomainId_){this.selectModelDomainId_();}
-return this.getTimeTransformerRaw_(domainId,this.modelDomainId_).fn;},getTimeTransformerError(fromDomainId,toDomainId){this.onDomainSeen_(fromDomainId);this.onDomainSeen_(toDomainId);return this.getTimeTransformerRaw_(fromDomainId,toDomainId).error;},getTimeTransformerRaw_(fromDomainId,toDomainId){const transformer=this.getTransformerBetween_(fromDomainId,toDomainId);if(!transformer){throw new Error('No clock sync markers exist pairing clock domain "'+
+return this.getTimeTransformerRaw_(domainId,this.modelDomainId_).fn;},getModelTimeTransformerInverse(domainId){this.onDomainSeen_(domainId);if(!this.modelDomainId_){this.selectModelDomainId_();}
+return this.getTimeTransformerRaw_(this.modelDomainId_,domainId).fn;},getTimeTransformerError(fromDomainId,toDomainId){this.onDomainSeen_(fromDomainId);this.onDomainSeen_(toDomainId);return this.getTimeTransformerRaw_(fromDomainId,toDomainId).error;},getTimeTransformerRaw_(fromDomainId,toDomainId){const transformer=this.getTransformerBetween_(fromDomainId,toDomainId);if(!transformer){throw new Error('No clock sync markers exist pairing clock domain "'+
 fromDomainId+'" '+'with target clock domain "'+
 toDomainId+'".');}
 return transformer;},getTransformerBetween_(fromDomainId,toDomainId){const visitedDomainIds=new Set();const queue=[{domainId:fromDomainId,transformer:Transformer.IDENTITY}];while(queue.length>0){queue.sort((domain1,domain2)=>domain1.transformer.error-domain2.transformer.error);const current=queue.shift();if(current.domainId===toDomainId){return current.transformer;}
@@ -5154,7 +5153,8 @@
 for(const event of this.childEvents()){event.start+=shiftAmount;}
 this.updateBounds();},convertTimestampToModelTime(sourceClockDomainName,ts){if(sourceClockDomainName!=='traceEventClock'){throw new Error('Only traceEventClock is supported.');}
 return tr.b.Unit.timestampFromUs(ts)+
-this.timestampShiftToZeroAmount_;},get numProcesses(){let n=0;for(const p in this.processes){n++;}
+this.timestampShiftToZeroAmount_;},convertTimestampFromModelTime(targetClockDomainName,ts){if(targetClockDomainName!=='traceEventClock'){throw new Error('Only traceEventClock is supported.');}
+const convertFn=this.clockSyncManager.getModelTimeTransformerInverse(tr.model.ClockDomainId.LINUX_FTRACE_GLOBAL);return convertFn(ts)-this.timestampShiftToZeroAmount_;},get numProcesses(){let n=0;for(const p in this.processes){n++;}
 return n;},getProcess(pid){return this.processes[pid];},getOrCreateProcess(pid){if(!this.processes[pid]){this.processes[pid]=new Process(this,pid);}
 return this.processes[pid];},addStackFrame(stackFrame){if(this.stackFrames[stackFrame.id]){throw new Error('Stack frame already exists');}
 this.stackFrames[stackFrame.id]=stackFrame;return stackFrame;},updateCategories_(){const categoriesDict={};this.userModel.addCategoriesToDict(categoriesDict);this.device.addCategoriesToDict(categoriesDict);this.kernel.addCategoriesToDict(categoriesDict);for(const pid in this.processes){this.processes[pid].addCategoriesToDict(categoriesDict);}
@@ -5683,10 +5683,10 @@
 const profileTree=new tr.model.ProfileTree();profileTreeMap.set(id,profileTree);const info=this.profileInfo_.get(id);if(info!==undefined){profileTree.startTime=info.startTime;profileTree.pid=info.pid;profileTree.tid=info.tid;}
 return profileTree;},processSample(event){if(event.args===undefined||event.args.data===undefined){return;}
 if(event.id===undefined){throw new Error('No event ID in sample');}
-const data=event.args.data;if(data.startTime!==undefined){this.profileInfo_.set(event.id,{startTime:data.startTime,pid:event.pid,tid:event.tid});}
+const data=event.args.data;if(data.startTime!==undefined){this.profileInfo_.set(`${event.pid} ${event.id}`,{startTime:data.startTime,pid:event.pid,tid:event.tid});}
 const timeDeltas=data.timeDeltas;for(const sampleType in data){if(sampleType==='timeDeltas'||sampleType==='startTime'){continue;}
 if(data[sampleType].samples&&timeDeltas&&data[sampleType].samples.length!==timeDeltas.length){throw new Error('samples and timeDeltas array should have same length');}
-const profileTree=this.getOrCreateProfileTree_(sampleType,event.id);const nodes=data[sampleType].nodes;const samples=data[sampleType].samples;if(nodes!==undefined){for(const node of nodes){const ProfileNodeType=tr.model.ProfileNode.subTypes.getConstructor(undefined,sampleType);const profileNode=ProfileNodeType.constructFromObject(profileTree,node);if(profileNode===undefined){continue;}
+const profileTree=this.getOrCreateProfileTree_(sampleType,`${event.pid} ${event.id}`);const nodes=data[sampleType].nodes;const samples=data[sampleType].samples;if(nodes!==undefined){for(const node of nodes){const ProfileNodeType=tr.model.ProfileNode.subTypes.getConstructor(undefined,sampleType);const profileNode=ProfileNodeType.constructFromObject(profileTree,node);if(profileNode===undefined){continue;}
 profileTree.add(profileNode);}}
 if(samples!==undefined){const thread=this.model_.getOrCreateProcess(profileTree.pid).getOrCreateThread(profileTree.tid);for(let i=0,len=samples.length;i<len;++i){const node=profileTree.getNode(samples[i]);profileTree.endTime+=timeDeltas[i];if(node===undefined)continue;const start=this.toModelTimeFromUs_(profileTree.endTime);this.model_.samples.push(new tr.model.Sample(start,node.sampleTitle,node,thread));}}}},processLegacyV8Sample(event){const data=event.args.data;const sampleType='legacySample';const ProfileNodeType=tr.model.ProfileNode.subTypes.getConstructor(undefined,sampleType);if(data.vm_state==='js'&&!data.stack.length)return;const profileTree=this.getOrCreateProfileTree_(sampleType,event.pid);if(profileTree.getNode(-1)===undefined){profileTree.add(new ProfileNodeType(-1,{url:'',scriptId:-1,functionName:'unknown'},undefined));}
 let node=undefined;if(data.stack.length>0&&this.v8ProcessCodeMaps_[event.pid]){const map=this.v8ProcessCodeMaps_[event.pid];data.stack.reverse();let parentNode=undefined;for(let i=0;i<data.stack.length;i++){const entry=map.lookupEntry(data.stack[i]);if(entry===undefined){node=profileTree.getNode(-1);}else{node=profileTree.getNode(entry.id);if(node===undefined){const sourceInfo=entry.sourceInfo;node=new ProfileNodeType(entry.id,{functionName:entry.name,url:entry.sourceInfo.file,lineNumber:sourceInfo.line!==-1?sourceInfo.line:undefined,columnNumber:sourceInfo.column!==-1?sourceInfo.column:undefined,scriptid:entry.sourceInfo.scriptId},parentNode);profileTree.add(node);}}
@@ -6066,7 +6066,10 @@
 const kswapdWakeRE=/nid=(\d+) order=(\d+)/;const kswapdSleepRE=/nid=(\d+)/;const reclaimBeginRE=/order=(\d+) may_writepage=\d+ gfp_flags=(.+)/;const reclaimEndRE=/nr_reclaimed=(\d+)/;const lowmemoryRE=/([^ ]+) \((\d+)\), page cache (\d+)kB \(limit (\d+)kB\), free (-?\d+)Kb/;MemReclaimParser.prototype={__proto__:Parser.prototype,kswapdWake(eventName,cpuNumber,pid,ts,eventBase){const event=kswapdWakeRE.exec(eventBase.details);if(!event)return false;const tgid=parseInt(eventBase.tgid);const nid=parseInt(event[1]);const order=parseInt(event[2]);const kthread=this.importer.getOrCreateKernelThread(eventBase.threadName,tgid,pid);if(kthread.openSliceTS){if(order>kthread.order){kthread.order=order;}}else{kthread.openSliceTS=ts;kthread.order=order;}
 kthread.waitingFor='kswapSleep';return true;},kswapdSleep(eventName,cpuNumber,pid,ts,eventBase){const tgid=parseInt(eventBase.tgid);const kthread=this.importer.getOrCreateKernelThread(eventBase.threadName,tgid,pid);if(kthread.waitingFor!=='kswapSleep')return false;kthread.waitingFor=undefined;if(kthread.openSliceTS){kthread.thread.sliceGroup.pushCompleteSlice('memreclaim',eventBase.threadName,kthread.openSliceTS,ts-kthread.openSliceTS,0,0,{order:kthread.order});}
 kthread.openSliceTS=undefined;kthread.order=undefined;return true;},reclaimBegin(eventName,cpuNumber,pid,ts,eventBase){const event=reclaimBeginRE.exec(eventBase.details);if(!event)return false;const order=parseInt(event[1]);const gfp=event[2];const tgid=parseInt(eventBase.tgid);const kthread=this.importer.getOrCreateKernelThread(eventBase.threadName,tgid,pid);kthread.openMemReclaimSliceTS=ts;kthread.order=order;kthread.gfp=gfp;kthread.waitingFor='reclaimEnd';return true;},reclaimEnd(eventName,cpuNumber,pid,ts,eventBase){const event=reclaimEndRE.exec(eventBase.details);if(!event)return false;const nrReclaimed=parseInt(event[1]);const tgid=parseInt(eventBase.tgid);const kthread=this.importer.getOrCreateKernelThread(eventBase.threadName,tgid,pid);if(kthread.waitingFor!=='reclaimEnd')return false;kthread.waitingFor=undefined;if(kthread.openMemReclaimSliceTS!==undefined){kthread.thread.sliceGroup.pushCompleteSlice('memreclaim','direct reclaim',kthread.openMemReclaimSliceTS,ts-kthread.openMemReclaimSliceTS,0,0,{order:kthread.order,gfp:kthread.gfp,nr_reclaimed:nrReclaimed});kthread.openMemReclaimSliceTS=undefined;kthread.order=undefined;kthread.gfp=undefined;return true;}
-return false;},lowmemoryKill(eventName,cpuNumber,pid,ts,eventBase){const event=lowmemoryRE.exec(eventBase.details);if(!event)return false;const tgid=parseInt(eventBase.tgid);const killedName=event[1];const killedPid=parseInt(event[2]);const cache=parseInt(event[3]);const free=parseInt(event[5]);const kthread=this.importer.getOrCreateKernelThread(eventBase.threadName,tgid,pid);kthread.thread.sliceGroup.pushCompleteSlice('lowmemory','low memory kill',ts,0,0,0,{killed_name:killedName,killed_pid:killedPid,cache,free});return true;}};Parser.register(MemReclaimParser);return{MemReclaimParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function PowerParser(importer){Parser.call(this,importer);importer.registerEventHandler('power_start',PowerParser.prototype.powerStartEvent.bind(this));importer.registerEventHandler('power_frequency',PowerParser.prototype.powerFrequencyEvent.bind(this));importer.registerEventHandler('cpu_frequency',PowerParser.prototype.cpuFrequencyEvent.bind(this));importer.registerEventHandler('cpu_frequency_limits',PowerParser.prototype.cpuFrequencyLimitsEvent.bind(this));importer.registerEventHandler('cpu_idle',PowerParser.prototype.cpuIdleEvent.bind(this));}
+return false;},lowmemoryKill(eventName,cpuNumber,pid,ts,eventBase){const event=lowmemoryRE.exec(eventBase.details);if(!event)return false;const tgid=parseInt(eventBase.tgid);const killedName=event[1];const killedPid=parseInt(event[2]);const cache=parseInt(event[3]);const free=parseInt(event[5]);const kthread=this.importer.getOrCreateKernelThread(eventBase.threadName,tgid,pid);kthread.thread.sliceGroup.pushCompleteSlice('lowmemory','low memory kill',ts,0,0,0,{killed_name:killedName,killed_pid:killedPid,cache,free});return true;}};Parser.register(MemReclaimParser);return{MemReclaimParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function MSMParser(importer){Parser.call(this,importer);importer.registerEventHandler('msm_gpu_freq_change',MSMParser.prototype.gpuFrequency.bind(this));importer.registerEventHandler('msm_gpu_submit_flush',MSMParser.prototype.gpuSubmitFlush.bind(this));importer.registerEventHandler('msm_gpu_submit_retired',MSMParser.prototype.gpuSubmitRetired.bind(this));this.model_=importer.model_;this.submits={};this.num_submits=0;}
+MSMParser.prototype={__proto__:Parser.prototype,gpuFrequency(eventName,cpuNumber,pid,ts,eventBase){const event=/new_freq=(\d+)/.exec(eventBase.details);if(!event)return false;const freq=parseInt(event[1]);const counter=this.model_.kernel.getOrCreateCounter('GPU','GPU Frequency');if(counter.numSeries===0){counter.addSeries(new tr.model.CounterSeries('frequency',ColorScheme.getColorIdForGeneralPurposeString(counter.name)));}
+counter.series.forEach(function(series){series.addCounterSample(ts,freq);});return true;},gpuSubmitFlush(eventName,cpuNumber,pid,ts,eventBase){const event=/id=(\d+) pid=(\d+) ring=(\d+):(\d+) ticks=(\d+)/.exec(eventBase.details);if(!event)return false;const id=parseInt(event[1]);const submit={};submit.flushTS=ts;submit.flushTicks=parseInt(event[5]);submit.pid=parseInt(event[2]);this.submits[id]=submit;this.num_submits++;return true;},gpuSubmitRetired(eventName,cpuNumber,pid,ts,eventBase){const event=/id=(\d+) pid=(\d+) ring=(\d+):(\d+) elapsed=(\d+) ns mhz=(\d+) start=(\d+) end=(\d+)/.exec(eventBase.details);if(!event)return false;const id=parseInt(event[1]);if(!(id in this.submits))return true;const submit=this.submits[id];delete this.submits[id];this.num_submits--;const gpuThread=this.importer.getOrCreatePseudoThread('GPU');submit.elapsedNs=parseInt(event[5]);submit.mhz=parseInt(event[6]);submit.startTicks=parseInt(event[7]);submit.endTicks=parseInt(event[8]);function ticks2ms(ticks){return ticks/19200;}
+const queuedDuration=ticks2ms(submit.startTicks-submit.flushTicks);const runningDuration=ticks2ms(submit.endTicks-submit.startTicks);submit.queuedDuration=queuedDuration;submit.runningDuration=runningDuration;const queued=new tr.model.AsyncSlice('',event[1]+' queued',tr.b.ColorScheme.getColorIdForReservedName('thread_state_runnable'),submit.flushTS,submit,queuedDuration);const running=new tr.model.AsyncSlice('',event[1]+' running',tr.b.ColorScheme.getColorIdForReservedName('thread_state_running'),submit.flushTS+queuedDuration,submit,runningDuration);const async=new tr.model.AsyncSlice('','pipeline',ColorScheme.getColorIdForGeneralPurposeString('ongpu:'+submit.pid),submit.flushTS,submit,queuedDuration+runningDuration);async.hidden=true;async.subSlices.push(queued);async.subSlices.push(running);gpuThread.thread.asyncSliceGroup.push(async);const onGpu=new tr.model.ThreadSlice('',event[1],ColorScheme.getColorIdForGeneralPurposeString('ongpu:'+submit.pid),submit.flushTS+queuedDuration,submit,runningDuration);gpuThread.thread.sliceGroup.pushSlice(onGpu);return true;}};Parser.register(MSMParser);return{MSMParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function PowerParser(importer){Parser.call(this,importer);importer.registerEventHandler('power_start',PowerParser.prototype.powerStartEvent.bind(this));importer.registerEventHandler('power_frequency',PowerParser.prototype.powerFrequencyEvent.bind(this));importer.registerEventHandler('cpu_frequency',PowerParser.prototype.cpuFrequencyEvent.bind(this));importer.registerEventHandler('cpu_frequency_limits',PowerParser.prototype.cpuFrequencyLimitsEvent.bind(this));importer.registerEventHandler('cpu_idle',PowerParser.prototype.cpuIdleEvent.bind(this));}
 PowerParser.prototype={__proto__:Parser.prototype,cpuStateSlice(ts,targetCpuNumber,eventType,cpuState){const targetCpu=this.importer.getOrCreateCpu(targetCpuNumber);if(eventType!=='1'){this.importer.model.importWarning({type:'parse_error',message:'Don\'t understand power_start events of '+'type '+eventType});return;}
 const powerCounter=targetCpu.getOrCreateCounter('','C-State');if(powerCounter.numSeries===0){powerCounter.addSeries(new tr.model.CounterSeries('state',ColorScheme.getColorIdForGeneralPurposeString(powerCounter.name+'.'+'state')));}
 powerCounter.series.forEach(function(series){series.addCounterSample(ts,cpuState);});},cpuIdleSlice(ts,targetCpuNumber,cpuState){const targetCpu=this.importer.getOrCreateCpu(targetCpuNumber);const powerCounter=targetCpu.getOrCreateCounter('','C-State');if(powerCounter.numSeries===0){powerCounter.addSeries(new tr.model.CounterSeries('state',ColorScheme.getColorIdForGeneralPurposeString(powerCounter.name)));}
@@ -6472,8 +6475,7 @@
 function scrollIntoViewIfNeeded(el){const pr=el.parentElement.getBoundingClientRect();const cr=el.getBoundingClientRect();if(cr.top<pr.top){el.scrollIntoView(true);}else if(cr.bottom>pr.bottom){el.scrollIntoView(false);}}
 function extractUrlString(url){let extracted=url.replace(/url\((.*)\)/,'$1');extracted=extracted.replace(/\"(.*)\"/,'$1');return extracted;}
 function toThreeDigitLocaleString(value){return value.toLocaleString(undefined,{minimumFractionDigits:3,maximumFractionDigits:3});}
-function isUnknownElementName(name){return document.createElement(name)instanceof HTMLUnknownElement;}
-return{isUnknownElementName,toThreeDigitLocaleString,instantiateTemplate,windowRectForElement,scrollIntoViewIfNeeded,extractUrlString,};});'use strict';tr.exportTo('tr.ui.b',function(){if(tr.isHeadless)return{};const THIS_DOC=document._currentScript.ownerDocument;const Overlay=tr.ui.b.define('overlay');Overlay.prototype={__proto__:HTMLDivElement.prototype,decorate(){Polymer.dom(this).classList.add('overlay');this.parentEl_=this.ownerDocument.body;this.visible_=false;this.userCanClose_=true;this.onKeyDown_=this.onKeyDown_.bind(this);this.onClick_=this.onClick_.bind(this);this.onFocusIn_=this.onFocusIn_.bind(this);this.onDocumentClick_=this.onDocumentClick_.bind(this);this.onClose_=this.onClose_.bind(this);this.addEventListener('visible-change',tr.ui.b.Overlay.prototype.onVisibleChange_.bind(this),true);const createShadowRoot=this.createShadowRoot||this.webkitCreateShadowRoot;this.shadow_=createShadowRoot.call(this);Polymer.dom(this.shadow_).appendChild(tr.ui.b.instantiateTemplate('#overlay-template',THIS_DOC));this.closeBtn_=Polymer.dom(this.shadow_).querySelector('close-button');this.closeBtn_.addEventListener('click',this.onClose_);Polymer.dom(this.shadow_).querySelector('overlay-frame').addEventListener('click',this.onClick_);this.observer_=new MutationObserver(this.didButtonBarMutate_.bind(this));this.observer_.observe(Polymer.dom(this.shadow_).querySelector('button-bar'),{childList:true});Object.defineProperty(this,'title',{get(){return Polymer.dom(Polymer.dom(this.shadow_).querySelector('title')).textContent;},set(title){Polymer.dom(Polymer.dom(this.shadow_).querySelector('title')).textContent=title;}});},set userCanClose(userCanClose){this.userCanClose_=userCanClose;this.closeBtn_.style.display=userCanClose?'block':'none';},get buttons(){return Polymer.dom(this.shadow_).querySelector('button-bar');},get visible(){return this.visible_;},set visible(newValue){if(this.visible_===newValue)return;this.visible_=newValue;const e=new tr.b.Event('visible-change');this.dispatchEvent(e);},onVisibleChange_(){this.visible_?this.show_():this.hide_();},show_(){Polymer.dom(this.parentEl_).appendChild(this);if(this.userCanClose_){this.addEventListener('keydown',this.onKeyDown_.bind(this));this.addEventListener('click',this.onDocumentClick_.bind(this));this.closeBtn_.addEventListener('click',this.onClose_);}
+return{toThreeDigitLocaleString,instantiateTemplate,windowRectForElement,scrollIntoViewIfNeeded,extractUrlString,};});'use strict';tr.exportTo('tr.ui.b',function(){if(tr.isHeadless)return{};const THIS_DOC=document._currentScript.ownerDocument;const Overlay=tr.ui.b.define('overlay');Overlay.prototype={__proto__:HTMLDivElement.prototype,decorate(){Polymer.dom(this).classList.add('overlay');this.parentEl_=this.ownerDocument.body;this.visible_=false;this.userCanClose_=true;this.onKeyDown_=this.onKeyDown_.bind(this);this.onClick_=this.onClick_.bind(this);this.onFocusIn_=this.onFocusIn_.bind(this);this.onDocumentClick_=this.onDocumentClick_.bind(this);this.onClose_=this.onClose_.bind(this);this.addEventListener('visible-change',tr.ui.b.Overlay.prototype.onVisibleChange_.bind(this),true);const createShadowRoot=this.createShadowRoot||this.webkitCreateShadowRoot;this.shadow_=createShadowRoot.call(this);Polymer.dom(this.shadow_).appendChild(tr.ui.b.instantiateTemplate('#overlay-template',THIS_DOC));this.closeBtn_=Polymer.dom(this.shadow_).querySelector('close-button');this.closeBtn_.addEventListener('click',this.onClose_);Polymer.dom(this.shadow_).querySelector('overlay-frame').addEventListener('click',this.onClick_);this.observer_=new MutationObserver(this.didButtonBarMutate_.bind(this));this.observer_.observe(Polymer.dom(this.shadow_).querySelector('button-bar'),{childList:true});Object.defineProperty(this,'title',{get(){return Polymer.dom(Polymer.dom(this.shadow_).querySelector('title')).textContent;},set(title){Polymer.dom(Polymer.dom(this.shadow_).querySelector('title')).textContent=title;}});},set userCanClose(userCanClose){this.userCanClose_=userCanClose;this.closeBtn_.style.display=userCanClose?'block':'none';},get buttons(){return Polymer.dom(this.shadow_).querySelector('button-bar');},get visible(){return this.visible_;},set visible(newValue){if(this.visible_===newValue)return;this.visible_=newValue;const e=new tr.b.Event('visible-change');this.dispatchEvent(e);},onVisibleChange_(){this.visible_?this.show_():this.hide_();},show_(){Polymer.dom(this.parentEl_).appendChild(this);if(this.userCanClose_){this.addEventListener('keydown',this.onKeyDown_.bind(this));this.addEventListener('click',this.onDocumentClick_.bind(this));this.closeBtn_.addEventListener('click',this.onClose_);}
 this.parentEl_.addEventListener('focusin',this.onFocusIn_);this.tabIndex=0;const elList=Polymer.dom(this).querySelectorAll('button, input, list, select, a');if(elList.length>0){if(elList[0]===this.closeBtn_){if(elList.length>1)return elList[1].focus();}else{return elList[0].focus();}}
 this.focus();},hide_(){Polymer.dom(this.parentEl_).removeChild(this);this.parentEl_.removeEventListener('focusin',this.onFocusIn_);if(this.closeBtn_){this.closeBtn_.removeEventListener('click',this.onClose_);}
 document.removeEventListener('keydown',this.onKeyDown_);document.removeEventListener('click',this.onDocumentClick_);},onClose_(e){this.visible=false;if((e.type!=='keydown')||(e.type==='keydown'&&e.keyCode===27)){e.stopPropagation();}
@@ -7090,7 +7092,7 @@
 static deserialize(data,deserializer){return new UnmergeableDiagnosticSet(d.map(i=>deserializer.getDiagnostic(i).diagnostic));}
 serialize(serializer){return this._diagnostics.map(d=>serializer.getOrAllocateDiagnosticId('',d));}
 static fromDict(d){return new UnmergeableDiagnosticSet(d.diagnostics.map(d=>((typeof d==='string')?new tr.v.d.DiagnosticRef(d):tr.v.d.Diagnostic.fromDict(d))));}}
-tr.v.d.Diagnostic.register(UnmergeableDiagnosticSet,{elementName:'tr-v-ui-unmergeable-diagnostic-set-span'});return{UnmergeableDiagnosticSet,};});'use strict';tr.exportTo('tr.v.d',function(){const RESERVED_INFOS={ANGLE_REVISIONS:{name:'angleRevisions',type:tr.v.d.GenericSet},ARCHITECTURES:{name:'architectures',type:tr.v.d.GenericSet},BENCHMARKS:{name:'benchmarks',type:tr.v.d.GenericSet},BENCHMARK_START:{name:'benchmarkStart',type:tr.v.d.DateRange},BENCHMARK_DESCRIPTIONS:{name:'benchmarkDescriptions',type:tr.v.d.GenericSet},BOTS:{name:'bots',type:tr.v.d.GenericSet},BUG_COMPONENTS:{name:'bugComponents',type:tr.v.d.GenericSet},BUILDS:{name:'builds',type:tr.v.d.GenericSet},CATAPULT_REVISIONS:{name:'catapultRevisions',type:tr.v.d.GenericSet},CHROMIUM_COMMIT_POSITIONS:{name:'chromiumCommitPositions',type:tr.v.d.GenericSet},CHROMIUM_REVISIONS:{name:'chromiumRevisions',type:tr.v.d.GenericSet},DESCRIPTION:{name:'description',type:tr.v.d.GenericSet},DEVICE_IDS:{name:'deviceIds',type:tr.v.d.GenericSet},DOCUMENTATION_URLS:{name:'documentationUrls',type:tr.v.d.GenericSet},INFO_BLURB:{name:'infoBlurb',type:tr.v.d.GenericSet},FUCHSIA_GARNET_REVISIONS:{name:'fuchsiaGarnetRevisions',type:tr.v.d.GenericSet},FUCHSIA_PERIDOT_REVISIONS:{name:'fuchsiaPeridotRevisions',type:tr.v.d.GenericSet},FUCHSIA_TOPAZ_REVISIONS:{name:'fuchsiaTopazRevisions',type:tr.v.d.GenericSet},FUCHSIA_ZIRCON_REVISIONS:{name:'fuchsiaZirconRevisions',type:tr.v.d.GenericSet},GPUS:{name:'gpus',type:tr.v.d.GenericSet},IS_REFERENCE_BUILD:{name:'isReferenceBuild',type:tr.v.d.GenericSet},LABELS:{name:'labels',type:tr.v.d.GenericSet},LOG_URLS:{name:'logUrls',type:tr.v.d.GenericSet},MASTERS:{name:'masters',type:tr.v.d.GenericSet},MEMORY_AMOUNTS:{name:'memoryAmounts',type:tr.v.d.GenericSet},OS_NAMES:{name:'osNames',type:tr.v.d.GenericSet},OS_VERSIONS:{name:'osVersions',type:tr.v.d.GenericSet},OWNERS:{name:'owners',type:tr.v.d.GenericSet},POINT_ID:{name:'pointId',type:tr.v.d.GenericSet},PRODUCT_VERSIONS:{name:'productVersions',type:tr.v.d.GenericSet},REVISION_TIMESTAMPS:{name:'revisionTimestamps',type:tr.v.d.DateRange},SKIA_REVISIONS:{name:'skiaRevisions',type:tr.v.d.GenericSet},STATISTICS_NAMES:{name:'statisticsNames',type:tr.v.d.GenericSet},STORIES:{name:'stories',type:tr.v.d.GenericSet},STORYSET_REPEATS:{name:'storysetRepeats',type:tr.v.d.GenericSet},STORY_TAGS:{name:'storyTags',type:tr.v.d.GenericSet},SUMMARY_KEYS:{name:'summaryKeys',type:tr.v.d.GenericSet},TEST_PATH:{name:'testPath',type:tr.v.d.GenericSet},TRACE_START:{name:'traceStart',type:tr.v.d.DateRange},TRACE_URLS:{name:'traceUrls',type:tr.v.d.GenericSet},V8_COMMIT_POSITIONS:{name:'v8CommitPositions',type:tr.v.d.DateRange},V8_REVISIONS:{name:'v8Revisions',type:tr.v.d.GenericSet},WEBRTC_REVISIONS:{name:'webrtcRevisions',type:tr.v.d.GenericSet},WEBRTC_INTERNAL_REVISIONS:{name:'webrtcInternalRevisions',type:tr.v.d.GenericSet},};const RESERVED_NAMES={};const RESERVED_NAMES_TO_TYPES=new Map();for(const[codename,info]of Object.entries(RESERVED_INFOS)){RESERVED_NAMES[codename]=info.name;if(RESERVED_NAMES_TO_TYPES.has(info.name)){throw new Error(`Duplicate reserved name "${info.name}"`);}
+tr.v.d.Diagnostic.register(UnmergeableDiagnosticSet,{elementName:'tr-v-ui-unmergeable-diagnostic-set-span'});return{UnmergeableDiagnosticSet,};});'use strict';tr.exportTo('tr.v.d',function(){const RESERVED_INFOS={ALERT_GROUPING:{name:'alertGrouping',type:tr.v.d.GenericSet},ANGLE_REVISIONS:{name:'angleRevisions',type:tr.v.d.GenericSet},ARCHITECTURES:{name:'architectures',type:tr.v.d.GenericSet},BENCHMARKS:{name:'benchmarks',type:tr.v.d.GenericSet},BENCHMARK_START:{name:'benchmarkStart',type:tr.v.d.DateRange},BENCHMARK_DESCRIPTIONS:{name:'benchmarkDescriptions',type:tr.v.d.GenericSet},BOTS:{name:'bots',type:tr.v.d.GenericSet},BUG_COMPONENTS:{name:'bugComponents',type:tr.v.d.GenericSet},BUILDS:{name:'builds',type:tr.v.d.GenericSet},CATAPULT_REVISIONS:{name:'catapultRevisions',type:tr.v.d.GenericSet},CHROMIUM_COMMIT_POSITIONS:{name:'chromiumCommitPositions',type:tr.v.d.GenericSet},CHROMIUM_REVISIONS:{name:'chromiumRevisions',type:tr.v.d.GenericSet},DESCRIPTION:{name:'description',type:tr.v.d.GenericSet},DEVICE_IDS:{name:'deviceIds',type:tr.v.d.GenericSet},DOCUMENTATION_URLS:{name:'documentationLinks',type:tr.v.d.GenericSet},INFO_BLURB:{name:'infoBlurb',type:tr.v.d.GenericSet},FUCHSIA_GARNET_REVISIONS:{name:'fuchsiaGarnetRevisions',type:tr.v.d.GenericSet},FUCHSIA_PERIDOT_REVISIONS:{name:'fuchsiaPeridotRevisions',type:tr.v.d.GenericSet},FUCHSIA_TOPAZ_REVISIONS:{name:'fuchsiaTopazRevisions',type:tr.v.d.GenericSet},FUCHSIA_ZIRCON_REVISIONS:{name:'fuchsiaZirconRevisions',type:tr.v.d.GenericSet},GPUS:{name:'gpus',type:tr.v.d.GenericSet},IS_REFERENCE_BUILD:{name:'isReferenceBuild',type:tr.v.d.GenericSet},LABELS:{name:'labels',type:tr.v.d.GenericSet},LOG_URLS:{name:'logUrls',type:tr.v.d.GenericSet},MASTERS:{name:'masters',type:tr.v.d.GenericSet},MEMORY_AMOUNTS:{name:'memoryAmounts',type:tr.v.d.GenericSet},OS_NAMES:{name:'osNames',type:tr.v.d.GenericSet},OS_VERSIONS:{name:'osVersions',type:tr.v.d.GenericSet},OWNERS:{name:'owners',type:tr.v.d.GenericSet},POINT_ID:{name:'pointId',type:tr.v.d.GenericSet},PRODUCT_VERSIONS:{name:'productVersions',type:tr.v.d.GenericSet},REVISION_TIMESTAMPS:{name:'revisionTimestamps',type:tr.v.d.DateRange},SKIA_REVISIONS:{name:'skiaRevisions',type:tr.v.d.GenericSet},STATISTICS_NAMES:{name:'statisticsNames',type:tr.v.d.GenericSet},STORIES:{name:'stories',type:tr.v.d.GenericSet},STORYSET_REPEATS:{name:'storysetRepeats',type:tr.v.d.GenericSet},STORY_TAGS:{name:'storyTags',type:tr.v.d.GenericSet},SUMMARY_KEYS:{name:'summaryKeys',type:tr.v.d.GenericSet},TEST_PATH:{name:'testPath',type:tr.v.d.GenericSet},TRACE_START:{name:'traceStart',type:tr.v.d.DateRange},TRACE_URLS:{name:'traceUrls',type:tr.v.d.GenericSet},V8_COMMIT_POSITIONS:{name:'v8CommitPositions',type:tr.v.d.DateRange},V8_REVISIONS:{name:'v8Revisions',type:tr.v.d.GenericSet},WEBRTC_REVISIONS:{name:'webrtcRevisions',type:tr.v.d.GenericSet},WEBRTC_INTERNAL_REVISIONS:{name:'webrtcInternalRevisions',type:tr.v.d.GenericSet},};const RESERVED_NAMES={};const RESERVED_NAMES_TO_TYPES=new Map();for(const[codename,info]of Object.entries(RESERVED_INFOS)){RESERVED_NAMES[codename]=info.name;if(RESERVED_NAMES_TO_TYPES.has(info.name)){throw new Error(`Duplicate reserved name "${info.name}"`);}
 RESERVED_NAMES_TO_TYPES.set(info.name,info.type);}
 const RESERVED_NAMES_SET=new Set(Object.values(RESERVED_NAMES));return{RESERVED_INFOS,RESERVED_NAMES,RESERVED_NAMES_SET,RESERVED_NAMES_TO_TYPES,};});'use strict';tr.exportTo('tr.v.d',function(){class DiagnosticMap extends Map{constructor(opt_allowReservedNames){super();if(opt_allowReservedNames===undefined){opt_allowReservedNames=true;}
 this.allowReservedNames_=opt_allowReservedNames;}
@@ -7134,13 +7136,17 @@
 const DEFAULT_SUMMARY_OPTIONS=new Map([['avg',true],['count',true],['geometricMean',false],['max',true],['min',true],['nans',false],['std',true],['sum',true],]);class Histogram{constructor(name,unit,opt_binBoundaries){if(!(unit instanceof tr.b.Unit)){throw new Error('unit must be a Unit: '+unit);}
 let binBoundaries=opt_binBoundaries;if(!binBoundaries){const baseUnit=unit.baseUnit?unit.baseUnit:unit;binBoundaries=DEFAULT_BOUNDARIES_FOR_UNIT.get(baseUnit.unitName);}
 this.binBoundariesDict_=binBoundaries.asDict();this.allBins=binBoundaries.bins.slice();this.description='';const allowReservedNames=false;this.diagnostics_=new tr.v.d.DiagnosticMap(allowReservedNames);this.maxNumSampleValues_=this.defaultMaxNumSampleValues_;this.name_=name;this.nanDiagnosticMaps=[];this.numNans=0;this.running_=undefined;this.sampleValues_=[];this.sampleMeans_=[];this.summaryOptions=new Map(DEFAULT_SUMMARY_OPTIONS);this.summaryOptions.set('percentile',[]);this.summaryOptions.set('iprs',[]);this.summaryOptions.set('ci',[]);this.unit=unit;}
-static create(name,unit,samples,opt_options){const options=opt_options||{};const hist=new Histogram(name,unit,options.binBoundaries);if(options.description)hist.description=options.description;if(options.summaryOptions){let summaryOptions=options.summaryOptions;if(!(summaryOptions instanceof Map)){summaryOptions=Object.entries(summaryOptions);}
+static create(name,unit,samples,opt_options){const options=opt_options||{};const hist=new Histogram(name,unit,options.binBoundaries);if(options.alertGrouping!==undefined){hist.setAlertGrouping(options.alertGrouping);}
+if(options.description)hist.description=options.description;if(options.summaryOptions){let summaryOptions=options.summaryOptions;if(!(summaryOptions instanceof Map)){summaryOptions=Object.entries(summaryOptions);}
 for(const[name,value]of summaryOptions){hist.summaryOptions.set(name,value);}}
 if(options.diagnostics!==undefined){let diagnostics=options.diagnostics;if(!(diagnostics instanceof Map)){diagnostics=Object.entries(diagnostics);}
 for(const[name,diagnostic]of diagnostics){if(!diagnostic)continue;hist.diagnostics.set(name,diagnostic);}}
 if(!(samples instanceof Array))samples=[samples];for(const sample of samples){if(typeof sample==='object'){hist.addSample(sample.value,sample.diagnostics);}else{hist.addSample(sample);}}
 return hist;}
 get diagnostics(){return this.diagnostics_;}
+setAlertGrouping(alertGrouping){if(alertGrouping===undefined||alertGrouping===null||alertGrouping.length===undefined){throw Error('alertGrouping must be an array');}
+for(const alertGroup of alertGrouping){if(!Object.values(tr.v.d.ALERT_GROUPS).includes(alertGroup)){throw Error(`Alert group ${alertGroup} must be added to `+'/tracing/value/diagnostics/alert_groups.html');}}
+this.diagnostics.set(tr.v.d.RESERVED_NAMES.ALERT_GROUPING,new tr.v.d.GenericSet(alertGrouping));}
 get running(){return this.running_;}
 get maxNumSampleValues(){return this.maxNumSampleValues_;}
 set maxNumSampleValues(n){this.maxNumSampleValues_=n;tr.b.math.Statistics.uniformlySampleArray(this.sampleValues_,this.maxNumSampleValues_);}
@@ -7362,7 +7368,7 @@
 Polymer({is:'tr-ui-a-generic-object-view',ready(){this.object_=undefined;},get object(){return this.object_;},set object(object){this.object_=object;this.updateContents_();},updateContents_(){Polymer.dom(this.$.content).textContent='';this.appendElementsForType_('',this.object_,0,0,5,'');},appendElementsForType_(label,object,indent,depth,maxDepth,suffix){if(depth>maxDepth){this.appendSimpleText_(label,indent,'<recursion limit reached>',suffix);return;}
 if(object===undefined){this.appendSimpleText_(label,indent,'undefined',suffix);return;}
 if(object===null){this.appendSimpleText_(label,indent,'null',suffix);return;}
-if(!(object instanceof Object)){const type=typeof object;if(type!=='string'){return this.appendSimpleText_(label,indent,object,suffix);}
+if(!(object instanceof Object)){const type=typeof object;if(type!=='string'){return this.appendSimpleText_(label,indent,String(object),suffix);}
 let objectReplaced=false;if((object[0]==='{'&&object[object.length-1]==='}')||(object[0]==='['&&object[object.length-1]===']')){try{object=JSON.parse(object);objectReplaced=true;}catch(e){}}
 if(!objectReplaced){if(object.includes('\n')){const lines=object.split('\n');lines.forEach(function(line,i){let text;let ioff;let ll;let ss;if(i===0){text='"'+line;ioff=0;ll=label;ss='';}else if(i<lines.length-1){text=line;ioff=1;ll='';ss='';}else{text=line+'"';ioff=1;ll='';ss=suffix;}
 const el=this.appendSimpleText_(ll,indent+ioff*label.length+ioff,text,ss);el.style.whiteSpace='pre';return el;},this);return;}
@@ -7700,7 +7706,8 @@
 if(event.category){rows.push({name:'Category',value:event.category});}
 if(event.model!==undefined){const ufc=event.model.getUserFriendlyCategoryFromEvent(event);if(ufc!==undefined){rows.push({name:'User Friendly Category',value:ufc});}}
 if(event.name){rows.push({name:'Name',value:event.name});}
-rows.push({name:'Start',value:tr.v.ui.createScalarSpan(event.start,{unit:tr.b.Unit.byName.timeStampInMs})});if(event.duration){rows.push({name:'Wall Duration',value:tr.v.ui.createScalarSpan(event.duration,{unit:tr.b.Unit.byName.timeDurationInMs})});}
+rows.push({name:'Start',value:tr.v.ui.createScalarSpan(event.start,{unit:tr.b.Unit.byName.timeStampInMs})});if(event.category==='android'&&event.model!==undefined){rows.push({name:'Start (Absolute time)',value:tr.v.ui.createScalarSpan(event.model.convertTimestampFromModelTime('traceEventClock',event.start),{unit:tr.b.Unit.byName.timeStampInMs})});}
+if(event.duration){rows.push({name:'Wall Duration',value:tr.v.ui.createScalarSpan(event.duration,{unit:tr.b.Unit.byName.timeDurationInMs})});}
 if(event.cpuDuration){rows.push({name:'CPU Duration',value:tr.v.ui.createScalarSpan(event.cpuDuration,{unit:tr.b.Unit.byName.timeDurationInMs})});}
 if(event.subSlices!==undefined&&event.subSlices.length!==0){if(event.selfTime){rows.push({name:'Self Time',value:tr.v.ui.createScalarSpan(event.selfTime,{unit:tr.b.Unit.byName.timeDurationInMs})});}
 if(event.cpuSelfTime){const cpuSelfTimeEl=tr.v.ui.createScalarSpan(event.cpuSelfTime,{unit:tr.b.Unit.byName.timeDurationInMs});if(event.cpuSelfTime>event.selfTime){cpuSelfTimeEl.warning=' Note that CPU Self Time is larger than Self Time. '+'This is a known limitation of this system, which occurs '+'due to several subslices, rounding issues, and imprecise '+'time at which we get cpu- and real-time.';}
@@ -8064,21 +8071,21 @@
 if(slice.title==='RenderAccessibilityImpl::SendLocationChanges'){renderAccessibilityLocationsHist.addSample(slice.duration,{event:new tr.v.d.RelatedEventSet(slice)});}}}
 for(const browserHelper of Object.values(chromeHelper.browserHelpers)){const mainThread=browserHelper.mainThread;if(mainThread===undefined)continue;for(const slice of mainThread.getDescendantEvents()){if(slice.title==='BrowserAccessibilityManager::OnAccessibilityEvents'){browserAccessibilityEventsHist.addSample(slice.duration,{event:new tr.v.d.RelatedEventSet(slice)});}}}
 histograms.addHistogram(browserAccessibilityEventsHist);histograms.addHistogram(renderAccessibilityEventsHist);histograms.addHistogram(renderAccessibilityLocationsHist);}
-tr.metrics.MetricRegistry.register(accessibilityMetric);return{accessibilityMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const MESSAGE_LOOP_EVENT_NAME='Startup.BrowserMessageLoopStartTime';const CONTENT_START_EVENT_NAME='content::Start';const NAVIGATION_EVENT_NAME='Navigation StartToCommit';const FIRST_CONTENTFUL_PAINT_EVENT_NAME='firstContentfulPaint';function androidStartupMetric(histograms,model){let messageLoopStartEvents=[];let navigationEvents=[];const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;for(const helper of chromeHelper.browserHelpers){for(const ev of helper.mainThread.asyncSliceGroup.childEvents()){if(ev.title===MESSAGE_LOOP_EVENT_NAME){messageLoopStartEvents.push(ev);}else if(ev.title===NAVIGATION_EVENT_NAME){navigationEvents.push(ev);}}}
+tr.metrics.MetricRegistry.register(accessibilityMetric);return{accessibilityMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const MESSAGE_LOOP_EVENT_NAME='Startup.BrowserMessageLoopStartTime';const CONTENT_START_EVENT_NAME='content::Start';const NAVIGATION_EVENT_NAME='Navigation StartToCommit';const FIRST_CONTENTFUL_PAINT_EVENT_NAME='firstContentfulPaint';const APPLICATION_START_EVENT_NAME='Startup.LoadTime.ProcessCreateToApplicationStart';function androidStartupMetric(histograms,model){let messageLoopStartEvents=[];let applicationStartEvents=[];let navigationEvents=[];const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;for(const helper of chromeHelper.browserHelpers){for(const ev of helper.mainThread.asyncSliceGroup.childEvents()){if(ev.title===MESSAGE_LOOP_EVENT_NAME){messageLoopStartEvents.push(ev);}else if(ev.title===APPLICATION_START_EVENT_NAME){applicationStartEvents.push(ev);}else if(ev.title===NAVIGATION_EVENT_NAME){navigationEvents.push(ev);}}}
 let contentStartEvents=[];let firstContentfulPaintEvents=[];const rendererHelpers=chromeHelper.rendererHelpers;const pids=Object.keys(rendererHelpers);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){if(!rendererHelper.mainThread)continue;for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(ev.title===FIRST_CONTENTFUL_PAINT_EVENT_NAME){firstContentfulPaintEvents.push(ev);break;}else if(ev.title===CONTENT_START_EVENT_NAME){contentStartEvents.push(ev);}}}
-let totalBrowserStarts=messageLoopStartEvents.length;let totalContentStartEvents=contentStartEvents.length;let totalFcpEvents=firstContentfulPaintEvents.length;let totalNavigations=navigationEvents.length;if(totalFcpEvents!==totalBrowserStarts||totalNavigations!==totalBrowserStarts||totalContentStartEvents!==totalBrowserStarts||totalBrowserStarts===0){messageLoopStartEvents=[];contentStartEvents=[];navigationEvents=[];firstContentfulPaintEvents=[];for(const proc of Object.values(model.processes)){for(const ev of proc.getDescendantEvents()){if(ev.title===MESSAGE_LOOP_EVENT_NAME){messageLoopStartEvents.push(ev);}else if(ev.title===NAVIGATION_EVENT_NAME){navigationEvents.push(ev);}else if(ev.title===CONTENT_START_EVENT_NAME){contentStartEvents.push(ev);}}
+let totalBrowserStarts=messageLoopStartEvents.length;let totalContentStartEvents=contentStartEvents.length;let totalFcpEvents=firstContentfulPaintEvents.length;let totalNavigations=navigationEvents.length;let totalApplicationStartEvents=applicationStartEvents.length;if(totalFcpEvents!==totalBrowserStarts||totalNavigations!==totalBrowserStarts||totalContentStartEvents!==totalBrowserStarts||totalApplicationStartEvents!==totalBrowserStarts||totalBrowserStarts===0){messageLoopStartEvents=[];contentStartEvents=[];navigationEvents=[];firstContentfulPaintEvents=[];applicationStartEvents=[];for(const proc of Object.values(model.processes)){for(const ev of proc.getDescendantEvents()){if(ev.title===MESSAGE_LOOP_EVENT_NAME){messageLoopStartEvents.push(ev);}else if(ev.title===APPLICATION_START_EVENT_NAME){applicationStartEvents.push(ev);}else if(ev.title===NAVIGATION_EVENT_NAME){navigationEvents.push(ev);}else if(ev.title===CONTENT_START_EVENT_NAME){contentStartEvents.push(ev);}}
 for(const ev of proc.getDescendantEvents()){if(ev.title===FIRST_CONTENTFUL_PAINT_EVENT_NAME){firstContentfulPaintEvents.push(ev);break;}}}
-totalBrowserStarts=messageLoopStartEvents.length;totalContentStartEvents=contentStartEvents.length;totalNavigations=navigationEvents.length;totalFcpEvents=firstContentfulPaintEvents.length;}
+totalBrowserStarts=messageLoopStartEvents.length;totalContentStartEvents=contentStartEvents.length;totalNavigations=navigationEvents.length;totalFcpEvents=firstContentfulPaintEvents.length;totalApplicationStartEvents=applicationStartEvents.length;}
 function orderEvents(event1,event2){return event1.start-event2.start;}
-messageLoopStartEvents.sort(orderEvents);contentStartEvents.sort(orderEvents);navigationEvents.sort(orderEvents);firstContentfulPaintEvents.sort(orderEvents);if(totalFcpEvents<totalBrowserStarts){throw new Error('Found fewer FCP events ('+totalFcpEvents+') than browser starts ('+totalBrowserStarts+')');}
-const messageLoopStartHistogram=histograms.createHistogram('messageloop_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const contentStartHistogram=histograms.createHistogram('experimental_content_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const navigationStartHistogram=histograms.createHistogram('experimental_navigation_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const navigationCommitHistogram=histograms.createHistogram('navigation_commit_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const firstContentfulPaintHistogram=histograms.createHistogram('first_contentful_paint_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);let contentIndex=0;let navIndex=0;let fcpIndex=0;for(let loopStartIndex=0;loopStartIndex<totalBrowserStarts;){const startEvent=messageLoopStartEvents[loopStartIndex];if(fcpIndex===totalFcpEvents){break;}
-const contentStartEvent=contentIndex<contentStartEvents.length?contentStartEvents[contentIndex]:null;if(contentStartEvent&&contentStartEvent.start<startEvent.start){contentIndex++;continue;}
-const navEvent=navIndex<navigationEvents.length?navigationEvents[navIndex]:null;if(navEvent&&navEvent.start<startEvent.start){navIndex++;continue;}
-const fcpEvent=firstContentfulPaintEvents[fcpIndex];if(fcpEvent.start<startEvent.start){fcpIndex++;continue;}
+messageLoopStartEvents.sort(orderEvents);applicationStartEvents.sort(orderEvents);contentStartEvents.sort(orderEvents);navigationEvents.sort(orderEvents);firstContentfulPaintEvents.sort(orderEvents);if(totalFcpEvents<totalBrowserStarts){throw new Error('Found fewer FCP events ('+totalFcpEvents+') than browser starts ('+totalBrowserStarts+')');}
+const messageLoopStartHistogram=histograms.createHistogram('messageloop_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const contentStartHistogram=histograms.createHistogram('experimental_content_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const navigationStartHistogram=histograms.createHistogram('experimental_navigation_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const navigationCommitHistogram=histograms.createHistogram('navigation_commit_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const firstContentfulPaintHistogram=histograms.createHistogram('first_contentful_paint_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const applicationStartHistogram=histograms.createHistogram('process_create_to_app_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);let contentIndex=0;let navIndex=0;let fcpIndex=0;for(let loopStartIndex=0;loopStartIndex<totalBrowserStarts;){const loopStartEvent=messageLoopStartEvents[loopStartIndex];const applicationStartEvent=applicationStartEvents[loopStartIndex];if(fcpIndex===totalFcpEvents){break;}
+const contentStartEvent=contentIndex<contentStartEvents.length?contentStartEvents[contentIndex]:null;if(contentStartEvent&&contentStartEvent.start<loopStartEvent.start){contentIndex++;continue;}
+const navEvent=navIndex<navigationEvents.length?navigationEvents[navIndex]:null;if(navEvent&&navEvent.start<loopStartEvent.start){navIndex++;continue;}
+const fcpEvent=firstContentfulPaintEvents[fcpIndex];if(fcpEvent.start<loopStartEvent.start){fcpIndex++;continue;}
 loopStartIndex++;if(fcpIndex<2){continue;}
-messageLoopStartHistogram.addSample(startEvent.duration,{events:new tr.v.d.RelatedEventSet([startEvent])});if(contentStartEvent){contentStartHistogram.addSample(contentStartEvent.start-startEvent.start,{events:new tr.v.d.RelatedEventSet([startEvent,contentStartEvent])});}
-if(navEvent){navigationStartHistogram.addSample(navEvent.start-startEvent.start,{events:new tr.v.d.RelatedEventSet([startEvent,navEvent])});navigationCommitHistogram.addSample(navEvent.end-startEvent.start,{events:new tr.v.d.RelatedEventSet([startEvent,navEvent])});}
-firstContentfulPaintHistogram.addSample(fcpEvent.end-startEvent.start,{events:new tr.v.d.RelatedEventSet([startEvent,fcpEvent])});}}
+messageLoopStartHistogram.addSample(loopStartEvent.duration,{events:new tr.v.d.RelatedEventSet([loopStartEvent])});if(contentStartEvent){contentStartHistogram.addSample(contentStartEvent.start-loopStartEvent.start,{events:new tr.v.d.RelatedEventSet([loopStartEvent,contentStartEvent])});}
+if(navEvent){navigationStartHistogram.addSample(navEvent.start-loopStartEvent.start,{events:new tr.v.d.RelatedEventSet([loopStartEvent,navEvent])});navigationCommitHistogram.addSample(navEvent.end-loopStartEvent.start,{events:new tr.v.d.RelatedEventSet([loopStartEvent,navEvent])});}
+firstContentfulPaintHistogram.addSample(fcpEvent.end-loopStartEvent.start,{events:new tr.v.d.RelatedEventSet([loopStartEvent,fcpEvent])});if(applicationStartEvent){applicationStartHistogram.addSample(applicationStartEvent.duration,{events:new tr.v.d.RelatedEventSet([applicationStartEvent])});}}}
 tr.metrics.MetricRegistry.register(androidStartupMetric);return{androidStartupMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const MAX_INPUT_EVENT_TO_STARTUP_DELAY_IN_MS=2000;const MIN_DRAW_DELAY_IN_MS=80;const MAX_DRAW_DELAY_IN_MS=2000;function findProcess(processName,model){for(const pid in model.processes){const process=model.processes[pid];if(process.name===processName){return process;}}
 return undefined;}
 function findThreads(process,threadPrefix){if(process===undefined)return undefined;const threads=[];for(const tid in process.threads){const thread=process.threads[tid];if(thread.name.startsWith(threadPrefix)){threads.push(thread);}}
@@ -8139,7 +8146,7 @@
 return TOP_GC_EVENTS[event.title];}
 function topGarbageCollectionEventNames(){return Object.values(TOP_GC_EVENTS);}
 function subGarbageCollectionEventName(event){const topEvent=findParent(event,isTopGarbageCollectionEvent);const prefix=topEvent?topGarbageCollectionEventName(topEvent):'unknown';const name=event.title.replace('V8.GC_MC_','').replace('V8.GC_SCAVENGER_','').replace('V8.GC_','').replace(/_/g,'-').toLowerCase();return prefix+'-'+name;}
-function jsExecutionThreads(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let threads=[];for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){if(rendererHelper.isChromeTracingUI)continue;threads.push(rendererHelper.mainThread);threads=threads.concat(rendererHelper.dedicatedWorkerThreads);threads=threads.concat(rendererHelper.foregroundWorkerThreads);}
+function jsExecutionThreads(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let threads=[];for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){if(rendererHelper.isChromeTracingUI)continue;threads.push(rendererHelper.mainThread);threads=threads.concat(rendererHelper.dedicatedWorkerThreads);threads=threads.concat(rendererHelper.serviceWorkerThreads);threads=threads.concat(rendererHelper.foregroundWorkerThreads);}
 return threads;}
 function groupAndProcessEvents(model,filterCallback,groupCallback,processCallback,groups){const groupToEvents={};if(groups){for(const group of groups){groupToEvents[group]=[];}}
 const threads=jsExecutionThreads(model);for(const thread of threads){for(const event of thread.sliceGroup.childEvents()){if(!filterCallback(event))continue;const group=groupCallback(event);if(groups&&!(group in groupToEvents)){continue;}
@@ -8163,8 +8170,10 @@
 function addMutatorUtilization(metricName,eventFilter,timeWindows,rendererHelpers,histograms){const histogramMap=new Map();for(const timeWindow of timeWindows){const summaryOptions={avg:false,count:false,max:false,min:true,std:false,sum:false};const description=`The minimum mutator utilization in ${timeWindow}ms time window`;const histogram=histograms.createHistogram(`${metricName}-${timeWindow}ms_window`,tr.b.Unit.byName.normalizedPercentage_biggerIsBetter,[],{summaryOptions,description});histogramMap.set(timeWindow,histogram);}
 for(const rendererHelper of rendererHelpers){if(rendererHelper.isChromeTracingUI)continue;if(rendererHelper.mainThread===undefined)continue;const pauses=[];for(const event of rendererHelper.mainThread.sliceGroup.childEvents()){if(eventFilter(event)&&event.end>event.start){pauses.push({start:event.start,end:event.end});}}
 pauses.sort((a,b)=>a.start-b.start);const start=rendererHelper.mainThread.bounds.min;const end=rendererHelper.mainThread.bounds.max;for(const timeWindow of timeWindows){const mu=mutatorUtilization(start,end,timeWindow,pauses);histogramMap.get(timeWindow).addSample(mu.min);}}}
-return{addMutatorUtilization,findParent,forcedGCEventName,filterEvents,filterAndOrderEvents,groupAndProcessEvents,isForcedGarbageCollectionEvent,isFullMarkCompactorEvent,isGarbageCollectionEvent,isIdleTask,isIncrementalMarkingEvent,isLatencyMarkCompactorEvent,isLowMemoryEvent,isMarkCompactorSummaryEvent,isMarkCompactorMarkingSummaryEvent,isMemoryMarkCompactorEvent,isNotForcedMarkCompactorEvent,isNotForcedTopGarbageCollectionEvent,isNotForcedSubGarbageCollectionEvent,isScavengerEvent,isScavengerStackScanningEvent,isSubGarbageCollectionEvent,isTopGarbageCollectionEvent,isTopV8ExecuteEvent,isV8Event,isV8ExecuteEvent,isV8RCSEvent,isCompileRCSCategory,isCompileOptimizeRCSCategory,isCompileUnoptimizeRCSCategory,isCompileParseRCSCategory,mutatorUtilization,rangeForMemoryDumps,subGarbageCollectionEventName,topGarbageCollectionEventName,topGarbageCollectionEventNames,unionOfIntervals,};});'use strict';tr.exportTo('tr.metrics.blink',function(){const BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP={'BlinkGC.AtomicPauseMarkEpilogue':'blink-gc-atomic-pause-mark-epilogue','BlinkGC.AtomicPauseMarkPrologue':'blink-gc-atomic-pause-mark-prologue','BlinkGC.AtomicPauseMarkRoots':'blink-gc-atomic-pause-mark-roots','BlinkGC.IncrementalMarkingStartMarking':'blink-gc-incremental-start','BlinkGC.IncrementalMarkingStep':'blink-gc-incremental-step','BlinkGC.UnifiedMarkingStep':'blink-gc-unified-marking-by-v8','BlinkGC.CompleteSweep':'blink-gc-complete-sweep','BlinkGC.LazySweepInIdle':'blink-gc-sweep-task-foreground','BlinkGC.LazySweepOnAllocation':'blink-gc-sweep-allocation','BlinkGC.AtomicPauseSweepAndCompact':'blink-gc-atomic-pause-sweep-and-compact'};const BLINK_TOP_GC_ROOTS_MARKING_EVENTS=['BlinkGC.VisitRoots'];const BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS=['BlinkGC.AtomicPauseMarkTransitiveClosure'];const BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS=['BlinkGC.AtomicPauseMarkTransitiveClosure','BlinkGC.IncrementalMarkingStep','BlinkGC.UnifiedMarkingStep'];const BLINK_TOP_GC_FOREGROUND_MARKING_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.IncrementalMarkingStartMarking',].concat(BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS);const BLINK_GC_FORCED_FOREGROUND_MARKING_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.IncrementalMarkingStartMarking','BlinkGC.MarkBailOutObjects','BlinkGC.MarkFlushV8References','BlinkGC.MarkFlushEphemeronPairs',];const BLINK_TOP_GC_BACKGROUND_MARKING_EVENTS=['BlinkGC.ConcurrentMarkingStep'];const BLINK_TOP_GC_FOREGROUND_SWEEPING_EVENTS=['BlinkGC.CompleteSweep','BlinkGC.LazySweepInIdle','BlinkGC.LazySweepOnAllocation'];const BLINK_TOP_GC_BACKGROUND_SWEEPING_EVENTS=['BlinkGC.ConcurrentSweepingStep'];const BLINK_TOP_GC_EVENTS=Object.keys(BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP).concat(BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS);const ATOMIC_PAUSE_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.AtomicPauseMarkTransitiveClosure','BlinkGC.AtomicPauseSweepAndCompact'];function blinkGarbageCollectionEventName(event){return BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP[event.title];}
+return{addMutatorUtilization,findParent,forcedGCEventName,filterEvents,filterAndOrderEvents,groupAndProcessEvents,isForcedGarbageCollectionEvent,isFullMarkCompactorEvent,isGarbageCollectionEvent,isIdleTask,isIncrementalMarkingEvent,isLatencyMarkCompactorEvent,isLowMemoryEvent,isMarkCompactorSummaryEvent,isMarkCompactorMarkingSummaryEvent,isMemoryMarkCompactorEvent,isNotForcedMarkCompactorEvent,isNotForcedTopGarbageCollectionEvent,isNotForcedSubGarbageCollectionEvent,isScavengerEvent,isScavengerStackScanningEvent,isSubGarbageCollectionEvent,isTopGarbageCollectionEvent,isTopV8ExecuteEvent,isV8Event,isV8ExecuteEvent,isV8RCSEvent,isCompileRCSCategory,isCompileOptimizeRCSCategory,isCompileUnoptimizeRCSCategory,isCompileParseRCSCategory,mutatorUtilization,rangeForMemoryDumps,subGarbageCollectionEventName,topGarbageCollectionEventName,topGarbageCollectionEventNames,unionOfIntervals,};});'use strict';tr.exportTo('tr.metrics.blink',function(){const BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP={'BlinkGC.AtomicPauseMarkEpilogue':'blink-gc-atomic-pause-mark-epilogue','BlinkGC.AtomicPauseMarkPrologue':'blink-gc-atomic-pause-mark-prologue','BlinkGC.AtomicPauseMarkRoots':'blink-gc-atomic-pause-mark-roots','BlinkGC.IncrementalMarkingStartMarking':'blink-gc-incremental-start','BlinkGC.IncrementalMarkingStep':'blink-gc-incremental-step','BlinkGC.UnifiedMarkingStep':'blink-gc-unified-marking-by-v8','BlinkGC.CompleteSweep':'blink-gc-complete-sweep','BlinkGC.LazySweepInIdle':'blink-gc-sweep-task-foreground','BlinkGC.LazySweepOnAllocation':'blink-gc-sweep-allocation','BlinkGC.AtomicPauseSweepAndCompact':'blink-gc-atomic-pause-sweep-and-compact'};const BLINK_NON_AGGREGATED_GC_EVENTS_NEW_NAMES_MAP={'BlinkGC.AtomicPauseMarkEpilogue':'blink:gc:main_thread:cycle:full:atomic:mark:epilogue','BlinkGC.AtomicPauseMarkPrologue':'blink:gc:main_thread:cycle:full:atomic:mark:prologue','BlinkGC.AtomicPauseMarkRoots':'blink:gc:main_thread:cycle:full:atomic:mark:roots','BlinkGC.IncrementalMarkingStartMarking':'blink:gc:main_thread:cycle:full:incremental:mark:start','BlinkGC.IncrementalMarkingStep':'blink:gc:main_thread:cycle:full:incremental:mark:step','BlinkGC.UnifiedMarkingStep':'unified:gc:main_thread:cycle:full:mark:step','BlinkGC.CompleteSweep':'blink:gc:main_thread:cycle:full:sweep:complete','BlinkGC.LazySweepInIdle':'blink:gc:main_thread:cycle:full:sweep:idle','BlinkGC.LazySweepOnAllocation':'blink:gc:main_thread:cycle:full:sweep:on_allocation','BlinkGC.AtomicPauseSweepAndCompact':'blink:gc:main_thread:cycle:full:atomic:sweep:compact'};const BLINK_TOP_GC_ROOTS_MARKING_EVENTS=['BlinkGC.VisitRoots'];const BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS=['BlinkGC.AtomicPauseMarkTransitiveClosure'];const BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS=['BlinkGC.AtomicPauseMarkTransitiveClosure','BlinkGC.IncrementalMarkingStep','BlinkGC.UnifiedMarkingStep'];const BLINK_TOP_GC_FOREGROUND_MARKING_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.IncrementalMarkingStartMarking',].concat(BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS);const BLINK_GC_FORCED_FOREGROUND_MARKING_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.IncrementalMarkingStartMarking','BlinkGC.MarkBailOutObjects','BlinkGC.MarkFlushV8References','BlinkGC.MarkFlushEphemeronPairs',];const BLINK_TOP_GC_BACKGROUND_MARKING_EVENTS=['BlinkGC.ConcurrentMarkingStep'];const BLINK_TOP_GC_FOREGROUND_SWEEPING_EVENTS=['BlinkGC.CompleteSweep','BlinkGC.LazySweepInIdle','BlinkGC.LazySweepOnAllocation'];const BLINK_TOP_GC_BACKGROUND_SWEEPING_EVENTS=['BlinkGC.ConcurrentSweepingStep'];const BLINK_TOP_GC_EVENTS=Object.keys(BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP).concat(BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS);const ATOMIC_PAUSE_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.AtomicPauseMarkTransitiveClosure','BlinkGC.AtomicPauseSweepAndCompact'];function blinkGarbageCollectionEventName(event){return BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP[event.title];}
 function blinkGarbageCollectionEventNames(){return Object.values(BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP);}
+function blinkGarbageCollectionEventNewName(event){return BLINK_NON_AGGREGATED_GC_EVENTS_NEW_NAMES_MAP[event.title];}
+function blinkGarbageCollectionEventNewNames(){return Object.values(BLINK_NON_AGGREGATED_GC_EVENTS_NEW_NAMES_MAP);}
 function isNonForcedEvent(event){return(!event.args||!event.args.forced)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}
 function isNonForcedBlinkGarbageCollectionEvent(event){return BLINK_TOP_GC_EVENTS.includes(event.title)&&isNonForcedEvent(event);}
 function isNonForcedNonAggregatedBlinkGarbageCollectionEvent(event){return event.title in BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP&&isNonForcedEvent(event);}
@@ -8181,32 +8190,27 @@
 function isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent(event){return BLINK_TOP_GC_BACKGROUND_SWEEPING_EVENTS.includes(event.title)&&isNonForcedEvent(event);}
 function isNonNestedNonForcedBlinkGarbageCollectionEvent(event){return isNonForcedBlinkGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isGarbageCollectionEvent);}
 function blinkGcMetric(histograms,model){addDurationOfTopEvents(histograms,model);addDurationOfAtomicPause(histograms,model);addDurationOfAtomicPauseTransitiveClosure(histograms,model);addTotalDurationOfTopEvents(histograms,model);addTotalDurationOfBlinkAndV8TopEvents(histograms,model);addTotalDurationOfRootsMarking(histograms,model);addTotalDurationOfMarkingTransitiveClosure(histograms,model);addTotalDurationOfForegroundMarking(histograms,model);addTotalDurationOfForcedForegroundMarking(histograms,model);addTotalDurationOfBackgroundMarking(histograms,model);addTotalDurationOfForegroundSweeping(histograms,model);addTotalDurationOfBackgroundSweeping(histograms,model);}
-tr.metrics.MetricRegistry.register(blinkGcMetric);const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,20,200).addExponentialBins(200,100);function createNumericForTopEventTime(name){const n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:true,max:true,min:false,std:true,sum:true,percentile:[0.90]});return n;}
-function createNumericForTotalEventTime(name){const n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:false,count:true,max:false,min:false,std:false,sum:true,percentile:[0.90]});return n;}
-function createNumericForUnifiedEventTime(name){const n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:false,count:true,max:true,min:false,std:false,sum:true,percentile:[0.90]});return n;}
-function addDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedNonAggregatedBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForTopEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},blinkGarbageCollectionEventNames());}
-function addDurationOfAtomicPause(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionAtomicPauseEvent,event=>event.args.epoch,function(group,events){const cpuDuration=createNumericForTopEventTime('blink-gc-atomic-pause');cpuDuration.addSample(events.reduce((acc,current)=>acc+current.cpuDuration,0));histograms.addHistogram(cpuDuration);});}
-function addDurationOfAtomicPauseTransitiveClosure(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionAtomicPauseTransitiveColsureEvent,event=>event.args.epoch,function(group,events){const cpuDuration=createNumericForTopEventTime('blink-gc-atomic-pause-mark-transitive-closure');cpuDuration.addSample(events.reduce((acc,current)=>acc+current.cpuDuration,0));histograms.addHistogram(cpuDuration);});}
-function addTotalDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionEvent,event=>'blink-gc-total',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-total']);}
-function addTotalDurationOfRootsMarking(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionRootsMarkingEvent,event=>'blink-gc-mark-roots',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-mark-roots']);}
-function addTotalDurationOfMarkingTransitiveClosure(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionMarkingTransitiveColsureEvent,event=>'blink-gc-mark-transitive-closure',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-mark-transitive-closure']);}
-function addTotalDurationOfForegroundMarking(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionForegroundMarkingEvent,event=>'blink-gc-mark-foreground',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-mark-foreground']);}
-function addTotalDurationOfForcedForegroundMarking(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionForcedForegroundMarkEvent,event=>'blink-gc-mark-foreground-forced',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-mark-foreground-forced']);}
-function addTotalDurationOfBackgroundMarking(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionBackgroundMarkingEvent,event=>'blink-gc-mark-background',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-mark-background']);}
-function addTotalDurationOfForegroundSweeping(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionForegroundSweepingEvent,event=>'blink-gc-sweep-foreground',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-sweep-foreground']);}
-function addTotalDurationOfBackgroundSweeping(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent,event=>'blink-gc-sweep-background',function(name,events){const cpuDuration=createNumericForTotalEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['blink-gc-sweep-background']);}
+function getEventEpochUniqueId(event){return event.parentContainer.parent.stableId+':'+event.args.epoch;}
+tr.metrics.MetricRegistry.register(blinkGcMetric);const CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,20,200).addExponentialBins(200,100);function createNumericForEventTime(name){const n=new tr.v.Histogram(name,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:true,max:true,min:true,std:true,sum:true,percentile:[0.90]});return n;}
+function addDurationOfTopEvents(histograms,model){const nameToNumeric={};const nameToEpochNumeric={};for(const name of blinkGarbageCollectionEventNames()){nameToNumeric[name]=createNumericForEventTime(name);}
+for(const name of blinkGarbageCollectionEventNewNames()){nameToEpochNumeric[name]=createNumericForEventTime(name);}
+tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedNonAggregatedBlinkGarbageCollectionEvent,getEventEpochUniqueId,function(epoch,events){const namesToPerEpochDurations={};for(const event of events){nameToNumeric[blinkGarbageCollectionEventName(event)].addSample(event.cpuDuration);const name=blinkGarbageCollectionEventNewName(event);namesToPerEpochDurations[name]=(namesToPerEpochDurations[name]||0)+event.cpuDuration;}
+for(const name in namesToPerEpochDurations){nameToEpochNumeric[name].addSample(namesToPerEpochDurations[name]);}});for(const name of blinkGarbageCollectionEventNames()){histograms.addHistogram(nameToNumeric[name]);}
+for(const name of blinkGarbageCollectionEventNewNames()){histograms.addHistogram(nameToEpochNumeric[name]);}}
+function addIndividualDurationsOfEvents(histograms,model,name,filter){const cpuDuration=createNumericForEventTime(name);tr.metrics.v8.utils.groupAndProcessEvents(model,filter,event=>name,function(group,events){for(const event of events){cpuDuration.addSample(event.cpuDuration);}},[name]);histograms.addHistogram(cpuDuration);}
+function addPerEpochDurationsOfEvents(histograms,model,name,filter){const cpuDuration=createNumericForEventTime(name);tr.metrics.v8.utils.groupAndProcessEvents(model,filter,getEventEpochUniqueId,function(epoch,events){cpuDuration.addSample(events.reduce((acc,current)=>acc+current.cpuDuration,0));});histograms.addHistogram(cpuDuration);}
+function addDurationOfAtomicPause(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-atomic-pause',isNonForcedBlinkGarbageCollectionAtomicPauseEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:atomic',isNonForcedBlinkGarbageCollectionAtomicPauseEvent);}
+function addDurationOfAtomicPauseTransitiveClosure(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-atomic-pause-mark-transitive-closure',isNonForcedBlinkGarbageCollectionAtomicPauseTransitiveColsureEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:atomic:mark:transitive_closure',isNonForcedBlinkGarbageCollectionAtomicPauseTransitiveColsureEvent);}
+function addTotalDurationOfTopEvents(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-total',isNonForcedBlinkGarbageCollectionEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full',isNonForcedBlinkGarbageCollectionEvent);}
+function addTotalDurationOfRootsMarking(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-mark-roots',isNonForcedBlinkGarbageCollectionRootsMarkingEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:mark:roots',isNonForcedBlinkGarbageCollectionRootsMarkingEvent);}
+function addTotalDurationOfMarkingTransitiveClosure(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-mark-transitive-closure',isNonForcedBlinkGarbageCollectionMarkingTransitiveColsureEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:mark:transitive_closure',isNonForcedBlinkGarbageCollectionMarkingTransitiveColsureEvent);}
+function addTotalDurationOfForegroundMarking(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-mark-foreground',isNonForcedBlinkGarbageCollectionForegroundMarkingEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:mark',isNonForcedBlinkGarbageCollectionForegroundMarkingEvent);}
+function addTotalDurationOfForcedForegroundMarking(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-mark-foreground-forced',isNonForcedBlinkGarbageCollectionForcedForegroundMarkEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:mark:forced',isNonForcedBlinkGarbageCollectionForcedForegroundMarkEvent);}
+function addTotalDurationOfBackgroundMarking(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-mark-background',isNonForcedBlinkGarbageCollectionBackgroundMarkingEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:concurrent_thread:cycle:full:mark',isNonForcedBlinkGarbageCollectionBackgroundMarkingEvent);}
+function addTotalDurationOfForegroundSweeping(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-sweep-foreground',isNonForcedBlinkGarbageCollectionForegroundSweepingEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:main_thread:cycle:full:sweep',isNonForcedBlinkGarbageCollectionForegroundSweepingEvent);}
+function addTotalDurationOfBackgroundSweeping(histograms,model){addIndividualDurationsOfEvents(histograms,model,'blink-gc-sweep-background',isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent);addPerEpochDurationsOfEvents(histograms,model,'blink:gc:concurrent_thread:cycle:full:sweep',isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent);}
 function isV8OrBlinkTopLevelGarbageCollectionEvent(event){return tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent(event)||isNonNestedNonForcedBlinkGarbageCollectionEvent(event);}
-function addTotalDurationOfBlinkAndV8TopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isV8OrBlinkTopLevelGarbageCollectionEvent,event=>'unified-gc-total',function(name,events){const cpuDuration=createNumericForUnifiedEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
-histograms.addHistogram(cpuDuration);},['unified-gc-total']);}
+function addTotalDurationOfBlinkAndV8TopEvents(histograms,model){addIndividualDurationsOfEvents(histograms,model,'unified-gc-total',isV8OrBlinkTopLevelGarbageCollectionEvent);}
 return{blinkGcMetric,};});'use strict';tr.exportTo('tr.metrics.blink',function(){function leakDetectionMetric(histograms,model){const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(modelHelper===undefined){throw new Error('Chrome is not present.');}
 const rendererHelpers=modelHelper.rendererHelpers;if(Object.keys(rendererHelpers).length===0){throw new Error('Renderer process is not present.');}
 const pids=Object.keys(rendererHelpers);const chromeDumps=tr.metrics.sh.splitGlobalDumpsByBrowserName(model,undefined).get('chrome');const sumCounter=new Map();for(const pid of pids){for(const[key,count]of countLeakedBlinkObjects(chromeDumps,pid)){sumCounter.set(key,(sumCounter.get(key)||0)+count);}}
@@ -8233,10 +8237,21 @@
 function cpuProcessMetric(histograms,model){const snapshots=getCpuSnapshotsFromModel(model);const processNumerics=buildNumericsFromSnapshots(snapshots);for(const[processName,processData]of processNumerics){const numeric=processData.numeric;const missingSnapshotCount=snapshots.length-numeric.numValues;for(let i=0;i<missingSnapshotCount;i++){numeric.addSample(0);}
 numeric.diagnostics.set('paths',new
 tr.v.d.GenericSet([...processData.paths]));histograms.addHistogram(numeric);}}
-tr.metrics.MetricRegistry.register(cpuProcessMetric);return{cpuProcessMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function mediaMetric(histograms,model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(chromeHelper===undefined)return;for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){const mainThread=rendererHelper.mainThread;if(mainThread===undefined)continue;const videoThreads=rendererHelper.process.findAllThreadsMatching(thread=>(thread.name?thread.name.startsWith('ThreadPoolSingleThreadSharedForegroundBlocking'):false));const compositorThread=rendererHelper.compositorThread;if(compositorThread!==undefined){videoThreads.push(compositorThread);}
+tr.metrics.MetricRegistry.register(cpuProcessMetric);return{cpuProcessMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function customMetric(histograms,model,opt_options){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper){return;}
+const TITLE_PREFIX='custom_metric:';const traces=new Map();const benchmarkValues=new Map();for(const helper of chromeHelper.browserHelpers){if(!helper.mainThread)continue;for(const slice of helper.mainThread.sliceGroup.slices.concat(helper.mainThread.asyncSliceGroup.slices)){if(!slice.error&&slice.title.startsWith(TITLE_PREFIX)){if(!traces.has(slice.title)){traces.set(slice.title,[]);}
+traces.get(slice.title).push(slice.duration);}}}
+const BENCHMARK_BEGIN='benchmark_begin';const BENCHMARK_END='benchmark_end';const BENCHMARK_VALUE='benchmark_value';const marks=new Map();for(const helper of Object.values(chromeHelper.rendererHelpers)){for(const event of helper.mainThread.sliceGroup.childEvents()){const navId=getNavigationId(event);if(!navId||!event.category.includes('blink.user_timing'))continue;const{title}=event;const index=title.lastIndexOf(':');if(index===-1){continue;}
+const name=title.substring(0,index);const lastPart=title.substring(index+1);if(lastPart===BENCHMARK_BEGIN){marks.set(name,event);}else if(lastPart===BENCHMARK_END){if(!marks.has(name)){continue;}
+const range=tr.b.math.Range.fromExplicitRange(marks.get(name).start,event.start);if(!traces.has(name)){traces.set(name,[]);}
+traces.get(name).push(range.duration);marks.delete(name);}else if(lastPart===BENCHMARK_VALUE){const index2=name.lastIndexOf(':');if(index2===-1){continue;}
+const key=name.substring(0,index2);const value=Number(name.substring(index2+1));if(key&&!isNaN(value)){if(!benchmarkValues.has(key)){benchmarkValues.set(key,[]);}
+benchmarkValues.get(key).push(value);}}}}
+traces.forEach((value,key)=>{histograms.createHistogram(key,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,value);});benchmarkValues.forEach((value,key)=>{histograms.createHistogram(key,tr.b.Unit.byName.unitlessNumber_smallerIsBetter,value);});}
+function getNavigationId(event){return event.args.data&&event.args.data.navigationId;}
+tr.metrics.MetricRegistry.register(customMetric,{supportsRangeOfInterest:false,});return{customMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function mediaMetric(histograms,model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(chromeHelper===undefined)return;for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){const mainThread=rendererHelper.mainThread;if(mainThread===undefined)continue;const videoThreads=rendererHelper.process.findAllThreadsMatching(thread=>(thread.name?thread.name.startsWith('ThreadPoolSingleThreadSharedForegroundBlocking'):false));const compositorThread=rendererHelper.compositorThread;if(compositorThread!==undefined){videoThreads.push(compositorThread);}
 const audioThreads=rendererHelper.process.findAllThreadsNamed('AudioOutputDevice');if(audioThreads.length===0&&videoThreads.length===0)continue;const processData=new PerProcessData();processData.recordPlayStarts(mainThread);if(!processData.hasPlaybacks)continue;if(videoThreads.length!==0){processData.calculateTimeToVideoPlays(videoThreads);processData.calculateDroppedFrameCounts(videoThreads);}
 if(audioThreads.length!==0){processData.calculateTimeToAudioPlays(audioThreads);}
-processData.calculateSeekTimes(mainThread);processData.calculateBufferingTimes(mainThread);processData.addMetricToHistograms(histograms);}}
+processData.calculateSeekTimes(mainThread);processData.calculateBufferingTimes(mainThread);const allThreads=rendererHelper.process.findAllThreadsMatching(function(){return true;});processData.calculateVideoPlaybackQuality(allThreads);processData.addMetricToHistograms(histograms);}}
 class PerProcessData{constructor(){this.playbackIdToDataMap_=new Map();}
 recordPlayStarts(mainThread){for(const event of mainThread.sliceGroup.getDescendantEvents()){if(event.title==='WebMediaPlayerImpl::DoLoad'){const id=event.args.id;if(this.playbackIdToDataMap_.has(id)){throw new Error('Unexpected multiple initialization of a media playback');}
 this.playbackIdToDataMap_.set(id,new PerPlaybackData(event.start));}}}
@@ -8246,15 +8261,18 @@
 calculateSeekTimes(mainThread){for(const event of mainThread.sliceGroup.getDescendantEvents()){if(event.title==='WebMediaPlayerImpl::DoSeek'){this.getPerPlaybackObject_(event.args.id).processDoSeek(event.args.target,event.start);}else if(event.title==='WebMediaPlayerImpl::OnPipelineSeeked'){this.getPerPlaybackObject_(event.args.id).processOnPipelineSeeked(event.args.target,event.start);}else if(event.title==='WebMediaPlayerImpl::BufferingHaveEnough'){this.getPerPlaybackObject_(event.args.id).processBufferingHaveEnough(event.start);}}}
 calculateBufferingTimes(mainThread){for(const event of mainThread.sliceGroup.getDescendantEvents()){if(event.title==='WebMediaPlayerImpl::OnEnded'){this.getPerPlaybackObject_(event.args.id).processOnEnded(event.start,event.args.duration);}}}
 calculateDroppedFrameCounts(videoThreads){for(const thread of videoThreads){for(const event of thread.sliceGroup.getDescendantEvents()){if(event.title==='VideoFramesDropped'){this.getPerPlaybackObject_(event.args.id).processVideoFramesDropped(event.args.count);}}}}
+calculateVideoPlaybackQuality(threads){for(const thread of threads){for(const event of thread.sliceGroup.getDescendantEvents()){if(event.title==='VideoPlaybackRoughness'){this.getPerPlaybackObject_(event.args.id).processVideoRoughness(event.args.roughness);}else if(event.title==='VideoPlaybackFreezing'){this.getPerPlaybackObject_(event.args.id).processVideoFreezing(event.args.freezing);}}}}
 addMetricToHistograms(histograms){for(const[id,playbackData]of this.playbackIdToDataMap_){playbackData.addMetricToHistograms(histograms);}}
 getPerPlaybackObject_(playbackId){let perPlaybackObject=this.playbackIdToDataMap_.get(playbackId);if(perPlaybackObject===undefined){perPlaybackObject=new PerPlaybackData(undefined);this.playbackIdToDataMap_.set(playbackId,perPlaybackObject);}
 return perPlaybackObject;}}
-class PerPlaybackData{constructor(playStartTime){this.playStart_=playStartTime;this.timeToVideoPlay_=undefined;this.timeToAudioPlay_=undefined;this.bufferingTime_=undefined;this.droppedFrameCount_=0;this.seekError_=false;this.seekTimes_=new Map();this.currentSeek_=undefined;}
+class PerPlaybackData{constructor(playStartTime){this.playStart_=playStartTime;this.timeToVideoPlay_=undefined;this.timeToAudioPlay_=undefined;this.bufferingTime_=undefined;this.droppedFrameCount_=0;this.seekError_=false;this.seekTimes_=new Map();this.currentSeek_=undefined;this.roughness_=undefined;this.freezing_=undefined;}
 get timeToVideoPlay(){return this.timeToVideoPlay_;}
 get timeToAudioPlay(){return this.timeToAudioPlay_;}
 get bufferingTime(){return this.bufferingTime_;}
 get droppedFrameCount(){return(this.timeToVideoPlay_!==undefined)?this.droppedFrameCount_:undefined;}
 get seekTimes(){if(this.seekError_||this.currentSeek_!==undefined)return new Map();return this.seekTimes_;}
+get roughness(){return this.roughness_;}
+get freezing(){return this.freezing_;}
 processVideoRenderTime(videoRenderTime){if(this.playStart_!==undefined&&this.timeToVideoPlay_===undefined){this.timeToVideoPlay_=videoRenderTime-this.playStart_;}}
 processAudioRenderTime(audioRenderTime){if(this.playStart_!==undefined&&this.timeToAudioPlay_===undefined){this.timeToAudioPlay_=audioRenderTime-this.playStart_;}}
 processVideoFramesDropped(count){this.droppedFrameCount_+=count;}
@@ -8268,11 +8286,20 @@
 if(currentSeek.pipelineSeekTime===undefined){return;}
 currentSeek.seekTime=time-currentSeek.startTime;this.currentSeek_=undefined;}
 processOnEnded(playEndTime,duration){if(this.playStart_===undefined)return;if(this.seekTimes_.size!==0||this.seekError_)return;if(this.bufferingTime_!==undefined)return;duration=tr.b.convertUnit(duration,tr.b.UnitPrefixScale.METRIC.NONE,tr.b.UnitPrefixScale.METRIC.MILLI);const playTime=playEndTime-this.playStart_;if(this.timeToVideoPlay_!==undefined){this.bufferingTime_=playTime-duration-this.timeToVideoPlay_;}else if(this.timeToAudioPlay!==undefined){this.bufferingTime_=playTime-duration-this.timeToAudioPlay_;}}
+processVideoRoughness(roughness){if(this.roughness_===undefined||this.roughness_>roughness){this.roughness_=roughness;}}
+processVideoFreezing(freezing){if(this.freezing_===undefined||this.freezing_>freezing){this.freezing_=freezing;}}
 addMetricToHistograms(histograms){this.addSample_(histograms,'time_to_video_play',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.timeToVideoPlay);this.addSample_(histograms,'time_to_audio_play',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.timeToAudioPlay);this.addSample_(histograms,'dropped_frame_count',tr.b.Unit.byName.count_smallerIsBetter,this.droppedFrameCount);for(const[key,value]of this.seekTimes.entries()){const keyString=key.toString().replace('.','_');this.addSample_(histograms,'pipeline_seek_time_'+keyString,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,value.pipelineSeekTime);this.addSample_(histograms,'seek_time_'+keyString,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,value.seekTime);}
-this.addSample_(histograms,'buffering_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.bufferingTime);}
+this.addSample_(histograms,'buffering_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.bufferingTime);this.addSample_(histograms,'roughness',tr.b.Unit.byName.count_smallerIsBetter,this.roughness);this.addSample_(histograms,'freezing',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.freezing);}
 addSample_(histograms,name,unit,sample){if(sample===undefined)return;const histogram=histograms.getHistogramNamed(name);if(histogram===undefined){histograms.createHistogram(name,unit,sample);}else{histogram.addSample(sample);}}}
 tr.metrics.MetricRegistry.register(mediaMetric);return{mediaMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function memoryAblationMetric(histograms,model){const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper.gpuHelper)return;const gpuProcess=modelHelper.gpuHelper.process;const events=[...gpuProcess.findTopmostSlicesNamed('Memory.GPU.PeakMemoryUsage.AblationTimes')];const allocHistogram=histograms.createHistogram('Ablation Alloc',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,10000,20),description:'The amount of time spent allocating the ablation '+'memory',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});const deallocHistogram=histograms.createHistogram('Ablation Dealloc',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,10000,20),description:'The amount of time spent deallocating the ablation '+'memory',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});for(let i=0;i<events.length;i++){allocHistogram.addSample(events[i].args.alloc);deallocHistogram.addSample(events[i].args.dealloc);}}
-tr.metrics.MetricRegistry.register(memoryAblationMetric,{requiredCategories:['gpu.memory'],});return{memoryAblationMetric,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const UNKNOWN_THREAD_NAME='Unknown';const CATEGORY_THREAD_MAP=new Map();CATEGORY_THREAD_MAP.set('total_all',[/.*/]);CATEGORY_THREAD_MAP.set('browser',[/^Browser Compositor$/,/^CrBrowserMain$/]);CATEGORY_THREAD_MAP.set('display_compositor',[/^VizCompositorThread$/]);CATEGORY_THREAD_MAP.set('GPU',[/^Chrome_InProcGpuThread$/,/^CrGpuMain$/]);CATEGORY_THREAD_MAP.set('IO',[/IOThread/]);CATEGORY_THREAD_MAP.set('raster',[/CompositorTileWorker/]);CATEGORY_THREAD_MAP.set('renderer_compositor',[/^Compositor$/]);CATEGORY_THREAD_MAP.set('renderer_main',[/^CrRendererMain$/]);CATEGORY_THREAD_MAP.set('total_rendering',[/^Browser Compositor$/,/^Chrome_InProcGpuThread$/,/^Compositor$/,/CompositorTileWorker/,/^CrBrowserMain$/,/^CrGpuMain$/,/^CrRendererMain$/,/IOThread/,/^VizCompositorThread$/]);const ALL_CATEGORIES=[...CATEGORY_THREAD_MAP.keys(),'other'];function addValueToMap_(map,key,value){const oldValue=map.get(key)||0;map.set(key,oldValue+value);}
+tr.metrics.MetricRegistry.register(memoryAblationMetric,{requiredCategories:['gpu.memory'],});return{memoryAblationMetric,};});'use strict';tr.exportTo('tr.metrics.pa',function(){function pcscanMetric(histograms,model){function createNumericForProcess(name,processName,desc){function createNumericForEventTime(name,desc){const n=new tr.v.Histogram(name,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);n.description=desc;n.customizeSummaryOptions({avg:true,count:true,max:true,min:true,std:true,sum:true});return n;}
+const scheme=['pa','pcscan',processName];if(name)scheme.push(name);return createNumericForEventTime(scheme.join(':'),desc);}
+function createHistsForProcess(processName){return{scan:createNumericForProcess('scan',processName,'Time for scanning heap for quarantine pointers'),sweep:createNumericForProcess('sweep',processName,'Time for sweeping quarantine'),clear:createNumericForProcess('clear',processName,'Time for clearing quarantine entries'),total:createNumericForProcess('',processName,'Total time for PCScan execution')};}
+function addSample(hists,slice){if(!(slice instanceof tr.model.ThreadSlice))return;if(slice.category!=='partition_alloc')return;if(slice.title==='PCScan.Scan'){hists.scan.addSample(slice.duration);}else if(slice.title==='PCScan.Sweep'){hists.sweep.addSample(slice.duration);}else if(slice.title==='PCScan.Clear'){hists.clear.addSample(slice.duration);}else if(slice.title==='PCScan'){hists.total.addSample(slice.duration);}}
+function addHistsForProcess(processHists,processHelpers){for(const helper of Object.values(processHelpers)){const processName=tr.e.chrome.chrome_processes.canonicalizeProcessName(helper.process.name);if(!processHists.has(processName)){processHists.set(processName,createHistsForProcess(processName));}
+for(const slice of helper.process.getDescendantEvents()){addSample(processHists.get(processName),slice);}}}
+const helper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const processHists=new Map();addHistsForProcess(processHists,helper.browserHelpers);addHistsForProcess(processHists,helper.rendererHelpers);for(const hists of processHists.values()){for(const hist of Object.values(hists)){histograms.addHistogram(hist);}}}
+tr.metrics.MetricRegistry.register(pcscanMetric);return{pcscanMetric,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const UNKNOWN_THREAD_NAME='Unknown';const CATEGORY_THREAD_MAP=new Map();CATEGORY_THREAD_MAP.set('total_all',[/.*/]);CATEGORY_THREAD_MAP.set('browser',[/^Browser Compositor$/,/^CrBrowserMain$/]);CATEGORY_THREAD_MAP.set('display_compositor',[/^VizCompositorThread$/]);CATEGORY_THREAD_MAP.set('GPU',[/^Chrome_InProcGpuThread$/,/^CrGpuMain$/]);CATEGORY_THREAD_MAP.set('IO',[/IOThread/]);CATEGORY_THREAD_MAP.set('raster',[/CompositorTileWorker/]);CATEGORY_THREAD_MAP.set('renderer_compositor',[/^Compositor$/]);CATEGORY_THREAD_MAP.set('renderer_main',[/^CrRendererMain$/]);CATEGORY_THREAD_MAP.set('total_rendering',[/^Browser Compositor$/,/^Chrome_InProcGpuThread$/,/^Compositor$/,/CompositorTileWorker/,/^CrBrowserMain$/,/^CrGpuMain$/,/^CrRendererMain$/,/IOThread/,/^VizCompositorThread$/]);const ALL_CATEGORIES=[...CATEGORY_THREAD_MAP.keys(),'other'];function addValueToMap_(map,key,value){const oldValue=map.get(key)||0;map.set(key,oldValue+value);}
 function addToArrayInMap_(map,key,value){const arr=map.get(key)||[];arr.push(value);map.set(key,arr);}
 function*getCategories_(threadName){let isOther=true;for(const[category,regexps]of CATEGORY_THREAD_MAP){for(const regexp of regexps){if(regexp.test(threadName)){if(category!=='total_all')isOther=false;yield category;break;}}}
 if(isOther)yield'other';}
@@ -8304,11 +8331,11 @@
 return legacyEvents;}
 function computeFrameSegments_(events,segments,opt_minFrameCount){const minFrameCount=opt_minFrameCount||MIN_FRAME_COUNT;const frameEvents=events.map(e=>new FrameEvent(e));const frameSegments=[];for(const segment of segments){const filtered=segment.boundsRange.filterArray(frameEvents,x=>x.eventStart);if(filtered.length<minFrameCount)continue;for(let i=1;i<filtered.length;i++){const duration=filtered[i].frameStart-filtered[i-1].frameStart;frameSegments.push(new FrameSegment(filtered[i-1],duration));}}
 return frameSegments;}
-function addBasicFrameTimeHistograms_(histograms,frameSegments,prefix){const frameTimes=(frameSegments.length===0)?[0]:frameSegments.map(x=>x.duration);histograms.createHistogram(`${prefix}frame_times`,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,frameTimes,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),description:'Raw frame times.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});histograms.createHistogram(`${prefix}percentage_smooth`,tr.b.Unit.byName.unitlessNumber_biggerIsBetter,100*tr.b.math.Statistics.sum(frameTimes,(x=>(x<17?1:0)))/frameTimes.length,{description:'Percentage of frames that were hitting 60 FPS.',summaryOptions:{},});}
+function addBasicFrameTimeHistograms_(histograms,frameSegments,prefix){const frameTimes=(frameSegments.length===0)?[0]:frameSegments.map(x=>x.duration);histograms.createHistogram(`${prefix}frame_times`,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,frameTimes,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),description:'Raw frame times.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});}
 function addFrameTimeHistograms(histograms,model,segments,opt_minFrameCount){const minFrameCount=opt_minFrameCount||MIN_FRAME_COUNT;const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const events=getDisplayCompositorPresentationEvents_(modelHelper);if(!events)return;addFrameTimeHistogramsHelper(histograms,model,segments,events,'',true,minFrameCount);const eventsExp=getDisplayCompositorPresentationEventsExp_(modelHelper);if(eventsExp&&eventsExp.length>0){addFrameTimeHistogramsHelper(histograms,model,segments,eventsExp,'exp_',false,minFrameCount);}}
 function addFrameTimeHistogramsHelper(histograms,model,segments,events,prefix,addCpuMetrics,minFrameCount){const frameSegments=computeFrameSegments_(events,segments,minFrameCount);addBasicFrameTimeHistograms_(histograms,frameSegments,prefix+'');if(addCpuMetrics){tr.metrics.rendering.addCpuUtilizationHistograms(histograms,model,frameSegments,(thread,segment)=>thread.getCpuTimeForRange(segment.boundsRange),category=>`thread_${category}_cpu_time_per_frame`,'CPU cores of a thread group per frame');tr.metrics.rendering.addCpuUtilizationHistograms(histograms,model,frameSegments,(thread,segment)=>thread.getNumToplevelSlicesForRange(segment.boundsRange),category=>`tasks_per_frame_${category}`,'Number of tasks of a thread group per frame');let totalWallTime=0;let totalCpuTime=0;for(const segment of frameSegments){for(const thread of model.getAllThreads()){totalCpuTime+=thread.getCpuTimeForRange(segment.boundsRange);totalWallTime+=thread.getWallTimeForRange(segment.boundsRange);}}
 histograms.createHistogram('cpu_wall_time_ratio',tr.b.Unit.byName.unitlessNumber_biggerIsBetter,totalCpuTime/totalWallTime,{description:'Ratio of total cpu-time vs. wall-time.',summaryOptions:{},});}
-const refreshPeriod=getRefreshPeriod(model,frameSegments.map(fs=>fs.boundsRange));frameSegments.forEach(fs=>fs.updateLength(refreshPeriod));const validFrames=frameSegments.filter(fs=>fs.length>=MIN_FRAME_LENGTH);const totalFrameDuration=tr.b.math.Statistics.sum(frameSegments,fs=>fs.duration);addJankCountHistograms(histograms,validFrames,prefix);const frameLengths=validFrames.map(frame=>frame.length);histograms.createHistogram(prefix+'frame_lengths',tr.b.Unit.byName.unitlessNumber_smallerIsBetter,frameLengths,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,5,20),summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,description:'Frame times in vsyncs.'});histograms.createHistogram(prefix+'avg_surface_fps',tr.b.Unit.byName.unitlessNumber_biggerIsBetter,frameLengths.length/tr.b.convertUnit(totalFrameDuration,tr.b.UnitScale.TIME.MILLI_SEC,tr.b.UnitScale.TIME.SEC),{description:'Average frames per second.',summaryOptions:{},});}
+const refreshPeriod=getRefreshPeriod(model,frameSegments.map(fs=>fs.boundsRange));frameSegments.forEach(fs=>fs.updateLength(refreshPeriod));const validFrames=frameSegments.filter(fs=>fs.length>=MIN_FRAME_LENGTH);const totalFrameDuration=tr.b.math.Statistics.sum(frameSegments,fs=>fs.duration);addJankCountHistograms(histograms,validFrames,prefix);const frameLengths=validFrames.map(frame=>frame.length);histograms.createHistogram(prefix+'avg_surface_fps',tr.b.Unit.byName.unitlessNumber_biggerIsBetter,frameLengths.length/tr.b.convertUnit(totalFrameDuration,tr.b.UnitScale.TIME.MILLI_SEC,tr.b.UnitScale.TIME.SEC),{description:'Average frames per second.',summaryOptions:{},});}
 function addUIFrameTimeHistograms(histograms,model,segments,opt_minFrameCount){const minFrameCount=opt_minFrameCount||MIN_FRAME_COUNT;const events=getUIPresentationEvents_(model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper));if(events.length===0)return;const frameSegments=computeFrameSegments_(events,segments,minFrameCount);addBasicFrameTimeHistograms_(histograms,frameSegments,'ui_');}
 function addJankCountHistograms(histograms,validFrames,prefix){const jankEvents=[];for(let i=1;i<validFrames.length;i++){const change=Math.round((validFrames[i].length-validFrames[i-1].length));if(change>0&&change<PAUSE_THRESHOLD){jankEvents.push(validFrames[i].event);}}
 const jankCount=jankEvents.length;const diagnostics=new tr.v.d.DiagnosticMap();diagnostics.set('events',new tr.v.d.RelatedEventSet(jankEvents));diagnostics.set('timestamps',new tr.v.d.GenericSet(jankEvents.map(e=>e.start)));const histogram=histograms.createHistogram(prefix+'jank_count',tr.b.Unit.byName.count_smallerIsBetter,{value:jankCount,diagnostics},{description:'Number of changes in frame rate.',summaryOptions:{},});}
@@ -8432,7 +8459,7 @@
 const rendererHelper=chromeHelper.rendererHelpers[expectation.renderProcess.pid];addRectsBasedSpeedIndexSample(rectsBasedSpeedIndexSamples,rendererHelper,expectation.navigationStart.start,expectation.duration,expectation.navigationStart.args.frame);}
 return rectsBasedSpeedIndexSamples;}
 function rectsBasedSpeedIndexMetric(histograms,model){const rectsBasedSpeedIndexHistogram=histograms.createHistogram('rectsBasedSpeedIndex',timeDurationInMs_smallerIsBetter,[],{binBoundaries:BIN_BOUNDARIES,description:' the average time at which visible parts of the'+' page are displayed (in ms).',summaryOptions:SUMMARY_OPTIONS,});const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const samples=collectRectsBasedSpeedIndexSamplesFromLoadExpectations(model,chromeHelper);for(const sample of samples){rectsBasedSpeedIndexHistogram.addSample(sample.value);}}
-tr.metrics.MetricRegistry.register(rectsBasedSpeedIndexMetric);return{rectsBasedSpeedIndexMetric};});'use strict';tr.exportTo('tr.metrics.sh',function(){const LONG_TASK_THRESHOLD_MS=50;const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const unitlessNumber_smallerIsBetter=tr.b.Unit.byName.unitlessNumber_smallerIsBetter;const RelatedEventSet=tr.v.d.RelatedEventSet;const hasCategoryAndName=tr.metrics.sh.hasCategoryAndName;const EventFinderUtils=tr.e.chrome.EventFinderUtils;function createBreakdownDiagnostic(breakdownTree){const breakdownDiagnostic=new tr.v.d.Breakdown();breakdownDiagnostic.colorScheme=tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;for(const label in breakdownTree){breakdownDiagnostic.set(label,breakdownTree[label].total);}
+tr.metrics.MetricRegistry.register(rectsBasedSpeedIndexMetric);return{rectsBasedSpeedIndexMetric};});'use strict';tr.exportTo('tr.v.d',function(){const ALERT_GROUPS={CPU_USAGE:'cpu_usage',LOADING_PAINT:'loading_paint',LOADING_INTERACTIVITY:'loading_interactivity',LOADING_LAYOUT:'loading_layout',};return{ALERT_GROUPS,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const LONG_TASK_THRESHOLD_MS=50;const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const unitlessNumber_smallerIsBetter=tr.b.Unit.byName.unitlessNumber_smallerIsBetter;const RelatedEventSet=tr.v.d.RelatedEventSet;const hasCategoryAndName=tr.metrics.sh.hasCategoryAndName;const EventFinderUtils=tr.e.chrome.EventFinderUtils;function createBreakdownDiagnostic(breakdownTree){const breakdownDiagnostic=new tr.v.d.Breakdown();breakdownDiagnostic.colorScheme=tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;for(const label in breakdownTree){breakdownDiagnostic.set(label,breakdownTree[label].total);}
 return breakdownDiagnostic;}
 const LOADING_METRIC_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,1e3,20).addLinearBins(3e3,20).addExponentialBins(20e3,20);const TIME_TO_INTERACTIVE_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,40e3,35).addExponentialBins(80e3,15);const LAYOUT_SHIFT_SCORE_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,50,25);const SUMMARY_OPTIONS={avg:true,count:false,max:true,min:true,std:true,sum:false,};function findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ts){const objects=rendererHelper.process.objects;const frameLoaderInstances=objects.instancesByTypeName_.FrameLoader;if(frameLoaderInstances===undefined)return undefined;let snapshot;for(const instance of frameLoaderInstances){if(!instance.isAliveAt(ts))continue;const maybeSnapshot=instance.getSnapshotAt(ts);if(frameIdRef!==maybeSnapshot.args.frame.id_ref)continue;snapshot=maybeSnapshot;}
 return snapshot;}
@@ -8473,7 +8500,7 @@
 function addSamplesToHistogram(samples,histogram,histograms){for(const sample of samples){histogram.addSample(sample.value,sample.diagnostics);if(histogram.name!=='timeToFirstContentfulPaint')continue;if(!sample.breakdownTree)continue;for(const[category,breakdown]of Object.entries(sample.breakdownTree)){const relatedName=`${histogram.name}:${category}`;let relatedHist=histograms.getHistogramsNamed(relatedName)[0];if(!relatedHist){relatedHist=histograms.createHistogram(relatedName,histogram.unit,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,summaryOptions:{count:false,max:false,min:false,sum:false,},});let relatedNames=histogram.diagnostics.get('breakdown');if(!relatedNames){relatedNames=new tr.v.d.RelatedNameMap();histogram.diagnostics.set('breakdown',relatedNames);}
 relatedNames.set(category,relatedName);}
 relatedHist.addSample(breakdown.total,{breakdown:tr.v.d.Breakdown.fromEntries(Object.entries(breakdown.events)),});}}}
-function loadingMetric(histograms,model){const firstPaintHistogram=histograms.createHistogram('timeToFirstPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first paint',summaryOptions:SUMMARY_OPTIONS,});const firstContentfulPaintHistogram=histograms.createHistogram('timeToFirstContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first contentful paint',summaryOptions:SUMMARY_OPTIONS,});const firstContentfulPaintCpuTimeHistogram=histograms.createHistogram('cpuTimeToFirstContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'CPU time to first contentful paint',summaryOptions:SUMMARY_OPTIONS,});const onLoadHistogram=histograms.createHistogram('timeToOnload',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to onload. '+'This is temporary metric used for PCv1/v2 sanity checking',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintHistogram=histograms.createHistogram('timeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintCpuTimeHistogram=histograms.createHistogram('cpuTimeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'CPU time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,});const timeToInteractiveHistogram=histograms.createHistogram('timeToInteractive',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to Interactive',summaryOptions:SUMMARY_OPTIONS,});const totalBlockingTimeHistogram=histograms.createHistogram('totalBlockingTime',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Total Blocking Time',summaryOptions:SUMMARY_OPTIONS,});const timeToFirstCpuIdleHistogram=histograms.createHistogram('timeToFirstCpuIdle',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to First CPU Idle',summaryOptions:SUMMARY_OPTIONS,});const aboveTheFoldLoadedToVisibleHistogram=histograms.createHistogram('aboveTheFoldLoadedToVisible',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time from first visible to load for AMP pages only.',summaryOptions:SUMMARY_OPTIONS,});const firstViewportReadyHistogram=histograms.createHistogram('timeToFirstViewportReady',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time from navigation to load for AMP pages only. ',summaryOptions:SUMMARY_OPTIONS,});const largestImagePaintHistogram=histograms.createHistogram('largestImagePaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'Time to Largest Image Paint',summaryOptions:SUMMARY_OPTIONS,});const largestTextPaintHistogram=histograms.createHistogram('largestTextPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'Time to Largest Text Paint',summaryOptions:SUMMARY_OPTIONS,});const largestContentfulPaintHistogram=histograms.createHistogram('largestContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'Time to Largest Contentful Paint',summaryOptions:SUMMARY_OPTIONS,});const layoutShiftHistogram=histograms.createHistogram('mainFrameCumulativeLayoutShift',unitlessNumber_smallerIsBetter,[],{binBoundaries:LAYOUT_SHIFT_SCORE_BOUNDARIES,description:'Main Frame Document Cumulative Layout Shift Score',summaryOptions:SUMMARY_OPTIONS,});const navigationStartHistogram=histograms.createHistogram('navigationStart',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'navigationStart',summaryOptions:SUMMARY_OPTIONS,});tr.metrics.sh.rectsBasedSpeedIndexMetric(histograms,model);const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const pid in chromeHelper.rendererHelpers){const rendererHelper=chromeHelper.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;const samplesSet=collectLoadingMetricsForRenderer(rendererHelper);const lcpSamples=findLargestContentfulPaintHistogramSamples(chromeHelper.browserHelper.mainThread.sliceGroup.slices);addSamplesToHistogram(lcpSamples,largestContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstPaintSamples,firstPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintSamples,firstContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintCpuTimeSamples,firstContentfulPaintCpuTimeHistogram,histograms);addSamplesToHistogram(samplesSet.onLoadSamples,onLoadHistogram,histograms);addSamplesToHistogram(samplesSet.aboveTheFoldLoadedToVisibleSamples,aboveTheFoldLoadedToVisibleHistogram,histograms);addSamplesToHistogram(samplesSet.firstViewportReadySamples,firstViewportReadyHistogram,histograms);addSamplesToHistogram(samplesSet.largestImagePaintSamples,largestImagePaintHistogram,histograms);addSamplesToHistogram(samplesSet.largestTextPaintSamples,largestTextPaintHistogram,histograms);addSamplesToHistogram(samplesSet.layoutShiftSamples,layoutShiftHistogram,histograms);addSamplesToHistogram(samplesSet.navigationStartSamples,navigationStartHistogram,histograms);}
+function loadingMetric(histograms,model){const firstPaintHistogram=histograms.createHistogram('timeToFirstPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first paint',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],});const firstContentfulPaintHistogram=histograms.createHistogram('timeToFirstContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first contentful paint',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],});const firstContentfulPaintCpuTimeHistogram=histograms.createHistogram('cpuTimeToFirstContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'CPU time to first contentful paint',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],});const onLoadHistogram=histograms.createHistogram('timeToOnload',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to onload. '+'This is temporary metric used for PCv1/v2 sanity checking',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintHistogram=histograms.createHistogram('timeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],});const firstMeaningfulPaintCpuTimeHistogram=histograms.createHistogram('cpuTimeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'CPU time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],});const timeToInteractiveHistogram=histograms.createHistogram('timeToInteractive',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to Interactive',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY],});const totalBlockingTimeHistogram=histograms.createHistogram('totalBlockingTime',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Total Blocking Time',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY],});const timeToFirstCpuIdleHistogram=histograms.createHistogram('timeToFirstCpuIdle',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to First CPU Idle',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_INTERACTIVITY],});const aboveTheFoldLoadedToVisibleHistogram=histograms.createHistogram('aboveTheFoldLoadedToVisible',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time from first visible to load for AMP pages only.',summaryOptions:SUMMARY_OPTIONS,});const firstViewportReadyHistogram=histograms.createHistogram('timeToFirstViewportReady',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time from navigation to load for AMP pages only. ',summaryOptions:SUMMARY_OPTIONS,});const largestImagePaintHistogram=histograms.createHistogram('largestImagePaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'Time to Largest Image Paint',summaryOptions:SUMMARY_OPTIONS,});const largestTextPaintHistogram=histograms.createHistogram('largestTextPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'Time to Largest Text Paint',summaryOptions:SUMMARY_OPTIONS,});const largestContentfulPaintHistogram=histograms.createHistogram('largestContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'Time to Largest Contentful Paint',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_PAINT],});const layoutShiftHistogram=histograms.createHistogram('mainFrameCumulativeLayoutShift',unitlessNumber_smallerIsBetter,[],{binBoundaries:LAYOUT_SHIFT_SCORE_BOUNDARIES,description:'Main Frame Document Cumulative Layout Shift Score',summaryOptions:SUMMARY_OPTIONS,alertGrouping:[tr.v.d.ALERT_GROUPS.LOADING_LAYOUT],});const navigationStartHistogram=histograms.createHistogram('navigationStart',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'navigationStart',summaryOptions:SUMMARY_OPTIONS,});tr.metrics.sh.rectsBasedSpeedIndexMetric(histograms,model);const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const pid in chromeHelper.rendererHelpers){const rendererHelper=chromeHelper.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;const samplesSet=collectLoadingMetricsForRenderer(rendererHelper);const lcpSamples=findLargestContentfulPaintHistogramSamples(chromeHelper.browserHelper.mainThread.sliceGroup.slices);addSamplesToHistogram(lcpSamples,largestContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstPaintSamples,firstPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintSamples,firstContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintCpuTimeSamples,firstContentfulPaintCpuTimeHistogram,histograms);addSamplesToHistogram(samplesSet.onLoadSamples,onLoadHistogram,histograms);addSamplesToHistogram(samplesSet.aboveTheFoldLoadedToVisibleSamples,aboveTheFoldLoadedToVisibleHistogram,histograms);addSamplesToHistogram(samplesSet.firstViewportReadySamples,firstViewportReadyHistogram,histograms);addSamplesToHistogram(samplesSet.largestImagePaintSamples,largestImagePaintHistogram,histograms);addSamplesToHistogram(samplesSet.largestTextPaintSamples,largestTextPaintHistogram,histograms);addSamplesToHistogram(samplesSet.layoutShiftSamples,layoutShiftHistogram,histograms);addSamplesToHistogram(samplesSet.navigationStartSamples,navigationStartHistogram,histograms);}
 const samplesSet=collectMetricsFromLoadExpectations(model,chromeHelper);addSamplesToHistogram(samplesSet.firstMeaningfulPaintSamples,firstMeaningfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstMeaningfulPaintCpuTimeSamples,firstMeaningfulPaintCpuTimeHistogram,histograms);addSamplesToHistogram(samplesSet.interactiveSamples,timeToInteractiveHistogram,histograms);addSamplesToHistogram(samplesSet.firstCpuIdleSamples,timeToFirstCpuIdleHistogram,histograms);addSamplesToHistogram(samplesSet.totalBlockingTimeSamples,totalBlockingTimeHistogram,histograms);}
 tr.metrics.MetricRegistry.register(loadingMetric);return{loadingMetric,createBreakdownDiagnostic};});'use strict';tr.exportTo('tr.metrics',function(){const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY=tr.v.HistogramBinBoundaries.createExponential(1,1000,50);function spaNavigationMetric(histograms,model){const histogram=new tr.v.Histogram('spaNavigationStartToFpDuration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY);histogram.description='Latency between the input event causing'+' a SPA navigation and the first paint event after it';histogram.customizeSummaryOptions({count:false,sum:false,});const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper){return;}
 const rendererHelpers=modelHelper.rendererHelpers;if(!rendererHelpers){return;}
@@ -8488,7 +8515,7 @@
 let processCpuTime=0;for(const tid in process.threads){const thread=process.threads[tid];processCpuTime+=thread.getCpuTimeForRange(rangeOfInterest);}
 allProcessCpuTime+=processCpuTime;}
 let normalizedAllProcessCpuTime=0;if(rangeOfInterest.duration>0){normalizedAllProcessCpuTime=allProcessCpuTime/rangeOfInterest.duration;}
-const unit=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;const cpuTimeHist=new tr.v.Histogram('cpu_time_percentage',unit,CPU_TIME_PERCENTAGE_BOUNDARIES);cpuTimeHist.description='Percent CPU utilization, normalized against a single core. Can be '+'greater than 100% if machine has multiple cores.';cpuTimeHist.customizeSummaryOptions({avg:true,count:false,max:false,min:false,std:false,sum:false});cpuTimeHist.addSample(normalizedAllProcessCpuTime);histograms.addHistogram(cpuTimeHist);}
+const unit=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;const cpuTimeHist=new tr.v.Histogram('cpu_time_percentage',unit,CPU_TIME_PERCENTAGE_BOUNDARIES);cpuTimeHist.description='Percent CPU utilization, normalized against a single core. Can be '+'greater than 100% if machine has multiple cores.';cpuTimeHist.setAlertGrouping([tr.v.d.ALERT_GROUPS.CPU_USAGE]);cpuTimeHist.customizeSummaryOptions({avg:true,count:false,max:false,min:false,std:false,sum:false});cpuTimeHist.addSample(normalizedAllProcessCpuTime);histograms.addHistogram(cpuTimeHist);}
 tr.metrics.MetricRegistry.register(cpuTimeMetric,{supportsRangeOfInterest:true});return{cpuTimeMetric,};});'use strict';tr.exportTo('tr.v',function(){class HistogramDeserializer{static deserialize(data){const deserializer=new HistogramDeserializer(data[0],data[1]);return data.slice(2).map(datum=>tr.v.Histogram.deserialize(datum,deserializer));}
 constructor(objects,diagnostics){this.objects_=objects;this.diagnostics_=[];for(const[type,diagnosticsByName]of Object.entries(diagnostics||{})){for(const[name,diagnosticsById]of Object.entries(diagnosticsByName)){for(const[id,data]of Object.entries(diagnosticsById)){const diagnostic=tr.v.d.Diagnostic.deserialize(type,data,this);this.diagnostics_[parseInt(id)]={name,diagnostic};}}}}
 getObject(id){return this.objects_[id];}
@@ -8924,13 +8951,16 @@
 const rendererHelper=chromeHelper.rendererHelpers[expectation.renderProcess.pid];addSpeedIndexScreenshotsBasedSample(speedIndexScreenshotsBasedSamples,expectation.navigationStart,expectation.duration,chromeHelper.browserHelper);}
 return speedIndexScreenshotsBasedSamples;}
 function screenshotsBasedSpeedIndexMetric(histograms,model){const speedIndexScreenshotsBasedHistogram=histograms.createHistogram('speedIndexScreenshotsBased',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'The average time at which visible parts of the'+' page are displayed.',summaryOptions:SUMMARY_OPTIONS,});const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const samples=collectSpeedIndexSamplesFromLoadExpectations(model,chromeHelper);for(const sample of samples){speedIndexScreenshotsBasedHistogram.addSample(sample.value);}}
-tr.metrics.MetricRegistry.register(screenshotsBasedSpeedIndexMetric);return{screenshotsBasedSpeedIndexMetric};});'use strict';tr.exportTo('tr.metrics.sh',function(){function webviewStartupMetric(histograms,model){const startupWallHist=new tr.v.Histogram('webview_startup_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupWallHist.description='WebView startup wall time';const startupCPUHist=new tr.v.Histogram('webview_startup_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupCPUHist.description='WebView startup CPU time';const loadWallHist=new tr.v.Histogram('webview_url_load_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadWallHist.description='WebView blank URL load wall time';const loadCPUHist=new tr.v.Histogram('webview_url_load_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadCPUHist.description='WebView blank URL load CPU time';for(const slice of model.getDescendantEvents()){if(!(slice instanceof tr.model.ThreadSlice))continue;if(slice.title==='WebViewStartupInterval'){startupWallHist.addSample(slice.duration);startupCPUHist.addSample(slice.cpuDuration);}
+tr.metrics.MetricRegistry.register(screenshotsBasedSpeedIndexMetric);return{screenshotsBasedSpeedIndexMetric};});'use strict';tr.exportTo('tr.metrics.sh',function(){function weblayerStartupMetric(histograms,model){const startupWallHist=new tr.v.Histogram('weblayer_startup_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupWallHist.description='WebLayer startup wall time';const loadWallHist=new tr.v.Histogram('weblayer_url_load_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadWallHist.description='WebLayer blank URL load wall time';for(const slice of model.getDescendantEvents()){if(!(slice instanceof tr.model.ThreadSlice))continue;if(slice.title==='WebLayerStartupInterval'){startupWallHist.addSample(slice.duration);}
+if(slice.title==='WebLayerBlankUrlLoadInterval'){loadWallHist.addSample(slice.duration);}}
+histograms.addHistogram(startupWallHist);histograms.addHistogram(loadWallHist);}
+tr.metrics.MetricRegistry.register(weblayerStartupMetric);return{weblayerStartupMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function webviewStartupMetric(histograms,model){const startupWallHist=new tr.v.Histogram('webview_startup_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupWallHist.description='WebView startup wall time';const startupCPUHist=new tr.v.Histogram('webview_startup_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupCPUHist.description='WebView startup CPU time';const loadWallHist=new tr.v.Histogram('webview_url_load_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadWallHist.description='WebView blank URL load wall time';const loadCPUHist=new tr.v.Histogram('webview_url_load_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadCPUHist.description='WebView blank URL load CPU time';for(const slice of model.getDescendantEvents()){if(!(slice instanceof tr.model.ThreadSlice))continue;if(slice.title==='WebViewStartupInterval'){startupWallHist.addSample(slice.duration);startupCPUHist.addSample(slice.cpuDuration);}
 if(slice.title==='WebViewBlankUrlLoadInterval'){loadWallHist.addSample(slice.duration);loadCPUHist.addSample(slice.cpuDuration);}}
 histograms.addHistogram(startupWallHist);histograms.addHistogram(startupCPUHist);histograms.addHistogram(loadWallHist);histograms.addHistogram(loadCPUHist);}
 tr.metrics.MetricRegistry.register(webviewStartupMetric);return{webviewStartupMetric,};});'use strict';tr.exportTo('tr.metrics.tabs',function(){function tabsMetric(histograms,model,opt_options){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper){return;}
 const tabSwitchRequestDelays=[];const TAB_SWITCHING_REQUEST_TITLE='TabSwitchVisibilityRequest';let startTabSwitchVisibilityRequest=Number.MAX_SAFE_INTEGER;for(const helper of chromeHelper.browserHelpers){if(!helper.mainThread)continue;for(const slice of helper.mainThread.asyncSliceGroup.slices){if(slice.title===TAB_SWITCHING_REQUEST_TITLE&&!slice.error){tabSwitchRequestDelays.push(slice.duration);if(slice.start<startTabSwitchVisibilityRequest){startTabSwitchVisibilityRequest=slice.start;}}}}
 histograms.createHistogram('tab_switching_request_delay',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tabSwitchRequestDelays,{description:'Delay before tab-request is made',summaryOptions:{sum:false}});const tabSwitchLatencies=[];const TAB_SWITCHING_SLICE_TITLE='TabSwitching::Latency';function extractLatencyFromHelpers(helpers,legacy){for(const helper of helpers){if(!helper.mainThread){continue;}
-const thread=helper.mainThread;for(const slice of thread.asyncSliceGroup.slices){if(slice.title===TAB_SWITCHING_SLICE_TITLE&&(legacy||slice.args.latency)&&slice.start>startTabSwitchVisibilityRequest){tabSwitchLatencies.push(legacy?slice.duration:slice.args.latency);}}}}
+const thread=helper.mainThread;for(const slice of thread.asyncSliceGroup.slices){if(slice.title===TAB_SWITCHING_SLICE_TITLE&&(legacy||slice.args.latency)&&slice.start>startTabSwitchVisibilityRequest-1){tabSwitchLatencies.push(legacy?slice.duration:slice.args.latency);}}}}
 extractLatencyFromHelpers(chromeHelper.browserHelpers);extractLatencyFromHelpers(Object.values(chromeHelper.rendererHelpers));if(tabSwitchLatencies.length===0){extractLatencyFromHelpers(chromeHelper.browserHelpers,true);}
 histograms.createHistogram('tab_switching_latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tabSwitchLatencies,{description:'Tab switching time in ms',summaryOptions:{sum:false}});}
 tr.metrics.MetricRegistry.register(tabsMetric,{supportsRangeOfInterest:false,});return{tabsMetric,};});'use strict';tr.exportTo('tr.metrics',function(){const MEMORY_INFRA_TRACING_CATEGORY='disabled-by-default-memory-infra';const TIME_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1e-3,1e5,30);const BYTE_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e9,30);const COUNT_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e5,30);const SUMMARY_OPTIONS=tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;function addMemoryInfraHistograms(histograms,model,categoryNamesToTotalEventSizes){const memoryDumpCount=model.globalMemoryDumps.length;if(memoryDumpCount===0)return;let totalOverhead=0;let nonMemoryInfraThreadOverhead=0;const overheadByProvider={};for(const process of Object.values(model.processes)){for(const thread of Object.values(process.threads)){for(const slice of Object.values(thread.sliceGroup.slices)){if(slice.category!==MEMORY_INFRA_TRACING_CATEGORY)continue;totalOverhead+=slice.duration;if(thread.name!=='MemoryInfra'){nonMemoryInfraThreadOverhead+=slice.duration;}
@@ -8948,20 +8978,17 @@
 function mergeBins_(x,y){x.sum+=y.sum;const allBins=[...x.bins,...y.bins];allBins.sort((a,b)=>a.min-b.min);x.bins=[];let last=undefined;for(const bin of allBins){if(last!==undefined&&bin.min===last.min){if(last.max!==bin.max)throw new Error('Incompatible bins');if(bin.count===0)continue;last.count+=bin.count;for(const event of bin.events){last.events.add(event);}
 last.processes.addDiagnostic(bin.processes);}else{if(last!==undefined&&bin.min<last.max){throw new Error('Incompatible bins');}
 x.bins.push(bin);last=bin;}}}
-function subtractBins_(x,y){x.sum-=y.sum;let p1=0;let p2=0;while(p2<y.bins.length){while(p1<x.bins.length&&x.bins[p1].min!==y.bins[p2].min){p1++;}
-if(p1===x.bins.length)throw new Error('Cannot subtract');if(x.bins[p1].max!==y.bins[p2].max){throw new Error('Incompatible bins');}
-if(x.bins[p1].count<y.bins[p2].count){throw new Error('Cannot subtract');}
-x.bins[p1].count-=y.bins[p2].count;for(const event of y.bins[p2].events){x.bins[p1].events.add(event);}
-const processName=tr.b.getOnlyElement(x.bins[p1].processes)[0];x.bins[p1].processes.set(processName,x.bins[p1].count);p2++;}}
 function getHistogramUnit_(name){return tr.b.Unit.byName.unitlessNumber_smallerIsBetter;}
+function getIsHistogramBinsLinear_(histogramName){return histogramName.startsWith('Graphics.Smoothness.Throughput')||histogramName.startsWith('Memory.Memory.GPU.PeakMemoryUsage');}
 function getHistogramBoundaries_(name){if(name.startsWith('Event.Latency.Scroll')){return tr.v.HistogramBinBoundaries.createExponential(1e3,1e5,50);}
 if(name.startsWith('Graphics.Smoothness.Throughput')){return tr.v.HistogramBinBoundaries.createLinear(0,100,101);}
 if(name.startsWith('Memory.Memory.GPU.PeakMemoryUsage')){return tr.v.HistogramBinBoundaries.createLinear(0,1e6,100);}
 return tr.v.HistogramBinBoundaries.createExponential(1e-3,1e3,50);}
 function umaMetric(histograms,model){const histogramValues=new Map();const nameCounts=new Map();for(const process of model.getAllProcesses()){const histogramEvents=new Map();for(const event of process.instantEvents){if(event.title!=='UMAHistogramSamples')continue;const name=event.args.name;const events=histogramEvents.get(name)||[];if(!histogramEvents.has(name))histogramEvents.set(name,events);events.push(event);}
-let processName=tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);nameCounts.set(processName,(nameCounts.get(processName)||0)+1);processName=`${processName}_${nameCounts.get(processName)}`;for(const[name,events]of histogramEvents){const values=histogramValues.get(name)||{sum:0,bins:[]};if(!histogramValues.has(name))histogramValues.set(name,values);const endValues=parseBuckets_(events[events.length-1],processName);if(events.length===1){mergeBins_(values,endValues);}else if(events.length===2){subtractBins_(endValues,parseBuckets_(events[0],processName));mergeBins_(values,endValues);}else{throw new Error('There should be at most two snapshots of an UMA '+'histogram in each process');}}}
-for(const[name,values]of histogramValues){const histogram=new tr.v.Histogram(name,getHistogramUnit_(name),getHistogramBoundaries_(name));let sumOfMiddles=0;let sumOfBinLengths=0;for(const bin of values.bins){sumOfMiddles+=bin.count*(bin.min+bin.max)/2;sumOfBinLengths+=bin.count*(bin.max-bin.min);}
-const shift=(values.sum-sumOfMiddles)/sumOfBinLengths;if(Math.abs(shift)>0.5)throw new Error('Samples sum is wrong');for(const bin of values.bins){if(bin.count===0)continue;const shiftedValue=(bin.min+bin.max)/2+shift*(bin.max-bin.min);for(const[processName,count]of bin.processes){bin.processes.set(processName,shiftedValue*count/bin.count);}
+let processName=tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);nameCounts.set(processName,(nameCounts.get(processName)||0)+1);processName=`${processName}_${nameCounts.get(processName)}`;for(const[name,events]of histogramEvents){const values=histogramValues.get(name)||{sum:0,bins:[]};if(!histogramValues.has(name))histogramValues.set(name,values);const endValues=parseBuckets_(events[events.length-1],processName);if(events.length===1){mergeBins_(values,endValues,name);}else{throw new Error('There should be at most one snapshot of UMA '+`histogram for ${name} in each process.`);}}}
+for(const[name,values]of histogramValues){const histogram=new tr.v.Histogram(name,getHistogramUnit_(name),getHistogramBoundaries_(name));const isLinear=getIsHistogramBinsLinear_(name);let sumOfMiddles=0;let sumOfBinLengths=0;for(const bin of values.bins){sumOfMiddles+=bin.count*(bin.min+bin.max)/2;sumOfBinLengths+=bin.count*(bin.max-bin.min);}
+const shift=(values.sum-sumOfMiddles)/sumOfBinLengths;if(isLinear&&Math.abs(shift)>0.5){throw new Error(`Samples sum is wrong for ${name}.`);}
+for(const bin of values.bins){if(bin.count===0)continue;const shiftedValue=(bin.min+bin.max)/2+shift*(bin.max-bin.min);for(const[processName,count]of bin.processes){bin.processes.set(processName,shiftedValue*count/bin.count);}
 for(let i=0;i<bin.count;i++){histogram.addSample(shiftedValue,{processes:bin.processes,events:bin.events});}}
 histograms.addHistogram(histogram);}}
 tr.metrics.MetricRegistry.register(umaMetric,{requiredCategories:['benchmark'],});return{umaMetric,};});'use strict';tr.exportTo('tr.metrics.v8',function(){const CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(4,200,100);function computeExecuteMetrics(histograms,model){const cpuTotalExecution=new tr.v.Histogram('v8_execution_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalExecution.description='cpu total time spent in script execution';const wallTotalExecution=new tr.v.Histogram('v8_execution_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalExecution.description='wall total time spent in script execution';const cpuSelfExecution=new tr.v.Histogram('v8_execution_cpu_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuSelfExecution.description='cpu self time spent in script execution';const wallSelfExecution=new tr.v.Histogram('v8_execution_wall_self',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallSelfExecution.description='wall self time spent in script execution';for(const e of model.findTopmostSlicesNamed('V8.Execute')){cpuTotalExecution.addSample(e.cpuDuration);wallTotalExecution.addSample(e.duration);cpuSelfExecution.addSample(e.cpuSelfTime);wallSelfExecution.addSample(e.selfTime);}
@@ -8981,7 +9008,52 @@
 function computeDeoptimizeCodeMetrics(histograms,model){const cpuTotalDeoptimizeCode=new tr.v.Histogram('v8_deoptimize_code_cpu_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);cpuTotalDeoptimizeCode.description='cpu total time spent in code deoptimization';const wallTotalDeoptimizeCode=new tr.v.Histogram('v8_deoptimize_code_wall_total',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);wallTotalDeoptimizeCode.description='wall total time spent in code deoptimization';for(const e of model.findTopmostSlicesNamed('V8.DeoptimizeCode')){cpuTotalDeoptimizeCode.addSample(e.cpuDuration);wallTotalDeoptimizeCode.addSample(e.duration);}
 histograms.addHistogram(cpuTotalDeoptimizeCode);histograms.addHistogram(wallTotalDeoptimizeCode);}
 function executionMetric(histograms,model){computeExecuteMetrics(histograms,model);computeParseLazyMetrics(histograms,model);computeCompileIgnitionMetrics(histograms,model);computeCompileFullCodeMetrics(histograms,model);computeRecompileMetrics(histograms,model);computeOptimizeCodeMetrics(histograms,model);computeDeoptimizeCodeMetrics(histograms,model);}
-tr.metrics.MetricRegistry.register(executionMetric);return{executionMetric,};});'use strict';tr.exportTo('tr.metrics.v8',function(){const TARGET_FPS=60;const MS_PER_SECOND=1000;const WINDOW_SIZE_MS=MS_PER_SECOND/TARGET_FPS;function gcMetric(histograms,model,options){options=options||{};addDurationOfTopEvents(histograms,model);addTotalDurationOfTopEvents(histograms,model);if(options.include_sub_events){addDurationOfSubEvents(histograms,model);}
+tr.metrics.MetricRegistry.register(executionMetric);return{executionMetric,};});'use strict';tr.exportTo('tr.metrics.v8',function(){const TARGET_FPS=60;const MS_PER_SECOND=1000;const WINDOW_SIZE_MS=MS_PER_SECOND/TARGET_FPS;const EPSILON=1e-6;const METRICS=['v8:gc:cycle:full','v8:gc:cycle:full:cpp','v8:gc:cycle:full:mark','v8:gc:cycle:full:mark:cpp','v8:gc:cycle:full:weak','v8:gc:cycle:full:weak:cpp','v8:gc:cycle:full:sweep','v8:gc:cycle:full:sweep:cpp','v8:gc:cycle:full:compact','v8:gc:cycle:full:compact:cpp','v8:gc:cycle:main_thread:full','v8:gc:cycle:main_thread:full:cpp','v8:gc:cycle:main_thread:full:mark','v8:gc:cycle:main_thread:full:mark:cpp','v8:gc:cycle:main_thread:full:weak','v8:gc:cycle:main_thread:full:weak:cpp','v8:gc:cycle:main_thread:full:sweep','v8:gc:cycle:main_thread:full:sweep:cpp','v8:gc:cycle:main_thread:full:compact','v8:gc:cycle:main_thread:full:compact:cpp','v8:gc:event:main_thread:full:atomic','v8:gc:event:main_thread:full:atomic:cpp','v8:gc:event:main_thread:full:atomic:mark','v8:gc:event:main_thread:full:atomic:mark:cpp','v8:gc:event:main_thread:full:atomic:weak','v8:gc:event:main_thread:full:atomic:weak:cpp','v8:gc:event:main_thread:full:atomic:sweep','v8:gc:event:main_thread:full:atomic:sweep:cpp','v8:gc:event:main_thread:full:atomic:compact','v8:gc:event:main_thread:full:atomic:compact:cpp','v8:gc:event:main_thread:full:incremental','v8:gc:event:main_thread:full:incremental:cpp','v8:gc:event:main_thread:full:incremental:mark','v8:gc:event:main_thread:full:incremental:mark:cpp','v8:gc:event:main_thread:full:incremental:sweep','v8:gc:event:main_thread:full:incremental:sweep:cpp','v8:gc:cycle:young','v8:gc:cycle:main_thread:young',];const V8_FULL_ATOMIC_EVENTS=['V8.GCCompactor','V8.GCFinalizeMC','V8.GCFinalizeMCReduceMemory',];const V8_FULL_MARK_EVENTS=['V8.GC_MC_BACKGROUND_MARKING','V8.GC_MC_MARK','V8.GCIncrementalMarking','V8.GCIncrementalMarkingFinalize','V8.GCIncrementalMarkingStart',];const V8_FULL_COMPACT_EVENTS=['V8.GC_MC_BACKGROUND_EVACUATE_COPY','V8.GC_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS','V8.GC_MC_EVACUATE',];const V8_FULL_SWEEP_EVENTS=['V8.GC_MC_BACKGROUND_SWEEPING','V8.GC_MC_SWEEP',];const V8_FULL_WEAK_EVENTS=['V8.GC_MC_CLEAR',];const V8_YOUNG_EVENTS=['V8.GC_SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL','V8.GCScavenger',];const CPP_GC_FULL_MARK_EVENTS=['BlinkGC.AtomicPauseMarkEpilogue','BlinkGC.AtomicPauseMarkPrologue','BlinkGC.AtomicPauseMarkRoots','BlinkGC.AtomicPauseMarkTransitiveClosure','BlinkGC.ConcurrentMarkingStep','BlinkGC.IncrementalMarkingStartMarking','BlinkGC.IncrementalMarkingStep','BlinkGC.MarkBailOutObjects','BlinkGC.MarkFlushEphemeronPairs','BlinkGC.MarkFlushV8References','BlinkGC.UnifiedMarkingStep','CppGC.AtomicMark','CppGC.IncrementalMark','CppGC.ConcurrentMark',];const CPP_GC_FULL_COMPACT_EVENTS=['BlinkGC.AtomicPauseSweepAndCompact','CppGC.AtomicCompact',];const CPP_GC_FULL_SWEEP_EVENTS=['BlinkGC.CompleteSweep','BlinkGC.ConcurrentSweepingStep','BlinkGC.LazySweepInIdle','BlinkGC.LazySweepOnAllocation','CppGC.AtomicSweep','CppGC.IncrementalSweep','CppGC.ConcurrentSweep',];const CPP_GC_FULL_WEAK_EVENTS=['BlinkGC.MarkWeakProcessing','CppGC.AtomicWeak',];const RULES=[{events:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic',},{events:V8_FULL_MARK_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:mark',},{events:CPP_GC_FULL_MARK_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:mark:cpp',},{events:V8_FULL_MARK_EVENTS,outside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:incremental:mark',},{events:CPP_GC_FULL_MARK_EVENTS,outside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:incremental:mark:cpp',},{events:V8_FULL_COMPACT_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:compact',},{events:CPP_GC_FULL_COMPACT_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:compact:cpp',},{events:V8_FULL_SWEEP_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:sweep',},{events:CPP_GC_FULL_SWEEP_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:sweep:cpp',},{events:V8_FULL_WEAK_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:weak',},{events:CPP_GC_FULL_WEAK_EVENTS,inside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:atomic:weak:cpp',},{events:V8_FULL_SWEEP_EVENTS,outside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:incremental:sweep',},{events:CPP_GC_FULL_SWEEP_EVENTS,outside:V8_FULL_ATOMIC_EVENTS,contribute_to:'full:incremental:sweep:cpp',},{events:V8_YOUNG_EVENTS,contribute_to:'young:atomic',},];const Granularity={CYCLE:'cycle',EVENT:'event',};const ThreadType={MAIN:'main',BACKGROUND:'background',ALL_THREADS:'all_threads',};class Metric{constructor(name){const parts=name.split(':');this.granularity_=parts[2];assert(this.granularity_===Granularity.CYCLE||this.granularity_===Granularity.EVENT);this.thread_=ThreadType.ALL_THREADS;let phasesIndex=3;if(parts[3]==='main_thread'){this.thread_=ThreadType.MAIN;phasesIndex=4;}
+if(parts[3]==='background_threads'){this.thread_=ThreadType.BACKGROUND;phasesIndex=4;}
+this.phases_=parts.slice(phasesIndex);const maxValue=this.isPerCycleMetric()?10000:1000;const boundaries=tr.v.HistogramBinBoundaries.createExponential(0.1,maxValue,100);this.histogram=new tr.v.Histogram(name,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,boundaries);this.histogram.customizeSummaryOptions({avg:true,count:true,max:true,min:false,std:false,sum:this.isPerCycleMetric(),});}
+isPerCycleMetric(){return this.granularity_===Granularity.CYCLE;}
+isMoreGeneralThanOrEqualTo(phases){const phasesSet=new Set(phases.split(':'));return this.phases_.every(phase=>phasesSet.has(phase));}
+contributingEvents(rules,events){const eventsByName=groupBy(events,e=>e.title);function matches(rule,event){function isEnclosing(name){if(!eventsByName.has(name))return false;return eventsByName.get(name).some(e=>encloses(e,event));}
+if(!rule.events.includes(event.title)){return false;}
+if(rule.inside&&!rule.inside.some(isEnclosing)){return false;}
+if(rule.outside&&rule.outside.some(isEnclosing)){return false;}
+return true;}
+const result=[];for(const event of events){const matching=rules.filter(r=>matches(r,event));if(matching.length===0){continue;}
+assert(matching.length===1,`${event.userFriendlyName} matches more than one rule: `+
+JSON.stringify(matching));if(this.isMoreGeneralThanOrEqualTo(matching[0].contribute_to)){result.push(event);}}
+return result;}
+apply(rules,events,threadTypes){const filtered=this.contributingEvents(rules,events);const eventsByThread=groupBy(filtered,e=>e.parentContainer.tid);let flattened=[];for(const[tid,threadEvents]of eventsByThread){if(this.thread_===ThreadType.ALL_THREADS||this.thread_===threadTypes.get(tid)){flattened=flattened.concat(flatten(threadEvents));}}
+if(this.isPerCycleMetric()){let sum=0;for(const event of flattened){sum+=event.cpuDuration;}
+if(flattened.length>0){this.histogram.addSample(sum);}}else{for(const event of flattened){this.histogram.addSample(event.cpuDuration);}}}}
+function assert(condition,message){if(!condition){throw new Error(message);}}
+function groupBy(objects,keyCallback){const result=new Map();for(const object of objects){const group=keyCallback(object);if(result.has(group)){result.get(group).push(object);}else{result.set(group,[object]);}}
+return result;}
+function eventsMentionedIn(rules){let result=[];for(const rule of rules){result=result.concat(rule.events);if(rule.inside){result=result.concat(rule.inside);}
+if(rule.outside){result=result.concat(rule.outside);}}
+return result;}
+function encloses(event1,event2){return(event1.start-EPSILON<=event2.start&&event2.end<=event1.end+EPSILON);}
+function jsExecutionThreadsWithTypes(rendererHelper){const mainThreads=([rendererHelper.mainThread].concat(rendererHelper.dedicatedWorkerThreads).concat(rendererHelper.serviceWorkerThreads));const backgroundThreads=rendererHelper.foregroundWorkerThreads;const threadTypes=new Map();for(const thread of mainThreads){threadTypes.set(thread.tid,ThreadType.MAIN);}
+for(const thread of backgroundThreads){threadTypes.set(thread.tid,ThreadType.BACKGROUND);}
+return[mainThreads.concat(backgroundThreads),threadTypes];}
+function flatten(events){function compareWithEpsilon(a,b){if(a.start<b.start-EPSILON)return-1;if(a.start>b.start+EPSILON)return 1;return b.end-a.end;}
+events.sort(compareWithEpsilon);let last=events[0];const result=[last];for(const e of events){if(e.end>last.end+EPSILON){assert(e.start>=last.end-EPSILON,'Overlapping events: '+
+e.userFriendlyName+' '+
+last.userFriendlyName);result.push(e);last=e;}}
+return result;}
+function groupByEpoch(events){function isV8Event(event){return event.category&&event.category.includes('v8');}
+function getEpoch(event){function checkEpochConsistency(epoch,event){if(epoch===null)return;assert(epoch===event.args.epoch,`${event.userFriendlyName} has epoch ${event.args.epoch} `+`which contradicts the epoch of nested events ${epoch}`);}
+const result={v8:null,cpp:null};while(event){if('epoch'in event.args){if(isV8Event(event)){checkEpochConsistency(result.v8,event);result.v8=event.args.epoch;}else{checkEpochConsistency(result.cpp,event);result.cpp=event.args.epoch;}}
+event=event.parentSlice;}
+return result;}
+const cppToV8=new Map();for(const event of events){const epoch=getEpoch(event);if(epoch.cpp!==null&&epoch.v8!==null){assert(!cppToV8.has(epoch.cpp)||cppToV8.get(epoch.cpp)===epoch.v8,`CppGC epoch ${epoch.cpp} corresponds to two v8 epochs `+`${cppToV8.get(epoch.cpp)} and ${epoch.v8}. `+`Detected at ${event.userFriendlyName}.`);cppToV8.set(epoch.cpp,epoch.v8);}}
+const result=new Map();for(const event of events){const epoch=getEpoch(event);if(epoch.cpp===null&&epoch.v8===null){continue;}
+assert(epoch.cpp===null||cppToV8.has(epoch.cpp),`CppGC epoch ${epoch.cpp} doesn't have the corresponding V8 epoch. `+`Detected at ${event.userFriendlyName}`);const key=epoch.v8===null?cppToV8.get(epoch.cpp):epoch.v8;if(result.has(key)){result.get(key).push(event);}else{result.set(key,[event]);}}
+return result;}
+function addGarbageCollectionMetrics(metricNames,histograms,model){const metrics=metricNames.map(name=>new Metric(name));const gcEventNames=new Set(eventsMentionedIn(RULES));const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){if(rendererHelper.isChromeTracingUI)continue;const[threads,threadTypes]=jsExecutionThreadsWithTypes(rendererHelper);const events=[];for(const thread of threads){for(const event of thread.sliceGroup.childEvents()){if(gcEventNames.has(event.title)){events.push(event);}}}
+for(const cycleEvents of groupByEpoch(events).values()){if(cycleEvents.some(tr.metrics.v8.utils.isForcedGarbageCollectionEvent)){continue;}
+for(const metric of metrics){metric.apply(RULES,cycleEvents,threadTypes);}}}
+for(const metric of metrics){histograms.addHistogram(metric.histogram);}}
+function gcMetric(histograms,model,options){options=options||{};addDurationOfTopEvents(histograms,model);addTotalDurationOfTopEvents(histograms,model);if(options.include_sub_events){addDurationOfSubEvents(histograms,model);}
 addPercentageInV8ExecuteOfTopEvents(histograms,model);addTotalPercentageInV8Execute(histograms,model);addMarkCompactorMutatorUtilization(histograms,model);addTotalMarkCompactorTime(histograms,model);addTotalMarkCompactorMarkingTime(histograms,model);addScavengerSurvivedFromStackEvents(histograms,model);}
 tr.metrics.MetricRegistry.register(gcMetric);const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const percentage_biggerIsBetter=tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;const percentage_smallerIsBetter=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;const bytes_smallerIsBetter=tr.b.Unit.byName.sizeInBytes_smallerIsBetter;const CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,20,200).addExponentialBins(200,100);function createNumericForTopEventTime(name){const n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:true,max:true,min:false,std:true,sum:true,percentile:[0.90]});return n;}
 function createNumericForSubEventTime(name){const n=new tr.v.Histogram(name,timeDurationInMs_smallerIsBetter,CUSTOM_BOUNDARIES);n.customizeSummaryOptions({avg:true,count:false,max:true,min:false,std:false,sum:false,percentile:[0.90]});return n;}
@@ -9005,7 +9077,7 @@
 function addPercentageInV8Execute(histograms,model,name,events){let cpuDurationInV8Execute=0;let cpuDurationTotal=0;events.forEach(function(event){const v8Execute=tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isV8ExecuteEvent);if(v8Execute){cpuDurationInV8Execute+=event.cpuDuration;}
 cpuDurationTotal+=event.cpuDuration;});const percentage=createPercentage(name+'_percentage_in_v8_execute',cpuDurationInV8Execute,cpuDurationTotal,percentage_smallerIsBetter);histograms.addHistogram(percentage);}
 function addMarkCompactorMutatorUtilization(histograms,model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const rendererHelpers=Object.values(chromeHelper.rendererHelpers);tr.metrics.v8.utils.addMutatorUtilization('v8-gc-mark-compactor-mmu',tr.metrics.v8.utils.isNotForcedMarkCompactorEvent,[100],rendererHelpers,histograms);}
-return{gcMetric,WINDOW_SIZE_MS,};});'use strict';tr.exportTo('tr.metrics.v8',function(){const COUNT_CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1000000,50);const DURATION_CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(0.1,10000,50);const SUMMARY_OPTIONS={std:false,count:false,sum:false,min:false,max:false,};function convertMicroToMilli_(time){return tr.b.convertUnit(time,tr.b.UnitPrefixScale.METRIC.MICRO,tr.b.UnitPrefixScale.METRIC.MILLI);}
+return{gcMetric,WINDOW_SIZE_MS,addGarbageCollectionMetrics,};});'use strict';tr.exportTo('tr.metrics.v8',function(){const COUNT_CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1000000,50);const DURATION_CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(0.1,10000,50);const SUMMARY_OPTIONS={std:false,count:false,sum:false,min:false,max:false,};function convertMicroToMilli_(time){return tr.b.convertUnit(time,tr.b.UnitPrefixScale.METRIC.MICRO,tr.b.UnitPrefixScale.METRIC.MILLI);}
 function addDurationHistogram(histogramName,time,histograms){const value=convertMicroToMilli_(time);histograms.createHistogram(`${histogramName}:duration`,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,{value},{binBoundaries:DURATION_CUSTOM_BOUNDARIES,summaryOptions:SUMMARY_OPTIONS,});}
 function addCountHistogram(histogramName,value,histograms){histograms.createHistogram(`${histogramName}:count`,tr.b.Unit.byName.count_smallerIsBetter,{value},{binBoundaries:COUNT_CUSTOM_BOUNDARIES,summaryOptions:SUMMARY_OPTIONS});}
 function runtimeStatsTotalMetric(histograms,model){const v8Slices=tr.metrics.v8.utils.filterEvents(model,ev=>ev instanceof tr.e.v8.V8ThreadSlice);const runtimeGroupCollection=new tr.e.v8.RuntimeStatsGroupCollection();runtimeGroupCollection.addSlices(v8Slices);let overallV8Time=runtimeGroupCollection.totalTime;let overallV8Count=runtimeGroupCollection.totalCount;let mainThreadTime=runtimeGroupCollection.totalTime;let mainThreadCount=runtimeGroupCollection.totalCount;let mainThreadV8Time=runtimeGroupCollection.totalTime;let mainThreadV8Count=runtimeGroupCollection.totalCount;for(const runtimeGroup of runtimeGroupCollection.runtimeGroups){addDurationHistogram(runtimeGroup.name,runtimeGroup.time,histograms);if(runtimeGroup.name==='Blink C++'){overallV8Time-=runtimeGroup.time;mainThreadV8Time-=runtimeGroup.time;}else if(runtimeGroup.name.includes('Background')){mainThreadTime-=runtimeGroup.time;mainThreadV8Time-=runtimeGroup.time;}
@@ -9015,12 +9087,16 @@
 tr.metrics.MetricRegistry.register(runtimeStatsTotalMetric);return{runtimeStatsTotalMetric,};});'use strict';tr.exportTo('tr.metrics.v8',function(){function v8AndMemoryMetrics(histograms,model){tr.metrics.v8.executionMetric(histograms,model);tr.metrics.v8.gcMetric(histograms,model);tr.metrics.sh.memoryMetric(histograms,model,{rangeOfInterest:tr.metrics.v8.utils.rangeForMemoryDumps(model)});}
 tr.metrics.MetricRegistry.register(v8AndMemoryMetrics);return{v8AndMemoryMetrics,};});'use strict';tr.exportTo('tr.metrics.v8',function(){function computeSyncInstantiationTimeMetric(histograms,wasmEvents){if(!wasmEvents.hasOwnProperty('wasm.SyncInstantiate'))return;const wasmSyncInstantiationTimeCPU=new tr.v.Histogram('v8:wasm:sync_instantiate:cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);wasmSyncInstantiationTimeCPU.description='cpu time spent instantiating a WebAssembly module';const wasmSyncInstantiationTimeWall=new tr.v.Histogram('v8:wasm:sync_instantiate:wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);wasmSyncInstantiationTimeWall.description='wall time spent instantiating a WebAssembly module';for(const e of wasmEvents['wasm.SyncInstantiate']){wasmSyncInstantiationTimeCPU.addSample(e.cpuDuration);wasmSyncInstantiationTimeWall.addSample(e.duration);}
 histograms.addHistogram(wasmSyncInstantiationTimeCPU);histograms.addHistogram(wasmSyncInstantiationTimeWall);}
+function computeSyncCompileTimeMetric(histograms,wasmEvents){if(!wasmEvents.hasOwnProperty('wasm.SyncCompile'))return;const wasmSyncCompileTimeCPU=new tr.v.Histogram('v8:wasm:sync_compile:cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);wasmSyncCompileTimeCPU.description='cpu time spent compiling a WebAssembly module synchronously';const wasmSyncCompileTimeWall=new tr.v.Histogram('v8:wasm:sync_compile:wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);wasmSyncCompileTimeWall.description='wall time spent compiling a WebAssembly module synchronously';for(const e of wasmEvents['wasm.SyncCompile']){wasmSyncCompileTimeCPU.addSample(e.cpuDuration);wasmSyncCompileTimeWall.addSample(e.duration);}
+histograms.addHistogram(wasmSyncCompileTimeCPU);histograms.addHistogram(wasmSyncCompileTimeWall);}
 function computeStreamingBaselineCompileTimeMetric(histograms,wasmEvents){if(!wasmEvents.hasOwnProperty('wasm.StartStreamingCompilation')||!wasmEvents.hasOwnProperty('wasm.BaselineFinished')){return;}
-const histogram=new tr.v.Histogram('v8:wasm:streaming_baseline_compilation:wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);const compilationStart=wasmEvents['wasm.StartStreamingCompilation'][0].start;const compilationEnd=wasmEvents['wasm.BaselineFinished'][0].end;histogram.addSample(compilationEnd-compilationStart);histograms.addHistogram(histogram);}
+const histogram=new tr.v.Histogram('v8:wasm:streaming_baseline_compilation:wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);for(const endEvent of wasmEvents['wasm.BaselineFinished']){const compilationEnd=endEvent.end;const startEvent=wasmEvents['wasm.StartStreamingCompilation'].find(e=>e.args.id===endEvent.args.id);if(!startEvent)continue;const compilationStart=startEvent.start;histogram.addSample(compilationEnd-compilationStart);}
+histograms.addHistogram(histogram);}
 function computeCompilationTierupWallTimeMetric(histograms,wasmEvents){if(!wasmEvents.hasOwnProperty('wasm.BaselineFinished')||!wasmEvents.hasOwnProperty('wasm.TopTierFinished')){return;}
-const histogram=new tr.v.Histogram('v8:wasm:compilation_tierup:wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);const tierupStart=wasmEvents['wasm.BaselineFinished'][0].start;const tierupEnd=wasmEvents['wasm.TopTierFinished'][0].end;histogram.addSample(tierupEnd-tierupStart);histograms.addHistogram(histogram);}
+const histogram=new tr.v.Histogram('v8:wasm:compilation_tierup:wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);for(const endEvent of wasmEvents['wasm.TopTierFinished']){const tierupEnd=endEvent.end;const startEvent=wasmEvents['wasm.BaselineFinished'].find(e=>e.args.id===endEvent.args.id);if(!startEvent)continue;const tierupStart=startEvent.start;histogram.addSample(tierupEnd-tierupStart);}
+histograms.addHistogram(histogram);}
 function collectWasmEvents(model){const wasmEvents=tr.metrics.v8.utils.filterAndOrderEvents(model,event=>event.title.startsWith('wasm.'),event=>event.title);return wasmEvents;}
-function wasmMetric(histograms,model){const wasmEvents=collectWasmEvents(model);computeSyncInstantiationTimeMetric(histograms,wasmEvents);computeStreamingBaselineCompileTimeMetric(histograms,wasmEvents);computeCompilationTierupWallTimeMetric(histograms,wasmEvents);}
+function wasmMetric(histograms,model){const wasmEvents=collectWasmEvents(model);computeSyncInstantiationTimeMetric(histograms,wasmEvents);computeSyncCompileTimeMetric(histograms,wasmEvents);computeStreamingBaselineCompileTimeMetric(histograms,wasmEvents);computeCompilationTierupWallTimeMetric(histograms,wasmEvents);}
 tr.metrics.MetricRegistry.register(wasmMetric);return{wasmMetric,};});'use strict';tr.exportTo('tr.metrics.vr',function(){const VR_GL_THREAD_NAME='VrShellGL';function createHistograms(histograms,name,options,hasCpuTime){const createdHistograms={wall:histograms.createHistogram(name+'_wall',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],options)};if(hasCpuTime){createdHistograms.cpu=histograms.createHistogram(name+'_cpu',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],options);}
 return createdHistograms;}
 function frameCycleDurationMetric(histograms,model,opt_options){const histogramsByEventTitle=new Map();const expectationEvents=tr.importer.VR_EXPECTATION_EVENTS;for(const eventName in expectationEvents){const extraInfo=expectationEvents[eventName];histogramsByEventTitle.set(eventName,createHistograms(histograms,extraInfo.histogramName,{description:extraInfo.description},extraInfo.hasCpuTime));}
@@ -9399,9 +9475,9 @@
 this.$.links.appendChild(linkEl);}}});return{};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-related-event-set-span',behaviors:[tr.v.ui.DIAGNOSTIC_SPAN_BEHAVIOR],updateContents_(){Polymer.dom(this).textContent='';const events=new tr.model.EventSet([...this.diagnostic]);const link=document.createElement('tr-ui-a-analysis-link');let label=events.length+' events';if(events.length===1){const event=tr.b.getOnlyElement(events);label=event.title+' ';label+=tr.b.Unit.byName.timeDurationInMs.format(event.duration);}
 link.setSelectionAndContent(events,label);Polymer.dom(this).appendChild(link);}});return{};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-scalar-diagnostic-span',behaviors:[tr.v.ui.DIAGNOSTIC_SPAN_BEHAVIOR],updateContents_(){this.$.scalar.setValueAndUnit(this.diagnostic.value.value,this.diagnostic.value.unit);}});return{};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-unmergeable-diagnostic-set-span',behaviors:[tr.v.ui.DIAGNOSTIC_SPAN_BEHAVIOR],updateContents_(){Polymer.dom(this).textContent='';for(const diagnostic of this.diagnostic){if(diagnostic instanceof tr.v.d.RelatedNameMap)continue;const div=document.createElement('div');div.appendChild(tr.v.ui.createDiagnosticSpan(diagnostic,this.name_,this.histogram_));Polymer.dom(this).appendChild(div);}}});return{};});'use strict';tr.exportTo('tr.v.ui',function(){function findElementNameForDiagnostic(diagnostic){let typeInfo=undefined;let curProto=diagnostic.constructor.prototype;while(curProto){typeInfo=tr.v.d.Diagnostic.findTypeInfo(curProto.constructor);if(typeInfo&&typeInfo.metadata.elementName)break;typeInfo=undefined;curProto=curProto.__proto__;}
 if(typeInfo===undefined){throw new Error(diagnostic.constructor.name+' or a base class must have a registered elementName');}
-const tagName=typeInfo.metadata.elementName;if(tr.ui.b.isUnknownElementName(tagName)){throw new Error('Element not registered: '+tagName);}
-return tagName;}
-function createDiagnosticSpan(diagnostic,name,histogram){const tagName=findElementNameForDiagnostic(diagnostic);const span=document.createElement(tagName);if(span.build===undefined)throw new Error(tagName);span.build(diagnostic,name,histogram);return span;}
+return typeInfo.metadata.elementName;}
+function createDiagnosticSpan(diagnostic,name,histogram){const tagName=findElementNameForDiagnostic(diagnostic);const span=document.createElement(tagName);if(span instanceof HTMLUnknownElement){throw new Error('Element not registered: '+tagName);}
+if(span.build===undefined)throw new Error(tagName);span.build(diagnostic,name,histogram);return span;}
 return{createDiagnosticSpan,};});'use strict';tr.exportTo('tr.v.ui',function(){function makeColumn(title,histogram){return{title,value(map){const diagnostic=map.get(title);if(!diagnostic)return'';return tr.v.ui.createDiagnosticSpan(diagnostic,title,histogram);}};}
 Polymer({is:'tr-v-ui-diagnostic-map-table',created(){this.diagnosticMaps_=undefined;this.histogram_=undefined;this.isMetadata_=false;},set histogram(h){this.histogram_=h;},set isMetadata(m){this.isMetadata_=m;this.$.table.showHeader=!this.isMetadata_;},set diagnosticMaps(maps){this.diagnosticMaps_=maps;this.updateContents_();},get diagnosticMaps(){return this.diagnosticMaps_;},updateContents_(){if(this.isMetadata_&&this.diagnosticMaps_.length!==1){throw new Error('Metadata diagnostic-map-tables require exactly 1 DiagnosticMap');}
 if(this.diagnosticMaps_===undefined||this.diagnosticMaps_.length===0){this.$.table.tableRows=[];this.$.table.tableColumns=[];return;}
@@ -9730,7 +9806,8 @@
 for(let i=0;i<subGroupRows.length;i++){rowsWithHeadings.push({row:subGroupRows[i],heading:(i===0?subGroup.title:'')});}}
 this.setPrebuiltSubRows(this.group_,rowsWithHeadings);}};function stripSlice_(slice){if(slice.subSlices!==undefined&&slice.subSlices.length===1){const subSlice=slice.subSlices[0];if(tr.b.math.approximately(subSlice.start,slice.start,1)&&tr.b.math.approximately(subSlice.duration,slice.duration,1)){return subSlice;}}
 return slice;}
-function makeLevelSubRows_(slices){const rows=[];const putSlice=(slice,level)=>{while(rows.length<=level){rows.push([]);}
+function makeLevelSubRows_(slices){const rows=[];const putSlice=(slice,level)=>{if(slice.hidden){return;}
+while(rows.length<=level){rows.push([]);}
 rows[level].push(slice);};const putSliceRecursively=(slice,level)=>{putSlice(slice,level);if(slice.subSlices!==undefined){for(const subSlice of slice.subSlices){putSliceRecursively(subSlice,level+1);}}};for(const slice of slices){putSliceRecursively(stripSlice_(slice),0);}
 return rows;}
 function groupAsyncSlicesIntoSubRows(slices,opt_skipSort){if(!opt_skipSort){slices.sort((x,y)=>x.start-y.start);}
@@ -9873,9 +9950,9 @@
 for(const labelEl of unsupportedLabelEls){Polymer.dom(this.tabStrip_).appendChild(labelEl);}
 if(previouslyActivePanelType&&supportedPanelTypes.includes(previouslyActivePanelType)){this.activePanelType=previouslyActivePanelType;Polymer.dom(this).setAttribute('expanded',true);}else{if(this.activePanel){Polymer.dom(this.activePanelContainer_).removeChild(this.activePanel);}
 Polymer.dom(this).removeAttribute('expanded');}},get rangeOfInterest(){return this.rangeOfInterest_;},set rangeOfInterest(range){if(range===undefined){throw new Error('Must not be undefined');}
-this.rangeOfInterest_=range;if(this.activePanel){this.activePanel.rangeOfInterest=range;}}});'use strict';Polymer({is:'tr-ui-timeline-view-help-overlay',ready(){const mod=tr.isMac?'cmd ':'ctrl';const spans=Polymer.dom(this.root).querySelectorAll('span.mod');for(let i=0;i<spans.length;i++){Polymer.dom(spans[i]).textContent=mod;}}});'use strict';Polymer({is:'tr-ui-timeline-view-metadata-overlay',created(){this.metadata_=undefined;},ready(){this.$.table.tableColumns=[{title:'name',value:d=>d.name,},{title:'value',value:d=>{const gov=document.createElement('tr-ui-a-generic-object-view');gov.object=d.value;return gov;},}];},get metadata(){return this.metadata_;},set metadata(metadata){this.metadata_=metadata;this.$.table.tableRows=this.metadata_;this.$.table.rebuild();}});'use strict';Polymer({is:'tr-v-ui-preferred-display-unit',ready(){this.preferredTimeDisplayMode_=undefined;},attached(){tr.b.Unit.didPreferredTimeDisplayUnitChange();},detached(){tr.b.Unit.didPreferredTimeDisplayUnitChange();},get preferredTimeDisplayMode(){return this.preferredTimeDisplayMode_;},set preferredTimeDisplayMode(v){if(this.preferredTimeDisplayMode_===v)return;this.preferredTimeDisplayMode_=v;tr.b.Unit.didPreferredTimeDisplayUnitChange();}});'use strict';const POLYFILL_WARNING_MESSAGE='Trace Viewer is running with WebComponentsV0 polyfill, and some '+'features may be broken. As a workaround, you may try running chrome '+'with "--enable-blink-features=ShadowDOMV0,CustomElementsV0,HTMLImports" '+'flag. See crbug.com/1036492.';Polymer({is:'tr-ui-timeline-view',created(){this.trackViewContainer_=undefined;this.queuedModel_=undefined;this.builtPromise_=undefined;this.doneBuilding_=undefined;},attached(){this.async(function(){this.trackViewContainer_=Polymer.dom(this).querySelector('#track_view_container');if(!this.trackViewContainer_){throw new Error('missing trackviewContainer');}
+this.rangeOfInterest_=range;if(this.activePanel){this.activePanel.rangeOfInterest=range;}}});'use strict';Polymer({is:'tr-ui-timeline-view-help-overlay',ready(){const mod=tr.isMac?'cmd ':'ctrl';const spans=Polymer.dom(this.root).querySelectorAll('span.mod');for(let i=0;i<spans.length;i++){Polymer.dom(spans[i]).textContent=mod;}}});'use strict';Polymer({is:'tr-ui-timeline-view-metadata-overlay',created(){this.metadata_=undefined;},ready(){this.$.table.tableColumns=[{title:'name',value:d=>d.name,},{title:'value',value:d=>{const gov=document.createElement('tr-ui-a-generic-object-view');gov.object=d.value;return gov;},}];},get metadata(){return this.metadata_;},set metadata(metadata){this.metadata_=metadata;this.$.table.tableRows=this.metadata_;this.$.table.rebuild();}});'use strict';Polymer({is:'tr-v-ui-preferred-display-unit',ready(){this.preferredTimeDisplayMode_=undefined;},attached(){tr.b.Unit.didPreferredTimeDisplayUnitChange();},detached(){tr.b.Unit.didPreferredTimeDisplayUnitChange();},get preferredTimeDisplayMode(){return this.preferredTimeDisplayMode_;},set preferredTimeDisplayMode(v){if(this.preferredTimeDisplayMode_===v)return;this.preferredTimeDisplayMode_=v;tr.b.Unit.didPreferredTimeDisplayUnitChange();}});'use strict';const POLYFILL_WARNING_MESSAGE='Trace Viewer is running with WebComponentsV0 polyfill, and some '+'features may be broken. See crbug.com/1036492.';Polymer({is:'tr-ui-timeline-view',created(){this.trackViewContainer_=undefined;this.queuedModel_=undefined;this.builtPromise_=undefined;this.doneBuilding_=undefined;},attached(){this.async(function(){this.trackViewContainer_=Polymer.dom(this).querySelector('#track_view_container');if(!this.trackViewContainer_){throw new Error('missing trackviewContainer');}
 if(this.queuedModel_)this.updateContents_();});},ready(){this.tabIndex=0;this.polyfillWarnedOnce_=false;this.titleEl_=this.$.title;this.leftControlsEl_=this.$.left_controls;this.rightControlsEl_=this.$.right_controls;this.collapsingControlsEl_=this.$.collapsing_controls;this.sidePanelContainer_=this.$.side_panel_container;this.brushingStateController_=new tr.c.BrushingStateController(this);this.findCtl_=this.$.view_find_control;this.findCtl_.controller=new tr.ui.FindController(this.brushingStateController_);this.scriptingCtl_=document.createElement('tr-ui-scripting-control');this.scriptingCtl_.controller=new tr.c.ScriptingController(this.brushingStateController_);this.sidePanelContainer_.brushingStateController=this.brushingStateController_;if(window.tr.metrics&&window.tr.metrics.sh&&window.tr.metrics.sh.SystemHealthMetric){this.railScoreSpan_=document.createElement('tr-metrics-ui-sh-system-health-span');Polymer.dom(this.rightControls).appendChild(this.railScoreSpan_);}else{this.railScoreSpan_=undefined;}
-this.flowEventFilter_=this.$.flow_event_filter_dropdown;this.processFilter_=this.$.process_filter_dropdown;this.optionsDropdown_=this.$.view_options_dropdown;this.selectedFlowEvents_=new Set();this.highlightVSync_=false;this.highlightVSyncCheckbox_=tr.ui.b.createCheckBox(this,'highlightVSync','tr.ui.TimelineView.highlightVSync',false,'Highlight VSync');Polymer.dom(this.optionsDropdown_).appendChild(this.highlightVSyncCheckbox_);this.initMetadataButton_();this.initConsoleButton_();this.initHelpButton_();Polymer.dom(this.collapsingControls).appendChild(this.scriptingCtl_);this.dragEl_=this.$.drag_handle;this.analysisEl_=this.$.analysis;this.analysisEl_.brushingStateController=this.brushingStateController_;this.addEventListener('requestSelectionChange',function(e){const sc=this.brushingStateController_;sc.changeSelectionFromRequestSelectionChangeEvent(e.selection);}.bind(this));this.onViewportChanged_=this.onViewportChanged_.bind(this);this.bindKeyListeners_();this.dragEl_.target=this.analysisEl_;},get globalMode(){return this.hotkeyController.globalMode;},set globalMode(globalMode){globalMode=!!globalMode;this.brushingStateController_.historyEnabled=globalMode;this.hotkeyController.globalMode=globalMode;},get hotkeyController(){return this.$.hkc;},warnPolyfill(){if(this.polyfillWarnedOnce_)return;console.warn(POLYFILL_WARNING_MESSAGE);this.polyfillWarnedOnce_=true;if(!window.__hideTraceViewerPolyfillWarning){const polyfillWarningsEl=Polymer.dom(this.root).querySelector('#polyfill-warning');polyfillWarningsEl.addMessage(POLYFILL_WARNING_MESSAGE,[{buttonText:'Hide',onClick:()=>polyfillWarningsEl.clearMessages()}]);}},updateDocumentFavicon(){let hue;if(!this.model){hue='blue';}else{hue=this.model.faviconHue;}
+this.flowEventFilter_=this.$.flow_event_filter_dropdown;this.processFilter_=this.$.process_filter_dropdown;this.optionsDropdown_=this.$.view_options_dropdown;this.selectedFlowEvents_=new Set();this.highlightVSync_=false;this.highlightVSyncCheckbox_=tr.ui.b.createCheckBox(this,'highlightVSync','tr.ui.TimelineView.highlightVSync',false,'Highlight VSync');Polymer.dom(this.optionsDropdown_).appendChild(this.highlightVSyncCheckbox_);this.initMetadataButton_();this.initConsoleButton_();this.initHelpButton_();Polymer.dom(this.collapsingControls).appendChild(this.scriptingCtl_);this.dragEl_=this.$.drag_handle;this.analysisEl_=this.$.analysis;this.analysisEl_.brushingStateController=this.brushingStateController_;this.addEventListener('requestSelectionChange',function(e){const sc=this.brushingStateController_;sc.changeSelectionFromRequestSelectionChangeEvent(e.selection);}.bind(this));this.onViewportChanged_=this.onViewportChanged_.bind(this);this.bindKeyListeners_();this.dragEl_.target=this.analysisEl_;},get globalMode(){return this.hotkeyController.globalMode;},set globalMode(globalMode){globalMode=!!globalMode;this.brushingStateController_.historyEnabled=globalMode;this.hotkeyController.globalMode=globalMode;},get hotkeyController(){return this.$.hkc;},warnPolyfill(){if(this.polyfillWarnedOnce_)return;console.warn(POLYFILL_WARNING_MESSAGE);this.polyfillWarnedOnce_=true;},updateDocumentFavicon(){let hue;if(!this.model){hue='blue';}else{hue=this.model.faviconHue;}
 let faviconData=tr.ui.b.FaviconsByHue[hue];if(faviconData===undefined){faviconData=tr.ui.b.FaviconsByHue.blue;}
 let link=Polymer.dom(document.head).querySelector('link[rel="shortcut icon"]');if(!link){link=document.createElement('link');link.rel='shortcut icon';Polymer.dom(document.head).appendChild(link);}
 link.href=faviconData;},get selectedFlowEvents(){return this.selectedFlowEvents_;},set selectedFlowEvents(selectedFlowEvents){this.selectedFlowEvents_=selectedFlowEvents;},get highlightVSync(){return this.highlightVSync_;},set highlightVSync(highlightVSync){this.highlightVSync_=highlightVSync;if(!this.trackView_)return;this.trackView_.viewport.highlightVSync=highlightVSync;},initHelpButton_(){const helpButtonEl=this.$.view_help_button;const dlg=new tr.ui.b.Overlay();dlg.title='Chrome Tracing Help';dlg.visible=false;dlg.appendChild(document.createElement('tr-ui-timeline-view-help-overlay'));function onClick(e){dlg.visible=!dlg.visible;e.stopPropagation();}
@@ -9954,7 +10031,8 @@
 if(config){return{excluded:config.excluded_categories||[],included:config.included_categories||[],};}}}
 function validateTraceCategories(requiredCategories,categories){if(!requiredCategories)return;if(!categories)throw new Error('Missing trace config metadata');for(const cat of requiredCategories){const isDisabledByDefault=(cat.indexOf('disabled-by-default')===0);let missing=false;if(isDisabledByDefault){if(!categories.included.includes(cat)){missing=true;}}else if(categories.excluded.includes(cat)){missing=true;}
 if(missing){throw new Error(`Trace is missing required category "${cat}"`);}}}
-function validateDiagnosticNames(histograms){for(const hist of histograms){for(const name of hist.diagnostics.keys()){if(tr.v.d.RESERVED_NAMES_SET.has(name)){throw new Error(`Illegal diagnostic name "${name}" on Histogram "${hist.name}"`);}}}}
+function validateDiagnosticNames(histograms){for(const hist of histograms){for(const name of hist.diagnostics.keys()){if(name===tr.v.d.RESERVED_NAMES.ALERT_GROUPING){continue;}
+if(tr.v.d.RESERVED_NAMES_SET.has(name)){throw new Error(`Illegal diagnostic name "${name}" on Histogram "${hist.name}"`);}}}}
 function addTelemetryInfo(histograms,model){for(const metadata of model.metadata){if(!metadata.value||!metadata.value.telemetry)continue;for(const[name,value]of Object.entries(metadata.value.telemetry)){const type=tr.v.d.RESERVED_NAMES_TO_TYPES.get(name);if(type===undefined){throw new Error(`Unexpected telemetry.${name}`);}
 histograms.addSharedDiagnosticToAllHistograms(name,new type(value));}}}
 function metricMapFunction(result,model,options){const histograms=runMetrics(model,options,result.addFailure.bind(result));addTelemetryInfo(histograms,model);if(model.canonicalUrl!==undefined){const info=tr.v.d.RESERVED_INFOS.TRACE_URLS;histograms.addSharedDiagnosticToAllHistograms(info.name,new info.type([model.canonicalUrl]));}
diff --git a/catapult/systrace/systrace/tracing_agents/atrace_agent.py b/catapult/systrace/systrace/tracing_agents/atrace_agent.py
index 9504685..05c4330 100644
--- a/catapult/systrace/systrace/tracing_agents/atrace_agent.py
+++ b/catapult/systrace/systrace/tracing_agents/atrace_agent.py
@@ -104,7 +104,8 @@
            'Your device SDK version is %d.' % device_sdk_version)
     return None
 
-  return AtraceAgent(device_sdk_version, util.get_tracing_path())
+  return AtraceAgent(device_sdk_version,
+                     util.get_tracing_path(config.device_serial_number))
 
 def _construct_extra_atrace_args(config, categories):
   """Construct extra arguments (-a, -k, categories) for atrace command.
diff --git a/catapult/systrace/systrace/util.py b/catapult/systrace/systrace/util.py
index 0298145..063f9ed 100644
--- a/catapult/systrace/systrace/util.py
+++ b/catapult/systrace/systrace/util.py
@@ -52,7 +52,7 @@
   return (adb_output, adb_return_code)
 
 
-def get_tracing_path():
+def get_tracing_path(device_serial=None):
   """Uses adb to attempt to determine tracing path. The newest kernel doesn't
      support mounting debugfs, so the Android master uses tracefs to replace it.
 
@@ -62,12 +62,14 @@
     /sys/kernel/debug/tracing if support can't be determined.
   """
   mount_info_args = ['mount']
-  parser = OptionParserIgnoreErrors()
-  parser.add_option('-e', '--serial', dest='device_serial', type='string')
-  options, _ = parser.parse_args()
 
-  adb_output, adb_return_code = run_adb_shell(mount_info_args,
-                                              options.device_serial)
+  if device_serial is None:
+    parser = OptionParserIgnoreErrors()
+    parser.add_option('-e', '--serial', dest='device_serial', type='string')
+    options, _ = parser.parse_args()
+    device_serial = options.device_serial
+
+  adb_output, adb_return_code = run_adb_shell(mount_info_args, device_serial)
   if adb_return_code == 0 and 'debugfs' not in adb_output:
     return '/sys/kernel/tracing'
   return '/sys/kernel/debug/tracing'