Merge "Check for valid slice in UpdateThreadDeltasForSliceId."
diff --git a/Android.bp b/Android.bp
index b5cf2a1..d16e223 100644
--- a/Android.bp
+++ b/Android.bp
@@ -56,6 +56,7 @@
     "src/trace_processor/metrics/android/unmapped_java_symbols.sql",
     "src/trace_processor/metrics/android/unsymbolized_frames.sql",
     "src/trace_processor/metrics/chrome/chrome_processes.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank.sql",
     "src/trace_processor/metrics/trace_metadata.sql",
     "src/trace_processor/metrics/webview/webview_power_usage.sql",
   ],
@@ -6246,6 +6247,7 @@
   name: "perfetto_src_profiling_memory_client_ext",
   srcs: [
     "src/profiling/memory/client_ext.cc",
+    "src/profiling/memory/client_ext_android.cc",
   ],
 }
 
diff --git a/BUILD b/BUILD
index 6772e25..cfea89b 100644
--- a/BUILD
+++ b/BUILD
@@ -781,6 +781,7 @@
         "src/trace_processor/metrics/android/unmapped_java_symbols.sql",
         "src/trace_processor/metrics/android/unsymbolized_frames.sql",
         "src/trace_processor/metrics/chrome/chrome_processes.sql",
+        "src/trace_processor/metrics/chrome/scroll_jank.sql",
         "src/trace_processor/metrics/trace_metadata.sql",
         "src/trace_processor/metrics/webview/webview_power_usage.sql",
     ],
diff --git a/src/base/subprocess_unittest.cc b/src/base/subprocess_unittest.cc
index f31b360..6bcdc99 100644
--- a/src/base/subprocess_unittest.cc
+++ b/src/base/subprocess_unittest.cc
@@ -171,7 +171,14 @@
   EXPECT_EQ(p.returncode(), 128 + SIGKILL);
 }
 
-TEST(SubprocessTest, PollBehavesProperly) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && defined(ADDRESS_SANITIZER)
+#define MAYBE_PollBehavesProperly DISABLED_PollBehavesProperly
+#else
+#define MAYBE_PollBehavesProperly PollBehavesProperly
+#endif
+
+// TODO(b/158484911): Re-enable once problem is fixed.
+TEST(SubprocessTest, MAYBE_PollBehavesProperly) {
   Subprocess p({"sh", "-c", "echo foobar"});
   p.args.stdout_mode = Subprocess::kBuffer;
   p.args.input = "ignored";
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 143860b..7e1192d 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -97,6 +97,9 @@
     "../common:proc_utils",
   ]
   sources = [ "client_ext.cc" ]
+  if (perfetto_build_with_android) {
+    sources += [ "client_ext_android.cc" ]
+  }
 }
 
 source_set("wire_protocol") {
diff --git a/src/profiling/memory/client_ext.cc b/src/profiling/memory/client_ext.cc
index 3406e3f..31c57a7 100644
--- a/src/profiling/memory/client_ext.cc
+++ b/src/profiling/memory/client_ext.cc
@@ -38,42 +38,15 @@
 
 #include "src/profiling/common/proc_utils.h"
 #include "src/profiling/memory/client.h"
+#include "src/profiling/memory/client_ext_factory.h"
 #include "src/profiling/memory/scoped_spinlock.h"
 #include "src/profiling/memory/unhooked_allocator.h"
 #include "src/profiling/memory/wire_protocol.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-#include <android/fdsan.h>
-#include <sys/system_properties.h>
-#endif
-
 using perfetto::profiling::ScopedSpinlock;
 using perfetto::profiling::UnhookedAllocator;
 
 namespace {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-std::string ReadSystemProperty(const char* key) {
-  std::string prop_value;
-  const prop_info* prop = __system_property_find(key);
-  if (!prop) {
-    return prop_value;  // empty
-  }
-  __system_property_read_callback(
-      prop,
-      [](void* cookie, const char*, const char* value, uint32_t) {
-        std::string* pv = reinterpret_cast<std::string*>(cookie);
-        *pv = value;
-      },
-      &prop_value);
-  return prop_value;
-}
-#else
-// TODO(fmayer): restructure code to separate android specifics.
-std::string ReadSystemProperty(const char*) {
-  return "";
-}
-#endif
-
 #if defined(__GLIBC__)
 const char* getprogname() {
   return program_invocation_short_name;
@@ -140,53 +113,6 @@
 
 std::atomic<uint32_t> g_next_heap_id{kMinHeapId};
 
-constexpr char kHeapprofdBinPath[] = "/system/bin/heapprofd";
-
-int CloneWithoutSigchld() {
-  auto ret = clone(nullptr, nullptr, 0, nullptr);
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  if (ret == 0)
-    android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_DISABLED);
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  return ret;
-}
-
-int ForklikeClone() {
-  auto ret = clone(nullptr, nullptr, SIGCHLD, nullptr);
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  if (ret == 0)
-    android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_DISABLED);
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  return ret;
-}
-
-// Like daemon(), but using clone to avoid invoking pthread_atfork(3) handlers.
-int Daemonize() {
-  switch (ForklikeClone()) {
-    case -1:
-      PERFETTO_PLOG("Daemonize.clone");
-      return -1;
-    case 0:
-      break;
-    default:
-      _exit(0);
-  }
-  if (setsid() == -1) {
-    PERFETTO_PLOG("Daemonize.setsid");
-    return -1;
-  }
-  // best effort chdir & fd close
-  chdir("/");
-  int fd = open("/dev/null", O_RDWR, 0);
-  if (fd != -1) {
-    dup2(fd, STDIN_FILENO);
-    dup2(fd, STDOUT_FILENO);
-    dup2(fd, STDERR_FILENO);
-    if (fd > STDERR_FILENO)
-      close(fd);
-  }
-  return 0;
-}
 
 // Called only if |g_client_lock| acquisition fails, which shouldn't happen
 // unless we're in a completely unexpected state (which we won't know how to
@@ -203,111 +129,6 @@
   abort();
 }
 
