Merge "Update to latest catapult (625dca847)"
diff --git a/UPSTREAM_REVISION b/UPSTREAM_REVISION
index ef75e06..60f3d3a 100644
--- a/UPSTREAM_REVISION
+++ b/UPSTREAM_REVISION
@@ -1 +1 @@
-eae13a4b34ccf54974dfacf7d72effd99729f543
+5e1c1c293b07ef04a247dd8dff50972d207663a4
diff --git a/catapult/common/lab/commits.py b/catapult/common/lab/commits.py
index ce02ee3..6d47b91 100755
--- a/catapult/common/lab/commits.py
+++ b/catapult/common/lab/commits.py
@@ -15,7 +15,7 @@
 
 _BASE_URL = 'https://chromium.googlesource.com'
 # Can be up to 10,000.
-_REVISION_COUNT = 1000
+_REVISION_COUNT = 10000
 
 _REPOSITORIES = [
     'chromium/src',
@@ -67,33 +67,37 @@
     commit_time_string = revision['committer']['time']
     commit_time = datetime.datetime.strptime(
         commit_time_string, '%a %b %d %H:%M:%S %Y')
-    commit_times.append(commit_time)
+    commit_times.append(commit_time - datetime.timedelta(hours=7))
 
   return commit_times
 
 
+def IsWeekday(time):
+  return time.weekday() >= 0 and time.weekday() < 5
+
+
 def main():
   for repository in _REPOSITORIES:
     commit_times = CommitTimes(repository, _REVISION_COUNT)
 
     commit_durations = []
     for time1, time2 in Pairwise(commit_times):
-      commit_durations.append((time1 - time2).total_seconds())
+      #if not (IsWeekday(time1) and IsWeekday(time2)):
+      #  continue
+      commit_durations.append((time1 - time2).total_seconds() / 60.)
     commit_durations.sort()
 
     print 'REPOSITORY:', repository
-    print 'Start Date:', min(commit_times)
-    print '  End Date:', max(commit_times)
+    print 'Start Date:', min(commit_times), 'PDT'
+    print '  End Date:', max(commit_times), 'PDT'
     print '  Duration:', max(commit_times) - min(commit_times)
     print '         n:', len(commit_times)
 
-    for p in (0.00, 0.05, 0.25, 0.50, 0.75, 0.95, 1.00):
+    for p in (0.25, 0.50, 0.90):
       percentile = Percentile(commit_durations, p)
-      print '%3d%% commit duration:' % (p * 100), '%6ds' % percentile
+      print '%3d%% commit duration:' % (p * 100), '%6.1fm' % percentile
     mean = math.fsum(commit_durations) / len(commit_durations)
-    print ' Min commit duration:', '%6ds' % min(commit_durations)
-    print 'Mean commit duration:', '%6ds' % mean
-    print ' Max commit duration:', '%6ds' % max(commit_durations)
+    print 'Mean commit duration:', '%6.1fm' % mean
     print
 
 
diff --git a/catapult/common/node_runner/node_runner/minify b/catapult/common/node_runner/node_runner/minify
index 9d5bb2f..a5a24cf 100755
--- a/catapult/common/node_runner/node_runner/minify
+++ b/catapult/common/node_runner/node_runner/minify
@@ -14,12 +14,6 @@
 const fs = require('fs');
 const nopt = require('nopt');
 
-const ESPREE_OPTIONS = {
-  attachComment: false,
-  comments: false,
-  ecmaVersion: 2018,
-};
-
 const args = nopt();
 const filename = args.argv.remain[0];
 
@@ -44,9 +38,10 @@
       .replace(/\n+/g, '\n'));
   } else if (dom5.predicates.hasTagName('script')(node) &&
              !dom5.predicates.hasAttr('src')(node)) {
-    dom5.setTextContent(node, escodegen.generate(
-      espree.parse(dom5.getTextContent(node), ESPREE_OPTIONS),
-      {format: {indent: {style: ''}}}));
+    let text = dom5.getTextContent(node);
+    const ast = espree.parse(text, {ecmaVersion: 2018});
+    text = escodegen.generate(ast, {format: {indent: {style: ''}}});
+    dom5.setTextContent(node, text);
   } else if (dom5.predicates.hasTagName('style')(node)) {
     dom5.setTextContent(node, dom5.getTextContent(node)
       .replace(/[\r\n]/g, '')
diff --git a/catapult/common/node_runner/node_runner/package.json b/catapult/common/node_runner/node_runner/package.json
index 9a92270..d743e00 100644
--- a/catapult/common/node_runner/node_runner/package.json
+++ b/catapult/common/node_runner/node_runner/package.json
@@ -16,11 +16,13 @@
   "private": true,
   "dependencies": {
     "dom5": "^1.0.0",
-    "escodegen": "^1.0.0",
+    "escodegen": "^1.11.0",
     "eslint": "^4.0.0",
     "eslint-config-google": "^0.6.0",
     "eslint-plugin-html": "^4.0.0",
     "espree": "^3.0.0",
-    "vulcanize": "^1.16.0"
+    "vulcanize": "^1.16.0",
+    "webpack": "^4.16.1",
+    "webpack-command": "^0.4.1"
   }
 }
diff --git a/catapult/dependency_manager/dependency_manager/__init__.py b/catapult/dependency_manager/dependency_manager/__init__.py
index 68efbdb..84cca5a 100644
--- a/catapult/dependency_manager/dependency_manager/__init__.py
+++ b/catapult/dependency_manager/dependency_manager/__init__.py
@@ -25,7 +25,7 @@
 _AddDirToPythonPath(DEPENDENCY_MANAGER_PATH)
 
 
-# pylint: disable=unused-import
+# pylint: disable=unused-import,wrong-import-position
 from .archive_info import ArchiveInfo
 from .base_config import BaseConfig
 from .cloud_storage_info import CloudStorageInfo
diff --git a/catapult/dependency_manager/dependency_manager/archive_info.py b/catapult/dependency_manager/dependency_manager/archive_info.py
index ff80b63..f28028c 100644
--- a/catapult/dependency_manager/dependency_manager/archive_info.py
+++ b/catapult/dependency_manager/dependency_manager/archive_info.py
@@ -62,8 +62,7 @@
   def ShouldUnzipArchive(self):
     if not self._has_minimum_data:
       raise exceptions.ArchiveError(
-          'Missing needed info to unzip archive. Known data: %s',
-          self.data_string)
+          'Missing needed info to unzip archive. Know data: %s' % self)
     return not os.path.exists(self._dependency_path)
 
   @property
diff --git a/catapult/dependency_manager/dependency_manager/cloud_storage_info_unittest.py b/catapult/dependency_manager/dependency_manager/cloud_storage_info_unittest.py
index 699cd50..844465d 100644
--- a/catapult/dependency_manager/dependency_manager/cloud_storage_info_unittest.py
+++ b/catapult/dependency_manager/dependency_manager/cloud_storage_info_unittest.py
@@ -144,7 +144,7 @@
     # All of the needed information is given, but the downloaded path doesn't
     # exists after calling cloud storage.
     self.fs.RemoveObject(self.download_path)
-    cs_get_mock.side_effect = [True]
+    cs_get_mock.side_effect = [True]  # pylint: disable=redefined-variable-type
     self.assertRaises(
         exceptions.FileNotFoundError, self.cs_info.GetRemotePath)
 
diff --git a/catapult/dependency_manager/dependency_manager/dependency_info.py b/catapult/dependency_manager/dependency_manager/dependency_info.py
index 942d57e..899657e 100644
--- a/catapult/dependency_manager/dependency_manager/dependency_info.py
+++ b/catapult/dependency_manager/dependency_manager/dependency_info.py
@@ -16,7 +16,7 @@
                        for error messages to improve debugging.
 
         Optional:
-          local_paths: A list of paths to search in order for a local file.
+          local_path_info: A LocalPathInfo instance.
           cloud_storage_info: An instance of CloudStorageInfo.
     """
     # TODO(aiolos): update the above doc string for A) the usage of zip files
diff --git a/catapult/dependency_manager/dependency_manager/local_path_info.py b/catapult/dependency_manager/dependency_manager/local_path_info.py
index 0103e8f..de544c7 100644
--- a/catapult/dependency_manager/dependency_manager/local_path_info.py
+++ b/catapult/dependency_manager/dependency_manager/local_path_info.py
@@ -8,9 +8,29 @@
 class LocalPathInfo(object):
 
   def __init__(self, path_priority_groups):
+    """Container for a set of local file paths where a given dependency
+    can be stored.
+
+    Organized as a list of groups, where each group is itself a file path list.
+    See GetLocalPath() to understand how they are used.
+
+    Args:
+      path_priority_groups: Can be either None, or a list of file path
+        strings (corresponding to a list of groups, where each group has
+        a single file path), or a list of a list of file path strings
+        (i.e. a list of groups).
+    """
     self._path_priority_groups = self._ParseLocalPaths(path_priority_groups)
 
   def GetLocalPath(self):
+    """Look for a local file, and return its path.
+
+    Looks for the first group which has at least one existing file path. Then
+    returns the most-recent of these files.
+
+    Returns:
+      Local file path, if found, or None otherwise.
+    """
     for priority_group in self._path_priority_groups:
       priority_group = filter(os.path.exists, priority_group)
       if not priority_group:
@@ -19,10 +39,19 @@
     return None
 
   def IsPathInLocalPaths(self, path):
+    """Returns true if |path| is in one of this instance's file path lists."""
     return any(
         path in priority_group for priority_group in self._path_priority_groups)
 
   def Update(self, local_path_info):
+    """Update this object from the content of another LocalPathInfo instance.
+
+    Any file path from |local_path_info| that is not already contained in the
+    current instance will be added into new groups to it.
+
+    Args:
+      local_path_info: Another LocalPathInfo instance, or None.
+    """
     if not local_path_info:
       return
     for priority_group in local_path_info._path_priority_groups:
diff --git a/catapult/dependency_manager/dependency_manager/local_path_info_unittest.py b/catapult/dependency_manager/dependency_manager/local_path_info_unittest.py
new file mode 100644
index 0000000..83921fa
--- /dev/null
+++ b/catapult/dependency_manager/dependency_manager/local_path_info_unittest.py
@@ -0,0 +1,136 @@
+# Copyright 2018 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.
+
+import os
+
+from pyfakefs import fake_filesystem_unittest
+
+import dependency_manager
+
+def _CreateFile(path):
+  """Create file at specific |path|, with specific |content|."""
+  with open(path, 'wb') as f:
+    f.write('x')
+
+
+def _ChangeFileTime(path, time0, days):
+  new_time = time0 + (days * 24 * 60 * 60)
+  os.utime(path, (new_time, new_time))
+
+
+class LocalPathInfoTest(fake_filesystem_unittest.TestCase):
+
+  def setUp(self):
+    self.setUpPyfakefs()
+
+  def tearDown(self):
+    self.tearDownPyfakefs()
+
+  def testEmptyInstance(self):
+    path_info = dependency_manager.LocalPathInfo(None)
+    self.assertIsNone(path_info.GetLocalPath())
+    self.assertFalse(path_info.IsPathInLocalPaths('file.txt'))
+
+  def testSimpleGroupWithOnePath(self):
+    path_info = dependency_manager.LocalPathInfo(['file.txt'])
+    self.assertTrue(path_info.IsPathInLocalPaths('file.txt'))
+    self.assertFalse(path_info.IsPathInLocalPaths('other.txt'))
+
+    # GetLocalPath returns None if the file doesn't exist.
+    # Otherwise it will return the file path.
+    self.assertIsNone(path_info.GetLocalPath())
+    _CreateFile('file.txt')
+    self.assertEqual('file.txt', path_info.GetLocalPath())
+
+  def testSimpleGroupsWithMultiplePaths(self):
+    path_info = dependency_manager.LocalPathInfo(
+        [['file1', 'file2', 'file3']])
+    self.assertTrue(path_info.IsPathInLocalPaths('file1'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file2'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file3'))
+
+    _CreateFile('file1')
+    _CreateFile('file2')
+    _CreateFile('file3')
+    s = os.stat('file1')
+    time0 = s.st_mtime
+
+    _ChangeFileTime('file1', time0, 4)
+    _ChangeFileTime('file2', time0, 2)
+    _ChangeFileTime('file3', time0, 0)
+    self.assertEqual('file1', path_info.GetLocalPath())
+
+    _ChangeFileTime('file1', time0, 0)
+    _ChangeFileTime('file2', time0, 4)
+    _ChangeFileTime('file3', time0, 2)
+    self.assertEqual('file2', path_info.GetLocalPath())
+
+    _ChangeFileTime('file1', time0, 2)
+    _ChangeFileTime('file2', time0, 0)
+    _ChangeFileTime('file3', time0, 4)
+    self.assertEqual('file3', path_info.GetLocalPath())
+
+  def testMultipleGroupsWithSinglePaths(self):
+    path_info = dependency_manager.LocalPathInfo(
+        ['file1', 'file2', 'file3'])
+    self.assertTrue(path_info.IsPathInLocalPaths('file1'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file2'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file3'))
+
+    self.assertIsNone(path_info.GetLocalPath())
+    _CreateFile('file3')
+    self.assertEqual('file3', path_info.GetLocalPath())
+    _CreateFile('file2')
+    self.assertEqual('file2', path_info.GetLocalPath())
+    _CreateFile('file1')
+    self.assertEqual('file1', path_info.GetLocalPath())
+
+  def testMultipleGroupsWithMultiplePaths(self):
+    path_info = dependency_manager.LocalPathInfo([
+        ['file1', 'file2'],
+        ['file3', 'file4']])
+    self.assertTrue(path_info.IsPathInLocalPaths('file1'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file2'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file3'))
+    self.assertTrue(path_info.IsPathInLocalPaths('file4'))
+
+    _CreateFile('file1')
+    _CreateFile('file3')
+    s = os.stat('file1')
+    time0 = s.st_mtime
+
+    # Check that file1 is always returned, even if it is not the most recent
+    # file, because it is part of the first group and exists.
+    _ChangeFileTime('file1', time0, 2)
+    _ChangeFileTime('file3', time0, 0)
+    self.assertEqual('file1', path_info.GetLocalPath())
+
+    _ChangeFileTime('file1', time0, 0)
+    _ChangeFileTime('file3', time0, 2)
+    self.assertEqual('file1', path_info.GetLocalPath())
+
+  def testUpdate(self):
+    path_info1 = dependency_manager.LocalPathInfo(
+        [['file1', 'file2']])  # One group with two files.
+    path_info2 = dependency_manager.LocalPathInfo(
+        ['file1', 'file2', 'file3'])  # Three groups
+    self.assertTrue(path_info1.IsPathInLocalPaths('file1'))
+    self.assertTrue(path_info1.IsPathInLocalPaths('file2'))
+    self.assertFalse(path_info1.IsPathInLocalPaths('file3'))
+
+    _CreateFile('file3')
+    self.assertIsNone(path_info1.GetLocalPath())
+
+    path_info1.Update(path_info2)
+    self.assertTrue(path_info1.IsPathInLocalPaths('file1'))
+    self.assertTrue(path_info1.IsPathInLocalPaths('file2'))
+    self.assertTrue(path_info1.IsPathInLocalPaths('file3'))
+    self.assertEqual('file3', path_info1.GetLocalPath())
+
+    _CreateFile('file1')
+    time0 = os.stat('file1').st_mtime
+    _ChangeFileTime('file3', time0, 2)  # Make file3 more recent.
+
+    # Check that file3 is in a later group.
+    self.assertEqual('file1', path_info1.GetLocalPath())
diff --git a/catapult/dependency_manager/dependency_manager/manager.py b/catapult/dependency_manager/dependency_manager/manager.py
index 7ebb46d..28fc532 100644
--- a/catapult/dependency_manager/dependency_manager/manager.py
+++ b/catapult/dependency_manager/dependency_manager/manager.py
@@ -33,7 +33,7 @@
         local files for the same platform will first look in those from
         config1, then those from config2, and finally those from config3.
     """
-    if configs is None or type(configs) != list:
+    if configs is None or not isinstance(configs, list):
       raise ValueError(
           'Must supply a list of config files to DependencyManager')
     # self._lookup_dict is a dictionary with the following format:
diff --git a/catapult/dependency_manager/dependency_manager/uploader.py b/catapult/dependency_manager/dependency_manager/uploader.py
index e88b7c0..d00d20c 100644
--- a/catapult/dependency_manager/dependency_manager/uploader.py
+++ b/catapult/dependency_manager/dependency_manager/uploader.py
@@ -100,7 +100,7 @@
     return cloud_storage_changed
 
   def __eq__(self, other, msg=None):
-    if type(self) != type(other):
+    if not isinstance(self, type(other)):
       return False
     return (self._local_path == other._local_path and
             self._cs_remote_path == other._cs_remote_path and
diff --git a/catapult/devil/BUILD.gn b/catapult/devil/BUILD.gn
new file mode 100644
index 0000000..661a24f
--- /dev/null
+++ b/catapult/devil/BUILD.gn
@@ -0,0 +1,21 @@
+# 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.
+
+import("//build/symlink.gni")
+import("//testing/android/empty_apk/empty_apk.gni")
+
+empty_apk("empty_system_webview_apk") {
+  package_name = "com.android.webview"
+  apk_name = "EmptySystemWebView"
+}
+
+group("devil") {
+  testonly = true
+  deps = [
+    ":empty_system_webview_apk",
+    "//buildtools/third_party/libc++($host_toolchain)",
+    "//tools/android/forwarder2",
+    "//tools/android/md5sum",
+  ]
+}
diff --git a/catapult/devil/devil/android/battery_utils.py b/catapult/devil/devil/android/battery_utils.py
index a8a08a9..9c83b5b 100644
--- a/catapult/devil/devil/android/battery_utils.py
+++ b/catapult/devil/devil/android/battery_utils.py
@@ -11,6 +11,7 @@
 import csv
 import logging
 
+from devil.android import crash_handler
 from devil.android import decorators
 from devil.android import device_errors
 from devil.android import device_utils
@@ -374,7 +375,12 @@
     Returns:
       True if the device is charging, false otherwise.
     """
-    battery_info = self.GetBatteryInfo()
+    # Wrapper function so that we can use `RetryOnSystemCrash`.
+    def GetBatteryInfoHelper(device):
+      return self.GetBatteryInfo()
+
+    battery_info = crash_handler.RetryOnSystemCrash(
+        GetBatteryInfoHelper, self._device)
     for k in ('AC powered', 'USB powered', 'Wireless powered'):
       if (k in battery_info and
           battery_info[k].lower() in ('true', '1', 'yes')):
diff --git a/catapult/devil/devil/android/decorators.py b/catapult/devil/devil/android/decorators.py
index 3844b49..93e1054 100644
--- a/catapult/devil/devil/android/decorators.py
+++ b/catapult/devil/devil/android/decorators.py
@@ -59,7 +59,7 @@
       raise device_errors.CommandTimeoutError(str(e)), None, (
           sys.exc_info()[2])
     except cmd_helper.TimeoutError as e:
-      raise device_errors.CommandTimeoutError(str(e)), None, (
+      raise device_errors.CommandTimeoutError(str(e), output=e.output), None, (
           sys.exc_info()[2])
   return timeout_retry_wrapper
 
diff --git a/catapult/devil/devil/android/device_errors.py b/catapult/devil/devil/android/device_errors.py
index cd0266c..e6893a4 100644
--- a/catapult/devil/devil/android/device_errors.py
+++ b/catapult/devil/devil/android/device_errors.py
@@ -148,7 +148,9 @@
 
 class CommandTimeoutError(base_error.BaseError):
   """Exception for command timeouts."""
-  pass
+  def __init__(self, message, is_infra_error=False, output=None):
+    super(CommandTimeoutError, self).__init__(message, is_infra_error)
+    self.output = output
 
 
 class DeviceUnreachableError(base_error.BaseError):
diff --git a/catapult/devil/devil/android/device_utils.py b/catapult/devil/devil/android/device_utils.py
index 518e439..575865b 100644
--- a/catapult/devil/devil/android/device_utils.py
+++ b/catapult/devil/devil/android/device_utils.py
@@ -13,6 +13,7 @@
 import fnmatch
 import json
 import logging
+import math
 import os
 import posixpath
 import pprint
@@ -209,6 +210,7 @@
     'marlin', # Pixel XL
     'sailfish', # Pixel
     'taimen', # Pixel 2 XL
+    'vega', # Lenovo Mirage Solo
     'walleye', # Pixel 2
 ]
 _IMEI_RE = re.compile(r'  Device ID = (.+)$')
@@ -425,6 +427,20 @@
   def HasRoot(self, timeout=None, retries=None):
     """Checks whether or not adbd has root privileges.
 
+    A device is considered to have root if all commands are implicitly run
+    with elevated privileges, i.e. without having to use "su" to run them.
+
+    Note that some devices do not allow this implicit privilige elevation,
+    but _can_ run commands as root just fine when done explicitly with "su".
+    To check if your device can run commands with elevated privileges at all
+    use:
+
+      device.HasRoot() or device.NeedsSU()
+
+    Luckily, for the most part you don't need to worry about this and using
+    RunShellCommand(cmd, as_root=True) will figure out for you the right
+    command incantation to run with elevated privileges.
+
     Args:
       timeout: timeout in seconds
       retries: number of retries
@@ -1004,8 +1020,8 @@
   @decorators.WithTimeoutAndRetriesFromInstance()
   def RunShellCommand(self, cmd, shell=False, check_return=False, cwd=None,
                       env=None, run_as=None, as_root=False, single_line=False,
-                      large_output=False, raw_output=False, timeout=None,
-                      retries=None):
+                      large_output=False, raw_output=False,
+                      ensure_logs_on_timeout=False, timeout=None, retries=None):
     """Run an ADB shell command.
 
     The command to run |cmd| should be a sequence of program arguments
@@ -1048,6 +1064,10 @@
         this large output will be truncated.
       raw_output: Whether to only return the raw output
           (no splitting into lines).
+      ensure_logs_on_timeout: If True, will use a slightly smaller timeout for
+          the internal adb command, which allows to retrive logs on timeout.
+          Note that that logs are not guaranteed to be produced with this option
+          as adb command may still hang and fail to respect the reduced timeout.
       timeout: timeout in seconds
       retries: number of retries
 
@@ -1071,7 +1091,7 @@
       return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
 
     def run(cmd):
-      return self.adb.Shell(cmd)
+      return self.adb.Shell(cmd, ensure_logs_on_timeout=ensure_logs_on_timeout)
 
     def handle_check_return(cmd):
       try:
@@ -2805,7 +2825,7 @@
       return parallelizer.SyncParallelizer(devices)
 
   @classmethod
-  def HealthyDevices(cls, blacklist=None, device_arg='default', retry=True,
+  def HealthyDevices(cls, blacklist=None, device_arg='default', retries=1,
                      abis=None, **kwargs):
     """Returns a list of DeviceUtils instances.
 
@@ -2828,8 +2848,9 @@
               blacklisted.
           ['A', 'B', ...] -> Returns instances for the subset that is not
               blacklisted.
-      retry: If true, will attempt to restart adb server and query it again if
-          no devices are found.
+      retries: Number of times to restart adb server and query it again if no
+          devices are found on the previous attempts, with exponential backoffs
+          up to 60s between each retry.
       abis: A list of ABIs for which the device needs to support at least one of
           (optional).
       A device serial, or a list of device serials (optional).
@@ -2891,15 +2912,20 @@
         raise device_errors.MultipleDevicesError(devices)
       return sorted(devices)
 
-    try:
-      return _get_devices()
-    except device_errors.NoDevicesError:
-      if not retry:
-        raise
-      logger.warning(
-          'No devices found. Will try again after restarting adb server.')
-      RestartServer()
-      return _get_devices()
+    for attempt in xrange(retries+1):
+      try:
+        return _get_devices()
+      except device_errors.NoDevicesError:
+        if attempt == retries:
+          logging.error('No devices found after exhausting all retries.')
+          raise
+        # math.pow returns floats, so cast to int for easier testing
+        sleep_s = min(int(math.pow(2, attempt + 1)), 60)
+        logger.warning(
+            'No devices found. Will try again after restarting adb server '
+            'and a short nap of %d s.', sleep_s)
+        time.sleep(sleep_s)
+        RestartServer()
 
   @decorators.WithTimeoutAndRetriesFromInstance()
   def RestartAdbd(self, timeout=None, retries=None):
diff --git a/catapult/devil/devil/android/device_utils_test.py b/catapult/devil/devil/android/device_utils_test.py
index 88c91b5..e0ed666 100755
--- a/catapult/devil/devil/android/device_utils_test.py
+++ b/catapult/devil/devil/android/device_utils_test.py
@@ -316,28 +316,32 @@
   def testHasRoot_true(self):
     with self.patch_call(self.call.device.product_name,
                           return_value='notasailfish'), (
-        self.assertCall(self.call.adb.Shell('ls /root'), 'foo\n')):
+        self.assertCall(self.call.adb.Shell(
+          'ls /root', ensure_logs_on_timeout=False), 'foo\n')):
       self.assertTrue(self.device.HasRoot())
 
   def testhasRootSpecial_true(self):
     with self.patch_call(self.call.device.product_name,
                          return_value='sailfish'), (
-        self.assertCall(self.call.adb.Shell('getprop service.adb.root'),
-                        '1\n')):
+        self.assertCall(
+          self.call.adb.Shell('getprop service.adb.root',
+            ensure_logs_on_timeout=False), '1\n')):
       self.assertTrue(self.device.HasRoot())
 
   def testHasRoot_false(self):
     with self.patch_call(self.call.device.product_name,
                          return_value='notasailfish'), (
-        self.assertCall(self.call.adb.Shell('ls /root'),
-                        self.ShellError())):
+        self.assertCall(
+          self.call.adb.Shell(
+            'ls /root', ensure_logs_on_timeout=False), self.ShellError())):
       self.assertFalse(self.device.HasRoot())
 
   def testHasRootSpecial_false(self):
     with self.patch_call(self.call.device.product_name,
                          return_value='sailfish'), (
-        self.assertCall(self.call.adb.Shell('getprop service.adb.root'),
-                        '\n')):
+        self.assertCall(
+          self.call.adb.Shell(
+            'getprop service.adb.root', ensure_logs_on_timeout=False), '\n')):
       self.assertFalse(self.device.HasRoot())
 
 
@@ -445,7 +449,8 @@
 
   def test_GetApplicationVersion_exists(self):
     with self.assertCalls(
-        (self.call.adb.Shell('dumpsys package com.android.chrome'),
+        (self.call.adb.Shell(
+          'dumpsys package com.android.chrome', ensure_logs_on_timeout=False),
          'Packages:\n'
          '  Package [com.android.chrome] (3901ecfb):\n'
          '    userId=1234 gids=[123, 456, 789]\n'
@@ -456,13 +461,16 @@
 
   def test_GetApplicationVersion_notExists(self):
     with self.assertCalls(
-        (self.call.adb.Shell('dumpsys package com.android.chrome'), '')):
+        (self.call.adb.Shell(
+          'dumpsys package com.android.chrome', ensure_logs_on_timeout=False),
+          '')):
       self.assertEquals(None,
                         self.device.GetApplicationVersion('com.android.chrome'))
 
   def test_GetApplicationVersion_fails(self):
     with self.assertCalls(
-        (self.call.adb.Shell('dumpsys package com.android.chrome'),
+        (self.call.adb.Shell(
+          'dumpsys package com.android.chrome', ensure_logs_on_timeout=False),
          'Packages:\n'
          '  Package [com.android.chrome] (3901ecfb):\n'
          '    userId=1234 gids=[123, 456, 789]\n'
@@ -520,7 +528,8 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -534,7 +543,8 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -542,7 +552,8 @@
         # boot_completed
         (self.call.device.GetProp('sys.boot_completed', cache=False), '1'),
         # wifi_enabled
-        (self.call.adb.Shell('dumpsys wifi'),
+        (self.call.adb.Shell(
+          'dumpsys wifi', ensure_logs_on_timeout=False),
          'stuff\nWi-Fi is enabled\nmore stuff\n')):
       self.device.WaitUntilFullyBooted(wifi=True)
 
@@ -559,7 +570,8 @@
         (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -573,7 +585,8 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -598,13 +611,18 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False),
+          self.ShellError()),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False),
+          self.ShellError()),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False),
          self.TimeoutError())):
       with self.assertRaises(device_errors.CommandTimeoutError):
         self.device.WaitUntilFullyBooted(wifi=False)
@@ -614,7 +632,8 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -635,7 +654,8 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -655,7 +675,8 @@
         self.call.adb.WaitForDevice(),
         # sd_card_ready
         (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
-        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        (self.call.adb.Shell(
+          'test -d /fake/storage/path', ensure_logs_on_timeout=False), ''),
         # pm_ready
         (self.call.device._GetApplicationPathsInternal('android',
                                                        skip_cache=True),
@@ -663,11 +684,14 @@
         # boot_completed
         (self.call.device.GetProp('sys.boot_completed', cache=False), '1'),
         # wifi_enabled
-        (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
+        (self.call.adb.Shell(
+          'dumpsys wifi', ensure_logs_on_timeout=False), 'stuff\nmore stuff\n'),
         # wifi_enabled
-        (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
+        (self.call.adb.Shell(
+          'dumpsys wifi', ensure_logs_on_timeout=False), 'stuff\nmore stuff\n'),
         # wifi_enabled
-        (self.call.adb.Shell('dumpsys wifi'), self.TimeoutError())):
+        (self.call.adb.Shell(
+          'dumpsys wifi', ensure_logs_on_timeout=False), self.TimeoutError())):
       with self.assertRaises(device_errors.CommandTimeoutError):
         self.device.WaitUntilFullyBooted(wifi=True)
 
@@ -930,30 +954,36 @@
     self.device.NeedsSU = mock.Mock(return_value=False)
 
   def testRunShellCommand_commandAsList(self):
-    with self.assertCall(self.call.adb.Shell('pm list packages'), ''):
+    with self.assertCall(self.call.adb.Shell(
+      'pm list packages', ensure_logs_on_timeout=False), ''):
       self.device.RunShellCommand(
           ['pm', 'list', 'packages'], check_return=True)
 
   def testRunShellCommand_commandAsListQuoted(self):
-    with self.assertCall(self.call.adb.Shell("echo 'hello world' '$10'"), ''):
+    with self.assertCall(self.call.adb.Shell(
+      "echo 'hello world' '$10'", ensure_logs_on_timeout=False), ''):
       self.device.RunShellCommand(
           ['echo', 'hello world', '$10'], check_return=True)
 
   def testRunShellCommand_commandAsString(self):
-    with self.assertCall(self.call.adb.Shell('echo "$VAR"'), ''):
+    with self.assertCall(self.call.adb.Shell(
+      'echo "$VAR"', ensure_logs_on_timeout=False), ''):
       self.device.RunShellCommand(
           'echo "$VAR"', shell=True, check_return=True)
 
   def testNewRunShellImpl_withEnv(self):
     with self.assertCall(
-        self.call.adb.Shell('VAR=some_string echo "$VAR"'), ''):
+        self.call.adb.Shell(
+          'VAR=some_string echo "$VAR"', ensure_logs_on_timeout=False), ''):
       self.device.RunShellCommand(
           'echo "$VAR"', shell=True, check_return=True,
           env={'VAR': 'some_string'})
 
   def testNewRunShellImpl_withEnvQuoted(self):
     with self.assertCall(
-        self.call.adb.Shell('PATH="$PATH:/other/path" run_this'), ''):
+        self.call.adb.Shell(
+          'PATH="$PATH:/other/path" run_this', ensure_logs_on_timeout=False),
+        ''):
       self.device.RunShellCommand(
           ['run_this'], check_return=True, env={'PATH': '$PATH:/other/path'})
 
@@ -963,13 +993,17 @@
           ['some_cmd'], check_return=True, env={'INVALID NAME': 'value'})
 
   def testNewRunShellImpl_withCwd(self):
-    with self.assertCall(self.call.adb.Shell('cd /some/test/path && ls'), ''):
+    with self.assertCall(self.call.adb.Shell(
+      'cd /some/test/path && ls', ensure_logs_on_timeout=False), ''):
       self.device.RunShellCommand(
           ['ls'], check_return=True, cwd='/some/test/path')
 
   def testNewRunShellImpl_withCwdQuoted(self):
     with self.assertCall(
-        self.call.adb.Shell("cd '/some test/path with/spaces' && ls"), ''):
+        self.call.adb.Shell(
+          "cd '/some test/path with/spaces' && ls",
+          ensure_logs_on_timeout=False),
+        ''):
       self.device.RunShellCommand(
           ['ls'], check_return=True, cwd='/some test/path with/spaces')
 
@@ -980,7 +1014,9 @@
       (mock.call.devil.android.device_temp_file.DeviceTempFile(
           self.adb, suffix='.sh'), MockTempFile('/sdcard/temp-123.sh')),
       self.call.device._WriteFileWithPush('/sdcard/temp-123.sh', expected_cmd),
-      (self.call.adb.Shell('sh /sdcard/temp-123.sh'), payload + '\n')):
+      (self.call.adb.Shell(
+        'sh /sdcard/temp-123.sh', ensure_logs_on_timeout=False),
+       payload + '\n')):
       self.assertEquals(
           [payload],
           self.device.RunShellCommand(['echo', payload], check_return=True))
@@ -995,7 +1031,9 @@
       (mock.call.devil.android.device_temp_file.DeviceTempFile(
           self.adb, suffix='.sh'), MockTempFile('/sdcard/temp-123.sh')),
       self.call.device._WriteFileWithPush('/sdcard/temp-123.sh', expected_cmd),
-      (self.call.adb.Shell('sh /sdcard/temp-123.sh'), payload + '\n')):
+      (self.call.adb.Shell(
+        'sh /sdcard/temp-123.sh', ensure_logs_on_timeout=False),
+       payload + '\n')):
       self.assertEquals(
           [payload],
           self.device.RunShellCommand(
@@ -1007,7 +1045,8 @@
     with self.assertCalls(
         (self.call.device.NeedsSU(), True),
         (self.call.device._Su(expected_cmd_without_su), expected_cmd),
-        (self.call.adb.Shell(expected_cmd), '')):
+        (self.call.adb.Shell(
+          expected_cmd, ensure_logs_on_timeout=False), '')):
       self.device.RunShellCommand(
           ['setprop', 'service.adb.root', '0'],
           check_return=True, as_root=True)
@@ -1016,7 +1055,8 @@
     expected_cmd_without_run_as = "sh -c 'mkdir -p files'"
     expected_cmd = (
         'run-as org.devil.test_package %s' % expected_cmd_without_run_as)
-    with self.assertCall(self.call.adb.Shell(expected_cmd), ''):
+    with self.assertCall(self.call.adb.Shell(
+      expected_cmd, ensure_logs_on_timeout=False), ''):
       self.device.RunShellCommand(
           ['mkdir', '-p', 'files'],
           check_return=True, run_as='org.devil.test_package')
@@ -1031,7 +1071,8 @@
     with self.assertCalls(
         (self.call.device.NeedsSU(), True),
         (self.call.device._Su(expected_cmd_without_su), expected_cmd),
-        (self.call.adb.Shell(expected_cmd), '')):
+        (self.call.adb.Shell(
+          expected_cmd, ensure_logs_on_timeout=False), '')):
       self.device.RunShellCommand(
           ['mkdir', '-p', 'files'],
           check_return=True, run_as='org.devil.test_package',
@@ -1039,14 +1080,16 @@
 
   def testRunShellCommand_manyLines(self):
     cmd = 'ls /some/path'
-    with self.assertCall(self.call.adb.Shell(cmd), 'file1\nfile2\nfile3\n'):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), 'file1\nfile2\nfile3\n'):
       self.assertEquals(
           ['file1', 'file2', 'file3'],
           self.device.RunShellCommand(cmd.split(), check_return=True))
 
   def testRunShellCommand_manyLinesRawOutput(self):
     cmd = 'ls /some/path'
-    with self.assertCall(self.call.adb.Shell(cmd), '\rfile1\nfile2\r\nfile3\n'):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), '\rfile1\nfile2\r\nfile3\n'):
       self.assertEquals(
           '\rfile1\nfile2\r\nfile3\n',
           self.device.RunShellCommand(
@@ -1054,7 +1097,8 @@
 
   def testRunShellCommand_singleLine_success(self):
     cmd = 'echo $VALUE'
-    with self.assertCall(self.call.adb.Shell(cmd), 'some value\n'):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), 'some value\n'):
       self.assertEquals(
           'some value',
           self.device.RunShellCommand(
@@ -1062,7 +1106,8 @@
 
   def testRunShellCommand_singleLine_successEmptyLine(self):
     cmd = 'echo $VALUE'
-    with self.assertCall(self.call.adb.Shell(cmd), '\n'):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), '\n'):
       self.assertEquals(
           '',
           self.device.RunShellCommand(
@@ -1070,7 +1115,8 @@
 
   def testRunShellCommand_singleLine_successWithoutEndLine(self):
     cmd = 'echo -n $VALUE'
-    with self.assertCall(self.call.adb.Shell(cmd), 'some value'):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), 'some value'):
       self.assertEquals(
           'some value',
           self.device.RunShellCommand(
@@ -1078,7 +1124,8 @@
 
   def testRunShellCommand_singleLine_successNoOutput(self):
     cmd = 'echo -n $VALUE'
-    with self.assertCall(self.call.adb.Shell(cmd), ''):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), ''):
       self.assertEquals(
           '',
           self.device.RunShellCommand(
@@ -1086,7 +1133,8 @@
 
   def testRunShellCommand_singleLine_failTooManyLines(self):
     cmd = 'echo $VALUE'
-    with self.assertCall(self.call.adb.Shell(cmd),
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False),
                          'some value\nanother value\n'):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.RunShellCommand(
@@ -1095,7 +1143,8 @@
   def testRunShellCommand_checkReturn_success(self):
     cmd = 'echo $ANDROID_DATA'
     output = '/data\n'
-    with self.assertCall(self.call.adb.Shell(cmd), output):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), output):
       self.assertEquals(
           [output.rstrip()],
           self.device.RunShellCommand(cmd, shell=True, check_return=True))
@@ -1103,14 +1152,16 @@
   def testRunShellCommand_checkReturn_failure(self):
     cmd = 'ls /root'
     output = 'opendir failed, Permission denied\n'
-    with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), self.ShellError(output)):
       with self.assertRaises(device_errors.AdbCommandFailedError):
         self.device.RunShellCommand(cmd.split(), check_return=True)
 
   def testRunShellCommand_checkReturn_disabled(self):
     cmd = 'ls /root'
     output = 'opendir failed, Permission denied\n'
-    with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), self.ShellError(output)):
       self.assertEquals(
           [output.rstrip()],
           self.device.RunShellCommand(cmd.split(), check_return=False))
@@ -1122,7 +1173,7 @@
     with self.assertCalls(
         (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
             temp_file),
-        (self.call.adb.Shell(cmd_redirect)),
+        (self.call.adb.Shell(cmd_redirect, ensure_logs_on_timeout=False)),
         (self.call.device.ReadFile(temp_file.name, force_pull=True),
          'something')):
       self.assertEquals(
@@ -1132,7 +1183,8 @@
 
   def testRunShellCommand_largeOutput_disabledNoTrigger(self):
     cmd = 'something'
-    with self.assertCall(self.call.adb.Shell(cmd), self.ShellError('')):
+    with self.assertCall(self.call.adb.Shell(
+      cmd, ensure_logs_on_timeout=False), self.ShellError('')):
       with self.assertRaises(device_errors.AdbCommandFailedError):
         self.device.RunShellCommand([cmd], check_return=True)
 
@@ -1141,10 +1193,12 @@
     temp_file = MockTempFile('/sdcard/temp-123')
     cmd_redirect = '( %s )>%s 2>&1' % (cmd, temp_file.name)
     with self.assertCalls(
-        (self.call.adb.Shell(cmd), self.ShellError('', None)),
+        (self.call.adb.Shell(
+          cmd, ensure_logs_on_timeout=False), self.ShellError('', None)),
         (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
             temp_file),
-        (self.call.adb.Shell(cmd_redirect)),
+        (self.call.adb.Shell(
+          cmd_redirect, ensure_logs_on_timeout=False)),
         (self.call.device.ReadFile(mock.ANY, force_pull=True),
          'something')):
       self.assertEquals(
@@ -1210,7 +1264,8 @@
     with self.assertCalls(
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234), ('some.process.thing', 5678))),
-        (self.call.adb.Shell('kill -9 1234 5678'), '')):
+        (self.call.adb.Shell(
+          'kill -9 1234 5678', ensure_logs_on_timeout=False), '')):
       self.assertEquals(
           2, self.device.KillAll('some.process', blocking=False))
 
@@ -1218,7 +1273,8 @@
     with self.assertCalls(
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234), ('some.process.thing', 5678))),
-        (self.call.adb.Shell('kill -9 1234 5678'), ''),
+        (self.call.adb.Shell(
+          'kill -9 1234 5678', ensure_logs_on_timeout=False), ''),
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process.thing', 5678))),
         (self.call.device.ListProcesses('some.process'),
@@ -1231,7 +1287,8 @@
     with self.assertCalls(
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234), ('some.process.thing', 5678))),
-        (self.call.adb.Shell('kill -9 1234'), '')):
+        (self.call.adb.Shell(
+          'kill -9 1234', ensure_logs_on_timeout=False), '')):
       self.assertEquals(
           1, self.device.KillAll('some.process', exact=True, blocking=False))
 
@@ -1239,7 +1296,8 @@
     with self.assertCalls(
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234), ('some.process.thing', 5678))),
-        (self.call.adb.Shell('kill -9 1234'), ''),
+        (self.call.adb.Shell(
+          'kill -9 1234', ensure_logs_on_timeout=False), ''),
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234), ('some.process.thing', 5678))),
         (self.call.device.ListProcesses('some.process'),
@@ -1254,7 +1312,8 @@
         (self.call.device.NeedsSU(), True),
         (self.call.device._Su("sh -c 'kill -9 1234'"),
          "su -c sh -c 'kill -9 1234'"),
-        (self.call.adb.Shell("su -c sh -c 'kill -9 1234'"), '')):
+        (self.call.adb.Shell(
+          "su -c sh -c 'kill -9 1234'", ensure_logs_on_timeout=False), '')):
       self.assertEquals(
           1, self.device.KillAll('some.process', as_root=True))
 
@@ -1262,7 +1321,8 @@
     with self.assertCalls(
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234))),
-        (self.call.adb.Shell('kill -15 1234'), '')):
+        (self.call.adb.Shell(
+          'kill -15 1234', ensure_logs_on_timeout=False), '')):
       self.assertEquals(
           1, self.device.KillAll('some.process', signum=device_signal.SIGTERM))
 
@@ -1270,7 +1330,8 @@
     with self.assertCalls(
         (self.call.device.ListProcesses('some.process'),
          Processes(('some.process', 1234), ('some.process', 4567))),
-        (self.call.adb.Shell('kill -15 1234 4567'), '')):
+        (self.call.adb.Shell(
+          'kill -15 1234 4567', ensure_logs_on_timeout=False), '')):
       self.assertEquals(
           2, self.device.KillAll('some.process', signum=device_signal.SIGTERM))
 
@@ -1280,8 +1341,10 @@
   def testStartActivity_actionOnly(self):
     test_intent = intent.Intent(action='android.intent.action.VIEW')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1290,9 +1353,11 @@
                                 package='test.package',
                                 activity='.Main')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1301,9 +1366,11 @@
                                 package='test.package',
                                 activity='.Main')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Error: Failed to start test activity'):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.StartActivity(test_intent)
@@ -1313,10 +1380,12 @@
                                 package='test.package',
                                 activity='.Main')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-W '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-W '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent, blocking=True)
 
@@ -1326,10 +1395,12 @@
                                 activity='.Main',
                                 category='android.intent.category.HOME')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-c android.intent.category.HOME '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-c android.intent.category.HOME '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1340,11 +1411,13 @@
                                 category=['android.intent.category.HOME',
                                           'android.intent.category.BROWSABLE'])
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-c android.intent.category.HOME '
-                            '-c android.intent.category.BROWSABLE '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-c android.intent.category.HOME '
+          '-c android.intent.category.BROWSABLE '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1354,10 +1427,12 @@
                                 activity='.Main',
                                 data='http://www.google.com/')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-d http://www.google.com/ '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-d http://www.google.com/ '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1367,10 +1442,12 @@
                                 activity='.Main',
                                 extras={'foo': 'test'})
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main '
-                            '--es foo test'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main '
+          '--es foo test',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1380,10 +1457,12 @@
                                 activity='.Main',
                                 extras={'foo': True})
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main '
-                            '--ez foo True'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main '
+          '--ez foo True',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1393,10 +1472,12 @@
                                 activity='.Main',
                                 extras={'foo': 123})
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main '
-                            '--ei foo 123'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main '
+          '--ei foo 123',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1405,10 +1486,12 @@
                                 package='test.package',
                                 activity='.Main')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '--start-profiler test_trace_file.out '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '--start-profiler test_trace_file.out '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent,
                                 trace_file_name='test_trace_file.out')
@@ -1418,10 +1501,12 @@
                                 package='test.package',
                                 activity='.Main')
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-S '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main'),
+        self.call.adb.Shell(
+          'am start '
+          '-S '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent, force_stop=True)
 
@@ -1434,10 +1519,12 @@
                                   intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                                 ])
     with self.assertCall(
-        self.call.adb.Shell('am start '
-                            '-a android.intent.action.VIEW '
-                            '-n test.package/.Main '
-                            '-f 0x10200000'),
+        self.call.adb.Shell(
+          'am start '
+          '-a android.intent.action.VIEW '
+          '-n test.package/.Main '
+          '-f 0x10200000',
+          ensure_logs_on_timeout=False),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1450,9 +1537,11 @@
     with self.patch_call(self.call.device.build_version_sdk,
                          return_value=version_codes.NOUGAT):
       with self.assertCall(
-          self.call.adb.Shell('am startservice '
-                              '-a android.intent.action.START '
-                              '-n test.package/.Main'),
+          self.call.adb.Shell(
+            'am startservice '
+            '-a android.intent.action.START '
+            '-n test.package/.Main',
+            ensure_logs_on_timeout=False),
           'Starting service: Intent { act=android.intent.action.START }'):
         self.device.StartService(test_intent)
 
@@ -1463,9 +1552,11 @@
     with self.patch_call(self.call.device.build_version_sdk,
                          return_value=version_codes.NOUGAT):
       with self.assertCall(
-          self.call.adb.Shell('am startservice '
-                              '-a android.intent.action.START '
-                              '-n test.package/.Main'),
+          self.call.adb.Shell(
+            'am startservice '
+            '-a android.intent.action.START '
+            '-n test.package/.Main',
+            ensure_logs_on_timeout=False),
           'Error: Failed to start test service'):
         with self.assertRaises(device_errors.CommandFailedError):
           self.device.StartService(test_intent)
@@ -1477,10 +1568,12 @@
     with self.patch_call(self.call.device.build_version_sdk,
                          return_value=version_codes.NOUGAT):
       with self.assertCall(
-          self.call.adb.Shell('am startservice '
-                              '--user TestUser '
-                              '-a android.intent.action.START '
-                              '-n test.package/.Main'),
+          self.call.adb.Shell(
+            'am startservice '
+            '--user TestUser '
+            '-a android.intent.action.START '
+            '-n test.package/.Main',
+            ensure_logs_on_timeout=False),
           'Starting service: Intent { act=android.intent.action.START }'):
         self.device.StartService(test_intent, user_id='TestUser')
 
@@ -1491,9 +1584,11 @@
     with self.patch_call(self.call.device.build_version_sdk,
                          return_value=version_codes.OREO):
       with self.assertCall(
-          self.call.adb.Shell('am start-service '
-                              '-a android.intent.action.START '
-                              '-n test.package/.Main'),
+          self.call.adb.Shell(
+            'am start-service '
+            '-a android.intent.action.START '
+            '-n test.package/.Main',
+            ensure_logs_on_timeout=False),
           'Starting service: Intent { act=android.intent.action.START }'):
         self.device.StartService(test_intent)
 
@@ -1546,7 +1641,9 @@
   def testBroadcastIntent_noExtras(self):
     test_intent = intent.Intent(action='test.package.with.an.INTENT')
     with self.assertCall(
-        self.call.adb.Shell('am broadcast -a test.package.with.an.INTENT'),
+        self.call.adb.Shell(
+          'am broadcast -a test.package.with.an.INTENT',
+          ensure_logs_on_timeout=False),
         'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
       self.device.BroadcastIntent(test_intent)
 
@@ -1555,7 +1652,8 @@
                                 extras={'foo': 'bar value'})
     with self.assertCall(
         self.call.adb.Shell(
-            "am broadcast -a test.package.with.an.INTENT --es foo 'bar value'"),
+            "am broadcast -a test.package.with.an.INTENT --es foo 'bar value'",
+            ensure_logs_on_timeout=False),
         'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
       self.device.BroadcastIntent(test_intent)
 
@@ -1564,7 +1662,8 @@
                                 extras={'foo': None})
     with self.assertCall(
         self.call.adb.Shell(
-            'am broadcast -a test.package.with.an.INTENT --esn foo'),
+            'am broadcast -a test.package.with.an.INTENT --esn foo',
+            ensure_logs_on_timeout=False),
         'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
       self.device.BroadcastIntent(test_intent)
 
@@ -1728,7 +1827,8 @@
 class DeviceUtilsSendKeyEventTest(DeviceUtilsTest):
 
   def testSendKeyEvent(self):
-    with self.assertCall(self.call.adb.Shell('input keyevent 66'), ''):
+    with self.assertCall(self.call.adb.Shell(
+      'input keyevent 66', ensure_logs_on_timeout=False), ''):
       self.device.SendKeyEvent(66)
 
 
@@ -2067,12 +2167,14 @@
 
   def testWriteFile_withEcho(self):
     with self.assertCall(self.call.adb.Shell(
-        "echo -n the.contents > /test/file/to.write"), ''):
+        "echo -n the.contents > /test/file/to.write",
+        ensure_logs_on_timeout=False), ''):
       self.device.WriteFile('/test/file/to.write', 'the.contents')
 
   def testWriteFile_withEchoAndQuotes(self):
     with self.assertCall(self.call.adb.Shell(
-        "echo -n 'the contents' > '/test/file/to write'"), ''):
+        "echo -n 'the contents' > '/test/file/to write'",
+        ensure_logs_on_timeout=False), ''):
       self.device.WriteFile('/test/file/to write', 'the contents')
 
   def testWriteFile_withEchoAndSU(self):
@@ -2081,7 +2183,8 @@
     with self.assertCalls(
         (self.call.device.NeedsSU(), True),
         (self.call.device._Su(expected_cmd_without_su), expected_cmd),
-        (self.call.adb.Shell(expected_cmd),
+        (self.call.adb.Shell(
+          expected_cmd, ensure_logs_on_timeout=False),
          '')):
       self.device.WriteFile('/test/file', 'contents', as_root=True)
 
@@ -2562,51 +2665,60 @@
 class DeviceUtilsGetSetEnforce(DeviceUtilsTest):
 
   def testGetEnforce_Enforcing(self):
-    with self.assertCall(self.call.adb.Shell('getenforce'), 'Enforcing'):
+    with self.assertCall(self.call.adb.Shell(
+      'getenforce', ensure_logs_on_timeout=False), 'Enforcing'):
       self.assertEqual(True, self.device.GetEnforce())
 
   def testGetEnforce_Permissive(self):
-    with self.assertCall(self.call.adb.Shell('getenforce'), 'Permissive'):
+    with self.assertCall(self.call.adb.Shell(
+      'getenforce', ensure_logs_on_timeout=False), 'Permissive'):
       self.assertEqual(False, self.device.GetEnforce())
 
   def testGetEnforce_Disabled(self):
-    with self.assertCall(self.call.adb.Shell('getenforce'), 'Disabled'):
+    with self.assertCall(self.call.adb.Shell(
+      'getenforce', ensure_logs_on_timeout=False), 'Disabled'):
       self.assertEqual(None, self.device.GetEnforce())
 
   def testSetEnforce_Enforcing(self):
     with self.assertCalls(
         (self.call.device.NeedsSU(), False),
-        (self.call.adb.Shell('setenforce 1'), '')):
+        (self.call.adb.Shell(
+          'setenforce 1', ensure_logs_on_timeout=False), '')):
       self.device.SetEnforce(enabled=True)
 
   def testSetEnforce_Permissive(self):
     with self.assertCalls(
         (self.call.device.NeedsSU(), False),
-        (self.call.adb.Shell('setenforce 0'), '')):
+        (self.call.adb.Shell(
+          'setenforce 0', ensure_logs_on_timeout=False), '')):
       self.device.SetEnforce(enabled=False)
 
   def testSetEnforce_EnforcingWithInt(self):
     with self.assertCalls(
         (self.call.device.NeedsSU(), False),
-        (self.call.adb.Shell('setenforce 1'), '')):
+        (self.call.adb.Shell(
+          'setenforce 1', ensure_logs_on_timeout=False), '')):
       self.device.SetEnforce(enabled=1)
 
   def testSetEnforce_PermissiveWithInt(self):
     with self.assertCalls(
         (self.call.device.NeedsSU(), False),
-        (self.call.adb.Shell('setenforce 0'), '')):
+        (self.call.adb.Shell(
+          'setenforce 0', ensure_logs_on_timeout=False), '')):
       self.device.SetEnforce(enabled=0)
 
   def testSetEnforce_EnforcingWithStr(self):
     with self.assertCalls(
         (self.call.device.NeedsSU(), False),
-        (self.call.adb.Shell('setenforce 1'), '')):
+        (self.call.adb.Shell(
+          'setenforce 1', ensure_logs_on_timeout=False), '')):
       self.device.SetEnforce(enabled='1')
 
   def testSetEnforce_PermissiveWithStr(self):
     with self.assertCalls(
         (self.call.device.NeedsSU(), False),
-        (self.call.adb.Shell('setenforce 0'), '')):
+        (self.call.adb.Shell(
+          'setenforce 0', ensure_logs_on_timeout=False), '')):
       self.device.SetEnforce(enabled='0')  # Not recommended but it works!
 
 
@@ -2614,12 +2726,14 @@
 
   def testSetWebViewImplementation_success(self):
     with self.assertCall(self.call.adb.Shell(
-        'cmd webviewupdate set-webview-implementation foo.org'), 'Success'):
+        'cmd webviewupdate set-webview-implementation foo.org',
+        ensure_logs_on_timeout=False), 'Success'):
       self.device.SetWebViewImplementation('foo.org')
 
   def testSetWebViewImplementation_failure(self):
     with self.assertCall(self.call.adb.Shell(
-        'cmd webviewupdate set-webview-implementation foo.org'), 'Oops!'):
+        'cmd webviewupdate set-webview-implementation foo.org',
+        ensure_logs_on_timeout=False), 'Oops!'):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.SetWebViewImplementation('foo.org')
 
@@ -2631,7 +2745,9 @@
         (mock.call.devil.android.device_temp_file.DeviceTempFile(
             self.adb, suffix='.png'),
          MockTempFile('/tmp/path/temp-123.png')),
-        (self.call.adb.Shell('/system/bin/screencap -p /tmp/path/temp-123.png'),
+        (self.call.adb.Shell(
+          '/system/bin/screencap -p /tmp/path/temp-123.png',
+          ensure_logs_on_timeout=False),
          ''),
         self.call.device.PullFile('/tmp/path/temp-123.png',
                                   '/test/host/screenshot.png')):
@@ -2775,7 +2891,7 @@
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
          [_AdbWrapperMock(s) for s in test_serials])):
       with self.assertRaises(device_errors.NoDevicesError):
-        device_utils.DeviceUtils.HealthyDevices(device_arg=None, retry=False)
+        device_utils.DeviceUtils.HealthyDevices(device_arg=None, retries=0)
 
   def testHealthyDevices_noneDeviceArg_multiple_attached_ANDROID_SERIAL(self):
     try:
@@ -2818,17 +2934,23 @@
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
          [_AdbWrapperMock(s) for s in test_serials])):
       with self.assertRaises(device_errors.NoDevicesError):
-        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retry=False)
+        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retries=0)
 
-  def testHealthyDevices_EmptyListDeviceArg_no_attached_with_retry(self):
-    test_serials = []
+  @mock.patch('time.sleep')
+  @mock.patch('devil.android.device_utils.RestartServer')
+  def testHealthyDevices_EmptyListDeviceArg_no_attached_with_retry(
+      self, mock_restart, mock_sleep):
     with self.assertCalls(
-        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
-         [_AdbWrapperMock(s) for s in test_serials]),
-        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
-         [_AdbWrapperMock(s) for s in test_serials])):
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(), []),
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(), []),
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(), []),
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(), []),
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(), [])):
       with self.assertRaises(device_errors.NoDevicesError):
-        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retry=True)
+        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retries=4)
+    self.assertEquals(mock_restart.call_count, 4)
+    self.assertEquals(mock_sleep.call_args_list, [
+        mock.call(2), mock.call(4), mock.call(8), mock.call(16)])
 
   def testHealthyDevices_ListDeviceArg(self):
     device_arg = ['0123456789abcdef', 'fedcba9876543210']
@@ -2850,7 +2972,7 @@
         (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
          ARM32_ABI)):
       with self.assertRaises(device_errors.NoDevicesError):
-        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retry=False,
+        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retries=0,
                                                 abis=[ARM64_ABI])
 
   def testHealthyDevices_abisArg_filter_on_abi(self):
@@ -2863,7 +2985,7 @@
         (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
          ARM32_ABI)):
       devices = device_utils.DeviceUtils.HealthyDevices(device_arg=[],
-                                                        retry=False,
+                                                        retries=0,
                                                         abis=[ARM64_ABI])
     self.assertEquals(1, len(devices))
 
@@ -3078,7 +3200,9 @@
         '  Device ID = 123454321')
     with self.assertCalls(
         (self.call.device.GetProp('ro.build.version.sdk', cache=True), '19'),
-        (self.call.adb.Shell('dumpsys iphonesubinfo'), dumpsys_output)):
+        (self.call.adb.Shell(
+          'dumpsys iphonesubinfo', ensure_logs_on_timeout=False),
+         dumpsys_output)):
       self.assertEquals(self.device.GetIMEI(), '123454321')
 
   def testSuccessfulServiceCall(self):
@@ -3090,20 +3214,25 @@
     """
     with self.assertCalls(
         (self.call.device.GetProp('ro.build.version.sdk', cache=True), '24'),
-        (self.call.adb.Shell('service call iphonesubinfo 1'), service_output)):
+        (self.call.adb.Shell(
+          'service call iphonesubinfo 1', ensure_logs_on_timeout=False),
+         service_output)):
       self.assertEquals(self.device.GetIMEI(), '765432101234567')
 
   def testNoIMEI(self):
     with self.assertCalls(
         (self.call.device.GetProp('ro.build.version.sdk', cache=True), '19'),
-        (self.call.adb.Shell('dumpsys iphonesubinfo'), 'no device id')):
+        (self.call.adb.Shell(
+          'dumpsys iphonesubinfo', ensure_logs_on_timeout=False),
+         'no device id')):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.GetIMEI()
 
   def testAdbError(self):
     with self.assertCalls(
         (self.call.device.GetProp('ro.build.version.sdk', cache=True), '24'),
-        (self.call.adb.Shell('service call iphonesubinfo 1'),
+        (self.call.adb.Shell(
+          'service call iphonesubinfo 1', ensure_logs_on_timeout=False),
          self.ShellError())):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.GetIMEI()
diff --git a/catapult/devil/devil/android/flag_changer.py b/catapult/devil/devil/android/flag_changer.py
index 9cb1c02..c96dbad 100644
--- a/catapult/devil/devil/android/flag_changer.py
+++ b/catapult/devil/devil/android/flag_changer.py
@@ -93,7 +93,8 @@
       A list of flags.
     """
     if self._device.PathExists(self._cmdline_path):
-      command_line = self._device.ReadFile(self._cmdline_path).strip()
+      command_line = self._device.ReadFile(
+          self._cmdline_path, as_root=True).strip()
     else:
       command_line = ''
     flags = _ParseFlags(command_line)
@@ -216,9 +217,9 @@
     """
     command_line = _SerializeFlags(self._state_stack[-1])
     if command_line is not None:
-      self._device.WriteFile(self._cmdline_path, command_line)
+      self._device.WriteFile(self._cmdline_path, command_line, as_root=True)
     else:
-      self._device.RemovePath(self._cmdline_path, force=True)
+      self._device.RemovePath(self._cmdline_path, force=True, as_root=True)
 
     current_flags = self.GetCurrentFlags()
     logger.info('Flags now set on the device: %s', current_flags)
diff --git a/catapult/devil/devil/android/perf/perf_control.py b/catapult/devil/devil/android/perf/perf_control.py
index 9ac85eb..2aa3b2f 100644
--- a/catapult/devil/devil/android/perf/perf_control.py
+++ b/catapult/devil/devil/android/perf/perf_control.py
@@ -9,6 +9,84 @@
 from devil.android import device_errors
 
 logger = logging.getLogger(__name__)
+_atexit_messages = set()
+
+
+# Defines how to switch between the default performance configuration
+# ('default_mode') and the mode for use when benchmarking ('high_perf_mode').
+# For devices not in the list the defaults are to set up the scaling governor to
+# 'performance' and reset it back to 'ondemand' when benchmarking is finished.
+#
+# The 'default_mode_governor' is mandatory to define, while
+# 'high_perf_mode_governor' is not taken into account. The latter is because the
+# governor 'performance' is currently used for all benchmarking on all devices.
+#
+# TODO(crbug.com/383566): Add definitions for all devices used in the perf
+# waterfall.
+_PERFORMANCE_MODE_DEFINITIONS = {
+  # Fire TV Edition - 4K
+  'AFTKMST12': {
+    'default_mode_governor': 'interactive',
+  },
+  'GT-I9300': {
+    'default_mode_governor': 'pegasusq',
+  },
+  'Galaxy Nexus': {
+    'default_mode_governor': 'interactive',
+  },
+  'Nexus 7': {
+    'default_mode_governor': 'interactive',
+  },
+  'Nexus 10': {
+    'default_mode_governor': 'interactive',
+  },
+  'Nexus 4': {
+    'high_perf_mode': {
+      'bring_cpu_cores_online': True,
+    },
+    'default_mode_governor': 'ondemand',
+  },
+  'Nexus 5': {
+    # The list of possible GPU frequency values can be found in:
+    #     /sys/class/kgsl/kgsl-3d0/gpu_available_frequencies.
+    # For CPU cores the possible frequency values are at:
+    #     /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
+    'high_perf_mode': {
+      'bring_cpu_cores_online': True,
+      'cpu_max_freq': 1190400,
+      'gpu_max_freq': 200000000,
+    },
+    'default_mode': {
+      'cpu_max_freq': 2265600,
+      'gpu_max_freq': 450000000,
+    },
+    'default_mode_governor': 'ondemand',
+  },
+  'Nexus 5X': {
+    'high_perf_mode': {
+      'bring_cpu_cores_online': True,
+      'cpu_max_freq': 1248000,
+      'gpu_max_freq': 300000000,
+    },
+    'default_mode': {
+      'governor': 'ondemand',
+      # The SoC is ARM big.LITTLE. The cores 4..5 are big, the 0..3 are LITTLE.
+      'cpu_max_freq': {'0..3': 1440000, '4..5': 1824000},
+      'gpu_max_freq': 600000000,
+    },
+    'default_mode_governor': 'ondemand',
+  },
+}
+
+
+def _NoisyWarning(message):
+  message += ' Results may be NOISY!!'
+  logger.warning(message)
+  # Add an additional warning at exit, such that it's clear that any results
+  # may be different/noisy (due to the lack of intended performance mode).
+  if message not in _atexit_messages:
+    _atexit_messages.add(message)
+    atexit.register(logger.warning, message)
 
 
 class PerfControl(object):
@@ -21,10 +99,10 @@
 
   def __init__(self, device):
     self._device = device
-    self._cpu_files = [
-        filename
-        for filename in self._device.ListDirectory(self._CPU_PATH, as_root=True)
-        if self._CPU_FILE_PATTERN.match(filename)]
+    self._cpu_files = []
+    for file_name in self._device.ListDirectory(self._CPU_PATH, as_root=True):
+      if self._CPU_FILE_PATTERN.match(file_name):
+        self._cpu_files.append(file_name)
     assert self._cpu_files, 'Failed to detect CPUs.'
     self._cpu_file_list = ' '.join(self._cpu_files)
     logger.info('CPUs found: %s', self._cpu_file_list)
@@ -36,34 +114,75 @@
         (cpu, raw_governors.strip().split() if not exit_code else None)
         for cpu, raw_governors, exit_code in raw]
 
+  def _SetMaxFrequenciesFromMode(self, mode):
+    """Set maximum frequencies for GPU and CPU cores.
+
+    Args:
+      mode: A dictionary mapping optional keys 'cpu_max_freq' and 'gpu_max_freq'
+            to integer values of frequency supported by the device.
+    """
+    cpu_max_freq = mode.get('cpu_max_freq')
+    if cpu_max_freq:
+      if not isinstance(cpu_max_freq, dict):
+        self._SetScalingMaxFreqForCpus(cpu_max_freq, self._cpu_file_list)
+      else:
+        for key, max_frequency in cpu_max_freq.iteritems():
+          # Convert 'X' to 'cpuX' and 'X..Y' to 'cpuX cpu<X+1> .. cpuY'.
+          if '..' in key:
+            range_min, range_max = key.split('..')
+            range_min, range_max = int(range_min), int(range_max)
+          else:
+            range_min = range_max = int(key)
+          cpu_files = ['cpu%d' % number
+                       for number in xrange(range_min, range_max + 1)]
+          # Set the |max_frequency| on requested subset of the cores.
+          self._SetScalingMaxFreqForCpus(max_frequency, ' '.join(cpu_files))
+    gpu_max_freq = mode.get('gpu_max_freq')
+    if gpu_max_freq:
+      self._SetMaxGpuClock(gpu_max_freq)
+
   def SetHighPerfMode(self):
     """Sets the highest stable performance mode for the device."""
     try:
       self._device.EnableRoot()
     except device_errors.CommandFailedError:
-      message = 'Need root for performance mode. Results may be NOISY!!'
-      logger.warning(message)
-      # Add an additional warning at exit, such that it's clear that any results
-      # may be different/noisy (due to the lack of intended performance mode).
-      atexit.register(logger.warning, message)
+      _NoisyWarning('Need root for performance mode.')
       return
+    mode_definitions = _PERFORMANCE_MODE_DEFINITIONS.get(
+        self._device.product_model)
+    if not mode_definitions:
+      self.SetScalingGovernor('performance')
+      return
+    high_perf_mode = mode_definitions.get('high_perf_mode')
+    if not high_perf_mode:
+      self.SetScalingGovernor('performance')
+      return
+    if high_perf_mode.get('bring_cpu_cores_online', False):
+      self._ForceAllCpusOnline(True)
+      if not self._AllCpusAreOnline():
+        _NoisyWarning('Failed to force CPUs online.')
+    # Scaling governor must be set _after_ bringing all CPU cores online,
+    # otherwise it would not affect the cores that are currently offline.
+    self.SetScalingGovernor('performance')
+    self._SetMaxFrequenciesFromMode(high_perf_mode)
 
-    product_model = self._device.product_model
-    # TODO(epenner): Enable on all devices (http://crbug.com/383566)
-    if product_model == 'Nexus 4':
-      self._ForceAllCpusOnline(True)
-      if not self._AllCpusAreOnline():
-        logger.warning('Failed to force CPUs online. Results may be NOISY!')
-      self.SetScalingGovernor('performance')
-    elif product_model == 'Nexus 5':
-      self._ForceAllCpusOnline(True)
-      if not self._AllCpusAreOnline():
-        logger.warning('Failed to force CPUs online. Results may be NOISY!')
-      self.SetScalingGovernor('performance')
-      self._SetScalingMaxFreq(1190400)
-      self._SetMaxGpuClock(200000000)
+  def SetDefaultPerfMode(self):
+    """Sets the performance mode for the device to its default mode."""
+    if not self._device.HasRoot():
+      return
+    mode_definitions = _PERFORMANCE_MODE_DEFINITIONS.get(
+        self._device.product_model)
+    if not mode_definitions:
+      self.SetScalingGovernor('ondemand')
     else:
-      self.SetScalingGovernor('performance')
+      default_mode_governor = mode_definitions.get('default_mode_governor')
+      assert default_mode_governor, ('Default mode governor must be provided '
+          'for all perf mode definitions.')
+      self.SetScalingGovernor(default_mode_governor)
+      default_mode = mode_definitions.get('default_mode')
+      if default_mode:
+        self._SetMaxFrequenciesFromMode(default_mode)
+    self._ForceAllCpusOnline(False)
 
   def SetPerfProfilingMode(self):
     """Enables all cores for reliable perf profiling."""
@@ -74,27 +193,6 @@
         raise RuntimeError('Need root to force CPUs online.')
       raise RuntimeError('Failed to force CPUs online.')
 
-  def SetDefaultPerfMode(self):
-    """Sets the performance mode for the device to its default mode."""
-    if not self._device.HasRoot():
-      return
-    product_model = self._device.product_model
-    if product_model == 'Nexus 5':
-      if self._AllCpusAreOnline():
-        self._SetScalingMaxFreq(2265600)
-        self._SetMaxGpuClock(450000000)
-
-    governor_mode = {
-        'GT-I9300': 'pegasusq',
-        'Galaxy Nexus': 'interactive',
-        'Nexus 4': 'ondemand',
-        'Nexus 5': 'ondemand',
-        'Nexus 7': 'interactive',
-        'Nexus 10': 'interactive'
-    }.get(product_model, 'ondemand')
-    self.SetScalingGovernor(governor_mode)
-    self._ForceAllCpusOnline(False)
-
   def GetCpuInfo(self):
     online = (output.rstrip() == '1' and status == 0
               for (_, output, status) in self._ForEachCpu('cat "$CPU/online"'))
@@ -103,9 +201,24 @@
                 in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"'))
     return zip(self._cpu_files, online, governor)
 
-  def _ForEachCpu(self, cmd):
+  def _ForEachCpu(self, cmd, cpu_list=None):
+    """Runs a command on the device for each of the CPUs.
+
+    Args:
+      cmd: A string with a shell command, may may use shell expansion: "$CPU" to
+           refer to the current CPU in the string form (e.g. "cpu0", "cpu1",
+           and so on).
+      cpu_list: A space-separated string of CPU core names, like in the example
+           above
+    Returns:
+      A list of tuples in the form (cpu_string, command_output, exit_code), one
+      tuple per each command invocation. As usual, all lines of the output
+      command are joined into one line with spaces.
+    """
+    if cpu_list is None:
+      cpu_list = self._cpu_file_list
     script = '; '.join([
-        'for CPU in %s' % self._cpu_file_list,
+        'for CPU in %s' % cpu_list,
         'do %s' % cmd,
         'echo -n "%~%$?%~%"',
         'done'
@@ -115,20 +228,20 @@
     output = '\n'.join(output).split('%~%')
     return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2]))
 
-  def _WriteEachCpuFile(self, path, value):
-    self._ConditionallyWriteEachCpuFile(path, value, condition='true')
-
-  def _ConditionallyWriteEachCpuFile(self, path, value, condition):
+  def _ConditionallyWriteCpuFiles(self, path, value, cpu_files, condition):
     template = (
         '{condition} && test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"')
     results = self._ForEachCpu(
-        template.format(path=path, value=value, condition=condition))
+        template.format(path=path, value=value, condition=condition), cpu_files)
     cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0)
     if cpus:
       logger.info('Successfully set %s to %r on: %s', path, value, cpus)
     else:
       logger.warning('Failed to set %s to %r on any cpus', path, value)
 
+  def _WriteCpuFiles(self, path, value, cpu_files):
+    self._ConditionallyWriteCpuFiles(path, value, cpu_files, condition='true')
+
   def _ReadEachCpuFile(self, path):
     return self._ForEachCpu(
         'cat "$CPU/{path}"'.format(path=path))
@@ -145,8 +258,8 @@
     condition = 'test -e "{path}" && grep -q {value} {path}'.format(
         path=('${CPU}/%s' % self._AVAILABLE_GOVERNORS_REL_PATH),
         value=value)
-    self._ConditionallyWriteEachCpuFile(
-        'cpufreq/scaling_governor', value, condition)
+    self._ConditionallyWriteCpuFiles(
+        'cpufreq/scaling_governor', value, self._cpu_file_list, condition)
 
   def GetScalingGovernor(self):
     """Gets the currently set governor for each CPU.
@@ -169,8 +282,8 @@
     """
     return self._available_governors
 
-  def _SetScalingMaxFreq(self, value):
-    self._WriteEachCpuFile('cpufreq/scaling_max_freq', '%d' % value)
+  def _SetScalingMaxFreqForCpus(self, value, cpu_files):
+    self._WriteCpuFiles('cpufreq/scaling_max_freq', '%d' % value, cpu_files)
 
   def _SetMaxGpuClock(self, value):
     self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk',
@@ -179,8 +292,9 @@
 
   def _AllCpusAreOnline(self):
     results = self._ForEachCpu('cat "$CPU/online"')
-    # TODO(epenner): Investigate why file may be missing
-    # (http://crbug.com/397118)
+    # The file 'cpu0/online' is missing on some devices (example: Nexus 9). This
+    # is likely because on these devices it is impossible to bring the cpu0
+    # offline. Assuming the same for all devices until proven otherwise.
     return all(output.rstrip() == '1' and status == 0
                for (cpu, output, status) in results
                if cpu != 'cpu0')
diff --git a/catapult/devil/devil/android/perf/perf_control_test.py b/catapult/devil/devil/android/perf/perf_control_test.py
new file mode 100644
index 0000000..3832424
--- /dev/null
+++ b/catapult/devil/devil/android/perf/perf_control_test.py
@@ -0,0 +1,105 @@
+# Copyright 2018 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.
+
+import unittest
+
+from devil import devil_env
+from devil.android import device_utils
+from devil.android.perf import perf_control
+from devil.android.sdk import adb_wrapper
+
+with devil_env.SysPath(devil_env.PYMOCK_PATH):
+  import mock
+
+
+# pylint: disable=unused-argument
+def _ShellCommandHandler(cmd, shell=False, check_return=False,
+    cwd=None, env=None, run_as=None, as_root=False, single_line=False,
+    large_output=False, raw_output=False, timeout=None, retries=None):
+  if cmd.startswith('for CPU in '):
+    if 'scaling_available_governors' in cmd:
+      contents = 'interactive ondemand userspace powersave performance'
+      return [contents + '\n%~%0%~%'] * 4
+    if 'cat "$CPU/online"' in cmd:
+      return ['1\n%~%0%~%'] * 4
+  assert False, 'Should not be called with cmd: {}'.format(cmd)
+
+
+class PerfControlTest(unittest.TestCase):
+  @staticmethod
+  def _MockOutLowLevelPerfControlMethods(perf_control_object):
+    # pylint: disable=protected-access
+    perf_control_object.SetScalingGovernor = mock.Mock()
+    perf_control_object._ForceAllCpusOnline = mock.Mock()
+    perf_control_object._SetScalingMaxFreqForCpus = mock.Mock()
+    perf_control_object._SetMaxGpuClock = mock.Mock()
+
+  # pylint: disable=no-self-use
+  def testNexus5HighPerfMode(self):
+    # Mock out the device state for PerfControl.
+    cpu_list = ['cpu%d' % cpu for cpu in xrange(4)]
+    mock_device = mock.Mock(spec=device_utils.DeviceUtils)
+    mock_device.product_model = 'Nexus 5'
+    mock_device.adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+    mock_device.ListDirectory.return_value = cpu_list + ['cpufreq']
+    mock_device.FileExists.return_value = True
+    mock_device.RunShellCommand = mock.Mock(side_effect=_ShellCommandHandler)
+    pc = perf_control.PerfControl(mock_device)
+    self._MockOutLowLevelPerfControlMethods(pc)
+
+    # Verify.
+    # pylint: disable=protected-access
+    # pylint: disable=no-member
+    pc.SetHighPerfMode()
+    mock_device.EnableRoot.assert_called_once_with()
+    pc._ForceAllCpusOnline.assert_called_once_with(True)
+    pc.SetScalingGovernor.assert_called_once_with('performance')
+    pc._SetScalingMaxFreqForCpus.assert_called_once_with(
+        1190400, ' '.join(cpu_list))
+    pc._SetMaxGpuClock.assert_called_once_with(200000000)
+
+  def testNexus5XHighPerfMode(self):
+    # Mock out the device state for PerfControl.
+    cpu_list = ['cpu%d' % cpu for cpu in xrange(6)]
+    mock_device = mock.Mock(spec=device_utils.DeviceUtils)
+    mock_device.product_model = 'Nexus 5X'
+    mock_device.adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+    mock_device.ListDirectory.return_value = cpu_list + ['cpufreq']
+    mock_device.FileExists.return_value = True
+    mock_device.RunShellCommand = mock.Mock(side_effect=_ShellCommandHandler)
+    pc = perf_control.PerfControl(mock_device)
+    self._MockOutLowLevelPerfControlMethods(pc)
+
+    # Verify.
+    # pylint: disable=protected-access
+    # pylint: disable=no-member
+    pc.SetHighPerfMode()
+    mock_device.EnableRoot.assert_called_once_with()
+    pc._ForceAllCpusOnline.assert_called_once_with(True)
+    pc.SetScalingGovernor.assert_called_once_with('performance')
+    pc._SetScalingMaxFreqForCpus.assert_called_once_with(
+        1248000, ' '.join(cpu_list))
+    pc._SetMaxGpuClock.assert_called_once_with(300000000)
+
+  def testNexus5XDefaultPerfMode(self):
+    # Mock out the device state for PerfControl.
+    cpu_list = ['cpu%d' % cpu for cpu in xrange(6)]
+    mock_device = mock.Mock(spec=device_utils.DeviceUtils)
+    mock_device.product_model = 'Nexus 5X'
+    mock_device.adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+    mock_device.ListDirectory.return_value = cpu_list + ['cpufreq']
+    mock_device.FileExists.return_value = True
+    mock_device.RunShellCommand = mock.Mock(side_effect=_ShellCommandHandler)
+    pc = perf_control.PerfControl(mock_device)
+    self._MockOutLowLevelPerfControlMethods(pc)
+
+    # Verify.
+    # pylint: disable=protected-access
+    # pylint: disable=no-member
+    pc.SetDefaultPerfMode()
+    pc.SetScalingGovernor.assert_called_once_with('ondemand')
+    pc._SetScalingMaxFreqForCpus.assert_any_call(1440000, 'cpu0 cpu1 cpu2 cpu3')
+    pc._SetScalingMaxFreqForCpus.assert_any_call(1824000, 'cpu4 cpu5')
+    pc._SetMaxGpuClock.assert_called_once_with(600000000)
+    pc._ForceAllCpusOnline.assert_called_once_with(False)
diff --git a/catapult/devil/devil/android/perf/surface_stats_collector.py b/catapult/devil/devil/android/perf/surface_stats_collector.py
index eab493d..ea46a39 100644
--- a/catapult/devil/devil/android/perf/surface_stats_collector.py
+++ b/catapult/devil/devil/android/perf/surface_stats_collector.py
@@ -116,6 +116,14 @@
     except StopIteration:
       raise Exception('Unable to get surface flinger process id')
 
+  def _GetSurfaceViewWindowName(self):
+    results = self._device.RunShellCommand(
+        ['dumpsys', 'SurfaceFlinger', '--list'], check_return=True)
+    for window_name in results:
+      if window_name.startswith('SurfaceView'):
+        return window_name
+    return None
+
   def _GetSurfaceFlingerFrameData(self):
     """Returns collected SurfaceFlinger frame timing data.
 
@@ -152,19 +160,21 @@
     # (each time the number above changes, we have a "jank").
     # If this happens a lot during an animation, the animation appears
     # janky, even if it runs at 60 fps in average.
-    #
-    # We use the special "SurfaceView" window name because the statistics for
-    # the activity's main window are not updated when the main web content is
-    # composited into a SurfaceView.
-    results = self._device.RunShellCommand(
-        ['dumpsys', 'SurfaceFlinger', '--latency', 'SurfaceView'],
-        check_return=True)
+    window_name = self._GetSurfaceViewWindowName()
+    command = ['dumpsys', 'SurfaceFlinger', '--latency']
+    # Even if we don't find the window name, run the command to get the refresh
+    # period.
+    if window_name:
+      command.append(window_name)
+    results = self._device.RunShellCommand(command, check_return=True)
     if not len(results):
       return (None, None)
 
     timestamps = []
     nanoseconds_per_millisecond = 1e6
     refresh_period = long(results[0]) / nanoseconds_per_millisecond
+    if not window_name:
+      return (refresh_period, timestamps)
 
     # If a fence associated with a frame is still pending when we query the
     # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX.
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper.py b/catapult/devil/devil/android/sdk/adb_wrapper.py
index 099a0f8..2fbe963 100644
--- a/catapult/devil/devil/android/sdk/adb_wrapper.py
+++ b/catapult/devil/devil/android/sdk/adb_wrapper.py
@@ -19,6 +19,7 @@
 import re
 import subprocess
 
+from devil import base_error
 from devil import devil_env
 from devil.android import decorators
 from devil.android import device_errors
@@ -96,7 +97,11 @@
 
 
 def _ShouldRetryAdbCmd(exc):
-  return not isinstance(exc, device_errors.NoAdbError)
+  # Errors are potentially transient and should be retried, with the exception
+  # of NoAdbError. Exceptions [e.g. generated from SIGTERM handler] should be
+  # raised.
+  return (isinstance(exc, base_error.BaseError) and
+          not isinstance(exc, device_errors.NoAdbError))
 
 
 DeviceStat = collections.namedtuple('DeviceStat',
@@ -257,13 +262,15 @@
   @classmethod
   @decorators.WithTimeoutAndConditionalRetries(_ShouldRetryAdbCmd)
   def _RunAdbCmd(cls, args, timeout=None, retries=None, device_serial=None,
-                 check_error=True, cpu_affinity=None):
-    # pylint: disable=no-member
+                 check_error=True, cpu_affinity=None,
+                 ensure_logs_on_timeout=False):
+    timeout = timeout_retry.CurrentTimeoutThreadGroup().GetRemainingTime()
+    if ensure_logs_on_timeout:
+      timeout = 0.95 * timeout
     try:
       status, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
           cls._BuildAdbCmd(args, device_serial, cpu_affinity=cpu_affinity),
-          timeout_retry.CurrentTimeoutThreadGroup().GetRemainingTime(),
-          env=cls._ADB_ENV)
+          timeout, env=cls._ADB_ENV)
     except OSError as e:
       if e.errno in (errno.ENOENT, errno.ENOEXEC):
         raise device_errors.NoAdbError(msg=str(e))
@@ -287,7 +294,9 @@
     return output
   # pylint: enable=unused-argument
 
-  def _RunDeviceAdbCmd(self, args, timeout, retries, check_error=True):
+  def _RunDeviceAdbCmd(
+      self, args, timeout, retries, check_error=True,
+      ensure_logs_on_timeout=False):
     """Runs an adb command on the device associated with this object.
 
     Args:
@@ -302,7 +311,8 @@
     """
     return self._RunAdbCmd(args, timeout=timeout, retries=retries,
                            device_serial=self._device_serial,
-                           check_error=check_error)
+                           check_error=check_error,
+                           ensure_logs_on_timeout=ensure_logs_on_timeout)
 
   def _IterRunDeviceAdbCmd(self, args, iter_timeout, timeout):
     """Runs an adb command and returns an iterator over its output lines.
@@ -497,14 +507,17 @@
     return cmd_helper.StartCmd(
         self._BuildAdbCmd(['shell'] + cmd, self._device_serial))
 
-  def Shell(self, command, expect_status=0, timeout=DEFAULT_TIMEOUT,
-            retries=DEFAULT_RETRIES):
+  def Shell(self, command, expect_status=0, ensure_logs_on_timeout=False,
+            timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
     """Runs a shell command on the device.
 
     Args:
       command: A string with the shell command to run.
       expect_status: (optional) Check that the command's exit status matches
         this value. Default is 0. If set to None the test is skipped.
+      ensure_logs_on_timeout: If True, will use a timeout that is 5% smaller
+        than the remaining time on the thread watchdog for the internal adb
+        command, which allows to retrive logs on timeout.
       timeout: (optional) Timeout per try in seconds.
       retries: (optional) Number of retries to attempt.
 
@@ -519,7 +532,9 @@
       args = ['shell', command]
     else:
       args = ['shell', '( %s );echo %%$?' % command.rstrip()]
-    output = self._RunDeviceAdbCmd(args, timeout, retries, check_error=False)
+    output = self._RunDeviceAdbCmd(
+        args, timeout, retries, check_error=False,
+        ensure_logs_on_timeout=ensure_logs_on_timeout)
     if expect_status is not None:
       output_end = output.rfind('%')
       if output_end < 0:
diff --git a/catapult/devil/devil/android/sdk/shared_prefs.py b/catapult/devil/devil/android/sdk/shared_prefs.py
index c985cac..c8c82b4 100644
--- a/catapult/devil/devil/android/sdk/shared_prefs.py
+++ b/catapult/devil/devil/android/sdk/shared_prefs.py
@@ -278,12 +278,17 @@
       self._xml = None
       self._changed = True
 
-  def Commit(self):
+  def Commit(self, force_commit=False):
     """Save the current set of preferences to the device.
 
-    Only actually saves if some preferences have been modified.
+    Only actually saves if some preferences have been modified or force_commit
+    is set to True.
+
+    Args:
+      force_commit: Commit even if no changes have been made to the SharedPrefs
+        instance.
     """
-    if not self.changed:
+    if not (self.changed or force_commit):
       return
     self._device.RunShellCommand(
         ['mkdir', '-p', posixpath.dirname(self.path)],
diff --git a/catapult/devil/devil/android/sdk/shared_prefs_test.py b/catapult/devil/devil/android/sdk/shared_prefs_test.py
index 49587c8..08bbb46 100755
--- a/catapult/devil/devil/android/sdk/shared_prefs_test.py
+++ b/catapult/devil/devil/android/sdk/shared_prefs_test.py
@@ -19,6 +19,14 @@
   import mock  # pylint: disable=import-error
 
 
+INITIAL_XML = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+               '<map>\n'
+               '  <int name="databaseVersion" value="107" />\n'
+               '  <boolean name="featureEnabled" value="false" />\n'
+               '  <string name="someHashValue">249b3e5af13d4db2</string>\n'
+               '</map>')
+
+
 def MockDeviceWithFiles(files=None):
   if files is None:
     files = {}
@@ -43,13 +51,7 @@
 
   def setUp(self):
     self.device = MockDeviceWithFiles({
-      '/data/data/com.some.package/shared_prefs/prefs.xml':
-          "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-          '<map>\n'
-          '  <int name="databaseVersion" value="107" />\n'
-          '  <boolean name="featureEnabled" value="false" />\n'
-          '  <string name="someHashValue">249b3e5af13d4db2</string>\n'
-          '</map>'})
+      '/data/data/com.some.package/shared_prefs/prefs.xml': INITIAL_XML})
     self.expected_data = {'databaseVersion': 107,
                           'featureEnabled': False,
                           'someHashValue': '249b3e5af13d4db2'}
@@ -127,6 +129,22 @@
         'bigNumner': 6000000000,
         'apps': ['gmail', 'chrome', 'music']})  # data survived roundtrip
 
+  def testForceCommit(self):
+    prefs = shared_prefs.SharedPrefs(
+        self.device, 'com.some.package', 'prefs.xml')
+    prefs.Load()
+    new_xml = 'Not valid XML'
+    self.device.WriteFile('/data/data/com.some.package/shared_prefs/prefs.xml',
+        new_xml)
+    prefs.Commit()
+    # Since we didn't change anything, Commit() should be a no-op.
+    self.assertEquals(self.device.ReadFile(
+        '/data/data/com.some.package/shared_prefs/prefs.xml'), new_xml)
+    prefs.Commit(force_commit=True)
+    # Forcing the commit should restore the originally read XML.
+    self.assertEquals(self.device.ReadFile(
+        '/data/data/com.some.package/shared_prefs/prefs.xml'), INITIAL_XML)
+
   def testAsContextManager_onlyReads(self):
     with shared_prefs.SharedPrefs(
         self.device, 'com.some.package', 'prefs.xml') as prefs:
diff --git a/catapult/devil/devil/android/sdk/version_codes.py b/catapult/devil/devil/android/sdk/version_codes.py
index 1750f00..29c7285 100644
--- a/catapult/devil/devil/android/sdk/version_codes.py
+++ b/catapult/devil/devil/android/sdk/version_codes.py
@@ -19,3 +19,4 @@
 NOUGAT_MR1 = 25
 OREO = 26
 OREO_MR1 = 27
+PIE = 28
diff --git a/catapult/devil/devil/android/tools/provision_devices.py b/catapult/devil/devil/android/tools/provision_devices.py
index 3a726e5..47b1dc3 100755
--- a/catapult/devil/devil/android/tools/provision_devices.py
+++ b/catapult/devil/devil/android/tools/provision_devices.py
@@ -39,6 +39,7 @@
 from devil.android.sdk import adb_wrapper
 from devil.android.sdk import intent
 from devil.android.sdk import keyevent
+from devil.android.sdk import shared_prefs
 from devil.android.sdk import version_codes
 from devil.android.tools import script_common
 from devil.constants import exit_codes
@@ -51,6 +52,9 @@
 _SYSTEM_WEBVIEW_NAMES = ['webview', 'WebViewGoogle']
 _CHROME_PACKAGE_REGEX = re.compile('.*chrom.*')
 _TOMBSTONE_REGEX = re.compile('tombstone.*')
+_STANDALONE_VR_DEVICES = [
+  'vega', # Lenovo Mirage Solo
+]
 
 
 class _DEFAULT_TIMEOUTS(object):
@@ -134,6 +138,7 @@
 
   steps.append(ProvisionStep(SetDate))
   steps.append(ProvisionStep(CheckExternalStorage))
+  steps.append(ProvisionStep(StandaloneVrDeviceSetup))
 
   parallel_devices.pMap(ProvisionDevice, steps, blacklist, reboot_timeout)
 
@@ -531,6 +536,30 @@
       device.WriteFile(f.name, 'test')
 
 
+def StandaloneVrDeviceSetup(device):
+  """Performs any additional setup necessary for standalone Android VR devices.
+
+  Arguments:
+    device: The device to check.
+  """
+  if device.product_name not in _STANDALONE_VR_DEVICES:
+    return
+
+  # Modify VrCore's settings so that any first time setup, etc. is skipped.
+  shared_pref = shared_prefs.SharedPrefs(device, 'com.google.vr.vrcore',
+      'VrCoreSettings.xml', use_encrypted_path=True)
+  shared_pref.Load()
+  # Skip first time setup.
+  shared_pref.SetBoolean('DaydreamSetupComplete', True)
+  # Disable the automatic prompt that shows anytime the device detects that a
+  # controller isn't connected.
+  shared_pref.SetBoolean('gConfigFlags:controller_recovery_enabled', False)
+  # Use an automated controller instead of a real one so we get past the
+  # controller pairing screen that's shown on startup.
+  shared_pref.SetBoolean('UseAutomatedController', True)
+  shared_pref.Commit()
+
+
 def main(raw_args):
   # Recommended options on perf bots:
   # --disable-network
diff --git a/catapult/devil/devil/android/tools/system_app.py b/catapult/devil/devil/android/tools/system_app.py
index 00ea312..4fe35e5 100755
--- a/catapult/devil/devil/android/tools/system_app.py
+++ b/catapult/devil/devil/android/tools/system_app.py
@@ -138,11 +138,11 @@
 @contextlib.contextmanager
 def _TemporarilyInstallApp(device, apk):
   """A context manager that installs an app while in scope."""
-  device.adb.Install(apk, reinstall=True)
+  device.Install(apk, reinstall=True)
   try:
     yield
   finally:
-    device.adb.Uninstall(apk_helper.GetPackageName(apk))
+    device.Uninstall(apk_helper.GetPackageName(apk))
 
 
 def _MoveApp(device, relocation_map):
diff --git a/catapult/devil/devil/android/tools/system_app_test.py b/catapult/devil/devil/android/tools/system_app_test.py
index f72aa16..1400d7e 100644
--- a/catapult/devil/devil/android/tools/system_app_test.py
+++ b/catapult/devil/devil/android/tools/system_app_test.py
@@ -47,7 +47,7 @@
     mock_device.GetProp.side_effect = dict_getprop
 
     with system_app.EnableSystemAppModification(mock_device):
-      mock_device.EnableRoot.assert_called_once()
+      mock_device.EnableRoot.assert_called_once_with()
       mock_device.GetProp.assert_called_once_with(
           system_app._ENABLE_MODIFICATION_PROP)
       mock_device.SetProp.assert_called_once_with(
@@ -55,10 +55,10 @@
       mock_device.reset_mock()
 
       with system_app.EnableSystemAppModification(mock_device):
-        mock_device.EnableRoot.assert_not_called()
+        self.assertFalse(mock_device.EnableRoot.mock_calls)  # assert not called
         mock_device.GetProp.assert_called_once_with(
             system_app._ENABLE_MODIFICATION_PROP)
-        mock_device.SetProp.assert_not_called()
+        self.assertFalse(mock_device.SetProp.mock_calls)  # assert not called
         mock_device.reset_mock()
 
     mock_device.SetProp.assert_called_once_with(
diff --git a/catapult/devil/devil/android/tools/unlock_bootloader.py b/catapult/devil/devil/android/tools/unlock_bootloader.py
index 46fec9d..b38f669 100644
--- a/catapult/devil/devil/android/tools/unlock_bootloader.py
+++ b/catapult/devil/devil/android/tools/unlock_bootloader.py
@@ -106,8 +106,10 @@
         logging.info('Device %s already unlocked.', d)
       elif 'unlock is not allowed' in out:
         logging.error("Device %s is oem locked. Can't unlock bootloader.", d)
+        return 1
       else:
         logging.error('Device %s in unknown state: "%s"', d, out)
+        return 1
     break
 
   if leftover_pids:
diff --git a/catapult/devil/devil/devil_dependencies.json b/catapult/devil/devil/devil_dependencies.json
index 6884a36..8a7943e 100644
--- a/catapult/devil/devil/devil_dependencies.json
+++ b/catapult/devil/devil/devil_dependencies.json
@@ -58,6 +58,10 @@
         "android_armeabi-v7a": {
           "cloud_storage_hash": "220ff3ba1a6c3c81877997e32784ffd008f293a5",
           "download_path": "../bin/deps/android/armeabi-v7a/apks/EmptySystemWebView.apk"
+        },
+        "android_arm64-v8a": {
+          "cloud_storage_hash": "34e583c631a495afbba82ce8a1d4f9b5118a4411",
+          "download_path": "../bin/deps/android/arm64-v8a/apks/EmptySystemWebView.apk"
         }
       }
     },
@@ -134,4 +138,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/catapult/devil/devil/utils/cmd_helper.py b/catapult/devil/devil/utils/cmd_helper.py
index 5ea85d8..b7b2f0d 100644
--- a/catapult/devil/devil/utils/cmd_helper.py
+++ b/catapult/devil/devil/utils/cmd_helper.py
@@ -152,12 +152,12 @@
   else:
     if shell:
       raise Exception('array args must be run with shell=False')
-    args = ' '.join(SingleQuote(c) for c in args)
+    args = ' '.join(SingleQuote(str(c)) for c in args)
   if cwd is None:
     cwd = ''
   else:
     cwd = ':' + cwd
-  logger.info('[host]%s> %s', cwd, args)
+  logger.debug('[host]%s> %s', cwd, args)
   return args
 
 
diff --git a/catapult/devil/devil/utils/lazy/weak_constant_test.py b/catapult/devil/devil/utils/lazy/weak_constant_test.py
index 9519150..643351d 100644
--- a/catapult/devil/devil/utils/lazy/weak_constant_test.py
+++ b/catapult/devil/devil/utils/lazy/weak_constant_test.py
@@ -38,7 +38,7 @@
     self.assertEquals(
         'initializer called',
         test_constant.read())
-    initializer.assert_called_once()
+    initializer.assert_called_once_with()
 
   def testInitialized(self):
     """Ensure that reading doesn't reinitialize the value."""
@@ -49,7 +49,7 @@
     self.assertEquals(
         'initializer not called',
         test_constant.read())
-    initializer.assert_not_called()
+    self.assertFalse(initializer.mock_calls)  # assert not called
 
   def testFirstCallHangs(self):
     """Ensure that reading works even if the first initializer call hangs."""
diff --git a/catapult/systrace/systrace/systrace_trace_viewer.html b/catapult/systrace/systrace/systrace_trace_viewer.html
index 29f8d59..bdb0cd9 100644
--- a/catapult/systrace/systrace/systrace_trace_viewer.html
+++ b/catapult/systrace/systrace/systrace_trace_viewer.html
@@ -2297,7 +2297,7 @@
 </style><style>
 .thread-track{flex-direction:column;display:flex;position:relative}
 </style><style>
-.process-track-header{flex:0 0 auto;background-image:-webkit-gradient(linear,0 0,100% 0,from(#E5E5E5),to(#D1D1D1));border-bottom:1px solid #8e8e8e;border-top:1px solid white;font-size:75%}.process-track-name:before{content:'\25B8';padding:0 5px}.process-track-base.expanded .process-track-name:before{content:'\25BE'}
+.process-track-header{display:flex;flex:0 0 auto;background-image:-webkit-gradient(linear,0 0,100% 0,from(#E5E5E5),to(#D1D1D1));border-bottom:1px solid #8e8e8e;border-top:1px solid white;font-size:75%}.process-track-name{flex-grow:1}.process-track-name:before{content:'\25B8';padding:0 5px}.process-track-base.expanded .process-track-name:before{content:'\25BE'}.process-track-close{color:black;border:1px solid transparent;padding:0px 2px}.process-track-close:hover{border:1px solid grey}
 </style><style>
 .model-track {
   flex-grow: 1;
@@ -2841,7 +2841,7 @@
     tr-ui-b-drag-handle { flex: 0 0 auto; }
     tr-ui-a-analysis-view { flex: 0 0 auto; }
 
-    #view_options_dropdown {
+    #view_options_dropdown, #process_filter_dropdown {
       --dropdown-button: {
         -webkit-appearance: none;
         align-items: normal;
@@ -2862,6 +2862,7 @@
         <div id="left_controls"></div>
         <div id="title">^_^</div>
         <div id="right_controls">
+          <tr-ui-b-dropdown id="process_filter_dropdown" label="Processes"></tr-ui-b-dropdown>
           <tr-ui-b-toolbar-button id="view_metadata_button">
             M
           </tr-ui-b-toolbar-button>
@@ -3088,6 +3089,10 @@
       margin-right: 20px;
     }
 
+    #show_visualization {
+      margin-right: 20px;
+    }
+
     #export {
       margin-right: 20px;
     }
@@ -3133,6 +3138,8 @@
       <select id="statistic" value="{{displayStatisticName::change}}">
       </select>
 
+      <button id="show_visualization" on-tap="loadVisualization_">Visualize</button>
+
       <tr-ui-b-dropdown label="Export">
         <tr-v-ui-histogram-set-controls-export>
         </tr-v-ui-histogram-set-controls-export>
@@ -3358,6 +3365,90 @@
 
     <tr-ui-b-table id="table">
   </tr-ui-b-table></template>
+</dom-module><dom-module id="tr-v-ui-metrics-visualization">
+  <template>
+    <style>
+      button {
+        padding: 5px;
+        font-size: 14px;
+      }
+
+      .text_input {
+        width: 50px;
+        padding: 4px;
+        font-size: 14px;
+      }
+      .error {
+        color: red;
+        display: none;
+      }
+
+      .child_container {
+        position: relative;
+        display: inline-block;
+      }
+
+      #title {
+        font-size: 20px;
+        font-weight: bold;
+        padding-bottom: 5px;
+      }
+
+      #selectors {
+        display: none;
+        padding-bottom: 10px;
+        display: none;
+      }
+
+      #search_page {
+        width: 200px;
+        margin-left: 30px;
+      }
+
+      #close {
+        display: none;
+        vertical-align: top;
+      }
+
+      #close svg{
+        height: 1em;
+      }
+
+      #close svg line {
+        stroke-width: 18;
+        stroke: black;
+      }
+
+      #close:hover svg {
+        background: black;
+      }
+
+      #close:hover svg line {
+        stroke: white;
+      }
+    </style>
+    <div id="title">Metrics Visualization</div>
+    <div class="error" id="data_error">Invalid data provided.</div>
+    <div id="selectors">
+      <div id="percentile_label">Percentile Range:</div>
+      <input class="text_input" id="start" placeholder="0"/>
+      <input class="text_input" id="end" placeholder="100"/>
+      <button id="filter" on-tap="filterByPercentile_">Filter</button>
+      <input class="text_input" id="search_page" placeholder="Page Name"/>
+      <button id="search" on-tap="searchByPage_">Search</button>
+      <div class="error" id="search_error">Sorry, could not find that page!</div>
+    </div>
+    <div id="container">
+    </div>
+    <div id="children">
+    </div>
+    <span id="close">
+      <svg viewBox="0 0 128 128">
+        <line x1="28" x2="100" y1="28" y2="100"></line>
+        <line x1="28" x2="100" y1="100" y2="28"></line>
+      </svg>
+    </span>
+  </template>
 </dom-module><dom-module id="tr-v-ui-histogram-set-view">
   <template>
     <style>
@@ -3386,6 +3477,9 @@
       <tr-v-ui-histogram-set-controls id="controls">
       </tr-v-ui-histogram-set-controls>
 
+      <tr-v-ui-metrics-visualization id="metrics">
+      </tr-v-ui-metrics-visualization>
+
       <tr-v-ui-histogram-set-table id="table"></tr-v-ui-histogram-set-table>
     </div>
   </template>
@@ -4229,7 +4323,7 @@
 let maxDiff=0;let minDiff=0;for(let i=1;i<locations.length;i++){const length=locations[i]-locations[i-1];const countClosed=countLessEqual[i]-countLess[i-1];const countOpen=countLess[i]-countLessEqual[i-1];const countClosedIncrement=countLessEqual[i]-countLessEqual[i-1];const countOpenIncrement=countLess[i]-countLess[i-1];maxDiff=Math.max(countClosedIncrement*invSampleCount-length+maxDiff,countClosed*invSampleCount-length);minDiff=Math.min(countOpenIncrement*invSampleCount-length+minDiff,countOpen*invSampleCount-length);maxLocalDiscrepancy=Math.max(maxDiff,-minDiff,maxLocalDiscrepancy);}
 return maxLocalDiscrepancy;};Statistics.timestampsDiscrepancy=function(timestamps,opt_absolute,opt_locationCount){if(timestamps.length===0)return 0.0;if(opt_absolute===undefined)opt_absolute=true;if(Array.isArray(timestamps[0])){const rangeDiscrepancies=timestamps.map(function(r){return Statistics.timestampsDiscrepancy(r);});return Math.max.apply(null,rangeDiscrepancies);}
 const s=Statistics.normalizeSamples(timestamps);const samples=s.normalized_samples;const sampleScale=s.scale;let discrepancy=Statistics.discrepancy(samples,opt_locationCount);const invSampleCount=1.0/samples.length;if(opt_absolute===true){discrepancy/=sampleScale;}else{discrepancy=tr.b.math.clamp((discrepancy-invSampleCount)/(1.0-invSampleCount),0.0,1.0);}
-return discrepancy;};Statistics.durationsDiscrepancy=function(durations,opt_absolute,opt_locationCount){if(durations.length===0)return 0.0;const timestamps=durations.reduce(function(prev,curr,index,array){prev.push(prev[prev.length-1]+curr);return prev;},[0]);return Statistics.timestampsDiscrepancy(timestamps,opt_absolute,opt_locationCount);};Statistics.uniformlySampleArray=function(samples,count){if(samples.length<=count){return samples;}
+return discrepancy;};Statistics.uniformlySampleArray=function(samples,count){if(samples.length<=count){return samples;}
 while(samples.length>count){const i=parseInt(Math.random()*samples.length);samples.splice(i,1);}
 return samples;};Statistics.uniformlySampleStream=function(samples,streamLength,newElement,numSamples){if(streamLength<=numSamples){if(samples.length>=streamLength){samples[streamLength-1]=newElement;}else{samples.push(newElement);}
 return;}
@@ -4250,19 +4344,24 @@
 compare(opt_alpha){const alpha=opt_alpha||Statistics.DEFAULT_ALPHA;if(this.p<alpha){this.significance_=Statistics.Significance.SIGNIFICANT;}else if(this.needMoreData_){this.significance_=Statistics.Significance.NEED_MORE_DATA;}else{this.significance_=Statistics.Significance.INSIGNIFICANT;}
 return this.significance_;}
 asDict(){return{p:this.p,U:this.U,significance:this.significance,};}}
-Statistics.mwu=function(a,b,opt_alpha,opt_reqSampleSize){const result=mannwhitneyu.test(a,b);const needMoreData=opt_reqSampleSize&&Math.min(a.length,b.length)<opt_reqSampleSize;return new HypothesisTestResult(result.p,result.U,needMoreData,opt_alpha);};return{Statistics,};});'use strict';const GREEK_SMALL_LETTER_MU=String.fromCharCode(956);tr.exportTo('tr.b',function(){const SECONDS_IN_A_MINUTE=60;const SECONDS_IN_AN_HOUR=SECONDS_IN_A_MINUTE*60;const SECONDS_IN_A_DAY=SECONDS_IN_AN_HOUR*24;const SECONDS_IN_A_WEEK=SECONDS_IN_A_DAY*7;const SECONDS_IN_A_YEAR=SECONDS_IN_A_DAY*365.2422;const SECONDS_IN_A_MONTH=SECONDS_IN_A_YEAR/12;const UnitPrefixScale={};const UnitScale={};function defineUnitPrefixScale(name,prefixes){if(UnitPrefixScale[name]!==undefined){throw new Error('Unit prefix scale \''+name+'\' already exists');}
+Statistics.mwu=function(a,b,opt_alpha,opt_reqSampleSize){const result=mannwhitneyu.test(a,b);const needMoreData=opt_reqSampleSize&&Math.min(a.length,b.length)<opt_reqSampleSize;return new HypothesisTestResult(result.p,result.U,needMoreData,opt_alpha);};return{Statistics,};});'use strict';tr.exportTo('tr.b',function(){const GREEK_SMALL_LETTER_MU=String.fromCharCode(956);const SECONDS_IN_A_MINUTE=60;const SECONDS_IN_AN_HOUR=SECONDS_IN_A_MINUTE*60;const SECONDS_IN_A_DAY=SECONDS_IN_AN_HOUR*24;const SECONDS_IN_A_WEEK=SECONDS_IN_A_DAY*7;const SECONDS_IN_A_YEAR=SECONDS_IN_A_DAY*365.2422;const SECONDS_IN_A_MONTH=SECONDS_IN_A_YEAR/12;const UnitPrefixScale={};const UnitScale={};function defineUnitPrefixScale(name,prefixes){if(UnitPrefixScale[name]!==undefined){throw new Error('Unit prefix scale \''+name+'\' already exists');}
 if(prefixes.AUTO!==undefined){throw new Error('The \'AUTO\' unit prefix is not supported for unit'+'prefix scales and cannot be added to scale \''+name+'\'');}
 UnitPrefixScale[name]=prefixes;}
 UnitScale.defineUnitScale=function(name,unitScale){if(UnitScale[name]!==undefined){throw new Error('Unit scale \''+name+'\' already exists');}
 if(unitScale.AUTO!==undefined){throw new Error('\'AUTO\' unit scale will be added automatically '+'for unit scale \''+name+'\'');}
-unitScale.AUTO=Object.values(unitScale);unitScale.AUTO.sort((a,b)=>a.value-b.value);if(name)UnitScale[name]=unitScale;return unitScale;};UnitScale.defineUnitScaleFromPrefixScale=function(baseSymbol,baseName,prefixScale,opt_scaleName){if(baseSymbol===undefined){throw new Error('Cannot create UnitScale with undefined baseSymbol.');}
+unitScale.AUTO=Object.values(unitScale);unitScale.AUTO.sort((a,b)=>a.value-b.value);if(name)UnitScale[name]=unitScale;return unitScale;};function definePrefixScaleFromUnitScale(prefixName,unitScale){if(!unitScale){throw new Error('Cannot create PrefixScale without a unit scale.');}
+const prefixScale={};for(const[curPrefix,curScale]of Object.entries(unitScale)){if(curPrefix==='AUTO'){continue;}
+if(curScale.symbol===undefined||!curScale.value){throw new Error(`Cannot create PrefixScale from malformed unit ${curScale}.`);}
+prefixScale[curPrefix]={value:curScale.value,symbol:curScale.symbol};}
+return defineUnitPrefixScale(prefixName,prefixScale);}
+UnitScale.defineUnitScaleFromPrefixScale=function(baseSymbol,baseName,prefixScale,opt_scaleName){if(baseSymbol===undefined){throw new Error('Cannot create UnitScale with undefined baseSymbol.');}
 if(!baseName){throw new Error('Cannot create UnitScale without a baseName.');}
 if(!prefixScale){throw new Error('Cannot create UnitScale without a prefix scale.');}
 const unitScale={};for(const curPrefix of Object.keys(prefixScale)){const curScale=prefixScale[curPrefix];if(curScale.symbol===undefined||!curScale.value){throw new Error(`Cannot convert PrefixScale with malformed prefix ${curScale}.`);}
 const name=curPrefix==='NONE'?baseName:`${curPrefix}_${baseName}`;unitScale[name]={value:curScale.value,symbol:curScale.symbol+baseSymbol,baseSymbol};}
 return UnitScale.defineUnitScale(opt_scaleName,unitScale);};function convertUnit(value,fromScale,toScale){if(value===undefined)return undefined;const fromScaleBase=fromScale.baseSymbol;const toScaleBase=toScale.baseSymbol;if(fromScaleBase!==undefined&&toScaleBase!==undefined&&fromScaleBase!==toScaleBase){throw new Error('Cannot convert between units with different base symbols.');}
 return value*(fromScale.value/toScale.value);}
-defineUnitPrefixScale('BINARY',{NONE:{value:Math.pow(1024,0),symbol:''},KIBI:{value:Math.pow(1024,1),symbol:'Ki'},MEBI:{value:Math.pow(1024,2),symbol:'Mi'},GIBI:{value:Math.pow(1024,3),symbol:'Gi'},TEBI:{value:Math.pow(1024,4),symbol:'Ti'}});defineUnitPrefixScale('METRIC',{NANO:{value:1e-9,symbol:'n'},MICRO:{value:1e-6,symbol:GREEK_SMALL_LETTER_MU},MILLI:{value:1e-3,symbol:'m'},NONE:{value:1,symbol:''},KILO:{value:1e3,symbol:'k'},MEGA:{value:1e6,symbol:'M'},GIGA:{value:1e9,symbol:'G'}});UnitScale.defineUnitScale('TIME',{NANO_SEC:{value:1e-9,symbol:'ns',baseSymbol:'s'},MICRO_SEC:{value:1e-6,symbol:GREEK_SMALL_LETTER_MU+'s',baseSymbol:'s'},MILLI_SEC:{value:1e-3,symbol:'ms',baseSymbol:'s'},SEC:{value:1,symbol:'s',baseSymbol:'s'},MINUTE:{value:SECONDS_IN_A_MINUTE,symbol:'min',baseSymbol:'s'},HOUR:{value:SECONDS_IN_AN_HOUR,symbol:'hr',baseSymbol:'s'},DAY:{value:SECONDS_IN_A_DAY,symbol:'days',baseSymbol:'s'},WEEK:{value:SECONDS_IN_A_WEEK,symbol:'weeks',baseSymbol:'s'},MONTH:{value:SECONDS_IN_A_MONTH,symbol:'months',baseSymbol:'s'},YEAR:{value:SECONDS_IN_A_YEAR,symbol:'years',baseSymbol:'s'}});UnitScale.defineUnitScaleFromPrefixScale('B','BYTE',UnitPrefixScale.BINARY,'MEMORY');return{UnitPrefixScale,UnitScale,convertUnit,};});'use strict';tr.exportTo('tr.b',function(){const msDisplayMode={scale:1e-3,suffix:'ms',roundedLess(a,b){return Math.round(a*1000)<Math.round(b*1000);},formatSpec:{unitScale:[tr.b.UnitScale.TIME.MILLI_SEC],minimumFractionDigits:3,}};const nsDisplayMode={scale:1e-9,suffix:'ns',roundedLess(a,b){return Math.round(a*1000000)<Math.round(b*1000000);},formatSpec:{unitScale:[tr.b.UnitScale.TIME.NANO_SEC],maximumFractionDigits:0}};const TimeDisplayModes={ns:nsDisplayMode,ms:msDisplayMode};return{TimeDisplayModes,};});'use strict';tr.exportTo('tr.ui.b',function(){function iterateElementDeeplyImpl(element,cb,thisArg,includeElement){if(includeElement&&cb.call(thisArg,element))return true;if(element.root&&element.root!==element&&iterateElementDeeplyImpl(element.root,cb,thisArg,false)){return true;}
+defineUnitPrefixScale('BINARY',{NONE:{value:Math.pow(1024,0),symbol:''},KIBI:{value:Math.pow(1024,1),symbol:'Ki'},MEBI:{value:Math.pow(1024,2),symbol:'Mi'},GIBI:{value:Math.pow(1024,3),symbol:'Gi'},TEBI:{value:Math.pow(1024,4),symbol:'Ti'}});defineUnitPrefixScale('METRIC',{NANO:{value:1e-9,symbol:'n'},MICRO:{value:1e-6,symbol:GREEK_SMALL_LETTER_MU},MILLI:{value:1e-3,symbol:'m'},NONE:{value:1,symbol:''},KILO:{value:1e3,symbol:'k'},MEGA:{value:1e6,symbol:'M'},GIGA:{value:1e9,symbol:'G'}});UnitScale.defineUnitScale('TIME',{NANO_SEC:{value:1e-9,symbol:'ns',baseSymbol:'s'},MICRO_SEC:{value:1e-6,symbol:GREEK_SMALL_LETTER_MU+'s',baseSymbol:'s'},MILLI_SEC:{value:1e-3,symbol:'ms',baseSymbol:'s'},SEC:{value:1,symbol:'s',baseSymbol:'s'},MINUTE:{value:SECONDS_IN_A_MINUTE,symbol:'min',baseSymbol:'s'},HOUR:{value:SECONDS_IN_AN_HOUR,symbol:'hr',baseSymbol:'s'},DAY:{value:SECONDS_IN_A_DAY,symbol:'days',baseSymbol:'s'},WEEK:{value:SECONDS_IN_A_WEEK,symbol:'weeks',baseSymbol:'s'},MONTH:{value:SECONDS_IN_A_MONTH,symbol:'months',baseSymbol:'s'},YEAR:{value:SECONDS_IN_A_YEAR,symbol:'years',baseSymbol:'s'}});UnitScale.defineUnitScaleFromPrefixScale('B','BYTE',UnitPrefixScale.BINARY,'MEMORY');definePrefixScaleFromUnitScale('DATA_SIZE',UnitScale.MEMORY);UnitScale.defineUnitScaleFromPrefixScale('/s','SECONDS',UnitPrefixScale.DATA_SIZE,'BANDWIDTH_BYTES');return{UnitPrefixScale,UnitScale,convertUnit,GREEK_SMALL_LETTER_MU,};});'use strict';tr.exportTo('tr.b',function(){const msDisplayMode={scale:1e-3,suffix:'ms',roundedLess(a,b){return Math.round(a*1000)<Math.round(b*1000);},formatSpec:{unitScale:[tr.b.UnitScale.TIME.MILLI_SEC],minimumFractionDigits:3,}};const nsDisplayMode={scale:1e-9,suffix:'ns',roundedLess(a,b){return Math.round(a*1000000)<Math.round(b*1000000);},formatSpec:{unitScale:[tr.b.UnitScale.TIME.NANO_SEC],maximumFractionDigits:0}};const TimeDisplayModes={ns:nsDisplayMode,ms:msDisplayMode};return{TimeDisplayModes,};});'use strict';tr.exportTo('tr.ui.b',function(){function iterateElementDeeplyImpl(element,cb,thisArg,includeElement){if(includeElement&&cb.call(thisArg,element))return true;if(element.root&&element.root!==element&&iterateElementDeeplyImpl(element.root,cb,thisArg,false)){return true;}
 const children=Polymer.dom(element).children;for(let i=0;i<children.length;i++){if(iterateElementDeeplyImpl(children[i],cb,thisArg,true)){return true;}}
 return false;}
 function iterateElementDeeply(element,cb,thisArg){iterateElementDeeplyImpl(element,cb,thisArg,false);}
@@ -4285,14 +4384,15 @@
 if(!(scale instanceof Array)){throw new Error('Unit has a malformed unit scale.');}
 return scale;},get unitString(){const scale=this.getUnitScale_();if(!scale){throw new Error('A UnitScale could not be found for Unit '+this.unitName);}
 return scale[0].symbol;},format(value,opt_context){let signString='';if(value<0){signString='-';value=-value;}else if(this.isDelta){signString=value===0?PLUS_MINUS_SIGN:'+';}
-const context=opt_context||{};const scale=this.getUnitScale_(context);let deltaValue=context.deltaValue===undefined?value:context.deltaValue;deltaValue=Math.abs(deltaValue)*this.scaleBaseUnit.value;let i=0;while(i<scale.length-1&&deltaValue/scale[i+1].value>=1){i++;}
+const context=opt_context||{};const scale=this.getUnitScale_(context);let deltaValue=context.deltaValue===undefined?value:context.deltaValue;deltaValue=Math.abs(deltaValue)*this.scaleBaseUnit.value;if(deltaValue===0){deltaValue=1;}
+let i=0;while(i<scale.length-1&&deltaValue/scale[i+1].value>=1){i++;}
 const selectedSubUnit=scale[i];let formatSpec=this.formatSpec_;if(typeof formatSpec==='function')formatSpec=formatSpec();let unitString='';if(selectedSubUnit.symbol){if(!formatSpec.avoidSpacePrecedingUnit)unitString=' ';unitString+=selectedSubUnit.symbol;}
 value=tr.b.convertUnit(value,this.scaleBaseUnit,selectedSubUnit);const numberString=getNumberFormatter(formatSpec.minimumFractionDigits,formatSpec.maximumFractionDigits,context.minimumFractionDigits,context.maximumFractionDigits).format(value);return signString+numberString+unitString;}};Unit.reset=function(){Unit.currentTimeDisplayMode=TimeDisplayModes.ms;};Unit.timestampFromUs=function(us){return tr.b.convertUnit(us,tr.b.UnitPrefixScale.METRIC.MICRO,tr.b.UnitPrefixScale.METRIC.MILLI);};Object.defineProperty(Unit,'currentTimeDisplayMode',{get(){return Unit.currentTimeDisplayMode_;},set(value){if(Unit.currentTimeDisplayMode_===value)return;Unit.currentTimeDisplayMode_=value;Unit.dispatchEvent(new tr.b.Event('display-mode-changed'));}});Unit.didPreferredTimeDisplayUnitChange=function(){let largest=undefined;const els=tr.ui.b.findDeepElementsMatching(document.body,'tr-v-ui-preferred-display-unit');els.forEach(function(el){largest=max(largest,el.preferredTimeDisplayMode);});Unit.currentTimeDisplayMode=largest===undefined?TimeDisplayModes.ms:largest;};Unit.byName={};Unit.byJSONName={};Unit.fromJSON=function(object){const u=Unit.byJSONName[object];if(u){return u;}
 throw new Error(`Unrecognized unit "${object}"`);};Unit.define=function(params){const definedUnits=[];for(const improvementDirection of Object.values(ImprovementDirection)){const regularUnit=Unit.defineUnitVariant_(params,false,improvementDirection);const deltaUnit=Unit.defineUnitVariant_(params,true,improvementDirection);regularUnit.correspondingDeltaUnit=deltaUnit;deltaUnit.correspondingDeltaUnit=deltaUnit;definedUnits.push(regularUnit,deltaUnit);}
 const baseUnit=Unit.byName[params.baseUnitName];definedUnits.forEach(u=>u.baseUnit=baseUnit);};Unit.nameSuffixForImprovementDirection=function(improvementDirection){switch(improvementDirection){case ImprovementDirection.DONT_CARE:return'';case ImprovementDirection.BIGGER_IS_BETTER:return'_biggerIsBetter';case ImprovementDirection.SMALLER_IS_BETTER:return'_smallerIsBetter';default:throw new Error('Unknown improvement direction: '+improvementDirection);}};Unit.defineUnitVariant_=function(params,isDelta,improvementDirection){let nameSuffix=isDelta?'Delta':'';nameSuffix+=Unit.nameSuffixForImprovementDirection(improvementDirection);const unitName=params.baseUnitName+nameSuffix;const jsonName=params.baseJsonName+nameSuffix;if(Unit.byName[unitName]!==undefined){throw new Error('Unit \''+unitName+'\' already exists');}
 if(Unit.byJSONName[jsonName]!==undefined){throw new Error('JSON unit \''+jsonName+'\' alread exists');}
 let scaleBaseUnit=params.scaleBaseUnit;if(!scaleBaseUnit){let formatSpec=params.formatSpec;if(typeof formatSpec==='function')formatSpec=formatSpec();const baseSymbol=formatSpec.unitScale?formatSpec.unitScale[0].baseSymbol:(formatSpec.baseSymbol||'');scaleBaseUnit={value:1,symbol:baseSymbol,baseSymbol};}
-const unit=new Unit(unitName,jsonName,scaleBaseUnit,isDelta,improvementDirection,params.formatSpec);Unit.byName[unitName]=unit;Unit.byJSONName[jsonName]=unit;return unit;};tr.b.EventTarget.decorate(Unit);Unit.reset();Unit.define({baseUnitName:'timeInMsAutoFormat',baseJsonName:'msBestFitFormat',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec:{unitScale:tr.b.UnitScale.TIME.AUTO,minimumFractionDigits:0,maximumFractionDigits:3}});Unit.define({baseUnitName:'timeDurationInMs',baseJsonName:'ms',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'timeStampInMs',baseJsonName:'tsMs',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'normalizedPercentage',baseJsonName:'n%',formatSpec:{unitScale:[{value:0.01,symbol:'%'}],avoidSpacePrecedingUnit:true,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'sizeInBytes',baseJsonName:'sizeInBytes',formatSpec:{unitScale:tr.b.UnitScale.MEMORY.AUTO,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'energyInJoules',baseJsonName:'J',formatSpec:{baseSymbol:'J',minimumFractionDigits:3}});Unit.define({baseUnitName:'powerInWatts',baseJsonName:'W',formatSpec:{baseSymbol:'W',minimumFractionDigits:3}});Unit.define({baseUnitName:'unitlessNumber',baseJsonName:'unitless',formatSpec:{minimumFractionDigits:3,maximumFractionDigits:3}});Unit.define({baseUnitName:'count',baseJsonName:'count',formatSpec:{minimumFractionDigits:0,maximumFractionDigits:0}});Unit.define({baseUnitName:'sigma',baseJsonName:'sigma',formatSpec:{baseSymbol:String.fromCharCode(963),minimumFractionDigits:1,maximumFractionDigits:1}});return{ImprovementDirection,Unit,};});'use strict';tr.exportTo('tr.b',function(){class Scalar{constructor(unit,value){if(!(unit instanceof tr.b.Unit)){throw new Error('Expected Unit');}
+const unit=new Unit(unitName,jsonName,scaleBaseUnit,isDelta,improvementDirection,params.formatSpec);Unit.byName[unitName]=unit;Unit.byJSONName[jsonName]=unit;return unit;};tr.b.EventTarget.decorate(Unit);Unit.reset();Unit.define({baseUnitName:'timeInMsAutoFormat',baseJsonName:'msBestFitFormat',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec:{unitScale:tr.b.UnitScale.TIME.AUTO,minimumFractionDigits:0,maximumFractionDigits:3}});Unit.define({baseUnitName:'timeDurationInMs',baseJsonName:'ms',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'timeStampInMs',baseJsonName:'tsMs',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'normalizedPercentage',baseJsonName:'n%',formatSpec:{unitScale:[{value:0.01,symbol:'%'}],avoidSpacePrecedingUnit:true,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'sizeInBytes',baseJsonName:'sizeInBytes',formatSpec:{unitScale:tr.b.UnitScale.MEMORY.AUTO,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'bandwidthInBytesPerSecond',baseJsonName:'bytesPerSecond',formatSpec:{unitScale:tr.b.UnitScale.BANDWIDTH_BYTES.AUTO,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'energyInJoules',baseJsonName:'J',formatSpec:{unitScale:tr.b.UnitScale.defineUnitScaleFromPrefixScale('J','JOULE',tr.b.UnitPrefixScale.METRIC,'JOULE').AUTO,minimumFractionDigits:3}});Unit.define({baseUnitName:'powerInWatts',baseJsonName:'W',formatSpec:{unitScale:tr.b.UnitScale.defineUnitScaleFromPrefixScale('W','WATT',tr.b.UnitPrefixScale.METRIC,'WATT').AUTO,minimumFractionDigits:3}});Unit.define({baseUnitName:'electricCurrentInAmperes',baseJsonName:'A',formatSpec:{baseSymbol:'A',unitScale:tr.b.UnitScale.defineUnitScaleFromPrefixScale('A','AMPERE',tr.b.UnitPrefixScale.METRIC,'AMPERE').AUTO,minimumFractionDigits:3}});Unit.define({baseUnitName:'electricPotentialInVolts',baseJsonName:'V',formatSpec:{baseSymbol:'V',unitScale:tr.b.UnitScale.defineUnitScaleFromPrefixScale('V','VOLT',tr.b.UnitPrefixScale.METRIC,'VOLT').AUTO,minimumFractionDigits:3}});Unit.define({baseUnitName:'frequencyInHertz',baseJsonName:'Hz',formatSpec:{baseSymbol:'Hz',unitScale:tr.b.UnitScale.defineUnitScaleFromPrefixScale('Hz','HERTZ',tr.b.UnitPrefixScale.METRIC,'HERTZ').AUTO,minimumFractionDigits:3}});Unit.define({baseUnitName:'unitlessNumber',baseJsonName:'unitless',formatSpec:{minimumFractionDigits:3,maximumFractionDigits:3}});Unit.define({baseUnitName:'count',baseJsonName:'count',formatSpec:{minimumFractionDigits:0,maximumFractionDigits:0}});Unit.define({baseUnitName:'sigma',baseJsonName:'sigma',formatSpec:{baseSymbol:String.fromCharCode(963),minimumFractionDigits:1,maximumFractionDigits:1}});return{ImprovementDirection,Unit,};});'use strict';tr.exportTo('tr.b',function(){class Scalar{constructor(unit,value){if(!(unit instanceof tr.b.Unit)){throw new Error('Expected Unit');}
 if(!(typeof(value)==='number')){throw new Error('Expected value to be number');}
 this.unit=unit;this.value=value;}
 asDict(){return{unit:this.unit.asJSON(),value:tr.b.numberToJson(this.value),};}
@@ -4528,17 +4628,30 @@
 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.indexOf('CompositorTileWorker')===0)return true;if(t.name.indexOf('CompositorRasterWorker')===0)return true;return false;});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 isChromeTracingUI(){return ChromeRendererHelper.isTracingProcess(this.process);},};return{ChromeRendererHelper,};});'use strict';tr.exportTo('tr.model.helpers',function(){function findChromeBrowserProcesses(model){return model.getAllProcesses(tr.model.helpers.ChromeBrowserHelper.isBrowserProcess);}
+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 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);}}
+return{Segment,};});'use strict';tr.exportTo('tr.model.helpers',function(){const GESTURE_EVENT='SyntheticGestureController::running';const IR_REG_EXP=/Interaction\.([^/]+)(\/[^/]*)?$/;const ChromeRendererHelper=tr.model.helpers.ChromeRendererHelper;class TelemetryHelper{constructor(modelHelper){this.modelHelper=modelHelper;this.renderersWithIR_=undefined;this.segments_=undefined;this.uiSegments_=undefined;}
+get renderersWithIR(){this.findIRs_();return this.renderersWithIR_;}
+get segments(){this.findIRs_();return this.segments_;}
+get uiSegments(){this.findIRs_();return this.uiSegments_;}
+findIRs_(){if(this.segments_!==undefined)return;this.renderersWithIR_=[];const gestureEvents=[];const interactionRecords=[];const processes=Object.values(this.modelHelper.rendererHelpers).concat(this.modelHelper.browserHelpers).map(processHelper=>processHelper.process);for(const process of processes){let foundIR=false;for(const thread of Object.values(process.threads)){for(const slice of thread.asyncSliceGroup.slices){if(slice.title===GESTURE_EVENT){gestureEvents.push(slice);}else if(IR_REG_EXP.test(slice.title)){interactionRecords.push(slice);foundIR=true;}}}
+if(foundIR&&ChromeRendererHelper.isRenderProcess(process)&&!ChromeRendererHelper.isTracingProcess(process)){this.renderersWithIR_.push(new ChromeRendererHelper(this.modelHelper,process));}}
+this.segments_=[];this.uiSegments_=[];for(const ir of interactionRecords){const parts=IR_REG_EXP.exec(ir.title);let gestureEventFound=false;if(parts[1].startsWith('Gesture_')){for(const gestureEvent of gestureEvents){if(ir.boundsRange.intersectsRangeInclusive(gestureEvent.boundsRange)){this.segments_.push(new tr.model.um.Segment(gestureEvent.start,gestureEvent.duration));gestureEventFound=true;break;}}}else if(parts[1].startsWith('ui_')){this.uiSegments_.push(new tr.model.um.Segment(ir.start,ir.duration));}
+if(!gestureEventFound){this.segments_.push(new tr.model.um.Segment(ir.start,ir.duration));}}
+this.segments_.sort((x,y)=>x.start-y.start);this.uiSegments_.sort((x,y)=>x.start-y.start);}}
+return{TelemetryHelper,};});'use strict';tr.exportTo('tr.model.helpers',function(){function findChromeBrowserProcesses(model){return model.getAllProcesses(tr.model.helpers.ChromeBrowserHelper.isBrowserProcess);}
 function findChromeRenderProcesses(model){return model.getAllProcesses(tr.model.helpers.ChromeRendererHelper.isRenderProcess);}
 function findChromeGpuProcess(model){const gpuProcesses=model.getAllProcesses(tr.model.helpers.ChromeGpuHelper.isGpuProcess);if(gpuProcesses.length!==1)return undefined;return gpuProcesses[0];}
+function findTelemetrySurfaceFlingerProcess(model){const surfaceFlingerProcesses=model.getAllProcesses(process=>(process.name==='SurfaceFlinger'));if(surfaceFlingerProcesses.length!==1)return undefined;return surfaceFlingerProcesses[0];}
 function ChromeModelHelper(model){this.model_=model;const browserProcesses=findChromeBrowserProcesses(model);this.browserHelpers_=browserProcesses.map(p=>new tr.model.helpers.ChromeBrowserHelper(this,p));const gpuProcess=findChromeGpuProcess(model);if(gpuProcess){this.gpuHelper_=new tr.model.helpers.ChromeGpuHelper(this,gpuProcess);}else{this.gpuHelper_=undefined;}
-const rendererProcesses_=findChromeRenderProcesses(model);this.rendererHelpers_={};rendererProcesses_.forEach(function(renderProcess){const rendererHelper=new tr.model.helpers.ChromeRendererHelper(this,renderProcess);this.rendererHelpers_[rendererHelper.pid]=rendererHelper;},this);this.chromeBounds_=undefined;}
-ChromeModelHelper.guid=tr.b.GUID.allocateSimple();ChromeModelHelper.supportsModel=function(model){if(findChromeBrowserProcesses(model).length)return true;if(findChromeRenderProcesses(model).length)return true;return false;};ChromeModelHelper.prototype={get pid(){throw new Error('woah');},get process(){throw new Error('woah');},get model(){return this.model_;},get browserProcess(){if(this.browserHelper===undefined)return undefined;return this.browserHelper.process;},get browserHelper(){return this.browserHelpers_[0];},get browserHelpers(){return this.browserHelpers_;},get gpuHelper(){return this.gpuHelper_;},get rendererHelpers(){return this.rendererHelpers_;},get rendererWithLargestPid(){let largestPid=-1;for(const pid in this.rendererHelpers){const rendererHelper=this.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;if(pid>largestPid)largestPid=pid;}
-if(largestPid===-1)return undefined;return this.rendererHelpers[largestPid];},get chromeBounds(){if(!this.chromeBounds_){this.chromeBounds_=new tr.b.math.Range();for(const browserHelper of Object.values(this.browserHelpers)){this.chromeBounds_.addRange(browserHelper.process.bounds);}
+const rendererProcesses_=findChromeRenderProcesses(model);this.rendererHelpers_={};rendererProcesses_.forEach(function(renderProcess){const rendererHelper=new tr.model.helpers.ChromeRendererHelper(this,renderProcess);this.rendererHelpers_[rendererHelper.pid]=rendererHelper;},this);this.surfaceFlingerProcess_=findTelemetrySurfaceFlingerProcess(model);this.chromeBounds_=undefined;this.telemetryHelper_=new tr.model.helpers.TelemetryHelper(this);}
+ChromeModelHelper.guid=tr.b.GUID.allocateSimple();ChromeModelHelper.supportsModel=function(model){if(findChromeBrowserProcesses(model).length)return true;if(findChromeRenderProcesses(model).length)return true;return false;};ChromeModelHelper.prototype={get pid(){throw new Error('woah');},get process(){throw new Error('woah');},get model(){return this.model_;},get browserProcess(){if(this.browserHelper===undefined)return undefined;return this.browserHelper.process;},get browserHelper(){return this.browserHelpers_[0];},get browserHelpers(){return this.browserHelpers_;},get gpuHelper(){return this.gpuHelper_;},get rendererHelpers(){return this.rendererHelpers_;},get surfaceFlingerProcess(){return this.surfaceFlingerProcess_;},get chromeBounds(){if(!this.chromeBounds_){this.chromeBounds_=new tr.b.math.Range();for(const browserHelper of Object.values(this.browserHelpers)){this.chromeBounds_.addRange(browserHelper.process.bounds);}
 for(const rendererHelper of Object.values(this.rendererHelpers)){this.chromeBounds_.addRange(rendererHelper.process.bounds);}
 if(this.gpuHelper){this.chromeBounds_.addRange(this.gpuHelper.process.bounds);}}
 if(this.chromeBounds_.isEmpty){return undefined;}
-return this.chromeBounds_;}};return{ChromeModelHelper,};});'use strict';tr.exportTo('tr.e.cc',function(){const AsyncSlice=tr.model.AsyncSlice;const EventSet=tr.model.EventSet;const UI_COMP_NAME='INPUT_EVENT_LATENCY_UI_COMPONENT';const ORIGINAL_COMP_NAME='INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT';const BEGIN_COMP_NAME='INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT';const END_COMP_NAME='INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT';const MAIN_RENDERER_THREAD_NAME='CrRendererMain';const COMPOSITOR_THREAD_NAME='Compositor';const POSTTASK_FLOW_EVENT='disabled-by-default-toplevel.flow';const IPC_FLOW_EVENT='disabled-by-default-ipc.flow';const INPUT_EVENT_TYPE_NAMES={CHAR:'Char',CLICK:'GestureClick',CONTEXT_MENU:'ContextMenu',FLING_CANCEL:'GestureFlingCancel',FLING_START:'GestureFlingStart',KEY_DOWN:'KeyDown',KEY_DOWN_RAW:'RawKeyDown',KEY_UP:'KeyUp',LATENCY_SCROLL_UPDATE:'ScrollUpdate',MOUSE_DOWN:'MouseDown',MOUSE_ENTER:'MouseEnter',MOUSE_LEAVE:'MouseLeave',MOUSE_MOVE:'MouseMove',MOUSE_UP:'MouseUp',MOUSE_WHEEL:'MouseWheel',PINCH_BEGIN:'GesturePinchBegin',PINCH_END:'GesturePinchEnd',PINCH_UPDATE:'GesturePinchUpdate',SCROLL_BEGIN:'GestureScrollBegin',SCROLL_END:'GestureScrollEnd',SCROLL_UPDATE:'GestureScrollUpdate',SCROLL_UPDATE_RENDERER:'ScrollUpdate',SHOW_PRESS:'GestureShowPress',TAP:'GestureTap',TAP_CANCEL:'GestureTapCancel',TAP_DOWN:'GestureTapDown',TOUCH_CANCEL:'TouchCancel',TOUCH_END:'TouchEnd',TOUCH_MOVE:'TouchMove',TOUCH_START:'TouchStart',UNKNOWN:'UNKNOWN'};function InputLatencyAsyncSlice(){AsyncSlice.apply(this,arguments);this.associatedEvents_=new EventSet();this.typeName_=undefined;if(!this.isLegacyEvent){this.determineModernTypeName_();}}
+return this.chromeBounds_;},get telemetryHelper(){return this.telemetryHelper_;}};return{ChromeModelHelper,};});'use strict';tr.exportTo('tr.e.cc',function(){const AsyncSlice=tr.model.AsyncSlice;const EventSet=tr.model.EventSet;const UI_COMP_NAME='INPUT_EVENT_LATENCY_UI_COMPONENT';const ORIGINAL_COMP_NAME='INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT';const BEGIN_COMP_NAME='INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT';const END_COMP_NAME='INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT';const LEGACY_END_COMP_NAME='INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT';const MAIN_RENDERER_THREAD_NAME='CrRendererMain';const COMPOSITOR_THREAD_NAME='Compositor';const POSTTASK_FLOW_EVENT='disabled-by-default-toplevel.flow';const IPC_FLOW_EVENT='disabled-by-default-ipc.flow';const INPUT_EVENT_TYPE_NAMES={CHAR:'Char',CLICK:'GestureClick',CONTEXT_MENU:'ContextMenu',FLING_CANCEL:'GestureFlingCancel',FLING_START:'GestureFlingStart',KEY_DOWN:'KeyDown',KEY_DOWN_RAW:'RawKeyDown',KEY_UP:'KeyUp',LATENCY_SCROLL_UPDATE:'ScrollUpdate',MOUSE_DOWN:'MouseDown',MOUSE_ENTER:'MouseEnter',MOUSE_LEAVE:'MouseLeave',MOUSE_MOVE:'MouseMove',MOUSE_UP:'MouseUp',MOUSE_WHEEL:'MouseWheel',PINCH_BEGIN:'GesturePinchBegin',PINCH_END:'GesturePinchEnd',PINCH_UPDATE:'GesturePinchUpdate',SCROLL_BEGIN:'GestureScrollBegin',SCROLL_END:'GestureScrollEnd',SCROLL_UPDATE:'GestureScrollUpdate',SCROLL_UPDATE_RENDERER:'ScrollUpdate',SHOW_PRESS:'GestureShowPress',TAP:'GestureTap',TAP_CANCEL:'GestureTapCancel',TAP_DOWN:'GestureTapDown',TOUCH_CANCEL:'TouchCancel',TOUCH_END:'TouchEnd',TOUCH_MOVE:'TouchMove',TOUCH_START:'TouchStart',UNKNOWN:'UNKNOWN'};function InputLatencyAsyncSlice(){AsyncSlice.apply(this,arguments);this.associatedEvents_=new EventSet();this.typeName_=undefined;if(!this.isLegacyEvent){this.determineModernTypeName_();}}
 InputLatencyAsyncSlice.prototype={__proto__:AsyncSlice.prototype,get isLegacyEvent(){return this.title==='InputLatency';},get typeName(){if(!this.typeName_){this.determineLegacyTypeName_();}
 return this.typeName_;},checkTypeName_(){if(!this.typeName_){throw new Error('Unable to determine typeName');}
 let found=false;for(const typeName in INPUT_EVENT_TYPE_NAMES){if(this.typeName===INPUT_EVENT_TYPE_NAMES[typeName]){found=true;break;}}
@@ -4567,8 +4680,8 @@
 const COMPOSITOR_ON_BIFD='Scheduler::OnBeginImplFrameDeadline';beginImplFrame=event.findDescendentSlice(COMPOSITOR_ON_BIFD);if(beginImplFrame){this.backtraceFromDraw(beginImplFrame,visitedEvents);}}
 const INPUT_GSU='InputLatency::GestureScrollUpdate';if(this.title===INPUT_GSU){this.addScrollUpdateEvents(rendererHelper);}},get associatedEvents(){if(this.associatedEvents_.length!==0){return this.associatedEvents_;}
 const modelIndices=this.startThread.parent.model.modelIndices;const flowEvents=modelIndices.getFlowEventsWithId(this.id);if(flowEvents.length===0){return this.associatedEvents_;}
-const sourceSlices=this.addDirectlyAssociatedEvents(flowEvents);const rendererHelper=this.getRendererHelper(sourceSlices);this.addOtherCausallyRelatedEvents(rendererHelper,sourceSlices,flowEvents);return this.associatedEvents_;},get inputLatency(){if(!('data'in this.args))return undefined;const data=this.args.data;if(!(END_COMP_NAME in data))return undefined;let latency=0;const endTime=data[END_COMP_NAME].time;if(ORIGINAL_COMP_NAME in data){latency=endTime-data[ORIGINAL_COMP_NAME].time;}else if(UI_COMP_NAME in data){latency=endTime-data[UI_COMP_NAME].time;}else if(BEGIN_COMP_NAME in data){latency=endTime-data[BEGIN_COMP_NAME].time;}else{throw new Error('No valid begin latency component');}
-return latency;}};const eventTypeNames=['Char','ContextMenu','GestureClick','GestureFlingCancel','GestureFlingStart','GestureScrollBegin','GestureScrollEnd','GestureScrollUpdate','GestureShowPress','GestureTap','GestureTapCancel','GestureTapDown','GesturePinchBegin','GesturePinchEnd','GesturePinchUpdate','KeyDown','KeyUp','MouseDown','MouseEnter','MouseLeave','MouseMove','MouseUp','MouseWheel','RawKeyDown','ScrollUpdate','TouchCancel','TouchEnd','TouchMove','TouchStart'];const allTypeNames=['InputLatency'];eventTypeNames.forEach(function(eventTypeName){allTypeNames.push('InputLatency:'+eventTypeName);allTypeNames.push('InputLatency::'+eventTypeName);});AsyncSlice.subTypes.register(InputLatencyAsyncSlice,{typeNames:allTypeNames,categoryParts:['latencyInfo']});return{InputLatencyAsyncSlice,INPUT_EVENT_TYPE_NAMES,};});'use strict';tr.exportTo('tr.e.chrome',function(){const SAME_AS_PARENT='same-as-parent';const TITLES_FOR_USER_FRIENDLY_CATEGORY={composite:['CompositingInputsUpdater::update','ThreadProxy::SetNeedsUpdateLayers','LayerTreeHost::UpdateLayers::CalcDrawProps','UpdateLayerTree',],gc:['minorGC','majorGC','MajorGC','MinorGC','V8.GCScavenger','V8.GCIncrementalMarking','V8.GCIdleNotification','V8.GCContext','V8.GCCompactor','V8GCController::traceDOMWrappers',],iframe_creation:['WebLocalFrameImpl::createChildframe',],imageDecode:['Decode Image','ImageFrameGenerator::decode','ImageFrameGenerator::decodeAndScale','ImageResourceContent::updateImage',],input:['HitTest','ScrollableArea::scrollPositionChanged','EventHandler::handleMouseMoveEvent',],layout:['DisplayItemList::Finalize','IntersectionObserverController::computeTrackedIntersectionObservations','LocalFrameView::invalidateTree','LocalFrameView::layout','LocalFrameView::performLayout','LocalFrameView::performPostLayoutTasks','LocalFrameView::performPreLayoutTasks','FrameView::invalidateTree','FrameView::layout','FrameView::performLayout','FrameView::performPostLayoutTasks','FrameView::performPreLayoutTasks','Layer::updateLayerPositionsAfterLayout','LayerTreeHostInProcess::UpdateLayers::BuildPropertyTrees','Layout','LayoutView::hitTest','PaintLayer::updateLayerPositionsAfterLayout','ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities','WebViewImpl::layout',],parseHTML:['BackgroundHTMLParser::pumpTokenizer','BackgroundHTMLParser::sendTokensToMainThread','HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser','HTMLDocumentParser::documentElementAvailable','HTMLDocumentParser::notifyPendingTokenizedChunks','HTMLDocumentParser::processParsedChunkFromBackgroundParser','HTMLDocumentParser::processTokenizedChunkFromBackgroundParser','ParseHTML',],raster:['DisplayListRasterSource::PerformSolidColorAnalysis','Picture::Raster','RasterBufferImpl::Playback','RasterTask','RasterizerTaskImpl::RunOnWorkerThread','SkCanvas::drawImageRect()','SkCanvas::drawPicture()','SkCanvas::drawTextBlob()','TileTaskWorkerPool::PlaybackToMemory',],record:['Canvas2DLayerBridge::flushRecordingOnly','CompositingRequirementsUpdater::updateRecursive','ContentLayerDelegate::paintContents','DeprecatedPaintLayerCompositor::updateIfNeededRecursive','DeprecatedPaintLayerCompositor::updateLayerPositionsAfterLayout','LocalFrameView::paintTree','LocalFrameView::prePaint','Paint','PaintController::commitNewDisplayItems','PaintLayerCompositor::updateIfNeededRecursive','Picture::Record','PictureLayer::Update','RenderLayer::updateLayerPositionsAfterLayout',],style:['CSSParserImpl::parseStyleSheet.parse','CSSParserImpl::parseStyleSheet.tokenize','Document::rebuildLayoutTree','Document::recalcStyle','Document::updateActiveStyle','Document::updateStyle','Document::updateStyleInvalidationIfNeeded','LocalFrameView::updateStyleAndLayoutIfNeededRecursive','ParseAuthorStyleSheet','RuleSet::addRulesFromSheet','StyleElement::processStyleSheet','StyleEngine::createResolver','StyleEngine::updateActiveStyleSheets','StyleSheetContents::parseAuthorStyleSheet','UpdateLayoutTree',],script_parse_and_compile:['V8.CompileFullCode','V8.NewContext','V8.Parse','V8.ParseLazy','V8.RecompileSynchronous','V8.ScriptCompiler','v8.compile','v8.parseOnBackground',],script_execute:['EvaluateScript','FunctionCall','HTMLParserScriptRunner ExecuteScript','V8.Execute','V8.RunMicrotasks','V8.Task','WindowProxy::initialize','v8.callFunction','v8.run',],resource_loading:['RenderFrameImpl::didFinishDocumentLoad','RenderFrameImpl::didFinishLoad','Resource::appendData','ResourceDispatcher::OnReceivedData','ResourceDispatcher::OnReceivedResponse','ResourceDispatcher::OnRequestComplete','ResourceFetcher::requestResource','WebURLLoaderImpl::Context::Cancel','WebURLLoaderImpl::Context::OnCompletedRequest','WebURLLoaderImpl::Context::OnReceivedData','WebURLLoaderImpl::Context::OnReceivedRedirect','WebURLLoaderImpl::Context::OnReceivedResponse','WebURLLoaderImpl::Context::Start','WebURLLoaderImpl::loadAsynchronously','WebURLLoaderImpl::loadSynchronously','content::mojom::URLLoaderClient',],renderer_misc:['DecodeFont','ThreadState::completeSweep',],v8_runtime:[],[SAME_AS_PARENT]:['SyncChannel::Send',]};const COLOR_FOR_USER_FRIENDLY_CATEGORY=new tr.b.SinebowColorGenerator();const USER_FRIENDLY_CATEGORY_FOR_TITLE=new Map();for(const category in TITLES_FOR_USER_FRIENDLY_CATEGORY){TITLES_FOR_USER_FRIENDLY_CATEGORY[category].forEach(function(title){USER_FRIENDLY_CATEGORY_FOR_TITLE.set(title,category);});}
+const sourceSlices=this.addDirectlyAssociatedEvents(flowEvents);const rendererHelper=this.getRendererHelper(sourceSlices);this.addOtherCausallyRelatedEvents(rendererHelper,sourceSlices,flowEvents);return this.associatedEvents_;},get inputLatency(){if(!('data'in this.args))return undefined;const data=this.args.data;const endTimeComp=data[END_COMP_NAME]||data[LEGACY_END_COMP_NAME];if(endTimeComp===undefined)return undefined;let latency=0;const endTime=endTimeComp.time;if(ORIGINAL_COMP_NAME in data){latency=endTime-data[ORIGINAL_COMP_NAME].time;}else if(UI_COMP_NAME in data){latency=endTime-data[UI_COMP_NAME].time;}else if(BEGIN_COMP_NAME in data){latency=endTime-data[BEGIN_COMP_NAME].time;}else{throw new Error('No valid begin latency component');}
+return latency;}};const eventTypeNames=['Char','ContextMenu','GestureClick','GestureFlingCancel','GestureFlingStart','GestureScrollBegin','GestureScrollEnd','GestureScrollUpdate','GestureShowPress','GestureTap','GestureTapCancel','GestureTapDown','GesturePinchBegin','GesturePinchEnd','GesturePinchUpdate','KeyDown','KeyUp','MouseDown','MouseEnter','MouseLeave','MouseMove','MouseUp','MouseWheel','RawKeyDown','ScrollUpdate','TouchCancel','TouchEnd','TouchMove','TouchStart'];const allTypeNames=['InputLatency'];eventTypeNames.forEach(function(eventTypeName){allTypeNames.push('InputLatency:'+eventTypeName);allTypeNames.push('InputLatency::'+eventTypeName);});AsyncSlice.subTypes.register(InputLatencyAsyncSlice,{typeNames:allTypeNames,categoryParts:['latencyInfo']});return{InputLatencyAsyncSlice,INPUT_EVENT_TYPE_NAMES,};});'use strict';tr.exportTo('tr.e.chrome',function(){const SAME_AS_PARENT='same-as-parent';const TITLES_FOR_USER_FRIENDLY_CATEGORY={composite:['CompositingInputsUpdater::update','ThreadProxy::SetNeedsUpdateLayers','LayerTreeHost::DoUpdateLayers','LayerTreeHost::UpdateLayers::BuildPropertyTrees','LocalFrameView::pushPaintArtifactToCompositor','LocalFrameView::updateCompositedSelectionIfNeeded','LocalFrameView::RunCompositingLifecyclePhase','UpdateLayerTree',],gc:['minorGC','majorGC','MajorGC','MinorGC','V8.GCScavenger','V8.GCIncrementalMarking','V8.GCIdleNotification','V8.GCContext','V8.GCCompactor','V8GCController::traceDOMWrappers',],iframe_creation:['WebLocalFrameImpl::createChildframe',],imageDecode:['Decode Image','ImageFrameGenerator::decode','ImageFrameGenerator::decodeAndScale','ImageResourceContent::updateImage',],input:['HitTest','ScrollableArea::scrollPositionChanged','EventHandler::handleMouseMoveEvent',],layout:['IntersectionObserverController::computeTrackedIntersectionObservations','LocalFrameView::invalidateTree','LocalFrameView::layout','LocalFrameView::performLayout','LocalFrameView::performPostLayoutTasks','LocalFrameView::performPreLayoutTasks','LocalFrameView::RunStyleAndLayoutCompositingPhases','Layout','PaintLayer::updateLayerPositionsAfterLayout','ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities','WebViewImpl::updateAllLifecyclePhases','WebViewImpl::beginFrame',],parseHTML:['BackgroundHTMLParser::pumpTokenizer','BackgroundHTMLParser::sendTokensToMainThread','HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser','HTMLDocumentParser::documentElementAvailable','HTMLDocumentParser::notifyPendingTokenizedChunks','HTMLDocumentParser::processParsedChunkFromBackgroundParser','HTMLDocumentParser::processTokenizedChunkFromBackgroundParser','ParseHTML',],raster:['DisplayListRasterSource::PerformSolidColorAnalysis','Picture::Raster','RasterBufferImpl::Playback','RasterTask','RasterizerTaskImpl::RunOnWorkerThread','SkCanvas::drawImageRect()','SkCanvas::drawPicture()','SkCanvas::drawTextBlob()','TileTaskWorkerPool::PlaybackToMemory',],record:['Canvas2DLayerBridge::flushRecordingOnly','CompositingInputsUpdater::update','CompositingRequirementsUpdater::updateRecursive','ContentLayerDelegate::paintContents','DisplayItemList::Finalize','LocalFrameView::RunPaintLifecyclePhase','LocalFrameView::RunPrePaintLifecyclePhase','Paint','PaintController::commitNewDisplayItems','PaintLayerCompositor::updateIfNeededRecursive','Picture::Record','PictureLayer::Update',],style:['CSSParserImpl::parseStyleSheet.parse','CSSParserImpl::parseStyleSheet.tokenize','Document::rebuildLayoutTree','Document::recalcStyle','Document::updateActiveStyle','Document::updateStyle','Document::updateStyleInvalidationIfNeeded','LocalFrameView::updateStyleAndLayoutIfNeededRecursive','ParseAuthorStyleSheet','RuleSet::addRulesFromSheet','StyleElement::processStyleSheet','StyleEngine::createResolver','StyleEngine::updateActiveStyleSheets','StyleSheetContents::parseAuthorStyleSheet','UpdateLayoutTree',],script_parse_and_compile:['V8.CompileFullCode','V8.NewContext','V8.Parse','V8.ParseLazy','V8.RecompileSynchronous','V8.ScriptCompiler','v8.compile','v8.parseOnBackground',],script_execute:['EvaluateScript','FunctionCall','HTMLParserScriptRunner ExecuteScript','V8.Execute','V8.RunMicrotasks','V8.Task','WindowProxy::initialize','v8.callFunction','v8.run',],resource_loading:['RenderFrameImpl::didFinishDocumentLoad','RenderFrameImpl::didFinishLoad','Resource::appendData','ResourceDispatcher::OnReceivedData','ResourceDispatcher::OnReceivedResponse','ResourceDispatcher::OnRequestComplete','ResourceFetcher::requestResource','WebURLLoaderImpl::Context::Cancel','WebURLLoaderImpl::Context::OnCompletedRequest','WebURLLoaderImpl::Context::OnReceivedData','WebURLLoaderImpl::Context::OnReceivedRedirect','WebURLLoaderImpl::Context::OnReceivedResponse','WebURLLoaderImpl::Context::Start','WebURLLoaderImpl::loadAsynchronously','WebURLLoaderImpl::loadSynchronously','content::mojom::URLLoaderClient',],renderer_misc:['DecodeFont','ThreadState::completeSweep',],v8_runtime:[],[SAME_AS_PARENT]:['SyncChannel::Send',]};const COLOR_FOR_USER_FRIENDLY_CATEGORY=new tr.b.SinebowColorGenerator();const USER_FRIENDLY_CATEGORY_FOR_TITLE=new Map();for(const category in TITLES_FOR_USER_FRIENDLY_CATEGORY){TITLES_FOR_USER_FRIENDLY_CATEGORY[category].forEach(function(title){USER_FRIENDLY_CATEGORY_FOR_TITLE.set(title,category);});}
 const USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY={netlog:'net',overhead:'overhead',startup:'startup',gpu:'gpu',};function ChromeUserFriendlyCategoryDriver(){}
 ChromeUserFriendlyCategoryDriver.fromEvent=function(event){let userFriendlyCategory=USER_FRIENDLY_CATEGORY_FOR_TITLE.get(event.title);if(userFriendlyCategory){if(userFriendlyCategory===SAME_AS_PARENT){if(event.parentSlice){return ChromeUserFriendlyCategoryDriver.fromEvent(event.parentSlice);}}else{return userFriendlyCategory;}}
 const eventCategoryParts=tr.b.getCategoryParts(event.category);for(let i=0;i<eventCategoryParts.length;++i){const eventCategory=eventCategoryParts[i];userFriendlyCategory=USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY[eventCategory];if(userFriendlyCategory){return userFriendlyCategory;}}
@@ -4586,7 +4699,8 @@
 return names;},getProperty(name){return this.otherProperties_[name];},get previousSnapshotLayoutObject(){if(!this.snapshot.previousSnapshot)return undefined;return this.snapshot.previousSnapshot.getLayoutObjectById(this.id);},get nextSnapshotLayoutObject(){if(!this.snapshot.nextSnapshot)return undefined;return this.snapshot.nextSnapshot.getLayoutObjectById(this.id);}};return{LayoutObject,};});'use strict';tr.exportTo('tr.e.chrome',function(){const ObjectSnapshot=tr.model.ObjectSnapshot;const ObjectInstance=tr.model.ObjectInstance;function LayoutTreeInstance(){ObjectInstance.apply(this,arguments);}
 LayoutTreeInstance.prototype={__proto__:ObjectInstance.prototype,};ObjectInstance.subTypes.register(LayoutTreeInstance,{typeName:'LayoutTree'});function LayoutTreeSnapshot(){ObjectSnapshot.apply(this,arguments);this.rootLayoutObject=new tr.e.chrome.LayoutObject(this,this.args);}
 LayoutTreeSnapshot.prototype={__proto__:ObjectSnapshot.prototype,};ObjectSnapshot.subTypes.register(LayoutTreeSnapshot,{typeName:'LayoutTree'});return{LayoutTreeInstance,LayoutTreeSnapshot,};});'use strict';tr.exportTo('tr.model',function(){function EventContainer(){this.guid_=tr.b.GUID.allocateSimple();this.important=true;this.bounds_=new tr.b.math.Range();}
-EventContainer.prototype={get guid(){return this.guid_;},get stableId(){throw new Error('Not implemented');},get bounds(){return this.bounds_;},updateBounds(){throw new Error('Not implemented');},shiftTimestampsForward(amount){throw new Error('Not implemented');},*childEvents(){},*getDescendantEvents(){yield*this.childEvents();for(const container of this.childEventContainers()){yield*container.getDescendantEvents();}},*childEventContainers(){},*getDescendantEventContainers(){yield this;for(const container of this.childEventContainers()){yield*container.getDescendantEventContainers();}},*findTopmostSlicesInThisContainer(eventPredicate,opt_this){},*findTopmostSlices(eventPredicate){for(const ec of this.getDescendantEventContainers()){yield*ec.findTopmostSlicesInThisContainer(eventPredicate);}},*findTopmostSlicesNamed(name){yield*this.findTopmostSlices(e=>e.title===name);}};return{EventContainer,};});'use strict';tr.exportTo('tr.model',function(){const Event=tr.model.Event;const EventRegistry=tr.model.EventRegistry;class ResourceUsageSample extends Event{constructor(series,start,usage){super();this.series_=series;this.start_=start;this.usage_=usage;}
+EventContainer.prototype={get guid(){return this.guid_;},get stableId(){throw new Error('Not implemented');},get bounds(){return this.bounds_;},updateBounds(){throw new Error('Not implemented');},shiftTimestampsForward(amount){throw new Error('Not implemented');},*childEvents(){},*getDescendantEvents(){yield*this.childEvents();for(const container of this.childEventContainers()){yield*container.getDescendantEvents();}},*childEventContainers(){},*getDescendantEventContainers(){yield this;for(const container of this.childEventContainers()){yield*container.getDescendantEventContainers();}},*getDescendantEventsInSortedRanges(ranges,opt_containerPredicate){if(opt_containerPredicate===undefined||opt_containerPredicate(this)){for(const event of this.childEvents()){const i=tr.b.findFirstTrueIndexInSortedArray(ranges,range=>event.start<=range.max);if(i<ranges.length&&event.end>=ranges[i].min)yield event;}}
+for(const container of this.childEventContainers()){yield*container.getDescendantEventsInSortedRanges(ranges,opt_containerPredicate);}},*findTopmostSlicesInThisContainer(eventPredicate,opt_this){},*findTopmostSlices(eventPredicate){for(const ec of this.getDescendantEventContainers()){yield*ec.findTopmostSlicesInThisContainer(eventPredicate);}},*findTopmostSlicesNamed(name){yield*this.findTopmostSlices(e=>e.title===name);}};return{EventContainer,};});'use strict';tr.exportTo('tr.model',function(){const Event=tr.model.Event;const EventRegistry=tr.model.EventRegistry;class ResourceUsageSample extends Event{constructor(series,start,usage){super();this.series_=series;this.start_=start;this.usage_=usage;}
 get series(){return this.series_;}
 get start(){return this.start_;}
 set start(value){this.start_=value;}
@@ -4638,10 +4752,12 @@
 return{pageDirectoryBase,uniqueProcessKey,processId,parentId,sessionId,exitStatus,directoryTableBase,flags,userSID,imageFileName,commandLine,packageFullName,applicationId,exitTime};},decodeStart(header,decoder){const fields=this.decodeFields(header,decoder);const process=this.model.getOrCreateProcess(fields.processId);if(process.hasOwnProperty('has_ended')){throw new Error('Process clash detected.');}
 process.name=fields.imageFileName;return true;},decodeEnd(header,decoder){const fields=this.decodeFields(header,decoder);const process=this.model.getOrCreateProcess(fields.processId);process.has_ended=true;return true;},decodeDCStart(header,decoder){const fields=this.decodeFields(header,decoder);const process=this.model.getOrCreateProcess(fields.processId);if(process.hasOwnProperty('has_ended')){throw new Error('Process clash detected.');}
 process.name=fields.imageFileName;return true;},decodeDCEnd(header,decoder){const fields=this.decodeFields(header,decoder);const process=this.model.getOrCreateProcess(fields.processId);process.has_ended=true;return true;},decodeDefunct(header,decoder){const fields=this.decodeFields(header,decoder);return true;}};Parser.register(ProcessParser);return{ProcessParser,};});'use strict';tr.exportTo('tr.e.importer.etw',function(){const Parser=tr.e.importer.etw.Parser;const guid='3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C';const kThreadStartOpcode=1;const kThreadEndOpcode=2;const kThreadDCStartOpcode=3;const kThreadDCEndOpcode=4;const kThreadCSwitchOpcode=36;function ThreadParser(importer){Parser.call(this,importer);importer.registerEventHandler(guid,kThreadStartOpcode,ThreadParser.prototype.decodeStart.bind(this));importer.registerEventHandler(guid,kThreadEndOpcode,ThreadParser.prototype.decodeEnd.bind(this));importer.registerEventHandler(guid,kThreadDCStartOpcode,ThreadParser.prototype.decodeDCStart.bind(this));importer.registerEventHandler(guid,kThreadDCEndOpcode,ThreadParser.prototype.decodeDCEnd.bind(this));importer.registerEventHandler(guid,kThreadCSwitchOpcode,ThreadParser.prototype.decodeCSwitch.bind(this));}
-ThreadParser.prototype={__proto__:Parser.prototype,decodeFields(header,decoder){if(header.version>3){throw new Error('Incompatible Thread event version.');}
+ThreadParser.prototype={__proto__:Parser.prototype,decodeFields(header,decoder){if(header.version>3){throw new Error('Incompatible Thread event version '+
+header.version+'.');}
 const processId=decoder.decodeUInt32();const threadId=decoder.decodeUInt32();let stackBase;let stackLimit;let userStackBase;let userStackLimit;let affinity;let startAddr;let win32StartAddr;let tebBase;let subProcessTag;let basePriority;let pagePriority;let ioPriority;let threadFlags;let waitMode;if(header.version===1){if(header.opcode===kThreadStartOpcode||header.opcode===kThreadDCStartOpcode){stackBase=decoder.decodeUInteger(header.is64);stackLimit=decoder.decodeUInteger(header.is64);userStackBase=decoder.decodeUInteger(header.is64);userStackLimit=decoder.decodeUInteger(header.is64);startAddr=decoder.decodeUInteger(header.is64);win32StartAddr=decoder.decodeUInteger(header.is64);waitMode=decoder.decodeInt8();decoder.skip(3);}}else{stackBase=decoder.decodeUInteger(header.is64);stackLimit=decoder.decodeUInteger(header.is64);userStackBase=decoder.decodeUInteger(header.is64);userStackLimit=decoder.decodeUInteger(header.is64);if(header.version===2){startAddr=decoder.decodeUInteger(header.is64);}else{affinity=decoder.decodeUInteger(header.is64);}
 win32StartAddr=decoder.decodeUInteger(header.is64);tebBase=decoder.decodeUInteger(header.is64);subProcessTag=decoder.decodeUInt32();if(header.version===3){basePriority=decoder.decodeUInt8();pagePriority=decoder.decodeUInt8();ioPriority=decoder.decodeUInt8();threadFlags=decoder.decodeUInt8();}}
-return{processId,threadId,stackBase,stackLimit,userStackBase,userStackLimit,affinity,startAddr,win32StartAddr,tebBase,subProcessTag,waitMode,basePriority,pagePriority,ioPriority,threadFlags};},decodeCSwitchFields(header,decoder){if(header.version!==2){throw new Error('Incompatible Thread event version.');}
+return{processId,threadId,stackBase,stackLimit,userStackBase,userStackLimit,affinity,startAddr,win32StartAddr,tebBase,subProcessTag,waitMode,basePriority,pagePriority,ioPriority,threadFlags};},decodeCSwitchFields(header,decoder){if(header.version<2||header.version>4){throw new Error('Incompatible cswitch event version '+
+header.version+'.');}
 const newThreadId=decoder.decodeUInt32();const oldThreadId=decoder.decodeUInt32();const newThreadPriority=decoder.decodeInt8();const oldThreadPriority=decoder.decodeInt8();const previousCState=decoder.decodeUInt8();const spareByte=decoder.decodeInt8();const oldThreadWaitReason=decoder.decodeInt8();const oldThreadWaitMode=decoder.decodeInt8();const oldThreadState=decoder.decodeInt8();const oldThreadWaitIdealProcessor=decoder.decodeInt8();const newThreadWaitTime=decoder.decodeUInt32();const reserved=decoder.decodeUInt32();return{newThreadId,oldThreadId,newThreadPriority,oldThreadPriority,previousCState,spareByte,oldThreadWaitReason,oldThreadWaitMode,oldThreadState,oldThreadWaitIdealProcessor,newThreadWaitTime,reserved};},decodeStart(header,decoder){const fields=this.decodeFields(header,decoder);this.importer.createThreadIfNeeded(fields.processId,fields.threadId);return true;},decodeEnd(header,decoder){const fields=this.decodeFields(header,decoder);this.importer.removeThreadIfPresent(fields.threadId);return true;},decodeDCStart(header,decoder){const fields=this.decodeFields(header,decoder);this.importer.createThreadIfNeeded(fields.processId,fields.threadId);return true;},decodeDCEnd(header,decoder){const fields=this.decodeFields(header,decoder);this.importer.removeThreadIfPresent(fields.threadId);return true;},decodeCSwitch(header,decoder){const fields=this.decodeCSwitchFields(header,decoder);const cpu=this.importer.getOrCreateCpu(header.cpu);const newThread=this.importer.getThreadFromWindowsTid(fields.newThreadId);let newThreadName;if(newThread&&newThread.userFriendlyName){newThreadName=newThread.userFriendlyName;}else{const newProcessId=this.importer.getPidFromWindowsTid(fields.newThreadId);const newProcess=this.model.getProcess(newProcessId);let newProcessName;if(newProcess){newProcessName=newProcess.name;}else{newProcessName='Unknown process';}
 newThreadName=newProcessName+' (tid '+fields.newThreadId+')';}
 cpu.switchActiveThread(header.timestamp,{},fields.newThreadId,newThreadName,fields);return true;}};Parser.register(ThreadParser);return{ThreadParser,};});'use strict';tr.exportTo('tr.b',function(){function max(a,b){if(a===undefined)return b;if(b===undefined)return a;return Math.max(a,b);}
@@ -4822,13 +4938,14 @@
 function getSliceHi(s){return s.end;}
 function SliceGroup(parentContainer,opt_sliceConstructor,opt_name){tr.model.EventContainer.call(this);this.parentContainer_=parentContainer;const sliceConstructor=opt_sliceConstructor||ThreadSlice;this.sliceConstructor=sliceConstructor;this.sliceConstructorSubTypes=this.sliceConstructor.subTypes;if(!this.sliceConstructorSubTypes){throw new Error('opt_sliceConstructor must have a subtype registry.');}
 this.openPartialSlices_=[];this.slices=[];this.topLevelSlices=[];this.haveTopLevelSlicesBeenBuilt=false;this.name_=opt_name;if(this.model===undefined){throw new Error('SliceGroup must have model defined.');}}
-SliceGroup.prototype={__proto__:tr.model.EventContainer.prototype,get parentContainer(){return this.parentContainer_;},get model(){return this.parentContainer_.model;},get stableId(){return this.parentContainer_.stableId+'.SliceGroup';},getSettingsKey(){if(!this.name_)return undefined;const parentKey=this.parentContainer_.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},get length(){return this.slices.length;},pushSlice(slice){this.haveTopLevelSlicesBeenBuilt=false;slice.parentContainer=this.parentContainer_;this.slices.push(slice);return slice;},pushSlices(slices){this.haveTopLevelSlicesBeenBuilt=false;slices.forEach(function(slice){slice.parentContainer=this.parentContainer_;this.slices.push(slice);},this);},beginSlice(category,title,ts,opt_args,opt_tts,opt_argsStripped,opt_colorId){const colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(category,title);const slice=new sliceType(category,title,colorId,ts,opt_args?opt_args:{},null,opt_tts,undefined,opt_argsStripped);this.openPartialSlices_.push(slice);slice.didNotFinish=true;this.pushSlice(slice);return slice;},isTimestampValidForBeginOrEnd(ts){if(!this.openPartialSlices_.length)return true;const top=this.openPartialSlices_[this.openPartialSlices_.length-1];return ts>=top.start;},get openSliceCount(){return this.openPartialSlices_.length;},get mostRecentlyOpenedPartialSlice(){if(!this.openPartialSlices_.length)return undefined;return this.openPartialSlices_[this.openPartialSlices_.length-1];},endSlice(ts,opt_tts,opt_colorId){if(!this.openSliceCount){throw new Error('endSlice called without an open slice');}
+SliceGroup.prototype={__proto__:tr.model.EventContainer.prototype,get parentContainer(){return this.parentContainer_;},get model(){return this.parentContainer_.model;},get stableId(){return this.parentContainer_.stableId+'.SliceGroup';},getSettingsKey(){if(!this.name_)return undefined;const parentKey=this.parentContainer_.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},get length(){return this.slices.length;},pushSlice(slice){this.haveTopLevelSlicesBeenBuilt=false;slice.parentContainer=this.parentContainer_;this.slices.push(slice);return slice;},pushSlices(slices){this.haveTopLevelSlicesBeenBuilt=false;slices.forEach(function(slice){slice.parentContainer=this.parentContainer_;this.slices.push(slice);},this);},beginSlice(category,title,ts,opt_args,opt_tts,opt_argsStripped,opt_colorId,opt_bindId){const colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(category,title);const slice=new sliceType(category,title,colorId,ts,opt_args?opt_args:{},null,opt_tts,undefined,opt_argsStripped,opt_bindId);this.openPartialSlices_.push(slice);slice.didNotFinish=true;this.pushSlice(slice);return slice;},isTimestampValidForBeginOrEnd(ts){if(!this.openPartialSlices_.length)return true;const top=this.openPartialSlices_[this.openPartialSlices_.length-1];return ts>=top.start;},get openSliceCount(){return this.openPartialSlices_.length;},get mostRecentlyOpenedPartialSlice(){if(!this.openPartialSlices_.length)return undefined;return this.openPartialSlices_[this.openPartialSlices_.length-1];},endSlice(ts,opt_tts,opt_colorId){if(!this.openSliceCount){throw new Error('endSlice called without an open slice');}
 const slice=this.openPartialSlices_[this.openSliceCount-1];this.openPartialSlices_.splice(this.openSliceCount-1,1);if(ts<slice.start){throw new Error('Slice '+slice.title+' end time is before its start.');}
 slice.duration=ts-slice.start;slice.didNotFinish=false;slice.colorId=opt_colorId||slice.colorId;if(opt_tts&&slice.cpuStart!==undefined){slice.cpuDuration=opt_tts-slice.cpuStart;}
 return slice;},pushCompleteSlice(category,title,ts,duration,tts,cpuDuration,opt_args,opt_argsStripped,opt_colorId,opt_bindId){const colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(category,title);const slice=new sliceType(category,title,colorId,ts,opt_args?opt_args:{},duration,tts,cpuDuration,opt_argsStripped,opt_bindId);if(duration===undefined){slice.didNotFinish=true;}
 this.pushSlice(slice);return slice;},autoCloseOpenSlices(){this.updateBounds();const maxTimestamp=this.bounds.max;for(let sI=0;sI<this.slices.length;sI++){const slice=this.slices[sI];if(slice.didNotFinish){slice.duration=maxTimestamp-slice.start;}}
 this.openPartialSlices_=[];},shiftTimestampsForward(amount){for(let sI=0;sI<this.slices.length;sI++){const slice=this.slices[sI];slice.start=(slice.start+amount);}},updateBounds(){this.bounds.reset();for(let i=0;i<this.slices.length;i++){this.bounds.addValue(this.slices[i].start);this.bounds.addValue(this.slices[i].end);}},copySlice(slice){const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(slice.category,slice.title);const newSlice=new sliceType(slice.category,slice.title,slice.colorId,slice.start,slice.args,slice.duration,slice.cpuStart,slice.cpuDuration);newSlice.didNotFinish=slice.didNotFinish;return newSlice;},*findTopmostSlicesInThisContainer(eventPredicate,opt_this){if(!this.haveTopLevelSlicesBeenBuilt){throw new Error('Nope');}
-for(const s of this.topLevelSlices){yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);}},*childEvents(){yield*this.slices;},*childEventContainers(){},getSlicesOfName(title){const slices=[];for(let i=0;i<this.slices.length;i++){if(this.slices[i].title===title){slices.push(this.slices[i]);}}
+for(const s of this.topLevelSlices){yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);}},*childEvents(){yield*this.slices;},*childEventContainers(){},*getDescendantEventsInSortedRanges(ranges,opt_containerPredicate){if(opt_containerPredicate===undefined||opt_containerPredicate(this)){let rangeIndex=0;let range=ranges[rangeIndex];for(const event of this.childEvents()){while(event.start>range.max){rangeIndex++;if(rangeIndex>=ranges.length)return;range=ranges[rangeIndex];}
+if(event.end>=range.min)yield event;}}},getSlicesOfName(title){const slices=[];for(let i=0;i<this.slices.length;i++){if(this.slices[i].title===title){slices.push(this.slices[i]);}}
 return slices;},iterSlicesInTimeRange(callback,start,end){const ret=[];tr.b.iterateOverIntersectingIntervals(this.topLevelSlices,function(s){return s.start;},function(s){return s.duration;},start,end,function(topLevelSlice){callback(topLevelSlice);for(const slice of topLevelSlice.enumerateAllDescendents()){callback(slice);}});return ret;},findFirstSlice(){if(!this.haveTopLevelSlicesBeenBuilt){throw new Error('Nope');}
 if(0===this.slices.length)return undefined;return this.slices[0];},findSliceAtTs(ts){if(!this.haveTopLevelSlicesBeenBuilt)throw new Error('Nope');let i=tr.b.findIndexInSortedClosedIntervals(this.topLevelSlices,getSliceLo,getSliceHi,ts);if(i===-1||i===this.topLevelSlices.length){return undefined;}
 let curSlice=this.topLevelSlices[i];while(true){i=tr.b.findIndexInSortedClosedIntervals(curSlice.subSlices,getSliceLo,getSliceHi,ts);if(i===-1||i===curSlice.subSlices.length){return curSlice;}
@@ -5007,6 +5124,12 @@
 if(this.isLeafNode){return;}
 this.regions_=undefined;this.children_=this.rule_.children.map(function(childRule){const child=new VMRegionClassificationNode(childRule);child.buildChildNodesRecursively_();return child;});},addStatsFromRegion_(region){this.hasRegions=true;const regionSizeInBytes=region.sizeInBytes;if(regionSizeInBytes!==undefined){this.sizeInBytes=(this.sizeInBytes||0)+regionSizeInBytes;}
 const thisByteStats=this.byteStats;const regionByteStats=region.byteStats;for(const byteStatName in regionByteStats){const regionByteStatValue=regionByteStats[byteStatName];if(regionByteStatValue===undefined)continue;thisByteStats[byteStatName]=(thisByteStats[byteStatName]||0)+regionByteStatValue;}
+if(region.mappedFile.includes('/base.odex')||region.mappedFile.includes('/base.vdex')){if(region.byteStats.proportionalResident!==undefined){thisByteStats.javaBasePss=(thisByteStats.javaBasePss||0)+
+region.byteStats.proportionalResident;}
+if(region.byteStats.privateCleanResident!==undefined){thisByteStats.javaBaseCleanResident=(thisByteStats.javaBaseCleanResident||0)+
+region.byteStats.privateCleanResident;}
+if(region.byteStats.sharedCleanResident!==undefined){thisByteStats.javaBaseCleanResident=(thisByteStats.javaBaseCleanResident||0)+
+region.byteStats.sharedCleanResident;}}
 const textProtectionFlags=(VMRegion.PROTECTION_FLAG_READ|VMRegion.PROTECTION_FLAG_EXECUTE);if((region.protectionFlags===textProtectionFlags)&&(region.mappedFile.includes('/base.apk')||region.mappedFile.includes('/libchrome.so'))){if(regionSizeInBytes!==undefined){this.nativeLibrarySizeInBytes=(this.nativeLibrarySizeInBytes||0)+regionSizeInBytes;}
 if(region.byteStats.privateCleanResident!==undefined){thisByteStats.nativeLibraryPrivateCleanResident=(thisByteStats.nativeLibraryPrivateCleanResident||0)+
 region.byteStats.privateCleanResident;}
@@ -5054,11 +5177,7 @@
 this.parentFrame_=parentFrame;if(this.parentFrame_){this.parentFrame_.addChild(this);}},addChild(child){this.children.push(child);},removeChild(child){const i=this.children.indexOf(child.id);if(i===-1){throw new Error('omg');}
 this.children.splice(i,1);},removeAllChildren(){for(let i=0;i<this.children.length;i++){this.children[i].parentFrame_=undefined;}
 this.children.splice(0,this.children.length);},get stackTrace(){const stack=[this];let cur=this.parentFrame;while(cur){stack.push(cur);cur=cur.parentFrame;}
-return stack;},getUserFriendlyStackTrace(){return this.stackTrace.map(function(x){return x.title;});}};return{StackFrame,};});'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);}}
-return{Segment,};});'use strict';tr.exportTo('tr.model.um',function(){class UserModel extends tr.model.EventContainer{constructor(parentModel){super();this.parentModel_=parentModel;this.expectations_=new tr.model.EventSet();this.segments_=[];}
+return stack;},getUserFriendlyStackTrace(){return this.stackTrace.map(function(x){return x.title;});}};return{StackFrame,};});'use strict';tr.exportTo('tr.model.um',function(){class UserModel extends tr.model.EventContainer{constructor(parentModel){super();this.parentModel_=parentModel;this.expectations_=new tr.model.EventSet();this.segments_=[];}
 get stableId(){return'UserModel';}
 get parentModel(){return this.parentModel_;}
 sortExpectations(){this.expectations_.sortEvents((x,y)=>(x.start-y.start));}
@@ -5569,7 +5688,7 @@
 XMarkerAnnotationView.prototype={__proto__:tr.ui.annotations.AnnotationView.prototype,draw(ctx){const dt=this.viewport_.currentDisplayTransform;const viewX=dt.xWorldToView(this.annotation_.timestamp);ctx.beginPath();tr.ui.b.drawLine(ctx,viewX,0,viewX,ctx.canvas.height);ctx.strokeStyle=this.annotation_.strokeStyle;ctx.stroke();}};return{XMarkerAnnotationView,};});'use strict';tr.exportTo('tr.model',function(){function XMarkerAnnotation(timestamp){tr.model.Annotation.apply(this,arguments);this.timestamp=timestamp;this.strokeStyle='rgba(0, 0, 255, 0.5)';}
 XMarkerAnnotation.fromDict=function(dict){return new XMarkerAnnotation(dict.args.timestamp);};XMarkerAnnotation.prototype={__proto__:tr.model.Annotation.prototype,toDict(){return{typeName:'xmarker',args:{timestamp:this.timestamp}};},createView_(viewport){return new tr.ui.annotations.XMarkerAnnotationView(viewport,this);}};tr.model.Annotation.register(XMarkerAnnotation,{typeName:'xmarker'});return{XMarkerAnnotation,};});'use strict';tr.exportTo('tr.e.importer',function(){const Base64=tr.b.Base64;const deepCopy=tr.b.deepCopy;const ColorScheme=tr.b.ColorScheme;const HeapDumpTraceEventImporter=tr.e.importer.HeapDumpTraceEventImporter;const LegacyHeapDumpTraceEventImporter=tr.e.importer.LegacyHeapDumpTraceEventImporter;const StreamingEventExpander=tr.e.importer.StreamingEventExpander;const ProfilingDictionaryReader=tr.e.importer.ProfilingDictionaryReader;function getEventColor(event,opt_customName){if(event.cname){return ColorScheme.getColorIdForReservedName(event.cname);}else if(opt_customName||event.name){return ColorScheme.getColorIdForGeneralPurposeString(opt_customName||event.name);}}
 function isLegacyChromeClockSyncEvent(event){return event.name!==undefined&&event.name.startsWith(LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX)&&((event.ph==='S')||(event.ph==='F'));}
-const PRODUCER='producer';const CONSUMER='consumer';const STEP='step';const BACKGROUND=tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;const LIGHT=tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;const DETAILED=tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;const MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER=[undefined,BACKGROUND,LIGHT,DETAILED];const GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX='global/';const LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX='ClockSyncEvent.';const BYTE_STAT_NAME_MAP={'pc':'privateCleanResident','pd':'privateDirtyResident','sc':'sharedCleanResident','sd':'sharedDirtyResident','pss':'proportionalResident','sw':'swapped'};const WEAK_MEMORY_ALLOCATOR_DUMP_FLAG=1<<0;const OBJECT_TYPE_NAME_PATTERNS=[{prefix:'const char *WTF::getStringWithTypeName() [T = ',suffix:']'},{prefix:'const char* WTF::getStringWithTypeName() [with T = ',suffix:']'},{prefix:'const char *__cdecl WTF::getStringWithTypeName<',suffix:'>(void)'}];const SUBTRACE_FIELDS=new Set(['powerTraceAsString','systemTraceEvents',]);const NON_METADATA_FIELDS=new Set(['displayTimeUnit','samples','stackFrames','traceAnnotations','traceEvents',...SUBTRACE_FIELDS]);function TraceEventImporter(model,eventData){this.hasEvents_=undefined;this.importPriority=1;this.model_=model;this.events_=undefined;this.sampleEvents_=undefined;this.stackFrameEvents_=undefined;this.stackFrameTree_=new tr.model.ProfileTree();this.subtraces_=[];this.eventsWereFromString_=false;this.softwareMeasuredCpuCount_=undefined;this.allAsyncEvents_=[];this.allFlowEvents_=[];this.allObjectEvents_=[];this.contextProcessorPerThread={};this.traceEventSampleStackFramesByName_={};this.v8ProcessCodeMaps_={};this.v8ProcessRootStackFrame_={};this.v8SamplingData_=[];this.profileTrees_=new Map();this.profileInfo_=new Map();this.legacyChromeClockSyncStartEvent_=undefined;this.legacyChromeClockSyncFinishEvent_=undefined;this.allMemoryDumpEvents_={};this.heapProfileExpander=new ProfilingDictionaryReader();this.objectTypeNameMap_={};this.clockDomainId_=tr.model.ClockDomainId.UNKNOWN_CHROME_LEGACY;this.toModelTime_=undefined;if(typeof(eventData)==='string'||eventData instanceof String){eventData=eventData.trim();if(eventData[0]==='['){eventData=eventData.replace(/\s*,\s*$/,'');if(eventData[eventData.length-1]!==']'){eventData=eventData+']';}}
+const PRODUCER='producer';const CONSUMER='consumer';const STEP='step';const BACKGROUND=tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;const LIGHT=tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;const DETAILED=tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;const MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER=[undefined,BACKGROUND,LIGHT,DETAILED];const GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX='global/';const LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX='ClockSyncEvent.';const BYTE_STAT_NAME_MAP={'pc':'privateCleanResident','pd':'privateDirtyResident','sc':'sharedCleanResident','sd':'sharedDirtyResident','pss':'proportionalResident','sw':'swapped'};const WEAK_MEMORY_ALLOCATOR_DUMP_FLAG=1<<0;const OBJECT_TYPE_NAME_PATTERNS=[{prefix:'const char *WTF::getStringWithTypeName() [T = ',suffix:']'},{prefix:'const char* WTF::getStringWithTypeName() [with T = ',suffix:']'},{prefix:'const char *__cdecl WTF::getStringWithTypeName<',suffix:'>(void)'}];const SUBTRACE_FIELDS=new Set(['powerTraceAsString','systemTraceEvents','androidProcessDump',]);const NON_METADATA_FIELDS=new Set(['displayTimeUnit','samples','stackFrames','traceAnnotations','traceEvents',...SUBTRACE_FIELDS]);function TraceEventImporter(model,eventData){this.hasEvents_=undefined;this.importPriority=1;this.model_=model;this.events_=undefined;this.sampleEvents_=undefined;this.stackFrameEvents_=undefined;this.stackFrameTree_=new tr.model.ProfileTree();this.subtraces_=[];this.eventsWereFromString_=false;this.softwareMeasuredCpuCount_=undefined;this.allAsyncEvents_=[];this.allFlowEvents_=[];this.allObjectEvents_=[];this.contextProcessorPerThread={};this.traceEventSampleStackFramesByName_={};this.v8ProcessCodeMaps_={};this.v8ProcessRootStackFrame_={};this.v8SamplingData_=[];this.profileTrees_=new Map();this.profileInfo_=new Map();this.legacyChromeClockSyncStartEvent_=undefined;this.legacyChromeClockSyncFinishEvent_=undefined;this.allMemoryDumpEvents_={};this.heapProfileExpander=new ProfilingDictionaryReader();this.objectTypeNameMap_={};this.clockDomainId_=tr.model.ClockDomainId.UNKNOWN_CHROME_LEGACY;this.toModelTime_=undefined;if(typeof(eventData)==='string'||eventData instanceof String){eventData=eventData.trim();if(eventData[0]==='['){eventData=eventData.replace(/\s*,\s*$/,'');if(eventData[eventData.length-1]!==']'){eventData=eventData+']';}}
 this.events_=JSON.parse(eventData);this.eventsWereFromString_=true;}else{this.events_=eventData;}
 if(this.events_.traceEvents){const container=this.events_;this.events_=this.events_.traceEvents;for(const subtraceField of SUBTRACE_FIELDS){if(container[subtraceField]){this.storeSubtrace_(container[subtraceField]);}}
 this.storeSamples_(container.samples);this.storeStackFrames_(container.stackFrames);this.storeDisplayTimeUnit_(container.displayTimeUnit);this.storeTraceAnnotations_(container.traceAnnotations);this.storeMetadata_(container);}else if(this.events_ instanceof tr.b.TraceStream){const parser=oboe().node('{cat ph}',function(e){return oboe.drop;}).node('!.powerTraceAsString',this.storeSubtrace_.bind(this)).node('!.systemTraceEvents',this.storeSubtrace_.bind(this)).node('!.samples',this.storeSamples_.bind(this)).node('!.stackFrames',this.storeStackFrames_.bind(this)).node('!.displayTimeUnit',this.storeDisplayTimeUnit_.bind(this)).node('!.traceAnnotations',this.storeTraceAnnotations_.bind(this)).done(this.storeMetadata_.bind(this));this.events_.rewind();while(this.events_.hasData){parser.write(this.events_.readNumBytes());}
@@ -5588,8 +5707,8 @@
 const ts=this.toModelTimeFromUs_(event.ts);ctr.series.forEach(function(series){const val=event.args[series.name]?event.args[series.name]:0;series.addCounterSample(ts,val);});},processObjectEvent(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);this.allObjectEvents_.push({sequenceNumber:this.allObjectEvents_.length,event,thread});if(thread.guid in this.contextProcessorPerThread){const processor=this.contextProcessorPerThread[thread.guid];const scopedId=TraceEventImporter.scopedIdForEvent_(event);if(event.ph==='D'){processor.destroyContext(scopedId);}
 processor.invalidateContextCacheForSnapshot(scopedId);}},processContextEvent(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);if(!(thread.guid in this.contextProcessorPerThread)){this.contextProcessorPerThread[thread.guid]=new tr.importer.ContextProcessor(this.model_);}
 const scopedId=TraceEventImporter.scopedIdForEvent_(event);const contextType=event.name;const processor=this.contextProcessorPerThread[thread.guid];if(event.ph==='('){processor.enterContext(contextType,scopedId);}else if(event.ph===')'){processor.leaveContext(contextType,scopedId);}else{this.model_.importWarning({type:'unknown_context_phase',message:'Unknown context event phase: '+event.ph+'.'});}},setContextsFromThread_(thread,slice){if(thread.guid in this.contextProcessorPerThread){slice.contexts=this.contextProcessorPerThread[thread.guid].activeContexts;}},processDurationEvent(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);const ts=this.toModelTimeFromUs_(event.ts);if(event.dur===0&&!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)){this.model_.importWarning({type:'duration_parse_error',message:'Timestamps are moving backward.'});return;}
-if(event.ph==='B'){const slice=thread.sliceGroup.beginSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args),this.toModelTimeFromUs_(event.tts),event.argsStripped,getEventColor(event));slice.startStackFrame=this.getStackFrameForEvent_(event);this.setContextsFromThread_(thread,slice);}else if(event.ph==='I'||event.ph==='i'||event.ph==='R'){if(event.s!==undefined&&event.s!=='t'){throw new Error('This should never happen');}
-thread.sliceGroup.beginSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args),this.toModelTimeFromUs_(event.tts),event.argsStripped,getEventColor(event));const slice=thread.sliceGroup.endSlice(this.toModelTimeFromUs_(event.ts),this.toModelTimeFromUs_(event.tts));slice.startStackFrame=this.getStackFrameForEvent_(event);slice.endStackFrame=undefined;}else{if(!thread.sliceGroup.openSliceCount){this.model_.importWarning({type:'duration_parse_error',message:'E phase event without a matching B phase event.'});return;}
+if(event.ph==='B'){const slice=thread.sliceGroup.beginSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args),this.toModelTimeFromUs_(event.tts),event.argsStripped,getEventColor(event),event.bind_id);slice.startStackFrame=this.getStackFrameForEvent_(event);this.setContextsFromThread_(thread,slice);}else if(event.ph==='I'||event.ph==='i'||event.ph==='R'){if(event.s!==undefined&&event.s!=='t'){throw new Error('This should never happen');}
+thread.sliceGroup.beginSlice(event.cat,event.name,this.toModelTimeFromUs_(event.ts),this.deepCopyIfNeeded_(event.args),this.toModelTimeFromUs_(event.tts),event.argsStripped,getEventColor(event),event.bind_id);const slice=thread.sliceGroup.endSlice(this.toModelTimeFromUs_(event.ts),this.toModelTimeFromUs_(event.tts));slice.startStackFrame=this.getStackFrameForEvent_(event);slice.endStackFrame=undefined;}else{if(!thread.sliceGroup.openSliceCount){this.model_.importWarning({type:'duration_parse_error',message:'E phase event without a matching B phase event.'});return;}
 const slice=thread.sliceGroup.endSlice(this.toModelTimeFromUs_(event.ts),this.toModelTimeFromUs_(event.tts),getEventColor(event));if(event.name&&slice.title!==event.name){this.model_.importWarning({type:'title_match_error',message:'Titles do not match. Title is '+
 slice.title+' in openSlice, and is '+
 event.name+' in endSlice'});}
@@ -5670,7 +5789,7 @@
 if(event.cat===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async events (ph: b, e, or n) require a '+'cat parameter.'});continue;}
 if(event.name===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async events (ph: b, e, or n) require a '+'name parameter.'});continue;}
 const id=TraceEventImporter.scopedIdForEvent_(event);if(id===undefined){this.model_.importWarning({type:'async_slice_parse_error',message:'Nestable async events (ph: b, e, or n) require an '+'id parameter.'});continue;}
-if(event.cat==='blink.user_timing'){const matched=/([^\/:]+):([^\/:]+)\/?(.*)/.exec(event.name);if(matched!==null){const key=matched[1]+':'+event.cat;event.args=JSON.parse(Base64.atob(matched[3])||'{}');if(nestableMeasureAsyncEventsByKey[key]===undefined){nestableMeasureAsyncEventsByKey[key]=[];}
+if(event.cat==='blink.user_timing'){const matched=/([^\/:]+):([^\/]+)\/?(.*)/.exec(event.name);if(matched!==null){const key=matched[1]+':'+event.cat;event.args=JSON.parse(Base64.atob(matched[3])||'{}');if(nestableMeasureAsyncEventsByKey[key]===undefined){nestableMeasureAsyncEventsByKey[key]=[];}
 nestableMeasureAsyncEventsByKey[key].push(asyncEventState);continue;}}
 const key=event.cat+':'+id.toStringWithDelimiter(':');if(nestableAsyncEventsByKey[key]===undefined){nestableAsyncEventsByKey[key]=[];}
 nestableAsyncEventsByKey[key].push(asyncEventState);}
@@ -5997,7 +6116,7 @@
 if(series.name==='Max Frequency'){series.addCounterSample(ts,maxFreq);}});},powerStartEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/type=(\d+) state=(\d) cpu_id=(\d+)/.exec(eventBase.details);if(!event)return false;const targetCpuNumber=parseInt(event[3]);const cpuState=parseInt(event[2]);this.cpuStateSlice(ts,targetCpuNumber,event[1],cpuState);return true;},powerFrequencyEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/type=(\d+) state=(\d+) cpu_id=(\d+)/.exec(eventBase.details);if(!event)return false;const targetCpuNumber=parseInt(event[3]);const powerState=parseInt(event[2]);this.cpuFrequencySlice(ts,targetCpuNumber,powerState);return true;},cpuFrequencyEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/state=(\d+) cpu_id=(\d+)/.exec(eventBase.details);if(!event)return false;const targetCpuNumber=parseInt(event[2]);const powerState=parseInt(event[1]);this.cpuFrequencySlice(ts,targetCpuNumber,powerState);return true;},cpuFrequencyLimitsEvent(eventName,cpu,pid,ts,eventBase){const event=/min=(\d+) max=(\d+) cpu_id=(\d+)/.exec(eventBase.details);if(!event)return false;const targetCpuNumber=parseInt(event[3]);const minFreq=parseInt(event[1]);const maxFreq=parseInt(event[2]);this.cpuFrequencyLimitsSlice(ts,targetCpuNumber,minFreq,maxFreq);return true;},cpuIdleEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/state=(\d+) cpu_id=(\d+)/.exec(eventBase.details);if(!event)return false;const targetCpuNumber=parseInt(event[2]);const cpuState=parseInt(event[1]);this.cpuIdleSlice(ts,targetCpuNumber,cpuState);return true;}};Parser.register(PowerParser);return{PowerParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function RegulatorParser(importer){Parser.call(this,importer);importer.registerEventHandler('regulator_enable',RegulatorParser.prototype.regulatorEnableEvent.bind(this));importer.registerEventHandler('regulator_enable_delay',RegulatorParser.prototype.regulatorEnableDelayEvent.bind(this));importer.registerEventHandler('regulator_enable_complete',RegulatorParser.prototype.regulatorEnableCompleteEvent.bind(this));importer.registerEventHandler('regulator_disable',RegulatorParser.prototype.regulatorDisableEvent.bind(this));importer.registerEventHandler('regulator_disable_complete',RegulatorParser.prototype.regulatorDisableCompleteEvent.bind(this));importer.registerEventHandler('regulator_set_voltage',RegulatorParser.prototype.regulatorSetVoltageEvent.bind(this));importer.registerEventHandler('regulator_set_voltage_complete',RegulatorParser.prototype.regulatorSetVoltageCompleteEvent.bind(this));this.model_=importer.model_;}
 const regulatorEnableRE=/name=(.+)/;const regulatorDisableRE=/name=(.+)/;const regulatorSetVoltageCompleteRE=/name=(\S+), val=(\d+)/;RegulatorParser.prototype={__proto__:Parser.prototype,getCtr_(ctrName,valueName){const ctr=this.model_.kernel.getOrCreateCounter(null,'vreg '+ctrName+' '+valueName);if(ctr.series[0]===undefined){ctr.addSeries(new tr.model.CounterSeries(valueName,ColorScheme.getColorIdForGeneralPurposeString(ctrName+'.'+valueName)));}
 return ctr;},regulatorEnableEvent(eventName,cpuNum,pid,ts,eventBase){const event=regulatorEnableRE.exec(eventBase.details);if(!event)return false;const name=event[1];const ctr=this.getCtr_(name,'enabled');ctr.series[0].addCounterSample(ts,1);return true;},regulatorEnableDelayEvent(eventName,cpuNum,pid,ts,eventBase){return true;},regulatorEnableCompleteEvent(eventName,cpuNum,pid,ts,eventBase){return true;},regulatorDisableEvent(eventName,cpuNum,pid,ts,eventBase){const event=regulatorDisableRE.exec(eventBase.details);if(!event)return false;const name=event[1];const ctr=this.getCtr_(name,'enabled');ctr.series[0].addCounterSample(ts,0);return true;},regulatorDisableCompleteEvent(eventName,cpuNum,pid,ts,eventBase){return true;},regulatorSetVoltageEvent(eventName,cpuNum,pid,ts,eventBase){return true;},regulatorSetVoltageCompleteEvent(eventName,cpuNum,pid,ts,eventBase){const event=regulatorSetVoltageCompleteRE.exec(eventBase.details);if(!event)return false;const name=event[1];const voltage=parseInt(event[2]);const ctr=this.getCtr_(name,'voltage');ctr.series[0].addCounterSample(ts,voltage);return true;}};Parser.register(RegulatorParser);return{RegulatorParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const Parser=tr.e.importer.linux_perf.Parser;function SchedParser(importer){Parser.call(this,importer);importer.registerEventHandler('sched_switch',SchedParser.prototype.schedSwitchEvent.bind(this));importer.registerEventHandler('sched_wakeup',SchedParser.prototype.schedWakeupEvent.bind(this));importer.registerEventHandler('sched_blocked_reason',SchedParser.prototype.schedBlockedEvent.bind(this));importer.registerEventHandler('sched_cpu_hotplug',SchedParser.prototype.schedCpuHotplugEvent.bind(this));}
-const TestExports={};const schedSwitchRE=new RegExp('prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) '+'prev_state=(\\S\\+?|\\S\\|\\S) ==> '+'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)');const schedBlockedRE=new RegExp('pid=(\\d+) iowait=(\\d) caller=(.+)');TestExports.schedSwitchRE=schedSwitchRE;const schedWakeupRE=/comm=(.+) pid=(\d+) prio=(\d+)(?: success=\d+)? target_cpu=(\d+)/;TestExports.schedWakeupRE=schedWakeupRE;SchedParser.prototype={__proto__:Parser.prototype,schedSwitchEvent(eventName,cpuNumber,pid,ts,eventBase){const event=schedSwitchRE.exec(eventBase.details);if(!event)return false;const prevState=event[4];const nextComm=event[5];const nextPid=parseInt(event[6]);const nextPrio=parseInt(event[7]);if(eventBase.tgid!==undefined){const tgid=parseInt(eventBase.tgid);const process=this.importer.model_.getOrCreateProcess(tgid);if(!process.getThread(pid)){const thread=process.getOrCreateThread(pid);thread.name=eventBase.threadName;}}
+const TestExports={};const schedSwitchRE=new RegExp('prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) '+'prev_state=(\\S\\+?|\\S\\|\\S) ==> '+'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)');const schedBlockedRE=new RegExp('pid=(\\d+) iowait=(\\d) caller=(.+)');TestExports.schedSwitchRE=schedSwitchRE;const schedWakeupRE=/comm=(.+) pid=(\d+) prio=(\d+)(?: success=\d+)? target_cpu=(\d+)/;TestExports.schedWakeupRE=schedWakeupRE;const unknownThreadName='<...>';SchedParser.prototype={__proto__:Parser.prototype,schedSwitchEvent(eventName,cpuNumber,pid,ts,eventBase){const event=schedSwitchRE.exec(eventBase.details);if(!event)return false;const prevState=event[4];const nextComm=event[5];const nextPid=parseInt(event[6]);const nextPrio=parseInt(event[7]);if(eventBase.tgid!==undefined){const tgid=parseInt(eventBase.tgid);const process=this.importer.model_.getOrCreateProcess(tgid);const storedThread=process.getThread(pid);if(!storedThread){const thread=process.getOrCreateThread(pid);thread.name=eventBase.threadName;}else if(storedThread.name===unknownThreadName){storedThread.name=eventBase.threadName;}}
 const nextThread=this.importer.threadsByLinuxPid[nextPid];let nextName;if(nextThread){nextName=nextThread.userFriendlyName;}else{nextName=nextComm;}
 const cpu=this.importer.getOrCreateCpu(cpuNumber);cpu.switchActiveThread(ts,{stateWhenDescheduled:prevState},nextPid,nextName,{comm:nextComm,tid:nextPid,prio:nextPrio});return true;},schedWakeupEvent(eventName,cpuNumber,pid,ts,eventBase){const event=schedWakeupRE.exec(eventBase.details);if(!event)return false;const fromPid=pid;const comm=event[1];pid=parseInt(event[2]);const prio=parseInt(event[3]);this.importer.markPidRunnable(ts,pid,comm,prio,fromPid);return true;},schedCpuHotplugEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/cpu (\d+) (.+) error=(\d+)/.exec(eventBase.details);if(!event)return false;cpuNumber=event[1];const state=event[2];const targetCpu=this.importer.getOrCreateCpu(cpuNumber);const powerCounter=targetCpu.getOrCreateCounter('','Cpu Hotplug');if(powerCounter.numSeries===0){powerCounter.addSeries(new tr.model.CounterSeries('State',tr.b.ColorScheme.getColorIdForGeneralPurposeString(powerCounter.name+'.'+'State')));}
 powerCounter.series.forEach(function(series){if(series.name==='State'){series.addCounterSample(ts,state.localeCompare('offline')?0:1);}});return true;},schedBlockedEvent(eventName,cpuNumber,pid,ts,eventBase){const event=schedBlockedRE.exec(eventBase.details);if(!event)return false;pid=parseInt(event[1]);const iowait=parseInt(event[2]);const caller=event[3];this.importer.addPidBlockedReason(ts,pid,iowait,caller);return true;}};Parser.register(SchedParser);return{SchedParser,_SchedParserTestExports:TestExports};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function SyncParser(importer){Parser.call(this,importer);importer.registerEventHandler('sync_timeline',SyncParser.prototype.timelineEvent.bind(this));importer.registerEventHandler('sync_wait',SyncParser.prototype.syncWaitEvent.bind(this));importer.registerEventHandler('sync_pt',SyncParser.prototype.syncPtEvent.bind(this));this.model_=importer.model_;}
@@ -6140,7 +6259,7 @@
 if(x.guid&&y.guid){return x.guid-y.guid;}
 return 0;}
 function forEventTypesIn(events,typeNames,cb,opt_this){events.forEach(function(event){if(typeNames.indexOf(event.typeName)>=0){cb.call(opt_this,event);}});}
-function causedFrame(event){return event.associatedEvents.some(x=>x.title===tr.model.helpers.IMPL_RENDERING_STATS);}
+function causedFrame(event){return event.associatedEvents.some(isImplFrameEvent);}
 function getSortedFrameEventsByProcess(modelHelper){const frameEventsByPid={};for(const[pid,rendererHelper]of
 Object.entries(modelHelper.rendererHelpers)){frameEventsByPid[pid]=rendererHelper.getFrameEventsInRange(tr.model.helpers.IMPL_FRAMETIME_TYPE,modelHelper.model.bounds);}
 return frameEventsByPid;}
@@ -6223,7 +6342,8 @@
 protoExpectations.forEach(function(rpe){if(rpe.type!==ProtoExpectation.RESPONSE_TYPE){return;}
 if(!ape.containsTimestampInclusive(rpe.end)){return;}
 if(ape.containsTimestampInclusive(rpe.start)){return;}
-ape.start=rpe.end;});});return protoExpectations;}
+ape.start=rpe.end;if(ape.associatedEvents!==undefined){ape.associatedEvents=ape.associatedEvents.filter(e=>(!isImplFrameEvent(e)||e.start>=ape.start));}});});return protoExpectations;}
+function isImplFrameEvent(event){return event.title===tr.model.helpers.IMPL_RENDERING_STATS;}
 function fixTapResponseTouchAnimations(protoExpectations){function isTapResponse(pe){return(pe.type===ProtoExpectation.RESPONSE_TYPE)&&pe.containsTypeNames([INPUT_TYPE.TAP]);}
 function isTouchAnimation(pe){return(pe.type===ProtoExpectation.ANIMATION_TYPE)&&pe.containsTypeNames([INPUT_TYPE.TOUCH_MOVE])&&!pe.containsTypeNames([INPUT_TYPE.SCROLL_UPDATE,INPUT_TYPE.PINCH_UPDATE]);}
 const newPEs=[];while(protoExpectations.length){const pe=protoExpectations.shift();newPEs.push(pe);const peIsTapResponse=isTapResponse(pe);const peIsTouchAnimation=isTouchAnimation(pe);if(!peIsTapResponse&&!peIsTouchAnimation){continue;}
@@ -6235,7 +6355,7 @@
 pe.associatedEvents.addEventSet(frameEvents);newPEs.push(pe);}
 return newPEs;}
 function checkAllInputEventsHandled(modelHelper,sortedInputEvents,protoExpectations,warn){const handledEvents=[];protoExpectations.forEach(function(protoExpectation){protoExpectation.associatedEvents.forEach(function(event){if((event.title===CSS_ANIMATION_TITLE)&&(event.subSlices.length>0)){return;}
-if((handledEvents.indexOf(event)>=0)&&(event.title!==tr.model.helpers.IMPL_RENDERING_STATS)){warn({type:'UserModelBuilder',message:`double-handled event: ${event.typeName} @ ${event.start}`,showToUser:false,});return;}
+if((handledEvents.indexOf(event)>=0)&&(!isImplFrameEvent(event))){warn({type:'UserModelBuilder',message:`double-handled event: ${event.typeName} @ ${event.start}`,showToUser:false,});return;}
 handledEvents.push(event);});});sortedInputEvents.forEach(function(event){if(handledEvents.indexOf(event)<0){warn({type:'UserModelBuilder',message:`double-handled event: ${event.typeName} @ ${event.start}`,showToUser:false,});}});}
 function findInputExpectations(modelHelper){let warning;function warn(w){if(warning)return;warning=w;}
 const sortedInputEvents=getSortedInputEvents(modelHelper);let protoExpectations=findProtoExpectations(modelHelper,sortedInputEvents,warn);protoExpectations=postProcessProtoExpectations(modelHelper,protoExpectations);checkAllInputEventsHandled(modelHelper,sortedInputEvents,protoExpectations,warn);if(warning)modelHelper.model.importWarning(warning);const expectations=[];protoExpectations.forEach(function(protoExpectation){const ir=protoExpectation.createInteractionRecord(modelHelper.model);if(ir){expectations.push(ir);}});return expectations;}
@@ -6258,19 +6378,22 @@
 map.set(uniqueKey,value);}
 function skipDumpsThatDoNotIntersectRange(dumps,opt_range){if(!opt_range)return dumps;return dumps.filter(d=>opt_range.intersectsExplicitRangeInclusive(d.start,d.end));}
 function hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
-return{hasCategoryAndName,filterExpectationsByRange,perceptualBlend,splitGlobalDumpsByBrowserName};});'use strict';tr.exportTo('tr.e.chrome',function(){const CHROME_INTERNAL_URLS=['','about:blank','data:text/html,pluginplaceholderdata','chrome-error://chromewebdata/'];const TOP_LEVEL_TASK_TITLES=['TaskQueueManager::ProcessTaskFromWorkQueue','ThreadControllerImpl::DoWork',];class EventFinderUtils{static hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
-static getSortedMainThreadEventsByFrame(rendererHelper,eventTitle,eventCategory){const eventsByFrame=new Map();for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(rendererHelper.isTelemetryInternalEvent(ev))continue;if(!this.hasCategoryAndName(ev,eventCategory,eventTitle)){continue;}
-const frameIdRef=ev.args.frame;if(frameIdRef===undefined)continue;if(!eventsByFrame.has(frameIdRef)){eventsByFrame.set(frameIdRef,[]);}
+return{hasCategoryAndName,filterExpectationsByRange,perceptualBlend,splitGlobalDumpsByBrowserName};});'use strict';tr.exportTo('tr.e.chrome',function(){const CHROME_INTERNAL_URLS=['','about:blank','data:text/html,pluginplaceholderdata','chrome-error://chromewebdata/'];const SCHEDULER_TOP_LEVEL_TASK_TITLE='ThreadControllerImpl::RunTask';const SCHEDULOER_TOP_LEVEL_TASKS=new Set([SCHEDULER_TOP_LEVEL_TASK_TITLE,'ThreadControllerImpl::DoWork','TaskQueueManager::ProcessTaskFromWorkQueue']);class EventFinderUtils{static hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
+static*getMainThreadEvents(rendererHelper,eventTitle,eventCategory){if(!rendererHelper.mainThread)return;for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(rendererHelper.isTelemetryInternalEvent(ev))continue;if(!this.hasCategoryAndName(ev,eventCategory,eventTitle)){continue;}
+yield ev;}}
+static getSortedMainThreadEventsByFrame(rendererHelper,eventTitle,eventCategory){const eventsByFrame=new Map();const events=this.getMainThreadEvents(rendererHelper,eventTitle,eventCategory);for(const ev of events){const frameIdRef=ev.args.frame;if(frameIdRef===undefined)continue;if(!eventsByFrame.has(frameIdRef)){eventsByFrame.set(frameIdRef,[]);}
 eventsByFrame.get(frameIdRef).push(ev);}
 return eventsByFrame;}
+static getSortedMainThreadEventsByNavId(rendererHelper,eventTitle,eventCategory){const eventsByNavId=new Map();const events=this.getMainThreadEvents(rendererHelper,eventTitle,eventCategory);for(const ev of events){if(ev.args.data===undefined)continue;const navIdRef=ev.args.data.navigationId;if(navIdRef===undefined)continue;eventsByNavId.set(navIdRef,ev);}
+return eventsByNavId;}
 static findLastEventStartingOnOrBeforeTimestamp(sortedEvents,timestamp){const firstIndexAfterTimestamp=tr.b.findFirstTrueIndexInSortedArray(sortedEvents,e=>e.start>timestamp);if(firstIndexAfterTimestamp===0)return undefined;return sortedEvents[firstIndexAfterTimestamp-1];}
 static findLastEventStartingBeforeTimestamp(sortedEvents,timestamp){const firstIndexAfterTimestamp=tr.b.findFirstTrueIndexInSortedArray(sortedEvents,e=>e.start>=timestamp);if(firstIndexAfterTimestamp===0)return undefined;return sortedEvents[firstIndexAfterTimestamp-1];}
 static findNextEventStartingOnOrAfterTimestamp(sortedEvents,timestamp){const firstIndexOnOrAfterTimestamp=tr.b.findFirstTrueIndexInSortedArray(sortedEvents,e=>e.start>=timestamp);if(firstIndexOnOrAfterTimestamp===sortedEvents.length){return undefined;}
 return sortedEvents[firstIndexOnOrAfterTimestamp];}
 static findNextEventStartingAfterTimestamp(sortedEvents,timestamp){const firstIndexOnOrAfterTimestamp=tr.b.findFirstTrueIndexInSortedArray(sortedEvents,e=>e.start>timestamp);if(firstIndexOnOrAfterTimestamp===sortedEvents.length){return undefined;}
 return sortedEvents[firstIndexOnOrAfterTimestamp];}
-static findToplevelSchedulerTasks(mainThread){const titles=new Set(TOP_LEVEL_TASK_TITLES);const tasks=[];tasks.push(...mainThread.findTopmostSlices(slice=>titles.has(slice.title)));return tasks;}}
-return{EventFinderUtils,CHROME_INTERNAL_URLS,};});'use strict';tr.exportTo('tr.e.chrome',function(){const TIME_TO_INTERACTIVE_WINDOW_SIZE_MS=5000;const ACTIVE_REQUEST_TOLERANCE=2;const FCI_MIN_CLUSTER_SEPARATION_MS=1000;const TASK_CLUSTER_HEAVINESS_THRESHOLD_MS=250;const ENDPOINT_TYPES={LONG_TASK_START:'LONG_TASK_START',LONG_TASK_END:'LONG_TASK_END',REQUEST_START:'REQUEST_START',REQUEST_END:'REQUEST_END'};function getEndpoints_(events,startType,endType){const endpoints=[];for(const event of events){endpoints.push({time:event.start,type:startType});endpoints.push({time:event.end,type:endType});}
+static findToplevelSchedulerTasks(mainThread){const tasks=[];tasks.push(...mainThread.findTopmostSlices(slice=>slice.category==='toplevel'&&SCHEDULOER_TOP_LEVEL_TASKS.has(slice.title)));return tasks;}}
+return{EventFinderUtils,CHROME_INTERNAL_URLS,SCHEDULER_TOP_LEVEL_TASK_TITLE,};});'use strict';tr.exportTo('tr.e.chrome',function(){const TIME_TO_INTERACTIVE_WINDOW_SIZE_MS=5000;const ACTIVE_REQUEST_TOLERANCE=2;const FCI_MIN_CLUSTER_SEPARATION_MS=1000;const TASK_CLUSTER_HEAVINESS_THRESHOLD_MS=250;const ENDPOINT_TYPES={LONG_TASK_START:'LONG_TASK_START',LONG_TASK_END:'LONG_TASK_END',REQUEST_START:'REQUEST_START',REQUEST_END:'REQUEST_END'};function getEndpoints_(events,startType,endType){const endpoints=[];for(const event of events){endpoints.push({time:event.start,type:startType});endpoints.push({time:event.end,type:endType});}
 return endpoints;}
 function reachedTTIQuiscence_(timestamp,networkQuietWindowStart,mainThreadQuietWindowStart){if(networkQuietWindowStart===undefined||mainThreadQuietWindowStart===undefined){return false;}
 const mainThreadQuietForLongEnough=timestamp-mainThreadQuietWindowStart>=TIME_TO_INTERACTIVE_WINDOW_SIZE_MS;const networkQuietForLongEnough=timestamp-networkQuietWindowStart>=TIME_TO_INTERACTIVE_WINDOW_SIZE_MS;return mainThreadQuietForLongEnough&&networkQuietForLongEnough;}
@@ -7018,7 +7141,7 @@
 get length(){return this._diagnostics.length;}*[Symbol.iterator](){for(const diagnostic of this._diagnostics)yield diagnostic;}
 asDictInto_(d){d.diagnostics=this._diagnostics.map(d=>d.asDictOrReference());}
 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},DEVICE_IDS:{name:'deviceIds',type:tr.v.d.GenericSet},DOCUMENTATION_URLS:{name:'documentationUrls',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},GROUPING_PATH:{name:'groupingPath',type:tr.v.d.GroupingPath},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},MERGED_FROM:{name:'mergedFrom',type:tr.v.d.RelatedHistogramMap},MERGED_TO:{name:'mergedTo',type:tr.v.d.RelatedHistogramMap},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},RELATED_NAMES:{name:'relatedNames',type:tr.v.d.GenericSet},SKIA_REVISIONS:{name:'skiaRevisions',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},TAG_MAP:{name:'tagmap',type:tr.v.d.TagMap},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},};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={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},DEVICE_IDS:{name:'deviceIds',type:tr.v.d.GenericSet},DOCUMENTATION_URLS:{name:'documentationUrls',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},GROUPING_PATH:{name:'groupingPath',type:tr.v.d.GroupingPath},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},MERGED_FROM:{name:'mergedFrom',type:tr.v.d.RelatedHistogramMap},MERGED_TO:{name:'mergedTo',type:tr.v.d.RelatedHistogramMap},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},RELATED_NAMES:{name:'relatedNames',type:tr.v.d.GenericSet},REVISION_TIMESTAMPS:{name:'revisionTimestamps',type:tr.v.d.DateRange},SKIA_REVISIONS:{name:'skiaRevisions',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},TAG_MAP:{name:'tagmap',type:tr.v.d.TagMap},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},};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;}
@@ -7670,10 +7793,12 @@
 function DataSeries(key){this.key_=key;this.target_=undefined;this.title_='';this.optional_=false;this.enabled_=true;this.color_=getColorOfKey(key,false);this.highlightedColor_=getColorOfKey(key,true);}
 DataSeries.prototype={get key(){return this.key_;},get title(){return this.title_;},set title(t){this.title_=t;},get color(){return this.color_;},set color(c){this.color_=c;},get highlightedColor(){return this.highlightedColor_;},set highlightedColor(c){this.highlightedColor_=c;},get optional(){return this.optional_;},set optional(optional){this.optional_=optional;},get enabled(){return this.enabled_;},set enabled(enabled){if(!this.optional&&!enabled){this.optional=true;}
 this.enabled_=enabled;},get target(){return this.target_;},set target(t){this.target_=t;}};const ChartBase=tr.ui.b.define('svg',undefined,svgNS);ChartBase.prototype={__proto__:HTMLUnknownElement.prototype,getDataSeries(key){if(!this.seriesByKey_.has(key)){this.seriesByKey_.set(key,new DataSeries(key));}
-return this.seriesByKey_.get(key);},decorate(){Polymer.dom(this).classList.add('chart-base');this.setAttribute('style','cursor: default; user-select: none;');this.chartTitle_=undefined;this.seriesByKey_=new Map();this.graphWidth_=undefined;this.graphHeight_=undefined;this.margin={top:0,right:0,bottom:0,left:0,};this.hideLegend_=false;const template=Polymer.dom(THIS_DOC).querySelector('#chart-base-template');const svgEl=Polymer.dom(template.content).querySelector('svg');for(let i=0;i<Polymer.dom(svgEl).children.length;i++){Polymer.dom(this).appendChild(Polymer.dom(svgEl.children[i]).cloneNode(true));}
-this.addEventListener(DataSeriesEnableChangeEventType,this.onDataSeriesEnableChange_.bind(this));},get hideLegend(){return this.hideLegend_;},set hideLegend(h){this.hideLegend_=h;this.updateContents_();},isSeriesEnabled(key){return this.getDataSeries(key).enabled;},onDataSeriesEnableChange_(event){this.getDataSeries(event.key).enabled=event.enabled;this.updateContents_();},get chartTitle(){return this.chartTitle_;},set chartTitle(chartTitle){this.chartTitle_=chartTitle;this.updateContents_();},get chartAreaElement(){return Polymer.dom(this).querySelector('#chart-area');},get graphWidth(){if(this.graphWidth_===undefined)return this.defaultGraphWidth;return this.graphWidth_;},set graphWidth(width){this.graphWidth_=width;this.updateContents_();},get defaultGraphWidth(){return 0;},get graphHeight(){if(this.graphHeight_===undefined)return this.defaultGraphHeight;return this.graphHeight_;},set graphHeight(height){this.graphHeight_=height;this.updateContents_();},get defaultGraphHeight(){return 0;},get totalWidth(){return this.margin.left+this.graphWidth+this.margin.right;},get totalHeight(){return this.margin.top+this.graphHeight+this.margin.bottom;},updateMargins_(){const legendSize=this.computeLegendSize_();this.margin.right=Math.max(this.margin.right,legendSize.width);this.margin.bottom=Math.max(this.margin.bottom,legendSize.height-this.graphHeight);if(this.chartTitle_){const titleSize=getSVGTextSize(this,this.chartTitle_,textNode=>{textNode.style.fontSize='16pt';});this.margin.top=Math.max(this.margin.top,titleSize.height+15);const horizontalOverhangPx=(titleSize.width-this.graphWidth)/2;this.margin.left=Math.max(this.margin.left,horizontalOverhangPx);this.margin.right=Math.max(this.margin.right,horizontalOverhangPx);}},computeLegendSize_(){let width=0;let height=0;if(this.hideLegend)return{width,height};for(const series of this.seriesByKey_.values()){const textSize=getSVGTextSize(this,series.key);width=Math.max(width,textSize.width+20);height+=textSize.height;}
+return this.seriesByKey_.get(key);},decorate(){Polymer.dom(this).classList.add('chart-base');this.setAttribute('style','cursor: default; user-select: none;');this.chartTitle_=undefined;this.seriesByKey_=new Map();this.graphWidth_=undefined;this.graphHeight_=undefined;this.margin={top:0,right:0,bottom:0,left:0,};this.hideLegend_=false;this.showTitleInLegend_=false;this.titleHeight_='16pt';const template=Polymer.dom(THIS_DOC).querySelector('#chart-base-template');const svgEl=Polymer.dom(template.content).querySelector('svg');for(let i=0;i<Polymer.dom(svgEl).children.length;i++){Polymer.dom(this).appendChild(Polymer.dom(svgEl.children[i]).cloneNode(true));}
+this.addEventListener(DataSeriesEnableChangeEventType,this.onDataSeriesEnableChange_.bind(this));},get hideLegend(){return this.hideLegend_;},set hideLegend(h){this.hideLegend_=h;this.updateContents_();},get showTitleInLegend(){return this.showTitleInLegend_;},set showTitleInLegend(s){this.showTitleInLegend_=s;this.updateContents_();},isSeriesEnabled(key){return this.getDataSeries(key).enabled;},onDataSeriesEnableChange_(event){this.getDataSeries(event.key).enabled=event.enabled;this.updateContents_();},get chartTitle(){return this.chartTitle_;},set chartTitle(chartTitle){this.chartTitle_=chartTitle;this.updateContents_();},get chartAreaElement(){return Polymer.dom(this).querySelector('#chart-area');},get graphWidth(){if(this.graphWidth_===undefined)return this.defaultGraphWidth;return this.graphWidth_;},set graphWidth(width){this.graphWidth_=width;this.updateContents_();},get defaultGraphWidth(){return 0;},get graphHeight(){if(this.graphHeight_===undefined)return this.defaultGraphHeight;return this.graphHeight_;},set graphHeight(height){this.graphHeight_=height;this.updateContents_();},get titleHeight(){return this.titleHeight_;},set titleHeight(height){this.titleHeight_=height;this.updateContents_();},get defaultGraphHeight(){return 0;},get totalWidth(){return this.margin.left+this.graphWidth+this.margin.right;},get totalHeight(){return this.margin.top+this.graphHeight+this.margin.bottom;},updateMargins_(){const legendSize=this.computeLegendSize_();this.margin.right=Math.max(this.margin.right,legendSize.width);this.margin.bottom=Math.max(this.margin.bottom,legendSize.height-this.graphHeight);if(this.chartTitle_){const titleSize=getSVGTextSize(this,this.chartTitle_,textNode=>{textNode.style.fontSize='16pt';});this.margin.top=Math.max(this.margin.top,titleSize.height+15);const horizontalOverhangPx=(titleSize.width-this.graphWidth)/2;this.margin.left=Math.max(this.margin.left,horizontalOverhangPx);this.margin.right=Math.max(this.margin.right,horizontalOverhangPx);}},computeLegendSize_(){let width=0;let height=0;if(this.hideLegend)return{width,height};let series=[...this.seriesByKey_.values()];if(this.showTitleInLegend){series=series.filter(series=>series.title!=='');}
+for(const seriesEntry of series){const legendText=this.showTitleInLegend?seriesEntry.title:seriesEntry.key;const textSize=getSVGTextSize(this,legendText);width=Math.max(width,textSize.width+30);height+=textSize.height;}
 return{width,height};},updateDimensions_(){const thisSel=d3.select(this);thisSel.attr('width',this.totalWidth);thisSel.attr('height',this.totalHeight);d3.select(this.chartAreaElement).attr('transform','translate('+this.margin.left+', '+this.margin.top+')');},updateContents_(){this.updateMargins_();this.updateDimensions_();this.updateTitle_();this.updateLegend_();},updateTitle_(){const titleSel=d3.select(this.chartAreaElement).select('#title');if(!this.chartTitle_){titleSel.style('display','none');return;}
-titleSel.attr('transform','translate('+this.graphWidth*0.5+',-15)').style('display',undefined).style('text-anchor','middle').style('font-size','16pt').attr('class','title').attr('width',this.graphWidth).text(this.chartTitle_);},updateLegend_(){const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.legend').remove();if(this.hideLegend)return;const series=[...this.seriesByKey_.values()].reverse();const legendEntriesSel=chartAreaSel.selectAll('.legend').data(series);legendEntriesSel.enter().append('foreignObject').attr('class','legend').attr('x',this.graphWidth+2).attr('width',this.margin.right).attr('height',18).attr('transform',(series,i)=>'translate(0,'+i*18+')').append('xhtml:body').style('margin',0).append('tr-ui-b-chart-legend-key').property('color',series=>((this.currentHighlightedLegendKey===series.key)?series.highlightedColor:series.color)).property('width',this.margin.right).property('target',series=>series.target).property('title',series=>series.title).property('optional',series=>series.optional).property('enabled',series=>series.enabled).text(series=>series.key);legendEntriesSel.exit().remove();},get highlightedLegendKey(){return this.highlightedLegendKey_;},set highlightedLegendKey(highlightedLegendKey){this.highlightedLegendKey_=highlightedLegendKey;this.updateHighlight_();},get currentHighlightedLegendKey(){if(this.tempHighlightedLegendKey_){return this.tempHighlightedLegendKey_;}
+titleSel.attr('transform','translate('+this.graphWidth*0.5+',-15)').style('display',undefined).style('text-anchor','middle').style('font-size',this.titleHeight).attr('class','title').attr('width',this.graphWidth).text(this.chartTitle_);},updateLegend_(){const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.legend').remove();if(this.hideLegend)return;let series;let seriesText;if(this.showTitleInLegend){series=[...this.seriesByKey_.values()].filter(series=>series.title!=='').reverse();seriesText=series=>series.title;}else{series=[...this.seriesByKey_.values()].reverse();seriesText=series=>series.key;}
+const legendEntriesSel=chartAreaSel.selectAll('.legend').data(series);legendEntriesSel.enter().append('foreignObject').attr('class','legend').attr('x',this.graphWidth+2).attr('width',this.margin.right).attr('height',18).attr('transform',(series,i)=>'translate(0,'+i*18+')').append('xhtml:body').style('margin',0).append('tr-ui-b-chart-legend-key').property('color',series=>((this.currentHighlightedLegendKey===series.key)?series.highlightedColor:series.color)).property('width',this.margin.right).property('target',series=>series.target).property('title',series=>series.title).property('optional',series=>series.optional).property('enabled',series=>series.enabled).text(seriesText);legendEntriesSel.exit().remove();},get highlightedLegendKey(){return this.highlightedLegendKey_;},set highlightedLegendKey(highlightedLegendKey){this.highlightedLegendKey_=highlightedLegendKey;this.updateHighlight_();},get currentHighlightedLegendKey(){if(this.tempHighlightedLegendKey_){return this.tempHighlightedLegendKey_;}
 return this.highlightedLegendKey_;},pushTempHighlightedLegendKey(key){if(this.tempHighlightedLegendKey_){throw new Error('push cannot nest');}
 this.tempHighlightedLegendKey_=key;this.updateHighlight_();},popTempHighlightedLegendKey(key){if(this.tempHighlightedLegendKey_!==key){throw new Error('pop cannot happen');}
 this.tempHighlightedLegendKey_=undefined;this.updateHighlight_();},updateHighlight_(){const chartAreaSel=d3.select(this.chartAreaElement);const legendEntriesSel=chartAreaSel.selectAll('.legend');const getDataSeries=chart.getDataSeries.bind(chart);const currentHighlightedLegendKey=chart.currentHighlightedLegendKey;legendEntriesSel.each(function(key){const dataSeries=getDataSeries(key);if(key===currentHighlightedLegendKey){this.style.fill=dataSeries.highlightedColor;this.style.fontWeight='bold';}else{this.style.fill=dataSeries.color;this.style.fontWeight='';}});}};return{ChartBase,DataSeriesEnableChangeEventType,getColorOfKey,getSVGTextSize,};});'use strict';tr.exportTo('tr.ui.b',function(){const D3_Y_AXIS_WIDTH_PX=9;const D3_X_AXIS_HEIGHT_PX=23;function sanitizePower(x,defaultValue){if(!isNaN(x)&&isFinite(x)&&(x!==0))return x;return defaultValue;}
@@ -7705,21 +7830,27 @@
 this.data_.forEach(function(multiSeriesDatum,index){const x=this.getXForDatum_(multiSeriesDatum,index);d3.keys(multiSeriesDatum).forEach(function(seriesKey){if(seriesKey==='x')return;if(multiSeriesDatum[seriesKey]===undefined)return;if(!this.isDatumFieldSeries_(seriesKey))return;const singleSeriesDatum={x};singleSeriesDatum[seriesKey]=multiSeriesDatum[seriesKey];dataBySeriesKey[seriesKey].push(singleSeriesDatum);},this);},this);return dataBySeriesKey;},getChartPointAtClientPoint_(clientPoint){const rect=this.getBoundingClientRect();return{x:clientPoint.x-rect.left-this.margin.left,y:clientPoint.y-rect.top-this.margin.top};},getDataPointAtChartPoint_(chartPoint){return{x:tr.b.math.clamp(this.xScale_.invert(chartPoint.x),this.xScale_.domain()[0],this.xScale_.domain()[1]),y:tr.b.math.clamp(this.yScale_.invert(chartPoint.y),this.yScale_.domain()[0],this.yScale_.domain()[1])};},getDataPointAtClientPoint_(clientX,clientY){const chartPoint=this.getChartPointAtClientPoint_({x:clientX,y:clientY});return this.getDataPointAtChartPoint_(chartPoint);},prepareDataEvent_(mouseEvent,dataEvent){const dataPoint=this.getDataPointAtClientPoint_(mouseEvent.clientX,mouseEvent.clientY);dataEvent.x=dataPoint.x;dataEvent.y=dataPoint.y;},onMouseDown_(mouseEvent){tr.ui.b.trackMouseMovesUntilMouseUp(this.onMouseMove_.bind(this,mouseEvent.button),this.onMouseUp_.bind(this,mouseEvent.button));mouseEvent.preventDefault();mouseEvent.stopPropagation();const dataEvent=new tr.b.Event('item-mousedown');dataEvent.button=mouseEvent.button;this.prepareDataEvent_(mouseEvent,dataEvent);this.dispatchEvent(dataEvent);for(const child of this.querySelector('#brushes').children){child.setAttribute('fill','rgb(103, 199, 165)');}},onMouseMove_(button,mouseEvent){if(mouseEvent.buttons!==undefined){mouseEvent.preventDefault();mouseEvent.stopPropagation();}
 const dataEvent=new tr.b.Event('item-mousemove');dataEvent.button=button;this.prepareDataEvent_(mouseEvent,dataEvent);this.dispatchEvent(dataEvent);for(const child of this.querySelector('#brushes').children){child.setAttribute('fill','rgb(103, 199, 165)');}},onMouseUp_(button,mouseEvent){mouseEvent.preventDefault();mouseEvent.stopPropagation();const dataEvent=new tr.b.Event('item-mouseup');dataEvent.button=button;this.prepareDataEvent_(mouseEvent,dataEvent);this.dispatchEvent(dataEvent);for(const child of this.querySelector('#brushes').children){child.setAttribute('fill','rgb(213, 236, 229)');}}};return{ChartBase2D,};});'use strict';tr.exportTo('tr.ui.b',function(){const ChartBase2D=tr.ui.b.ChartBase2D;const ChartBase2DBrushX=tr.ui.b.define('chart-base-2d-brush-1d',ChartBase2D);ChartBase2DBrushX.prototype={__proto__:ChartBase2D.prototype,decorate(){super.decorate();this.brushedRange_=new tr.b.math.Range();},set brushedRange(range){this.brushedRange_.reset();this.brushedRange_.addRange(range);this.updateContents_();},get brushedRange(){return tr.b.math.Range.fromDict(this.brushedRange_.toJSON());},computeBrushRangeFromIndices(indexA,indexB){indexA=tr.b.math.clamp(indexA,0,this.data_.length-1);indexB=tr.b.math.clamp(indexB,0,this.data_.length-1);const leftIndex=Math.min(indexA,indexB);const rightIndex=Math.max(indexA,indexB);const brushRange=new tr.b.math.Range();brushRange.addValue(this.getXForDatum_(this.data_[leftIndex],leftIndex)-
 this.getSampleWidth_(this.data_,leftIndex,true));brushRange.addValue(this.getXForDatum_(this.data_[rightIndex],rightIndex)+
-this.getSampleWidth_(this.data_,rightIndex,false));return brushRange;},getDataIndex_(dataX){if(this.data.length===0)return undefined;const bisect=d3.bisector(this.getXForDatum_.bind(this)).right;return bisect(this.data_,dataX)-1;},prepareDataEvent_(mouseEvent,dataEvent){ChartBase2D.prototype.prepareDataEvent_.call(this,mouseEvent,dataEvent);dataEvent.index=this.getDataIndex_(dataEvent.x);if(dataEvent.index!==undefined){dataEvent.data=this.data_[dataEvent.index];}},updateBrushContents_(brushSel){brushSel.selectAll('*').remove();const brushes=this.brushedRange_.isEmpty?[]:[this.brushedRange_];const brushRectsSel=brushSel.selectAll('rect').data(brushes);brushRectsSel.enter().append('rect');brushRectsSel.exit().remove();this.drawBrush_(brushRectsSel);},drawBrush_(brushRectsSel){brushRectsSel.attr('x',d=>this.xScale_(d.min)).attr('y',0).attr('width',d=>this.xScale_(d.max)-this.xScale_(d.min)).attr('height',this.graphHeight).attr('fill','rgb(213, 236, 229)');}};return{ChartBase2DBrushX,};});'use strict';tr.exportTo('tr.ui.b',function(){const ColumnChart=tr.ui.b.define('column-chart',tr.ui.b.ChartBase2DBrushX);ColumnChart.prototype={__proto__:tr.ui.b.ChartBase2DBrushX.prototype,decorate(){super.decorate();this.xCushion_=1;this.isStacked_=false;this.enableHoverBox=true;this.displayXInHover=false;},set isStacked(stacked){this.isStacked_=true;this.updateContents_();},get isStacked(){return this.isStacked_;},get defaultGraphHeight(){return 100;},get defaultGraphWidth(){return 10*this.data_.length;},updateScales_(){if(this.data_.length===0)return;let xDifferences=0;let currentX=undefined;let previousX=undefined;this.data_.forEach(function(datum,index){previousX=currentX;currentX=this.getXForDatum_(datum,index);if(previousX!==undefined){xDifferences+=currentX-previousX;}},this);this.xScale_.range([0,this.graphWidth]);const domain=d3.extent(this.data_,this.getXForDatum_.bind(this));if(this.data_.length>1){this.xCushion_=xDifferences/(this.data_.length-1);}
+this.getSampleWidth_(this.data_,rightIndex,false));return brushRange;},getDataIndex_(dataX){if(this.data.length===0)return undefined;const bisect=d3.bisector(this.getXForDatum_.bind(this)).right;return bisect(this.data_,dataX)-1;},prepareDataEvent_(mouseEvent,dataEvent){ChartBase2D.prototype.prepareDataEvent_.call(this,mouseEvent,dataEvent);dataEvent.index=this.getDataIndex_(dataEvent.x);if(dataEvent.index!==undefined){dataEvent.data=this.data_[dataEvent.index];}},updateBrushContents_(brushSel){brushSel.selectAll('*').remove();const brushes=this.brushedRange_.isEmpty?[]:[this.brushedRange_];const brushRectsSel=brushSel.selectAll('rect').data(brushes);brushRectsSel.enter().append('rect');brushRectsSel.exit().remove();this.drawBrush_(brushRectsSel);},drawBrush_(brushRectsSel){brushRectsSel.attr('x',d=>this.xScale_(d.min)).attr('y',0).attr('width',d=>this.xScale_(d.max)-this.xScale_(d.min)).attr('height',this.graphHeight).attr('fill','rgb(213, 236, 229)');}};return{ChartBase2DBrushX,};});'use strict';tr.exportTo('tr.ui.b',function(){const ColumnChart=tr.ui.b.define('column-chart',tr.ui.b.ChartBase2DBrushX);ColumnChart.prototype={__proto__:tr.ui.b.ChartBase2DBrushX.prototype,decorate(){super.decorate();this.xCushion_=1;this.isStacked_=false;this.isGrouped_=false;this.enableHoverBox=true;this.displayXInHover=false;this.enableToolTip=false;this.toolTipCallBack_=()=>{};},set toolTipCallBack(callback){this.toolTipCallBack_=callback;},get toolTipCallBack(){return this.toolTipCallBack_;},set isGrouped(grouped){this.isGrouped_=grouped;if(grouped){this.getDataSeries('group').color='transparent';}
+this.updateContents_();},get isGrouped(){return this.isGrouped_;},set isStacked(stacked){this.isStacked_=true;this.updateContents_();},get isStacked(){return this.isStacked_;},get defaultGraphHeight(){return 100;},get defaultGraphWidth(){return 10*this.data_.length;},updateScales_(){if(this.data_.length===0)return;let xDifferences=0;let currentX=undefined;let previousX=undefined;this.data_.forEach(function(datum,index){previousX=currentX;currentX=this.getXForDatum_(datum,index);if(previousX!==undefined){xDifferences+=currentX-previousX;}},this);this.xScale_.range([0,this.graphWidth]);const domain=d3.extent(this.data_,this.getXForDatum_.bind(this));if(this.data_.length>1){this.xCushion_=xDifferences/(this.data_.length-1);}
 this.xScale_.domain([domain[0],domain[1]+this.xCushion_]);this.yScale_.range([this.graphHeight,0]);this.yScale_.domain(this.getYScaleDomain_(this.dataRange.min,this.dataRange.max));},updateDataRange_(){if(!this.isStacked){super.updateDataRange_();return;}
-this.autoDataRange_.reset();this.autoDataRange_.addValue(0);for(const datum of this.data_){let sum=0;for(const[key,series]of this.seriesByKey_){if(datum[key]===undefined){continue;}
+this.autoDataRange_.reset();this.autoDataRange_.addValue(0);for(const datum of this.data_){let sum=0;for(const[key,series]of this.seriesByKey_){if(datum[key]===undefined){continue;}else if(this.isGrouped&&key==='group'){continue;}
 sum+=datum[key];}
-this.autoDataRange_.addValue(sum);}},getStackedRectsForDatum_(datum,index){const stacks=[];let bottom=this.yScale_.range()[0];let sum=0;for(const[key,series]of this.seriesByKey_){if(datum[key]===undefined||!this.isSeriesEnabled(key)){continue;}
+this.autoDataRange_.addValue(sum);}},getStackedRectsForDatum_(datum,index){const stacks=[];let bottom=this.yScale_.range()[0];let sum=0;for(const[key,series]of this.seriesByKey_){if(datum[key]===undefined||!this.isSeriesEnabled(key)){continue;}else if(this.isGrouped&&key==='group'){continue;}
 sum+=this.dataRange.clamp(datum[key]);const heightPx=bottom-this.yScale_(sum);bottom-=heightPx;stacks.push({key,value:datum[key],color:this.getDataSeries(key).color,heightPx,topPx:bottom,underflow:sum<this.dataRange.min,overflow:sum>this.dataRange.max,});}
 return stacks;},getRectsForDatum_(datum,index){if(this.isStacked){return this.getStackedRectsForDatum_(datum,index);}
 const stacks=[];for(const[key,series]of this.seriesByKey_){if(datum[key]===undefined||!this.isSeriesEnabled(key)){continue;}
 const clampedValue=this.dataRange.clamp(datum[key]);const topPx=this.yScale_(Math.max(clampedValue,this.getYScaleMin_()));stacks.push({key,value:datum[key],topPx,heightPx:this.yScale_.range()[0]-topPx,color:this.getDataSeries(key).color,underflow:datum[key]<this.dataRange.min,overflow:datum[key]>this.dataRange.max,});}
-stacks.sort(function(a,b){return b.topPx-a.topPx;});return stacks;},drawHoverValueBox_(rect){const rectHoverEvent=new tr.b.Event('rect-mouseenter');rectHoverEvent.rect=rect;this.dispatchEvent(rectHoverEvent);if(!this.enableHoverBox)return;const seriesKeys=[...this.seriesByKey_.keys()];const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.hover').remove();let keyWidthPx=0;let keyHeightPx=0;if(seriesKeys.length>1){keyWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.key).width+5;keyHeightPx=this.textHeightPx_;}
+stacks.sort(function(a,b){return b.topPx-a.topPx;});return stacks;},drawToolTip_(rect){if(!this.enableToolTip)return;const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.tooltip').remove();const labelText='View Breakdown';const labelWidth=tr.ui.b.getSVGTextSize(this.chartAreaElement,labelText).width+5;const labelHeight=this.textHeightPx_;const toolTipLeftPx=rect.leftPx+(rect.widthPx/2);const toolTipTopPx=rect.topPx;chartAreaSel.append('rect').attr('class','tooltip').attr('fill','white').attr('opacity',0.8).attr('stroke','black').attr('x',toolTipLeftPx).attr('y',toolTipTopPx).attr('width',labelWidth+5).attr('height',labelHeight+10);chartAreaSel.append('text').style('cursor','pointer').attr('class','tooltip').on('mousedown',()=>this.toolTipCallBack_(rect)).attr('fill','blue').attr('x',toolTipLeftPx+4).attr('y',toolTipTopPx+labelHeight).attr('text-decoration','underline').text(labelText);},drawHoverValueBox_(rect){const rectHoverEvent=new tr.b.Event('rect-mouseenter');rectHoverEvent.rect=rect;this.dispatchEvent(rectHoverEvent);if(!this.enableHoverBox)return;const seriesKeys=[...this.seriesByKey_.keys()];const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.hover').remove();let keyWidthPx=0;let keyHeightPx=0;if(seriesKeys.length>1&&!this.isGrouped){keyWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.key).width+5;keyHeightPx=this.textHeightPx_;}
 let xLabelWidthPx=0;let xLabelHeightPx=0;if(this.displayXInHover){xLabelWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.datum.x).width+5;xLabelHeightPx=this.textHeightPx_;}
-let value=rect.value;if(this.unit)value=this.unit.format(value);const valueWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,value).width+5;const valueHeightPx=this.textHeightPx_;const hoverWidthPx=Math.max(keyWidthPx,valueWidthPx,xLabelWidthPx);let hoverLeftPx=rect.leftPx+(rect.widthPx/2);hoverLeftPx=Math.max(hoverLeftPx-hoverWidthPx,-this.margin.left);const hoverHeightPx=keyHeightPx+valueHeightPx+xLabelHeightPx+2;let hoverTopPx=rect.topPx;hoverTopPx=Math.min(hoverTopPx,this.getBoundingClientRect().height-hoverHeightPx-12);chartAreaSel.append('rect').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).attr('fill','white').attr('stroke','black').attr('x',hoverLeftPx).attr('y',hoverTopPx).attr('width',hoverWidthPx).attr('height',hoverHeightPx);if(seriesKeys.length>1){chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx-2).text(rect.key);}
-if(this.displayXInHover){chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx+xLabelHeightPx-2).text(rect.datum.x);}
-chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+hoverHeightPx-2).text(value);},clearHoverValueBox_(rect){const event=window.event;if(event.relatedTarget&&Array.from(event.relatedTarget.classList).includes('hover')){return;}
-const rectHoverEvent=new tr.b.Event('rect-mouseleave');rectHoverEvent.rect=rect;this.dispatchEvent(rectHoverEvent);d3.select(this.chartAreaElement).selectAll('.hover').remove();},drawRect_(rect,sel){sel=sel.data([rect]);sel.enter().append('rect').attr('fill',rect.color).attr('x',rect.leftPx).attr('y',rect.topPx).attr('width',rect.widthPx).attr('height',rect.heightPx).on('mouseenter',this.drawHoverValueBox_.bind(this,rect)).on('mouseleave',this.clearHoverValueBox_.bind(this,rect));sel.exit().remove();},drawUnderflow_(rect,sel){sel=sel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',rect.leftPx+(rect.widthPx/2)).attr('y',this.graphHeight).on('mouseenter',this.drawHoverValueBox_.bind(this,rect)).on('mouseleave',this.clearHoverValueBox_.bind(this,rect));sel.exit().remove();},drawOverflow_(rect,sel){sel=sel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',rect.leftPx+(rect.widthPx/2)).attr('y',0);sel.exit().remove();},updateDataContents_(dataSel){dataSel.selectAll('*').remove();const chartAreaSel=d3.select(this.chartAreaElement);const seriesKeys=[...this.seriesByKey_.keys()];const rectsSel=dataSel.selectAll('path');this.data_.forEach(function(datum,index){const currentX=this.getXForDatum_(datum,index);let width=undefined;if(index<(this.data_.length-1)){const nextX=this.getXForDatum_(this.data_[index+1],index+1);width=nextX-currentX;}else{width=this.xCushion_;}
+let groupWidthPx=0;let groupHeightPx=0;if(this.isGrouped&&rect.datum.group!==undefined){groupWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.datum.group).width+5;groupHeightPx=this.textHeightPx_;}
+let value=rect.value;if(this.unit)value=this.unit.format(value);const valueWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,value).width+5;const valueHeightPx=this.textHeightPx_;const hoverWidthPx=Math.max(keyWidthPx,valueWidthPx,xLabelWidthPx,groupWidthPx);let hoverLeftPx=rect.leftPx+(rect.widthPx/2);hoverLeftPx=Math.max(hoverLeftPx-hoverWidthPx,-this.margin.left);const hoverHeightPx=keyHeightPx+valueHeightPx+
+xLabelHeightPx+groupHeightPx+2;const topOffSetPx=this.isGrouped?36:12;let hoverTopPx=rect.topPx;hoverTopPx=Math.min(hoverTopPx,this.getBoundingClientRect().height-
+hoverHeightPx-topOffSetPx);chartAreaSel.append('rect').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).on('mousedown',this.drawToolTip_.bind(this,rect)).attr('fill','white').attr('stroke','black').attr('x',hoverLeftPx).attr('y',hoverTopPx).attr('width',hoverWidthPx).attr('height',hoverHeightPx);if(seriesKeys.length>1&&!this.isGrouped){chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).on('mousedown',this.drawToolTip_.bind(this,rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx-2).text(rect.key);}
+if(this.displayXInHover){chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).on('mousedown',this.drawToolTip_.bind(this,rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx+xLabelHeightPx-2).text(rect.datum.x);}
+if(this.isGrouped&&rect.datum.group!==undefined){chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).on('mousedown',this.drawToolTip_.bind(this,rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx+
+xLabelHeightPx+groupHeightPx-2).text(rect.datum.group);}
+chartAreaSel.append('text').attr('class','hover').on('mouseleave',()=>this.clearHoverValueBox_(rect)).on('mousedown',this.drawToolTip_.bind(this,rect)).attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+hoverHeightPx-2).text(value);},clearHoverValueBox_(rect){const event=window.event;if(event.relatedTarget&&Array.from(event.relatedTarget.classList).includes('hover')){return;}
+const rectHoverEvent=new tr.b.Event('rect-mouseleave');rectHoverEvent.rect=rect;this.dispatchEvent(rectHoverEvent);d3.select(this.chartAreaElement).selectAll('.hover').remove();},drawRect_(rect,sel){sel=sel.data([rect]);sel.enter().append('rect').attr('fill',rect.color).attr('x',rect.leftPx).attr('y',rect.topPx).attr('width',rect.widthPx).attr('height',rect.heightPx).on('mousedown',this.drawToolTip_.bind(this,rect)).on('mouseenter',this.drawHoverValueBox_.bind(this,rect)).on('mouseleave',this.clearHoverValueBox_.bind(this,rect));sel.exit().remove();},drawUnderflow_(rect,sel){sel=sel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',rect.leftPx+(rect.widthPx/2)).attr('y',this.graphHeight).on('mousedown',this.drawToolTip_.bind(this,rect)).on('mouseenter',this.drawHoverValueBox_.bind(this,rect)).on('mouseleave',this.clearHoverValueBox_.bind(this,rect));sel.exit().remove();},drawOverflow_(rect,sel){sel=sel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',rect.leftPx+(rect.widthPx/2)).attr('y',0);sel.exit().remove();},updateDataContents_(dataSel){dataSel.selectAll('*').remove();const chartAreaSel=d3.select(this.chartAreaElement);const seriesKeys=[...this.seriesByKey_.keys()];const rectsSel=dataSel.selectAll('path');this.data_.forEach(function(datum,index){const currentX=this.getXForDatum_(datum,index);let width=undefined;if(index<(this.data_.length-1)){const nextX=this.getXForDatum_(this.data_[index+1],index+1);width=nextX-currentX;}else{width=this.xCushion_;}
 for(const rect of this.getRectsForDatum_(datum,index)){rect.datum=datum;rect.index=index;rect.leftPx=this.xScale_(currentX);rect.rightPx=this.xScale_(currentX+width);rect.widthPx=rect.rightPx-rect.leftPx;this.drawRect_(rect,rectsSel);if(rect.underflow){this.drawUnderflow_(rect,rectsSel);}
 if(rect.overflow){this.drawOverflow_(rect,rectsSel);}}},this);}};return{ColumnChart,};});'use strict';tr.exportTo('tr.ui.b',function(){const LineChart=tr.ui.b.define('line-chart',tr.ui.b.ChartBase2DBrushX);LineChart.prototype={__proto__:tr.ui.b.ChartBase2DBrushX.prototype,decorate(){super.decorate();this.enableHoverBox=true;this.displayXInHover=false;},get defaultGraphWidth(){return 20*this.data_.length;},get defaultGraphHeight(){return 100;},drawHoverValueBox_(circle){tr.ui.b.ColumnChart.prototype.drawHoverValueBox_.call(this,circle);},clearHoverValueBox_(circle){tr.ui.b.ColumnChart.prototype.clearHoverValueBox_.call(this,circle);},updateDataContents_(dataSel){dataSel.selectAll('*').remove();const dataBySeriesKey=this.getDataBySeriesKey_();const seriesKeys=[...this.seriesByKey_.keys()];const pathsSel=dataSel.selectAll('path').data(seriesKeys);pathsSel.enter().append('path').style('fill','none').style('stroke-width','1.5px').style('stroke',key=>this.getDataSeries(key).color).attr('d',key=>{const line=d3.svg.line().x(d=>this.xScale_(d.x)).y(d=>this.yScale_(this.dataRange.clamp(d[key])));return line(dataBySeriesKey[key]);});pathsSel.exit().remove();if(this.enableHoverBox){for(let index=0;index<this.data_.length;++index){const datum=this.data_[index];const x=this.getXForDatum_(datum,index);for(const[key,value]of Object.entries(datum)){if(key==='x')continue;if(value===undefined)continue;const color=this.getDataSeries(key).color;const circle=document.createElementNS('http://www.w3.org/2000/svg','circle');circle.setAttribute('cx',this.xScale_(x));circle.setAttribute('cy',this.yScale_(this.dataRange.clamp(value)));circle.setAttribute('r',5);circle.style.fill=color;circle.datum=datum;circle.key=key;circle.value=datum[key];circle.leftPx=this.xScale_(x);circle.widthPx=0;circle.color=color;circle.topPx=this.yScale_(this.dataRange.clamp(value));circle.heightPx=0;circle.addEventListener('mouseenter',()=>this.drawHoverValueBox_(circle));circle.addEventListener('mouseleave',()=>this.clearHoverValueBox_(circle));dataSel[0][0].appendChild(circle);}}}}};return{LineChart,};});'use strict';Polymer({is:'tr-ui-e-s-input-latency-side-panel',behaviors:[tr.ui.behaviors.SidePanel],ready(){this.rangeOfInterest_=new tr.b.math.Range();this.frametimeType_=tr.model.helpers.IMPL_FRAMETIME_TYPE;this.latencyChart_=undefined;this.frametimeChart_=undefined;this.selectedProcessId_=undefined;this.mouseDownIndex_=undefined;this.curMouseIndex_=undefined;},get model(){return this.model_;},set model(model){this.model_=model;if(this.model_){this.modelHelper_=this.model_.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);}else{this.modelHelper_=undefined;}
 this.updateToolbar_();this.updateContents_();},get frametimeType(){return this.frametimeType_;},set frametimeType(type){if(this.frametimeType_===type)return;this.frametimeType_=type;this.updateContents_();},get selectedProcessId(){return this.selectedProcessId_;},set selectedProcessId(process){if(this.selectedProcessId_===process)return;this.selectedProcessId_=process;this.updateContents_();},set selection(selection){if(this.latencyChart_===undefined)return;this.latencyChart_.brushedRange=selection.bounds;},setBrushedIndices(mouseDownIndex,curIndex){this.mouseDownIndex_=mouseDownIndex;this.curMouseIndex_=curIndex;this.updateBrushedRange_();},updateBrushedRange_(){if(this.latencyChart_===undefined)return;let r=new tr.b.math.Range();if(this.mouseDownIndex_===undefined){this.latencyChart_.brushedRange=r;return;}
@@ -7734,7 +7865,7 @@
 if(frametimeData.length!==0){this.frametimeChart_=this.createLatencyLineChart(frametimeData,'Frame Times',resultArea);}},get rangeOfInterest(){return this.rangeOfInterest_;},set rangeOfInterest(rangeOfInterest){this.rangeOfInterest_=rangeOfInterest;this.updateContents_();},supportsModel(m){if(m===undefined){return{supported:false,reason:'Unknown tracing model'};}
 if(!tr.model.helpers.ChromeModelHelper.supportsModel(m)){return{supported:false,reason:'No Chrome browser or renderer process found'};}
 const modelHelper=m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(modelHelper.browserHelper&&modelHelper.browserHelper.hasLatencyEvents){return{supported:true};}
-return{supported:false,reason:'No InputLatency events trace. Consider enabling '+'benchmark" and "input" category when recording the trace'};},get textLabel(){return'Input Latency';}});tr.ui.side_panel.SidePanelRegistry.register(function(){return document.createElement('tr-ui-e-s-input-latency-side-panel');});'use strict';tr.exportTo('tr.e.system_stats',function(){const ObjectSnapshot=tr.model.ObjectSnapshot;function SystemStatsSnapshot(objectInstance,ts,args){ObjectSnapshot.apply(this,arguments);this.objectInstance=objectInstance;this.ts=ts;this.args=args;this.stats=args;}
+return{supported:false,reason:'No InputLatency events trace. Consider enabling '+'benchmark" and "input" category when recording the trace'};},get textLabel(){return'Input Latency';}});tr.ui.side_panel.SidePanelRegistry.register(function(){return document.createElement('tr-ui-e-s-input-latency-side-panel');});'use strict';tr.exportTo('tr.e.system_stats',function(){const ObjectSnapshot=tr.model.ObjectSnapshot;function SystemStatsSnapshot(objectInstance,ts,args){ObjectSnapshot.apply(this,arguments);this.objectInstance=objectInstance;this.ts=ts;this.args=args;this.stats_=args;}
 SystemStatsSnapshot.prototype={__proto__:ObjectSnapshot.prototype,initialize(){if(this.args.length===0){throw new Error('No system stats snapshot data.');}
 this.stats_=this.args;},getStats(){return this.stats_;},setStats(stats){this.stats_=stats;}};ObjectSnapshot.subTypes.register(SystemStatsSnapshot,{typeName:'base::TraceEventSystemStatsMonitor::SystemStats'});return{SystemStatsSnapshot,};});'use strict';tr.exportTo('tr.ui.b',function(){const constants={HEADING_WIDTH:250};return{constants,};});'use strict';Polymer({is:'tr-ui-b-heading',DOWN_ARROW:String.fromCharCode(0x25BE),RIGHT_ARROW:String.fromCharCode(0x25B8),ready(viewport){this.style.width=(tr.ui.b.constants.HEADING_WIDTH-6)+'px';this.heading_='';this.expanded_=true;this.arrowVisible_=false;this.selectionGenerator_=undefined;this.updateContents_();},get heading(){return this.heading_;},set heading(text){if(this.heading_===text)return;this.heading_=text;this.updateContents_();},set arrowVisible(val){if(this.arrowVisible_===val)return;this.arrowVisible_=!!val;this.updateContents_();},set tooltip(text){this.$.heading.title=text;},set selectionGenerator(generator){if(this.selectionGenerator_===generator)return;this.selectionGenerator_=generator;this.updateContents_();},get expanded(){return this.expanded_;},set expanded(expanded){if(this.expanded_===expanded)return;this.expanded_=!!expanded;this.updateContents_();},onHeadingDivClicked_(){this.dispatchEvent(new tr.b.Event('heading-clicked',true));},updateContents_(){if(this.arrowVisible_){this.$.arrow.style.display='';}else{this.$.arrow.style.display='none';this.$.heading.style.display=this.expanded_?'':'none';}
 if(this.arrowVisible_){Polymer.dom(this.$.arrow).textContent=this.expanded_?this.DOWN_ARROW:this.RIGHT_ARROW;}
@@ -7754,7 +7885,7 @@
 return snapshots[i].ts-snapshots[i-1].ts;}
 return snapshots[i+1].ts-snapshots[i].ts;},loWX,hiWX,onSnapshot);},addEventNearToProvidedEventToSelection(event,offset,selection){if(!(event instanceof tr.model.ObjectSnapshot)){throw new Error('Unrecognized event');}
 const objectSnapshots=this.objectInstance_.snapshots;const index=objectSnapshots.indexOf(event);const newIndex=index+offset;if(newIndex>=0&&newIndex<objectSnapshots.length){selection.push(objectSnapshots[newIndex]);return true;}
-return false;},addAllEventsMatchingFilterToSelection(filter,selection){},addClosestEventToSelection(worldX,worldMaxDist,loY,hiY,selection){const snapshot=tr.b.findClosestElementInSortedArray(this.objectInstance_.snapshots,function(x){return x.ts;},worldX,worldMaxDist);if(!snapshot)return;selection.push(snapshot);}};return{StackedBarsTrack,};});'use strict';tr.exportTo('tr.ui.e.system_stats',function(){const EventPresenter=tr.ui.b.EventPresenter;let statCount;const excludedStats={'meminfo':{'pswpin':0,'pswpout':0,'pgmajfault':0},'diskinfo':{'io':0,'io_time':0,'read_time':0,'reads':0,'reads_merged':0,'sectors_read':0,'sectors_written':0,'weighted_io_time':0,'write_time':0,'writes':0,'writes_merged':0},'swapinfo':{}};const SystemStatsInstanceTrack=tr.ui.b.define('tr-ui-e-system-stats-instance-track',tr.ui.tracks.StackedBarsTrack);SystemStatsInstanceTrack.prototype={__proto__:tr.ui.tracks.StackedBarsTrack.prototype,decorate(viewport){tr.ui.tracks.StackedBarsTrack.prototype.decorate.call(this,viewport);Polymer.dom(this).classList.add('tr-ui-e-system-stats-instance-track');this.objectInstance_=null;},set objectInstances(objectInstances){if(!objectInstances){this.objectInstance_=[];return;}
+return false;},addAllEventsMatchingFilterToSelection(filter,selection){},addClosestEventToSelection(worldX,worldMaxDist,loY,hiY,selection){const snapshot=tr.b.findClosestElementInSortedArray(this.objectInstance_.snapshots,function(x){return x.ts;},worldX,worldMaxDist);if(!snapshot)return;selection.push(snapshot);}};return{StackedBarsTrack,};});'use strict';tr.exportTo('tr.ui.e.system_stats',function(){const EventPresenter=tr.ui.b.EventPresenter;let statCount;const excludedStats={'meminfo':{'pswpin':0,'pswpout':0,'pgmajfault':0},'diskinfo':{'io':0,'io_time':0,'read_time':0,'reads':0,'reads_merged':0,'sectors_read':0,'sectors_written':0,'weighted_io_time':0,'write_time':0,'writes':0,'writes_merged':0},'swapinfo':{},'perfinfo':{'idle_time':0,'read_transfer_count':0,'write_transfer_count':0,'other_transfer_count':0,'read_operation_count':0,'write_operation_count':0,'other_operation_count':0,'pagefile_pages_written':0,'pagefile_pages_write_ios':0,'available_pages':0,'pages_read':0,'page_read_ios':0}};const SystemStatsInstanceTrack=tr.ui.b.define('tr-ui-e-system-stats-instance-track',tr.ui.tracks.StackedBarsTrack);const kPageSizeWindows=4096;SystemStatsInstanceTrack.prototype={__proto__:tr.ui.tracks.StackedBarsTrack.prototype,decorate(viewport){tr.ui.tracks.StackedBarsTrack.prototype.decorate.call(this,viewport);Polymer.dom(this).classList.add('tr-ui-e-system-stats-instance-track');this.objectInstance_=null;},set objectInstances(objectInstances){if(!objectInstances){this.objectInstance_=[];return;}
 if(objectInstances.length!==1){throw new Error('Bad object instance count.');}
 this.objectInstance_=objectInstances[0];if(this.objectInstance_!==null){this.computeRates_(this.objectInstance_.snapshots);this.maxStats_=this.computeMaxStats_(this.objectInstance_.snapshots);}},computeRates_(snapshots){for(let i=0;i<snapshots.length;i++){const snapshot=snapshots[i];const stats=snapshot.getStats();let prevSnapshot;if(i===0){prevSnapshot=snapshots[0];}else{prevSnapshot=snapshots[i-1];}
 const prevStats=prevSnapshot.getStats();let timeIntervalSeconds=(snapshot.ts-prevSnapshot.ts)/1000;if(timeIntervalSeconds===0){timeIntervalSeconds=1;}
@@ -7767,7 +7898,27 @@
 if(statName==='pswpin'){stats.bytes_swpin_per_sec=(stats.pswpin-
 prevStats.pswpin)*1000/timeIntervalSeconds;}
 if(statName==='pswpout'){stats.bytes_swpout_per_sec=(stats.pswpout-
-prevStats.pswpout)*1000/timeIntervalSeconds;}}}},computeMaxStats_(snapshots){const maxStats={};statCount=0;for(let i=0;i<snapshots.length;i++){const snapshot=snapshots[i];const stats=snapshot.getStats();this.computeMaxStatsRecursive_(stats,maxStats,excludedStats);}
+prevStats.pswpout)*1000/timeIntervalSeconds;}
+if(statName==='idle_time'){const units=tr.b.convertUnit(100.,tr.b.UnitScale.TIME.NANO_SEC,tr.b.UnitScale.TIME.SEC);const idleTile=(stats.idle_time-prevStats.idle_time)*units;stats.idle_time_per_sec=idleTile/timeIntervalSeconds;}
+if(statName==='read_transfer_count'){const bytesRead=stats.read_transfer_count-
+prevStats.read_transfer_count;stats.bytes_read_per_sec=bytesRead/timeIntervalSeconds;}
+if(statName==='write_transfer_count'){const bytesWritten=stats.write_transfer_count-
+prevStats.write_transfer_count;stats.bytes_written_per_sec=bytesWritten/timeIntervalSeconds;}
+if(statName==='other_transfer_count'){const bytesTransfer=stats.other_transfer_count-
+prevStats.other_transfer_count;stats.bytes_other_per_sec=bytesTransfer/timeIntervalSeconds;}
+if(statName==='read_operation_count'){const readOperation=stats.read_operation_count-
+prevStats.read_operation_count;stats.read_operation_per_sec=readOperation/timeIntervalSeconds;}
+if(statName==='write_operation_count'){const writeOperation=stats.write_operation_count-
+prevStats.write_operation_count;stats.write_operation_per_sec=writeOperation/timeIntervalSeconds;}
+if(statName==='other_operation_count'){const otherOperation=stats.other_operation_count-
+prevStats.other_operation_count;stats.other_operation_per_sec=otherOperation/timeIntervalSeconds;}
+if(statName==='pagefile_pages_written'){const pageFileBytesWritten=(stats.pagefile_pages_written-
+prevStats.pagefile_pages_written)*kPageSizeWindows;stats.pagefile_bytes_written_per_sec=pageFileBytesWritten/timeIntervalSeconds;}
+if(statName==='pagefile_pages_write_ios'){const pagefileWriteOperation=stats.pagefile_pages_write_ios-
+prevStats.pagefile_pages_write_ios;stats.pagefile_write_operation_per_sec=pagefileWriteOperation/timeIntervalSeconds;}
+if(statName==='available_pages'){stats.available_pages_in_bytes=stats.available_pages*kPageSizeWindows;}
+if(statName==='pages_read'){const pagesBytesRead=(stats.pages_read-prevStats.pages_read)*kPageSizeWindows;stats.bytes_read_per_sec=pagesBytesRead/timeIntervalSeconds;}
+if(statName==='page_read_ios'){const pagesBytesReadOperations=stats.page_read_ios-prevStats.page_read_ios;stats.pagefile_write_operation_per_sec=pagesBytesReadOperations/timeIntervalSeconds;}}}},computeMaxStats_(snapshots){const maxStats={};statCount=0;for(let i=0;i<snapshots.length;i++){const snapshot=snapshots[i];const stats=snapshot.getStats();this.computeMaxStatsRecursive_(stats,maxStats,excludedStats);}
 return maxStats;},computeMaxStatsRecursive_(stats,maxStats,excludedStats){for(const statName in stats){if(stats[statName]instanceof Object){if(!(statName in maxStats)){maxStats[statName]={};}
 let excludedNested;if(excludedStats&&statName in excludedStats){excludedNested=excludedStats[statName];}else{excludedNested=null;}
 this.computeMaxStatsRecursive_(stats[statName],maxStats[statName],excludedNested);}else{if(excludedStats&&statName in excludedStats){continue;}
@@ -7843,7 +7994,7 @@
 this.updateTable_();if(slices.length>1){this.buildOptions_();this.constructDiffTable_();}},updateTable_(){this.constructTable_();this.$.table.tableRows=this.isolateEntries_;this.$.table.rebuild();},});return{};});'use strict';Polymer({is:'tr-ui-e-multi-v8-gc-stats-thread-slice-sub-view',behaviors:[tr.ui.analysis.AnalysisSubView],get selection(){return this.$.content.selection;},set selection(selection){this.$.gcObjectsStats.selection=selection;}});tr.ui.analysis.AnalysisSubView.register('tr-ui-e-multi-v8-gc-stats-thread-slice-sub-view',tr.e.v8.V8GCStatsThreadSlice,{multi:true,title:'V8 GC Stats slices'});'use strict';tr.exportTo('tr.e.v8',function(){const IC_STATS_PROPERTIES=['type','category','scriptName','filePosition','state','isNative','map','propertiesMode','numberOfOwnProperties','instanceType'];class ICStatsEntry{constructor(obj){this.type_=obj.type;if(this.type_.includes('Store')){this.category_='Store';}else if(this.type_.includes('Load')){this.category_='Load';}
 this.state_=obj.state;if(obj.functionName){this.functionName_=obj.optimized?'*':'~';this.functionName_+=obj.functionName.length===0?'(anonymous function)':obj.functionName;}
 this.offset_=obj.offset;this.scriptName_=obj.scriptName?obj.scriptName:'unknown';this.isNative_=obj.scriptName&&obj.scriptName.includes('native');this.lineNum_=obj.lineNum?obj.lineNum:'unknown';this.filePosition_=this.scriptName_+':'+this.lineNum_;if(this.functionName_){this.filePosition_+=' '+this.functionName_+'+'+this.offset_;}
-this.constructor_=obj.constructor?false:true;this.map_=obj.map;if(this.map_){this.propertiesMode_=obj.dict===0?'slow':'fast';}else{this.propertiesMode_='unknown';}
+this.constructor_=obj.constructor?false:true;this.map_=obj.map;if(this.map_){this.propertiesMode_=obj.dict===1?'slow':'fast';}else{this.propertiesMode_='unknown';}
 this.numberOfOwnProperties_=obj.own;this.instanceType_=obj.instanceType;this.key_=obj.key;}
 get type(){return this.type_;}
 get category(){return this.category_;}
@@ -7890,7 +8041,7 @@
 add(entry){const value=this.entries_.get(entry.name);if(value!==undefined){value.addSample(entry.count,entry.time);}else{this.entries_.set(entry.name,entry);}
 this.count_+=entry.count;this.time_+=entry.time;}
 get values(){return Array.from(this.entries_.values());}}
-class RuntimeStatsGroupCollection{constructor(){this.blink_cpp_group_=new RuntimeStatsGroup('Blink C++',/.*Callback.*/);this.api_group_=new RuntimeStatsGroup('API',/.*API.*/);this.groups_=[new RuntimeStatsGroup('Total'),new RuntimeStatsGroup('IC',/.*IC_.*/),new RuntimeStatsGroup('Optimize',/StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/),new RuntimeStatsGroup('Compile-Background',/(.*CompileBackground.*)/),new RuntimeStatsGroup('Compile',/(^Compile.*)|(.*_Compile.*)/),new RuntimeStatsGroup('Parse-Background',/.*ParseBackground.*/),new RuntimeStatsGroup('Parse',/.*Parse.*/),this.blink_cpp_group_,this.api_group_,new RuntimeStatsGroup('GC-Background-Marking',/.*GC.MC.BACKGROUND.*MARKING.*/),new RuntimeStatsGroup('GC-Background-Sweeping',/.*GC.MC.BACKGROUND.*SWEEPING.*/),new RuntimeStatsGroup('GC-Background-Scavenger',/.*GC.SCAVENGER.BACKGROUND.*/),new RuntimeStatsGroup('GC-Background-MinorMC',/.*GC.MINOR_MC.BACKGROUND.*/),new RuntimeStatsGroup('GC-Background-MajorMC',/.*GC.MC.BACKGROUND.*/),new RuntimeStatsGroup('GC-Background-Other',/.*GC.*BACKGROUND.*/),new RuntimeStatsGroup('GC',/GC|AllocateInTargetSpace/),new RuntimeStatsGroup('JavaScript',/JS_Execution/),new RuntimeStatsGroup('V8 C++',/.*/)];this.blink_group_collection_=null;}
+class RuntimeStatsGroupCollection{constructor(){this.blink_cpp_group_=new RuntimeStatsGroup('Blink C++',/.*Callback.*/);this.api_group_=new RuntimeStatsGroup('API',/.*API.*/);this.groups_=[new RuntimeStatsGroup('Total'),new RuntimeStatsGroup('IC',/.*IC_.*/),new RuntimeStatsGroup('Optimize-Background',/(.*OptimizeConcurrent.*)|RecompileConcurrent.*/),new RuntimeStatsGroup('Optimize',/StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/),new RuntimeStatsGroup('Compile-Background',/(.*CompileBackground.*)/),new RuntimeStatsGroup('Compile',/(^Compile.*)|(.*_Compile.*)/),new RuntimeStatsGroup('Parse-Background',/.*ParseBackground.*/),new RuntimeStatsGroup('Parse',/.*Parse.*/),this.blink_cpp_group_,this.api_group_,new RuntimeStatsGroup('GC-Background-Marking',/.*GC.MC.BACKGROUND.*MARKING.*/),new RuntimeStatsGroup('GC-Background-Sweeping',/.*GC.MC.BACKGROUND.*SWEEPING.*/),new RuntimeStatsGroup('GC-Background-Scavenger',/.*GC.SCAVENGER.BACKGROUND.*/),new RuntimeStatsGroup('GC-Background-MinorMC',/.*GC.MINOR_MC.BACKGROUND.*/),new RuntimeStatsGroup('GC-Background-MajorMC',/.*GC.MC.BACKGROUND.*/),new RuntimeStatsGroup('GC-Background-Other',/.*GC.*BACKGROUND.*/),new RuntimeStatsGroup('GC',/GC|AllocateInTargetSpace/),new RuntimeStatsGroup('JavaScript',/JS_Execution/),new RuntimeStatsGroup('V8 C++',/.*/)];this.blink_group_collection_=null;}
 addSlices(slices){const blinkEntries=[];for(const slice of slices){if(!(slice instanceof tr.e.v8.V8ThreadSlice))return;let runtimeCallStats;try{runtimeCallStats=JSON.parse(slice.runtimeCallStats);}catch(e){runtimeCallStats=slice.runtimeCallStats;}
 if(runtimeCallStats===undefined)continue;for(const[name,stat]of Object.entries(runtimeCallStats)){if(name.match(/Blink_.*/)){if(name==='Blink_V8')continue;const entry=new RuntimeStatsEntry(name,stat[0],stat[1]);blinkEntries.push(entry);continue;}
 for(let i=1;i<this.groups_.length;++i){if(this.groups_[i].match(name)){if(stat.length!==2)break;const entry=new RuntimeStatsEntry(name,stat[0],stat[1]);this.groups_[0].addSample(stat[0],stat[1]);this.groups_[i].add(entry);break;}}}}
@@ -7934,12 +8085,14 @@
 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.BrowserMessageLoopStartTimeFromMainEntry3';const NAVIGATION_TIME_TO_NETWORK_STACK_EVENT_NAME='Navigation timeToNetworkStack';const FIRST_CONTENTFUL_PAINT_EVENT_NAME='firstContentfulPaint';function androidStartupMetric(histograms,model){const browserStartTimestamps=[];const requestStartTimestampsAndEvents=[];const firstContentfulPaintTimestampsAndEvents=[];const messageLoopStartHistogram=histograms.createHistogram('messageloop_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const requestStartHistogram=histograms.createHistogram('request_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);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){messageLoopStartHistogram.addSample(ev.duration,{events:new tr.v.d.RelatedEventSet([ev])});browserStartTimestamps.push(ev.start);}
-if(ev.title===NAVIGATION_TIME_TO_NETWORK_STACK_EVENT_NAME){requestStartTimestampsAndEvents.push({timestamp:ev.end,event:new tr.v.d.RelatedEventSet([ev])});}}}
-const firstContentfulPaintHistogram=histograms.createHistogram('first_contentful_paint_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const rendererHelpers=chromeHelper.rendererHelpers;const pids=Object.keys(rendererHelpers);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(ev.title===FIRST_CONTENTFUL_PAINT_EVENT_NAME){firstContentfulPaintTimestampsAndEvents.push({timestamp:ev.end,event:new tr.v.d.RelatedEventSet([ev])});break;}}}
-const totalRequestStarts=requestStartTimestampsAndEvents.length;const totalBrowserStarts=browserStartTimestamps.length;if(totalRequestStarts!==totalBrowserStarts){throw new Error('Number of request starts ('+totalRequestStarts+') differs from number of browser starts ('+totalBrowserStarts+')');}
-const totalFcpTimestamps=firstContentfulPaintTimestampsAndEvents.length;if(totalFcpTimestamps!==totalBrowserStarts){throw new Error('Number of FCP events ('+totalFcpTimestamps+') differs from number of browser starts ('+totalBrowserStarts+')');}
-for(let i=0;i<totalBrowserStarts;i++){const browserStart=browserStartTimestamps[i];const requestStartInfo=requestStartTimestampsAndEvents[i];requestStartHistogram.addSample(requestStartInfo.timestamp-browserStart,{events:requestStartInfo.event});const fcpInfo=firstContentfulPaintTimestampsAndEvents[i];firstContentfulPaintHistogram.addSample(fcpInfo.timestamp-browserStart,{events:fcpInfo.event});}}
+tr.metrics.MetricRegistry.register(accessibilityMetric);return{accessibilityMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const MESSAGE_LOOP_EVENT_NAME='Startup.BrowserMessageLoopStartTimeFromMainEntry3';const FIRST_CONTENTFUL_PAINT_EVENT_NAME='firstContentfulPaint';function androidStartupMetric(histograms,model){let messageLoopStartEvents=[];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);}}}
+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;}}}
+let totalBrowserStarts=messageLoopStartEvents.length;let totalFcpEvents=firstContentfulPaintEvents.length;if(totalFcpEvents!==totalBrowserStarts||totalBrowserStarts===0){messageLoopStartEvents=[];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);}}
+for(const ev of proc.getDescendantEvents()){if(ev.title===FIRST_CONTENTFUL_PAINT_EVENT_NAME){firstContentfulPaintEvents.push(ev);break;}}}
+totalBrowserStarts=messageLoopStartEvents.length;totalFcpEvents=firstContentfulPaintEvents.length;}
+if(totalFcpEvents!==totalBrowserStarts){throw new Error('Number of FCP events ('+totalFcpEvents+') differs from number of browser starts ('+totalBrowserStarts+')');}
+const messageLoopStartHistogram=histograms.createHistogram('messageloop_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const firstContentfulPaintHistogram=histograms.createHistogram('first_contentful_paint_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);function orderEvents(event1,event2){return event1.start-event2.start;}
+messageLoopStartEvents.sort(orderEvents);firstContentfulPaintEvents.sort(orderEvents);for(let i=2;i<totalBrowserStarts;i++){const startEvent=messageLoopStartEvents[i];messageLoopStartHistogram.addSample(startEvent.duration,{events:new tr.v.d.RelatedEventSet([startEvent])});const fcpEvent=firstContentfulPaintEvents[i];firstContentfulPaintHistogram.addSample(fcpEvent.end-startEvent.start,{events:new tr.v.d.RelatedEventSet([startEvent,fcpEvent])});}}
 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);}}
@@ -7966,7 +8119,7 @@
 if(x1<x2){this.pieces.push(new Piece(x1,y1,x2,y2));}},partBelow(y){return this.pieces.reduce((acc,p)=>(acc+p.partBelow(y)),0);},get min(){return this.pieces.reduce((acc,p)=>Math.min(acc,p.min),Infinity);},get max(){return this.pieces.reduce((acc,p)=>Math.max(acc,p.max),-Infinity);},get average(){let weightedSum=0;let totalWeight=0;this.pieces.forEach(function(piece){weightedSum+=piece.width*piece.average;totalWeight+=piece.width;});if(totalWeight===0)return 0;return weightedSum/totalWeight;},percentile(percent){if(!(percent>=0&&percent<=1)){throw new Error('percent must be [0,1]');}
 let lower=this.min;let upper=this.max;const total=this.partBelow(upper);if(total===0)return 0;while(upper-lower>PERCENTILE_PRECISION){const middle=(lower+upper)/2;const below=this.partBelow(middle);if(below/total<percent){lower=middle;}else{upper=middle;}}
 return(lower+upper)/2;}};function Piece(x1,y1,x2,y2){this.x1=x1;this.y1=y1;this.x2=x2;this.y2=y2;}
-Piece.prototype={partBelow(y){const width=this.width;if(width===0)return 0;const minY=this.min;const maxY=this.max;if(y>=maxY)return width;if(y<minY)return 0;return(y-minY)/(maxY-minY)*width;},get min(){return Math.min(this.y1,this.y2);},get max(){return Math.max(this.y1,this.y2);},get average(){return(this.y1+this.y2)/2;},get width(){return this.x2-this.x1;}};return{PiecewiseLinearFunction,};});'use strict';tr.exportTo('tr.metrics.v8.utils',function(){const IDLE_TASK_EVENT='SingleThreadIdleTaskRunner::RunTask';const V8_EXECUTE='V8.Execute';const GC_EVENT_PREFIX='V8.GC';const FULL_GC_EVENT='V8.GCCompactor';const LOW_MEMORY_EVENT='V8.GCLowMemoryNotification';const MAJOR_GC_EVENT='MajorGC';const MINOR_GC_EVENT='MinorGC';const TOP_GC_EVENTS={'V8.GCCompactor':'v8-gc-full-mark-compactor','V8.GCFinalizeMC':'v8-gc-latency-mark-compactor','V8.GCFinalizeMCReduceMemory':'v8-gc-memory-mark-compactor','V8.GCIncrementalMarking':'v8-gc-incremental-step','V8.GCIncrementalMarkingFinalize':'v8-gc-incremental-finalize','V8.GCIncrementalMarkingStart':'v8-gc-incremental-start','V8.GCPhantomHandleProcessingCallback':'v8-gc-phantom-handle-callback','V8.GCScavenger':'v8-gc-scavenger'};const LOW_MEMORY_MARK_COMPACTOR='v8-gc-low-memory-mark-compactor';function findParent(event,predicate){let parent=event.parentSlice;while(parent){if(predicate(parent)){return parent;}
+Piece.prototype={partBelow(y){const width=this.width;if(width===0)return 0;const minY=this.min;const maxY=this.max;if(y>=maxY)return width;if(y<minY)return 0;return(y-minY)/(maxY-minY)*width;},get min(){return Math.min(this.y1,this.y2);},get max(){return Math.max(this.y1,this.y2);},get average(){return(this.y1+this.y2)/2;},get width(){return this.x2-this.x1;}};return{PiecewiseLinearFunction,};});'use strict';tr.exportTo('tr.metrics.v8.utils',function(){const IDLE_TASK_EVENT='SingleThreadIdleTaskRunner::RunTask';const V8_EXECUTE='V8.Execute';const GC_EVENT_PREFIX='V8.GC';const FULL_GC_EVENT='V8.GCCompactor';const LOW_MEMORY_EVENT='V8.GCLowMemoryNotification';const MAJOR_GC_EVENT='MajorGC';const MINOR_GC_EVENT='MinorGC';const TOP_GC_EVENTS={'V8.GCCompactor':'v8-gc-full-mark-compactor','V8.GCFinalizeMC':'v8-gc-latency-mark-compactor','V8.GCFinalizeMCReduceMemory':'v8-gc-memory-mark-compactor','V8.GCIncrementalMarking':'v8-gc-incremental-step','V8.GCIncrementalMarkingFinalize':'v8-gc-incremental-finalize','V8.GCIncrementalMarkingStart':'v8-gc-incremental-start','V8.GCPhantomHandleProcessingCallback':'v8-gc-phantom-handle-callback','V8.GCScavenger':'v8-gc-scavenger'};const MARK_COMPACTOR_EVENTS=new Set(['V8.GCCompactor','V8.GCFinalizeMC','V8.GCFinalizeMCReduceMemory','V8.GCIncrementalMarking','V8.GCIncrementalMarkingFinalize','V8.GCIncrementalMarkingStart','V8.GCPhantomHandleProcessingCallback']);const LOW_MEMORY_MARK_COMPACTOR='v8-gc-low-memory-mark-compactor';function findParent(event,predicate){let parent=event.parentSlice;while(parent){if(predicate(parent)){return parent;}
 parent=parent.parentSlice;}
 return null;}
 function isIdleTask(event){return event.title===IDLE_TASK_EVENT;}
@@ -7978,7 +8131,11 @@
 function isTopGarbageCollectionEvent(event){return event.title in TOP_GC_EVENTS;}
 function isForcedGarbageCollectionEvent(event){return findParent(event,isLowMemoryEvent)!==null;}
 function isSubGarbageCollectionEvent(event){return isGarbageCollectionEvent(event)&&event.parentSlice&&(isTopGarbageCollectionEvent(event.parentSlice)||event.parentSlice.title===MAJOR_GC_EVENT||event.parentSlice.title===MINOR_GC_EVENT);}
+function isNotForcedTopGarbageCollectionEvent(event){return tr.metrics.v8.utils.isTopGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}
+function isNotForcedSubGarbageCollectionEvent(event){return tr.metrics.v8.utils.isSubGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}
 function isFullMarkCompactorEvent(event){return event.title==='V8.GCCompactor';}
+function isMarkCompactorSummaryEvent(event){return event.title==='V8.GCMarkCompactorSummary';}
+function isMarkCompactorMarkingSummaryEvent(event){return event.title==='V8.GCMarkCompactorMarkingSummary';}
 function isIncrementalMarkingEvent(event){return event.title.startsWith('V8.GCIncrementalMarking');}
 function isLatencyMarkCompactorEvent(event){return event.title==='V8.GCFinalizeMC';}
 function isMemoryMarkCompactorEvent(event){return event.title==='V8.GCFinalizeMCReduceMemory';}
@@ -7988,6 +8145,9 @@
 function isCompileParseRCSCategory(name){return name==='Parse';}
 function isCompileRCSCategory(name){return name==='Compile'||name==='Optimize'||name==='Parse';}
 function isV8RCSEvent(event){return event instanceof tr.e.v8.V8ThreadSlice;}
+function isMarkCompactorEvent(event){return MARK_COMPACTOR_EVENTS.has(event.title);}
+function isNotForcedMarkCompactorEvent(event){return!isForcedGarbageCollectionEvent(event)&&isMarkCompactorEvent(event);}
+function forcedGCEventName(){return LOW_MEMORY_EVENT;}
 function topGarbageCollectionEventName(event){if(event.title===FULL_GC_EVENT){if(findParent(event,isLowMemoryEvent)){return LOW_MEMORY_MARK_COMPACTOR;}}
 return TOP_GC_EVENTS[event.title];}
 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;}
@@ -7996,19 +8156,39 @@
 function unionOfIntervals(intervals){if(intervals.length===0)return[];return tr.b.math.mergeRanges(intervals.map(x=>{return{min:x.start,max:x.end};}),1e-6,function(ranges){return{start:ranges.reduce((acc,x)=>Math.min(acc,x.min),ranges[0].min),end:ranges.reduce((acc,x)=>Math.max(acc,x.max),ranges[0].max)};});}
 function hasV8Stats(globalMemoryDump){let v8stats=undefined;globalMemoryDump.iterateContainerDumps(function(dump){v8stats=v8stats||dump.getMemoryAllocatorDumpByFullName('v8');});return!!v8stats;}
 function rangeForMemoryDumps(model){const startOfFirstDumpWithV8=model.globalMemoryDumps.filter(hasV8Stats).reduce((start,dump)=>Math.min(start,dump.start),Infinity);if(startOfFirstDumpWithV8===Infinity)return new tr.b.math.Range();return tr.b.math.Range.fromExplicitRange(startOfFirstDumpWithV8,Infinity);}
-return{findParent,groupAndProcessEvents,isForcedGarbageCollectionEvent,isFullMarkCompactorEvent,isGarbageCollectionEvent,isIdleTask,isIncrementalMarkingEvent,isLatencyMarkCompactorEvent,isLowMemoryEvent,isMemoryMarkCompactorEvent,isScavengerEvent,isSubGarbageCollectionEvent,isTopGarbageCollectionEvent,isTopV8ExecuteEvent,isV8Event,isV8ExecuteEvent,isV8RCSEvent,isCompileRCSCategory,isCompileOptimizeRCSCategory,isCompileUnoptimizeRCSCategory,isCompileParseRCSCategory,rangeForMemoryDumps,subGarbageCollectionEventName,topGarbageCollectionEventName,unionOfIntervals,};});'use strict';tr.exportTo('tr.metrics.blink',function(){const BLINK_GC_EVENTS={'BlinkGC.AtomicPhaseMarking':'blink-gc-marking','BlinkGC.CompleteSweep':'blink-gc-complete-sweep','BlinkGC.LazySweepInIdle':'blink-gc-idle-lazy-sweep'};function isBlinkGarbageCollectionEvent(event){return event.title in BLINK_GC_EVENTS;}
-function blinkGarbageCollectionEventName(event){return BLINK_GC_EVENTS[event.title];}
-function blinkGcMetric(histograms,model){addDurationOfTopEvents(histograms,model);addTotalDurationOfTopEvents(histograms,model);addIdleTimesOfTopEvents(histograms,model);addTotalIdleTimesOfTopEvents(histograms,model);}
+class WindowEndpoint{constructor(start,points){this.points=points;this.lastIndex=-1;this.position=start;this.distanceUntilNextPoint=points[0].position-start;this.cummulativePause=0;this.stackDepth=0;}
+advance(delta){if(delta<this.distanceUntilNextPoint){this.position+=delta;this.cummulativePause+=this.stackDepth>0?delta:0;this.distanceUntilNextPoint=this.points[this.lastIndex+1].position-this.position;}else{this.position+=this.distanceUntilNextPoint;this.cummulativePause+=this.stackDepth>0?this.distanceUntilNextPoint:0;this.distanceUntilNextPoint=0;this.lastIndex++;if(this.lastIndex<this.points.length){this.stackDepth+=this.points[this.lastIndex].delta;if(this.lastIndex+1<this.points.length){this.distanceUntilNextPoint=this.points[this.lastIndex+1].position-this.position;}}}}}
+function mutatorUtilization(start,end,timeWindow,intervals){const mu=new tr.b.math.PiecewiseLinearFunction();if(end-start<=timeWindow){return mu;}
+if(intervals.length===0){mu.push(start,1.0,end-timeWindow,1.0);return mu;}
+intervals=unionOfIntervals(intervals);const points=[];for(const interval of intervals){points.push({position:interval.start,delta:1});points.push({position:interval.end,delta:-1});}
+points.sort((a,b)=>a.position-b.position);points.push({position:end,delta:0});const left=new WindowEndpoint(start,points);const right=new WindowEndpoint(start,points);while(right.position-left.position<timeWindow){right.advance(timeWindow-(right.position-left.position));}
+while(right.lastIndex<points.length){const distanceUntilNextPoint=Math.min(left.distanceUntilNextPoint,right.distanceUntilNextPoint);const position1=left.position;const value1=right.cummulativePause-left.cummulativePause;left.advance(distanceUntilNextPoint);right.advance(distanceUntilNextPoint);if(distanceUntilNextPoint>0){const position2=left.position;const value2=right.cummulativePause-left.cummulativePause;mu.push(position1,1.0-value1/timeWindow,position2,1.0-value2/timeWindow);}}
+return mu;}
+function addMutatorUtilization(metricName,eventFilter,timeWindows,rendererHelpers,histograms){const histogramMap=new Map();for(const timeWindow of timeWindows){const histogramOptions={avg:false,count:false,max:false,min:true,std:false,sum:false};const histogram=histograms.createHistogram(`${metricName}-${timeWindow}ms_window`,tr.b.Unit.byName.normalizedPercentage_biggerIsBetter,[],histogramOptions);histogramMap.set(timeWindow,histogram);}
+for(const rendererHelper of rendererHelpers){if(rendererHelper.isChromeTracingUI)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,groupAndProcessEvents,isForcedGarbageCollectionEvent,isFullMarkCompactorEvent,isGarbageCollectionEvent,isIdleTask,isIncrementalMarkingEvent,isLatencyMarkCompactorEvent,isLowMemoryEvent,isMarkCompactorSummaryEvent,isMarkCompactorMarkingSummaryEvent,isMemoryMarkCompactorEvent,isNotForcedMarkCompactorEvent,isNotForcedTopGarbageCollectionEvent,isNotForcedSubGarbageCollectionEvent,isScavengerEvent,isSubGarbageCollectionEvent,isTopGarbageCollectionEvent,isTopV8ExecuteEvent,isV8Event,isV8ExecuteEvent,isV8RCSEvent,isCompileRCSCategory,isCompileOptimizeRCSCategory,isCompileUnoptimizeRCSCategory,isCompileParseRCSCategory,mutatorUtilization,rangeForMemoryDumps,subGarbageCollectionEventName,topGarbageCollectionEventName,unionOfIntervals,};});'use strict';tr.exportTo('tr.metrics.blink',function(){const BLINK_TOP_GC_EVENTS={'BlinkGC.AtomicPhase':'blink-gc-atomic-phase','BlinkGC.CompleteSweep':'blink-gc-complete-sweep','BlinkGC.IncrementalMarkingStartMarking':'blink-gc-incremental-start','BlinkGC.IncrementalMarkingStep':'blink-gc-incremental-step','BlinkGC.LazySweepInIdle':'blink-gc-lazy-sweep-idle','BlinkGC.LazySweepOnAllocation':'blink-gc-lazy-sweep-allocation'};function blinkGarbageCollectionEventName(event){return BLINK_TOP_GC_EVENTS[event.title];}
+function isNonForcedBlinkGarbageCollectionEvent(event){return event.title in BLINK_TOP_GC_EVENTS&&(!event.args||!event.args.forced)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(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);addTotalDurationOfTopEvents(histograms,model);addIdleTimesOfTopEvents(histograms,model);addTotalIdleTimesOfTopEvents(histograms,model);addTotalDurationOfBlinkAndV8TopEvents(histograms,model);}
 tr.metrics.MetricRegistry.register(blinkGcMetric);const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const percentage_biggerIsBetter=tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;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 createNumericForIdleTime(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:true,percentile:[]});return n;}
 function createPercentage(name,numerator,denominator){const histogram=new tr.v.Histogram(name,percentage_biggerIsBetter);if(denominator===0){histogram.addSample(0);}else{histogram.addSample(numerator/denominator);}
 return histogram;}
-function addDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
-function addTotalDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,event=>'blink-gc-total',function(name,events){const cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
-function addIdleTimesOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){addIdleTimes(histograms,model,name,events);});}
-function addTotalIdleTimesOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isBlinkGarbageCollectionEvent,event=>'blink-gc-total',function(name,events){addIdleTimes(histograms,model,name,events);});}
-function addIdleTimes(histograms,model,name,events){const cpuDuration=createNumericForIdleTime(name+'_cpu');const insideIdle=createNumericForIdleTime(name+'_inside_idle');const outsideIdle=createNumericForIdleTime(name+'_outside_idle');const idleDeadlineOverrun=createNumericForIdleTime(name+'_idle_deadline_overrun');events.forEach(function(event){const idleTask=tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isIdleTask);let inside=0;let overrun=0;if(idleTask){const allottedTime=idleTask.args.allotted_time_ms;if(event.duration>allottedTime){overrun=event.duration-allottedTime;inside=event.cpuDuration*allottedTime/event.duration;}else{inside=event.cpuDuration;}}
-cpuDuration.addSample(event.cpuDuration);insideIdle.addSample(inside);outsideIdle.addSample(event.cpuDuration-inside);idleDeadlineOverrun.addSample(overrun);});histograms.addHistogram(idleDeadlineOverrun);histograms.addHistogram(outsideIdle);const percentage=createPercentage(name+'_percentage_idle',insideIdle.sum,cpuDuration.sum);histograms.addHistogram(percentage);}
+function addDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForTopEventTime(name);for(const event of events){cpuDuration.addSample(event.cpuDuration);}
+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);});}
+function addIdleTimesOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionEvent,blinkGarbageCollectionEventName,function(name,events){addIdleTimes(histograms,model,name,events);});}
+function addTotalIdleTimesOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNonForcedBlinkGarbageCollectionEvent,event=>'blink-gc-total',function(name,events){addIdleTimes(histograms,model,name,events);});}
+function addIdleTimes(histograms,model,name,events){const cpuDuration=createNumericForIdleTime(name+'_cpu');const insideIdle=createNumericForIdleTime(name+'_inside_idle');const outsideIdle=createNumericForIdleTime(name+'_outside_idle');const idleDeadlineOverrun=createNumericForIdleTime(name+'_idle_deadline_overrun');for(const event of events){const idleTask=tr.metrics.v8.utils.findParent(event,tr.metrics.v8.utils.isIdleTask);let inside=0;let overrun=0;if(idleTask){const allottedTime=idleTask.args.allotted_time_ms;if(event.duration>allottedTime){overrun=event.duration-allottedTime;inside=event.cpuDuration*allottedTime/event.duration;}else{inside=event.cpuDuration;}}
+cpuDuration.addSample(event.cpuDuration);insideIdle.addSample(inside);outsideIdle.addSample(event.cpuDuration-inside);idleDeadlineOverrun.addSample(overrun);}
+histograms.addHistogram(idleDeadlineOverrun);histograms.addHistogram(outsideIdle);const percentage=createPercentage(name+'_percentage_idle',insideIdle.sum,cpuDuration.sum);histograms.addHistogram(percentage);}
+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);});}
 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);}}
@@ -8019,7 +8199,11 @@
 return diffCounter;}
 function countBlinkObjects(dump,pid){const counter=new Map();const processesMemoryDumps=dump.processMemoryDumps;if(processesMemoryDumps[pid]===undefined)return counter;const blinkObjectsDump=processesMemoryDumps[pid].memoryAllocatorDumps.find(dump=>dump.fullName==='blink_objects');for(const v of blinkObjectsDump.children){counter.set(v.name,v.numerics.object_count.value);}
 return counter;}
-return{leakDetectionMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function getCpuSnapshotsFromModel(model){const snapshots=[];for(const pid in model.processes){const snapshotInstances=model.processes[pid].objects.getAllInstancesNamed('CPUSnapshots');if(!snapshotInstances)continue;for(const object of snapshotInstances[0].snapshots){snapshots.push(object.args.processes);}}
+return{leakDetectionMetric,};});'use strict';tr.exportTo('tr.metrics.console',function(){const COUNT_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e4,30);const SUMMARY_OPTIONS=tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;const SOURCES=['all','js','network'];function consoleErrorMetric(histograms,model){const counts={};for(const source of SOURCES){counts[source]=0;}
+for(const slice of model.getDescendantEvents()){if(slice.category==='blink.console'&&slice.title==='ConsoleMessage::Error'){const source=slice.args.source.toLowerCase();counts.all++;if(source in counts){counts[source]++;}}
+if(slice.category==='v8.console'&&(slice.title==='V8ConsoleMessage::Exception'||slice.title==='V8ConsoleMessage::Error'||slice.title==='V8ConsoleMessage::Assert')){counts.all++;counts.js++;}}
+for(const source of SOURCES){histograms.createHistogram(`console:error:${source}`,tr.b.Unit.byName.count_smallerIsBetter,counts[source],{description:`Number of ${source} console error messages`,summaryOptions:SUMMARY_OPTIONS});}}
+tr.metrics.MetricRegistry.register(consoleErrorMetric);return{consoleErrorMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function getCpuSnapshotsFromModel(model){const snapshots=[];for(const pid in model.processes){const snapshotInstances=model.processes[pid].objects.getAllInstancesNamed('CPUSnapshots');if(!snapshotInstances)continue;for(const object of snapshotInstances[0].snapshots){snapshots.push(object.args.processes);}}
 return snapshots;}
 function getProcessSumsFromSnapshot(snapshot){const processSums=new Map();for(const processData of snapshot){const processName=processData.name;if(!(processSums.has(processName))){processSums.set(processName,{sum:0.0,paths:new Set()});}
 processSums.get(processName).sum+=parseFloat(processData.pCpu);if(processData.path){processSums.get(processName).paths.add(processData.path);}}
@@ -8067,39 +8251,71 @@
 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);}
 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.rendering',function(){const UNKNOWN_THREAD_NAME='Unknown';const CATEGORY_THREAD_MAP=new Map();CATEGORY_THREAD_MAP.set('all',[/.*/]);CATEGORY_THREAD_MAP.set('browser',[/^Browser Compositor$/,/^CrBrowserMain$/]);CATEGORY_THREAD_MAP.set('display_compositor',[/^VizCompositorThread$/]);CATEGORY_THREAD_MAP.set('fast_path',[/^Browser Compositor$/,/^Chrome_InProcGpuThread$/,/^Compositor$/,/^CrBrowserMain$/,/^CrGpuMain$/,/IOThread/,/^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$/]);const DRM_EVENT='DrmEventFlipComplete';const DISPLAY_EVENT='BenchmarkInstrumentation::DisplayRenderingStats';const GESTURE_EVENT='SyntheticGestureController::running';function addValueToMap_(map,key,value){const oldValue=map.get(key)||0;map.set(key,oldValue+value);}
+tr.metrics.MetricRegistry.register(mediaMetric);return{mediaMetric,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const UNKNOWN_THREAD_NAME='Unknown';const CATEGORY_THREAD_MAP=new Map();CATEGORY_THREAD_MAP.set('all',[/.*/]);CATEGORY_THREAD_MAP.set('browser',[/^Browser Compositor$/,/^CrBrowserMain$/]);CATEGORY_THREAD_MAP.set('display_compositor',[/^VizCompositorThread$/]);CATEGORY_THREAD_MAP.set('fast_path',[/^Browser Compositor$/,/^Chrome_InProcGpuThread$/,/^Compositor$/,/^CrBrowserMain$/,/^CrGpuMain$/,/IOThread/,/^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$/]);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*getCategories_(threadName){let isOther=true;for(const[category,regexps]of CATEGORY_THREAD_MAP){for(const regexp of regexps){if(regexp.test(threadName)){if(category!=='all')isOther=false;yield category;break;}}}
 if(isOther)yield'other';}
-function addCoresPerSecondHistograms(histograms,model,segments){const totalDuration=tr.b.math.Statistics.sum(segments,segment=>segment.duration);const threadValues=new Map();for(const thread of model.getAllThreads()){addValueToMap_(threadValues,thread.name||UNKNOWN_THREAD_NAME,tr.b.math.Statistics.sum(segments,segment=>thread.getCpuTimeForRange(segment.boundsRange))/totalDuration);}
+function isSubset_(regexps1,regexps2){for(const r1 of regexps1){if(regexps2.find(r2=>r2.toString()===r1.toString())===undefined){return false;}}
+return true;}
+function addCpuUtilizationHistograms(histograms,model,segments,segmentName,shouldNormalize){const histogramMap=new Map();for(const category of ALL_CATEGORIES){const histogram=histograms.createHistogram(`thread_${category}_cpu_time_per_${segmentName}_tbmv2`,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{binBoundaries:tr.v.HistogramBinBoundaries.createExponential(1,50,20),description:`CPU cores per ${segmentName} of a thread group`,summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});histogramMap.set(category,histogram);}
+for(const[category,regexps]of CATEGORY_THREAD_MAP){const relatedCategories=new tr.v.d.RelatedNameMap();const histogram=histogramMap.get(category);for(const[otherCategory,otherRegexps]of CATEGORY_THREAD_MAP){if(otherCategory===category)continue;if(category!=='all'&&!isSubset_(otherRegexps,regexps))continue;const otherHistogram=histogramMap.get(otherCategory);relatedCategories.set(otherCategory,otherHistogram.name);}
+if([...relatedCategories.values()].length>0){histogram.diagnostics.set('breakdown',relatedCategories);}}
+for(const segment of segments){const threadValues=new Map();for(const thread of model.getAllThreads()){addValueToMap_(threadValues,thread.name||UNKNOWN_THREAD_NAME,thread.getCpuTimeForRange(segment.boundsRange));}
 const categoryValues=new Map();const breakdowns=new Map();for(const[threadName,coresPerSec]of threadValues){for(const category of getCategories_(threadName)){addValueToMap_(categoryValues,category,coresPerSec);if(!breakdowns.has(category)){breakdowns.set(category,new tr.v.d.Breakdown());}
 breakdowns.get(category).set(threadName,coresPerSec);}}
-for(const[category,coresPerSec]of categoryValues){histograms.createHistogram(`cores_per_second_${category}_thread`,tr.b.Unit.byName.unitlessNumber_smallerIsBetter,{value:coresPerSec,diagnostics:{breakdown:breakdowns.get(category)}},{description:'CPU cores per second of a thread group'});}}
-function addFrameTimeHistograms(histograms,model,segments){const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper||!modelHelper.browserProcess)return;let events=[];if(modelHelper.gpuHelper){const gpuProcess=modelHelper.gpuHelper.process;events=[...gpuProcess.findTopmostSlicesNamed(DRM_EVENT)];if(events.length===0){events=[...gpuProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];}}
-if(events.length===0){events=[...modelHelper.browserProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];}
-if(events.length===0)return;const timestamps=events.map(event=>(event.title!==DRM_EVENT?event.start:(tr.b.convertUnit(event.args.data['vblank.tv_sec'],tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)+
-tr.b.convertUnit(event.args.data['vblank.tv_usec'],tr.b.UnitScale.TIME.MICRO_SEC,tr.b.UnitScale.TIME.MILLI_SEC))));const frameTimes=[];for(const segment of segments){const filteredTimestamps=segment.boundsRange.filterArray(timestamps);for(let i=1;i<filteredTimestamps.length;i++){frameTimes.push(filteredTimestamps[i]-filteredTimestamps[i-1]);}}
-histograms.createHistogram('frame_times_tbmv2',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,frameTimes,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),description:'Raw frame times.'});histograms.createHistogram('mean_frame_time_tbmv2',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.b.math.Statistics.mean(frameTimes),{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),description:'Arithmetic mean of frame times.'});histograms.createHistogram('smooth_frames',tr.b.Unit.byName.normalizedPercentage_biggerIsBetter,tr.b.math.Statistics.sum(frameTimes,(x=>(x<17?1:0)))/frameTimes.length,{description:'Percentage of frames that were hitting 60 FPS.'});}
-function eventIsValidGraphicsEvent(event,eventMap){if(!event.bindId||!event.args||!event.args.step){return false;}
-const bindId=event.bindId;if(bindId in eventMap&&event.args.step in eventMap[bindId]){if(event.args.step==='IssueBeginFrame'||event.args.step==='ReceiveBeginFrame'){throw new Error('Unexpected duplicate step: '+event.args.step);}
+for(const category of ALL_CATEGORIES){let value=categoryValues.get(category)||0;if(shouldNormalize)value/=segment.duration;const diagnostics=new tr.v.d.DiagnosticMap();const breakdown=breakdowns.get(category);if(breakdown)diagnostics.set('breakdown',breakdown);const histogram=histogramMap.get(category);histogram.addSample(value,diagnostics);}}}
+const SUMMARY_OPTIONS={percentile:[0.90,0.95],};return{addCpuUtilizationHistograms,SUMMARY_OPTIONS,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const DISPLAY_EVENT='BenchmarkInstrumentation::DisplayRenderingStats';const DRM_EVENT='DrmEventFlipComplete';const SURFACE_FLINGER_EVENT='vsync_before';const COMPOSITOR_FRAME_PRESENTED_EVENT='FramePresented';const MIN_FRAME_LENGTH=0.5;const PAUSE_THRESHOLD=20;const ASH_ENVIRONMENT='ash';const BROWSER_ENVIRONMENT='browser';function getDisplayCompositorPresentationEvents_(modelHelper){if(!modelHelper||!modelHelper.browserProcess)return[];let events=[];if(modelHelper.surfaceFlingerProcess){events=[...modelHelper.surfaceFlingerProcess.findTopmostSlicesNamed(SURFACE_FLINGER_EVENT)];if(events.length>0)return events;}
+if(modelHelper.gpuHelper){const gpuProcess=modelHelper.gpuHelper.process;events=[...gpuProcess.findTopmostSlicesNamed(DRM_EVENT)];if(events.length>0)return events;events=[...gpuProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];if(events.length>0)return events;}
+return[...modelHelper.browserProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];}
+function getUIPresentationEvents_(modelHelper){if(!modelHelper||!modelHelper.browserProcess)return[];const legacyEvents=[];const eventsByEnvironment={};eventsByEnvironment[ASH_ENVIRONMENT]=[];eventsByEnvironment[BROWSER_ENVIRONMENT]=[];for(const event of modelHelper.browserProcess.findTopmostSlicesNamed(COMPOSITOR_FRAME_PRESENTED_EVENT)){if(!('environment'in event.args)){legacyEvents.push(event);}else{eventsByEnvironment[event.args.environment].push(event);}}
+if(eventsByEnvironment[ASH_ENVIRONMENT].length>0){return eventsByEnvironment[ASH_ENVIRONMENT];}
+if(eventsByEnvironment[BROWSER_ENVIRONMENT].length>0){return eventsByEnvironment[BROWSER_ENVIRONMENT];}
+return legacyEvents;}
+function addSurfaceFlingerHistograms_(histograms,frameSegments,refreshPeriod){let frameLengths=frameSegments.map(x=>x.duration/refreshPeriod);frameLengths=frameLengths.filter(length=>length>=MIN_FRAME_LENGTH);histograms.createHistogram('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('avg_surface_fps',tr.b.Unit.byName.unitlessNumber_biggerIsBetter,frameLengths.length/tr.b.convertUnit(tr.b.math.Statistics.sum(frameSegments,x=>x.duration),tr.b.UnitScale.TIME.MILLI_SEC,tr.b.UnitScale.TIME.SEC),{description:'Average frames per second.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});let jankCount=0;for(let i=1;i<frameLengths.length;i++){const change=Math.round(frameLengths[i]-frameLengths[i-1]);if(change>0&&change<PAUSE_THRESHOLD)jankCount++;}
+histograms.createHistogram('jank_count',tr.b.Unit.byName.unitlessNumber_smallerIsBetter,jankCount,{description:'Number of changes in frame rate.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});}
+function computeFrameSegments_(timestamps,segments){const frameSegments=[];for(const segment of segments){const filtered=segment.boundsRange.filterArray(timestamps,x=>x[0]);for(let i=1;i<filtered.length;i++){const duration=filtered[i][1]-filtered[i-1][1];frameSegments.push(new tr.model.um.Segment(filtered[i-1][0],duration));}}
+return frameSegments;}
+function addBasicFrameTimeHistograms_(histograms,frameSegments,prefix){const frameTimes=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:tr.metrics.rendering.SUMMARY_OPTIONS,});}
+function addFrameTimeHistograms(histograms,model,segments){const events=getDisplayCompositorPresentationEvents_(model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper));if(!events)return;const timestamps=events.map(event=>[event.start,event.title!==DRM_EVENT?event.start:(tr.b.convertUnit(event.args.data['vblank.tv_sec'],tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)+
+tr.b.convertUnit(event.args.data['vblank.tv_usec'],tr.b.UnitScale.TIME.MICRO_SEC,tr.b.UnitScale.TIME.MILLI_SEC))]);const frameSegments=computeFrameSegments_(timestamps,segments);addBasicFrameTimeHistograms_(histograms,frameSegments,'');tr.metrics.rendering.addCpuUtilizationHistograms(histograms,model,frameSegments,'frame',false);for(const metadata of model.metadata){if(metadata.value&&metadata.value.surface_flinger){addSurfaceFlingerHistograms_(histograms,frameSegments,metadata.value.surface_flinger.refresh_period);return;}}}
+function addUIFrameTimeHistograms(histograms,model,segments){const events=getUIPresentationEvents_(model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper));if(!events)return;const timestamps=events.map(event=>[event.start,event.start]);const frameSements=computeFrameSegments_(timestamps,segments);addBasicFrameTimeHistograms_(histograms,frameSements,'ui_');}
+return{addFrameTimeHistograms,addUIFrameTimeHistograms,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const BEGIN_SCROLL_UPDATE_COMP_NAME='LATENCY_BEGIN_SCROLL_LISTENER_UPDATE_MAIN_COMPONENT';const END_COMP_NAME='INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT';function*iterAsyncEvents_(processHelpers,ranges,processEventFn){for(const processHelper of processHelpers){const process=processHelper.process;for(const event of process.getDescendantEventsInSortedRanges(ranges,container=>container instanceof tr.model.AsyncSliceGroup)){yield*processEventFn(event);}}}
+function*processInputLatencyEvent(event){if(!(event instanceof tr.e.cc.InputLatencyAsyncSlice))return;const latency=event.inputLatency;if(latency===undefined)return;yield tr.b.Unit.timestampFromUs(latency);}
+function*processLatencyEvent(event){if(event.title!=='Latency::ScrollUpdate'||!('data'in event.args)||!(END_COMP_NAME in event.args.data)){return;}
+const data=event.args.data;const endTime=data[END_COMP_NAME].time;if(BEGIN_SCROLL_UPDATE_COMP_NAME in data){yield tr.b.Unit.timestampFromUs(endTime-data[BEGIN_SCROLL_UPDATE_COMP_NAME].time);}else{throw new Error('LatencyInfo has no begin component');}}
+function*processGestureScrollUpdateLatencyEvent(event){if(event.title!=='InputLatency::GestureScrollUpdate')return;if(!(event instanceof tr.e.cc.InputLatencyAsyncSlice)){throw new Error('Gesture scroll update latency event is not an '+'instance of tr.e.cc.InputLatencyAsyncSlice');}
+const latency=event.inputLatency;if(latency===undefined)return;yield[event.start,tr.b.Unit.timestampFromUs(latency)];}
+function addLatencyHistograms(histograms,model,segments){const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper)return;const ranges=segments.map(s=>s.boundsRange);const inputEventLatencies=[...iterAsyncEvents_(modelHelper.browserHelpers,ranges,processInputLatencyEvent)];histograms.createHistogram('input_event_latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,inputEventLatencies,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,description:'Input event latencies.'});const mainThreadScrollLatencies=[...iterAsyncEvents_(Object.values(modelHelper.rendererHelpers),ranges,processLatencyEvent)];histograms.createHistogram('main_thread_scroll_latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,mainThreadScrollLatencies,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,50),summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,description:'Main thread scroll latencies.'});const gestureScrollUpdateLatencies=[...iterAsyncEvents_(modelHelper.browserHelpers,ranges,processGestureScrollUpdateLatencyEvent)].sort();if(gestureScrollUpdateLatencies.length){histograms.createHistogram('first_gesture_scroll_update_latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,gestureScrollUpdateLatencies[0][1],{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,description:'Latency of the first gesture scroll update.'});}}
+return{addLatencyHistograms,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){function eventIsValidGraphicsEvent_(event,eventMap){if(event.title!=='Graphics.Pipeline'||!event.bindId||!event.args||!event.args.step){return false;}
+const bindId=event.bindId;if(eventMap.has(bindId)&&event.args.step in eventMap.get(bindId)){if(event.args.step==='IssueBeginFrame'||event.args.step==='ReceiveBeginFrame'){throw new Error('Unexpected duplicate step: '+event.args.step);}
 return false;}
 return true;}
-function generateBreakdownForCompositorPipelineInClient(flow){const breakdown=new tr.v.d.Breakdown();breakdown.set('time before GenerateRenderPass',flow.GenerateRenderPass.start-flow.ReceiveBeginFrame.start);breakdown.set('GenerateRenderPass duration',flow.GenerateRenderPass.duration);breakdown.set('GenerateCompositorFrame duration',flow.GenerateCompositorFrame.duration);breakdown.set('SubmitCompositorFrame duration',flow.SubmitCompositorFrame.duration);return breakdown;}
-function generateBreakdownForCompositorPipelineInService(flow){const breakdown=new tr.v.d.Breakdown();breakdown.set('Processing CompositorFrame on reception',flow.ReceiveCompositorFrame.duration);breakdown.set('Delay before SurfaceAggregation',flow.SurfaceAggregation.start-flow.ReceiveCompositorFrame.end);breakdown.set('SurfaceAggregation duration',flow.SurfaceAggregation.duration);return breakdown;}
-function generateBreakdownForDraw(drawEvent){const breakdown=new tr.v.d.Breakdown();for(const slice of drawEvent.subSlices){breakdown.set(slice.title,slice.duration);}
+function generateBreakdownForCompositorPipelineInClient_(flow){const breakdown=new tr.v.d.Breakdown();breakdown.set('time before GenerateRenderPass',flow.GenerateRenderPass.start-flow.ReceiveBeginFrame.start);breakdown.set('GenerateRenderPass duration',flow.GenerateRenderPass.duration);breakdown.set('GenerateCompositorFrame duration',flow.GenerateCompositorFrame.duration);breakdown.set('SubmitCompositorFrame duration',flow.SubmitCompositorFrame.duration);return breakdown;}
+function generateBreakdownForCompositorPipelineInService_(flow){const breakdown=new tr.v.d.Breakdown();breakdown.set('Processing CompositorFrame on reception',flow.ReceiveCompositorFrame.duration);breakdown.set('Delay before SurfaceAggregation',flow.SurfaceAggregation.start-flow.ReceiveCompositorFrame.end);breakdown.set('SurfaceAggregation duration',flow.SurfaceAggregation.duration);return breakdown;}
+function generateBreakdownForDraw_(drawEvent){const breakdown=new tr.v.d.Breakdown();for(const slice of drawEvent.subSlices){breakdown.set(slice.title,slice.duration);}
 return breakdown;}
-function getDisplayCompositorThread(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const gpuHelper=chromeHelper.gpuHelper;if(gpuHelper){const thread=gpuHelper.process.findAtMostOneThreadNamed('VizCompositorThread');if(thread){return thread;}}
-const browserHelper=chromeHelper.browserHelpers[0];return browserHelper.process.findAtMostOneThreadNamed('CrBrowserMain');}
-function addPipelineHistograms(histograms,model,segments){let events=[];for(const thread of model.getAllThreads()){const graphicsEvents=thread.sliceGroup.slices.filter(slice=>slice.title==='Graphics.Pipeline');for(const segment of segments){const filteredEvents=segment.boundsRange.filterArray(graphicsEvents,evt=>evt.start);events=events.concat(filteredEvents);}}
-const bindEvents={};for(const event of events){if(!eventIsValidGraphicsEvent(event,bindEvents))continue;const bindId=event.bindId;if(!(bindId in bindEvents)){bindEvents[bindId]={};}
-bindEvents[bindId][event.args.step]=event;}
-const dcThread=getDisplayCompositorThread(model);const drawEvents={};if(dcThread){const events=[...dcThread.findTopmostSlicesNamed('Graphics.Pipeline.DrawAndSwap')];for(const segment of segments){const filteredEvents=segment.boundsRange.filterArray(events,evt=>evt.start);for(const event of filteredEvents){if(!event.id.startsWith(':ptr:'))continue;const id=parseInt(event.id.substring(5),16);if(id in drawEvents){throw new Error('Duplicate draw events: '+id);}
+function getDisplayCompositorThread_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const gpuHelper=chromeHelper.gpuHelper;if(gpuHelper){const thread=gpuHelper.process.findAtMostOneThreadNamed('VizCompositorThread');if(thread){return thread;}}
+if(!chromeHelper.browserProcess)return null;return chromeHelper.browserProcess.findAtMostOneThreadNamed('CrBrowserMain');}
+function addPipelineHistograms(histograms,model,segments){const ranges=segments.map(s=>s.boundsRange);const bindEvents=new Map();for(const thread of model.getAllThreads()){for(const event of thread.sliceGroup.childEvents()){if(!eventIsValidGraphicsEvent_(event,bindEvents))continue;for(const range of ranges){if(range.containsExplicitRangeInclusive(event.start,event.end)){if(!bindEvents.has(event.bindId))bindEvents.set(event.bindId,{});break;}}
+if(bindEvents.has(event.bindId)){bindEvents.get(event.bindId)[event.args.step]=event;}}}
+const dcThread=getDisplayCompositorThread_(model);const drawEvents={};if(dcThread){const events=[...dcThread.findTopmostSlicesNamed('Graphics.Pipeline.DrawAndSwap')];for(const segment of segments){const filteredEvents=segment.boundsRange.filterArray(events,evt=>evt.start);for(const event of filteredEvents){if((event.args&&event.args.status==='canceled')||!event.id.startsWith(':ptr:')){continue;}
+const id=parseInt(event.id.substring(5),16);if(id in drawEvents){throw new Error('Duplicate draw events: '+id);}
 drawEvents[id]=event;}}}
-const issueToReceipt=histograms.createHistogram('pipeline:begin_frame_transport',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency of begin-frame message from the display '+'compositor to the client, including the IPC latency and task-'+'queue time in the client.',});const receiptToSubmit=histograms.createHistogram('pipeline:begin_frame_to_frame_submission',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency between begin-frame reception and '+'CompositorFrame submission in the renderer.'});const submitToAggregate=histograms.createHistogram('pipeline:frame_submission_to_display',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency between CompositorFrame submission in the '+'renderer to display in the display-compositor, including IPC '+'latency, task-queue time in the display-compositor, and '+'additional processing (e.g. surface-sync etc.)',});const aggregateToDraw=histograms.createHistogram('pipeline:draw',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'How long it takes for the gpu-swap step.'});for(const flow of Object.values(bindEvents)){if(!flow.IssueBeginFrame||!flow.ReceiveBeginFrame||!flow.SubmitCompositorFrame||!flow.SurfaceAggregation){continue;}
+const issueToReceipt=histograms.createHistogram('pipeline:begin_frame_transport',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency of begin-frame message from the display '+'compositor to the client, including the IPC latency and task-'+'queue time in the client.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});const receiptToSubmit=histograms.createHistogram('pipeline:begin_frame_to_frame_submission',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency between begin-frame reception and '+'CompositorFrame submission in the renderer.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});const submitToAggregate=histograms.createHistogram('pipeline:frame_submission_to_display',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency between CompositorFrame submission in the '+'renderer to display in the display-compositor, including IPC '+'latency, task-queue time in the display-compositor, and '+'additional processing (e.g. surface-sync etc.)',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});const aggregateToDraw=histograms.createHistogram('pipeline:draw',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'How long it takes for the gpu-swap step.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});for(const flow of bindEvents.values()){if(!flow.IssueBeginFrame||!flow.ReceiveBeginFrame||!flow.SubmitCompositorFrame||!flow.SurfaceAggregation){continue;}
 issueToReceipt.addSample(flow.ReceiveBeginFrame.start-
-flow.IssueBeginFrame.start);receiptToSubmit.addSample(flow.SubmitCompositorFrame.end-flow.ReceiveBeginFrame.start,{breakdown:generateBreakdownForCompositorPipelineInClient(flow)});submitToAggregate.addSample(flow.SurfaceAggregation.end-flow.SubmitCompositorFrame.end,{breakdown:generateBreakdownForCompositorPipelineInService(flow)});if(flow.SurfaceAggregation.args&&flow.SurfaceAggregation.args.display_trace){const displayTrace=flow.SurfaceAggregation.args.display_trace;if(!(displayTrace in drawEvents))continue;const drawEvent=drawEvents[displayTrace];aggregateToDraw.addSample(drawEvent.duration,{breakdown:generateBreakdownForDraw(drawEvent)});}}}
-function renderingMetric(histograms,model){const segments=[];const IRExp=/Interaction\.([^/]+)(\/[^/]*)?$/;for(const thread of model.getAllThreads()){for(const slice of thread.asyncSliceGroup.slices){if(slice.title===GESTURE_EVENT){segments.push(new tr.model.um.Segment(slice.start,slice.duration));}else{const parts=IRExp.exec(slice.title);if(parts&&!parts[1].startsWith('Gesture_')){segments.push(new tr.model.um.Segment(slice.start,slice.duration));}}}}
-if(segments.length===0)return;addCoresPerSecondHistograms(histograms,model,segments);addFrameTimeHistograms(histograms,model,segments);addPipelineHistograms(histograms,model,segments);}
-tr.metrics.MetricRegistry.register(renderingMetric);return{renderingMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function sampleExceptionMetric(histograms,model){const hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.GenericSet([{hello:42}])});for(const expectation of model.userModel.expectations){if(expectation instanceof tr.model.um.ResponseExpectation){}else if(expectation instanceof tr.model.um.AnimationExpectation){}else if(expectation instanceof tr.model.um.IdleExpectation){}else if(expectation instanceof tr.model.um.LoadExpectation){}}
+flow.IssueBeginFrame.start);receiptToSubmit.addSample(flow.SubmitCompositorFrame.end-flow.ReceiveBeginFrame.start,{breakdown:generateBreakdownForCompositorPipelineInClient_(flow)});submitToAggregate.addSample(flow.SurfaceAggregation.end-flow.SubmitCompositorFrame.end,{breakdown:generateBreakdownForCompositorPipelineInService_(flow)});if(flow.SurfaceAggregation.args&&flow.SurfaceAggregation.args.display_trace){const displayTrace=flow.SurfaceAggregation.args.display_trace;if(!(displayTrace in drawEvents))continue;const drawEvent=drawEvents[displayTrace];aggregateToDraw.addSample(drawEvent.duration,{breakdown:generateBreakdownForDraw_(drawEvent)});}}}
+return{addPipelineHistograms,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const IMPL_THREAD_RENDERING_STATS_EVENT='BenchmarkInstrumentation::ImplThreadRenderingStats';const VISIBLE_CONTENT_DATA='visible_content_area';const APPROXIMATED_VISIBLE_CONTENT_DATA='approximated_visible_content_area';const CHECKERBOARDED_VISIBLE_CONTENT_DATA='checkerboarded_visible_content_area';function addPixelsHistograms(histograms,model,segments){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;const approximatedPixelPercentages=[];const checkerboardedPixelPercentages=[];const ranges=segments.map(s=>s.boundsRange);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){if(rendererHelper.compositorThread===undefined)continue;const slices=rendererHelper.compositorThread.sliceGroup;for(const slice of slices.getDescendantEventsInSortedRanges(ranges)){if(slice.title!==IMPL_THREAD_RENDERING_STATS_EVENT)continue;const data=slice.args.data;if(!(VISIBLE_CONTENT_DATA in data)){throw new Error(`${VISIBLE_CONTENT_DATA} is missing`);}
+const visibleContentArea=data[VISIBLE_CONTENT_DATA];if(visibleContentArea===0){continue;}
+if(APPROXIMATED_VISIBLE_CONTENT_DATA in data){approximatedPixelPercentages.push(data[APPROXIMATED_VISIBLE_CONTENT_DATA]/visibleContentArea);}
+if(CHECKERBOARDED_VISIBLE_CONTENT_DATA in data){checkerboardedPixelPercentages.push(data[CHECKERBOARDED_VISIBLE_CONTENT_DATA]/visibleContentArea);}}}
+histograms.createHistogram('mean_pixels_approximated',tr.b.Unit.byName.normalizedPercentage_smallerIsBetter,100*tr.b.math.Statistics.mean(approximatedPixelPercentages),{description:'Percentage of pixels that were approximated '+'(checkerboarding, low-resolution tiles, etc.).',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});histograms.createHistogram('mean_pixels_checkerboarded',tr.b.Unit.byName.normalizedPercentage_smallerIsBetter,100*tr.b.math.Statistics.mean(checkerboardedPixelPercentages),{description:'Percentage of pixels that were checkerboarded.',summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,});}
+return{addPixelsHistograms,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const BEGIN_MAIN_FRAME_EVENT='ThreadProxy::BeginMainFrame';const SEND_BEGIN_FRAME_EVENT='ThreadProxy::ScheduledActionSendBeginMainFrame';function getEventTimesByBeginFrameId_(thread,title,ranges){const out=new Map();const slices=thread.sliceGroup;for(const slice of slices.getDescendantEventsInSortedRanges(ranges)){if(slice.title!==title)continue;const id=slice.args.begin_frame_id;if(id===undefined)throw new Error('Event is missing begin_frame_id');if(out.has(id))throw new Error(`There must be exactly one ${title}`);out.set(id,slice.start);}
+return out;}
+function addQueueingDurationHistograms(histograms,model,segments){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;let targetRenderers=chromeHelper.telemetryHelper.renderersWithIR;if(targetRenderers.length===0){targetRenderers=Object.values(chromeHelper.rendererHelpers);}
+const queueingDurations=[];const ranges=segments.map(s=>s.boundsRange);for(const rendererHelper of targetRenderers){const mainThread=rendererHelper.mainThread;const compositorThread=rendererHelper.compositorThread;if(mainThread===undefined||compositorThread===undefined)continue;const beginMainFrameTimes=getEventTimesByBeginFrameId_(mainThread,BEGIN_MAIN_FRAME_EVENT,ranges);const sendBeginFrameTimes=getEventTimesByBeginFrameId_(compositorThread,SEND_BEGIN_FRAME_EVENT,ranges);for(const[id,time]of sendBeginFrameTimes){queueingDurations.push(beginMainFrameTimes.get(id)-time);}}
+histograms.createHistogram('queueing_durations',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,queueingDurations,{binBoundaries:tr.v.HistogramBinBoundaries.createExponential(0.01,2,20),summaryOptions:tr.metrics.rendering.SUMMARY_OPTIONS,description:'Time between ScheduledActionSendBeginMainFrame in '+'the compositor thread and the corresponding '+'BeginMainFrame in the main thread.'});}
+return{addQueueingDurationHistograms,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const GESTURE_EVENT='SyntheticGestureController::running';function renderingMetric(histograms,model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;const segments=chromeHelper.telemetryHelper.segments;if(segments.length>0){tr.metrics.rendering.addFrameTimeHistograms(histograms,model,segments);tr.metrics.rendering.addLatencyHistograms(histograms,model,segments);tr.metrics.rendering.addPipelineHistograms(histograms,model,segments);tr.metrics.rendering.addPixelsHistograms(histograms,model,segments);tr.metrics.rendering.addQueueingDurationHistograms(histograms,model,segments);}
+const uiSegments=chromeHelper.telemetryHelper.uiSegments;if(uiSegments.length>0){tr.metrics.rendering.addUIFrameTimeHistograms(histograms,model,chromeHelper.telemetryHelper.uiSegments);}}
+tr.metrics.MetricRegistry.register(renderingMetric,{requiredCategories:['benchmark','toplevel'],});return{renderingMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function sampleExceptionMetric(histograms,model){const hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.GenericSet([{hello:42}])});for(const expectation of model.userModel.expectations){if(expectation instanceof tr.model.um.ResponseExpectation){}else if(expectation instanceof tr.model.um.AnimationExpectation){}else if(expectation instanceof tr.model.um.IdleExpectation){}else if(expectation instanceof tr.model.um.LoadExpectation){}}
 const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const[pid,process]of Object.entries(model.processes)){}
 histograms.addHistogram(hist);throw new Error('There was an error');}
 tr.metrics.MetricRegistry.register(sampleExceptionMetric);return{sampleExceptionMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function sampleMetric(histograms,model){const hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.GenericSet([{hello:42}])});for(const expectation of model.userModel.expectations){if(expectation instanceof tr.model.um.ResponseExpectation){}else if(expectation instanceof tr.model.um.AnimationExpectation){}else if(expectation instanceof tr.model.um.IdleExpectation){}else if(expectation instanceof tr.model.um.LoadExpectation){}}
@@ -8147,12 +8363,17 @@
 return snapshot;}
 function findAllEvents(rendererHelper,category,title){const targetEvents=[];for(const ev of rendererHelper.process.getDescendantEvents()){if(!hasCategoryAndName(ev,category,title))continue;targetEvents.push(ev);}
 return targetEvents;}
-function collectTimeToEvent(category,eventName,rendererHelper,frameToNavStartEvents){const targetEvents=findAllEvents(rendererHelper,category,eventName);const samples=[];for(const ev of targetEvents){if(rendererHelper.isTelemetryInternalEvent(ev))continue;const frameIdRef=ev.args.frame;const snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ev.start);if(snapshot===undefined||!snapshot.args.isLoadingMainFrame)continue;const url=snapshot.args.documentLoaderURL;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(url))continue;const navigationStartEvent=EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(frameToNavStartEvents.get(frameIdRef)||[],ev.start);if(navigationStartEvent===undefined)continue;const navStartToEventRange=tr.b.math.Range.fromExplicitRange(navigationStartEvent.start,ev.start);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToEventRange);const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToEventRange);samples.push({value:navStartToEventRange.duration,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),url:new tr.v.d.GenericSet([url]),Start:new RelatedEventSet(navigationStartEvent),End:new RelatedEventSet(ev)}});}
+function findTimeToXEntries(category,eventName,rendererHelper,frameToNavStartEvents,navIdToNavStartEvents){const targetEvents=findAllEvents(rendererHelper,category,eventName);const entries=[];for(const targetEvent of targetEvents){if(rendererHelper.isTelemetryInternalEvent(targetEvent))continue;const frameIdRef=targetEvent.args.frame;const snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,targetEvent.start);if(snapshot===undefined||!snapshot.args.isLoadingMainFrame)continue;const url=snapshot.args.documentLoaderURL;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(url))continue;let navigationStartEvent;if(targetEvent.args.data===undefined||targetEvent.args.data.navigationId===undefined){navigationStartEvent=EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(frameToNavStartEvents.get(frameIdRef)||[],targetEvent.start);}else{navigationStartEvent=navIdToNavStartEvents.get(targetEvent.args.data.navigationId);}
+if(navigationStartEvent===undefined)continue;entries.push({navigationStartEvent,targetEvent,url,});}
+return entries;}
+function collectTimeToEvent(rendererHelper,timeToXEntries){const samples=[];for(const{targetEvent,navigationStartEvent,url}of timeToXEntries){const navStartToEventRange=tr.b.math.Range.fromExplicitRange(navigationStartEvent.start,targetEvent.start);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToEventRange);const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToEventRange);samples.push({value:navStartToEventRange.duration,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),url:new tr.v.d.GenericSet([url]),Start:new RelatedEventSet(navigationStartEvent),End:new RelatedEventSet(targetEvent)}});}
+return samples;}
+function collectTimeToEventInCpuTime(rendererHelper,timeToXEntries){const samples=[];for(const{targetEvent,navigationStartEvent,url}of timeToXEntries){const navStartToEventRange=tr.b.math.Range.fromExplicitRange(navigationStartEvent.start,targetEvent.start);const mainThreadCpuTime=rendererHelper.mainThread.getCpuTimeForRange(navStartToEventRange);const breakdownTree=tr.metrics.sh.generateCpuTimeBreakdownTree(rendererHelper.mainThread,navStartToEventRange);samples.push({value:mainThreadCpuTime,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),start:new RelatedEventSet(navigationStartEvent),end:new RelatedEventSet(targetEvent),infos:new tr.v.d.GenericSet([{pid:rendererHelper.pid,start:navigationStartEvent.start,event:targetEvent.start,}]),}});}
 return samples;}
 function addFirstMeaningfulPaintSample(samples,rendererHelper,navigationStart,fmpMarkerEvent,url){const navStartToFMPRange=tr.b.math.Range.fromExplicitRange(navigationStart.start,fmpMarkerEvent.start);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToFMPRange);const timeToFirstMeaningfulPaint=navStartToFMPRange.duration;const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToFMPRange);samples.push({value:timeToFirstMeaningfulPaint,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),start:new RelatedEventSet(navigationStart),end:new RelatedEventSet(fmpMarkerEvent),infos:new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,start:navigationStart.start,fmp:fmpMarkerEvent.start,}]),}});}
 function addFirstMeaningfulPaintCpuTimeSample(samples,rendererHelper,navigationStart,fmpMarkerEvent,url){const navStartToFMPRange=tr.b.math.Range.fromExplicitRange(navigationStart.start,fmpMarkerEvent.start);const mainThreadCpuTime=rendererHelper.mainThread.getCpuTimeForRange(navStartToFMPRange);const breakdownTree=tr.metrics.sh.generateCpuTimeBreakdownTree(rendererHelper.mainThread,navStartToFMPRange);samples.push({value:mainThreadCpuTime,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),start:new RelatedEventSet(navigationStart),end:new RelatedEventSet(fmpMarkerEvent),infos:new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,start:navigationStart.start,fmp:fmpMarkerEvent.start,}]),}});}
 function decorateInteractivitySampleWithDiagnostics_(rendererHelper,eventTimestamp,navigationStartEvent,firstMeaningfulPaintTime,domContentLoadedEndTime,url){if(eventTimestamp===undefined)return undefined;const navigationStartTime=navigationStartEvent.start;const navStartToEventTimeRange=tr.b.math.Range.fromExplicitRange(navigationStartTime,eventTimestamp);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToEventTimeRange);const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToEventTimeRange);const breakdownDiagnostic=createBreakdownDiagnostic(breakdownTree);return{value:navStartToEventTimeRange.duration,diagnostics:tr.v.d.DiagnosticMap.fromObject({'Start':new RelatedEventSet(navigationStartEvent),'Navigation infos':new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,navigationStartTime,firstMeaningfulPaintTime,domContentLoadedEndTime,eventTimestamp,}]),'Breakdown of [navStart, eventTimestamp]':breakdownDiagnostic,}),};}
-function collectLoadingMetricsForRenderer(rendererHelper){const frameToNavStartEvents=EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'navigationStart','blink.user_timing');const firstPaintSamples=collectTimeToEvent('loading','firstPaint',rendererHelper,frameToNavStartEvents);const firstContentfulPaintSamples=collectTimeToEvent('loading','firstContentfulPaint',rendererHelper,frameToNavStartEvents);const onLoadSamples=collectTimeToEvent('blink.user_timing','loadEventStart',rendererHelper,frameToNavStartEvents);return{frameToNavStartEvents,firstPaintSamples,firstContentfulPaintSamples,onLoadSamples,};}
+function collectLoadingMetricsForRenderer(rendererHelper){const frameToNavStartEvents=EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'navigationStart','blink.user_timing');const navIdToNavStartEvents=EventFinderUtils.getSortedMainThreadEventsByNavId(rendererHelper,'navigationStart','blink.user_timing');const firstPaintSamples=collectTimeToEvent(rendererHelper,findTimeToXEntries('loading','firstPaint',rendererHelper,frameToNavStartEvents,navIdToNavStartEvents));const timeToFCPEntries=findTimeToXEntries('loading','firstContentfulPaint',rendererHelper,frameToNavStartEvents,navIdToNavStartEvents);const firstContentfulPaintSamples=collectTimeToEvent(rendererHelper,timeToFCPEntries);const firstContentfulPaintCpuTimeSamples=collectTimeToEventInCpuTime(rendererHelper,timeToFCPEntries);const onLoadSamples=collectTimeToEvent(rendererHelper,findTimeToXEntries('blink.user_timing','loadEventStart',rendererHelper,frameToNavStartEvents,navIdToNavStartEvents));return{frameToNavStartEvents,firstPaintSamples,firstContentfulPaintSamples,firstContentfulPaintCpuTimeSamples,onLoadSamples,};}
 function collectMetricsFromLoadExpectations(model,chromeHelper){const interactiveSamples=[];const firstCpuIdleSamples=[];const firstMeaningfulPaintSamples=[];const firstMeaningfulPaintCpuTimeSamples=[];for(const expectation of model.userModel.expectations){if(!(expectation instanceof tr.model.um.LoadExpectation))continue;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
 const rendererHelper=chromeHelper.rendererHelpers[expectation.renderProcess.pid];if(expectation.fmpEvent!==undefined){addFirstMeaningfulPaintSample(firstMeaningfulPaintSamples,rendererHelper,expectation.navigationStart,expectation.fmpEvent,expectation.url);addFirstMeaningfulPaintCpuTimeSample(firstMeaningfulPaintCpuTimeSamples,rendererHelper,expectation.navigationStart,expectation.fmpEvent,expectation.url);}
 if(expectation.firstCpuIdleTime!==undefined){firstCpuIdleSamples.push(decorateInteractivitySampleWithDiagnostics_(rendererHelper,expectation.firstCpuIdleTime,expectation.navigationStart,expectation.fmpEvent.start,expectation.domContentLoadedEndEvent.start,expectation.url));}
@@ -8161,7 +8382,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 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 timeToFirstCpuIdleHistogram=histograms.createHistogram('timeToFirstCpuIdle',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to First CPU Idle',summaryOptions:SUMMARY_OPTIONS,});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);addSamplesToHistogram(samplesSet.firstPaintSamples,firstPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintSamples,firstContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.onLoadSamples,onLoadHistogram,histograms);}
+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 timeToFirstCpuIdleHistogram=histograms.createHistogram('timeToFirstCpuIdle',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to First CPU Idle',summaryOptions:SUMMARY_OPTIONS,});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);addSamplesToHistogram(samplesSet.firstPaintSamples,firstPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintSamples,firstContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintCpuTimeSamples,firstContentfulPaintCpuTimeHistogram,histograms);addSamplesToHistogram(samplesSet.onLoadSamples,onLoadHistogram,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);}
 tr.metrics.MetricRegistry.register(loadingMetric);return{loadingMetric,getNetworkEventsInRange,};});'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;}
@@ -8216,7 +8437,8 @@
 lookupDiagnostic(guid){return this.sharedDiagnosticsByGuid_.get(guid);}
 resolveRelatedHistograms(){const handleDiagnosticMap=dm=>{for(const[name,diagnostic]of dm){if(diagnostic instanceof tr.v.d.RelatedHistogramMap){diagnostic.resolve(this);}}};for(const hist of this){handleDiagnosticMap(hist.diagnostics);for(const dm of hist.nanDiagnosticMaps){handleDiagnosticMap(dm);}
 for(const bin of hist.allBins){for(const dm of bin.diagnosticMaps){handleDiagnosticMap(dm);}}}}
-importDicts(dicts){for(const dict of dicts){if(dict.type&&tr.v.d.Diagnostic.findTypeInfoWithName(dict.type)){this.sharedDiagnosticsByGuid_.set(dict.guid,tr.v.d.Diagnostic.fromDict(dict));}else{const hist=tr.v.Histogram.fromDict(dict);this.addHistogram(hist);hist.diagnostics.resolveSharedDiagnostics(this,true);}}}
+importDicts(dicts){for(const dict of dicts){this.importDict(dict);}}
+importDict(dict){if(dict.type&&tr.v.d.Diagnostic.findTypeInfoWithName(dict.type)){this.sharedDiagnosticsByGuid_.set(dict.guid,tr.v.d.Diagnostic.fromDict(dict));}else{const hist=tr.v.Histogram.fromDict(dict);this.addHistogram(hist);hist.diagnostics.resolveSharedDiagnostics(this,true);}}
 asDicts(){const dicts=[];for(const diagnostic of this.sharedDiagnosticsByGuid_.values()){dicts.push(diagnostic.asDict());}
 for(const hist of this){dicts.push(hist.asDict());}
 return dicts;}
@@ -8280,7 +8502,8 @@
 return{getPostInteractiveTaskWindows,getNavStartTimestamps,getInteractiveTimestamps,expectedQueueingTime,maxExpectedQueueingTimeInSlidingWindow,weightedExpectedQueueingTime};});'use strict';tr.exportTo('tr.metrics.sh',function(){const WINDOW_SIZE_MS=500;const EQT_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(0.01,WINDOW_SIZE_MS,50);function containsForcedGC_(slice){return slice.findTopmostSlicesRelativeToThisSlice(tr.metrics.v8.utils.isForcedGarbageCollectionEvent).length>0;}
 function createHistogramForEQT_(name,description){const histogram=new tr.v.Histogram(name,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,EQT_BOUNDARIES);histogram.customizeSummaryOptions({avg:false,count:false,max:true,min:false,std:false,sum:false,});histogram.description=description;return histogram;}
 function expectedQueueingTimeMetric(histograms,model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const rendererHelpers=Object.values(chromeHelper.rendererHelpers);const rendererToInteractiveTimestamps=tr.e.chrome.getInteractiveTimestamps(model);addExpectedQueueingTimeMetric_('renderer_eqt',event=>{return{start:event.start,duration:event.duration};},false,rendererHelpers,rendererToInteractiveTimestamps,histograms,model);addExpectedQueueingTimeMetric_('renderer_eqt_cpu',event=>{return{start:event.cpuStart,duration:event.cpuDuration};},true,rendererHelpers,rendererToInteractiveTimestamps,histograms,model);}
-function addExpectedQueueingTimeMetric_(eqtName,getEventTimes,isCpuTime,rendererHelpers,rendererToInteractiveTimestamps,histograms,model){function getTasks(rendererHelper){const tasks=[];for(const slice of rendererHelper.mainThread.sliceGroup.topLevelSlices){const times=getEventTimes(slice);if(times.duration>0&&!containsForcedGC_(slice)){tasks.push({start:times.start,end:times.start+times.duration});}}
+function addExpectedQueueingTimeMetric_(eqtName,getEventTimes,isCpuTime,rendererHelpers,rendererToInteractiveTimestamps,histograms,model){function getTasks(rendererHelper){const tasks=[];for(const slice of
+tr.e.chrome.EventFinderUtils.findToplevelSchedulerTasks(rendererHelper.mainThread)){const times=getEventTimes(slice);if(times.duration>0&&!containsForcedGC_(slice)){tasks.push({start:times.start,end:times.start+times.duration});}}
 return tasks;}
 const totalHistogram=createHistogramForEQT_(`total:${WINDOW_SIZE_MS}ms_window:${eqtName}`,`The maximum EQT in a ${WINDOW_SIZE_MS}ms sliding window`+' for a given renderer');const interactiveHistogram=createHistogramForEQT_(`interactive:${WINDOW_SIZE_MS}ms_window:${eqtName}`,`The maximum EQT in a ${WINDOW_SIZE_MS}ms sliding window`+' for a given renderer while the page is interactive');for(const rendererHelper of rendererHelpers){if(rendererHelper.isChromeTracingUI)continue;if(rendererHelper.mainThread.bounds.duration<WINDOW_SIZE_MS)continue;const tasks=getTasks(rendererHelper);totalHistogram.addSample(tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow(rendererHelper.mainThread.bounds.min,rendererHelper.mainThread.bounds.max,WINDOW_SIZE_MS,tasks));const interactiveTimestamps=rendererToInteractiveTimestamps.get(rendererHelper.pid);if(interactiveTimestamps.length===0)continue;if(interactiveTimestamps.length>1){continue;}
 const interactiveWindow=tr.b.math.Range.fromExplicitRange(interactiveTimestamps[0],Infinity).findIntersection(rendererHelper.mainThread.bounds);interactiveHistogram.addSample(tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow(interactiveWindow.min,interactiveWindow.max,WINDOW_SIZE_MS,tasks));}
@@ -8380,7 +8603,8 @@
 let largestValue=0;let largestType='';for(const key in typeToSize){if(largestValue<typeToSize[key]){largestValue=typeToSize[key];largestType=key;}}
 addProcessScalar({source:'reported_by_chrome',component:[allocatorName,largestType],property:HEAP_CATEGORY_SIZE,value:largestValue});}}
 function addV8MemoryDumpValues(processDump,addProcessScalar){const v8Dump=processDump.getMemoryAllocatorDumpByFullName('v8');if(v8Dump===undefined)return;v8Dump.children.forEach(function(isolateDump){const mallocDump=isolateDump.getDescendantDumpByFullName('malloc');if(mallocDump!==undefined){addV8ComponentValues(mallocDump,['v8','allocated_by_malloc'],addProcessScalar);}
-const heapDump=isolateDump.getDescendantDumpByFullName('heap_spaces');if(heapDump!==undefined){addV8ComponentValues(heapDump,['v8','heap'],addProcessScalar);heapDump.children.forEach(function(spaceDump){if(spaceDump.name==='other_spaces')return;addV8ComponentValues(spaceDump,['v8','heap',spaceDump.name],addProcessScalar);});}});addProcessScalar({source:'reported_by_chrome',component:['v8'],property:CODE_AND_METADATA_SIZE,value:v8Dump.numerics.code_and_metadata_size});addProcessScalar({source:'reported_by_chrome',component:['v8'],property:CODE_AND_METADATA_SIZE,value:v8Dump.numerics.bytecode_and_metadata_size});}
+let heapDump=isolateDump.getDescendantDumpByFullName('heap');if(heapDump===undefined){heapDump=isolateDump.getDescendantDumpByFullName('heap_spaces');}
+if(heapDump!==undefined){addV8ComponentValues(heapDump,['v8','heap'],addProcessScalar);heapDump.children.forEach(function(spaceDump){if(spaceDump.name==='other_spaces')return;addV8ComponentValues(spaceDump,['v8','heap',spaceDump.name],addProcessScalar);});}});addProcessScalar({source:'reported_by_chrome',component:['v8'],property:CODE_AND_METADATA_SIZE,value:v8Dump.numerics.code_and_metadata_size});addProcessScalar({source:'reported_by_chrome',component:['v8'],property:CODE_AND_METADATA_SIZE,value:v8Dump.numerics.bytecode_and_metadata_size});}
 function addV8ComponentValues(componentDump,componentPath,addProcessScalar){CHROME_VALUE_PROPERTIES.forEach(function(property){addProcessScalar({source:'reported_by_chrome',component:componentPath,property,value:componentDump.numerics[property.name]});});}
 const PROCESS_COUNT={unit:count_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){if(componentPath.length>0){throw new Error('Unexpected process count non-empty component path: '+
 componentPath.join(':'));}
@@ -8391,7 +8615,7 @@
 nameParts.push(formatSpec.userFriendlyPropertyName);nameParts.push(formatSpec.componentPreposition);if(componentPath[componentPath.length-1]==='allocated_by_malloc'){nameParts.push('objects allocated by malloc for');nameParts.push(componentPath.slice(0,componentPath.length-1).join(':'));}else{nameParts.push(componentPath.join(':'));}}
 nameParts.push('in');}
 nameParts.push(convertProcessNameToUserFriendlyName(processName));return nameParts.join(' ');}
-const RESIDENT_SIZE={name:'resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'resident set size (RSS)');}};const PEAK_RESIDENT_SIZE={name:'peak_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'peak resident set size');}};const PROPORTIONAL_RESIDENT_SIZE={name:'proportional_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'proportional resident size (PSS)');}};const PRIVATE_DIRTY_SIZE={name:'private_dirty_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private dirty size');}};const PRIVATE_FOOTPRINT_SIZE={name:'private_footprint_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private footprint size');}};const NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT={name:'native_library_private_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library private clean resident size');}};const NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT={name:'native_library_shared_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library shared clean resident size');}};const NATIVE_LIBRARY_PROPORTIONAL_RESIDENT={name:'native_library_proportional_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library proportional resident size');}};function buildOsValueDescriptionPrefix(componentPath,processName,userFriendlyPropertyName){if(componentPath.length>2){throw new Error('OS value component path for \''+
+const RESIDENT_SIZE={name:'resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'resident set size (RSS)');}};const PEAK_RESIDENT_SIZE={name:'peak_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'peak resident set size');}};const PROPORTIONAL_RESIDENT_SIZE={name:'proportional_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'proportional resident size (PSS)');}};const PRIVATE_DIRTY_SIZE={name:'private_dirty_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private dirty size');}};const PRIVATE_FOOTPRINT_SIZE={name:'private_footprint_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private footprint size');}};const JAVA_BASE_CLEAN_RESIDENT={name:'java_base_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'java base odex and vdex total clean resident size');}};const JAVA_BASE_PSS={name:'java_base_pss',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'java base odex and vdex proportional resident size');}};const NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT={name:'native_library_private_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library private clean resident size');}};const NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT={name:'native_library_shared_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library shared clean resident size');}};const NATIVE_LIBRARY_PROPORTIONAL_RESIDENT={name:'native_library_proportional_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library proportional resident size');}};function buildOsValueDescriptionPrefix(componentPath,processName,userFriendlyPropertyName){if(componentPath.length>2){throw new Error('OS value component path for \''+
 userFriendlyPropertyName+'\' too long: '+componentPath.join(':'));}
 const nameParts=[];if(componentPath.length<2){nameParts.push('total');}
 nameParts.push(userFriendlyPropertyName);if(componentPath.length>0){switch(componentPath[0]){case'system_memory':if(componentPath.length>1){const userFriendlyComponentName=SYSTEM_VALUE_COMPONENTS[componentPath[1]].userFriendlyName;if(userFriendlyComponentName===undefined){throw new Error('System value sub-component for \''+
@@ -8404,9 +8628,11 @@
 componentPath.join(':'));}}else{nameParts.push('reported by the OS for');}
 nameParts.push(convertProcessNameToUserFriendlyName(processName));return nameParts.join(' ');}
 function addDetailedMemoryDumpValues(browserNameToGlobalDumps,values){addMemoryDumpValues(browserNameToGlobalDumps,g=>g.levelOfDetail===DETAILED,function(processDump,addProcessScalar){for(const[componentName,componentSpec]of
-Object.entries(SYSTEM_VALUE_COMPONENTS)){const node=getDescendantVmRegionClassificationNode(processDump.vmRegions,componentSpec.classificationPath);const componentPath=['system_memory'];if(componentName)componentPath.push(componentName);addProcessScalar({source:'reported_by_os',component:componentPath,property:PROPORTIONAL_RESIDENT_SIZE,value:node===undefined?0:(node.byteStats.proportionalResident||0)});addProcessScalar({source:'reported_by_os',component:componentPath,property:PRIVATE_DIRTY_SIZE,value:node===undefined?0:(node.byteStats.privateDirtyResident||0)});if(node){if(node.byteStats.nativeLibraryPrivateCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT,value:node===undefined?0:(node.byteStats.nativeLibraryPrivateCleanResident||0)});}
-if(node.byteStats.nativeLibrarySharedCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT,value:node===undefined?0:(node.byteStats.nativeLibrarySharedCleanResident||0)});}
-if(node.byteStats.nativeLibraryProportionalResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_PROPORTIONAL_RESIDENT,value:node===undefined?0:(node.byteStats.nativeLibraryProportionalResident||0)});}}}
+Object.entries(SYSTEM_VALUE_COMPONENTS)){const node=getDescendantVmRegionClassificationNode(processDump.vmRegions,componentSpec.classificationPath);const componentPath=['system_memory'];if(componentName)componentPath.push(componentName);addProcessScalar({source:'reported_by_os',component:componentPath,property:PROPORTIONAL_RESIDENT_SIZE,value:node===undefined?0:(node.byteStats.proportionalResident||0)});addProcessScalar({source:'reported_by_os',component:componentPath,property:PRIVATE_DIRTY_SIZE,value:node===undefined?0:(node.byteStats.privateDirtyResident||0)});if(node){if(node.byteStats.javaBasePss){addProcessScalar({source:'reported_by_os',component:componentPath,property:JAVA_BASE_PSS,value:node.byteStats.javaBasePss});}
+if(node.byteStats.javaBaseCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:JAVA_BASE_CLEAN_RESIDENT,value:node.byteStats.javaBaseCleanResident});}}
+if(node){if(node.byteStats.nativeLibraryPrivateCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT,value:node.byteStats.nativeLibraryPrivateCleanResident});}
+if(node.byteStats.nativeLibrarySharedCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT,value:node.byteStats.nativeLibrarySharedCleanResident});}
+if(node.byteStats.nativeLibraryProportionalResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_PROPORTIONAL_RESIDENT,value:node.byteStats.nativeLibraryProportionalResident});}}}
 const memtrackDump=processDump.getMemoryAllocatorDumpByFullName('gpu/android_memtrack');if(memtrackDump!==undefined){memtrackDump.children.forEach(function(memtrackChildDump){addProcessScalar({source:'reported_by_os',component:['gpu_memory',memtrackChildDump.name],property:PROPORTIONAL_RESIDENT_SIZE,value:memtrackChildDump.numerics.memtrack_pss});});}},function(componentTree){},values);}
 const SYSTEM_VALUE_COMPONENTS={'':{classificationPath:[],},'java_heap':{classificationPath:['Android','Java runtime','Spaces'],userFriendlyName:'the Java heap'},'ashmem':{classificationPath:['Android','Ashmem'],userFriendlyName:'ashmem'},'native_heap':{classificationPath:['Native heap'],userFriendlyName:'the native heap'},'stack':{classificationPath:['Stack'],userFriendlyName:'the thread stacks'}};function getDescendantVmRegionClassificationNode(node,path){for(let i=0;i<path.length;i++){if(node===undefined)break;node=node.children.find(c=>c.title===path[i]);}
 return node;}
@@ -8512,21 +8738,26 @@
 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){addDurationOfTopEvents(histograms,model);addTotalDurationOfTopEvents(histograms,model);addDurationOfSubEvents(histograms,model);addPercentageInV8ExecuteOfTopEvents(histograms,model);addTotalPercentageInV8Execute(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){addDurationOfTopEvents(histograms,model);addTotalDurationOfTopEvents(histograms,model);addDurationOfSubEvents(histograms,model);addPercentageInV8ExecuteOfTopEvents(histograms,model);addTotalPercentageInV8Execute(histograms,model);addMarkCompactorMutatorUtilization(histograms,model);addTotalMarkCompactorTime(histograms,model);addTotalMarkCompactorMarkingTime(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 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;}
 function createNumericForIdleTime(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:true,percentile:[]});return n;}
 function createPercentage(name,numerator,denominator,unit){const hist=new tr.v.Histogram(name,unit);if(denominator===0){hist.addSample(0);}else{hist.addSample(numerator/denominator);}
 hist.customizeSummaryOptions({avg:true,count:false,max:false,min:false,std:false,sum:false,percentile:[]});return hist;}
-function isNotForcedTopGarbageCollectionEvent(event){return tr.metrics.v8.utils.isTopGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}
-function isNotForcedSubGarbageCollectionEvent(event){return tr.metrics.v8.utils.isSubGarbageCollectionEvent(event)&&!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);}
-function addDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
-function addTotalDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){const cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
-function addDurationOfSubEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedSubGarbageCollectionEvent,tr.metrics.v8.utils.subGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForSubEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
-function addPercentageInV8ExecuteOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){addPercentageInV8Execute(histograms,model,name,events);});}
-function addTotalPercentageInV8Execute(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){addPercentageInV8Execute(histograms,model,name,events);});}
+function addDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
+function addTotalDurationOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){const cpuDuration=createNumericForTopEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
+function isV8MarkCompactorSummary(event){return!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event)&&tr.metrics.v8.utils.isMarkCompactorSummaryEvent(event);}
+function isV8MarkCompactorMarkingSummary(event){return!tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event)&&tr.metrics.v8.utils.isMarkCompactorMarkingSummaryEvent(event);}
+function createHistogramFromSummary(histograms,name,events){const foregroundDuration=createNumericForTopEventTime(name+'-foreground');const backgroundDuration=createNumericForTopEventTime(name+'-background');const totalDuration=createNumericForTopEventTime(name+'-total');const relatedNames=new tr.v.d.RelatedNameMap();relatedNames.set('foreground',foregroundDuration.name);relatedNames.set('background',backgroundDuration.name);for(const event of events){foregroundDuration.addSample(event.args.duration);backgroundDuration.addSample(event.args.background_duration);const breakdownForTotal=new tr.v.d.Breakdown();breakdownForTotal.set('foreground',event.args.duration);breakdownForTotal.set('background',event.args.background_duration);totalDuration.addSample(event.args.duration+event.args.background_duration,{breakdown:breakdownForTotal});}
+histograms.addHistogram(foregroundDuration);histograms.addHistogram(backgroundDuration);histograms.addHistogram(totalDuration,{breakdown:relatedNames});}
+function addTotalMarkCompactorTime(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isV8MarkCompactorSummary,event=>'v8-gc-mark-compactor',(name,events)=>createHistogramFromSummary(histograms,name,events));}
+function addTotalMarkCompactorMarkingTime(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,isV8MarkCompactorMarkingSummary,event=>'v8-gc-mark-compactor-marking',(name,events)=>createHistogramFromSummary(histograms,name,events));}
+function addDurationOfSubEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,tr.metrics.v8.utils.isNotForcedSubGarbageCollectionEvent,tr.metrics.v8.utils.subGarbageCollectionEventName,function(name,events){const cpuDuration=createNumericForSubEventTime(name);events.forEach(function(event){cpuDuration.addSample(event.cpuDuration);});histograms.addHistogram(cpuDuration);});}
+function addPercentageInV8ExecuteOfTopEvents(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,tr.metrics.v8.utils.topGarbageCollectionEventName,function(name,events){addPercentageInV8Execute(histograms,model,name,events);});}
+function addTotalPercentageInV8Execute(histograms,model){tr.metrics.v8.utils.groupAndProcessEvents(model,tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent,event=>'v8-gc-total',function(name,events){addPercentageInV8Execute(histograms,model,name,events);});}
 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 computeDomContentLoadedTime_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let domContentLoadedTime=0;for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(ev.title==='domContentLoadedEventEnd'&&ev.start>domContentLoadedTime){domContentLoadedTime=ev.start;}}}
 return domContentLoadedTime;}
 function computeInteractiveTime_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let interactiveTime=0;for(const expectation of model.userModel.expectations){if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
@@ -8558,7 +8789,7 @@
 tr.metrics.MetricRegistry.register(runtimeStatsTotalMetric);tr.metrics.MetricRegistry.register(runtimeStatsMetric);return{runtimeStatsMetric,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.vr',function(){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();histogramsByEventTitle.set('VrShellGl::DrawFrame',createHistograms(histograms,'draw_frame',{description:'Duration to render one frame'},true));histogramsByEventTitle.set('VrShellGl::AcquireFrame',createHistograms(histograms,'acquire_frame',{description:'Duration acquire a frame from GVR'},true));histogramsByEventTitle.set('VrShellGl::UpdateController',createHistograms(histograms,'update_controller',{description:'Duration to query input from the controller'},true));histogramsByEventTitle.set('VrShellGl::DrawFrameSubmitNow',createHistograms(histograms,'submit_frame',{description:'Duration to submit a frame to GVR'},true));histogramsByEventTitle.set('VrShellGl::PostSubmitDrawOnGpu',createHistograms(histograms,'post_submit_draw_on_gpu',{description:'Duration to draw a frame on GPU post submit to '+'GVR. Note this duration may include time spent on '+'reprojection'},false));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',createHistograms(histograms,'update_animations_and_opacity',{description:'Duration to apply animation and opacity changes'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateBindings',createHistograms(histograms,'update_bindings',{description:'Duration to push binding values'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateLayout',createHistograms(histograms,'update_layout',{description:'Duration to compute element sizes, layout and textures'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateWorldSpaceTransform',createHistograms(histograms,'update_world_space_transforms',{description:'Duration to calculate element transforms in world space'},true));histogramsByEventTitle.set('UiRenderer::DrawUiView',createHistograms(histograms,'draw_ui',{description:'Duration to draw the UI'},true));histogramsByEventTitle.set('UiElementRenderer::DrawTexturedQuad',createHistograms(histograms,'draw_textured_quad',{description:'Duration to draw a textured element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawGradientQuad',createHistograms(histograms,'draw_gradient_quad',{description:'Duration to draw a gradient element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawGradientGridQuad',createHistograms(histograms,'draw_gradient_grid_quad',{description:'Duration to draw a gradient grid element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawController',createHistograms(histograms,'draw_controller',{description:'Duration to draw the controller'},true));histogramsByEventTitle.set('UiElementRenderer::DrawLaser',createHistograms(histograms,'draw_laser',{description:'Duration to draw the laser'},true));histogramsByEventTitle.set('UiElementRenderer::DrawReticle',createHistograms(histograms,'draw_reticle',{description:'Duration to draw the reticle'},true));histogramsByEventTitle.set('UiElementRenderer::DrawShadow',createHistograms(histograms,'draw_shadow',{description:'Duration to draw a shadow element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawStars',createHistograms(histograms,'draw_stars',{description:'Duration to draw the stars'},true));histogramsByEventTitle.set('UiElementRenderer::DrawBackground',createHistograms(histograms,'draw_background',{description:'Duration to draw the textured background'},true));histogramsByEventTitle.set('UiElementRenderer::DrawKeyboard',createHistograms(histograms,'draw_keyboard',{description:'Duration to draw the keyboard'},true));const drawUiSubSlicesMap=new Map();const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let rangeOfInterest=model.bounds;const userExpectationsOfInterest=[tr.model.um.AnimationExpectation];if(opt_options&&opt_options.rangeOfInterest){rangeOfInterest=opt_options.rangeOfInterest;userExpectationsOfInterest.push(tr.model.um.ResponseExpectation);}
+function frameCycleDurationMetric(histograms,model,opt_options){const histogramsByEventTitle=new Map();histogramsByEventTitle.set('Vr.DrawFrame',createHistograms(histograms,'draw_frame',{description:'Duration to render one frame'},true));histogramsByEventTitle.set('Vr.AcquireGvrFrame',createHistograms(histograms,'acquire_frame',{description:'Duration acquire a frame from GVR'},true));histogramsByEventTitle.set('Vr.ProcessControllerInput',createHistograms(histograms,'update_controller',{description:'Duration to query input from the controller'},true));histogramsByEventTitle.set('Vr.ProcessControllerInputForWebXr',createHistograms(histograms,'update_controller_webxr',{description:'Duration to query input from the controller '+'for WebXR'},true));histogramsByEventTitle.set('Vr.SubmitFrameNow',createHistograms(histograms,'submit_frame',{description:'Duration to submit a frame to GVR'},true));histogramsByEventTitle.set('Vr.PostSubmitDrawOnGpu',createHistograms(histograms,'post_submit_draw_on_gpu',{description:'Duration to draw a frame on GPU post submit to '+'GVR. Note this duration may include time spent on '+'reprojection'},false));histogramsByEventTitle.set('VrShellGl::DrawFrame',histogramsByEventTitle.get('Vr.DrawFrame'));histogramsByEventTitle.set('VrShellGl::AcquireFrame',histogramsByEventTitle.get('Vr.AcquireGvrFrame'));histogramsByEventTitle.set('VrShellGl::UpdateController',histogramsByEventTitle.get('Vr.ProcessControllerInput'));histogramsByEventTitle.set('VrShellGl::DrawFrameSubmitNow',histogramsByEventTitle.get('Vr.SubmitFrameNow'));histogramsByEventTitle.set('VrShellGl::PostSubmitDrawOnGpu',histogramsByEventTitle.get('Vr.PostSubmitDrawOnGpu'));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',createHistograms(histograms,'update_animations_and_opacity',{description:'Duration to apply animation and opacity changes'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateBindings',createHistograms(histograms,'update_bindings',{description:'Duration to push binding values'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateLayout',createHistograms(histograms,'update_layout',{description:'Duration to compute element sizes, layout and textures'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateWorldSpaceTransform',createHistograms(histograms,'update_world_space_transforms',{description:'Duration to calculate element transforms in world space'},true));histogramsByEventTitle.set('UiRenderer::DrawUiView',createHistograms(histograms,'draw_ui',{description:'Duration to draw the UI'},true));histogramsByEventTitle.set('UiElementRenderer::DrawTexturedQuad',createHistograms(histograms,'draw_textured_quad',{description:'Duration to draw a textured element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawGradientQuad',createHistograms(histograms,'draw_gradient_quad',{description:'Duration to draw a gradient element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawGradientGridQuad',createHistograms(histograms,'draw_gradient_grid_quad',{description:'Duration to draw a gradient grid element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawController',createHistograms(histograms,'draw_controller',{description:'Duration to draw the controller'},true));histogramsByEventTitle.set('UiElementRenderer::DrawLaser',createHistograms(histograms,'draw_laser',{description:'Duration to draw the laser'},true));histogramsByEventTitle.set('UiElementRenderer::DrawReticle',createHistograms(histograms,'draw_reticle',{description:'Duration to draw the reticle'},true));histogramsByEventTitle.set('UiElementRenderer::DrawShadow',createHistograms(histograms,'draw_shadow',{description:'Duration to draw a shadow element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawStars',createHistograms(histograms,'draw_stars',{description:'Duration to draw the stars'},true));histogramsByEventTitle.set('UiElementRenderer::DrawBackground',createHistograms(histograms,'draw_background',{description:'Duration to draw the textured background'},true));histogramsByEventTitle.set('UiElementRenderer::DrawKeyboard',createHistograms(histograms,'draw_keyboard',{description:'Duration to draw the keyboard'},true));const drawUiSubSlicesMap=new Map();const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let rangeOfInterest=model.bounds;const userExpectationsOfInterest=[tr.model.um.AnimationExpectation];if(opt_options&&opt_options.rangeOfInterest){rangeOfInterest=opt_options.rangeOfInterest;userExpectationsOfInterest.push(tr.model.um.ResponseExpectation);}
 for(const ue of model.userModel.expectations){if(ue.initiatorType!==tr.model.um.INITIATOR_TYPE.VR){continue;}
 if(!userExpectationsOfInterest.some(function(ueOfInterest){return ue instanceof ueOfInterest;})){continue;}
 if(!rangeOfInterest.intersectsExplicitRangeInclusive(ue.start,ue.end)){continue;}
@@ -8862,11 +9093,17 @@
 this.margin.right=Math.max(this.margin.right,xAxisTickOverhangPx);},getXForDatum_(datum,index){return index;},get xAxisTickOffset(){return 0.5;},updateXAxis_(xAxis){xAxis.selectAll('*').remove();if(this.hideXAxis)return;const nameTexts=xAxis.selectAll('text').data(this.data_);nameTexts.enter().append('text').attr('transform',(d,index)=>'translate(0, '+
 this.textHeightPx_*(this.data_.length-index)+')').attr('x',(d,index)=>this.xScale_(index)).attr('y',d=>this.graphHeight).text(d=>d.x);nameTexts.exit().remove();const guideLines=xAxis.selectAll('line.guide').data(this.data_);guideLines.enter().append('line').attr('x1',(d,index)=>this.xScale_(index+this.xAxisTickOffset)).attr('x2',(d,index)=>this.xScale_(index+this.xAxisTickOffset)).attr('y1',()=>this.graphHeight).attr('y2',(d,index)=>this.graphHeight+Math.max(MIN_GUIDELINE_HEIGHT_PX,(this.textHeightPx_*(this.data_.length-index-1))));}};return{NameColumnChart,};});'use strict';tr.exportTo('tr.ui.b',function(){const LineChart=tr.ui.b.LineChart;const NameLineChart=tr.ui.b.define('name-line-chart',LineChart);NameLineChart.prototype={__proto__:LineChart.prototype,getXForDatum_(datum,index){return index;},get xAxisHeight(){return 5+(this.textHeightPx_*this.data_.length);},get xAxisTickOffset(){return 0;},updateMargins_(){tr.ui.b.NameColumnChart.prototype.updateMargins_.call(this);},updateXAxis_(xAxis){xAxis.selectAll('*').remove();if(this.hideXAxis)return;tr.ui.b.NameColumnChart.prototype.updateXAxis_.call(this,xAxis);const baseline=xAxis.selectAll('path').data([this]);baseline.enter().append('line').attr('stroke','black').attr('x1',this.xScale_(0)).attr('x2',this.xScale_(this.data_.length-1)).attr('y1',this.graphHeight).attr('y2',this.graphHeight);baseline.exit().remove();}};return{NameLineChart,};});'use strict';tr.exportTo('tr.ui.b',function(){const BoxChart=tr.ui.b.define('box-chart',tr.ui.b.NameLineChart);BoxChart.prototype={__proto__:tr.ui.b.NameLineChart.prototype,get hideLegend(){return true;},updateDataRange_(){if(this.overrideDataRange_!==undefined){return;}
 this.autoDataRange_.reset();for(const datum of this.data_){this.autoDataRange_.addValue(datum.percentile_0);this.autoDataRange_.addValue(datum.percentile_100);}},updateScales_(){super.updateScales_();this.xScale_.domain([0,this.data_.length]);},get xAxisTickOffset(){return 0.5;},updateDataRange_(){if(this.overrideDataRange_!==undefined)return;this.autoDataRange_.reset();for(const datum of this.data_){this.autoDataRange_.addValue(datum.percentile_0);this.autoDataRange_.addValue(datum.percentile_100);}},updateXAxis_(xAxis){xAxis.selectAll('*').remove();if(this.hideXAxis)return;tr.ui.b.NameColumnChart.prototype.updateXAxis_.call(this,xAxis);const baseline=xAxis.selectAll('path').data([this]);baseline.enter().append('line').attr('stroke','black').attr('x1',this.xScale_(0)).attr('x2',this.xScale_(this.data_.length)).attr('y1',this.graphHeight).attr('y2',this.graphHeight);baseline.exit().remove();},updateDataContents_(dataSel){dataSel.selectAll('*').remove();const boxesSel=dataSel.selectAll('path');for(let index=0;index<this.data_.length;++index){const datum=this.data_[index];const color=datum.color||'black';let sel=boxesSel.data([datum]);sel.enter().append('rect').attr('fill',color).attr('x',this.xScale_(index+0.2)).attr('width',this.xScale_(index+0.8)-this.xScale_(index+0.2)).attr('y',this.yScale_(datum.percentile_75)).attr('height',this.yScale_(datum.percentile_25)-
-this.yScale_(datum.percentile_75));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index)).attr('x2',this.xScale_(index+1)).attr('y1',this.yScale_(datum.percentile_50)).attr('y2',this.yScale_(datum.percentile_50));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index+0.4)).attr('x2',this.xScale_(index+0.6)).attr('y1',this.yScale_(datum.percentile_0)).attr('y2',this.yScale_(datum.percentile_0));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index+0.4)).attr('x2',this.xScale_(index+0.6)).attr('y1',this.yScale_(datum.percentile_100)).attr('y2',this.yScale_(datum.percentile_100));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index+0.5)).attr('x2',this.xScale_(index+0.5)).attr('y1',this.yScale_(datum.percentile_100)).attr('y2',this.yScale_(datum.percentile_0));sel.exit().remove();}}};return{BoxChart,};});'use strict';tr.exportTo('tr.ui.b',function(){const BarChart=tr.ui.b.define('bar-chart',tr.ui.b.ColumnChart);BarChart.prototype={__proto__:tr.ui.b.ColumnChart.prototype,decorate(){super.decorate();this.verticalScale_=undefined;this.horizontalScale_=undefined;},updateScales_(){super.updateScales_();this.yScale_.range([this.graphWidth,0]);this.xScale_.range([0,this.graphHeight]);this.verticalScale_=this.isYLogScale_?d3.scale.log(10):d3.scale.linear();this.verticalScale_.domain(this.xScale_.domain());this.verticalScale_.range([this.graphHeight,0]);this.horizontalScale_=d3.scale.linear();this.horizontalScale_.domain(this.yScale_.domain());this.horizontalScale_.range([0,this.graphWidth]);},get defaultGraphHeight(){return Math.max(20,10*this.data_.length);},get defaultGraphWidth(){return 100;},get barHeight(){return this.graphHeight/this.data.length;},drawBrush_(brushRectsSel){brushRectsSel.attr('x',0).attr('width',this.graphWidth).attr('y',d=>this.verticalScale_(d.max)).attr('height',d=>this.verticalScale_(d.min)-this.verticalScale_(d.max)).attr('fill','rgb(213, 236, 229)');},getDataPointAtChartPoint_(chartPoint){const flippedPoint={x:this.graphHeight-chartPoint.y,y:this.graphWidth-chartPoint.x};return super.getDataPointAtChartPoint_(flippedPoint);},drawXAxis_(xAxis){xAxis.attr('transform','translate(0,'+this.graphHeight+')').call(d3.svg.axis().scale(this.horizontalScale_).orient('bottom'));},get yAxisWidth(){return this.computeScaleTickWidth_(this.verticalScale_);},drawYAxis_(yAxis){const axisModifier=d3.svg.axis().scale(this.verticalScale_).orient('left');yAxis.call(axisModifier);},drawHoverValueBox_(rect){const rectHoverEvent=new tr.b.Event('rect-mouseenter');rectHoverEvent.rect=rect;this.dispatchEvent(rectHoverEvent);if(!this.enableHoverBox)return;const seriesKeys=[...this.seriesByKey_.keys()];const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.hover').remove();let keyWidthPx=0;let keyHeightPx=0;let xWidthPx=0;let xHeightPx=0;if(seriesKeys.length>1){keyWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.key).width;keyHeightPx=this.textHeightPx_;}
-if(this.data.length>1){xWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,''+rect.datum.x).width;xHeightPx=this.textHeightPx_;}
-const valueWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.value).width;const valueHeightPx=this.textHeightPx_;const hoverWidthPx=Math.min(Math.max(keyWidthPx,xWidthPx,valueWidthPx)+5,Math.max(50,rect.widthPx));const hoverTopPx=rect.topPx+(rect.heightPx/2);const hoverLeftPx=rect.leftPx+rect.widthPx-hoverWidthPx;chartAreaSel.append('rect').attr('class','hover').attr('fill','white').attr('x',hoverLeftPx).attr('y',hoverTopPx).attr('width',hoverWidthPx).attr('height',keyHeightPx+xHeightPx+valueHeightPx);if(seriesKeys.length>1){chartAreaSel.append('text').attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx-3).text(rect.key);}
-if(this.data.length>1){chartAreaSel.append('text').attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx+valueHeightPx-3).text(''+rect.datum.x);}
-chartAreaSel.append('text').attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+xHeightPx+keyHeightPx+valueHeightPx-3).text(rect.value);},flipRect_(rect){return{datum:rect.datum,index:rect.index,key:rect.key,value:rect.value,color:rect.color,topPx:this.graphHeight-rect.leftPx-rect.widthPx,leftPx:this.graphWidth-rect.topPx-rect.heightPx,widthPx:rect.heightPx,heightPx:rect.widthPx,underflow:rect.underflow,overflow:rect.overflow,};},drawRect_(rect,sel){super.drawRect_(this.flipRect_(rect),sel);},drawUnderflow_(rect,rectsSel){let sel=rectsSel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',0).attr('y',this.graphHeight-rect.leftPx+
+this.yScale_(datum.percentile_75));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index)).attr('x2',this.xScale_(index+1)).attr('y1',this.yScale_(datum.percentile_50)).attr('y2',this.yScale_(datum.percentile_50));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index+0.4)).attr('x2',this.xScale_(index+0.6)).attr('y1',this.yScale_(datum.percentile_0)).attr('y2',this.yScale_(datum.percentile_0));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index+0.4)).attr('x2',this.xScale_(index+0.6)).attr('y1',this.yScale_(datum.percentile_100)).attr('y2',this.yScale_(datum.percentile_100));sel.exit().remove();sel=boxesSel.data([datum]);sel.enter().append('line').attr('stroke',color).attr('x1',this.xScale_(index+0.5)).attr('x2',this.xScale_(index+0.5)).attr('y1',this.yScale_(datum.percentile_100)).attr('y2',this.yScale_(datum.percentile_0));sel.exit().remove();}}};return{BoxChart,};});'use strict';tr.exportTo('tr.ui.b',function(){const BarChart=tr.ui.b.define('bar-chart',tr.ui.b.ColumnChart);BarChart.prototype={__proto__:tr.ui.b.ColumnChart.prototype,decorate(){super.decorate();this.verticalScale_=undefined;this.horizontalScale_=undefined;this.isWaterfall_=false;},updateScales_(){super.updateScales_();this.yScale_.range([this.graphWidth,0]);this.xScale_.range([0,this.graphHeight]);this.verticalScale_=this.isYLogScale_?d3.scale.log(10):d3.scale.linear();this.verticalScale_.domain(this.xScale_.domain());this.verticalScale_.range([this.graphHeight,0]);this.horizontalScale_=d3.scale.linear();this.horizontalScale_.domain(this.yScale_.domain());this.horizontalScale_.range([0,this.graphWidth]);},set isWaterfall(waterfall){this.isWaterfall_=waterfall;if(waterfall){this.getDataSeries('hide').color='transparent';}
+this.updateContents_();},get isWaterfall(){return this.isWaterfall_;},get defaultGraphHeight(){return Math.max(20,10*this.data_.length);},get defaultGraphWidth(){return 100;},get barHeight(){return this.graphHeight/this.data.length;},drawBrush_(brushRectsSel){brushRectsSel.attr('x',0).attr('width',this.graphWidth).attr('y',d=>this.verticalScale_(d.max)).attr('height',d=>this.verticalScale_(d.min)-this.verticalScale_(d.max)).attr('fill','rgb(213, 236, 229)');},getDataPointAtChartPoint_(chartPoint){const flippedPoint={x:this.graphHeight-chartPoint.y,y:this.graphWidth-chartPoint.x};return super.getDataPointAtChartPoint_(flippedPoint);},drawXAxis_(xAxis){xAxis.attr('transform','translate(0,'+this.graphHeight+')').call(d3.svg.axis().scale(this.horizontalScale_).orient('bottom'));},get yAxisWidth(){return this.computeScaleTickWidth_(this.verticalScale_);},drawYAxis_(yAxis){const axisModifier=d3.svg.axis().scale(this.verticalScale_).orient('left');yAxis.call(axisModifier);},drawHoverValueBox_(rect){const rectHoverEvent=new tr.b.Event('rect-mouseenter');rectHoverEvent.rect=rect;this.dispatchEvent(rectHoverEvent);if(!this.enableHoverBox||(this.isWaterfall_&&rect.key==='hide')){return;}
+const seriesKeys=[...this.seriesByKey_.keys()];const chartAreaSel=d3.select(this.chartAreaElement);chartAreaSel.selectAll('.hover').remove();let keyWidthPx=0;let keyHeightPx=0;let xWidthPx=0;let xHeightPx=0;let groupWidthPx=0;let groupHeightPx=0;if(seriesKeys.length>1&&!this.isWaterfall_){keyWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.key).width;keyHeightPx=this.textHeightPx_;}
+if(this.data.length>1&&!this.isWaterfall_){xWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,''+rect.datum.x).width;xHeightPx=this.textHeightPx_;}
+if(this.isGrouped&&rect.datum.group!==undefined){groupWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.datum.group).width;groupHeightPx=this.textHeightPx_;}
+const valueWidthPx=tr.ui.b.getSVGTextSize(this.chartAreaElement,rect.value).width;const valueHeightPx=this.textHeightPx_;const maxWidthPx=Math.max(keyWidthPx,xWidthPx,groupWidthPx,valueWidthPx)+5;const hoverWidthPx=this.isGrouped?maxWidthPx:Math.min(maxWidthPx,Math.max(50,rect.widthPx));const hoverTopPx=rect.topPx+(rect.heightPx/2);const hoverLeftPx=rect.leftPx+rect.widthPx-hoverWidthPx;chartAreaSel.append('rect').attr('class','hover').attr('fill','white').attr('x',hoverLeftPx).attr('y',hoverTopPx).attr('width',hoverWidthPx).attr('height',keyHeightPx+xHeightPx+
+valueHeightPx+groupHeightPx);if(seriesKeys.length>1&&!this.isWaterfall_){chartAreaSel.append('text').attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx-3).text(rect.key);}
+if(this.data.length>1&&!this.isWaterfall_){chartAreaSel.append('text').attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx+valueHeightPx-3).text(''+rect.datum.x);}
+if(this.isGrouped&&rect.datum.group!==undefined){chartAreaSel.append('text').on('mouseleave',()=>this.clearHoverValueBox_(rect)).attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+keyHeightPx+xHeightPx+groupHeightPx-3).text(rect.datum.group);}
+chartAreaSel.append('text').attr('class','hover').attr('fill',rect.color).attr('x',hoverLeftPx+2).attr('y',hoverTopPx+xHeightPx+keyHeightPx+
+groupHeightPx+valueHeightPx-3).text(rect.value);},flipRect_(rect){return{datum:rect.datum,index:rect.index,key:rect.key,value:rect.value,color:rect.color,topPx:this.graphHeight-rect.leftPx-rect.widthPx,leftPx:this.graphWidth-rect.topPx-rect.heightPx,widthPx:rect.heightPx,heightPx:rect.widthPx,underflow:rect.underflow,overflow:rect.overflow,};},drawRect_(rect,sel){super.drawRect_(this.flipRect_(rect),sel);},drawUnderflow_(rect,rectsSel){let sel=rectsSel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',0).attr('y',this.graphHeight-rect.leftPx+
 3+(rect.widthPx/2));sel.exit().remove();sel=rectsSel.data([rect]);sel.enter().append('rect').attr('fill','rgba(0, 0, 0, 0)').attr('x',0).attr('y',this.graphHeight-rect.leftPx-rect.widthPx).attr('width',10).attr('height',rect.widthPx).on('mouseenter',()=>this.drawHoverValueBox_(this.flipRect_(rect))).on('mouseleave',()=>this.clearHoverValueBox_(rect));sel.exit().remove();},drawOverflow_(rect,sel){sel=sel.data([rect]);sel.enter().append('text').text('*').attr('fill',rect.color).attr('x',this.graphWidth).attr('y',this.graphHeight-rect.leftPx+
 3+(rect.widthPx/2));sel.exit().remove();}};return{BarChart,};});'use strict';tr.exportTo('tr.ui.b',function(){const NameBarChart=tr.ui.b.define('name-bar-chart',tr.ui.b.BarChart);const Y_AXIS_PADDING=2;NameBarChart.prototype={__proto__:tr.ui.b.BarChart.prototype,getDataPointAtChartPoint_(chartPoint){return{x:tr.ui.b.BarChart.prototype.getDataPointAtChartPoint_.call(this,chartPoint).x,y:parseInt(Math.floor((this.graphHeight-chartPoint.y)/this.barHeight))};},getXForDatum_(datum,index){return index;},get yAxisWidth(){if(this.data.length===0)return 0;return Y_AXIS_PADDING+tr.b.math.Statistics.max(this.data_,d=>tr.ui.b.getSVGTextSize(this,d.x).width);},get defaultGraphHeight(){return(3+this.textHeightPx_)*this.data.length;},updateYAxis_(yAxis){if(tr.ui.b.getSVGTextSize(this,'test').width===0){tr.b.requestAnimationFrame(()=>this.updateYAxis_(yAxis));return;}
 yAxis.selectAll('*').remove();const nameTexts=yAxis.selectAll('text').data(this.data_);nameTexts.enter().append('text').attr('x',d=>-(tr.ui.b.getSVGTextSize(this,d.x).width+Y_AXIS_PADDING)).attr('y',(d,index)=>this.verticalScale_(index)).text(d=>d.x);nameTexts.exit().remove();let previousTop=undefined;for(const text of nameTexts[0]){const bbox=text.getBBox();if((previousTop===undefined)||(previousTop>(bbox.y+bbox.height))){previousTop=bbox.y;}else{text.style.opacity=0;}}}};return{NameBarChart,};});'use strict';tr.exportTo('tr.v.ui',function(){const DIAGNOSTIC_SPAN_BEHAVIOR={created(){this.diagnostic_=undefined;this.name_=undefined;this.histogram_=undefined;},attached(){if(this.diagnostic_)this.updateContents_();},get diagnostic(){return this.diagnostic_;},build(diagnostic,name,histogram){this.diagnostic_=diagnostic;this.name_=name;this.histogram_=histogram;if(this.isAttached)this.updateContents_();},updateContents_(){throw new Error('dom-modules must override updateContents_()');}};return{DIAGNOSTIC_SPAN_BEHAVIOR,};});'use strict';tr.exportTo('tr.v.ui',function(){const DEFAULT_COLOR_SCHEME=new tr.b.SinebowColorGenerator();function getHistogramName(histogram,diagnosticName,key){if(histogram===undefined)return undefined;const nameMap=histogram.diagnostics.get(diagnosticName);if(nameMap===undefined)return undefined;return nameMap.get(key);}
@@ -9275,9 +9512,9 @@
 h=h*0.5;}}else{for(let i=0;i<this.tracks.length;++i){this.tracks[i].height=this.tracks[0].height;this.tracks[i].style.display='';}}}};return{ThreadTrack,};});'use strict';tr.exportTo('tr.ui.tracks',function(){const OtherThreadsTrack=tr.ui.b.define('other-threads-track',tr.ui.tracks.OtherThreadsTrack);const SpacingTrack=tr.ui.tracks.SpacingTrack;OtherThreadsTrack.prototype={__proto__:tr.ui.tracks.ContainerTrack.prototype,decorate(viewport){tr.ui.tracks.ContainerTrack.prototype.decorate.call(this,viewport);this.header_=document.createElement('tr-ui-b-heading');this.header_.addEventListener('click',this.onHeaderClick_.bind(this));this.header_.heading='Other Threads';this.header_.tooltip='Threads with only scheduling information';this.header_.arrowVisible=true;this.threads_=[];this.expanded=false;this.collapsible_=true;},set threads(threads){this.threads_=threads;this.updateContents_();},set collapsible(collapsible){this.collapsible_=collapsible;this.updateContents_();},onHeaderClick_(e){e.stopPropagation();e.preventDefault();this.expanded=!this.expanded;},get expanded(){return this.header_.expanded;},set expanded(expanded){expanded=!!expanded;if(this.expanded===expanded)return;this.header_.expanded=expanded;this.viewport_.dispatchChangeEvent();this.updateContents_();},updateContents_(){this.detach();if(this.collapsible_){Polymer.dom(this).appendChild(this.header_);}
 if(this.expanded||!this.collapsible_){for(const thread of this.threads_){const track=new tr.ui.tracks.ThreadTrack(this.viewport);track.thread=thread;if(!track.hasVisibleContent)return;Polymer.dom(this).appendChild(track);Polymer.dom(this).appendChild(new SpacingTrack(this.viewport));}}}};return{OtherThreadsTrack,};});'use strict';tr.exportTo('tr.ui.tracks',function(){const ColorScheme=tr.b.ColorScheme;const ProcessSummaryTrack=tr.ui.b.define('process-summary-track',tr.ui.tracks.RectTrack);ProcessSummaryTrack.buildRectsFromProcess=function(process){if(!process)return[];const ops=[];const pushOp=function(isStart,time,slice){ops.push({isStart,time,slice});};for(const tid in process.threads){const sliceGroup=process.threads[tid].sliceGroup;sliceGroup.topLevelSlices.forEach(function(slice){pushOp(true,slice.start,undefined);pushOp(false,slice.end,undefined);});sliceGroup.slices.forEach(function(slice){if(slice.important){pushOp(true,slice.start,slice);pushOp(false,slice.end,slice);}});}
 ops.sort(function(a,b){return a.time-b.time;});const rects=[];const genericColorId=ColorScheme.getColorIdForReservedName('generic_work');const pushRect=function(start,end,slice){rects.push(new tr.ui.tracks.Rect(slice,slice?slice.title:'',slice?slice.colorId:genericColorId,start,end-start));};let depth=0;let currentSlice=undefined;let lastStart=undefined;ops.forEach(function(op){depth+=op.isStart?1:-1;if(currentSlice){if(!op.isStart&&op.slice===currentSlice){pushRect(lastStart,op.time,currentSlice);lastStart=depth>=1?op.time:undefined;currentSlice=undefined;}}else{if(op.isStart){if(depth===1){lastStart=op.time;currentSlice=op.slice;}else if(op.slice){if(op.time!==lastStart){pushRect(lastStart,op.time,undefined);lastStart=op.time;}
-currentSlice=op.slice;}}else{if(depth===0){pushRect(lastStart,op.time,undefined);lastStart=undefined;}}}});return rects;};ProcessSummaryTrack.prototype={__proto__:tr.ui.tracks.RectTrack.prototype,decorate(viewport){tr.ui.tracks.RectTrack.prototype.decorate.call(this,viewport);},get process(){return this.process_;},set process(process){this.process_=process;this.rects=ProcessSummaryTrack.buildRectsFromProcess(process);}};return{ProcessSummaryTrack,};});'use strict';tr.exportTo('tr.ui.tracks',function(){const ObjectSnapshotView=tr.ui.analysis.ObjectSnapshotView;const ObjectInstanceView=tr.ui.analysis.ObjectInstanceView;const SpacingTrack=tr.ui.tracks.SpacingTrack;const ProcessTrackBase=tr.ui.b.define('process-track-base',tr.ui.tracks.ContainerTrack);ProcessTrackBase.prototype={__proto__:tr.ui.tracks.ContainerTrack.prototype,decorate(viewport){tr.ui.tracks.ContainerTrack.prototype.decorate.call(this,viewport);this.processBase_=undefined;Polymer.dom(this).classList.add('process-track-base');Polymer.dom(this).classList.add('expanded');this.processNameEl_=tr.ui.b.createSpan();Polymer.dom(this.processNameEl_).classList.add('process-track-name');this.headerEl_=tr.ui.b.createDiv({className:'process-track-header'});Polymer.dom(this.headerEl_).appendChild(this.processNameEl_);this.headerEl_.addEventListener('click',this.onHeaderClick_.bind(this));Polymer.dom(this).appendChild(this.headerEl_);},get processBase(){return this.processBase_;},set processBase(processBase){this.processBase_=processBase;if(this.processBase_){const modelSettings=new tr.model.ModelSettings(this.processBase_.model);const defaultValue=this.processBase_.important;this.expanded=modelSettings.getSettingFor(this.processBase_,'expanded',defaultValue);}
-this.updateContents_();},get expanded(){return Polymer.dom(this).classList.contains('expanded');},set expanded(expanded){expanded=!!expanded;if(this.expanded===expanded)return;Polymer.dom(this).classList.toggle('expanded');this.viewport_.dispatchChangeEvent();if(!this.processBase_)return;const modelSettings=new tr.model.ModelSettings(this.processBase_.model);modelSettings.setSettingFor(this.processBase_,'expanded',expanded);this.updateContents_();this.viewport.rebuildEventToTrackMap();this.viewport.rebuildContainerToTrackMap();},get hasVisibleContent(){if(this.expanded){return this.children.length>1;}
-return true;},onHeaderClick_(e){e.stopPropagation();e.preventDefault();this.expanded=!this.expanded;},updateContents_(){this.clearTracks_();if(!this.processBase_)return;Polymer.dom(this.processNameEl_).textContent=this.processBase_.userFriendlyName;this.headerEl_.title=this.processBase_.userFriendlyDetails;this.willAppendTracks_();if(this.expanded){this.appendMemoryDumpTrack_();this.appendObjectInstanceTracks_();this.appendCounterTracks_();this.appendFrameTrack_();this.appendThreadTracks_();}else{this.appendSummaryTrack_();}
+currentSlice=op.slice;}}else{if(depth===0){pushRect(lastStart,op.time,undefined);lastStart=undefined;}}}});return rects;};ProcessSummaryTrack.prototype={__proto__:tr.ui.tracks.RectTrack.prototype,decorate(viewport){tr.ui.tracks.RectTrack.prototype.decorate.call(this,viewport);},get process(){return this.process_;},set process(process){this.process_=process;this.rects=ProcessSummaryTrack.buildRectsFromProcess(process);}};return{ProcessSummaryTrack,};});'use strict';tr.exportTo('tr.ui.tracks',function(){const ObjectSnapshotView=tr.ui.analysis.ObjectSnapshotView;const ObjectInstanceView=tr.ui.analysis.ObjectInstanceView;const SpacingTrack=tr.ui.tracks.SpacingTrack;const ProcessTrackBase=tr.ui.b.define('process-track-base',tr.ui.tracks.ContainerTrack);ProcessTrackBase.prototype={__proto__:tr.ui.tracks.ContainerTrack.prototype,decorate(viewport){tr.ui.tracks.ContainerTrack.prototype.decorate.call(this,viewport);this.processBase_=undefined;Polymer.dom(this).classList.add('process-track-base');Polymer.dom(this).classList.add('expanded');this.processNameEl_=tr.ui.b.createSpan();Polymer.dom(this.processNameEl_).classList.add('process-track-name');this.closeEl_=tr.ui.b.createSpan();Polymer.dom(this.closeEl_).classList.add('process-track-close');this.closeEl_.textContent='X';this.headerEl_=tr.ui.b.createDiv({className:'process-track-header'});Polymer.dom(this.headerEl_).appendChild(this.processNameEl_);Polymer.dom(this.headerEl_).appendChild(this.closeEl_);this.headerEl_.addEventListener('click',this.onHeaderClick_.bind(this));Polymer.dom(this).appendChild(this.headerEl_);},get processBase(){return this.processBase_;},set processBase(processBase){this.processBase_=processBase;if(this.processBase_){const modelSettings=new tr.model.ModelSettings(this.processBase_.model);const defaultValue=this.processBase_.important;this.expanded=modelSettings.getSettingFor(this.processBase_,'expanded',defaultValue);}
+this.updateContents_();},get expanded(){return Polymer.dom(this).classList.contains('expanded');},set expanded(expanded){expanded=!!expanded;if(this.expanded===expanded)return;Polymer.dom(this).classList.toggle('expanded');this.viewport_.dispatchChangeEvent();if(!this.processBase_)return;const modelSettings=new tr.model.ModelSettings(this.processBase_.model);modelSettings.setSettingFor(this.processBase_,'expanded',expanded);this.updateContents_();this.viewport.rebuildEventToTrackMap();this.viewport.rebuildContainerToTrackMap();},set visible(visible){if(visible===this.visible)return;this.hidden=!visible;tr.b.dispatchSimpleEvent(this,'visibility');this.viewport_.dispatchChangeEvent();if(!this.processBase_)return;this.updateContents_();this.viewport.rebuildEventToTrackMap();this.viewport.rebuildContainerToTrackMap();},get visible(){return!this.hidden;},get hasVisibleContent(){if(this.expanded){return this.children.length>1;}
+return true;},onHeaderClick_(e){e.stopPropagation();e.preventDefault();if(e.target===this.closeEl_){this.visible=false;}else{this.expanded=!this.expanded;}},updateContents_(){this.clearTracks_();if(!this.processBase_)return;Polymer.dom(this.processNameEl_).textContent=this.processBase_.userFriendlyName;this.headerEl_.title=this.processBase_.userFriendlyDetails;this.willAppendTracks_();if(this.expanded){this.appendMemoryDumpTrack_();this.appendObjectInstanceTracks_();this.appendCounterTracks_();this.appendFrameTrack_();this.appendThreadTracks_();}else{this.appendSummaryTrack_();}
 this.didAppendTracks_();},willAppendTracks_(){},didAppendTracks_(){},appendMemoryDumpTrack_(){},appendSummaryTrack_(){const track=new tr.ui.tracks.ProcessSummaryTrack(this.viewport);track.process=this.process;if(!track.hasVisibleContent)return;Polymer.dom(this).appendChild(track);},appendFrameTrack_(){const frames=this.process?this.process.frames:undefined;if(!frames||!frames.length)return;const track=new tr.ui.tracks.FrameTrack(this.viewport);track.frames=frames;Polymer.dom(this).appendChild(track);},appendObjectInstanceTracks_(){const instancesByTypeName=this.processBase_.objects.getAllInstancesByTypeName();const instanceTypeNames=Object.keys(instancesByTypeName);instanceTypeNames.sort();let didAppendAtLeastOneTrack=false;instanceTypeNames.forEach(function(typeName){const allInstances=instancesByTypeName[typeName];let instanceViewInfo=ObjectInstanceView.getTypeInfo(undefined,typeName);let snapshotViewInfo=ObjectSnapshotView.getTypeInfo(undefined,typeName);if(instanceViewInfo&&!instanceViewInfo.metadata.showInTrackView){instanceViewInfo=undefined;}
 if(snapshotViewInfo&&!snapshotViewInfo.metadata.showInTrackView){snapshotViewInfo=undefined;}
 const hasViewInfo=instanceViewInfo||snapshotViewInfo;const visibleInstances=[];for(let i=0;i<allInstances.length;i++){const instance=allInstances[i];if(instance.snapshots.length===0)continue;if(instance.hasImplicitSnapshots&&!hasViewInfo)continue;visibleInstances.push(instance);}
@@ -9292,7 +9529,7 @@
 draw=!draw;if(!draw)continue;const bounds=this.children[i].getBoundingClientRect();ctx.fillRect(0,pixelRatio*(bounds.top-canvasBounds.top),ctx.canvas.width,pixelRatio*bounds.height);}},set process(process){this.processBase=process;},get process(){return this.processBase;},get eventContainer(){return this.process;},addContainersToTrackMap(containerToTrackMap){tr.ui.tracks.ProcessTrackBase.prototype.addContainersToTrackMap.apply(this,arguments);containerToTrackMap.addContainer(this.process,this);},appendMemoryDumpTrack_(){const processMemoryDumps=this.process.memoryDumps;if(processMemoryDumps.length){const pmdt=new tr.ui.tracks.ProcessMemoryDumpTrack(this.viewport_);pmdt.memoryDumps=processMemoryDumps;Polymer.dom(this).appendChild(pmdt);}},addIntersectingEventsInRangeToSelectionInWorldSpace(loWX,hiWX,viewPixWidthWorld,selection){function onPickHit(instantEvent){selection.push(instantEvent);}
 const instantEventWidth=2*viewPixWidthWorld;tr.b.iterateOverIntersectingIntervals(this.processBase.instantEvents,function(x){return x.start;},function(x){return x.duration+instantEventWidth;},loWX,hiWX,onPickHit.bind(this));tr.ui.tracks.ContainerTrack.prototype.addIntersectingEventsInRangeToSelectionInWorldSpace.apply(this,arguments);},addClosestEventToSelection(worldX,worldMaxDist,loY,hiY,selection){this.addClosestInstantEventToSelection(this.processBase.instantEvents,worldX,worldMaxDist,selection);tr.ui.tracks.ContainerTrack.prototype.addClosestEventToSelection.apply(this,arguments);}};return{ProcessTrack,};});'use strict';tr.exportTo('tr.ui.tracks',function(){const SelectionState=tr.model.SelectionState;const ColorScheme=tr.b.ColorScheme;const EventPresenter=tr.ui.b.EventPresenter;const ModelTrack=tr.ui.b.define('model-track',tr.ui.tracks.ContainerTrack);ModelTrack.VSYNC_HIGHLIGHT_ALPHA=0.1;ModelTrack.VSYNC_DENSITY_TRANSPARENT=0.20;ModelTrack.VSYNC_DENSITY_OPAQUE=0.10;ModelTrack.VSYNC_DENSITY_RANGE=ModelTrack.VSYNC_DENSITY_TRANSPARENT-ModelTrack.VSYNC_DENSITY_OPAQUE;ModelTrack.generateStripes_=function(times,minTime,maxTime){if(times.length===0)return[];const lowIndex=tr.b.findLowIndexInSortedArray(times,(x=>x),minTime);let highIndex=lowIndex-1;while(times[highIndex+1]<=maxTime){highIndex++;}
 const stripes=[];for(let i=lowIndex-(lowIndex%2);i<=highIndex;i+=2){const left=i<lowIndex?minTime:times[i];const right=i+1>highIndex?maxTime:times[i+1];stripes.push(tr.b.math.Range.fromExplicitRange(left,right));}
-return stripes;};ModelTrack.prototype={__proto__:tr.ui.tracks.ContainerTrack.prototype,decorate(viewport){tr.ui.tracks.ContainerTrack.prototype.decorate.call(this,viewport);Polymer.dom(this).classList.add('model-track');this.upperMode_=false;this.annotationViews_=[];this.vSyncTimes_=[];},get upperMode(){return this.upperMode_;},set upperMode(upperMode){this.upperMode_=upperMode;this.updateContents_();},detach(){tr.ui.tracks.ContainerTrack.prototype.detach.call(this);},get model(){return this.model_;},set model(model){this.model_=model;this.updateContents_();this.model_.addEventListener('annotationChange',this.updateAnnotations_.bind(this));},get hasVisibleContent(){return this.children.length>0;},updateContents_(){Polymer.dom(this).textContent='';if(!this.model_)return;if(this.upperMode_){this.updateContentsForUpperMode_();}else{this.updateContentsForLowerMode_();}},updateContentsForUpperMode_(){},updateContentsForLowerMode_(){if(this.model_.userModel.expectations.length>1){const mrt=new tr.ui.tracks.InteractionTrack(this.viewport_);mrt.model=this.model_;Polymer.dom(this).appendChild(mrt);}
+return stripes;};ModelTrack.prototype={__proto__:tr.ui.tracks.ContainerTrack.prototype,decorate(viewport){tr.ui.tracks.ContainerTrack.prototype.decorate.call(this,viewport);Polymer.dom(this).classList.add('model-track');this.upperMode_=false;this.annotationViews_=[];this.vSyncTimes_=[];},get processViews(){return Polymer.dom(this).querySelectorAll('.process-track-base');},get upperMode(){return this.upperMode_;},set upperMode(upperMode){this.upperMode_=upperMode;this.updateContents_();},detach(){tr.ui.tracks.ContainerTrack.prototype.detach.call(this);},get model(){return this.model_;},set model(model){this.model_=model;this.updateContents_();this.model_.addEventListener('annotationChange',this.updateAnnotations_.bind(this));},get hasVisibleContent(){return this.children.length>0;},updateContents_(){Polymer.dom(this).textContent='';if(!this.model_)return;if(this.upperMode_){this.updateContentsForUpperMode_();}else{this.updateContentsForLowerMode_();}},updateContentsForUpperMode_(){},updateContentsForLowerMode_(){if(this.model_.userModel.expectations.length>1){const mrt=new tr.ui.tracks.InteractionTrack(this.viewport_);mrt.model=this.model_;Polymer.dom(this).appendChild(mrt);}
 if(this.model_.alerts.length){const at=new tr.ui.tracks.AlertTrack(this.viewport_);at.alerts=this.model_.alerts;Polymer.dom(this).appendChild(at);}
 if(this.model_.globalMemoryDumps.length){const gmdt=new tr.ui.tracks.GlobalMemoryDumpTrack(this.viewport_);gmdt.memoryDumps=this.model_.globalMemoryDumps;Polymer.dom(this).appendChild(gmdt);}
 this.appendDeviceTrack_();this.appendCpuUsageTrack_();this.appendMemoryTrack_();this.appendKernelTrack_();const processes=this.model_.getAllProcesses();processes.sort(tr.model.Process.compare);for(let i=0;i<processes.length;++i){const process=processes[i];const track=new tr.ui.tracks.ProcessTrack(this.viewport);track.process=process;if(!track.hasVisibleContent)continue;Polymer.dom(this).appendChild(track);}
@@ -9319,7 +9556,7 @@
 ctx.fillStyle=displayTextColor;ctx.fillText(textToDraw,textLeftView,textPosY);ctx.strokeStyle=arrowColor;ctx.beginPath();tr.ui.b.drawLine(ctx,leftMarkerView,arrowPosY,rightMarkerView,arrowPosY);ctx.stroke();ctx.fillStyle=arrowColor;tr.ui.b.drawArrow(ctx,leftMarkerView-1.5*arrowSpacing,arrowPosY,leftMarkerView,arrowPosY,arrowLengthView,arrowWidthView);tr.ui.b.drawArrow(ctx,rightMarkerView+1.5*arrowSpacing,arrowPosY,rightMarkerView,arrowPosY,arrowLengthView,arrowWidthView);}else if(spaceForArrowsView<=distanceBetweenMarkersView){let leftArrowStart;let rightArrowStart;if(spaceForArrowsAndTextView<=distanceBetweenMarkersView){ctx.fillStyle=displayTextColor;ctx.fillText(textToDraw,textLeftView,textPosY);leftArrowStart=textLeftView-arrowSpacing;rightArrowStart=textRightView+arrowSpacing;}else{leftArrowStart=positionInMiddleOfMarkersView;rightArrowStart=positionInMiddleOfMarkersView;}
 ctx.strokeStyle=arrowColor;ctx.fillStyle=arrowColor;tr.ui.b.drawArrow(ctx,leftArrowStart,arrowPosY,leftMarkerView,arrowPosY,arrowLengthView,arrowWidthView);tr.ui.b.drawArrow(ctx,rightArrowStart,arrowPosY,rightMarkerView,arrowPosY,arrowLengthView,arrowWidthView);}
 ctx.restore();},drawMarkers_(viewLWorld,viewRWorld){const pixelRatio=window.devicePixelRatio||1;const trackBounds=this.getBoundingClientRect();const viewHeight=trackBounds.height*pixelRatio;if(!this.viewport.interestRange.isEmpty){this.viewport.interestRange.draw(this.context(),viewLWorld,viewRWorld,viewHeight);}},addIntersectingEventsInRangeToSelection(loVX,hiVX,loY,hiY,selection){},addAllEventsMatchingFilterToSelection(filter,selection){}};return{XAxisTrack,};});'use strict';Polymer({is:'tr-ui-timeline-track-view',ready(){this.displayTransform_=new tr.ui.TimelineDisplayTransform();this.model_=undefined;this.timelineView_=undefined;this.pollIfViewportAttachedInterval_=undefined;this.viewport_=new tr.ui.TimelineViewport(this);this.viewportDisplayTransformAtMouseDown_=undefined;this.brushingStateController_=undefined;this.rulerTrackContainer_=new tr.ui.tracks.DrawingContainer(this.viewport_);Polymer.dom(this).appendChild(this.rulerTrackContainer_);this.rulerTrackContainer_.invalidate();this.rulerTrackContainer_.style.overflowY='hidden';this.rulerTrackContainer_.style.flexShrink='0';this.rulerTrack_=new tr.ui.tracks.XAxisTrack(this.viewport_);Polymer.dom(this.rulerTrackContainer_).appendChild(this.rulerTrack_);this.upperModelTrack_=new tr.ui.tracks.ModelTrack(this.viewport_);this.upperModelTrack_.upperMode=true;Polymer.dom(this.rulerTrackContainer_).appendChild(this.upperModelTrack_);this.modelTrackContainer_=new tr.ui.tracks.DrawingContainer(this.viewport_);Polymer.dom(this).appendChild(this.modelTrackContainer_);this.modelTrackContainer_.style.display='block';this.modelTrackContainer_.style.flexGrow='1';this.modelTrackContainer_.invalidate();this.viewport_.modelTrackContainer=this.modelTrackContainer_;this.modelTrack_=new tr.ui.tracks.ModelTrack(this.viewport_);Polymer.dom(this.modelTrackContainer_).appendChild(this.modelTrack_);this.timingTool_=new tr.ui.b.TimingTool(this.viewport_,this);this.initMouseModeSelector();this.hideDragBox_();this.initHintText_();this.onSelectionChanged_=this.onSelectionChanged_.bind(this);this.onDblClick_=this.onDblClick_.bind(this);this.addEventListener('dblclick',this.onDblClick_);this.onMouseWheel_=this.onMouseWheel_.bind(this);this.addEventListener('mousewheel',this.onMouseWheel_);this.onMouseDown_=this.onMouseDown_.bind(this);this.addEventListener('mousedown',this.onMouseDown_);this.onMouseMove_=this.onMouseMove_.bind(this);this.addEventListener('mousemove',this.onMouseMove_);this.onTouchStart_=this.onTouchStart_.bind(this);this.addEventListener('touchstart',this.onTouchStart_);this.onTouchMove_=this.onTouchMove_.bind(this);this.addEventListener('touchmove',this.onTouchMove_);this.onTouchEnd_=this.onTouchEnd_.bind(this);this.addEventListener('touchend',this.onTouchEnd_);this.addHotKeys_();this.mouseViewPosAtMouseDown_={x:0,y:0};this.lastMouseViewPos_={x:0,y:0};this.lastTouchViewPositions_=[];this.alert_=undefined;this.isPanningAndScanning_=false;this.isZooming_=false;},initMouseModeSelector(){this.mouseModeSelector_=document.createElement('tr-ui-b-mouse-mode-selector');this.mouseModeSelector_.targetElement=this;Polymer.dom(this).appendChild(this.mouseModeSelector_);this.mouseModeSelector_.addEventListener('beginpan',this.onBeginPanScan_.bind(this));this.mouseModeSelector_.addEventListener('updatepan',this.onUpdatePanScan_.bind(this));this.mouseModeSelector_.addEventListener('endpan',this.onEndPanScan_.bind(this));this.mouseModeSelector_.addEventListener('beginselection',this.onBeginSelection_.bind(this));this.mouseModeSelector_.addEventListener('updateselection',this.onUpdateSelection_.bind(this));this.mouseModeSelector_.addEventListener('endselection',this.onEndSelection_.bind(this));this.mouseModeSelector_.addEventListener('beginzoom',this.onBeginZoom_.bind(this));this.mouseModeSelector_.addEventListener('updatezoom',this.onUpdateZoom_.bind(this));this.mouseModeSelector_.addEventListener('endzoom',this.onEndZoom_.bind(this));this.mouseModeSelector_.addEventListener('entertiming',this.timingTool_.onEnterTiming.bind(this.timingTool_));this.mouseModeSelector_.addEventListener('begintiming',this.timingTool_.onBeginTiming.bind(this.timingTool_));this.mouseModeSelector_.addEventListener('updatetiming',this.timingTool_.onUpdateTiming.bind(this.timingTool_));this.mouseModeSelector_.addEventListener('endtiming',this.timingTool_.onEndTiming.bind(this.timingTool_));this.mouseModeSelector_.addEventListener('exittiming',this.timingTool_.onExitTiming.bind(this.timingTool_));const m=tr.ui.b.MOUSE_SELECTOR_MODE;this.mouseModeSelector_.supportedModeMask=m.SELECTION|m.PANSCAN|m.ZOOM|m.TIMING;this.mouseModeSelector_.settingsKey='timelineTrackView.mouseModeSelector';this.mouseModeSelector_.setKeyCodeForMode(m.PANSCAN,'2'.charCodeAt(0));this.mouseModeSelector_.setKeyCodeForMode(m.SELECTION,'1'.charCodeAt(0));this.mouseModeSelector_.setKeyCodeForMode(m.ZOOM,'3'.charCodeAt(0));this.mouseModeSelector_.setKeyCodeForMode(m.TIMING,'4'.charCodeAt(0));this.mouseModeSelector_.setModifierForAlternateMode(m.SELECTION,tr.ui.b.MODIFIER.SHIFT);this.mouseModeSelector_.setModifierForAlternateMode(m.PANSCAN,tr.ui.b.MODIFIER.SPACE);},get brushingStateController(){return this.brushingStateController_;},set brushingStateController(brushingStateController){if(this.brushingStateController_){this.brushingStateController_.removeEventListener('change',this.onSelectionChanged_);}
-this.brushingStateController_=brushingStateController;if(this.brushingStateController_){this.brushingStateController_.addEventListener('change',this.onSelectionChanged_);}},set timelineView(view){this.timelineView_=view;},onSelectionChanged_(){this.showHintText_('Press \'m\' to mark current selection');this.viewport_.dispatchChangeEvent();},set selection(selection){throw new Error('DO NOT CALL THIS');},set highlight(highlight){throw new Error('DO NOT CALL THIS');},detach(){this.modelTrack_.detach();this.upperModelTrack_.detach();if(this.pollIfViewportAttachedInterval_){window.clearInterval(this.pollIfViewportAttachedInterval_);this.pollIfViewportAttachedInterval_=undefined;}
+this.brushingStateController_=brushingStateController;if(this.brushingStateController_){this.brushingStateController_.addEventListener('change',this.onSelectionChanged_);}},set timelineView(view){this.timelineView_=view;},get processViews(){return this.modelTrack_.processViews;},onSelectionChanged_(){this.showHintText_('Press \'m\' to mark current selection');this.viewport_.dispatchChangeEvent();},set selection(selection){throw new Error('DO NOT CALL THIS');},set highlight(highlight){throw new Error('DO NOT CALL THIS');},detach(){this.modelTrack_.detach();this.upperModelTrack_.detach();if(this.pollIfViewportAttachedInterval_){window.clearInterval(this.pollIfViewportAttachedInterval_);this.pollIfViewportAttachedInterval_=undefined;}
 this.viewport_.detach();},get viewport(){return this.viewport_;},get model(){return this.model_;},set model(model){if(!model){throw new Error('Model cannot be undefined');}
 const modelInstanceChanged=this.model_!==model;this.model_=model;this.modelTrack_.model=model;this.upperModelTrack_.model=model;if(modelInstanceChanged){this.pollIfViewportAttachedInterval_=window.setInterval(this.pollIfViewportAttached_.bind(this),250);}},get hasVisibleContent(){return this.modelTrack_.hasVisibleContent||this.upperModelTrack_.hasVisibleContent;},pollIfViewportAttached_(){if(!this.viewport_.isAttachedToDocumentOrInTestMode||this.viewport_.clientWidth===0){return;}
 window.addEventListener('resize',this.viewport_.dispatchChangeEvent);window.clearInterval(this.pollIfViewportAttachedInterval_);this.pollIfViewportAttachedInterval_=undefined;this.setInitialViewport_();},setInitialViewport_(){this.modelTrackContainer_.updateCanvasSizeIfNeeded_();const w=this.modelTrackContainer_.canvas.width;let min;let range;if(this.model_.bounds.isEmpty){min=0;range=1000;}else if(this.model_.bounds.range===0){min=this.model_.bounds.min;range=1000;}else{min=this.model_.bounds.min;range=this.model_.bounds.range;}
@@ -9395,13 +9632,14 @@
 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';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.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.optionsDropdown_=this.$.view_options_dropdown;Polymer.dom(this.optionsDropdown_.iconElement).textContent='View Options';this.showFlowEvents_=false;Polymer.dom(this.optionsDropdown_).appendChild(tr.ui.b.createCheckBox(this,'showFlowEvents','tr.ui.TimelineView.showFlowEvents',false,'Flow events'));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;},updateDocumentFavicon(){let hue;if(!this.model){hue='blue';}else{hue=this.model.faviconHue;}
+this.processFilter_=this.$.process_filter_dropdown;this.optionsDropdown_=this.$.view_options_dropdown;Polymer.dom(this.optionsDropdown_.iconElement).textContent='View Options';this.showFlowEvents_=false;Polymer.dom(this.optionsDropdown_).appendChild(tr.ui.b.createCheckBox(this,'showFlowEvents','tr.ui.TimelineView.showFlowEvents',false,'Flow events'));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;},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 showFlowEvents(){return this.showFlowEvents_;},set showFlowEvents(showFlowEvents){this.showFlowEvents_=showFlowEvents;if(!this.trackView_)return;this.trackView_.viewport.showFlowEvents=showFlowEvents;},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();}
 helpButtonEl.addEventListener('click',onClick.bind(this));},initConsoleButton_(){const toggleEl=this.$.view_console_button;function onClick(e){this.scriptingCtl_.toggleVisibility();e.stopPropagation();return false;}
 toggleEl.addEventListener('click',onClick.bind(this));},initMetadataButton_(){const showEl=this.$.view_metadata_button;function onClick(e){const dlg=new tr.ui.b.Overlay();dlg.title='Metadata for trace';const metadataOverlay=document.createElement('tr-ui-timeline-view-metadata-overlay');metadataOverlay.metadata=this.model.metadata;Polymer.dom(dlg).appendChild(metadataOverlay);dlg.visible=true;e.stopPropagation();return false;}
-showEl.addEventListener('click',onClick.bind(this));this.updateMetadataButtonVisibility_();},updateMetadataButtonVisibility_(){const showEl=this.$.view_metadata_button;showEl.style.display=(this.model&&this.model.metadata.length)?'':'none';},get leftControls(){return this.leftControlsEl_;},get rightControls(){return this.rightControlsEl_;},get collapsingControls(){return this.collapsingControlsEl_;},get viewTitle(){return Polymer.dom(this.titleEl_).textContent.substring(Polymer.dom(this.titleEl_).textContent.length-2);},set viewTitle(text){if(text===undefined){Polymer.dom(this.titleEl_).textContent='';this.titleEl_.hidden=true;return;}
+showEl.addEventListener('click',onClick.bind(this));this.updateMetadataButtonVisibility_();},updateMetadataButtonVisibility_(){const showEl=this.$.view_metadata_button;showEl.style.display=(this.model&&this.model.metadata.length)?'':'none';},updateProcessList_(){const dropdown=Polymer.dom(this.processFilter_);while(dropdown.firstChild){dropdown.removeChild(dropdown.firstChild);}
+if(!this.model)return;const trackView=this.trackViewContainer_.querySelector('tr-ui-timeline-track-view');const processViews=trackView.processViews;const cboxes=[];const updateAll=(checked)=>{for(const cbox of cboxes){cbox.checked=checked;}};dropdown.appendChild(tr.ui.b.createButton('All',()=>updateAll(true)));dropdown.appendChild(tr.ui.b.createButton('None',()=>updateAll(false)));for(const view of processViews){const cbox=tr.ui.b.createCheckBox(undefined,undefined,undefined,true,view.processBase.userFriendlyName,()=>view.visible=cbox.checked);cbox.checked=view.visible;cboxes.push(cbox);view.addEventListener('visibility',()=>cbox.checked=view.visible);dropdown.appendChild(cbox);}},get leftControls(){return this.leftControlsEl_;},get rightControls(){return this.rightControlsEl_;},get collapsingControls(){return this.collapsingControlsEl_;},get viewTitle(){return Polymer.dom(this.titleEl_).textContent.substring(Polymer.dom(this.titleEl_).textContent.length-2);},set viewTitle(text){if(text===undefined){Polymer.dom(this.titleEl_).textContent='';this.titleEl_.hidden=true;return;}
 this.titleEl_.hidden=false;Polymer.dom(this.titleEl_).textContent=text;},get model(){if(this.trackView_){return this.trackView_.model;}
 return undefined;},set model(model){this.build(model);},async build(model){this.queuedModel_=model;this.builtPromise_=new Promise((resolve,reject)=>{this.doneBuilding_=resolve;});if(this.trackViewContainer_)await this.updateContents_();},get builtPromise(){return this.builtPromise_;},async updateContents_(){if(this.trackViewContainer_===undefined){throw new Error('timeline-view.updateContents_ requires trackViewContainer_');}
 const model=this.queuedModel_;this.queuedModel_=undefined;const modelInstanceChanged=model!==this.model;const modelValid=model&&!model.bounds.isEmpty;const importWarningsEl=Polymer.dom(this.root).querySelector('#import-warnings');Polymer.dom(importWarningsEl).textContent='';if(modelInstanceChanged){if(this.railScoreSpan_){this.railScoreSpan_.model=undefined;}
@@ -9411,7 +9649,7 @@
 if(modelValid){this.trackView_.model=model;this.trackView_.viewport.showFlowEvents=this.showFlowEvents;this.trackView_.viewport.highlightVSync=this.highlightVSync;if(this.railScoreSpan_){this.railScoreSpan_.model=model;}
 this.$.display_unit.preferredTimeDisplayMode=model.intrinsicTimeUnit;}
 if(model){for(const warning of model.importWarningsThatShouldBeShownToUser){importWarningsEl.addMessage(`Import Warning: ${warning.type}: ${warning.message}`,[{buttonText:'Dismiss',onClick(event,infobar){infobar.visible=false;}}]);}}
-if(modelInstanceChanged){this.updateMetadataButtonVisibility_();this.brushingStateController_.modelDidChange();this.onViewportChanged_();}
+if(modelInstanceChanged){this.updateProcessList_();this.updateMetadataButtonVisibility_();this.brushingStateController_.modelDidChange();this.onViewportChanged_();}
 this.doneBuilding_();},get brushingStateController(){return this.brushingStateController_;},get trackView(){return this.trackView_;},get settings(){if(!this.settings_){this.settings_=new tr.b.Settings();}
 return this.settings_;},set focusElement(value){throw new Error('This is deprecated. Please set globalMode to true.');},bindKeyListeners_(){const hkc=this.hotkeyController;hkc.addHotKey(new tr.ui.b.HotKey({eventType:'keypress',keyCode:'`'.charCodeAt(0),useCapture:true,thisArg:this,callback(e){this.scriptingCtl_.toggleVisibility();if(!this.scriptingCtl_.hasFocus){this.focus();}
 e.stopPropagation();}}));hkc.addHotKey(new tr.ui.b.HotKey({eventType:'keypress',keyCode:'/'.charCodeAt(0),useCapture:true,thisArg:this,callback(e){if(this.scriptingCtl_.hasFocus)return;if(this.findCtl_.hasFocus){this.focus();}else{this.findCtl_.focus();}
@@ -9541,8 +9779,8 @@
 get labels(){const displayLabels=Array.from(this.labelsToStartTimes_.keys());displayLabels.sort((x,y)=>this.labelsToStartTimes_.get(x)-this.labelsToStartTimes_.get(y));return displayLabels;}
 get possibleGroupings(){for(const[key,values]of this.keysToValues_){if(values.size>=2)continue;this.keysToGroupings_.delete(key);}
 return Array.from(this.keysToGroupings_.values());}}
-return{HistogramParameterCollector,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-histogram-set-controls-export',exportRawCsv_(){this.export_(false,'csv');},exportRawJson_(){this.export_(false,'json');},exportMergedCsv_(){this.export_(true,'csv');},exportMergedJson_(){this.export_(true,'json');},export_(merged,format){tr.b.dispatchSimpleEvent(this,'export',true,true,{merged,format});},});return{};});'use strict';tr.exportTo('tr.v.ui',function(){const ALPHA_OPTIONS=[];for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-3);for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-2);ALPHA_OPTIONS.push(0.1);Polymer({is:'tr-v-ui-histogram-set-controls',properties:{searchQuery:{type:String,value:'',observer:'onUserChange_',},showAll:{type:Boolean,value:true,observer:'onUserChange_',},referenceDisplayLabel:{type:String,value:'',observer:'onUserChange_',},displayStatisticName:{type:String,value:'',observer:'onUserChange_',},alphaString:{type:String,computed:'getAlphaString_(alphaIndex)',},alphaIndex:{type:Number,value:9,observer:'onUserChange_',},},created(){this.viewState_=undefined;this.rowListener_=this.onRowViewStateUpdate_.bind(this);this.baseStatisticNames_=[];this.isInOnViewStateUpdate_=false;},ready(){this.$.picker.addEventListener('current-groups-changed',this.onGroupsChanged_.bind(this));},get viewState(){return this.viewState_;},set viewState(vs){if(this.viewState_){throw new Error('viewState must be set exactly once.');}
-this.viewState_=vs;this.viewState.addUpdateListener(this.onViewStateUpdate_.bind(this));},async onUserChange_(){if(!this.viewState)return;if(this.isInOnViewStateUpdate_)return;const marks=[];if(this.searchQuery!==this.viewState.searchQuery){marks.push(tr.b.Timing.mark('histogram-set-controls','search'));}
+return{HistogramParameterCollector,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-histogram-set-controls-export',exportRawCsv_(){this.export_(false,'csv');},exportRawJson_(){this.export_(false,'json');},exportMergedCsv_(){this.export_(true,'csv');},exportMergedJson_(){this.export_(true,'json');},export_(merged,format){tr.b.dispatchSimpleEvent(this,'export',true,true,{merged,format});},});return{};});'use strict';tr.exportTo('tr.v.ui',function(){const ALPHA_OPTIONS=[];for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-3);for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-2);ALPHA_OPTIONS.push(0.1);Polymer({is:'tr-v-ui-histogram-set-controls',properties:{searchQuery:{type:String,value:'',observer:'onSearchQueryChange_',},showAll:{type:Boolean,value:true,observer:'onUserChange_',},referenceDisplayLabel:{type:String,value:'',observer:'onUserChange_',},displayStatisticName:{type:String,value:'',observer:'onUserChange_',},alphaString:{type:String,computed:'getAlphaString_(alphaIndex)',},alphaIndex:{type:Number,value:9,observer:'onUserChange_',},},created(){this.viewState_=undefined;this.rowListener_=this.onRowViewStateUpdate_.bind(this);this.baseStatisticNames_=[];this.isInOnViewStateUpdate_=false;this.searchQueryDebounceMs=200;},ready(){this.$.picker.addEventListener('current-groups-changed',this.onGroupsChanged_.bind(this));},get viewState(){return this.viewState_;},set viewState(vs){if(this.viewState_){throw new Error('viewState must be set exactly once.');}
+this.viewState_=vs;this.viewState.addUpdateListener(this.onViewStateUpdate_.bind(this));},async onSearchQueryChange_(){if(this.searchQueryDebounceMs===0)return this.onUserChange_();this.debounce('onSearchQueryDebounce',this.onUserChange_,this.searchQueryDebounceMs);},async onUserChange_(){if(!this.viewState)return;if(this.isInOnViewStateUpdate_)return;const marks=[];if(this.searchQuery!==this.viewState.searchQuery){marks.push(tr.b.Timing.mark('histogram-set-controls','search'));}
 if(this.showAll!==this.viewState.showAll){marks.push(tr.b.Timing.mark('histogram-set-controls','showAll'));}
 if(this.referenceDisplayLabel!==this.viewState.referenceDisplayLabel){marks.push(tr.b.Timing.mark('histogram-set-controls','referenceColumn'));}
 if(this.displayStatisticName!==this.viewState.displayStatisticName){marks.push(tr.b.Timing.mark('histogram-set-controls','statistic'));}
@@ -9567,7 +9805,7 @@
 if(names.includes(this.viewState.displayStatisticName)){this.displayStatisticName=this.viewState.displayStatisticName;this.$.statistic.value=this.displayStatisticName;}else{this.viewState.displayStatisticName=names[0]||'';}},get anyOverviewCharts_(){for(const row of tr.v.ui.HistogramSetTableRowState.walkAll(this.viewState.tableRowStates.values())){if(row.isOverviewed)return true;}
 return false;},async toggleOverviewLineCharts_(){const showOverviews=!this.anyOverviewCharts_;const mark=tr.b.Timing.mark('histogram-set-controls',(showOverviews?'show':'hide')+'OverviewCharts');for(const row of tr.v.ui.HistogramSetTableRowState.walkAll(this.viewState.tableRowStates.values())){await row.update({isOverviewed:showOverviews});}
 this.$.hide_overview.style.display=showOverviews?'inline':'none';this.$.show_overview.style.display=showOverviews?'none':'inline';await tr.b.animationFrame();mark.end();},set helpHref(href){this.$.help.href=href;this.$.help.style.display='inline';},set feedbackHref(href){this.$.feedback.href=href;this.$.feedback.style.display='inline';},clearSearch_(){this.set('searchQuery','');this.$.search.focus();},getAlphaString_(alphaIndex){return(''+ALPHA_OPTIONS[alphaIndex]).substr(0,5);},openAlphaSlider_(){const alphaButtonRect=this.$.alpha.getBoundingClientRect();this.$.alpha_slider_container.style.display='flex';this.$.alpha_slider_container.style.top=alphaButtonRect.bottom+'px';this.$.alpha_slider_container.style.left=alphaButtonRect.left+'px';this.$.alpha_slider.focus();},closeAlphaSlider_(){this.$.alpha_slider_container.style.display='';},updateAlpha_(){this.alphaIndex=this.$.alpha_slider.value;},getAlphaIndexFromViewState_(){for(let i=0;i<ALPHA_OPTIONS.length;++i){if(ALPHA_OPTIONS[i]>=this.viewState.alpha)return i;}
-return ALPHA_OPTIONS.length-1;},});return{};});'use strict';tr.exportTo('tr.v',function(){function deleteMergedToDiagnostics(histogramArrayMap){for(const[name,histograms]of histogramArrayMap){if(histograms instanceof Array){for(const histogram of histograms){histogram.diagnostics.delete(tr.v.d.RESERVED_NAMES.MERGED_TO);}}else if(histograms instanceof Map){deleteMergedToDiagnostics(histograms);}}}
+return ALPHA_OPTIONS.length-1;},set enableVisualization(enable){this.$.show_visualization.style.display=enable?'inline':'none';},loadVisualization_(){tr.b.dispatchSimpleEvent(this,'loadVisualization',true,true,{});},});return{};});'use strict';tr.exportTo('tr.v',function(){function deleteMergedToDiagnostics(histogramArrayMap){for(const[name,histograms]of histogramArrayMap){if(histograms instanceof Array){for(const histogram of histograms){histogram.diagnostics.delete(tr.v.d.RESERVED_NAMES.MERGED_TO);}}else if(histograms instanceof Map){deleteMergedToDiagnostics(histograms);}}}
 class HistogramSetHierarchy{constructor(name){this.name=name;this.description='';this.depth=0;this.subRows=[];this.columns=new Map();this.mergeRelationshipsForColumn_=new Map();}*walk(){yield this;for(const row of this.subRows)yield*row.walk();}
 static*walkAll(rootRows){for(const rootRow of rootRows)yield*rootRow.walk();}
 static build(histogramArrayMap){const rootRows=[];HistogramSetHierarchy.buildInternal_(histogramArrayMap,[],rootRows);const histograms=new tr.v.HistogramSet();for(const row of HistogramSetHierarchy.walkAll(rootRows)){for(const hist of row.columns.values()){if(!(hist instanceof tr.v.Histogram))continue;histograms.addHistogram(hist);}}
@@ -9676,11 +9914,32 @@
 for(const row of this.tableRows_){if(this.viewState.tableRowStates.get(row.name)!==row.viewState){throw new Error('Only histogram-set-table may update tableRowStates');}}}
 await this.updateContents_();},onSortColumnChanged_(event){tr.b.Timing.instant('histogram-set-table','sortColumn');this.viewState.update({sortColumnIndex:event.sortColumnIndex,sortDescending:event.sortDescending,});},onRequestSelectionChange_(event){if(event.selection instanceof tr.model.EventSet)return;event.stopPropagation();tr.b.Timing.instant('histogram-set-table','selectHistogramNames');let histogramNames=event.selection;histogramNames.sort();histogramNames=histogramNames.map(escapeRegExp).join('|');this.viewState.update({showAll:true,searchQuery:`^(${histogramNames})$`,});},get leafHistograms(){const histograms=new tr.v.HistogramSet();for(const row of
 tr.v.ui.HistogramSetTableRow.walkAll(this.$.table.tableRows)){if(row.subRows.length)continue;for(const hist of row.columns.values()){if(!(hist instanceof tr.v.Histogram))continue;histograms.addHistogram(hist);}}
-return histograms;}});return{MIDLINE_HORIZONTAL_ELLIPSIS,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-histogram-set-view',listeners:{export:'onExport_',},created(){this.brushingStateController_=new tr.ui.NullBrushingStateController();this.viewState_=new tr.v.ui.HistogramSetViewState();},ready(){this.$.table.viewState=this.viewState;this.$.controls.viewState=this.viewState;},attached(){this.brushingStateController.parentController=tr.c.BrushingStateController.getControllerForElement(this.parentNode);},get brushingStateController(){return this.brushingStateController_;},get viewState(){return this.viewState_;},get histograms(){return this.$.table.histograms;},async build(histograms,opt_options){const options=opt_options||{};const progress=options.progress||(()=>Promise.resolve());if(options.helpHref)this.$.controls.helpHref=options.helpHref;if(options.feedbackHref){this.$.controls.feedbackHref=options.feedbackHref;}
+return histograms;}});return{MIDLINE_HORIZONTAL_ELLIPSIS,};});'use strict';const COLORS=[['#FFD740','#FFC400','#FFAB00','#E29800'],['#FF6E40','#FF3D00','#DD2C00','#A32000'],['#40C4FF','#00B0FF','#0091EA','#006DAF'],['#89C641','#54B503','#4AA510','#377A0D'],['#B388FF','#7C4DFF','#651FFF','#6200EA'],['#FF80AB','#FF4081','#F50057','#C51162'],['#FFAB40','#FF9100','#FF6D00','#D65C02'],['#8C9EFF','#536DFE','#3D5AFE','#304FFE']];const METRICS=new Map([['Pipeline',['pipeline:begin_frame_transport','pipeline:begin_frame_to_frame_submission','pipeline:frame_submission_to_display','pipeline:draw']],['Thread',['thread_browser_cpu_time_per_frame','thread_display_compositor_cpu_time_per_frame','thread_GPU_cpu_time_per_frame','thread_IO_cpu_time_per_frame','thread_other_cpu_time_per_frame','thread_raster_cpu_time_per_frame','thread_renderer_compositor_cpu_time_per_frame','thread_renderer_main_cpu_time_per_frame']]]);const STATISTIC_KEY='statistic';Polymer({is:'tr-v-ui-metrics-visualization',created(){this.sortedPages_=new Map();this.displayLabels_=new Map();this.groups_=new Map();this.charts_=new Map();this.subMetricNames_=new Map();},build(leafHistograms,histograms){if(!leafHistograms||leafHistograms.length<1||!histograms||histograms.length<1){this.$.data_error.style.display='block';return;}
+this.histograms=histograms;for(const key of METRICS.keys()){const newAggregateChart=this.initializeColumnChart(key);Polymer.dom(this.$.container).appendChild(newAggregateChart);this.populateChart_(leafHistograms,key,newAggregateChart,true);newAggregateChart.toolTipCallBack=()=>this.openMetricsChart_(key);}
+if(this.groups_.size<1){this.$.data_error.style.display='block';return;}},populateChart_(histograms,metricName,chart,aggregate){const storiesGrouping=tr.v.HistogramGrouping.BY_KEY.get(tr.v.d.RESERVED_NAMES.STORIES);const benchmarkStartGrouping=tr.v.HistogramGrouping.BY_KEY.get(tr.v.d.RESERVED_NAMES.BENCHMARK_START);const labels=new Map();const groups=new Map();for(const metric of METRICS.get(metricName)){const metricHistograms=histograms.getHistogramsNamed(metric);for(const histogram of metricHistograms){const benchmarkStart=benchmarkStartGrouping.callback(histogram);const page=storiesGrouping.callback(histogram);const displayLabel=tr.v.HistogramGrouping.DISPLAY_LABEL.callback(histogram);const average=histogram.average===undefined?0:histogram.average;labels.set(displayLabel,Date.parse(benchmarkStart));let pageMap=new Map();let labelMap=new Map();if(groups.get(page)===undefined){groups.set(page,pageMap);pageMap.set(displayLabel,labelMap);}else{pageMap=groups.get(page);if(pageMap.get(displayLabel)===undefined){pageMap.set(displayLabel,labelMap);}else{labelMap=pageMap.get(displayLabel);}}
+let metricMap=new Map();let statistic=new tr.b.math.RunningStatistics();if(labelMap.has(metric)){metricMap=labelMap.get(metric);statistic=metricMap.get(STATISTIC_KEY);}
+statistic.add(average);metricMap.set(STATISTIC_KEY,statistic);labelMap.set(metric,metricMap);const merged=new tr.v.d.DiagnosticMap();for(const bin of histogram.allBins){for(const map of bin.diagnosticMaps){merged.addDiagnostics(map);}}
+const subMetrics=[];if(merged.get('breakdown')===undefined){metricMap.set(metric,statistic);subMetrics.push(metric);}else{for(const[subMetric,total]of merged.get('breakdown')){let subStatistic=new tr.b.math.RunningStatistics();if(metricMap.has(subMetric)){subStatistic=metricMap.get(subMetric);}
+subStatistic.add(total);metricMap.set(subMetric,subStatistic);subMetrics.push(subMetric);}}
+const prevSubMetrics=this.subMetricNames_.get(metric);if(!prevSubMetrics||subMetrics.length>prevSubMetrics.length){this.subMetricNames_.set(metric,subMetrics);}}}
+const displayLabels=this.getSortedDisplayLabels_(labels);const sortedPages=[...groups.keys()].sort((a,b)=>this.sortGroups_(a,b,groups,displayLabels,METRICS.get(metricName)));this.setChartColors_(METRICS.get(metricName),displayLabels,chart);this.setChartSize_(groups.size,displayLabels.length,chart);this.setChartData_(groups,sortedPages,displayLabels,chart);let mapName=metricName;if(aggregate){mapName='Aggregate '+mapName;}
+this.groups_.set(mapName,groups);this.displayLabels_.set(mapName,displayLabels);this.sortedPages_.set(mapName,sortedPages);},getSortedDisplayLabels_(labels){return Array.from(labels.keys()).sort((a,b)=>labels.get(a)-labels.get(b));},sortGroups_(a,b,groups,displayLabels,mainMetricNames){let aValue=0;const aMap=groups.get(a);if(aMap.get(displayLabels[0])!==undefined){for(const metricName of mainMetricNames){const aMetricMap=aMap.get(displayLabels[0]).get(metricName);aValue+=aMetricMap.get(STATISTIC_KEY).mean;}}
+let bValue=0;const bMap=groups.get(b);if(bMap.get(displayLabels[0])!==undefined){for(const metricName of mainMetricNames){const bMetricMap=bMap.get(displayLabels[0]).get(metricName);bValue+=bMetricMap.get(STATISTIC_KEY).mean;}}
+if(aValue===bValue)return 0;return(aValue<bValue)?-1:1;},setChartData_(groups,sortedPages,displayLabels,chart){const chartData=[];for(const page of sortedPages){const pageMap=groups.get(page);for(const label of displayLabels){const data={x:label,group:page};if(!pageMap.has(label))continue;const labelMap=pageMap.get(label);for(const[metric,metricMap]of labelMap){const key=this.getSeriesKey_(metric,label);const mean=metricMap.get(STATISTIC_KEY).mean;data[key]=Math.round(mean*100)/100;if(label===displayLabels[0]){chart.getDataSeries(key).title=metric;}else{chart.getDataSeries(key).title='';}}
+chartData.push(data);}
+chartData.push({});}
+chart.data=chartData;},setChartSize_(pageCount,displayLabelCount,chart){chart.graphHeight=tr.b.math.clamp(pageCount*displayLabelCount*20,300,600);chart.graphWidth=tr.b.math.clamp(pageCount*displayLabelCount*25,500,1000);},setChartColors_(mainMetricNames,displayLabels,chart){for(let i=0;i<mainMetricNames.length;++i){const mainMetric=mainMetricNames[i];for(let j=0;j<displayLabels.length;++j){const mainColorIndex=i%COLORS.length;const subColorIndex=j%COLORS[mainColorIndex].length;const color=COLORS[mainColorIndex][subColorIndex];const displayLabel=displayLabels[j];const series=this.getSeriesKey_(mainMetric,displayLabel);chart.getDataSeries(series).color=color;}}},getSeriesKey_(metricName,displayLabel){return metricName+'-'+displayLabel;},initializeColumnChart(title){const newChart=new tr.ui.b.NameColumnChart();newChart.isStacked=true;newChart.yAxisLabel='ms';newChart.hideXAxis=true;newChart.displayXInHover=true;newChart.isGrouped=true;newChart.enableToolTip=true;newChart.showTitleInLegend=true;newChart.chartTitle=title;newChart.titleHeight='14pt';return newChart;},initializeChildChart_(title,height,width){const childChart=new tr.ui.b.NameBarChart();childChart.xAxisLabel='ms';childChart.chartTitle=title;childChart.graphHeight=height;childChart.graphWidth=width;childChart.titleHeight='14pt';childChart.isStacked=true;childChart.hideLegend=true;childChart.isGrouped=true;childChart.isWaterfall=true;return childChart;},initializeCloseButton_(div,parent){const button=this.$.close.cloneNode(true);button.style.display='inline-block';button.addEventListener('click',()=>{Polymer.dom(parent).removeChild(div);});return button;},initializeSelectors_(mainStep){const select=this.$.selectors.cloneNode(true);select.style.display='block';Polymer.dom(select).querySelector('#start').addEventListener('keydown',(e)=>{if(e.key==='Enter')this.filterByPercentile_(select,mainStep);});Polymer.dom(select).querySelector('#end').addEventListener('keydown',(e)=>{if(e.key==='Enter')this.filterByPercentile_(select,mainStep);});Polymer.dom(select).querySelector('#filter').addEventListener('click',()=>{this.filterByPercentile_(select,mainStep);});Polymer.dom(select).querySelector('#search_page').addEventListener('keydown',(e)=>{if(e.key==='Enter')this.searchByPage_(select,mainStep);});Polymer.dom(select).querySelector('#search').addEventListener('click',()=>{this.searchByPage_(select,mainStep);});return select;},openMetricsChart_(mainStep){const div=document.createElement('div');div.classList.add('child_container');Polymer.dom(this.$.container).appendChild(div);const selectors=this.initializeSelectors_(mainStep);div.appendChild(selectors);const childChart=this.initializeColumnChart(mainStep+' Breakdown');childChart.toolTipCallBack=(rect)=>this.openChildChart_(rect,mainStep);div.appendChild(childChart);const button=this.initializeCloseButton_(div,this.$.container);div.appendChild(button);this.populateChart_(this.histograms,mainStep,childChart,false);this.charts_.set(mainStep,childChart);},initializeChild_(title,height,width){const div=document.createElement('div');div.classList.add('child_container');Polymer.dom(this.$.children).insertBefore(div,this.$.children.firstChild);const childChart=this.initializeChildChart_(title,height,width);div.appendChild(childChart);const button=this.initializeCloseButton_(div,this.$.children);div.appendChild(button);return childChart;},openChildChart_(rect,metricName){let mainStep;let mainStepIndex;for(let i=0;i<METRICS.get(metricName).length;++i){if(rect.key.startsWith(METRICS.get(metricName)[i])){mainStep=METRICS.get(metricName)[i];mainStepIndex=i;break;}}
+const subSteps=this.subMetricNames_.get(mainStep);const width=tr.b.math.clamp(subSteps.length*150,300,700);const height=tr.b.math.clamp(subSteps.length*this.displayLabels_.get(metricName).length*50,300,700);const pageName=rect.datum.group;const title=mainStep+': '+pageName;const childChart=this.initializeChild_(title,height,width);const pageData=this.groups_.get(metricName).get(pageName);const subStepData=this.processSubStepData(childChart,mainStepIndex,mainStep,pageData,pageName,0);childChart.data=subStepData.childData;},processSubStepData(childChart,mainStepIndex,mainStep,pageData,pageName,total){const childData=[];const subSteps=this.subMetricNames_.get(mainStep);if(subSteps===undefined)return{childData,total};for(const subStep of subSteps){const currentTotal=total;let j=0;for(const[displayLabel,labelMap]of pageData){const data={x:subStep,hide:currentTotal,group:displayLabel};const metricMap=labelMap.get(mainStep);const series=this.getSeriesKey_(subStep,displayLabel);const mean=metricMap.get(subStep).mean;const roundedMean=Math.round(mean*100)/100;data[series]=roundedMean===undefined?0:roundedMean;childData.push(data);if(j===0)total+=data[series];else data.x='.';const subColorIndex=j%COLORS[mainStepIndex].length;const color=COLORS[mainStepIndex][subColorIndex];childChart.getDataSeries(series).color=color;j++;}
+childData.push({x:'.'});}
+return{childData,total};},filterByPercentile_(select,metricName){const startPercentile=Polymer.dom(select).querySelector('#start').value;const endPercentile=Polymer.dom(select).querySelector('#end').value;if(startPercentile===''||endPercentile==='')return;const length=this.sortedPages_.get(metricName).length;const startIndex=this.getPercentileIndex_(startPercentile,length);const endIndex=this.getPercentileIndex_(endPercentile,length);const slicedPages=this.sortedPages_.get(metricName).slice(startIndex,endIndex);this.setChartData_(this.groups_.get(metricName),slicedPages,this.displayLabels_.get(metricName),this.charts_.get(metricName));},getPercentileIndex_(percentile,arrayLength){const index=Math.ceil(arrayLength*(percentile/100.0));if(index===-1)return 0;if(index>=arrayLength)return arrayLength;return index;},searchByPage_(select,metricName){const criteria=Polymer.dom(select).querySelector('#search_page').value;if(criteria==='')return;const query=new RegExp(criteria);const filteredGroups=[...this.groups_.get(metricName)].filter(group=>group[0].match(query));if(filteredGroups.length<1){Polymer.dom(select).querySelector('#search_error').style.display='block';return;}
+Polymer.dom(select).querySelector('#search_error').style.display='none';const matchedPageName=filteredGroups[0][0];const match=this.groups_.get(metricName).get(matchedPageName);const title=metricName+' Breakdown: '+matchedPageName;let totalSteps=0;for(const[mainStep,subSteps]of this.subMetricNames_){totalSteps+=subSteps.length;}
+const width=tr.b.math.clamp(totalSteps*150,300,700);const height=tr.b.math.clamp(totalSteps*this.displayLabels_.get(metricName).length*30,300,700);const childChart=this.initializeChild_(title,height,width);const childData=[];let total=0;for(let i=0;i<METRICS.get(metricName).length;++i){const stepData=this.processSubStepData(childChart,i,METRICS.get(metricName)[i],match,matchedPageName,total);childData.push(...stepData.childData);total=stepData.total;}
+childChart.data=childData;},});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-histogram-set-view',listeners:{export:'onExport_',loadVisualization:'onLoadVisualization_'},created(){this.brushingStateController_=new tr.ui.NullBrushingStateController();this.viewState_=new tr.v.ui.HistogramSetViewState();this.metricsVisualizationLoaded_=false;},ready(){this.$.table.viewState=this.viewState;this.$.controls.viewState=this.viewState;},attached(){this.brushingStateController.parentController=tr.c.BrushingStateController.getControllerForElement(this.parentNode);},get brushingStateController(){return this.brushingStateController_;},get viewState(){return this.viewState_;},get histograms(){return this.$.table.histograms;},async build(histograms,opt_options){const options=opt_options||{};const progress=options.progress||(()=>Promise.resolve());if(options.helpHref)this.$.controls.helpHref=options.helpHref;if(options.feedbackHref){this.$.controls.feedbackHref=options.feedbackHref;}
 if(histograms===undefined||histograms.length===0){this.$.container.style.display='none';this.$.zero.style.display='block';this.style.display='block';return;}
-this.$.zero.style.display='none';this.$.container.style.display='block';this.$.container.style.maxHeight=(window.innerHeight-16)+'px';const buildMark=tr.b.Timing.mark('histogram-set-view','build');await progress('Finding important Histograms...');const sourceHistogramsMark=tr.b.Timing.mark('histogram-set-view','sourceHistograms');const sourceHistograms=histograms.sourceHistograms;sourceHistogramsMark.end();this.$.controls.showAllEnabled=(sourceHistograms.length!==histograms.length);await progress('Collecting parameters...');const collectParametersMark=tr.b.Timing.mark('histogram-set-view','collectParameters');const parameterCollector=new tr.v.HistogramParameterCollector();parameterCollector.process(histograms);this.$.controls.baseStatisticNames=parameterCollector.statisticNames;this.$.controls.possibleGroupings=parameterCollector.possibleGroupings;const displayLabels=parameterCollector.labels;this.$.controls.displayLabels=displayLabels;collectParametersMark.end();await this.$.table.build(histograms,sourceHistograms,displayLabels,progress);buildMark.end();},onExport_(event){const mark=tr.b.Timing.mark('histogram-set-view','export'+
+this.$.zero.style.display='none';this.$.container.style.display='block';this.$.container.style.maxHeight=(window.innerHeight-16)+'px';const buildMark=tr.b.Timing.mark('histogram-set-view','build');await progress('Finding important Histograms...');const sourceHistogramsMark=tr.b.Timing.mark('histogram-set-view','sourceHistograms');const sourceHistograms=histograms.sourceHistograms;sourceHistogramsMark.end();this.$.controls.showAllEnabled=(sourceHistograms.length!==histograms.length);await progress('Collecting parameters...');const collectParametersMark=tr.b.Timing.mark('histogram-set-view','collectParameters');const parameterCollector=new tr.v.HistogramParameterCollector();parameterCollector.process(histograms);this.$.controls.baseStatisticNames=parameterCollector.statisticNames;this.$.controls.possibleGroupings=parameterCollector.possibleGroupings;const displayLabels=parameterCollector.labels;this.$.controls.displayLabels=displayLabels;collectParametersMark.end();const hist=[...histograms][0];const benchmarks=hist.diagnostics.get(tr.v.d.RESERVED_NAMES.BENCHMARKS);let enable=false;if(benchmarks!==undefined&&benchmarks.length>0){for(const benchmark of benchmarks){if(benchmark.includes('rendering')){enable=true;break;}}}
+this.$.controls.enableVisualization=enable;await this.$.table.build(histograms,sourceHistograms,displayLabels,progress);buildMark.end();},onExport_(event){const mark=tr.b.Timing.mark('histogram-set-view','export'+
 (event.merged?'Merged':'Raw')+event.format.toUpperCase());const histograms=event.merged?this.$.table.leafHistograms:this.histograms;let blob;if(event.format==='csv'){const csv=new tr.v.CSVBuilder(histograms);csv.build();blob=new window.Blob([csv.toString()],{type:'text/csv'});}else if(event.format==='json'){blob=new window.Blob([JSON.stringify(histograms.asDicts())],{type:'text/json'});}else{throw new Error(`Unable to export format "${event.format}"`);}
-const path=window.location.pathname.split('/');const basename=path[path.length-1].split('.')[0]||'histograms';const anchor=document.createElement('a');anchor.download=`${basename}.${event.format}`;anchor.href=window.URL.createObjectURL(blob);anchor.click();mark.end();},});return{};});'use strict';tr.exportTo('tr.ui',function(){Polymer({is:'tr-ui-sp-metrics-side-panel',behaviors:[tr.ui.behaviors.SidePanel],ready(){this.model_=undefined;this.rangeOfInterest_=undefined;this.metricLatenciesMs_=[];this.metrics_=[];tr.metrics.MetricRegistry.getAllRegisteredTypeInfos().forEach(function(m){if(m.constructor.name==='sampleMetric')return;this.metrics_.push({label:m.constructor.name,value:m.constructor.name});},this);this.metrics_.sort((x,y)=>x.label.localeCompare(y.label));this.settingsKey_='metrics-side-panel-metric-name';this.currentMetricName_='responsivenessMetric';const metricSelector=tr.ui.b.createSelector(this,'currentMetricName_',this.settingsKey_,this.currentMetricName_,this.metrics_);Polymer.dom(this.$.top_left_controls).appendChild(metricSelector);metricSelector.addEventListener('change',this.onMetricChange_.bind(this));this.currentMetricTypeInfo_=tr.metrics.MetricRegistry.findTypeInfoWithName(this.currentMetricName_);this.recomputeButton_=tr.ui.b.createButton('Recompute',this.onRecompute_,this);Polymer.dom(this.$.top_left_controls).appendChild(this.recomputeButton_);this.$.results.addEventListener('display-ready',()=>{this.$.results.style.display='';});},async build(model){this.model_=model;await this.updateContents_();},get metricLatencyMs(){return tr.b.math.Statistics.mean(this.metricLatenciesMs_);},onMetricChange_(){this.currentMetricTypeInfo_=tr.metrics.MetricRegistry.findTypeInfoWithName(this.currentMetricName_);this.metricLatenciesMs_=[];this.updateContents_();},onRecompute_(){this.updateContents_();},get textLabel(){return'Metrics';},supportsModel(m){if(!m){return{supported:false,reason:'No model available'};}
+const path=window.location.pathname.split('/');const basename=path[path.length-1].split('.')[0]||'histograms';const anchor=document.createElement('a');anchor.download=`${basename}.${event.format}`;anchor.href=window.URL.createObjectURL(blob);anchor.click();mark.end();},onLoadVisualization_(event){if(!this.metricsVisualizationLoaded_){this.$.metrics.build(this.$.table.leafHistograms,this.histograms);this.metricsVisualizationLoaded_=true;}else if(this.$.metrics.style.display==='none'){this.$.metrics.style.display='block';}else{this.$.metrics.style.display='none';}},});return{};});'use strict';tr.exportTo('tr.ui',function(){Polymer({is:'tr-ui-sp-metrics-side-panel',behaviors:[tr.ui.behaviors.SidePanel],ready(){this.model_=undefined;this.rangeOfInterest_=undefined;this.metricLatenciesMs_=[];this.metrics_=[];tr.metrics.MetricRegistry.getAllRegisteredTypeInfos().forEach(function(m){if(m.constructor.name==='sampleMetric')return;this.metrics_.push({label:m.constructor.name,value:m.constructor.name});},this);this.metrics_.sort((x,y)=>x.label.localeCompare(y.label));this.settingsKey_='metrics-side-panel-metric-name';this.currentMetricName_='responsivenessMetric';const metricSelector=tr.ui.b.createSelector(this,'currentMetricName_',this.settingsKey_,this.currentMetricName_,this.metrics_);Polymer.dom(this.$.top_left_controls).appendChild(metricSelector);metricSelector.addEventListener('change',this.onMetricChange_.bind(this));this.currentMetricTypeInfo_=tr.metrics.MetricRegistry.findTypeInfoWithName(this.currentMetricName_);this.recomputeButton_=tr.ui.b.createButton('Recompute',this.onRecompute_,this);Polymer.dom(this.$.top_left_controls).appendChild(this.recomputeButton_);this.$.results.addEventListener('display-ready',()=>{this.$.results.style.display='';});},async build(model){this.model_=model;await this.updateContents_();},get metricLatencyMs(){return tr.b.math.Statistics.mean(this.metricLatenciesMs_);},onMetricChange_(){this.currentMetricTypeInfo_=tr.metrics.MetricRegistry.findTypeInfoWithName(this.currentMetricName_);this.metricLatenciesMs_=[];this.updateContents_();},onRecompute_(){this.updateContents_();},get textLabel(){return'Metrics';},supportsModel(m){if(!m){return{supported:false,reason:'No model available'};}
 return{supported:true};},get model(){return this.model_;},set model(model){this.build(model);},get selection(){},set selection(_){},get rangeOfInterest(){return this.rangeOfInterest_;},set rangeOfInterest(range){this.rangeOfInterest_=range;if(this.currentMetricTypeInfo_&&this.currentMetricTypeInfo_.metadata.supportsRangeOfInterest){if((this.metricLatencyMs===undefined)||(this.metricLatencyMs<100)){this.updateContents_();}else{this.recomputeButton_.style.background='red';}}},async updateContents_(){Polymer.dom(this.$.error).textContent='';this.$.results.style.display='none';if(!this.model_){Polymer.dom(this.$.error).textContent='Missing model';return;}
 const options={metrics:[this.currentMetricName_]};if(this.currentMetricTypeInfo_&&this.currentMetricTypeInfo_.metadata.supportsRangeOfInterest&&this.rangeOfInterest&&!this.rangeOfInterest.isEmpty){options.rangeOfInterest=this.rangeOfInterest;}
 const startDate=new Date();const addFailureCb=failure=>{Polymer.dom(this.$.error).textContent=failure.description;};const histograms=tr.metrics.runMetrics(this.model_,options,addFailureCb);this.metricLatenciesMs_.push(new Date()-startDate);while(this.metricLatenciesMs_.length>20){this.metricLatenciesMs_.shift();}
diff --git a/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py b/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py
index ee621d6..58ddb7a 100644
--- a/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py
+++ b/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py
@@ -2,8 +2,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import hashlib
 import os
 import re
+import stat
+import subprocess
+import sys
+import urllib
 
 import py_utils
 
@@ -17,13 +22,77 @@
 # Text that ADB sends, but does not need to be displayed to the user.
 ADB_IGNORE_REGEXP = r'^capturing trace\.\.\. done|^capturing trace\.\.\.'
 
+# Constants required for converting perfetto traces.
+LINUX_SHA1 = 'cd9dbc5c92ed0167245c4559bf1971bb21378928'
+MAC_SHA1 = 'aed4ad02da526a3f1e4f9df47d4989ae9305b30e'
+T2T_OUTPUT = 'trace.systrace'
 
 def try_create_agent(options):
   if options.from_file is not None:
+    with open(options.from_file, 'rb') as f_in:
+      if is_perfetto(f_in):
+        if convert_perfetto_trace(options.from_file):
+          options.from_file = T2T_OUTPUT
+        else:
+          print ('Perfetto trace file: ' + options.from_file +
+                 ' could not be converted.')
+          sys.exit(1)
     return AtraceFromFileAgent(options)
   else:
     return False
 
+def convert_perfetto_trace(in_file):
+  t2t_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                          '../trace_to_text'))
+  loaded_t2t = False
+  if sys.platform.startswith('linux'):
+    t2t_path += '-linux-' + LINUX_SHA1
+    loaded_t2t = load_trace_to_text(t2t_path, 'linux', LINUX_SHA1)
+  elif sys.platform.startswith('darwin'):
+    t2t_path += '-mac-' + MAC_SHA1
+    loaded_t2t = load_trace_to_text(t2t_path, 'mac', MAC_SHA1)
+  if loaded_t2t:
+    os.chmod(t2t_path, stat.S_IXUSR)
+    return subprocess.call([t2t_path, 'systrace', in_file, T2T_OUTPUT]) == 0
+  return False
+
+def load_trace_to_text(t2t_path, platform, sha_value):
+  if not os.path.exists(t2t_path):
+    with open(t2t_path, 'w') as t2t:
+      url = ('https://storage.googleapis.com/chromium-telemetry/binary_dependencies/trace_to_text-'
+             + platform + '-' + sha_value)
+      return urllib.urlretrieve(url, t2t_path)
+  os.chmod(t2t_path, stat.S_IRUSR)
+  with open(t2t_path, 'rb') as t2t:
+    existing_file_hash = hashlib.sha1(t2t.read()).hexdigest()
+    if existing_file_hash != sha_value:
+      print 'Hash of trace_to_text binary does not match.'
+      os.remove(t2t_path)
+      return False
+    return True
+
+def is_perfetto(from_file):
+  # Starts with a preamble for field ID=1 (TracePacket)
+  if ord(from_file.read(1)) != 0x0a:
+    return False
+  for _ in range(10): # Check the first 10 packets are structured correctly
+    # Then a var int that specifies field size
+    field_size = 0
+    shift = 0
+    while True:
+      c = ord(from_file.read(1))
+      field_size |= (c & 0x7f) << shift
+      shift += 7
+      if not c & 0x80:
+        break
+    # The packet itself
+    from_file.seek(field_size, os.SEEK_CUR)
+    # The preamble for the next field ID=1 (TracePacket)
+    if ord(from_file.read(1)) != 0x0a:
+      return False
+  # Go back to the beginning of the file
+  from_file.seek(0)
+  return True
 
 class AtraceFromFileConfig(tracing_agents.TracingConfig):
   def __init__(self, from_file):
diff --git a/catapult/tracing/tracing/trace_data/trace_data.py b/catapult/tracing/tracing/trace_data/trace_data.py
index 6c0ff04..b3b783c 100644
--- a/catapult/tracing/tracing/trace_data/trace_data.py
+++ b/catapult/tracing/tracing/trace_data/trace_data.py
@@ -50,7 +50,6 @@
 CHROME_TRACE_PART = TraceDataPart('traceEvents')
 CPU_TRACE_DATA = TraceDataPart('cpuSnapshots')
 INSPECTOR_TRACE_PART = TraceDataPart('inspectorTimelineEvents')
-SURFACE_FLINGER_PART = TraceDataPart('surfaceFlinger')
 TELEMETRY_PART = TraceDataPart('telemetry')
 WALT_TRACE_PART = TraceDataPart('waltTraceEvents')
 
@@ -60,7 +59,6 @@
                    CHROME_TRACE_PART,
                    CPU_TRACE_DATA,
                    INSPECTOR_TRACE_PART,
-                   SURFACE_FLINGER_PART,
                    TELEMETRY_PART}
 
 ALL_TRACE_PARTS_RAW_NAMES = set(k.raw_field_name for k in ALL_TRACE_PARTS)
@@ -274,7 +272,7 @@
     self._raw_data = {}
 
   def AsData(self):
-    if self._raw_data == None:
+    if self._raw_data is None:
       raise Exception('Can only AsData once')
     data = TraceData()
     data._SetFromBuilder(self._raw_data)
@@ -292,7 +290,7 @@
               isinstance(trace, dict) or
               isinstance(trace, list))
 
-    if self._raw_data == None:
+    if self._raw_data is None:
       raise Exception('Already called AsData() on this builder.')
 
     self._raw_data.setdefault(part.raw_field_name, [])