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) {