Merge "[Tracing] Add proto configuration for the ETW proto." into main
diff --git a/Android.bp b/Android.bp
index c2a5340..3b4f823 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14690,7 +14690,7 @@
host: {
static_libs: [
"libprotobuf-cpp-full",
- "libsqlite",
+ "libsqlite_static_noicu",
"libz",
"sqlite_ext_percentile",
],
@@ -14843,7 +14843,7 @@
":perfetto_src_traceconv_utils",
],
static_libs: [
- "libsqlite",
+ "libsqlite_static_noicu",
"libz",
"perfetto_src_trace_processor_demangle",
"sqlite_ext_percentile",
diff --git a/BUILD b/BUILD
index 80fc73e..2d28e55 100644
--- a/BUILD
+++ b/BUILD
@@ -860,6 +860,7 @@
"include/perfetto/tracing/console_interceptor.h",
"include/perfetto/tracing/data_source.h",
"include/perfetto/tracing/debug_annotation.h",
+ "include/perfetto/tracing/default_socket.h",
"include/perfetto/tracing/event_context.h",
"include/perfetto/tracing/interceptor.h",
"include/perfetto/tracing/internal/basic_types.h",
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index cd41e86..a0e6bb9 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -133,12 +133,10 @@
// http://msdn.microsoft.com/en-us/library/b0084kay.aspx
// http://www.agner.org/optimize/calling_conventions.pdf
// or with gcc, run: "echo | gcc -E -dM -"
-#if defined(_M_X64) || defined(__x86_64__)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_X86_64() 1
-#elif defined(__aarch64__) || defined(_M_ARM64)
+#if defined(__aarch64__) || defined(_M_ARM64)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 1
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_X86_64() 0
+#else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 0
#endif
// perfetto_build_flags.h contains the tweakable build flags defined via GN.
diff --git a/include/perfetto/base/platform_handle.h b/include/perfetto/base/platform_handle.h
index 879fa85..88f6d59 100644
--- a/include/perfetto/base/platform_handle.h
+++ b/include/perfetto/base/platform_handle.h
@@ -17,6 +17,8 @@
#ifndef INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
#define INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
+#include <stdint.h>
+
#include "perfetto/base/build_config.h"
namespace perfetto {
@@ -30,10 +32,17 @@
// in Windows.h take an int, not a HANDLE.
// 2. Handles returned by old-school WINAPI like CreateFile, CreateEvent etc.
// These are proper HANDLE(s). PlatformHandle should be used here.
+//
+// On Windows, sockets have their own type (SOCKET) which is neither a HANDLE
+// nor an int. However Windows SOCKET(s) can have an event HANDLE attached
+// to them (which in Perfetto is a PlatformHandle), and that can be used in
+// WaitForMultipleObjects, hence in base::TaskRunner.AddFileDescriptorWatch().
+// On POSIX OSes, a SocketHandle is really just an int (a file descriptor).
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-// Windows.h typedefs HANDLE to void*. We use void* here to avoid leaking
-// Windows.h through our headers.
+// Windows.h typedefs HANDLE to void*, and SOCKET to uintptr_t. We use their
+// types to avoid leaking Windows.h through our headers.
using PlatformHandle = void*;
+using SocketHandle = uintptr_t;
// On Windows both nullptr and 0xffff... (INVALID_HANDLE_VALUE) are invalid.
struct PlatformHandleChecker {
@@ -43,6 +52,7 @@
};
#else
using PlatformHandle = int;
+using SocketHandle = int;
struct PlatformHandleChecker {
static inline bool IsValid(PlatformHandle h) { return h >= 0; }
};
diff --git a/include/perfetto/ext/base/unix_socket.h b/include/perfetto/ext/base/unix_socket.h
index 74ce2d9..e5cc536 100644
--- a/include/perfetto/ext/base/unix_socket.h
+++ b/include/perfetto/ext/base/unix_socket.h
@@ -27,6 +27,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/export.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/platform_handle.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/weak_ptr.h"
@@ -36,23 +37,13 @@
namespace perfetto {
namespace base {
-// Define the SocketHandle and ScopedSocketHandle types.
-// On POSIX OSes, a SocketHandle is really just an int (a file descriptor).
-// On Windows, sockets are have their own type (SOCKET) which is neither a
-// HANDLE nor an int. However Windows SOCKET(s) can have a event HANDLE attached
-// to them (which in Perfetto is a PlatformHandle), and that can be used in
-// WaitForMultipleObjects, hence in base::TaskRunner.AddFileDescriptorWatch().
+// Define the ScopedSocketHandle type.
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-// uintptr_t really reads as SOCKET here (Windows headers typedef to that).
-// As usual we don't just use SOCKET here to avoid leaking Windows.h includes
-// in our headers.
-using SocketHandle = uintptr_t; // SOCKET
int CloseSocket(SocketHandle); // A wrapper around ::closesocket().
using ScopedSocketHandle =
ScopedResource<SocketHandle, CloseSocket, static_cast<SocketHandle>(-1)>;
#else
-using SocketHandle = int;
using ScopedSocketHandle = ScopedFile;
#endif
diff --git a/include/perfetto/ext/tracing/ipc/default_socket.h b/include/perfetto/ext/tracing/ipc/default_socket.h
index 86d7b78..b7d667b 100644
--- a/include/perfetto/ext/tracing/ipc/default_socket.h
+++ b/include/perfetto/ext/tracing/ipc/default_socket.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -17,25 +17,9 @@
#ifndef INCLUDE_PERFETTO_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
#define INCLUDE_PERFETTO_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
-#include <string>
-#include <vector>
-
-#include "perfetto/base/export.h"
-
-namespace perfetto {
-
-PERFETTO_EXPORT_COMPONENT const char* GetConsumerSocket();
-// This function is used for tokenize the |producer_socket_names| string into
-// multiple producer socket names.
-PERFETTO_EXPORT_COMPONENT std::vector<std::string> TokenizeProducerSockets(
- const char* producer_socket_names);
-PERFETTO_EXPORT_COMPONENT const char* GetProducerSocket();
-
-// Optionally returns the relay socket name (nullable). The relay socket is used
-// for forwarding the IPC messages between the local producers and the remote
-// tracing service.
-PERFETTO_EXPORT_COMPONENT const char* GetRelaySocket();
-
-} // namespace perfetto
+// TODO(khokhlov): Migrate usages of "perfetto/ext/tracing/ipc/default_socket.h"
+// in Chromium to include "perfetto/tracing/internal/default_socket.h" instead,
+// then delete this file.
+#include "perfetto/tracing/default_socket.h"
#endif // INCLUDE_PERFETTO_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
diff --git a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
index 1af399b..33e19b4 100644
--- a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
+++ b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
@@ -25,6 +25,7 @@
#include "perfetto/ext/tracing/core/shared_memory.h"
#include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
+#include "perfetto/tracing/tracing_backend.h"
namespace perfetto {
@@ -89,7 +90,8 @@
size_t shared_memory_size_hint_bytes = 0,
size_t shared_memory_page_size_hint_bytes = 0,
std::unique_ptr<SharedMemory> shm = nullptr,
- std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr,
+ CreateSocketAsync create_socket_async = {});
protected:
ProducerIPCClient() = delete;
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 159e155..3e0e9f2 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -33,6 +33,7 @@
"console_interceptor.h",
"data_source.h",
"debug_annotation.h",
+ "default_socket.h",
"event_context.h",
"interceptor.h",
"internal/basic_types.h",
diff --git a/include/perfetto/tracing/default_socket.h b/include/perfetto/tracing/default_socket.h
new file mode 100644
index 0000000..1989724
--- /dev/null
+++ b/include/perfetto/tracing/default_socket.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 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 INCLUDE_PERFETTO_TRACING_DEFAULT_SOCKET_H_
+#define INCLUDE_PERFETTO_TRACING_DEFAULT_SOCKET_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/base/export.h"
+
+namespace perfetto {
+
+PERFETTO_EXPORT_COMPONENT const char* GetConsumerSocket();
+// This function is used for tokenize the |producer_socket_names| string into
+// multiple producer socket names.
+PERFETTO_EXPORT_COMPONENT std::vector<std::string> TokenizeProducerSockets(
+ const char* producer_socket_names);
+PERFETTO_EXPORT_COMPONENT const char* GetProducerSocket();
+
+// Optionally returns the relay socket name (nullable). The relay socket is used
+// for forwarding the IPC messages between the local producers and the remote
+// tracing service.
+PERFETTO_EXPORT_COMPONENT const char* GetRelaySocket();
+
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_TRACING_DEFAULT_SOCKET_H_
diff --git a/include/perfetto/tracing/internal/system_tracing_backend.h b/include/perfetto/tracing/internal/system_tracing_backend.h
index c1ea664..fdcdc99 100644
--- a/include/perfetto/tracing/internal/system_tracing_backend.h
+++ b/include/perfetto/tracing/internal/system_tracing_backend.h
@@ -18,6 +18,7 @@
#define INCLUDE_PERFETTO_TRACING_INTERNAL_SYSTEM_TRACING_BACKEND_H_
#include "perfetto/base/export.h"
+#include "perfetto/tracing/default_socket.h"
#include "perfetto/tracing/tracing_backend.h"
namespace perfetto {
diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h
index 44503ba..824f492 100644
--- a/include/perfetto/tracing/tracing.h
+++ b/include/perfetto/tracing/tracing.h
@@ -152,6 +152,16 @@
// event tracks for the same thread.
bool disallow_merging_with_system_tracks = false;
+ // If set, this function will be called by the producer client to create a
+ // socket for connection to the system service. The function takes two
+ // arguments: a name of the socket, and a callback that takes an open file
+ // descriptor. It should create a socket with the given name, connect to it,
+ // and return the corresponding descriptor via the callback.
+ // This is intended for the use-case where a process being traced is run
+ // inside a sandbox and can't create sockets directly.
+ // Not yet supported for consumer connections currently.
+ CreateSocketAsync create_socket_async;
+
protected:
friend class Tracing;
friend class internal::TracingMuxerImpl;
diff --git a/include/perfetto/tracing/tracing_backend.h b/include/perfetto/tracing/tracing_backend.h
index 965d726..b915776 100644
--- a/include/perfetto/tracing/tracing_backend.h
+++ b/include/perfetto/tracing/tracing_backend.h
@@ -17,10 +17,12 @@
#ifndef INCLUDE_PERFETTO_TRACING_TRACING_BACKEND_H_
#define INCLUDE_PERFETTO_TRACING_TRACING_BACKEND_H_
+#include <functional>
#include <memory>
#include <string>
#include "perfetto/base/export.h"
+#include "perfetto/base/platform_handle.h"
// The embedder can (but doesn't have to) extend the TracingBackend class and
// pass as an argument to Tracing::Initialize(kCustomBackend) to override the
@@ -43,6 +45,9 @@
class Producer;
class ProducerEndpoint;
+using CreateSocketCallback = std::function<void(base::SocketHandle)>;
+using CreateSocketAsync = std::function<void(CreateSocketCallback)>;
+
// Responsible for connecting to the producer.
class PERFETTO_EXPORT_COMPONENT TracingProducerBackend {
public:
@@ -74,6 +79,10 @@
// it to the service when connecting.
// It's used in startup tracing.
bool use_producer_provided_smb = false;
+
+ // If set, the producer will call this function to create and connect to a
+ // socket. See the corresponding field in TracingInitArgs for more info.
+ CreateSocketAsync create_socket_async;
};
virtual std::unique_ptr<ProducerEndpoint> ConnectProducer(
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 90cde7d..a68feef 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -1460,7 +1460,7 @@
message StartUp {
// This enum must be kept up to date with LaunchCauseMetrics.LaunchCause.
- enum LauchCauseType {
+ enum LaunchCauseType {
OTHER = 0;
CUSTOM_TAB = 1;
TWA = 2;
@@ -1483,7 +1483,8 @@
}
optional int64 activity_id = 1;
- optional LauchCauseType launch_cause = 2;
+ // deprecated field 2.
+ optional LaunchCauseType launch_cause = 3;
}
message WebContentInteraction {
diff --git a/src/tracing/internal/system_tracing_backend.cc b/src/tracing/internal/system_tracing_backend.cc
index 9488495..1b83579 100644
--- a/src/tracing/internal/system_tracing_backend.cc
+++ b/src/tracing/internal/system_tracing_backend.cc
@@ -65,11 +65,12 @@
shm.get(), shmem_page_size_hint, SharedMemoryABI::ShmemMode::kDefault);
}
+ ipc::Client::ConnArgs conn_args(GetProducerSocket(), true);
auto endpoint = ProducerIPCClient::Connect(
- GetProducerSocket(), args.producer, args.producer_name, args.task_runner,
+ std::move(conn_args), args.producer, args.producer_name, args.task_runner,
TracingService::ProducerSMBScrapingMode::kEnabled, shmem_size_hint,
shmem_page_size_hint, std::move(shm), std::move(arbiter),
- ProducerIPCClient::ConnectionFlags::kRetryIfUnreachable);
+ args.create_socket_async);
PERFETTO_CHECK(endpoint);
return endpoint;
}
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index c0d88ac..80e451f 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -970,6 +970,7 @@
rb.producer_conn_args.shmem_size_hint_bytes = args.shmem_size_hint_kb * 1024;
rb.producer_conn_args.shmem_page_size_hint_bytes =
args.shmem_page_size_hint_kb * 1024;
+ rb.producer_conn_args.create_socket_async = args.create_socket_async;
rb.producer->Initialize(rb.backend->ConnectProducer(rb.producer_conn_args));
}
diff --git a/src/tracing/ipc/BUILD.gn b/src/tracing/ipc/BUILD.gn
index dfd0eda..1423223 100644
--- a/src/tracing/ipc/BUILD.gn
+++ b/src/tracing/ipc/BUILD.gn
@@ -50,6 +50,7 @@
"../../../gn:default_deps",
"../../../include/perfetto/ext/ipc",
"../../../include/perfetto/ext/tracing/core",
+ "../../../include/perfetto/tracing",
"../../base",
]
}
diff --git a/src/tracing/ipc/default_socket.cc b/src/tracing/ipc/default_socket.cc
index f053756..81ff6df 100644
--- a/src/tracing/ipc/default_socket.cc
+++ b/src/tracing/ipc/default_socket.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "perfetto/ext/tracing/ipc/default_socket.h"
+#include "perfetto/tracing/default_socket.h"
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
index ea87953..3959b0a 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
@@ -66,7 +66,7 @@
ProducerIPCClient::ConnectionFlags::kRetryIfUnreachable},
producer, producer_name, task_runner, smb_scraping_mode,
shared_memory_size_hint_bytes, shared_memory_page_size_hint_bytes,
- std::move(shm), std::move(shm_arbiter)));
+ std::move(shm), std::move(shm_arbiter), CreateSocketAsync()));
}
// static. (Declared in include/tracing/ipc/producer_ipc_client.h).
@@ -79,13 +79,14 @@
size_t shared_memory_size_hint_bytes,
size_t shared_memory_page_size_hint_bytes,
std::unique_ptr<SharedMemory> shm,
- std::unique_ptr<SharedMemoryArbiter> shm_arbiter) {
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+ CreateSocketAsync create_socket_async) {
return std::unique_ptr<TracingService::ProducerEndpoint>(
- new ProducerIPCClientImpl(std::move(conn_args), producer, producer_name,
- task_runner, smb_scraping_mode,
- shared_memory_size_hint_bytes,
- shared_memory_page_size_hint_bytes,
- std::move(shm), std::move(shm_arbiter)));
+ new ProducerIPCClientImpl(
+ std::move(conn_args), producer, producer_name, task_runner,
+ smb_scraping_mode, shared_memory_size_hint_bytes,
+ shared_memory_page_size_hint_bytes, std::move(shm),
+ std::move(shm_arbiter), create_socket_async));
}
ProducerIPCClientImpl::ProducerIPCClientImpl(
@@ -97,13 +98,12 @@
size_t shared_memory_size_hint_bytes,
size_t shared_memory_page_size_hint_bytes,
std::unique_ptr<SharedMemory> shm,
- std::unique_ptr<SharedMemoryArbiter> shm_arbiter)
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+ CreateSocketAsync create_socket_async)
: producer_(producer),
task_runner_(task_runner),
receive_shmem_fd_cb_fuchsia_(
std::move(conn_args.receive_shmem_fd_cb_fuchsia)),
- ipc_channel_(
- ipc::Client::CreateInstance(std::move(conn_args), task_runner)),
producer_port_(
new protos::gen::ProducerPortProxy(this /* event_listener */)),
shared_memory_(std::move(shm)),
@@ -124,7 +124,28 @@
shared_buffer_page_size_kb_ = shared_memory_page_size_hint_bytes_ / 1024;
}
- ipc_channel_->BindService(producer_port_->GetWeakPtr());
+ if (create_socket_async) {
+ PERFETTO_DCHECK(conn_args.socket_name);
+ auto weak_this = weak_factory_.GetWeakPtr();
+ create_socket_async(
+ [weak_this, task_runner = task_runner_](base::SocketHandle fd) {
+ task_runner->PostTask([weak_this, fd] {
+ base::ScopedSocketHandle handle(fd);
+ if (!weak_this) {
+ return;
+ }
+ ipc::Client::ConnArgs args(std::move(handle));
+ weak_this->ipc_channel_ = ipc::Client::CreateInstance(
+ std::move(args), weak_this->task_runner_);
+ weak_this->ipc_channel_->BindService(
+ weak_this->producer_port_->GetWeakPtr());
+ });
+ });
+ } else {
+ ipc_channel_ =
+ ipc::Client::CreateInstance(std::move(conn_args), task_runner);
+ ipc_channel_->BindService(producer_port_->GetWeakPtr());
+ }
PERFETTO_DCHECK_THREAD(thread_checker_);
}
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.h b/src/tracing/ipc/producer/producer_ipc_client_impl.h
index 664b698..f50dcd7 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.h
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.h
@@ -46,6 +46,8 @@
// IPC channel to the remote Service. This class is the glue layer between the
// generic Service interface exposed to the clients of the library and the
// actual IPC transport.
+// If create_socket_async is set, it will be called to create and connect to a
+// socket to the service. If unset, the producer will create and connect itself.
class ProducerIPCClientImpl : public TracingService::ProducerEndpoint,
public ipc::ServiceProxy::EventListener {
public:
@@ -57,7 +59,8 @@
size_t shared_memory_size_hint_bytes,
size_t shared_memory_page_size_hint_bytes,
std::unique_ptr<SharedMemory> shm,
- std::unique_ptr<SharedMemoryArbiter> shm_arbiter);
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+ CreateSocketAsync create_socket_async);
~ProducerIPCClientImpl() override;
// TracingService::ProducerEndpoint implementation.
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 66b2cff..0040714 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -16,6 +16,9 @@
#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
#include <chrono>
#include <condition_variable>
#include <fstream>
@@ -6303,6 +6306,7 @@
// Create a new trace session.
auto* tracing_session = NewTraceWithCategories({"test"});
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
// Emit another event after starting.
@@ -6323,6 +6327,7 @@
// Create a new trace session.
auto* tracing_session = NewTraceWithCategories({"test"});
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
// Emit another event after starting.
@@ -6340,6 +6345,7 @@
TRACE_EVENT_BEGIN("test", "Event");
auto* tracing_session = NewTraceWithCategories({"test"});
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
TRACE_EVENT_END("test");
@@ -6358,7 +6364,9 @@
ds_cfg->set_name("CustomDataSource");
SetupStartupTracing(cfg);
TRACE_EVENT_BEGIN("test", "TrackEvent.Startup");
+
auto* tracing_session = NewTraceWithCategories({"test"}, {}, cfg);
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
TRACE_EVENT_BEGIN("test", "TrackEvent.Main");
@@ -6397,7 +6405,9 @@
auto packet = ctx.NewTracePacket();
packet->set_for_testing()->set_str("CustomDataSource.Startup");
});
+
auto* tracing_session = NewTraceWithCategories({"test"}, {}, cfg);
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
TRACE_EVENT_BEGIN("test", "TrackEvent.Main");
@@ -6470,6 +6480,7 @@
TRACE_EVENT_BEGIN("test", "StartupEvent2");
auto* tracing_session = NewTraceWithCategories({"test"});
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
TRACE_EVENT_BEGIN("test", "MainEvent");
@@ -6555,7 +6566,9 @@
// during startup tracing session.
TEST_P(PerfettoStartupTracingApiTest, NoEventInStartupTracing) {
SetupStartupTracing();
+
auto* tracing_session = NewTraceWithCategories({"test"});
+ ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
tracing_session->get()->StartBlocking();
// Emit an event now that the session was fully started. This should go
// strait to the SMB.
@@ -6797,6 +6810,198 @@
perfetto::Tracing::ResetForTesting();
}
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+namespace {
+
+int ConnectUnixSocket() {
+ std::string socket_name = perfetto::GetProducerSocket();
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ struct sockaddr_un saddr;
+ memset(&saddr, 0, sizeof(saddr));
+ memcpy(saddr.sun_path, socket_name.data(), socket_name.size());
+ saddr.sun_family = AF_UNIX;
+ auto size = static_cast<socklen_t>(__builtin_offsetof(sockaddr_un, sun_path) +
+ socket_name.size() + 1);
+ connect(fd, reinterpret_cast<const struct sockaddr*>(&saddr), size);
+ return fd;
+}
+
+} // namespace
+
+TEST(PerfettoApiInitTest, AsyncSocket) {
+ auto system_service = perfetto::test::SystemService::Start();
+ // If the system backend isn't supported, skip
+ if (!system_service.valid()) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(perfetto::Tracing::IsInitialized());
+
+ perfetto::CreateSocketCallback socket_callback;
+ WaitableTestEvent create_socket_called;
+
+ TracingInitArgs args;
+ args.backends = perfetto::kSystemBackend;
+ args.tracing_policy = g_test_tracing_policy;
+ args.create_socket_async = [&socket_callback, &create_socket_called](
+ perfetto::CreateSocketCallback cb) {
+ socket_callback = cb;
+ create_socket_called.Notify();
+ };
+
+ perfetto::Tracing::Initialize(args);
+ create_socket_called.Wait();
+
+ int fd = ConnectUnixSocket();
+ socket_callback(fd);
+
+ perfetto::test::SyncProducers();
+ EXPECT_TRUE(perfetto::Tracing::NewTrace(perfetto::kSystemBackend)
+ ->QueryServiceStateBlocking()
+ .success);
+
+ perfetto::Tracing::ResetForTesting();
+}
+
+TEST(PerfettoApiInitTest, AsyncSocketDisconnect) {
+ auto system_service = perfetto::test::SystemService::Start();
+ // If the system backend isn't supported, skip
+ if (!system_service.valid()) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(perfetto::Tracing::IsInitialized());
+
+ perfetto::CreateSocketCallback socket_callback;
+ testing::MockFunction<perfetto::CreateSocketAsync> mock_create_socket;
+ WaitableTestEvent create_socket_called1, create_socket_called2;
+
+ TracingInitArgs args;
+ args.backends = perfetto::kSystemBackend;
+ args.tracing_policy = g_test_tracing_policy;
+ args.create_socket_async = mock_create_socket.AsStdFunction();
+
+ EXPECT_CALL(mock_create_socket, Call)
+ .WillOnce(Invoke([&socket_callback, &create_socket_called1](
+ perfetto::CreateSocketCallback cb) {
+ socket_callback = cb;
+ create_socket_called1.Notify();
+ }))
+ .WillOnce(Invoke([&socket_callback, &create_socket_called2](
+ perfetto::CreateSocketCallback cb) {
+ socket_callback = cb;
+ create_socket_called2.Notify();
+ }));
+
+ perfetto::Tracing::Initialize(args);
+ create_socket_called1.Wait();
+ int fd = ConnectUnixSocket();
+ socket_callback(fd);
+
+ perfetto::test::SyncProducers();
+ EXPECT_TRUE(perfetto::Tracing::NewTrace(perfetto::kSystemBackend)
+ ->QueryServiceStateBlocking()
+ .success);
+
+ // Restart the system service. This will cause the producer and consumer to
+ // disconnect and reconnect. The create_socket_async function should be called
+ // for the second time.
+ system_service.Restart();
+ create_socket_called2.Wait();
+ fd = ConnectUnixSocket();
+ socket_callback(fd);
+
+ perfetto::test::SyncProducers();
+ EXPECT_TRUE(perfetto::Tracing::NewTrace(perfetto::kSystemBackend)
+ ->QueryServiceStateBlocking()
+ .success);
+
+ perfetto::Tracing::ResetForTesting();
+}
+
+TEST(PerfettoApiInitTest, AsyncSocketStartupTracing) {
+ auto system_service = perfetto::test::SystemService::Start();
+ // If the system backend isn't supported, skip
+ if (!system_service.valid()) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(perfetto::Tracing::IsInitialized());
+
+ perfetto::CreateSocketCallback socket_callback;
+ WaitableTestEvent create_socket_called;
+
+ TracingInitArgs args;
+ args.backends = perfetto::kSystemBackend;
+ args.tracing_policy = g_test_tracing_policy;
+ args.create_socket_async = [&socket_callback, &create_socket_called](
+ perfetto::CreateSocketCallback cb) {
+ socket_callback = cb;
+ create_socket_called.Notify();
+ };
+
+ perfetto::Tracing::Initialize(args);
+ perfetto::TrackEvent::Register();
+
+ perfetto::TraceConfig cfg;
+ cfg.set_duration_ms(500);
+ cfg.add_buffers()->set_size_kb(1024);
+ auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ ds_cfg->set_name("track_event");
+
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_disabled_categories("*");
+ te_cfg.add_enabled_categories("test");
+ ds_cfg->set_track_event_config_raw(te_cfg.SerializeAsString());
+
+ perfetto::Tracing::SetupStartupTracingOpts opts;
+ opts.backend = perfetto::kSystemBackend;
+ auto startup_session =
+ perfetto::Tracing::SetupStartupTracingBlocking(cfg, std::move(opts));
+
+ // Emit a significant number of events to write >1 chunk of data.
+ constexpr size_t kNumEvents = 1000;
+ for (size_t i = 0; i < kNumEvents; i++) {
+ TRACE_EVENT_INSTANT("test", "StartupEvent");
+ }
+
+ // Now proceed with the connection to the service and wait until it completes.
+ int fd = ConnectUnixSocket();
+ socket_callback(fd);
+ perfetto::test::SyncProducers();
+
+ auto session = perfetto::Tracing::NewTrace(perfetto::kSystemBackend);
+ session->Setup(cfg);
+ session->StartBlocking();
+
+ // Write even more events, now with connection established.
+ for (size_t i = 0; i < kNumEvents; i++) {
+ TRACE_EVENT_INSTANT("test", "TraceEvent");
+ }
+
+ perfetto::TrackEvent::Flush();
+ session->StopBlocking();
+
+ auto raw_trace = session->ReadTraceBlocking();
+ perfetto::protos::gen::Trace parsed_trace;
+ EXPECT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+ size_t n_track_events = 0;
+ for (const auto& packet : parsed_trace.packet()) {
+ if (packet.has_track_event()) {
+ ++n_track_events;
+ }
+ }
+
+ // Events from both startup and service-initiated sessions should be retained.
+ EXPECT_EQ(n_track_events, kNumEvents * 2);
+
+ startup_session.reset();
+ session.reset();
+ perfetto::Tracing::ResetForTesting();
+}
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
struct BackendTypeAsString {
std::string operator()(
const ::testing::TestParamInfo<perfetto::BackendType>& info) const {
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 11f6d16..6d00144 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -335,7 +335,7 @@
def enable_sqlite(module):
if module.type == 'cc_binary_host':
- module.static_libs.add('libsqlite')
+ module.static_libs.add('libsqlite_static_noicu')
module.static_libs.add('sqlite_ext_percentile')
elif module.host_supported:
# Copy what the sqlite3 command line tool does.
@@ -344,7 +344,7 @@
module.android.shared_libs.add('liblog')
module.android.shared_libs.add('libutils')
module.android.static_libs.add('sqlite_ext_percentile')
- module.host.static_libs.add('libsqlite')
+ module.host.static_libs.add('libsqlite_static_noicu')
module.host.static_libs.add('sqlite_ext_percentile')
else:
module.shared_libs.add('libsqlite')
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 91a0c0e..8ee9b76 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
},
{
"name": "canary",
- "rev": "c69b33b9abcc20fad9ad5f39de883216e4b43130"
+ "rev": "9ca89e30931314dec4af1131d516e07e39d8657d"
},
{
"name": "autopush",
diff --git a/ui/src/common/color.ts b/ui/src/common/color.ts
index 30b4dd9..127bfbb 100644
--- a/ui/src/common/color.ts
+++ b/ui/src/common/color.ts
@@ -48,7 +48,10 @@
// creation time, so they may be used in the hot path (render loop).
export interface Color {
readonly cssString: string;
- readonly isLight: boolean;
+
+ // The perceived brightness of the color using a weighted average of the
+ // r, g and b channels based on human perception.
+ readonly perceivedBrightness: number;
// Bring up the lightness by |percent| percent.
lighten(percent: number, max?: number): Color;
@@ -78,11 +81,14 @@
// Saturation: 0-100
// Lightness: 0-100
// Alpha: 0-1
- constructor(hsl: ColorTuple|HSL, alpha?: number) {
- if (Array.isArray(hsl)) {
- this.hsl = hsl;
+ constructor(init: ColorTuple|HSL|string, alpha?: number) {
+ if (Array.isArray(init)) {
+ this.hsl = init;
+ } else if (typeof init === 'string') {
+ const rgb = hexToRgb(init);
+ this.hsl = rgbToHsl(rgb);
} else {
- this.hsl = [hsl.h, hsl.s, hsl.l];
+ this.hsl = [init.h, init.s, init.l];
}
this.alpha = alpha;
}
@@ -128,19 +134,19 @@
// Describes a color defined in standard HSL color space.
export class HSLColor extends HSLColorBase<HSLColor> implements Color {
readonly cssString: string;
- readonly isLight: boolean;
+ readonly perceivedBrightness: number;
// Values are in the range:
// Hue: 0-360
// Saturation: 0-100
// Lightness: 0-100
// Alpha: 0-1
- constructor(hsl: ColorTuple|HSL, alpha?: number) {
+ constructor(hsl: ColorTuple|HSL|string, alpha?: number) {
super(hsl, alpha);
- const [r, g, b] = hslToRGB(...this.hsl);
+ const [r, g, b] = hslToRgb(...this.hsl);
- this.isLight = isLight(r, g, b);
+ this.perceivedBrightness = perceivedBrightness(r, g, b);
if (this.alpha === undefined) {
this.cssString = `rgb(${r} ${g} ${b})`;
@@ -149,7 +155,7 @@
}
}
- create(values: ColorTuple|HSL, alpha?: number|undefined): HSLuvColor {
+ create(values: ColorTuple|HSL, alpha?: number|undefined): HSLColor {
return new HSLColor(values, alpha);
}
}
@@ -158,7 +164,7 @@
// See: https://www.hsluv.org/
export class HSLuvColor extends HSLColorBase<HSLuvColor> implements Color {
readonly cssString: string;
- readonly isLight: boolean;
+ readonly perceivedBrightness: number;
constructor(hsl: ColorTuple|HSL, alpha?: number) {
super(hsl, alpha);
@@ -168,7 +174,7 @@
const g = Math.floor(rgb[1] * 255);
const b = Math.floor(rgb[2] * 255);
- this.isLight = isLight(r, g, b);
+ this.perceivedBrightness = perceivedBrightness(r, g, b);
if (this.alpha === undefined) {
this.cssString = `rgb(${r} ${g} ${b})`;
@@ -186,7 +192,7 @@
// Saturation: 0-100
// Lightness: 0-100
// RGB: 0-255
-export function hslToRGB(h: number, s: number, l: number): ColorTuple {
+export function hslToRgb(h: number, s: number, l: number): ColorTuple {
h = h;
s = s / SATURATION_MAX;
l = l / LIGHTNESS_MAX;
@@ -219,12 +225,63 @@
return [r, g, b];
}
-// Get whether a color should be considered "light" based on its perceived
-// brightness.
-function isLight(r: number, g: number, b: number): boolean {
+export function hexToRgb(hex: string): ColorTuple {
+ // Convert hex to RGB first
+ let r: number = 0;
+ let g: number = 0;
+ let b: number = 0;
+
+ if (hex.length === 4) {
+ r = parseInt(hex[1] + hex[1], 16);
+ g = parseInt(hex[2] + hex[2], 16);
+ b = parseInt(hex[3] + hex[3], 16);
+ } else if (hex.length === 7) {
+ r = parseInt(hex.substring(1, 3), 16);
+ g = parseInt(hex.substring(3, 5), 16);
+ b = parseInt(hex.substring(5, 7), 16);
+ }
+
+ return [r, g, b];
+}
+
+export function rgbToHsl(rgb: ColorTuple): ColorTuple {
+ let [r, g, b] = rgb;
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ let h: number = (max + min) / 2;
+ let s: number = (max + min) / 2;
+ const l: number = (max + min) / 2;
+
+ if (max === min) {
+ h = s = 0; // achromatic
+ } else {
+ const d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+
+ return [h * 360, s * 100, l * 100];
+}
+
+// Return the perceived brightness of a color using a weighted average of the
+// r, g and b channels based on human perception.
+function perceivedBrightness(r: number, g: number, b: number): number {
// YIQ calculation from https://24ways.org/2010/calculating-color-contrast
- const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
- return (yiq >= 128);
+ return ((r * 299) + (g * 587) + (b * 114)) / 1000;
}
// Comparison function used for sorting colors.
diff --git a/ui/src/common/color_unittest.ts b/ui/src/common/color_unittest.ts
index d2793f4..4973308 100644
--- a/ui/src/common/color_unittest.ts
+++ b/ui/src/common/color_unittest.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {HSLColor, hslToRGB, HSLuvColor} from './color';
+import {HSLColor, hslToRgb, HSLuvColor} from './color';
describe('HSLColor', () => {
const col = new HSLColor({h: 123, s: 66, l: 45});
@@ -45,13 +45,17 @@
expect(col.setAlpha(undefined).alpha).toEqual(undefined);
});
- test('isLight', () => {
+ test('perceivedBrightness', () => {
// Test a few obviously light/dark colours.
- expect(new HSLColor({h: 0, s: 0, l: 0}).isLight).toBeFalsy();
- expect(new HSLColor({h: 0, s: 0, l: 100}).isLight).toBeTruthy();
+ expect(new HSLColor({h: 0, s: 0, l: 0}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLColor({h: 0, s: 0, l: 100}).perceivedBrightness)
+ .toBeGreaterThan(128);
- expect(new HSLColor({h: 0, s: 0, l: 40}).isLight).toBeFalsy();
- expect(new HSLColor({h: 0, s: 0, l: 60}).isLight).toBeTruthy();
+ expect(new HSLColor({h: 0, s: 0, l: 40}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLColor({h: 0, s: 0, l: 60}).perceivedBrightness)
+ .toBeGreaterThan(128);
});
});
@@ -86,20 +90,24 @@
expect(col.setAlpha(undefined).alpha).toEqual(undefined);
});
- test('isLight', () => {
+ test('perceivedBrightness', () => {
// Test a few obviously light/dark colours.
- expect(new HSLuvColor({h: 0, s: 0, l: 0}).isLight).toBeFalsy();
- expect(new HSLuvColor({h: 0, s: 0, l: 100}).isLight).toBeTruthy();
+ expect(new HSLuvColor({h: 0, s: 0, l: 0}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLuvColor({h: 0, s: 0, l: 100}).perceivedBrightness)
+ .toBeGreaterThan(128);
- expect(new HSLuvColor({h: 0, s: 0, l: 40}).isLight).toBeFalsy();
- expect(new HSLuvColor({h: 0, s: 0, l: 60}).isLight).toBeTruthy();
+ expect(new HSLuvColor({h: 0, s: 0, l: 40}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLuvColor({h: 0, s: 0, l: 60}).perceivedBrightness)
+ .toBeGreaterThan(128);
});
});
test('hslToRGB', () => {
// Pick a few well-known conversions to check we're in the right ballpark.
- expect(hslToRGB(0, 0, 0)).toEqual([0, 0, 0]);
- expect(hslToRGB(0, 100, 50)).toEqual([255, 0, 0]);
- expect(hslToRGB(120, 100, 50)).toEqual([0, 255, 0]);
- expect(hslToRGB(240, 100, 50)).toEqual([0, 0, 255]);
+ expect(hslToRgb(0, 0, 0)).toEqual([0, 0, 0]);
+ expect(hslToRgb(0, 100, 50)).toEqual([255, 0, 0]);
+ expect(hslToRgb(120, 100, 50)).toEqual([0, 255, 0]);
+ expect(hslToRgb(240, 100, 50)).toEqual([0, 0, 255]);
});
diff --git a/ui/src/common/colorizer.ts b/ui/src/common/colorizer.ts
index b2951a4..584deb5 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -19,6 +19,11 @@
import {Color, HSLColor, HSLuvColor} from './color';
+// 128 would provide equal weighting between dark and light text, but we want to
+// slightly prefer light text for stylistic reasons.
+// 140 means we must be brighter on average before switching to dark text.
+const PERCEIVED_BRIGHTNESS_LIMIT = 140;
+
// This file defines some opinionated colors and provides functions to access
// random but predictable colors based on a seed, as well as standardized ways
// to access colors for core objects such as slices and thread states.
@@ -98,15 +103,19 @@
// Create a color scheme based on a single color, which defines the variant
// color as a slightly darker and more saturated version of the base color.
-export function makeColorScheme(base: Color): ColorScheme {
- const variant = base.darken(15).saturate(15);
+export function makeColorScheme(base: Color, variant?: Color): ColorScheme {
+ variant = variant ?? base.darken(15).saturate(15);
return {
base,
variant,
disabled: GRAY_COLOR,
- textBase: base.isLight ? BLACK_COLOR : WHITE_COLOR,
- textVariant: variant.isLight ? BLACK_COLOR : WHITE_COLOR,
+ textBase: base.perceivedBrightness >= PERCEIVED_BRIGHTNESS_LIMIT ?
+ BLACK_COLOR :
+ WHITE_COLOR,
+ textVariant: variant.perceivedBrightness >= PERCEIVED_BRIGHTNESS_LIMIT ?
+ BLACK_COLOR :
+ WHITE_COLOR,
textDisabled: WHITE_COLOR, // Low contrast is on purpose
};
}
@@ -150,14 +159,7 @@
const base =
new HSLuvColor({h: hue, s: saturation, l: hash(seed + 'x', 40) + 40});
const variant = new HSLuvColor({h: hue, s: saturation, l: 30});
- const colorScheme: ColorScheme = {
- base,
- variant,
- disabled: GRAY_COLOR,
- textBase: base.isLight ? BLACK_COLOR : WHITE_COLOR,
- textVariant: variant.isLight ? BLACK_COLOR : WHITE_COLOR,
- textDisabled: WHITE_COLOR,
- };
+ const colorScheme = makeColorScheme(base, variant);
proceduralColorCache.set(seed, colorScheme);
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index 4de7003..7543110 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -17,7 +17,9 @@
import {Area, Sorting} from '../../common/state';
import {globals} from '../../frontend/globals';
import {Engine} from '../../trace_processor/engine';
-import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices';
+import {
+ ASYNC_SLICE_TRACK_KIND,
+} from '../../tracks/async_slices/async_slice_track';
import {SLICE_TRACK_KIND} from '../../tracks/chrome_slices';
import {AggregationController} from './aggregation_controller';
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index c123e78..8c6433b 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -39,7 +39,7 @@
STR_NULL,
} from '../trace_processor/query_result';
import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
-import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices';
+import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/async_slice_track';
import {
ENABLE_SCROLL_JANK_PLUGIN_V2,
getScrollJankTracks,
@@ -288,14 +288,25 @@
}
}
- const track: AddTrackArgs = {
- uri: `perfetto.AsyncSlices#${rawName}`,
- trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
- trackGroup,
- name,
- };
+ if (showV1()) {
+ const track: AddTrackArgs = {
+ uri: `perfetto.AsyncSlices#${rawName}`,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup,
+ name,
+ };
+ this.tracksToAdd.push(track);
+ }
- this.tracksToAdd.push(track);
+ if (showV2()) {
+ const track: AddTrackArgs = {
+ uri: `perfetto.AsyncSlices#${rawName}.v2`,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup,
+ name,
+ };
+ this.tracksToAdd.push(track);
+ }
}
}
@@ -1022,12 +1033,24 @@
processName,
kind: ASYNC_SLICE_TRACK_KIND,
});
- this.tracksToAdd.push({
- uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
- name,
- trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
- trackGroup: uuid,
- });
+
+ if (showV1()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
+
+ if (showV2()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}.v2`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
}
}
@@ -1078,12 +1101,24 @@
const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
const name =
getTrackName({name: trackName, upid, pid, processName, kind});
- this.tracksToAdd.push({
- uri: `perfetto.ActualFrames#${upid}`,
- name,
- trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
- trackGroup: uuid,
- });
+
+ if (showV1()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ActualFrames#${upid}`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
+
+ if (showV2()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ActualFrames#${upid}.v2`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
}
}
@@ -1135,12 +1170,24 @@
const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
const name =
getTrackName({name: trackName, upid, pid, processName, kind});
- this.tracksToAdd.push({
- uri: `perfetto.ExpectedFrames#${upid}`,
- name,
- trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
- trackGroup: uuid,
- });
+
+ if (showV1()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ExpectedFrames#${upid}`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
+
+ if (showV2()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ExpectedFrames#${upid}.v2`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
}
}
diff --git a/ui/src/tracks/actual_frames/actual_frames_track.ts b/ui/src/tracks/actual_frames/actual_frames_track.ts
new file mode 100644
index 0000000..4b6c198b
--- /dev/null
+++ b/ui/src/tracks/actual_frames/actual_frames_track.ts
@@ -0,0 +1,146 @@
+// Copyright (C) 2023 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {duration, time} from '../../base/time';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {
+ EngineProxy,
+} from '../../public';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ STR,
+} from '../../trace_processor/query_result';
+
+export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
+
+const BLUE_COLOR = '#03A9F4'; // Blue 500
+const GREEN_COLOR = '#4CAF50'; // Green 500
+const YELLOW_COLOR = '#FFEB3B'; // Yellow 500
+const RED_COLOR = '#FF5722'; // Red 500
+const LIGHT_GREEN_COLOR = '#C0D588'; // Light Green 500
+const PINK_COLOR = '#F515E0'; // Pink 500
+
+export class ActualFramesTrack extends SliceTrackBase {
+ private maxDur = 0n;
+
+ constructor(
+ private engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[], namespace?: string) {
+ super(maxDepth, trackKey, 'actual_frame_timeline_slice', namespace);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<SliceData> {
+ if (this.maxDur === 0n) {
+ const maxDurResult = await this.engine.query(`
+ select
+ max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
+ as maxDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `);
+ this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+ }
+
+ const rawResult = await this.engine.query(`
+ SELECT
+ (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+ s.ts as ts,
+ max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
+ as dur,
+ s.layout_depth as layoutDepth,
+ s.name as name,
+ s.id as id,
+ s.dur = 0 as isInstant,
+ s.dur = -1 as isIncomplete,
+ CASE afs.jank_tag
+ WHEN 'Self Jank' THEN '${RED_COLOR}'
+ WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
+ WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
+ WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
+ WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
+ WHEN 'No Jank' THEN '${GREEN_COLOR}'
+ ELSE '${PINK_COLOR}'
+ END as color
+ from experimental_slice_layout s
+ join actual_frame_timeline_slice afs using(id)
+ where
+ filter_track_ids = '${this.trackIds.join(',')}' and
+ s.ts >= ${start - this.maxDur} and
+ s.ts <= ${end}
+ group by tsq, s.layout_depth
+ order by tsq, s.layout_depth
+`);
+
+ const numRows = rawResult.numRows();
+ const slices: SliceData = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new BigInt64Array(numRows),
+ ends: new BigInt64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ colors: new Uint16Array(numRows),
+ isInstant: new Uint16Array(numRows),
+ isIncomplete: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ const it = rawResult.iter({
+ 'tsq': LONG,
+ 'ts': LONG,
+ 'dur': LONG,
+ 'layoutDepth': NUM,
+ 'id': NUM,
+ 'name': STR,
+ 'isInstant': NUM,
+ 'isIncomplete': NUM,
+ 'color': STR,
+ });
+ for (let i = 0; it.valid(); i++, it.next()) {
+ const startQ = it.tsq;
+ const start = it.ts;
+ const dur = it.dur;
+ const end = start + dur;
+ const minEnd = startQ + resolution;
+ const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+ slices.starts[i] = startQ;
+ slices.ends[i] = endQ;
+ slices.depths[i] = it.layoutDepth;
+ slices.titles[i] = internString(it.name);
+ slices.colors![i] = internString(it.color);
+ slices.sliceIds[i] = it.id;
+ slices.isInstant[i] = it.isInstant;
+ slices.isIncomplete[i] = it.isIncomplete;
+ }
+ return slices;
+ }
+}
diff --git a/ui/src/tracks/actual_frames/actual_frames_track_v2.ts b/ui/src/tracks/actual_frames/actual_frames_track_v2.ts
new file mode 100644
index 0000000..c30e673
--- /dev/null
+++ b/ui/src/tracks/actual_frames/actual_frames_track_v2.ts
@@ -0,0 +1,96 @@
+// Copyright (C) 2023 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 {HSLColor} from '../../common/color';
+import {ColorScheme, makeColorScheme} from '../../common/colorizer';
+import {
+ NAMED_ROW,
+ NamedSliceTrack,
+ NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {EngineProxy, Slice, STR_NULL} from '../../public';
+
+const BLUE = makeColorScheme(new HSLColor('#03A9F4')); // Blue 500
+const GREEN = makeColorScheme(new HSLColor('#4CAF50')); // Green 500
+const YELLOW = makeColorScheme(new HSLColor('#FFEB3B')); // Yellow 500
+const RED = makeColorScheme(new HSLColor('#FF5722')); // Red 500
+const LIGHT_GREEN =
+ makeColorScheme(new HSLColor('#C0D588')); // Light Green 500
+const PINK = makeColorScheme(new HSLColor('#F515E0')); // Pink 500
+
+export const ACTUAL_FRAME_ROW = {
+ // Base columns (tsq, ts, dur, id, depth).
+ ...NAMED_ROW,
+
+ // Chrome-specific columns.
+ jankTag: STR_NULL,
+};
+export type ActualFrameRow = typeof ACTUAL_FRAME_ROW;
+
+export interface ActualFrameTrackTypes extends NamedSliceTrackTypes {
+ row: ActualFrameRow;
+}
+
+export class ActualFramesTrack extends NamedSliceTrack<ActualFrameTrackTypes> {
+ constructor(
+ engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[]) {
+ super({engine, trackKey});
+ this.sliceLayout.maxDepth = maxDepth + 1;
+ }
+
+ // This is used by the base class to call iter().
+ getRowSpec() {
+ return ACTUAL_FRAME_ROW;
+ }
+
+ getSqlSource(): string {
+ return `
+ SELECT
+ s.ts as ts,
+ s.dur as dur,
+ s.layout_depth as depth,
+ s.name as name,
+ s.id as id,
+ afs.jank_tag as jankTag
+ from experimental_slice_layout s
+ join actual_frame_timeline_slice afs using(id)
+ where
+ filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+
+ rowToSlice(row: ActualFrameRow): Slice {
+ const baseSlice = super.rowToSlice(row);
+ return {...baseSlice, colorScheme: getColorSchemeForJank(row.jankTag)};
+ }
+}
+
+function getColorSchemeForJank(jankTag: string|null): ColorScheme {
+ switch (jankTag) {
+ case 'Self Jank':
+ return RED;
+ case 'Other Jank':
+ return YELLOW;
+ case 'Dropped Frame':
+ return BLUE;
+ case 'Buffer Stuffing':
+ case 'SurfaceFlinger Stuffing':
+ return LIGHT_GREEN;
+ case 'No Jank':
+ return GREEN;
+ default:
+ return PINK;
+ }
+}
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/actual_frames/index.ts
index fbc3cf7..8fcd597 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -12,11 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {duration, time} from '../../base/time';
-import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
import {
- EngineProxy,
Plugin,
PluginContext,
PluginContextTrace,
@@ -24,134 +20,19 @@
} from '../../public';
import {getTrackName} from '../../public/utils';
import {
- LONG,
- LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
+import {ActualFramesTrack} from './actual_frames_track';
+import {
+ ActualFramesTrack as ActualFramesTrackV2,
+} from './actual_frames_track_v2';
+
export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
-const BLUE_COLOR = '#03A9F4'; // Blue 500
-const GREEN_COLOR = '#4CAF50'; // Green 500
-const YELLOW_COLOR = '#FFEB3B'; // Yellow 500
-const RED_COLOR = '#FF5722'; // Red 500
-const LIGHT_GREEN_COLOR = '#C0D588'; // Light Green 500
-const PINK_COLOR = '#F515E0'; // Pink 500
-
-class SliceTrack extends SliceTrackBase {
- private maxDur = 0n;
-
- constructor(
- private engine: EngineProxy, maxDepth: number, trackKey: string,
- private trackIds: number[], namespace?: string) {
- super(maxDepth, trackKey, 'actual_frame_timeline_slice', namespace);
- }
-
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<SliceData> {
- if (this.maxDur === 0n) {
- const maxDurResult = await this.engine.query(`
- select
- max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
- as maxDur
- from experimental_slice_layout
- where filter_track_ids = '${this.trackIds.join(',')}'
- `);
- this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
- }
-
- const rawResult = await this.engine.query(`
- SELECT
- (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
- s.ts as ts,
- max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
- as dur,
- s.layout_depth as layoutDepth,
- s.name as name,
- s.id as id,
- s.dur = 0 as isInstant,
- s.dur = -1 as isIncomplete,
- CASE afs.jank_tag
- WHEN 'Self Jank' THEN '${RED_COLOR}'
- WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
- WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
- WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
- WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
- WHEN 'No Jank' THEN '${GREEN_COLOR}'
- ELSE '${PINK_COLOR}'
- END as color
- from experimental_slice_layout s
- join actual_frame_timeline_slice afs using(id)
- where
- filter_track_ids = '${this.trackIds.join(',')}' and
- s.ts >= ${start - this.maxDur} and
- s.ts <= ${end}
- group by tsq, s.layout_depth
- order by tsq, s.layout_depth
-`);
-
- const numRows = rawResult.numRows();
- const slices: SliceData = {
- start,
- end,
- resolution,
- length: numRows,
- strings: [],
- sliceIds: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
- depths: new Uint16Array(numRows),
- titles: new Uint16Array(numRows),
- colors: new Uint16Array(numRows),
- isInstant: new Uint16Array(numRows),
- isIncomplete: new Uint16Array(numRows),
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = slices.strings.length;
- slices.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
-
- const it = rawResult.iter({
- 'tsq': LONG,
- 'ts': LONG,
- 'dur': LONG,
- 'layoutDepth': NUM,
- 'id': NUM,
- 'name': STR,
- 'isInstant': NUM,
- 'isIncomplete': NUM,
- 'color': STR,
- });
- for (let i = 0; it.valid(); i++, it.next()) {
- const startQ = it.tsq;
- const start = it.ts;
- const dur = it.dur;
- const end = start + dur;
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
- slices.starts[i] = startQ;
- slices.ends[i] = endQ;
- slices.depths[i] = it.layoutDepth;
- slices.titles[i] = internString(it.name);
- slices.colors![i] = internString(it.color);
- slices.sliceIds[i] = it.id;
- slices.isInstant[i] = it.isInstant;
- slices.isIncomplete[i] = it.isIncomplete;
- }
- return slices;
- }
-}
-
class ActualFrames implements Plugin {
onActivate(_ctx: PluginContext): void {}
@@ -211,7 +92,22 @@
trackIds,
kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
track: ({trackKey}) => {
- return new SliceTrack(
+ return new ActualFramesTrack(
+ engine,
+ maxDepth,
+ trackKey,
+ trackIds,
+ );
+ },
+ });
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.ActualFrames#${upid}.v2`,
+ displayName,
+ trackIds,
+ kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new ActualFramesTrackV2(
engine,
maxDepth,
trackKey,
diff --git a/ui/src/tracks/async_slices/async_slice_track.ts b/ui/src/tracks/async_slices/async_slice_track.ts
new file mode 100644
index 0000000..5d6e25a
--- /dev/null
+++ b/ui/src/tracks/async_slices/async_slice_track.ts
@@ -0,0 +1,119 @@
+// Copyright (C) 2021 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {duration, time} from '../../base/time';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {EngineProxy} from '../../public';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ STR,
+} from '../../trace_processor/query_result';
+
+export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
+
+export class AsyncSliceTrack extends SliceTrackBase {
+ private maxDurNs: duration = 0n;
+
+ constructor(
+ private engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[], namespace?: string) {
+ // TODO is 'slice' right here?
+ super(maxDepth, trackKey, 'slice', namespace);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<SliceData> {
+ if (this.maxDurNs === 0n) {
+ const maxDurResult = await this.engine.query(`
+ select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts,
+ dur)) as maxDur from experimental_slice_layout where filter_track_ids
+ = '${this.trackIds.join(',')}'
+ `);
+ this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+ }
+
+ const queryRes = await this.engine.query(`
+ SELECT
+ (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+ ts,
+ max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as
+ dur, layout_depth as depth, ifnull(name, '[null]') as name, id, dur =
+ 0 as isInstant, dur = -1 as isIncomplete
+ from experimental_slice_layout
+ where
+ filter_track_ids = '${this.trackIds.join(',')}' and
+ ts >= ${start - this.maxDurNs} and
+ ts <= ${end}
+ group by tsq, layout_depth
+ order by tsq, layout_depth
+ `);
+
+ const numRows = queryRes.numRows();
+ const slices: SliceData = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new BigInt64Array(numRows),
+ ends: new BigInt64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ isInstant: new Uint16Array(numRows),
+ isIncomplete: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ const it = queryRes.iter({
+ tsq: LONG,
+ ts: LONG,
+ dur: LONG,
+ depth: NUM,
+ name: STR,
+ id: NUM,
+ isInstant: NUM,
+ isIncomplete: NUM,
+ });
+ for (let row = 0; it.valid(); it.next(), row++) {
+ const startQ = it.tsq;
+ const start = it.ts;
+ const dur = it.dur;
+ const end = start + dur;
+ const minEnd = startQ + resolution;
+ const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+ slices.starts[row] = startQ;
+ slices.ends[row] = endQ;
+ slices.depths[row] = it.depth;
+ slices.titles[row] = internString(it.name);
+ slices.sliceIds[row] = it.id;
+ slices.isInstant[row] = it.isInstant;
+ slices.isIncomplete[row] = it.isIncomplete;
+ }
+ return slices;
+ }
+}
diff --git a/ui/src/tracks/async_slices/async_slice_track_v2.ts b/ui/src/tracks/async_slices/async_slice_track_v2.ts
new file mode 100644
index 0000000..4e87c9b
--- /dev/null
+++ b/ui/src/tracks/async_slices/async_slice_track_v2.ts
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 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 {NamedSliceTrack} from '../../frontend/named_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {Slice} from '../../public';
+
+export class AsyncSliceTrackV2 extends NamedSliceTrack {
+ constructor(
+ args: NewTrackArgs, maxDepth: number, private trackIds: number[]) {
+ super(args);
+ this.sliceLayout.maxDepth = maxDepth + 1;
+ }
+
+ getSqlSource(): string {
+ return `
+ select
+ ts,
+ dur,
+ layout_depth as depth,
+ ifnull(name, '[null]') as name,
+ id,
+ thread_dur as threadDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+
+ onUpdatedSlices(slices: Slice[]) {
+ for (const slice of slices) {
+ slice.isHighlighted = (slice === this.hoveredSlice);
+ }
+ }
+}
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 97d43fa..eb3ad88 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -12,11 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {duration, time} from '../../base/time';
-import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
import {
- EngineProxy,
Plugin,
PluginContext,
PluginContextTrace,
@@ -24,109 +20,17 @@
} from '../../public';
import {getTrackName} from '../../public/utils';
import {
- LONG,
- LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
+import {AsyncSliceTrack} from './async_slice_track';
+import {AsyncSliceTrackV2} from './async_slice_track_v2';
+
export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
-class AsyncSliceTrack extends SliceTrackBase {
- private maxDurNs: duration = 0n;
-
- constructor(
- private engine: EngineProxy, maxDepth: number, trackKey: string,
- private trackIds: number[], namespace?: string) {
- // TODO is 'slice' right here?
- super(maxDepth, trackKey, 'slice', namespace);
- }
-
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<SliceData> {
- if (this.maxDurNs === 0n) {
- const maxDurResult = await this.engine.query(`
- select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts,
- dur)) as maxDur from experimental_slice_layout where filter_track_ids
- = '${this.trackIds.join(',')}'
- `);
- this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
- }
-
- const queryRes = await this.engine.query(`
- SELECT
- (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
- ts,
- max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as
- dur, layout_depth as depth, ifnull(name, '[null]') as name, id, dur =
- 0 as isInstant, dur = -1 as isIncomplete
- from experimental_slice_layout
- where
- filter_track_ids = '${this.trackIds.join(',')}' and
- ts >= ${start - this.maxDurNs} and
- ts <= ${end}
- group by tsq, layout_depth
- order by tsq, layout_depth
- `);
-
- const numRows = queryRes.numRows();
- const slices: SliceData = {
- start,
- end,
- resolution,
- length: numRows,
- strings: [],
- sliceIds: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
- depths: new Uint16Array(numRows),
- titles: new Uint16Array(numRows),
- isInstant: new Uint16Array(numRows),
- isIncomplete: new Uint16Array(numRows),
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = slices.strings.length;
- slices.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
-
- const it = queryRes.iter({
- tsq: LONG,
- ts: LONG,
- dur: LONG,
- depth: NUM,
- name: STR,
- id: NUM,
- isInstant: NUM,
- isIncomplete: NUM,
- });
- for (let row = 0; it.valid(); it.next(), row++) {
- const startQ = it.tsq;
- const start = it.ts;
- const dur = it.dur;
- const end = start + dur;
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
- slices.starts[row] = startQ;
- slices.ends[row] = endQ;
- slices.depths[row] = it.depth;
- slices.titles[row] = internString(it.name);
- slices.sliceIds[row] = it.id;
- slices.isInstant[row] = it.isInstant;
- slices.isIncomplete[row] = it.isIncomplete;
- }
- return slices;
- }
-}
-
class AsyncSlicePlugin implements Plugin {
onActivate(_ctx: PluginContext) {}
@@ -220,6 +124,20 @@
);
},
});
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.AsyncSlices#${rawName}.v2`,
+ displayName,
+ trackIds,
+ kind: ASYNC_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new AsyncSliceTrackV2(
+ {engine, trackKey},
+ maxDepth,
+ trackIds,
+ );
+ },
+ });
}
}
@@ -288,6 +206,20 @@
);
},
});
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}.v2`,
+ displayName,
+ trackIds,
+ kind: ASYNC_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new AsyncSliceTrackV2(
+ {engine: ctx.engine, trackKey},
+ maxDepth,
+ trackIds,
+ );
+ },
+ });
}
}
}
diff --git a/ui/src/tracks/expected_frames/expected_frames_track.ts b/ui/src/tracks/expected_frames/expected_frames_track.ts
new file mode 100644
index 0000000..665cfc5
--- /dev/null
+++ b/ui/src/tracks/expected_frames/expected_frames_track.ts
@@ -0,0 +1,123 @@
+// Copyright (C) 2021 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {Duration, duration, time} from '../../base/time';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {EngineProxy} from '../../public';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ STR,
+} from '../../trace_processor/query_result';
+
+export class ExpectedFramesTrack extends SliceTrackBase {
+ private maxDur = Duration.ZERO;
+
+ constructor(
+ private engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[], namespace?: string) {
+ super(maxDepth, trackKey, '', namespace);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<SliceData> {
+ if (this.maxDur === Duration.ZERO) {
+ const maxDurResult = await this.engine.query(`
+ select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
+ as maxDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `);
+ this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+ }
+
+ const queryRes = await this.engine.query(`
+ SELECT
+ (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+ ts,
+ max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
+ layout_depth as layoutDepth,
+ name,
+ id,
+ dur = 0 as isInstant,
+ dur = -1 as isIncomplete
+ from experimental_slice_layout
+ where
+ filter_track_ids = '${this.trackIds.join(',')}' and
+ ts >= ${start - this.maxDur} and
+ ts <= ${end}
+ group by tsq, layout_depth
+ order by tsq, layout_depth
+ `);
+
+ const numRows = queryRes.numRows();
+ const slices: SliceData = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new BigInt64Array(numRows),
+ ends: new BigInt64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ colors: new Uint16Array(numRows),
+ isInstant: new Uint16Array(numRows),
+ isIncomplete: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+ const greenIndex = internString('#4CAF50');
+
+ const it = queryRes.iter({
+ tsq: LONG,
+ ts: LONG,
+ dur: LONG,
+ layoutDepth: NUM,
+ id: NUM,
+ name: STR,
+ isInstant: NUM,
+ isIncomplete: NUM,
+ });
+ for (let row = 0; it.valid(); it.next(), ++row) {
+ const startQ = it.tsq;
+ const start = it.ts;
+ const dur = it.dur;
+ const end = start + dur;
+ const minEnd = startQ + resolution;
+ const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+ slices.starts[row] = startQ;
+ slices.ends[row] = endQ;
+ slices.depths[row] = it.layoutDepth;
+ slices.titles[row] = internString(it.name);
+ slices.sliceIds[row] = it.id;
+ slices.isInstant[row] = it.isInstant;
+ slices.isIncomplete[row] = it.isIncomplete;
+ slices.colors![row] = greenIndex;
+ }
+ return slices;
+ }
+}
diff --git a/ui/src/tracks/expected_frames/expected_frames_track_v2.ts b/ui/src/tracks/expected_frames/expected_frames_track_v2.ts
new file mode 100644
index 0000000..268b4b6
--- /dev/null
+++ b/ui/src/tracks/expected_frames/expected_frames_track_v2.ts
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 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 {HSLColor} from '../../common/color';
+import {makeColorScheme} from '../../common/colorizer';
+import {NamedRow, NamedSliceTrack} from '../../frontend/named_slice_track';
+import {EngineProxy, Slice} from '../../public';
+
+const GREEN = makeColorScheme(new HSLColor('#4CAF50')); // Green 500
+
+export class ExpectedFramesTrack extends NamedSliceTrack {
+ constructor(
+ engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[]) {
+ super({engine, trackKey});
+ this.sliceLayout.maxDepth = maxDepth + 1;
+ }
+
+ getSqlSource(): string {
+ return `
+ SELECT
+ ts,
+ dur,
+ layout_depth as depth,
+ name,
+ id
+ from experimental_slice_layout
+ where
+ filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+
+ rowToSlice(row: NamedRow): Slice {
+ const baseSlice = super.rowToSlice(row);
+ return {...baseSlice, colorScheme: GREEN};
+ }
+}
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/expected_frames/index.ts
index 8cc1833..10b3f44 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -12,14 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {Duration, duration, time} from '../../base/time';
+// import { NamedSliceTrack } from 'src/frontend/named_slice_track';
import {
- SliceData,
- SliceTrackBase,
-} from '../../frontend/slice_track_base';
-import {
- EngineProxy,
Plugin,
PluginContext,
PluginContextTrace,
@@ -27,115 +21,19 @@
} from '../../public';
import {getTrackName} from '../../public/utils';
import {
- LONG,
- LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
+import {ExpectedFramesTrack} from './expected_frames_track';
+import {
+ ExpectedFramesTrack as ExpectedFramesTrackV2,
+} from './expected_frames_track_v2';
+
export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
-class SliceTrack extends SliceTrackBase {
- private maxDur = Duration.ZERO;
-
- constructor(
- private engine: EngineProxy, maxDepth: number, trackKey: string,
- private trackIds: number[], namespace?: string) {
- super(maxDepth, trackKey, '', namespace);
- }
-
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<SliceData> {
- if (this.maxDur === Duration.ZERO) {
- const maxDurResult = await this.engine.query(`
- select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
- as maxDur
- from experimental_slice_layout
- where filter_track_ids = '${this.trackIds.join(',')}'
- `);
- this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
- }
-
- const queryRes = await this.engine.query(`
- SELECT
- (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
- ts,
- max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
- layout_depth as layoutDepth,
- name,
- id,
- dur = 0 as isInstant,
- dur = -1 as isIncomplete
- from experimental_slice_layout
- where
- filter_track_ids = '${this.trackIds.join(',')}' and
- ts >= ${start - this.maxDur} and
- ts <= ${end}
- group by tsq, layout_depth
- order by tsq, layout_depth
- `);
-
- const numRows = queryRes.numRows();
- const slices: SliceData = {
- start,
- end,
- resolution,
- length: numRows,
- strings: [],
- sliceIds: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
- depths: new Uint16Array(numRows),
- titles: new Uint16Array(numRows),
- colors: new Uint16Array(numRows),
- isInstant: new Uint16Array(numRows),
- isIncomplete: new Uint16Array(numRows),
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = slices.strings.length;
- slices.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
- const greenIndex = internString('#4CAF50');
-
- const it = queryRes.iter({
- tsq: LONG,
- ts: LONG,
- dur: LONG,
- layoutDepth: NUM,
- id: NUM,
- name: STR,
- isInstant: NUM,
- isIncomplete: NUM,
- });
- for (let row = 0; it.valid(); it.next(), ++row) {
- const startQ = it.tsq;
- const start = it.ts;
- const dur = it.dur;
- const end = start + dur;
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
- slices.starts[row] = startQ;
- slices.ends[row] = endQ;
- slices.depths[row] = it.layoutDepth;
- slices.titles[row] = internString(it.name);
- slices.sliceIds[row] = it.id;
- slices.isInstant[row] = it.isInstant;
- slices.isIncomplete[row] = it.isIncomplete;
- slices.colors![row] = greenIndex;
- }
- return slices;
- }
-}
-
class ExpectedFramesPlugin implements Plugin {
onActivate(_ctx: PluginContext): void {}
@@ -195,7 +93,22 @@
trackIds,
kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
track: ({trackKey}) => {
- return new SliceTrack(
+ return new ExpectedFramesTrack(
+ engine,
+ maxDepth,
+ trackKey,
+ trackIds,
+ );
+ },
+ });
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.ExpectedFrames#${upid}.v2`,
+ displayName,
+ trackIds,
+ kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new ExpectedFramesTrackV2(
engine,
maxDepth,
trackKey,
diff --git a/ui/src/tracks/thread_state/thread_state_track_v2.ts b/ui/src/tracks/thread_state/thread_state_track_v2.ts
deleted file mode 100644
index c61ca5b..0000000
--- a/ui/src/tracks/thread_state/thread_state_track_v2.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (C) 2021 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 {Actions} from '../../common/actions';
-import {colorForState} from '../../common/colorizer';
-import {Selection} from '../../common/state';
-import {translateState} from '../../common/thread_state';
-import {
- BASE_ROW,
- BaseSliceTrack,
- BaseSliceTrackTypes,
- OnSliceClickArgs,
-} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
-import {
- SLICE_LAYOUT_FLAT_DEFAULTS,
- SliceLayout,
-} from '../../frontend/slice_layout';
-import {NewTrackArgs} from '../../frontend/track';
-import {NUM_NULL, STR} from '../../trace_processor/query_result';
-
-export const THREAD_STATE_ROW = {
- ...BASE_ROW,
- state: STR,
- ioWait: NUM_NULL,
-};
-
-export type ThreadStateRow = typeof THREAD_STATE_ROW;
-
-export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
- row: ThreadStateRow;
-}
-
-export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
- protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
-
- constructor(args: NewTrackArgs, private utid: number) {
- super(args);
- }
-
- // This is used by the base class to call iter().
- getRowSpec(): ThreadStateTrackTypes['row'] {
- return THREAD_STATE_ROW;
- }
-
- getSqlSource(): string {
- // Do not display states 'x' and 'S' (dead & sleeping).
- const sql = `
- select
- id,
- ts,
- dur,
- cpu,
- state,
- io_wait as ioWait,
- 0 as depth
- from thread_state
- where
- utid = ${this.utid} and
- state != 'x' and
- state != 'S'
- `;
- return sql;
- }
-
- rowToSlice(row: ThreadStateTrackTypes['row']):
- ThreadStateTrackTypes['slice'] {
- const baseSlice = super.rowToSlice(row);
- const ioWait = row.ioWait === null ? undefined : !!row.ioWait;
- const title = translateState(row.state, ioWait);
- const color = colorForState(title);
- return {...baseSlice, title, colorScheme: color};
- }
-
- onUpdatedSlices(slices: ThreadStateTrackTypes['slice'][]) {
- for (const slice of slices) {
- slice.isHighlighted = (slice === this.hoveredSlice);
- }
- }
-
- onSliceClick(args: OnSliceClickArgs<ThreadStateTrackTypes['slice']>) {
- globals.makeSelection(Actions.selectThreadState({
- id: args.slice.id,
- trackKey: this.trackKey,
- }));
- }
-
- protected isSelectionHandled(selection: Selection): boolean {
- return selection.kind === 'THREAD_STATE';
- }
-}
diff --git a/ui/src/tracks/thread_state/thread_state_v2.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
index 54dec7d..c35ed5c 100644
--- a/ui/src/tracks/thread_state/thread_state_v2.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -35,19 +35,13 @@
state: STR,
ioWait: NUM_NULL,
};
+
export type ThreadStateRow = typeof THREAD_STATE_ROW;
-
-export interface ThreadStateTrackConfig {
- utid: number;
-}
-
export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
row: ThreadStateRow;
}
-export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
-
export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
@@ -62,11 +56,15 @@
getSqlSource(): string {
// Do not display states 'x' and 'S' (dead & sleeping).
+ // Note: Thread state tracks V1 basically ignores incomplete slices, faking
+ // their duration as 1 instead. Let's just do this here as well for now to
+ // achieve feature parity with tracks V1 and tackle the issue of overlapping
+ // incomplete slices later.
return `
select
id,
ts,
- dur,
+ max(dur, 1) as dur,
cpu,
state,
io_wait as ioWait,