-bool ForceForkPrivateDaemon() {
-  // Note: if renaming the property, also update system_property.cc
-  std::string mode = ReadSystemProperty("heapprofd.userdebug.mode");
-  return mode == "fork";
-}
-
-std::shared_ptr<perfetto::profiling::Client> CreateClientForCentralDaemon(
-    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
-  PERFETTO_LOG("Constructing client for central daemon.");
-  using perfetto::profiling::Client;
-
-  perfetto::base::Optional<perfetto::base::UnixSocketRaw> sock =
-      Client::ConnectToHeapprofd(perfetto::profiling::kHeapprofdSocketFile);
-  if (!sock) {
-    PERFETTO_ELOG("Failed to connect to %s. This is benign on user builds.",
-                  perfetto::profiling::kHeapprofdSocketFile);
-    return nullptr;
-  }
-  return Client::CreateAndHandshake(std::move(sock.value()),
-                                    unhooked_allocator);
-}
-
-std::shared_ptr<perfetto::profiling::Client> CreateClientAndPrivateDaemon(
-    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
-  PERFETTO_LOG("Setting up fork mode profiling.");
-  perfetto::base::UnixSocketRaw parent_sock;
-  perfetto::base::UnixSocketRaw child_sock;
-  std::tie(parent_sock, child_sock) = perfetto::base::UnixSocketRaw::CreatePair(
-      perfetto::base::SockFamily::kUnix, perfetto::base::SockType::kStream);
-
-  if (!parent_sock || !child_sock) {
-    PERFETTO_PLOG("Failed to create socketpair.");
-    return nullptr;
-  }
-
-  child_sock.RetainOnExec();
-
-  // Record own pid and cmdline, to pass down to the forked heapprofd.
-  pid_t target_pid = getpid();
-  std::string target_cmdline;
-  if (!perfetto::profiling::GetCmdlineForPID(target_pid, &target_cmdline)) {
-    target_cmdline = "failed-to-read-cmdline";
-    PERFETTO_ELOG(
-        "Failed to read own cmdline, proceeding as this might be a by-pid "
-        "profiling request (which will still work).");
-  }
-
-  // Prepare arguments for heapprofd.
-  std::string pid_arg =
-      std::string("--exclusive-for-pid=") + std::to_string(target_pid);
-  std::string cmd_arg =
-      std::string("--exclusive-for-cmdline=") + target_cmdline;
-  std::string fd_arg =
-      std::string("--inherit-socket-fd=") + std::to_string(child_sock.fd());
-  const char* argv[] = {kHeapprofdBinPath, pid_arg.c_str(), cmd_arg.c_str(),
-                        fd_arg.c_str(), nullptr};
-
-  // Use fork-like clone to avoid invoking the host's pthread_atfork(3)
-  // handlers. Also avoid sending the current process a SIGCHILD to further
-  // reduce our interference.
-  pid_t clone_pid = CloneWithoutSigchld();
-  if (clone_pid == -1) {
-    PERFETTO_PLOG("Failed to clone.");
-    return nullptr;
-  }
-  if (clone_pid == 0) {  // child
-    // Daemonize clones again, terminating the calling thread (i.e. the direct
-    // child of the original process). So the rest of this codepath will be
-    // executed in a new reparented process.
-    if (Daemonize() == -1) {
-      PERFETTO_PLOG("Daemonization failed.");
-      _exit(1);
-    }
-    execv(kHeapprofdBinPath, const_cast<char**>(argv));
-    PERFETTO_PLOG("Failed to execute private heapprofd.");
-    _exit(1);
-  }  // else - parent continuing the client setup
-
-  child_sock.ReleaseFd().reset();  // close child socket's fd
-  if (!parent_sock.SetTxTimeout(perfetto::profiling::kClientSockTimeoutMs)) {
-    PERFETTO_PLOG("Failed to set socket transmit timeout.");
-    return nullptr;
-  }
-
-  if (!parent_sock.SetRxTimeout(perfetto::profiling::kClientSockTimeoutMs)) {
-    PERFETTO_PLOG("Failed to set socket receive timeout.");
-    return nullptr;
-  }
-
-  // Wait on the immediate child to exit (allow for ECHILD in the unlikely case
-  // we're in a process that has made its children unwaitable).
-  // __WCLONE is defined in hex on glibc, so is unsigned per C++ semantics.
-  // waitpid expects a signed integer, so clang complains without the cast.
-  int unused = 0;
-  if (PERFETTO_EINTR(waitpid(clone_pid, &unused, static_cast<int>(__WCLONE))) ==
-          -1 &&
-      errno != ECHILD) {
-    PERFETTO_PLOG("Failed to waitpid on immediate child.");
-    return nullptr;
-  }
-
-  return perfetto::profiling::Client::CreateAndHandshake(std::move(parent_sock),
-                                                         unhooked_allocator);
-}
-
 // Note: g_client can be reset by heapprofd_initialize without calling this
 // function.
 
@@ -324,12 +145,13 @@
   }
 }
 
-void ShutdownLazy() {
+void ShutdownLazy(const std::shared_ptr<perfetto::profiling::Client>& client) {
   ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
   if (PERFETTO_UNLIKELY(!s.locked()))
     AbortOnSpinlockTimeout();
 
-  if (!*GetClientLocked())  // other invocation already initiated shutdown
+  // other invocation already initiated shutdown
+  if (*GetClientLocked() != client)
     return;
 
   DisableAllHeaps();
@@ -434,7 +256,7 @@
   if (!client->RecordMalloc(
           heap.service_heap_id.load(std::memory_order_relaxed),
           sampled_alloc_sz, size, id)) {
-    ShutdownLazy();
+    ShutdownLazy(client);
   }
   return true;
 }
@@ -458,7 +280,7 @@
   if (client) {
     if (!client->RecordFree(
             heap.service_heap_id.load(std::memory_order_relaxed), id))
-      ShutdownLazy();
+      ShutdownLazy(client);
   }
 }
 
@@ -503,11 +325,8 @@
 
   // These factory functions use heap objects, so we need to run them without
   // the spinlock held.
-  std::shared_ptr<perfetto::profiling::Client> client;
-  if (!ForceForkPrivateDaemon())
-    client = CreateClientForCentralDaemon(unhooked_allocator);
-  if (!client)
-    client = CreateClientAndPrivateDaemon(unhooked_allocator);
+  std::shared_ptr<perfetto::profiling::Client> client =
+      perfetto::profiling::ConstructClient(unhooked_allocator);
 
   if (!client) {
     PERFETTO_LOG("%s: heapprofd_client not initialized, not installing hooks.",
diff --git a/src/profiling/memory/client_ext_android.cc b/src/profiling/memory/client_ext_android.cc
new file mode 100644
index 0000000..5a2a0fc
--- /dev/null
+++ b/src/profiling/memory/client_ext_android.cc
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/memory/client_ext_factory.h"
+
+#include <string>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/unix_socket.h"
+#include "src/profiling/common/proc_utils.h"
+#include "src/profiling/memory/client.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#error "Must be built on Android."
+#endif
+
+#include <android/fdsan.h>
+#include <sys/system_properties.h>
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+constexpr char kHeapprofdBinPath[] = "/system/bin/heapprofd";
+
+int CloneWithoutSigchld() {
+  auto ret = clone(nullptr, nullptr, 0, nullptr);
+  if (ret == 0)
+    android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_DISABLED);
+  return ret;
+}
+
+int ForklikeClone() {
+  auto ret = clone(nullptr, nullptr, SIGCHLD, nullptr);
+  if (ret == 0)
+    android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_DISABLED);
+  return ret;
+}
+
+// Like daemon(), but using clone to avoid invoking pthread_atfork(3) handlers.
+int Daemonize() {
+  switch (ForklikeClone()) {
+    case -1:
+      PERFETTO_PLOG("Daemonize.clone");
+      return -1;
+    case 0:
+      break;
+    default:
+      _exit(0);
+  }
+  if (setsid() == -1) {
+    PERFETTO_PLOG("Daemonize.setsid");
+    return -1;
+  }
+  // best effort chdir & fd close
+  chdir("/");
+  int fd = open("/dev/null", O_RDWR, 0);
+  if (fd != -1) {
+    dup2(fd, STDIN_FILENO);
+    dup2(fd, STDOUT_FILENO);
+    dup2(fd, STDERR_FILENO);
+    if (fd > STDERR_FILENO)
+      close(fd);
+  }
+  return 0;
+}
+
+std::string ReadSystemProperty(const char* key) {
+  std::string prop_value;
+  const prop_info* prop = __system_property_find(key);
+  if (!prop) {
+    return prop_value;  // empty
+  }
+  __system_property_read_callback(
+      prop,
+      [](void* cookie, const char*, const char* value, uint32_t) {
+        std::string* pv = reinterpret_cast<std::string*>(cookie);
+        *pv = value;
+      },
+      &prop_value);
+  return prop_value;
+}
+
+bool ForceForkPrivateDaemon() {
+  // Note: if renaming the property, also update system_property.cc
+  std::string mode = ReadSystemProperty("heapprofd.userdebug.mode");
+  return mode == "fork";
+}
+
+std::shared_ptr<perfetto::profiling::Client> CreateClientForCentralDaemon(
+    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
+  PERFETTO_LOG("Constructing client for central daemon.");
+  using perfetto::profiling::Client;
+
+  perfetto::base::Optional<perfetto::base::UnixSocketRaw> sock =
+      Client::ConnectToHeapprofd(perfetto::profiling::kHeapprofdSocketFile);
+  if (!sock) {
+    PERFETTO_ELOG("Failed to connect to %s. This is benign on user builds.",
+                  perfetto::profiling::kHeapprofdSocketFile);
+    return nullptr;
+  }
+  return Client::CreateAndHandshake(std::move(sock.value()),
+                                    unhooked_allocator);
+}
+
+std::shared_ptr<perfetto::profiling::Client> CreateClientAndPrivateDaemon(
+    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
+  PERFETTO_LOG("Setting up fork mode profiling.");
+  perfetto::base::UnixSocketRaw parent_sock;
+  perfetto::base::UnixSocketRaw child_sock;
+  std::tie(parent_sock, child_sock) = perfetto::base::UnixSocketRaw::CreatePair(
+      perfetto::base::SockFamily::kUnix, perfetto::base::SockType::kStream);
+
+  if (!parent_sock || !child_sock) {
+    PERFETTO_PLOG("Failed to create socketpair.");
+    return nullptr;
+  }
+
+  child_sock.RetainOnExec();
+
+  // Record own pid and cmdline, to pass down to the forked heapprofd.
+  pid_t target_pid = getpid();
+  std::string target_cmdline;
+  if (!perfetto::profiling::GetCmdlineForPID(target_pid, &target_cmdline)) {
+    target_cmdline = "failed-to-read-cmdline";
+    PERFETTO_ELOG(
+        "Failed to read own cmdline, proceeding as this might be a by-pid "
+        "profiling request (which will still work).");
+  }
+
+  // Prepare arguments for heapprofd.
+  std::string pid_arg =
+      std::string("--exclusive-for-pid=") + std::to_string(target_pid);
+  std::string cmd_arg =
+      std::string("--exclusive-for-cmdline=") + target_cmdline;
+  std::string fd_arg =
+      std::string("--inherit-socket-fd=") + std::to_string(child_sock.fd());
+  const char* argv[] = {kHeapprofdBinPath, pid_arg.c_str(), cmd_arg.c_str(),
+                        fd_arg.c_str(), nullptr};
+
+  // Use fork-like clone to avoid invoking the host's pthread_atfork(3)
+  // handlers. Also avoid sending the current process a SIGCHILD to further
+  // reduce our interference.
+  pid_t clone_pid = CloneWithoutSigchld();
+  if (clone_pid == -1) {
+    PERFETTO_PLOG("Failed to clone.");
+    return nullptr;
+  }
+  if (clone_pid == 0) {  // child
+    // Daemonize clones again, terminating the calling thread (i.e. the direct
+    // child of the original process). So the rest of this codepath will be
+    // executed in a new reparented process.
+    if (Daemonize() == -1) {
+      PERFETTO_PLOG("Daemonization failed.");
+      _exit(1);
+    }
+    execv(kHeapprofdBinPath, const_cast<char**>(argv));
+    PERFETTO_PLOG("Failed to execute private heapprofd.");
+    _exit(1);
+  }  // else - parent continuing the client setup
+
+  child_sock.ReleaseFd().reset();  // close child socket's fd
+  if (!parent_sock.SetTxTimeout(perfetto::profiling::kClientSockTimeoutMs)) {
+    PERFETTO_PLOG("Failed to set socket transmit timeout.");
+    return nullptr;
+  }
+
+  if (!parent_sock.SetRxTimeout(perfetto::profiling::kClientSockTimeoutMs)) {
+    PERFETTO_PLOG("Failed to set socket receive timeout.");
+    return nullptr;
+  }
+
+  // Wait on the immediate child to exit (allow for ECHILD in the unlikely case
+  // we're in a process that has made its children unwaitable).
+  // __WCLONE is defined in hex on glibc, so is unsigned per C++ semantics.
+  // waitpid expects a signed integer, so clang complains without the cast.
+  int unused = 0;
+  if (PERFETTO_EINTR(waitpid(clone_pid, &unused, static_cast<int>(__WCLONE))) ==
+          -1 &&
+      errno != ECHILD) {
+    PERFETTO_PLOG("Failed to waitpid on immediate child.");
+    return nullptr;
+  }
+
+  return perfetto::profiling::Client::CreateAndHandshake(std::move(parent_sock),
+                                                         unhooked_allocator);
+}
+
+}  // namespace
+
+std::shared_ptr<Client> ConstructClient(
+    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
+  std::shared_ptr<perfetto::profiling::Client> client;
+  if (!ForceForkPrivateDaemon())
+    client = CreateClientForCentralDaemon(unhooked_allocator);
+  if (!client)
+    client = CreateClientAndPrivateDaemon(unhooked_allocator);
+  return client;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/client_ext_factory.h b/src/profiling/memory/client_ext_factory.h
new file mode 100644
index 0000000..832e54c
--- /dev/null
+++ b/src/profiling/memory/client_ext_factory.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
+#define SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
+
+#include <memory>
+
+#include "src/profiling/memory/client.h"
+#include "src/profiling/memory/unhooked_allocator.h"
+
+namespace perfetto {
+namespace profiling {
+
+std::shared_ptr<Client> ConstructClient(
+    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator);
+
+}
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
diff --git a/src/trace_processor/importers/common/clock_tracker.cc b/src/trace_processor/importers/common/clock_tracker.cc
index af809db..4f52029 100644
--- a/src/trace_processor/importers/common/clock_tracker.cc
+++ b/src/trace_processor/importers/common/clock_tracker.cc
@@ -44,8 +44,7 @@
   const auto snapshot_id = cur_snapshot_id_++;
 
   // Clear the cache
-  static_assert(std::is_trivial<decltype(cache_)>::value, "must be trivial");
-  memset(&cache_[0], 0, sizeof(cache_));
+  cache_.fill({});
 
   // Compute the fingerprint of the snapshot by hashing all clock ids. This is
   // used by the clock pathfinding logic.
@@ -252,11 +251,18 @@
     // And use that to retrieve the corresponding time in the next clock domain.
     // The snapshot id must exist in the target clock domain. If it doesn't
     // either the hash logic or the pathfinding logic are bugged.
+    // This can also happen if the sanity checks in AddSnapshot fail and we
+    // skip part of the snapshot.
     const ClockSnapshots& next_snap = next_clock->GetSnapshot(hash);
+
+    // Using std::lower_bound because snapshot_ids is sorted, so we can do
+    // a binary search. std::find would do a linear scan.
     auto next_it = std::lower_bound(next_snap.snapshot_ids.begin(),
                                     next_snap.snapshot_ids.end(), snapshot_id);
-    PERFETTO_DCHECK(next_it != next_snap.snapshot_ids.end() &&
-                    *next_it == snapshot_id);
+    if (next_it == next_snap.snapshot_ids.end() || *next_it != snapshot_id) {
+      PERFETTO_DFATAL("Snapshot does not exist in clock domain.");
+      continue;
+    }
     size_t next_index = static_cast<size_t>(
         std::distance(next_snap.snapshot_ids.begin(), next_it));
     PERFETTO_DCHECK(next_index < next_snap.snapshot_ids.size());
diff --git a/src/trace_processor/metrics/BUILD.gn b/src/trace_processor/metrics/BUILD.gn
index 088e4bb..b2493a3 100644
--- a/src/trace_processor/metrics/BUILD.gn
+++ b/src/trace_processor/metrics/BUILD.gn
@@ -55,6 +55,7 @@
   "android/unmapped_java_symbols.sql",
   "android/unsymbolized_frames.sql",
   "chrome/chrome_processes.sql",
+  "chrome/scroll_jank.sql",
   "webview/webview_power_usage.sql",
 ]
 
diff --git a/src/trace_processor/metrics/chrome/scroll_jank.sql b/src/trace_processor/metrics/chrome/scroll_jank.sql
new file mode 100644
index 0000000..21cae10
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/scroll_jank.sql
@@ -0,0 +1,278 @@
+--
+-- Copyright 2020 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- A collection of metrics related to GestureScrollUpdate events.
+--
+-- We define a GestureScrollUpdate to be janky if comparing forwards or
+-- backwards (ignoring coalesced updates) a given GestureScrollUpdate exceeds
+-- the duration of its predecessor or successor by 50% of a vsync interval
+-- (defaulted to 60 FPS).
+--
+-- WARNING: This metric should not be used as a source of truth. It is under
+--          active development and the values & meaning might change without
+--          notice.
+
+
+-- Get all chrome processes and threads tables set up.
+SELECT RUN_METRIC('chrome/chrome_processes.sql');
+
+-- When working on GestureScrollUpdate events we need to ensure we have all the
+-- events from the browser, renderer, and GPU processes. This query isn't quite
+-- perfect. In system tracing we could have 3 browser processes all in the
+-- background and this would match, but for now its the best we can do (renderer
+-- and GPU names on android are quite complicated, but this should filter 99% (
+-- citation needed) of what we want.
+--
+-- See b/151077536 for historical context.
+DROP VIEW IF EXISTS sufficient_chrome_processes;
+
+CREATE VIEW sufficient_chrome_processes AS
+  SELECT
+    CASE WHEN (
+      SELECT COUNT(*) FROM chrome_process) = 0
+    THEN
+      FALSE
+    ELSE (
+      SELECT COUNT(*) >= 3 FROM (
+        SELECT name FROM chrome_process
+        WHERE
+          name LIKE "Browser" OR
+          name LIKE "Renderer" OR
+          name LIKE "Gpu" OR
+          name LIKE 'com.android.chrome%' OR
+          name LIKE 'com.chrome.beta%' OR
+          name LIKE 'com.chrome.dev%' OR
+          name LIKE 'com.chrome.canary%' OR
+          name LIKE 'com.google.android.apps.chrome%' OR
+          name LIKE 'org.chromium.chrome%'
+        GROUP BY name
+    )) END AS have_enough_chrome_processes;
+
+-- A simple table that checks the time between VSync (this can be used to
+-- determine if we're scrolling at 90 FPS or 60 FPS.
+--
+-- Note: In traces without the "Java" category there will be no VSync
+--       TraceEvents and this table will be empty.
+--
+-- Note: Must be a TABLE because it uses a window function which can behave
+--       strangely in views.
+DROP TABLE IF EXISTS vsync_intervals;
+
+CREATE TABLE vsync_intervals AS
+  SELECT
+    slice_id,
+    ts,
+    dur,
+    track_id,
+    LEAD(ts) OVER(PARTITION BY track_id ORDER BY ts) - ts AS time_to_next_vsync
+  FROM slice
+  WHERE name = "VSync"
+  ORDER BY track_id, ts;
+
+-- Get all the GestureScrollBegin and GestureScrollEnd events. We take their
+-- IDs to group them together into scrolls later and the timestamp and duration
+-- to compute the duration of the scroll.
+DROP VIEW IF EXISTS scroll_begin_and_end;
+
+CREATE VIEW scroll_begin_and_end AS
+  SELECT
+    slice.name,
+    slice.id,
+    slice.ts,
+    slice.dur,
+    slice.track_id,
+    EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id')
+        AS gesture_scroll_id,
+    EXTRACT_ARG(arg_set_id, "chrome_latency_info.trace_id") AS trace_id
+  FROM
+    slice
+  WHERE
+    slice.name IN (
+      'InputLatency::GestureScrollBegin',
+      'InputLatency::GestureScrollEnd'
+    )
+  ORDER BY ts;
+
+-- Now we take the GestureScrollBegin and the GestureScrollEnd events and join
+-- the information into a single row per scroll. We also compute the average
+-- Vysnc interval of the scroll (hopefully this would be either 60 FPS for the
+-- whole scroll or 90 FPS but that isn't always the case). If the trace doesn't
+-- contain the VSync TraceEvent we just fall back on assuming its 60 FPS (this
+-- is the 1.6e+7 in the COALESCE which corresponds to 16 ms or 60 FPS).
+DROP VIEW IF EXISTS joined_scroll_begin_and_end;
+
+CREATE VIEW joined_scroll_begin_and_end AS
+  SELECT
+    begin.id AS begin_id,
+    begin.ts AS begin_ts,
+    begin.dur AS begin_dur,
+    begin.track_id AS begin_track_id,
+    begin.trace_id AS begin_trace_id,
+    COALESCE(begin.gesture_scroll_id, begin.trace_id)
+        AS begin_gesture_scroll_id,
+    end.ts AS end_ts,
+    end.ts + end.dur AS end_ts_and_dur,
+    end.trace_id AS end_trace_id,
+    COALESCE((
+      SELECT
+        CAST(AVG(time_to_next_vsync) AS FLOAT)
+      FROM vsync_intervals in_query
+      WHERE
+        time_to_next_vsync IS NOT NULL AND
+        in_query.ts > begin.ts AND
+        in_query.ts < end.ts
+    ), 1.6e+7) AS avg_vsync_interval
+  FROM scroll_begin_and_end begin JOIN scroll_begin_and_end end ON
+    begin.trace_id < end.trace_id AND
+    begin.name = 'InputLatency::GestureScrollBegin' AND
+    end.name = 'InputLatency::GestureScrollEnd' AND (
+      (
+        begin.gesture_scroll_id IS NULL AND
+        end.trace_id = (
+          SELECT MIN(trace_id)
+          FROM scroll_begin_and_end in_query
+          WHERE
+            name = 'InputLatency::GestureScrollEnd' AND
+          in_query.trace_id > begin.trace_id
+        )
+      ) OR
+      end.gesture_scroll_id = begin.gesture_scroll_id
+    )
+  ORDER BY begin.ts;
+
+-- Get the GestureScrollUpdate events by name ordered by the
+-- |gesture_scroll_id|, and timestamp. Then compute the number of frames (
+-- relative to vsync interval) that each event took. 1.6e+7 is 16 ms in
+-- nanoseconds and is used in case there are no VSync events to default to 60
+-- fps. We join each GestureScrollUpdate event to the information about it'
+-- begin and end events for easy computation later.
+--
+-- We remove updates with |dur| == -1 because this means we have no end event
+-- and can't reasonably determine what it should be. We have separate tracking
+-- to ensure this only happens at the end of the trace where its expected.
+--
+-- Note: Must be a TABLE because it uses a window function which can behave
+--       strangely in views.
+DROP TABLE IF EXISTS gesture_scroll_update;
+
+CREATE TABLE gesture_scroll_update AS
+  SELECT
+    ROW_NUMBER() OVER (
+      ORDER BY gesture_scroll_id ASC, ts ASC) AS row_number,
+    begin_id,
+    begin_ts,
+    begin_dur,
+    begin_track_id,
+    begin_trace_id,
+    COALESCE(gesture_scroll_id, begin_trace_id) AS gesture_scroll_id,
+    CASE WHEN
+      end_ts_and_dur > ts + dur THEN
+        end_ts_and_dur
+      ELSE
+        ts + dur
+      END AS maybe_scroll_end,
+    id,
+    ts,
+    dur,
+    track_id,
+    trace_id,
+    dur/avg_vsync_interval AS scroll_frames_exact
+  FROM joined_scroll_begin_and_end begin_and_end JOIN (
+    SELECT
+      EXTRACT_ARG(arg_set_id, "chrome_latency_info.trace_id") AS trace_id,
+      EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id')
+          AS gesture_scroll_id,
+      *
+    FROM
+      slice JOIN track ON slice.track_id = track.id
+    WHERE
+      slice.name = 'InputLatency::GestureScrollUpdate' AND
+      slice.dur != -1 AND
+      NOT COALESCE(
+              EXTRACT_ARG(arg_set_id, "chrome_latency_info.is_coalesced"),
+              TRUE)
+  ) scroll_update ON
+  scroll_update.ts <= begin_and_end.end_ts AND
+  scroll_update.ts >= begin_and_end.begin_ts AND
+  scroll_update.trace_id > begin_and_end.begin_trace_id AND
+  scroll_update.trace_id < begin_and_end.end_trace_id AND (
+    scroll_update.gesture_scroll_id IS NULL OR
+    scroll_update.gesture_scroll_id = begin_and_end.begin_gesture_scroll_id
+  );
+
+-- This takes the GestureScrollUpdate events and joins it to the previous
+-- GestureScrollUpdate event (previous row and NULL if there isn't one) and the
+-- next GestureScrollUpdate event (next row and again NULL if there isn't one).
+-- Then we compute the duration of the event (relative to fps) and see if it
+-- increased by more then 0.5 (which is 1/2 of 16 ms at 60 fps, and so on).
+--
+-- We only compare a GestureScrollUpdate event to another event within the same
+-- scroll (gesture_scroll_id == prev/next gesture_scroll_id). This controls
+-- somewhat for variability of scrolls.
+--
+-- Note: Must be a TABLE because it uses a window function which can behave
+--       strangely in views.
+DROP TABLE IF EXISTS scroll_jank_maybe_null_prev_and_next;
+
+CREATE TABLE scroll_jank_maybe_null_prev_and_next AS
+  SELECT
+    currprev.*,
+    CASE WHEN
+      currprev.gesture_scroll_id != prev_gesture_scroll_id OR
+      prev_ts IS NULL OR
+      prev_ts < currprev.begin_ts OR
+      prev_ts > currprev.maybe_scroll_end
+    THEN
+      FALSE
+    ELSE
+      currprev.scroll_frames_exact > prev_scroll_frames_exact + 0.5
+    END AS prev_jank,
+    CASE WHEN
+      currprev.gesture_scroll_id != next.gesture_scroll_id OR
+      next.ts IS NULL OR
+      next.ts < currprev.begin_ts OR
+      next.ts > currprev.maybe_scroll_end
+    THEN
+      FALSE
+    ELSE
+      currprev.scroll_frames_exact > next.scroll_frames_exact + 0.5
+    END AS next_jank,
+    next.scroll_frames_exact AS next_scroll_frames_exact
+  FROM (
+    SELECT
+      curr.*,
+      curr.maybe_scroll_end - curr.begin_ts AS scroll_dur,
+      prev.ts AS prev_ts,
+      prev.gesture_scroll_id AS prev_gesture_scroll_id,
+      prev.scroll_frames_exact AS prev_scroll_frames_exact
+    FROM
+      gesture_scroll_update curr LEFT JOIN
+      gesture_scroll_update prev ON prev.row_number + 1 = curr.row_number
+  ) currprev LEFT JOIN
+  gesture_scroll_update next ON currprev.row_number + 1 = next.row_number
+  ORDER BY currprev.gesture_scroll_id ASC, currprev.ts ASC;
+
+-- This just uses prev_jank and next_jank to see if each GestureScrollUpdate
+-- event is a jank.
+DROP VIEW IF EXISTS scroll_jank;
+
+CREATE VIEW scroll_jank AS
+  SELECT
+    *,
+    (next_jank IS NOT NULL AND next_jank) OR
+    (prev_jank IS NOT NULL AND prev_jank)
+    AS jank
+  FROM scroll_jank_maybe_null_prev_and_next
+  ORDER BY gesture_scroll_id ASC, ts ASC;
diff --git a/test/trace_processor/index b/test/trace_processor/index
index b4d603f..dec650b 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -202,6 +202,10 @@
 track_event_counters.textproto track_event_counters.sql track_event_counters_counters.out
 track_event_monotonic_trace_clock.textproto track_event_slices.sql track_event_monotonic_trace_clock_slices.out
 
+# Chrome metrics (found in the trace_processor/chrome directory).
+../data/chrome_scroll_without_vsync.pftrace scroll_jank_general_validation.sql scroll_jank_general_validation.out
+../data/chrome_scroll_without_vsync.pftrace scroll_jank.sql scroll_jank.out
+
 # Parsing systrace files
 ../data/systrace.html systrace_html.sql systrace_html.out
 ../data/trailing_empty.systrace sched_smoke.sql sched_smoke_trailing_empty.out
diff --git a/test/trace_processor/scroll_jank.out b/test/trace_processor/scroll_jank.out
new file mode 100644
index 0000000..fb1120f
--- /dev/null
+++ b/test/trace_processor/scroll_jank.out
@@ -0,0 +1,141 @@
+
+"gesture_scroll_id","trace_id","jank","ts","dur"
+2708,2709,0,544958000403,19009426
+2708,2711,0,544966000403,22609426
+2708,2715,0,544983000403,28089426
+2708,2717,0,544991000403,30714426
+2708,2719,0,544999000403,34740426
+2708,2721,0,545007000403,37462426
+2708,2725,1,545024000403,31889426
+2708,2727,0,545032000403,23876426
+2708,2729,0,545040000403,28316426
+2708,2733,0,545056000403,22620426
+2708,2735,0,545065000403,23642426
+2708,2739,0,545081000403,19016426
+2708,2741,0,545089000403,22491426
+2708,2743,0,545098000403,24940426
+2708,2747,0,545114000403,19365426
+2708,2749,0,545122000403,22629426
+2708,2753,0,545139000403,17669426
+2708,2755,0,545147000403,20941426
+2708,2757,0,545155000403,23321426
+2708,2761,0,545172000403,18585426
+2708,2763,0,545180000403,22430426
+2708,2765,0,545188000403,23545426
+2708,2769,0,545204000403,19115426
+2708,2771,0,545213000403,21107426
+2708,2773,0,545221000403,25065426
+2708,2777,0,545237000403,20064426
+2708,2779,0,545246000403,22190426
+2708,2783,0,545262000403,16635426
+2708,2785,0,545270000403,19926426
+2708,2787,0,545278000403,23003426
+2708,2791,0,545295000403,17169426
+2708,2793,0,545303000403,20626426
+2708,2795,0,545311000403,23560426
+2708,2799,0,545328000403,18191426
+2708,2801,0,545336000403,20911426
+2708,2803,0,545344000403,24228426
+2708,2807,0,545361000403,18189426
+2708,2809,0,545369000403,21379426
+2708,2811,0,545377000403,24913426
+2708,2815,0,545394000403,18671426
+2708,2817,0,545402000403,21928426
+2708,2821,0,545418000403,17254426
+2708,2823,0,545426000403,20407426
+2708,2825,0,545435000403,22633426
+2708,2827,0,545443000403,27179426
+2708,2831,0,545459000403,20575426
+2708,2833,0,545468000403,23489426
+2708,2837,0,545484000403,18277426
+2708,2839,0,545492000403,21649426
+2708,2841,0,545500000403,24734426
+2708,2845,0,545517000403,18824426
+2708,2847,0,545525000403,22343426
+2708,2849,0,545533000403,25222426
+2708,2853,0,545550000403,19151426
+2708,2855,0,545558000403,22660426
+2708,2859,0,545574000403,17603426
+2708,2861,0,545583000403,19608426
+2708,2863,0,545591000403,22822426
+2708,2867,0,545607000403,18005426
+2708,2869,0,545615000403,21063426
+2708,2871,0,545624000403,23894426
+2708,2875,0,545640000403,18611426
+2708,2877,0,545648000403,21759426
+2708,2879,0,545656000403,25004426
+2708,2881,0,545671000403,32742426
+2708,2884,0,545681794403,32652426
+2708,2885,0,545691182403,34488426
+2708,2886,0,545702355403,34887426
+2708,2887,0,545713526403,34615426
+2708,2888,0,545724697403,36799426
+2708,2889,0,545735867403,35326426
+2708,2890,0,545747040403,35047426
+2708,2891,0,545758211403,34990426
+2708,2892,0,545769381403,39529426
+2708,2893,0,545780550403,35967426
+2708,2894,1,545791721403,45468426
+2708,2895,1,545802892403,45651426
+2708,2897,0,545825234403,34662426
+2917,2918,0,546027000403,24672426
+2917,2920,0,546035000403,27274426
+2917,2922,0,546044000403,28587426
+2917,2924,1,546052000403,41821426
+2917,2926,1,546060000403,44914426
+2917,2930,0,546076000403,28902426
+2917,2932,0,546085000403,31881426
+2917,2934,0,546093000403,34989426
+2917,2938,1,546109000403,41953426
+2917,2940,0,546118000403,32872426
+2917,2942,0,546126000403,35348426
+2917,2946,0,546142000403,42106426
+2917,2948,1,546150000403,45005426
+2917,2950,0,546159000403,35994426
+2917,2954,0,546175000403,30970426
+2917,2956,1,546183000403,45256426
+2917,2960,1,546200000403,40097426
+2917,2962,0,546208000403,32086426
+2917,2964,0,546216000403,34686426
+2917,2968,0,546233000403,28852426
+2917,2970,0,546241000403,32494426
+2917,2972,0,546249000403,37304426
+2917,2976,0,546265000403,32101426
+2917,2978,0,546273000403,34156426
+2917,2982,0,546290000403,27915426
+2917,2984,0,546298000403,30723426
+2917,2986,0,546307000403,32918426
+2917,2990,0,546323000403,29800426
+2917,2992,0,546331000403,31314426
+2917,2994,0,546339000403,35092426
+2917,2998,0,546356000403,28702426
+2917,3000,0,546364000403,32457426
+2917,3002,0,546372000403,35043426
+2917,3006,0,546389000403,29285426
+2917,3008,0,546397000403,32831426
+2917,3010,0,546405000403,35630426
+2917,3014,0,546422000403,30119426
+2917,3016,0,546430000403,33185426
+2917,3020,0,546446000403,28386426
+2917,3022,0,546455000403,30102426
+2917,3024,0,546463000403,33512426
+2917,3028,0,546479000403,28455426
+2917,3030,0,546488000403,30791426
+2917,3032,0,546496000403,34105426
+2917,3036,0,546512000403,29329426
+2917,3038,0,546520000403,32321426
+2917,3040,0,546529000403,34457426
+2917,3044,0,546545000403,29668426
+2917,3046,0,546553000403,32830426
+2917,3048,0,546561000403,35849426
+2917,3052,0,546578000403,30365426
+2917,3054,0,546586000403,33666426
+2917,3056,0,546594000403,36913426
+2917,3060,0,546611000403,30855426
+2917,3062,0,546619000403,33897426
+2917,3064,0,546627000403,36941426
+2917,3068,0,546644000403,31194426
+2917,3070,0,546652000403,34276426
+2917,3074,0,546676000403,32746426
+2917,3077,0,546701627403,29680426
+2917,3079,0,546718977403,34597426
diff --git a/test/trace_processor/scroll_jank.sql b/test/trace_processor/scroll_jank.sql
new file mode 100644
index 0000000..b7faab9
--- /dev/null
+++ b/test/trace_processor/scroll_jank.sql
@@ -0,0 +1,25 @@
+--
+-- Copyright 2020 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE TABLE suppress_output AS
+  SELECT RUN_METRIC('chrome/scroll_jank.sql');
+
+SELECT
+  gesture_scroll_id,
+  trace_id,
+  jank,
+  ts,
+  dur
+FROM scroll_jank;
diff --git a/test/trace_processor/scroll_jank_general_validation.out b/test/trace_processor/scroll_jank_general_validation.out
new file mode 100644
index 0000000..d4323b2
--- /dev/null
+++ b/test/trace_processor/scroll_jank_general_validation.out
@@ -0,0 +1,3 @@
+
+"total","scroll_dur","non_coalesced_updates","non_coalesced_dur","non_coalesced_janky_updates","non_coalesced_janky_dur","janky_percentage","avg_vsync_interval"
+2,1628470852,139,3974685214,9,382057834,9,16000000.000000
diff --git a/test/trace_processor/scroll_jank_general_validation.sql b/test/trace_processor/scroll_jank_general_validation.sql
new file mode 100644
index 0000000..5fb0529
--- /dev/null
+++ b/test/trace_processor/scroll_jank_general_validation.sql
@@ -0,0 +1,83 @@
+--
+-- Copyright 2020 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE TABLE suppress_output AS
+  SELECT RUN_METRIC('chrome/scroll_jank.sql');
+
+SELECT (
+  -- There are only two valid scrolls (one additional scroll is missing a begin
+  -- and the other is missing an end).
+  SELECT COUNT(*) FROM joined_scroll_begin_and_end
+) AS total,
+(
+  -- Of the two valid scrolls
+  -- gesture_scroll_id: 2708
+  --     starts at: 544958000403
+  --     ends at:   545859896829
+  --     adds dur:     901896426 nanoseconds of scrolling.
+  --
+  -- gesture_scroll_id: 2917
+  --     starts at: 546027000403
+  --     ends at:   546753574829
+  --     adds dur:     726574426 nanoseconds of scrolling.
+  -- This means we should have scroll_dur == 1628470852
+  SELECT SUM(scroll_dur) FROM (
+    SELECT
+      gesture_scroll_id, max(maybe_scroll_end) - begin_ts AS scroll_dur
+    FROM scroll_jank
+    GROUP BY gesture_scroll_id
+  )
+) AS scroll_dur,
+(
+  -- This can be verified by the following simple query to ensure the end result
+  -- in scroll_jank table is sane. The result should be 139.
+  -- SELECT
+  --   COUNT(*)
+  -- FROM slice
+  -- WHERE
+  --    name = "InputLatency::GestureScrollUpdate" AND
+  --    NOT EXTRACT_ARG(arg_set_id, 'chrome_latency_info.is_coalesced') AND
+  --    (
+  --    EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id') = 2708
+  --    OR
+  --    EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id') = 2917
+  --    )
+  SELECT COUNT(*) FROM scroll_jank
+) AS non_coalesced_updates,
+(
+  -- This can be verified by the following simple query as above but replace
+  -- COUNT(*) with SUM(dur). The result should be 3974685214.
+  SELECT SUM(dur) FROM scroll_jank
+) AS non_coalesced_dur,
+(
+  -- This was found by running the previous metric before porting on the
+  -- example trace.
+  SELECT COUNT(*) FROM scroll_jank WHERE jank
+) AS non_coalesced_janky_updates,
+(
+  -- This was found by running the previous metric before porting on the
+  -- example trace, and also manually summing them.
+  SELECT SUM(dur) FROM scroll_jank WHERE jank
+) AS non_coalesced_janky_dur,
+(
+  -- This is floor((non_coalesced_janky_dur/non_coalesced_dur) * 100) in SQLite.
+  SELECT
+    CAST((CAST((SELECT SUM(dur) FROM scroll_jank WHERE jank) AS FLOAT) /
+    CAST((SELECT SUM(dur) FROM scroll_jank) AS FLOAT)) * 100 AS INT)
+) AS janky_percentage,
+(
+  SELECT avg_vsync_interval FROM joined_scroll_begin_and_end
+) as avg_vsync_interval;
+
diff --git a/tools/install-build-deps b/tools/install-build-deps
index ed76fbd..ebc4178 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -145,8 +145,8 @@
     # Example traces for regression tests.
     (
         'buildtools/test_data.zip',
-        'https://storage.googleapis.com/perfetto/test-data-20200702-201444.zip',
-        'f15ebb6a0185c0ce4741f0740d7c4016ebed46e4',
+        'https://storage.googleapis.com/perfetto/test-data-20200707-104443.zip',
+        '06a99400aad71e12e4f5fcfa71ec47163d380f41',
         'all',
     ),
 
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 16fe564..9573cb5 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -212,6 +212,21 @@
   }
 }
 
+.trace-error {
+  * {
+    user-select: text;
+  }
+  grid-area: alerts;
+  background-color: #d40000;
+  color: #f2f2f2;
+  >div {
+    font-family: 'Raleway', sans-serif;
+    font-weight: 400;
+    letter-spacing: 0.25px;
+    padding: 1rem;
+  }
+}
+
 .home-page {
     text-align: center;
     padding-top: 20vh;
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 67a5327..0442ceb 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -23,7 +23,8 @@
 type PublishKinds = 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|
     'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapProfileDetails'|
     'HeapProfileFlamegraph'|'FileDownload'|'Loading'|'Search'|'BufferUsage'|
-    'RecordingLog'|'SearchResult'|'AggregateData'|'CpuProfileDetails';
+    'RecordingLog'|'SearchResult'|'AggregateData'|'CpuProfileDetails'|
+    'TraceErrors';
 
 export interface App {
   state: State;
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index f0d7b4b..7b81136 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -79,6 +79,9 @@
   SelectionControllerArgs
 } from './selection_controller';
 import {
+  TraceErrorController,
+} from './trace_error_controller';
+import {
   TraceBufferStream,
   TraceFileStream,
   TraceHttpStream,
@@ -193,7 +196,8 @@
           engine,
           app: globals,
         }));
-
+        childControllers.push(
+            Child('traceError', TraceErrorController, {engine}));
         return childControllers;
 
       default:
diff --git a/ui/src/controller/trace_error_controller.ts b/ui/src/controller/trace_error_controller.ts
new file mode 100644
index 0000000..fc1ce3f
--- /dev/null
+++ b/ui/src/controller/trace_error_controller.ts
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {Engine} from '../common/engine';
+
+import {Controller} from './controller';
+import {globals} from './globals';
+
+export interface TraceErrorControllerArgs {
+  engine: Engine;
+}
+
+export class TraceErrorController extends Controller<'main'> {
+  private hasRun = false;
+  constructor(private args: TraceErrorControllerArgs) {
+    super('main');
+  }
+
+  run() {
+    if (this.hasRun) {
+      return;
+    }
+    this.hasRun = true;
+    this.args.engine
+        .query(`SELECT name FROM stats WHERE severity = 'error' and value > 0`)
+        .then(result => {
+          globals.publish('TraceErrors', result.columns[0].stringValues!);
+        });
+  }
+}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 5b859ad..32546ff 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -113,6 +113,7 @@
   private _numQueriesQueued = 0;
   private _bufferUsage?: number = undefined;
   private _recordingLog?: string = undefined;
+  private _traceErrors: string[] = [];
 
   private _currentSearchResults: CurrentSearchResults = {
     sliceIds: new Float64Array(0),
@@ -222,6 +223,14 @@
     this._heapProfileDetails = assertExists(click);
   }
 
+  get traceErrors(): string[] {
+    return this._traceErrors;
+  }
+
+  set traceErrors(arg: string[]) {
+    this._traceErrors = arg;
+  }
+
   get cpuProfileDetails() {
     return assertExists(this._cpuProfileDetails);
   }
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 6c5b8fd..2cebf9c 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -182,6 +182,11 @@
     this.redraw();
   }
 
+  publishTraceErrors(arg: string[]) {
+    globals.traceErrors = arg;
+    this.redraw();
+  }
+
   publishAggregateData(args: {data: AggregateData, kind: string}) {
     globals.setAggregateData(args.kind, args.data);
     this.redraw();
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
index 3f3e29f..c0a2704 100644
--- a/ui/src/frontend/pages.ts
+++ b/ui/src/frontend/pages.ts
@@ -39,6 +39,20 @@
   }
 }
 
+class TraceErrors implements m.ClassComponent {
+  view() {
+    if (globals.traceErrors.length === 0) {
+      return m('.trace-error');
+    }
+    return m(
+        '.trace-error',
+        m('div',
+          'Errors occured importing trace: ',
+          m('br'),
+          m('ul', globals.traceErrors.map(error => m('li', error)))));
+  }
+}
+
 /**
  * Wrap component with common UI elements (nav bar etc).
  */
@@ -49,6 +63,7 @@
         m(Sidebar),
         m(Topbar),
         m(Alerts),
+        m(TraceErrors),
         m(component),
       ];
       if (globals.frontendLocalState.perfDebug) {