Merge changes I40c4d184,I5d9a9e7b into main
* changes:
shared_lib: autogenerate required proto headers
Protozero generator for C
diff --git a/Android.bp b/Android.bp
index 081ab9b..d668c2b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4589,6 +4589,7 @@
srcs: [
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
@@ -4669,6 +4670,7 @@
srcs: [
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
@@ -4732,6 +4734,7 @@
srcs: [
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
@@ -10431,6 +10434,7 @@
"src/trace_processor/metrics/sql/android/android_batt.sql",
"src/trace_processor/metrics/sql/android/android_binder.sql",
"src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql",
+ "src/trace_processor/metrics/sql/android/android_boot.sql",
"src/trace_processor/metrics/sql/android/android_camera.sql",
"src/trace_processor/metrics/sql/android/android_camera_unagg.sql",
"src/trace_processor/metrics/sql/android/android_cpu.sql",
@@ -10617,6 +10621,7 @@
"src/trace_processor/perfetto_sql/engine/function_util.cc",
"src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc",
"src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc",
+ "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc",
"src/trace_processor/perfetto_sql/engine/runtime_table_function.cc",
],
}
@@ -10627,6 +10632,7 @@
srcs: [
"src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc",
"src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc",
+ "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc",
],
}
diff --git a/BUILD b/BUILD
index ee2d513..42122bd 100644
--- a/BUILD
+++ b/BUILD
@@ -65,160 +65,6 @@
linkstatic = True,
)
-# GN target: //src/bigtrace:bigtrace
-perfetto_cc_library(
- name = "bigtrace",
- srcs = [
- ":src_base_threading_threading",
- ":src_bigtrace_sources",
- ":src_kernel_utils_syscall_table",
- ":src_protozero_proto_ring_buffer",
- ":src_trace_processor_db_db",
- ":src_trace_processor_db_overlays_overlays",
- ":src_trace_processor_db_storage_storage",
- ":src_trace_processor_export_json",
- ":src_trace_processor_importers_android_bugreport_android_bugreport",
- ":src_trace_processor_importers_common_common",
- ":src_trace_processor_importers_common_parser_types",
- ":src_trace_processor_importers_common_trace_parser_hdr",
- ":src_trace_processor_importers_ftrace_ftrace_descriptors",
- ":src_trace_processor_importers_ftrace_full",
- ":src_trace_processor_importers_ftrace_minimal",
- ":src_trace_processor_importers_fuchsia_fuchsia_record",
- ":src_trace_processor_importers_fuchsia_full",
- ":src_trace_processor_importers_fuchsia_minimal",
- ":src_trace_processor_importers_gzip_full",
- ":src_trace_processor_importers_i2c_full",
- ":src_trace_processor_importers_json_full",
- ":src_trace_processor_importers_json_minimal",
- ":src_trace_processor_importers_memory_tracker_graph_processor",
- ":src_trace_processor_importers_ninja_ninja",
- ":src_trace_processor_importers_proto_full",
- ":src_trace_processor_importers_proto_minimal",
- ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
- ":src_trace_processor_importers_proto_proto_importer_module",
- ":src_trace_processor_importers_proto_winscope_full",
- ":src_trace_processor_importers_syscalls_full",
- ":src_trace_processor_importers_systrace_full",
- ":src_trace_processor_importers_systrace_systrace_line",
- ":src_trace_processor_importers_systrace_systrace_parser",
- ":src_trace_processor_lib",
- ":src_trace_processor_metatrace",
- ":src_trace_processor_metrics_metrics",
- ":src_trace_processor_perfetto_sql_engine_engine",
- ":src_trace_processor_perfetto_sql_intrinsics_functions_functions",
- ":src_trace_processor_perfetto_sql_intrinsics_functions_interface",
- ":src_trace_processor_perfetto_sql_intrinsics_operators_operators",
- ":src_trace_processor_perfetto_sql_intrinsics_table_functions_interface",
- ":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
- ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
- ":src_trace_processor_rpc_rpc",
- ":src_trace_processor_sorter_sorter",
- ":src_trace_processor_sqlite_query_constraints",
- ":src_trace_processor_sqlite_sqlite",
- ":src_trace_processor_storage_minimal",
- ":src_trace_processor_storage_storage",
- ":src_trace_processor_tables_tables",
- ":src_trace_processor_tables_tables_python",
- ":src_trace_processor_types_types",
- ":src_trace_processor_util_bump_allocator",
- ":src_trace_processor_util_descriptors",
- ":src_trace_processor_util_glob",
- ":src_trace_processor_util_gzip",
- ":src_trace_processor_util_interned_message_view",
- ":src_trace_processor_util_profile_builder",
- ":src_trace_processor_util_proto_profiler",
- ":src_trace_processor_util_proto_to_args_parser",
- ":src_trace_processor_util_protozero_to_json",
- ":src_trace_processor_util_protozero_to_text",
- ":src_trace_processor_util_regex",
- ":src_trace_processor_util_sql_argument",
- ":src_trace_processor_util_stack_traces_util",
- ":src_trace_processor_util_stdlib",
- ":src_trace_processor_util_util",
- ":src_trace_processor_util_zip_reader",
- ":src_trace_processor_views_views",
- ],
- hdrs = [
- ":include_perfetto_base_base",
- ":include_perfetto_ext_base_base",
- ":include_perfetto_ext_base_threading_threading",
- ":include_perfetto_ext_bigtrace_bigtrace",
- ":include_perfetto_ext_trace_processor_demangle",
- ":include_perfetto_ext_trace_processor_export_json",
- ":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
- ":include_perfetto_ext_trace_processor_rpc_query_result_serializer",
- ":include_perfetto_ext_traced_sys_stats_counters",
- ":include_perfetto_protozero_protozero",
- ":include_perfetto_public_abi_base",
- ":include_perfetto_public_base",
- ":include_perfetto_public_protozero",
- ":include_perfetto_trace_processor_basic_types",
- ":include_perfetto_trace_processor_storage",
- ":include_perfetto_trace_processor_trace_processor",
- ],
- deps = [
- ":protos_perfetto_bigtrace_lite",
- ":protos_perfetto_common_lite",
- ":protos_perfetto_common_zero",
- ":protos_perfetto_config_android_zero",
- ":protos_perfetto_config_ftrace_zero",
- ":protos_perfetto_config_gpu_zero",
- ":protos_perfetto_config_inode_file_zero",
- ":protos_perfetto_config_interceptors_zero",
- ":protos_perfetto_config_power_zero",
- ":protos_perfetto_config_process_stats_zero",
- ":protos_perfetto_config_profiling_zero",
- ":protos_perfetto_config_statsd_zero",
- ":protos_perfetto_config_sys_stats_zero",
- ":protos_perfetto_config_system_info_zero",
- ":protos_perfetto_config_track_event_zero",
- ":protos_perfetto_config_zero",
- ":protos_perfetto_trace_android_zero",
- ":protos_perfetto_trace_chrome_zero",
- ":protos_perfetto_trace_filesystem_zero",
- ":protos_perfetto_trace_ftrace_zero",
- ":protos_perfetto_trace_gpu_zero",
- ":protos_perfetto_trace_interned_data_zero",
- ":protos_perfetto_trace_minimal_zero",
- ":protos_perfetto_trace_non_minimal_zero",
- ":protos_perfetto_trace_perfetto_zero",
- ":protos_perfetto_trace_power_zero",
- ":protos_perfetto_trace_processor_lite",
- ":protos_perfetto_trace_processor_metrics_impl_zero",
- ":protos_perfetto_trace_processor_zero",
- ":protos_perfetto_trace_profiling_zero",
- ":protos_perfetto_trace_ps_zero",
- ":protos_perfetto_trace_statsd_zero",
- ":protos_perfetto_trace_sys_stats_zero",
- ":protos_perfetto_trace_system_info_zero",
- ":protos_perfetto_trace_track_event_zero",
- ":protos_perfetto_trace_translation_zero",
- ":protos_third_party_pprof_zero",
- ":protozero",
- ":src_base_base",
- ":src_base_version",
- ":src_trace_processor_containers_containers",
- ":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
- ":src_trace_processor_importers_proto_gen_cc_config_descriptor",
- ":src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor",
- ":src_trace_processor_importers_proto_gen_cc_trace_descriptor",
- ":src_trace_processor_importers_proto_gen_cc_track_event_descriptor",
- ":src_trace_processor_importers_proto_winscope_gen_cc_winscope_descriptor",
- ":src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
- ":src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
- ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
- ":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
- ":src_trace_processor_perfetto_sql_prelude_prelude",
- ":src_trace_processor_perfetto_sql_stdlib_stdlib",
- ] + PERFETTO_CONFIG.deps.jsoncpp +
- PERFETTO_CONFIG.deps.sqlite +
- PERFETTO_CONFIG.deps.sqlite_ext_percentile +
- PERFETTO_CONFIG.deps.zlib +
- PERFETTO_CONFIG.deps.demangle_wrapper,
- linkstatic = True,
-)
-
# GN target: //src/ipc/protoc_plugin:ipc_plugin
perfetto_cc_binary(
name = "ipc_plugin",
@@ -723,22 +569,6 @@
],
)
-# GN target: //include/perfetto/ext/base/threading:threading
-perfetto_filegroup(
- name = "include_perfetto_ext_base_threading_threading",
- srcs = [
- "include/perfetto/ext/base/threading/channel.h",
- "include/perfetto/ext/base/threading/future.h",
- "include/perfetto/ext/base/threading/future_combinators.h",
- "include/perfetto/ext/base/threading/poll.h",
- "include/perfetto/ext/base/threading/spawn.h",
- "include/perfetto/ext/base/threading/stream.h",
- "include/perfetto/ext/base/threading/stream_combinators.h",
- "include/perfetto/ext/base/threading/thread_pool.h",
- "include/perfetto/ext/base/threading/util.h",
- ],
-)
-
# GN target: //include/perfetto/ext/base:base
perfetto_filegroup(
name = "include_perfetto_ext_base_base",
@@ -798,16 +628,6 @@
],
)
-# GN target: //include/perfetto/ext/bigtrace:bigtrace
-perfetto_filegroup(
- name = "include_perfetto_ext_bigtrace_bigtrace",
- srcs = [
- "include/perfetto/ext/bigtrace/environment.h",
- "include/perfetto/ext/bigtrace/orchestrator.h",
- "include/perfetto/ext/bigtrace/worker.h",
- ],
-)
-
# GN target: //include/perfetto/ext/ipc:ipc
perfetto_filegroup(
name = "include_perfetto_ext_ipc_ipc",
@@ -1129,16 +949,6 @@
linkstatic = True,
)
-# GN target: //src/base/threading:threading
-perfetto_filegroup(
- name = "src_base_threading_threading",
- srcs = [
- "src/base/threading/spawn.cc",
- "src/base/threading/stream_combinators.cc",
- "src/base/threading/thread_pool.cc",
- ],
-)
-
# GN target: //src/base:base
perfetto_cc_library(
name = "src_base_base",
@@ -1231,19 +1041,6 @@
],
)
-# GN target: //src/bigtrace:sources
-perfetto_filegroup(
- name = "src_bigtrace_sources",
- srcs = [
- "src/bigtrace/orchestrator_impl.cc",
- "src/bigtrace/orchestrator_impl.h",
- "src/bigtrace/trace_processor_wrapper.cc",
- "src/bigtrace/trace_processor_wrapper.h",
- "src/bigtrace/worker_impl.cc",
- "src/bigtrace/worker_impl.h",
- ],
-)
-
# GN target: //src/ipc:client
perfetto_filegroup(
name = "src_ipc_client",
@@ -1994,6 +1791,7 @@
"src/trace_processor/metrics/sql/android/android_batt.sql",
"src/trace_processor/metrics/sql/android/android_binder.sql",
"src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql",
+ "src/trace_processor/metrics/sql/android/android_boot.sql",
"src/trace_processor/metrics/sql/android/android_camera.sql",
"src/trace_processor/metrics/sql/android/android_camera_unagg.sql",
"src/trace_processor/metrics/sql/android/android_cpu.sql",
@@ -2263,6 +2061,8 @@
"src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h",
"src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc",
"src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h",
+ "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc",
+ "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h",
"src/trace_processor/perfetto_sql/engine/runtime_table_function.cc",
"src/trace_processor/perfetto_sql/engine/runtime_table_function.h",
],
@@ -3605,41 +3405,50 @@
],
)
-# GN target: //protos/perfetto/bigtrace:lite
-perfetto_cc_proto_library(
- name = "protos_perfetto_bigtrace_lite",
- deps = [
- ":protos_perfetto_bigtrace_protos",
- ],
-)
-
-# GN target: //protos/perfetto/bigtrace:source_set
+# GN target: [//protos/perfetto/trace_processor:source_set]
perfetto_proto_library(
- name = "protos_perfetto_bigtrace_protos",
- srcs = [
- "protos/perfetto/bigtrace/orchestrator.proto",
- "protos/perfetto/bigtrace/worker.proto",
- ],
- visibility = [
- PERFETTO_CONFIG.proto_library_visibility,
- ],
+ name = "trace_processor_proto",
deps = [
":protos_perfetto_common_protos",
":protos_perfetto_trace_processor_protos",
],
)
-# GN target: //protos/perfetto/common:cpp
-perfetto_cc_protocpp_library(
- name = "protos_perfetto_common_cpp",
+# GN target: [//protos/perfetto/trace_processor:source_set]
+perfetto_cc_proto_library(
+ name = "trace_processor_cc_proto",
deps = [
- ":protos_perfetto_common_protos",
+ ":trace_processor_proto",
],
)
-# GN target: //protos/perfetto/common:lite
-perfetto_cc_proto_library(
- name = "protos_perfetto_common_lite",
+# GN target: [//protos/perfetto/trace_processor:source_set]
+perfetto_java_proto_library(
+ name = "trace_processor_java_proto",
+ deps = [
+ ":trace_processor_proto",
+ ],
+)
+
+# GN target: [//protos/perfetto/trace_processor:source_set]
+perfetto_java_lite_proto_library(
+ name = "trace_processor_java_proto_lite",
+ deps = [
+ ":trace_processor_proto",
+ ],
+)
+
+# GN target: [//protos/perfetto/trace_processor:source_set]
+perfetto_py_proto_library(
+ name = "trace_processor_py_pb2",
+ deps = [
+ ":trace_processor_proto",
+ ],
+)
+
+# GN target: //protos/perfetto/common:cpp
+perfetto_cc_protocpp_library(
+ name = "protos_perfetto_common_cpp",
deps = [
":protos_perfetto_common_protos",
],
@@ -4223,6 +4032,7 @@
srcs = [
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
@@ -4823,14 +4633,6 @@
],
)
-# GN target: //protos/perfetto/trace_processor:lite
-perfetto_cc_proto_library(
- name = "protos_perfetto_trace_processor_lite",
- deps = [
- ":protos_perfetto_trace_processor_protos",
- ],
-)
-
# GN target: //protos/perfetto/trace_processor:metrics_impl_source_set
perfetto_proto_library(
name = "protos_perfetto_trace_processor_metrics_impl_protos",
diff --git a/BUILD.gn b/BUILD.gn
index cff5360..0a93081 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -39,6 +39,9 @@
if (enable_perfetto_traced_probes) {
all_targets += [ "src/traced/probes:traced_probes" ]
}
+ if (enable_perfetto_traced_relay) {
+ all_targets += [ "src/traced_relay:traced_relay" ]
+ }
}
if (enable_perfetto_trace_processor && enable_perfetto_trace_processor_sqlite) {
diff --git a/CHANGELOG b/CHANGELOG
index 9b27861..bec63e0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,29 @@
*
+v38.0 - 2023-10-10:
+ Tracing service and probes:
+ * Added capability to transfer and clear buffers on CLONE_SNAPSHOT.
+ * Added new service for relaying IPC messages from local producers to a
+ remote tracing instance.
+ Trace Processor:
+ * Added new PerfettoSQL syntax (INCLUDE PERFETTO MODULE) for including
+ tables/views/functions defined in SQL modules.
+ * Added new PerfettoSQL syntax (CREATE PERFETTO TABLE) for defining analytic
+ tables in SQL.
+ * Added new PerfettoSQL syntax (CREATE PERFETTO MACRO) for defining macros
+ in SQL.
+ * Added TO_REALTIME function to convert timestamps to the realtime clock.
+ * Added support for parsing binder_command and binder_return events.
+ UI:
+ * Added support for zooming when using deep-links.
+ * Added track for displaying screenshots in traces.
+ * Added support for displaying UTC timestamps.
+ * Added capability to list, search and debug plugin tracks.
+ * Added plugins with commands for pinning tracks for latency and large
+ screen debugging in Android.
+
+
v37.0 - 2023-08-10:
Tracing service and probes:
* Fixed a bug which would cause sessions cloned with CLONE_SNAPSHOT to not
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index b23aa06..ecc7252 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -266,6 +266,10 @@
# extremely low.
enable_perfetto_traced_probes = enable_perfetto_platform_services && !is_win
+ # The relay service is enabled when platform services are enabled.
+ # TODO(chinglinyu) check if we can enable on Windows.
+ enable_perfetto_traced_relay = enable_perfetto_platform_services && !is_win
+
# Whether info-level logging is enabled.
perfetto_verbose_logs_enabled =
!build_with_chromium || perfetto_force_dlog == "on"
diff --git a/include/perfetto/ext/base/file_utils.h b/include/perfetto/ext/base/file_utils.h
index d2412d0..42e9a28e 100644
--- a/include/perfetto/ext/base/file_utils.h
+++ b/include/perfetto/ext/base/file_utils.h
@@ -93,6 +93,11 @@
base::Status ListFilesRecursive(const std::string& dir_path,
std::vector<std::string>& output);
+// Sets |path|'s owner group to |group_name| and permission mode bits to
+// |mode_bits|.
+base::Status SetFilePermissions(const std::string& path,
+ const std::string& group_name,
+ const std::string& mode_bits);
} // namespace base
} // namespace perfetto
diff --git a/include/perfetto/ext/tracing/ipc/default_socket.h b/include/perfetto/ext/tracing/ipc/default_socket.h
index c173ebf..86d7b78 100644
--- a/include/perfetto/ext/tracing/ipc/default_socket.h
+++ b/include/perfetto/ext/tracing/ipc/default_socket.h
@@ -31,6 +31,11 @@
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_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
diff --git a/include/perfetto/protozero/message.h b/include/perfetto/protozero/message.h
index efcdf9f..760da54 100644
--- a/include/perfetto/protozero/message.h
+++ b/include/perfetto/protozero/message.h
@@ -65,6 +65,9 @@
// all nested messages) and seals the message. Returns the size of the message
// (and all nested sub-messages), without taking into account any chunking.
// Finalize is idempotent and can be called several times w/o side effects.
+ // Short messages may be compacted in memory into the size field, since their
+ // size can be represented with fewer than
+ // proto_utils::kMessageLengthFieldSize bytes.
uint32_t Finalize();
// Optional. If is_valid() == true, the corresponding memory region (its
@@ -78,11 +81,16 @@
// This is to deal with case of backfilling the size of a root (non-nested)
// message which is split into multiple chunks. Upon finalization only the
// partial size that lies in the last chunk has to be backfilled.
- void inc_size_already_written(uint32_t sz) { size_already_written_ += sz; }
+ void inc_size_already_written(uint32_t sz) {
+ PERFETTO_DCHECK(!is_finalized());
+ size_already_written_ += sz;
+ }
Message* nested_message() { return nested_message_; }
- bool is_finalized() const { return finalized_; }
+ bool is_finalized() const {
+ return message_state_ != MessageState::kNotFinalized;
+ }
#if PERFETTO_DCHECK_IS_ON()
void set_handle(MessageHandleBase* handle) { handle_ = handle; }
@@ -197,7 +205,7 @@
void EndNestedMessage();
void WriteToStream(const uint8_t* src_begin, const uint8_t* src_end) {
- PERFETTO_DCHECK(!finalized_);
+ PERFETTO_DCHECK(!is_finalized());
PERFETTO_DCHECK(src_begin <= src_end);
const uint32_t size = static_cast<uint32_t>(src_end - src_begin);
stream_writer_->WriteBytes(src_begin, size);
@@ -234,9 +242,19 @@
// See comment for inc_size_already_written().
uint32_t size_already_written_;
- // When true, no more changes to the message are allowed. This is to DCHECK
- // attempts of writing to a message which has been Finalize()-d.
- bool finalized_;
+ enum class MessageState : uint8_t {
+ // Message is still being written to.
+ kNotFinalized,
+ // Finalized, no more changes to the message are allowed. This is to DCHECK
+ // attempts of writing to a message which has been Finalize()-d.
+ kFinalized,
+ // Finalized, and additionally the message data has been partially or fully
+ // compacted into the last 3 bytes of `size_field_`. See the comment in
+ // Finalize().
+ kFinalizedWithCompaction,
+ };
+
+ MessageState message_state_;
#if PERFETTO_DCHECK_IS_ON()
// Current generation of message. Incremented on Reset.
diff --git a/include/perfetto/protozero/proto_utils.h b/include/perfetto/protozero/proto_utils.h
index 98c10d3..b961bee 100644
--- a/include/perfetto/protozero/proto_utils.h
+++ b/include/perfetto/protozero/proto_utils.h
@@ -120,6 +120,7 @@
// Maximum message size supported: 256 MiB (4 x 7-bit due to varint encoding).
constexpr size_t kMessageLengthFieldSize = 4;
constexpr size_t kMaxMessageLength = (1u << (kMessageLengthFieldSize * 7)) - 1;
+constexpr size_t kMaxOneByteMessageLength = (1 << 7) - 1;
// Field tag is encoded as 32-bit varint (5 bytes at most).
// Largest value of simple (not length-delimited) field is 64-bit varint
diff --git a/include/perfetto/protozero/scattered_stream_writer.h b/include/perfetto/protozero/scattered_stream_writer.h
index 40a1c66..96329e9 100644
--- a/include/perfetto/protozero/scattered_stream_writer.h
+++ b/include/perfetto/protozero/scattered_stream_writer.h
@@ -24,6 +24,7 @@
#include "perfetto/base/compiler.h"
#include "perfetto/base/export.h"
+#include "perfetto/base/logging.h"
#include "perfetto/protozero/contiguous_memory_range.h"
namespace protozero {
@@ -114,6 +115,20 @@
return begin;
}
+ // Shifts the previously written `size` bytes backwards in memory by `offset`
+ // bytes, moving the write pointer back accordingly. The shifted result must
+ // still be fully contained by the current range.
+ void Rewind(size_t size, size_t offset) {
+ uint8_t* src = write_ptr_ - size;
+ uint8_t* dst = src - offset;
+ PERFETTO_DCHECK(src >= cur_range_.begin);
+ PERFETTO_DCHECK(src + size <= cur_range_.end);
+ PERFETTO_DCHECK(dst >= cur_range_.begin);
+ PERFETTO_DCHECK(dst + size <= cur_range_.end);
+ memmove(dst, src, size);
+ write_ptr_ -= offset;
+ }
+
// Resets the buffer boundaries and the write pointer to the given |range|.
// Subsequent WriteByte(s) will write into |range|.
void Reset(ContiguousMemoryRange range);
diff --git a/include/perfetto/trace_processor/metatrace_config.h b/include/perfetto/trace_processor/metatrace_config.h
index b89a61e..adcfa41 100644
--- a/include/perfetto/trace_processor/metatrace_config.h
+++ b/include/perfetto/trace_processor/metatrace_config.h
@@ -16,25 +16,46 @@
#define INCLUDE_PERFETTO_TRACE_PROCESSOR_METATRACE_CONFIG_H_
#include <cstddef>
+#include <cstdint>
namespace perfetto {
namespace trace_processor {
namespace metatrace {
-enum MetatraceCategories {
- TOPLEVEL = 1 << 0,
- QUERY = 1 << 1,
- FUNCTION = 1 << 2,
+enum MetatraceCategories : uint32_t {
+ // Category for low-frequency events which provide a high-level timeline of
+ // SQL query execution.
+ QUERY_TIMELINE = 1 << 0,
+
+ // Category for high-frequency events which provide details about SQL query
+ // execution.
+ QUERY_DETAILED = 1 << 1,
+
+ // Category for high-frequency events which provide details about SQL function
+ // calls.
+ FUNCTION_CALL = 1 << 2,
+
+ // Category for high-frequency events which provide details about the columnar
+ // database operations.
DB = 1 << 3,
+ // Category for low-frequency events which provide a high-level timeline of
+ // SQL query execution.
+ API_TIMELINE = 1 << 4,
+
+ // Alias for turning off all other categories.
NONE = 0,
- ALL = TOPLEVEL | QUERY | FUNCTION | DB,
+
+ // Alias for turning on all other categories.
+ ALL = QUERY_TIMELINE | QUERY_DETAILED | FUNCTION_CALL | DB | API_TIMELINE,
};
struct MetatraceConfig {
MetatraceConfig();
- MetatraceCategories categories = MetatraceCategories::TOPLEVEL;
+ MetatraceCategories categories = static_cast<MetatraceCategories>(
+ MetatraceCategories::QUERY_TIMELINE | MetatraceCategories::API_TIMELINE);
+
// Requested buffer size. The implemenation may choose to allocate a larger
// buffer size for efficiency.
size_t override_buffer_size = 0;
diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h
index f0c515f..44503ba 100644
--- a/include/perfetto/tracing/tracing.h
+++ b/include/perfetto/tracing/tracing.h
@@ -210,30 +210,8 @@
// Start a new tracing session using the given tracing backend. Use
// |kUnspecifiedBackend| to select an available backend automatically.
- static inline std::unique_ptr<TracingSession> NewTrace(
- BackendType backend = kUnspecifiedBackend) PERFETTO_ALWAYS_INLINE {
- // This code is inlined to allow dead-code elimination for unused consumer
- // implementation. The logic behind it is the following:
- // Nothing other than the code below references the GetInstance() method
- // below. From a linker-graph viewpoint, those GetInstance() pull in many
- // other pieces of the codebase (ConsumerOnlySystemTracingBackend pulls
- // ConsumerIPCClient). Due to the inline, the compiler can see through the
- // code and realize that some branches are always not taken. When that
- // happens, no reference to the backends' GetInstance() is emitted and that
- // allows the linker GC to get rid of the entire set of dependencies.
- TracingConsumerBackend* (*system_backend_factory)();
- system_backend_factory = nullptr;
- // In case PERFETTO_IPC is disabled, a fake system backend is used, which
- // always panics. NewTrace(kSystemBackend) should fail if PERFETTO_IPC is
- // diabled, not panic.
-#if PERFETTO_BUILDFLAG(PERFETTO_IPC)
- if (backend & kSystemBackend) {
- system_backend_factory =
- &internal::SystemConsumerTracingBackend::GetInstance;
- }
-#endif
- return NewTraceInternal(backend, system_backend_factory);
- }
+ static PERFETTO_ALWAYS_INLINE inline std::unique_ptr<TracingSession> NewTrace(
+ BackendType backend = kUnspecifiedBackend);
// Shut down Perfetto, releasing any allocated OS resources (threads, files,
// sockets, etc.). Note that Perfetto cannot be reinitialized again in the
@@ -514,6 +492,31 @@
virtual void AbortBlocking() = 0;
};
+PERFETTO_ALWAYS_INLINE inline std::unique_ptr<TracingSession>
+Tracing::NewTrace(BackendType backend) {
+ // This code is inlined to allow dead-code elimination for unused consumer
+ // implementation. The logic behind it is the following:
+ // Nothing other than the code below references the GetInstance() method
+ // below. From a linker-graph viewpoint, those GetInstance() pull in many
+ // other pieces of the codebase (ConsumerOnlySystemTracingBackend pulls
+ // ConsumerIPCClient). Due to the inline, the compiler can see through the
+ // code and realize that some branches are always not taken. When that
+ // happens, no reference to the backends' GetInstance() is emitted and that
+ // allows the linker GC to get rid of the entire set of dependencies.
+ TracingConsumerBackend* (*system_backend_factory)();
+ system_backend_factory = nullptr;
+ // In case PERFETTO_IPC is disabled, a fake system backend is used, which
+ // always panics. NewTrace(kSystemBackend) should fail if PERFETTO_IPC is
+ // diabled, not panic.
+#if PERFETTO_BUILDFLAG(PERFETTO_IPC)
+ if (backend & kSystemBackend) {
+ system_backend_factory =
+ &internal::SystemConsumerTracingBackend::GetInstance;
+ }
+#endif
+ return NewTraceInternal(backend, system_backend_factory);
+}
+
} // namespace perfetto
#endif // INCLUDE_PERFETTO_TRACING_TRACING_H_
diff --git a/protos/perfetto/ipc/wire_protocol.proto b/protos/perfetto/ipc/wire_protocol.proto
index 038d4bb..8117316 100644
--- a/protos/perfetto/ipc/wire_protocol.proto
+++ b/protos/perfetto/ipc/wire_protocol.proto
@@ -65,6 +65,17 @@
// Host -> Client.
message RequestError { optional string error = 1; }
+ // Client (relay service) -> Host. This is generated by the relay service to
+ // fill the producer identity in the guest. This message is sent to the host
+ // service *before* any IPCFrame is from a local producer is relayed. This is
+ // accepted only on AF_VSOCK and AF_INET sockets, where we cannot validate the
+ // endpoont of the connection. for AF_UNIX sockets, this is ignored and traced
+ // uses instead the SO_PEERCRED.
+ message SetPeerIdentity {
+ optional int32 pid = 1;
+ optional int32 uid = 2;
+ }
+
// The client is expected to send requests with monotonically increasing
// request_id. The host will match the request_id sent from the client.
// In the case of a Streaming response (has_more = true) the host will send
@@ -77,6 +88,7 @@
InvokeMethod msg_invoke_method = 5;
InvokeMethodReply msg_invoke_method_reply = 6;
RequestError msg_request_error = 7;
+ SetPeerIdentity set_peer_identity = 8;
}
// Used only in unittests to generate a parsable message of arbitrary size.
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 74e7a72..a2ae264 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -22,6 +22,7 @@
sources = [
"android_blocking_call.proto",
"android_blocking_calls_cuj_metric.proto",
+ "android_boot.proto",
"android_frame_timeline_metric.proto",
"android_sysui_notifications_blocking_calls_metric.proto",
"android_trusty_workqueues.proto",
diff --git a/protos/perfetto/metrics/android/android_boot.proto b/protos/perfetto/metrics/android/android_boot.proto
new file mode 100644
index 0000000..820b4e0
--- /dev/null
+++ b/protos/perfetto/metrics/android/android_boot.proto
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// This metric computes how much time processes spend in UNINTERRUPTIBLE_SLEEP state
+message ProcessStateDurations {
+ optional int64 total_dur = 2;
+ optional int64 uninterruptible_sleep_dur = 3;
+}
+
+message AndroidBootMetric {
+ optional ProcessStateDurations system_server_durations = 1;
+ optional ProcessStateDurations systemui_durations = 2;
+ optional ProcessStateDurations launcher_durations = 3;
+ optional ProcessStateDurations gms_durations = 4;
+}
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 94c7666..95fd3b4 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -18,6 +18,7 @@
package perfetto.protos;
+import "protos/perfetto/metrics/android/android_boot.proto";
import "protos/perfetto/metrics/android/android_frame_timeline_metric.proto";
import "protos/perfetto/metrics/android/anr_metric.proto";
import "protos/perfetto/metrics/android/batt_metric.proto";
@@ -108,7 +109,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 57
+// Next id: 58
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -263,6 +264,8 @@
// Aggregated Android Monitor Contention metrics
optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = 56;
+ optional AndroidBootMetric android_boot = 57;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 70a1f52..e940682 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -105,6 +105,23 @@
// End of protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto
+// Begin of protos/perfetto/metrics/android/android_boot.proto
+
+// This metric computes how much time processes spend in UNINTERRUPTIBLE_SLEEP state
+message ProcessStateDurations {
+ optional int64 total_dur = 2;
+ optional int64 uninterruptible_sleep_dur = 3;
+}
+
+message AndroidBootMetric {
+ optional ProcessStateDurations system_server_durations = 1;
+ optional ProcessStateDurations systemui_durations = 2;
+ optional ProcessStateDurations launcher_durations = 3;
+ optional ProcessStateDurations gms_durations = 4;
+}
+
+// End of protos/perfetto/metrics/android/android_boot.proto
+
// Begin of protos/perfetto/metrics/android/android_frame_timeline_metric.proto
message AndroidFrameTimelineMetric {
@@ -2266,7 +2283,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 57
+// Next id: 58
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -2421,6 +2438,8 @@
// Aggregated Android Monitor Contention metrics
optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = 56;
+ optional AndroidBootMetric android_boot = 57;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/trace_processor/metatrace_categories.proto b/protos/perfetto/trace_processor/metatrace_categories.proto
index b581ab1..6b9ee23 100644
--- a/protos/perfetto/trace_processor/metatrace_categories.proto
+++ b/protos/perfetto/trace_processor/metatrace_categories.proto
@@ -22,16 +22,17 @@
// Keep in sync with TraceProcessor::MetatraceCategories.
enum MetatraceCategories {
// 1 << 0.
- TOPLEVEL = 1;
+ QUERY_TIMELINE = 1;
// 1 << 1.
- QUERY = 2;
+ QUERY_DETAILED = 2;
// 1 << 2.
- FUNCTION = 4;
+ FUNCTION_CALL = 4;
// 1 << 3.
DB = 8;
+ // 1 << 4.
+ API_TIMELINE = 16;
// Aliases for common subsets.
NONE = 0;
- // TOPLEVEL | QUERY | FUNCTION
- ALL = 15;
+ ALL = 31;
}
diff --git a/python/perfetto/__init__.py b/python/perfetto/__init__.py
deleted file mode 100644
index c569069..0000000
--- a/python/perfetto/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-try:
- __import__('pkg_resources').declare_namespace(__name__)
-except ImportError:
- __path__ = __import__('pkgutil').extend_path(__path__, __name__)
\ No newline at end of file
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index 8d88a66..b31da6e 100755
--- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py
+++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACE_PROCESSOR_SHELL_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'trace_processor_shell',
'file_size':
- 9551608,
+ 9682976,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/trace_processor_shell',
'sha256':
- 'f3def324604f551fdf7df088363aebf74de573fdaa02796fd994f1863ad58463',
+ '74b097836f16d788edce11bfda46f52e6499d8ec546d10f8dbab182612407b3b',
'platform':
'darwin',
'machine': ['x86_64']
@@ -19,11 +19,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 8064168,
+ 8180008,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/trace_processor_shell',
'sha256':
- 'f34a2cfb4f93579452bbaf4f47c9b0fe475394d55316c9626949760c206ec801',
+ '9a96a2f9ef81f210fcba4a08b21db6f2f57fb1d325e91924043ae066327c29a8',
'platform':
'darwin',
'machine': ['arm64']
@@ -33,11 +33,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 9415488,
+ 9533320,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/trace_processor_shell',
'sha256':
- '86d133fc2fe0204693d2b22e9287363cb44bf76794b0091d4d0bb3388b7cd8d5',
+ 'ee0ccae766aad09f0135efa83cc3a1cd78bd0e86824a7b1245e013d32e61d820',
'platform':
'linux',
'machine': ['x86_64']
@@ -47,11 +47,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 6880412,
+ 6963584,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/trace_processor_shell',
'sha256':
- '21fd4b2ad68b1f9829cee72d3905b1712cbfa5e169e89b89cdd0a3a4b1682e7d',
+ '2c69d2016dd18b6d42bf6c2a5b4398e29e4fd294950882387942272dd25a045a',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 8843272,
+ 8950112,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/trace_processor_shell',
'sha256':
- '97a6fb150fd6655f03394d8ad166fd2041a1b4ca56a9e7ce5b35902ff64e1a35',
+ 'a10a7a9a6614461beb1f32ee16da3290e2770bb6f3a506269defbdbf7e8803af',
'platform':
'linux',
'machine': ['aarch64']
@@ -75,55 +75,55 @@
'file_name':
'trace_processor_shell',
'file_size':
- 6497440,
+ 6580152,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/trace_processor_shell',
'sha256':
- '6018ce36314a75693c5e5c2a69a3aacc9848385bd3ab9121e23de70df5188e1e'
+ '994a038b932accf5796550207a4e7d80c7612fd25e6eee414324e7e4f3ae14f2'
}, {
'arch':
'android-arm64',
'file_name':
'trace_processor_shell',
'file_size':
- 8019768,
+ 8115560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/trace_processor_shell',
'sha256':
- '4e36255ebcd5e19fe26def6578cc604d636df9c4fa335ec5901e6cd9aba92ac3'
+ '42426b12ad60894aee37e502e4d023cfded53e706d990abf5c70c4556c2b73ff'
}, {
'arch':
'android-x86',
'file_name':
'trace_processor_shell',
'file_size':
- 8897636,
+ 9009020,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/trace_processor_shell',
'sha256':
- 'f21573cc77603150e461402c6f032b7ff8f103debcd9d9b2093f521a69f581f7'
+ 'af982ad7897d8cafb29a9f1303e32df20486a92eb07930db8b1898a75e84a667'
}, {
'arch':
'android-x64',
'file_name':
'trace_processor_shell',
'file_size':
- 9162616,
+ 9270696,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/trace_processor_shell',
'sha256':
- '2cc5a02270a28db52aa17bfafcf215e3d754a936de595186a7152535ab28531b'
+ '081be392ddccdf37da80dd888277ea9215cfa8d05ddf6f90d9bff13a0a72f672'
}, {
'arch':
'windows-amd64',
'file_name':
'trace_processor_shell.exe',
'file_size':
- 8790016,
+ 8898560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/windows-amd64/trace_processor_shell.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/trace_processor_shell.exe',
'sha256':
- 'e3193141bcb8bcc35b47c3ee8126d1f3ebe1b54a9ecde59ade9db4f22c6f77d6',
+ '2e66e5b6ab9c0f7ecad98c3b5b822133c9aa34f137b4007c228c78672fdea5a7',
'platform':
'win32',
'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index 81c904b..16ce884 100755
--- a/python/perfetto/prebuilts/manifests/tracebox.py
+++ b/python/perfetto/prebuilts/manifests/tracebox.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACEBOX_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'tracebox',
'file_size':
- 1481952,
+ 1498560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/tracebox',
'sha256':
- '81787717e819e1412609a7c67e561f643516cabd897d6b91e1f5cf572a40521a',
+ 'b760c7ed682d23f8d268174a939f1b8cb130ffb0d52f42b4cc4499a25423e782',
'platform':
'darwin',
'machine': ['x86_64']
@@ -19,11 +19,11 @@
'file_name':
'tracebox',
'file_size':
- 1359320,
+ 1376008,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/tracebox',
'sha256':
- '5cd27e847e1e88493d136a2a803fd644cffc673d12685442a1df00c928170429',
+ '2835127a5fc42e501e29a685a1cbcd98c26f977ee845bb1bd60a43008fd48161',
'platform':
'darwin',
'machine': ['arm64']
@@ -33,11 +33,11 @@
'file_name':
'tracebox',
'file_size':
- 2200152,
+ 2212088,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/tracebox',
'sha256':
- 'de4f883b1a3509ad9533c3057787a00cf8e68cbb1b5b6bde385caee3b2433574',
+ '0b4a61a3e45f4e1b6111ca0b440f9e4a0b0726df912542373f69b6821a3113bc',
'platform':
'linux',
'machine': ['x86_64']
@@ -47,11 +47,11 @@
'file_name':
'tracebox',
'file_size':
- 1317900,
+ 1327204,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/tracebox',
'sha256':
- 'c404c93a16b3808b9b668f2ef071faecbde1a8c012be670a595716d2a730e5e9',
+ '43c20d80e5b40cebe5b26579319be6d57796b04155ffc90f133b6a13c2de25ab',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
'file_name':
'tracebox',
'file_size':
- 2128352,
+ 2139928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/tracebox',
'sha256':
- '851c82a00e84b492627503ca2c46afcbd3ec1770c06b002134602541e6006b0c',
+ '261cd7912dd69e8d82ce61b66a78f098eba46f6f492f6ec6bf73124d40a9a202',
'platform':
'linux',
'machine': ['aarch64']
@@ -75,42 +75,42 @@
'file_name':
'tracebox',
'file_size':
- 1198036,
+ 1202132,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/tracebox',
'sha256':
- 'fac9f7cbe888139c569068d973be79f4d820a8f323ad04cdb9c5c3be6faba067'
+ '43874c4d187e3da5820ae3988d16608a8bf89d04282b8ab3e1486c6e57cd891f'
}, {
'arch':
'android-arm64',
'file_name':
'tracebox',
'file_size':
- 1813160,
+ 1825448,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/tracebox',
'sha256':
- '5c344c218c440c678fa56c8ce4d2f9c26efc300ebf268afd6e68a5775628cc0c'
+ '9264d8f23ea3988f2952696668d3d572669134e4ca5b3e25a3209d3bfdb7d015'
}, {
'arch':
'android-x86',
'file_name':
'tracebox',
'file_size':
- 1812396,
+ 1820588,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/tracebox',
'sha256':
- 'cd44b4ab3a1579dd0082f6f0fa4d7f710b9d056b44606cb247d41524be6d576e'
+ 'd591e903b1e2b0e4b270f0510c24992aac68167c80e61450ebf6c36cc780cd21'
}, {
'arch':
'android-x64',
'file_name':
'tracebox',
'file_size':
- 2091688,
+ 2108072,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/tracebox',
'sha256':
- '6d614a3d4e77c7fb0712bf13d7a2c86a59b0945a043fefb70ffc70854551fca0'
+ 'd3e4278e17764b605236c568202eb8d94f3f7e7593b0be9eba09f670a987acba'
}]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index de17e0d..7065064 100755
--- a/python/perfetto/prebuilts/manifests/traceconv.py
+++ b/python/perfetto/prebuilts/manifests/traceconv.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 8758200,
+ 8889568,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
'sha256':
- 'b6eb3552efc97b15b1cb7a746b90f77a4ac781566faa4ffa84e1785f5d90b38b',
+ 'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -19,11 +19,11 @@
'file_name':
'traceconv',
'file_size':
- 7332088,
+ 7447928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
'sha256':
- '585ee448560fa89f559566cdf41dcc6677a28c74ea3b5bae3cfe31cde813578c',
+ '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
'platform':
'darwin',
'machine': ['arm64']
@@ -33,11 +33,11 @@
'file_name':
'traceconv',
'file_size':
- 8515568,
+ 8633416,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
'sha256':
- 'bff2c0c525193fc3555725b5767a30bc2b36d7d84b849b7ce41f7abb349f21be',
+ 'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
'platform':
'linux',
'machine': ['x86_64']
@@ -47,11 +47,11 @@
'file_name':
'traceconv',
'file_size':
- 6421928,
+ 6505268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
'sha256':
- '566298db777a31f3eabd0e6b17e1b0638875725be767333308f02a7fb7d4e36c',
+ '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
'file_name':
'traceconv',
'file_size':
- 8001776,
+ 8108432,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
'sha256':
- 'ccf81438df9ed30f88e640f62938b6334698b9158364db43252d7183136e137d',
+ '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
'platform':
'linux',
'machine': ['aarch64']
@@ -75,55 +75,55 @@
'file_name':
'traceconv',
'file_size':
- 6021600,
+ 6096120,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
'sha256':
- 'edae3606563616fa304e3240a8426db3dfd7d12ad28bb879e7d482af357af7f2'
+ 'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 7305880,
+ 7401672,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
'sha256':
- '1a0ef7601a6d2f77f9a04d5bb8c092f5a536764a8e9a99325fff6b81c2879d1e'
+ '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 8118692,
+ 8238268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
'sha256':
- '6f9b53427d367e8582b12763d27e83217c9e744f947f58acc0b404b6ce5103e9'
+ '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 8288976,
+ 8397056,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
'sha256':
- '5c843ddb151f5aa32288814cb190a879f15e6da63d642d3ded0f15954f480b5f'
+ 'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 7756800,
+ 7867904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
'sha256':
- 'b07849cd60872279dda68d4b8ec173af01e0cbb1692eb5473ef230ac89198f2d',
+ '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
'platform':
'win32',
'machine': ['amd64']
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index ad0d5da..926dee9 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index eca1b1a..33ca764 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/android_internal/statsd.cc b/src/android_internal/statsd.cc
index e69de29..3df3986 100644
--- a/src/android_internal/statsd.cc
+++ b/src/android_internal/statsd.cc
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#include "statsd.h"
+
+#include <binder/ProcessState.h>
+#include <stats_subscription.h>
+
+namespace perfetto {
+namespace android_internal {
+
+int32_t AddAtomSubscription(const uint8_t* subscription_config,
+ size_t num_bytes,
+ const AtomCallback callback,
+ void* cookie) {
+ // Although the binder messages we use are one-way we pass an interface that
+ // statsd uses to talk back to us. For this to work we need the some binder
+ // threads listening to the for these messages. To handle this we start a
+ // thread pool if it hasn't been started already:
+ android::ProcessState::self()->startThreadPool();
+
+ auto c = reinterpret_cast<AStatsManager_SubscriptionCallback>(callback);
+ return AStatsManager_addSubscription(subscription_config, num_bytes, c,
+ cookie);
+}
+
+void RemoveAtomSubscription(int32_t subscription_id) {
+ AStatsManager_removeSubscription(subscription_id);
+}
+
+void FlushAtomSubscription(int32_t subscription_id) {
+ AStatsManager_flushSubscription(subscription_id);
+}
+
+} // namespace android_internal
+} // namespace perfetto
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index e7d4c94..0969515 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -26,11 +26,13 @@
#include <vector>
#include "perfetto/base/build_config.h"
+#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/platform_handle.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/platform.h"
#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/utils.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
@@ -43,6 +45,17 @@
#include <unistd.h>
#endif
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+#define PERFETTO_SET_FILE_PERMISSIONS
+#include <fcntl.h>
+#include <grp.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
namespace perfetto {
namespace base {
namespace {
@@ -346,5 +359,54 @@
return filename.substr(ext_idx);
}
+base::Status SetFilePermissions(const std::string& file_path,
+ const std::string& group_name_or_id,
+ const std::string& mode_bits) {
+#ifdef PERFETTO_SET_FILE_PERMISSIONS
+ PERFETTO_CHECK(!file_path.empty());
+ PERFETTO_CHECK(!group_name_or_id.empty());
+
+ // Default |group_id| to -1 for not changing the group ownership.
+ gid_t group_id = static_cast<gid_t>(-1);
+ auto maybe_group_id = base::StringToUInt32(group_name_or_id);
+ if (maybe_group_id) { // A numerical group ID.
+ group_id = *maybe_group_id;
+ } else { // A group name.
+ struct group* file_group = nullptr;
+ // Query the group ID of |group|.
+ do {
+ file_group = getgrnam(group_name_or_id.c_str());
+ } while (file_group == nullptr && errno == EINTR);
+ if (file_group == nullptr) {
+ return base::ErrStatus("Failed to get group information of %s ",
+ group_name_or_id.c_str());
+ }
+ group_id = file_group->gr_gid;
+ }
+
+ if (PERFETTO_EINTR(chown(file_path.c_str(), geteuid(), group_id))) {
+ return base::ErrStatus("Failed to chown %s ", file_path.c_str());
+ }
+
+ // |mode| accepts values like "0660" as "rw-rw----" mode bits.
+ auto mode_value = base::StringToInt32(mode_bits, 8);
+ if (!(mode_bits.size() == 4 && mode_value.has_value())) {
+ return base::ErrStatus(
+ "The chmod mode bits must be a 4-digit octal number, e.g. 0660");
+ }
+ if (PERFETTO_EINTR(
+ chmod(file_path.c_str(), static_cast<mode_t>(mode_value.value())))) {
+ return base::ErrStatus("Failed to chmod %s", file_path.c_str());
+ }
+ return base::OkStatus();
+#else
+ base::ignore_result(file_path);
+ base::ignore_result(group_name_or_id);
+ base::ignore_result(mode_bits);
+ return base::ErrStatus(
+ "Setting file permissions is not supported on this platform");
+#endif
+}
+
} // namespace base
} // namespace perfetto
diff --git a/src/ipc/BUILD.gn b/src/ipc/BUILD.gn
index 39843f1..1cd1a8d 100644
--- a/src/ipc/BUILD.gn
+++ b/src/ipc/BUILD.gn
@@ -141,6 +141,7 @@
perfetto_component("perfetto_ipc") {
public_deps = [
":client",
+ ":common",
":host",
"../../gn:default_deps",
]
diff --git a/src/ipc/host_impl.cc b/src/ipc/host_impl.cc
index 0b852c0..029b450 100644
--- a/src/ipc/host_impl.cc
+++ b/src/ipc/host_impl.cc
@@ -21,9 +21,12 @@
#include <utility>
#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
#include "perfetto/base/task_runner.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/crash_keys.h"
+#include "perfetto/ext/base/sys_types.h"
+#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/ipc/service.h"
#include "perfetto/ext/ipc/service_descriptor.h"
@@ -41,8 +44,9 @@
kUseTCPSocket ? base::SockFamily::kInet : base::SockFamily::kUnix;
base::CrashKey g_crash_key_uid("ipc_uid");
+} // namespace
-uid_t GetPosixPeerUid(base::UnixSocket* sock) {
+uid_t HostImpl::ClientConnection::GetPosixPeerUid() const {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
@@ -50,22 +54,23 @@
return sock->peer_uid_posix();
#endif
- // Unsupported. Must be != kInvalidUid or the PacketValidator will fail.
- base::ignore_result(sock);
+ // For non-unix sockets, check if the UID is set in OnSetPeerIdentity().
+ if (uid_override != base::kInvalidUid)
+ return uid_override;
+ // Must be != kInvalidUid or the PacketValidator will fail.
return 0;
}
-pid_t GetLinuxPeerPid(base::UnixSocket* sock) {
+pid_t HostImpl::ClientConnection::GetLinuxPeerPid() const {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
if (sock->family() == base::SockFamily::kUnix)
return sock->peer_pid_linux();
#endif
- base::ignore_result(sock);
- return base::kInvalidPid; // Unsupported.
-}
-} // namespace
+ // For non-unix sockets, return the PID set in OnSetPeerIdentity().
+ return pid_override;
+}
// static
std::unique_ptr<Host> Host::CreateInstance(const char* socket_name,
@@ -179,7 +184,7 @@
ClientConnection* client = it->second;
BufferedFrameDeserializer& frame_deserializer = client->frame_deserializer;
- auto peer_uid = GetPosixPeerUid(client->sock.get());
+ auto peer_uid = client->GetPosixPeerUid();
auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
size_t rsize;
@@ -209,6 +214,8 @@
return OnBindService(client, req_frame);
if (req_frame.has_msg_invoke_method())
return OnInvokeMethod(client, req_frame);
+ if (req_frame.has_set_peer_identity())
+ return OnSetPeerIdentity(client, req_frame);
PERFETTO_DLOG("Received invalid RPC frame from client %" PRIu64, client->id);
Frame reply_frame;
@@ -276,16 +283,35 @@
});
}
- auto peer_uid = GetPosixPeerUid(client->sock.get());
+ auto peer_uid = client->GetPosixPeerUid();
auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
service->client_info_ =
- ClientInfo(client->id, peer_uid, GetLinuxPeerPid(client->sock.get()));
+ ClientInfo(client->id, peer_uid, client->GetLinuxPeerPid());
service->received_fd_ = &client->received_fd;
method.invoker(service, *decoded_req_args, std::move(deferred_reply));
service->received_fd_ = nullptr;
service->client_info_ = ClientInfo();
}
+void HostImpl::OnSetPeerIdentity(ClientConnection* client,
+ const Frame& req_frame) {
+ if (client->sock->family() == base::SockFamily::kUnix) {
+ PERFETTO_DLOG("SetPeerIdentity is ignored for unix socket connections.");
+ return;
+ }
+
+ // This is can only be set once by the relay service.
+ if (client->pid_override != base::kInvalidPid ||
+ client->uid_override != base::kInvalidUid) {
+ PERFETTO_DLOG("Already received SetPeerIdentity.");
+ return;
+ }
+
+ client->pid_override = req_frame.set_peer_identity().pid();
+ client->uid_override =
+ static_cast<uid_t>(req_frame.set_peer_identity().uid());
+}
+
void HostImpl::ReplyToMethodInvocation(ClientID client_id,
RequestID request_id,
AsyncResult<ProtoMessage> reply) {
@@ -312,7 +338,7 @@
// static
void HostImpl::SendFrame(ClientConnection* client, const Frame& frame, int fd) {
- auto peer_uid = GetPosixPeerUid(client->sock.get());
+ auto peer_uid = client->GetPosixPeerUid();
auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
std::string buf = BufferedFrameDeserializer::Serialize(frame);
@@ -345,10 +371,11 @@
auto it = clients_by_socket_.find(sock);
if (it == clients_by_socket_.end())
return;
- ClientID client_id = it->second->id;
+ auto* client = it->second;
+ ClientID client_id = client->id;
- ClientInfo client_info(client_id, GetPosixPeerUid(sock),
- GetLinuxPeerPid(sock));
+ ClientInfo client_info(client_id, client->GetPosixPeerUid(),
+ client->GetLinuxPeerPid());
clients_by_socket_.erase(it);
PERFETTO_DCHECK(clients_.count(client_id));
clients_.erase(client_id);
diff --git a/src/ipc/host_impl.h b/src/ipc/host_impl.h
index 788b81a..8738459 100644
--- a/src/ipc/host_impl.h
+++ b/src/ipc/host_impl.h
@@ -24,6 +24,7 @@
#include "perfetto/base/task_runner.h"
#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/sys_types.h"
#include "perfetto/ext/base/thread_checker.h"
#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/ext/ipc/deferred.h"
@@ -66,6 +67,14 @@
BufferedFrameDeserializer frame_deserializer;
base::ScopedFile received_fd;
std::function<bool(int)> send_fd_cb_fuchsia;
+ // Peer identity set using IPCFrame sent by the client. These 2 fields
+ // should be used only for non-AF_UNIX connections AF_UNIX connections
+ // should only rely on the peer identity obtained from the socket.
+ uid_t uid_override = base::kInvalidUid;
+ pid_t pid_override = base::kInvalidPid;
+
+ pid_t GetLinuxPeerPid() const;
+ uid_t GetPosixPeerUid() const;
};
struct ExposedService {
ExposedService(ServiceID, const std::string&, std::unique_ptr<Service>);
@@ -85,6 +94,8 @@
void OnReceivedFrame(ClientConnection*, const Frame&);
void OnBindService(ClientConnection*, const Frame&);
void OnInvokeMethod(ClientConnection*, const Frame&);
+ void OnSetPeerIdentity(ClientConnection*, const Frame&);
+
void ReplyToMethodInvocation(ClientID, RequestID, AsyncResult<ProtoMessage>);
const ExposedService* GetServiceByName(const std::string&);
diff --git a/src/protozero/filtering/message_tokenizer_unittest.cc b/src/protozero/filtering/message_tokenizer_unittest.cc
index 666c4cf..6808694 100644
--- a/src/protozero/filtering/message_tokenizer_unittest.cc
+++ b/src/protozero/filtering/message_tokenizer_unittest.cc
@@ -131,7 +131,7 @@
EXPECT_THAT(
tokens,
ElementsAre(Token{1, ProtoWireType::kVarInt, 101u},
- Token{2, ProtoWireType::kLengthDelimited, 24u},
+ Token{2, ProtoWireType::kLengthDelimited, 21u},
Token{3, ProtoWireType::kVarInt, 103u},
Token{4, ProtoWireType::kFixed32, 104u},
Token{5, ProtoWireType::kLengthDelimited, 7},
diff --git a/src/protozero/message.cc b/src/protozero/message.cc
index aa5954e..d760e27 100644
--- a/src/protozero/message.cc
+++ b/src/protozero/message.cc
@@ -34,6 +34,8 @@
namespace {
+constexpr int kBytesToCompact = proto_utils::kMessageLengthFieldSize - 1u;
+
#if PERFETTO_DCHECK_IS_ON()
std::atomic<uint32_t> g_generation;
#endif
@@ -59,7 +61,7 @@
size_field_ = nullptr;
size_already_written_ = 0;
nested_message_ = nullptr;
- finalized_ = false;
+ message_state_ = MessageState::kNotFinalized;
#if PERFETTO_DCHECK_IS_ON()
handle_ = nullptr;
generation_ = g_generation.fetch_add(1, std::memory_order_relaxed);
@@ -118,7 +120,7 @@
}
uint32_t Message::Finalize() {
- if (finalized_)
+ if (is_finalized())
return size_;
if (nested_message_)
@@ -127,15 +129,54 @@
// Write the length of the nested message a posteriori, using a leading-zero
// redundant varint encoding.
if (size_field_) {
- PERFETTO_DCHECK(!finalized_);
+ PERFETTO_DCHECK(!is_finalized());
PERFETTO_DCHECK(size_ < proto_utils::kMaxMessageLength);
PERFETTO_DCHECK(size_ >= size_already_written_);
- proto_utils::WriteRedundantVarInt(size_ - size_already_written_,
- size_field_);
+ //
+ // Normally the size of a protozero message is written with 4 bytes just
+ // before the contents of the message itself:
+ //
+ // size message data
+ // [aa bb cc dd] [01 23 45 67 ...]
+ //
+ // We always reserve 4 bytes for the size, because the real size of the
+ // message isn't known until the call to Finalize(). This is possible
+ // because we can use leading zero redundant varint coding to expand any
+ // size smaller than 256 MiB to 4 bytes.
+ //
+ // However this is wasteful for short, frequently written messages, so the
+ // code below uses a 1 byte size field when possible. This is done by
+ // shifting the already-written data (which should still be in the cache)
+ // back by 3 bytes, resulting in this layout:
+ //
+ // size message data
+ // [aa] [01 23 45 67 ...]
+ //
+ // We can only do this optimization if the message is contained in a single
+ // chunk (since we can't modify previously committed chunks). We can check
+ // this by verifying that the size field is immediately before the message
+ // in memory and is fully contained by the current chunk.
+ //
+ if (PERFETTO_LIKELY(size_already_written_ == 0 &&
+ size_ <= proto_utils::kMaxOneByteMessageLength &&
+ size_field_ ==
+ stream_writer_->write_ptr() - size_ -
+ proto_utils::kMessageLengthFieldSize &&
+ size_field_ >= stream_writer_->cur_range().begin)) {
+ stream_writer_->Rewind(size_, kBytesToCompact);
+ PERFETTO_DCHECK(size_field_ == stream_writer_->write_ptr() - size_ - 1u);
+ *size_field_ = static_cast<uint8_t>(size_);
+ message_state_ = MessageState::kFinalizedWithCompaction;
+ } else {
+ proto_utils::WriteRedundantVarInt(size_ - size_already_written_,
+ size_field_);
+ message_state_ = MessageState::kFinalized;
+ }
size_field_ = nullptr;
+ } else {
+ message_state_ = MessageState::kFinalized;
}
- finalized_ = true;
#if PERFETTO_DCHECK_IS_ON()
if (handle_)
handle_->reset_message();
@@ -170,6 +211,10 @@
void Message::EndNestedMessage() {
size_ += nested_message_->Finalize();
+ if (nested_message_->message_state_ ==
+ MessageState::kFinalizedWithCompaction) {
+ size_ -= kBytesToCompact;
+ }
arena_->DeleteLastMessage(nested_message_);
nested_message_ = nullptr;
}
diff --git a/src/protozero/message_unittest.cc b/src/protozero/message_unittest.cc
index 985ed5c..26ad5f0 100644
--- a/src/protozero/message_unittest.cc
+++ b/src/protozero/message_unittest.cc
@@ -23,6 +23,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/proto_utils.h"
#include "perfetto/protozero/root_message.h"
#include "src/base/test/utils.h"
#include "src/protozero/test/fake_scattered_buffer.h"
@@ -51,11 +52,7 @@
class MessageTest : public ::testing::Test {
public:
- void SetUp() override {
- buffer_.reset(new FakeScatteredBuffer(kChunkSize));
- stream_writer_.reset(new ScatteredStreamWriter(buffer_.get()));
- readback_pos_ = 0;
- }
+ void SetUp() override { SetChunkSize(kChunkSize); }
void TearDown() override {
// Check that none of the messages created by the text fixtures below did
@@ -77,6 +74,13 @@
void ResetMessage(FakeRootMessage* msg) { msg->Reset(stream_writer_.get()); }
+ void SetChunkSize(size_t size) {
+ buffer_.reset(new FakeScatteredBuffer(size));
+ stream_writer_.reset(new ScatteredStreamWriter(buffer_.get()));
+ chunk_size_ = size;
+ readback_pos_ = 0;
+ }
+
FakeRootMessage* NewMessage() {
std::unique_ptr<uint8_t[]> mem(
new uint8_t[sizeof(kStartWatermark) + sizeof(FakeRootMessage) +
@@ -92,10 +96,19 @@
return msg;
}
+ FakeRootMessage* NewMessageWithSizeField() {
+ FakeRootMessage* msg = NewMessage();
+ uint8_t* size_field =
+ stream_writer_->ReserveBytes(proto_utils::kMessageLengthFieldSize);
+ memset(size_field, 0u, proto_utils::kMessageLengthFieldSize);
+ msg->set_size_field(size_field);
+ return msg;
+ }
+
size_t GetNumSerializedBytes() {
if (buffer_->chunks().empty())
return 0;
- return buffer_->chunks().size() * kChunkSize -
+ return buffer_->chunks().size() * chunk_size_ -
stream_writer_->bytes_available();
}
@@ -107,18 +120,23 @@
static void BuildNestedMessages(Message* msg,
uint32_t max_depth,
+ bool empty = false,
uint32_t depth = 0) {
- for (uint32_t i = 1; i <= 128; ++i)
- msg->AppendBytes(i, kTestBytes, sizeof(kTestBytes));
+ if (!empty) {
+ for (uint32_t i = 1; i <= 128; ++i)
+ msg->AppendBytes(i, kTestBytes, sizeof(kTestBytes));
+ }
if (depth < max_depth) {
auto* nested_msg =
msg->BeginNestedMessage<FakeChildMessage>(1 + depth * 10);
- BuildNestedMessages(nested_msg, max_depth, depth + 1);
+ BuildNestedMessages(nested_msg, max_depth, empty, depth + 1);
}
- for (uint32_t i = 129; i <= 256; ++i)
- msg->AppendVarInt(i, 42);
+ if (!empty) {
+ for (uint32_t i = 129; i <= 256; ++i)
+ msg->AppendVarInt(i, 42);
+ }
if ((depth & 2) == 0)
msg->Finalize();
@@ -128,7 +146,8 @@
std::unique_ptr<FakeScatteredBuffer> buffer_;
std::unique_ptr<ScatteredStreamWriter> stream_writer_;
std::vector<std::unique_ptr<uint8_t[]>> messages_;
- size_t readback_pos_;
+ size_t chunk_size_{};
+ size_t readback_pos_{};
};
TEST_F(MessageTest, ZeroLengthArraysAndStrings) {
@@ -189,26 +208,26 @@
root_msg->AppendVarInt(5 /* field_id */, 3);
- // The expected size of the root message is supposed to be 20 bytes:
+ // The expected size of the root message is supposed to be 14 bytes:
// 2 bytes for the varint field (id: 1) (1 for preamble and one for payload)
- // 6 bytes for the preamble of the 1st nested message (2 for id, 4 for size)
+ // 3 bytes for the preamble of the 1st nested message (2 for id, 1 for size)
// 2 bytes for the varint field (id: 2) of the 1st nested message
- // 6 bytes for the premable of the 2nd nested message
+ // 3 bytes for the premable of the 2nd nested message
// 2 bytes for the varint field (id: 4) of the 2nd nested message.
// 2 bytes for the last varint (id : 5) field of the root message.
// Test also that finalization is idempontent and Finalize() can be safely
// called more than once without side effects.
for (int i = 0; i < 3; ++i) {
- EXPECT_EQ(20u, root_msg->Finalize());
- EXPECT_EQ(20u, GetNumSerializedBytes());
+ EXPECT_EQ(14u, root_msg->Finalize());
+ EXPECT_EQ(14u, GetNumSerializedBytes());
}
ASSERT_EQ("0801", GetNextSerializedBytes(2));
- ASSERT_EQ("820882808000", GetNextSerializedBytes(6));
+ ASSERT_EQ("820802", GetNextSerializedBytes(3));
ASSERT_EQ("1002", GetNextSerializedBytes(2));
- ASSERT_EQ("8A0882808000", GetNextSerializedBytes(6));
+ ASSERT_EQ("8A0802", GetNextSerializedBytes(3));
ASSERT_EQ("2002", GetNextSerializedBytes(2));
ASSERT_EQ("2803", GetNextSerializedBytes(2));
@@ -254,7 +273,7 @@
// Nested message should have been finalized as a side effect of appending
// raw bytes.
- EXPECT_EQ(0x82u, *nested_msg_size_field);
+ EXPECT_EQ(0x2u, *nested_msg_size_field);
}
TEST_F(MessageTest, AppendScatteredBytesFinalizesNestedMessage) {
@@ -276,7 +295,7 @@
// Nested message should have been finalized as a side effect of appending
// scattered bytes.
- EXPECT_EQ(0x82u, *nested_msg_size_field);
+ EXPECT_EQ(0x2u, *nested_msg_size_field);
}
// Checks that the size field of root and nested messages is properly written
@@ -305,7 +324,7 @@
// However the size written in the size field should take into account the
// inc_size_already_written() call and be equal to 118 - 6 = 112, encoded
- // in a rendundant varint encoding of kMessageLengthFieldSize bytes.
+ // in a redundant varint encoding of kMessageLengthFieldSize bytes.
EXPECT_STREQ("\xD3\x81\x80\x00", reinterpret_cast<char*>(root_msg_size));
// Skip 2 bytes for the 0x42 varint + 1 byte for the |nested_msg_1| preamble.
@@ -341,8 +360,6 @@
}
TEST_F(MessageTest, DeeplyNested) {
- std::vector<Message*> nested_msgs;
-
Message* root_msg = NewMessage();
BuildNestedMessages(root_msg, /*max_depth=*/1000);
root_msg->Finalize();
@@ -352,6 +369,23 @@
EXPECT_EQ(0xc0fde419, buf_hash);
}
+TEST_F(MessageTest, DeeplyNestedEmptyMessages) {
+ // Stress test writing deeply nested empty messages, many of which will be
+ // packed into the protobuf length field.
+
+ // Use a larger chunk size for this test so there is more opportunity to pack
+ // messages.
+ SetChunkSize(4096u);
+
+ Message* root_msg = NewMessage();
+ BuildNestedMessages(root_msg, /*max_depth=*/1000, /*empty=*/true);
+ root_msg->Finalize();
+
+ std::string full_buf = GetNextSerializedBytes(GetNumSerializedBytes());
+ size_t buf_hash = SimpleHash(full_buf);
+ EXPECT_EQ(0x9371fe8eu, buf_hash);
+}
+
TEST_F(MessageTest, DestructInvalidMessageHandle) {
FakeRootMessage* msg = NewMessage();
EXPECT_DCHECK_DEATH({
@@ -361,16 +395,13 @@
}
TEST_F(MessageTest, MessageHandle) {
- FakeRootMessage* msg1 = NewMessage();
- FakeRootMessage* msg2 = NewMessage();
- FakeRootMessage* msg3 = NewMessage();
+ FakeRootMessage* msg3 = NewMessageWithSizeField();
+ FakeRootMessage* msg2 = NewMessageWithSizeField();
+ FakeRootMessage* msg1 = NewMessageWithSizeField();
FakeRootMessage* ignored_msg = NewMessage();
- uint8_t msg1_size[proto_utils::kMessageLengthFieldSize] = {};
- uint8_t msg2_size[proto_utils::kMessageLengthFieldSize] = {};
- uint8_t msg3_size[proto_utils::kMessageLengthFieldSize] = {};
- msg1->set_size_field(&msg1_size[0]);
- msg2->set_size_field(&msg2_size[0]);
- msg3->set_size_field(&msg3_size[0]);
+ uint8_t* msg1_size = msg1->size_field();
+ uint8_t* msg2_size = msg2->size_field();
+ uint8_t* msg3_size = msg3->size_field();
// Test that the handle going out of scope causes the finalization of the
// target message and triggers the optional callback.
@@ -379,7 +410,7 @@
handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */);
ASSERT_EQ(0u, msg1_size[0]);
}
- ASSERT_EQ(0x83u, msg1_size[0]);
+ ASSERT_EQ(0x3u, msg1_size[0]);
// Test that the handle can be late initialized.
MessageHandle<FakeRootMessage> handle2(ignored_msg);
@@ -445,13 +476,12 @@
ASSERT_EQ(0x82u, size_msg_1[0]);
ASSERT_EQ(0u, size_msg_2[0]);
}
- ASSERT_EQ(0x83u, size_msg_2[0]);
+ ASSERT_EQ(0x3u, size_msg_2[0]);
}
TEST_F(MessageTest, MoveMessageHandle) {
- FakeRootMessage* msg = NewMessage();
- uint8_t msg_size[proto_utils::kMessageLengthFieldSize] = {};
- msg->set_size_field(&msg_size[0]);
+ FakeRootMessage* msg = NewMessageWithSizeField();
+ uint8_t* msg_size = msg->size_field();
// Test that the handle going out of scope causes the finalization of the
// target message.
@@ -462,7 +492,27 @@
handle2 = std::move(handle1);
ASSERT_EQ(0u, msg_size[0]);
}
- ASSERT_EQ(0x83u, msg_size[0]);
+ ASSERT_EQ(0x3u, msg_size[0]);
+}
+
+TEST_F(MessageTest, FinalizeWithCompaction) {
+ FakeRootMessage* msg = NewMessageWithSizeField();
+
+ msg->AppendBytes(1 /* field_id */, kTestBytes, 5 /* size */);
+ uint32_t size = msg->Finalize();
+ EXPECT_EQ(7u, size);
+ EXPECT_EQ(8u, GetNumSerializedBytes());
+}
+
+TEST_F(MessageTest, FinalizeWithoutCompaction) {
+ FakeRootMessage* msg = NewMessageWithSizeField();
+
+ // This message doesn't fit into a single chunk, so it won't be compacted.
+ msg->AppendBytes(1 /* field_id */, kTestBytes, sizeof(kTestBytes) /* size */);
+ msg->AppendBytes(1 /* field_id */, kTestBytes, sizeof(kTestBytes) /* size */);
+ uint32_t size = msg->Finalize();
+ EXPECT_EQ(24u, size);
+ EXPECT_EQ(28u, GetNumSerializedBytes());
}
} // namespace
diff --git a/src/protozero/scattered_stream_writer_unittest.cc b/src/protozero/scattered_stream_writer_unittest.cc
index b3ec99c..af75cbf 100644
--- a/src/protozero/scattered_stream_writer_unittest.cc
+++ b/src/protozero/scattered_stream_writer_unittest.cc
@@ -107,5 +107,27 @@
EXPECT_EQ(0x52u, other_buffer[3]);
}
+TEST(ScatteredStreamWriterTest, Rewind) {
+ FakeScatteredBuffer delegate(kChunkSize);
+ ScatteredStreamWriter ssw(&delegate);
+ const uint8_t kTestBytes[] = {0x01, 0x02, 0x03, 0x04};
+
+ ssw.WriteBytes(kTestBytes, sizeof(kTestBytes));
+ EXPECT_EQ("0102030400000000", delegate.GetChunkAsString(0));
+ EXPECT_EQ(ssw.write_ptr(), ssw.cur_range().begin + 4u);
+
+ ssw.Rewind(3, 1);
+ EXPECT_EQ(ssw.write_ptr(), ssw.cur_range().begin + 3u);
+ EXPECT_EQ("0203040400000000", delegate.GetChunkAsString(0));
+
+ ssw.Rewind(1, 2);
+ EXPECT_EQ(ssw.write_ptr(), ssw.cur_range().begin + 1u);
+ EXPECT_EQ("0403040400000000", delegate.GetChunkAsString(0));
+
+ ssw.Rewind(1, 0);
+ EXPECT_EQ(ssw.write_ptr(), ssw.cur_range().begin + 1u);
+ EXPECT_EQ("0403040400000000", delegate.GetChunkAsString(0));
+}
+
} // namespace
} // namespace protozero
diff --git a/src/protozero/test/protozero_conformance_unittest.cc b/src/protozero/test/protozero_conformance_unittest.cc
index 7a73b69..74e2204 100644
--- a/src/protozero/test/protozero_conformance_unittest.cc
+++ b/src/protozero/test/protozero_conformance_unittest.cc
@@ -108,7 +108,7 @@
msg_c->set_value_c(1000);
std::string serialized = msg_a.SerializeAsString();
- EXPECT_EQ(serialized.size(), 26u);
+ EXPECT_EQ(serialized.size(), 14u);
pbgold::NestedA gold_msg_a;
gold_msg_a.ParseFromString(serialized);
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 31a6c7e..dd5c724 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -38,8 +38,8 @@
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/softirq_action.h"
#include "src/trace_processor/types/tcp_state.h"
-
#include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
+#include "protos/perfetto/trace/ftrace/android_fs.pbzero.h"
#include "protos/perfetto/trace/ftrace/binder.pbzero.h"
#include "protos/perfetto/trace/ftrace/cma.pbzero.h"
#include "protos/perfetto/trace/ftrace/cpuhp.pbzero.h"
@@ -217,8 +217,7 @@
}
return buffer;
}
-} // namespace
-
+} // namespace
FtraceParser::FtraceParser(TraceProcessorContext* context)
: context_(context),
rss_stat_tracker_(context),
@@ -315,7 +314,15 @@
cma_nr_test_fail_id_(context_->storage->InternString("cma_nr_test_fail")),
syscall_ret_id_(context->storage->InternString("ret")),
syscall_args_id_(context->storage->InternString("args")),
- replica_slice_id_(context->storage->InternString("replica_slice")) {
+ replica_slice_id_(context->storage->InternString("replica_slice")),
+ file_path_id_(context_->storage->InternString("file_path")),
+ offset_id_start_(context_->storage->InternString("offset_start")),
+ offset_id_end_(context_->storage->InternString("offset_end")),
+ bytes_read_id_start_(context_->storage->InternString("bytes_read_start")),
+ bytes_read_id_end_(context_->storage->InternString("bytes_read_end")),
+ android_fs_category_id_(context_->storage->InternString("android_fs")),
+ android_fs_data_read_id_(
+ context_->storage->InternString("android_fs_data_read")) {
// Build the lookup table for the strings inside ftrace events (e.g. the
// name of ftrace event fields and the names of their args).
for (size_t i = 0; i < GetDescriptorsSize(); i++) {
@@ -1047,7 +1054,14 @@
ParseMdssTracingMarkWrite(ts, pid, fld_bytes);
break;
}
-
+ case FtraceEvent::kAndroidFsDatareadEndFieldNumber: {
+ ParseAndroidFsDatareadEnd(ts, fld_bytes);
+ break;
+ }
+ case FtraceEvent::kAndroidFsDatareadStartFieldNumber: {
+ ParseAndroidFsDatareadStart(ts, pid, fld_bytes);
+ break;
+ }
default:
break;
}
@@ -3092,6 +3106,62 @@
context_->slice_tracker->End(timestamp, track, kNullStringId, name_id);
}
+/** Parses android_fs_dataread_start event.*/
+void FtraceParser::ParseAndroidFsDatareadStart(int64_t ts,
+ uint32_t pid,
+ ConstBytes data) {
+ protos::pbzero::AndroidFsDatareadStartFtraceEvent::Decoder
+ android_fs_read_begin(data);
+ base::StringView file_path(android_fs_read_begin.pathbuf());
+ std::pair<uint64_t, int64_t> key(android_fs_read_begin.ino(),
+ android_fs_read_begin.offset());
+ inode_offset_thread_map_.Insert(key, pid);
+ // Create a new Track object for the event.
+ auto async_track = context_->async_track_set_tracker->InternGlobalTrackSet(
+ android_fs_category_id_);
+ TrackId track_id = context_->async_track_set_tracker->Begin(async_track, pid);
+ StringId string_id = context_->storage->InternString(file_path);
+ auto args_inserter = [this, &android_fs_read_begin,
+ &string_id](ArgsTracker::BoundInserter* inserter) {
+ inserter->AddArg(file_path_id_, Variadic::String(string_id));
+ inserter->AddArg(offset_id_start_,
+ Variadic::Integer(android_fs_read_begin.offset()));
+ inserter->AddArg(bytes_read_id_start_,
+ Variadic::Integer(android_fs_read_begin.bytes()));
+ };
+ context_->slice_tracker->Begin(ts, track_id, kNullStringId,
+ android_fs_data_read_id_, args_inserter);
+}
+
+/** Parses android_fs_dataread_end event.*/
+void FtraceParser::ParseAndroidFsDatareadEnd(int64_t ts, ConstBytes data) {
+ protos::pbzero::AndroidFsDatareadEndFtraceEvent::Decoder android_fs_read_end(
+ data);
+ std::pair<uint64_t, int64_t> key(android_fs_read_end.ino(),
+ android_fs_read_end.offset());
+ // Find the corresponding (inode, offset) pair in the map.
+ auto it = inode_offset_thread_map_.Find(key);
+ if (!it) {
+ return;
+ }
+ uint32_t start_event_tid = *it;
+ auto async_track = context_->async_track_set_tracker->InternGlobalTrackSet(
+ android_fs_category_id_);
+ TrackId track_id =
+ context_->async_track_set_tracker->End(async_track, start_event_tid);
+ auto args_inserter =
+ [this, &android_fs_read_end](ArgsTracker::BoundInserter* inserter) {
+ inserter->AddArg(offset_id_end_,
+ Variadic::Integer(android_fs_read_end.offset()));
+ inserter->AddArg(bytes_read_id_end_,
+ Variadic::Integer(android_fs_read_end.bytes()));
+ };
+ context_->slice_tracker->End(ts, track_id, kNullStringId, kNullStringId,
+ args_inserter);
+ // Erase the entry from the map.
+ inode_offset_thread_map_.Erase(key);
+}
+
StringId FtraceParser::InternedKernelSymbolOrFallback(
uint64_t key,
PacketSequenceStateGeneration* seq_state) {
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 174861d..8003fb1 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -17,6 +17,8 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_PARSER_H_
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
#include "perfetto/trace_processor/status.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/parser_types.h"
@@ -285,6 +287,10 @@
void ParseMaliKcpuFenceSignal(uint32_t pid, int64_t ts);
void ParseMaliKcpuFenceWaitStart(uint32_t pid, int64_t ts);
void ParseMaliKcpuFenceWaitEnd(uint32_t pid, int64_t ts);
+ void ParseAndroidFsDatareadEnd(int64_t timestamp, protozero::ConstBytes);
+ void ParseAndroidFsDatareadStart(int64_t ts,
+ uint32_t pid,
+ protozero::ConstBytes);
TraceProcessorContext* context_;
RssStatTracker rss_stat_tracker_;
@@ -366,6 +372,13 @@
const StringId syscall_ret_id_;
const StringId syscall_args_id_;
const StringId replica_slice_id_;
+ const StringId file_path_id_;
+ const StringId offset_id_start_;
+ const StringId offset_id_end_;
+ const StringId bytes_read_id_start_;
+ const StringId bytes_read_id_end_;
+ const StringId android_fs_category_id_;
+ const StringId android_fs_data_read_id_;
std::vector<StringId> syscall_arg_name_ids_;
struct FtraceMessageStrings {
@@ -428,6 +441,18 @@
// putting them in the metadata multiple times (the ftrace data sources
// re-emits begin stats on every flush).
std::unordered_set<uint32_t> seen_errors_for_sequence_id_;
+
+ struct PairHash {
+ std::size_t operator()(const std::pair<uint64_t, int64_t>& p) const {
+ base::Hasher hasher;
+ hasher.Update(p.first);
+ hasher.Update(p.second);
+ return static_cast<std::size_t>(hasher.digest());
+ }
+ };
+
+ base::FlatHashMap<std::pair<uint64_t, int64_t>, uint32_t, PairHash>
+ inode_offset_thread_map_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_module.cc b/src/trace_processor/importers/proto/heap_graph_module.cc
index ad2b806..cc1d7ad 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.cc
+++ b/src/trace_processor/importers/proto/heap_graph_module.cc
@@ -36,72 +36,6 @@
using ObjectTable = tables::HeapGraphObjectTable;
using ReferenceTable = tables::HeapGraphReferenceTable;
-const char* HeapGraphRootTypeToString(int32_t type) {
- switch (type) {
- case protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN:
- return "ROOT_UNKNOWN";
- case protos::pbzero::HeapGraphRoot::ROOT_JNI_GLOBAL:
- return "ROOT_JNI_GLOBAL";
- case protos::pbzero::HeapGraphRoot::ROOT_JNI_LOCAL:
- return "ROOT_JNI_LOCAL";
- case protos::pbzero::HeapGraphRoot::ROOT_JAVA_FRAME:
- return "ROOT_JAVA_FRAME";
- case protos::pbzero::HeapGraphRoot::ROOT_NATIVE_STACK:
- return "ROOT_NATIVE_STACK";
- case protos::pbzero::HeapGraphRoot::ROOT_STICKY_CLASS:
- return "ROOT_STICKY_CLASS";
- case protos::pbzero::HeapGraphRoot::ROOT_THREAD_BLOCK:
- return "ROOT_THREAD_BLOCK";
- case protos::pbzero::HeapGraphRoot::ROOT_MONITOR_USED:
- return "ROOT_MONITOR_USED";
- case protos::pbzero::HeapGraphRoot::ROOT_THREAD_OBJECT:
- return "ROOT_THREAD_OBJECT";
- case protos::pbzero::HeapGraphRoot::ROOT_INTERNED_STRING:
- return "ROOT_INTERNED_STRING";
- case protos::pbzero::HeapGraphRoot::ROOT_FINALIZING:
- return "ROOT_FINALIZING";
- case protos::pbzero::HeapGraphRoot::ROOT_DEBUGGER:
- return "ROOT_DEBUGGER";
- case protos::pbzero::HeapGraphRoot::ROOT_REFERENCE_CLEANUP:
- return "ROOT_REFERENCE_CLEANUP";
- case protos::pbzero::HeapGraphRoot::ROOT_VM_INTERNAL:
- return "ROOT_VM_INTERNAL";
- case protos::pbzero::HeapGraphRoot::ROOT_JNI_MONITOR:
- return "ROOT_JNI_MONITOR";
- default:
- return "ROOT_UNKNOWN";
- }
-}
-
-const char* HeapGraphTypeKindToString(int32_t type) {
- switch (type) {
- case protos::pbzero::HeapGraphType::KIND_NORMAL:
- return "KIND_NORMAL";
- case protos::pbzero::HeapGraphType::KIND_NOREFERENCES:
- return "KIND_NOREFERENCES";
- case protos::pbzero::HeapGraphType::KIND_STRING:
- return "KIND_STRING";
- case protos::pbzero::HeapGraphType::KIND_ARRAY:
- return "KIND_ARRAY";
- case protos::pbzero::HeapGraphType::KIND_CLASS:
- return "KIND_CLASS";
- case protos::pbzero::HeapGraphType::KIND_CLASSLOADER:
- return "KIND_CLASSLOADER";
- case protos::pbzero::HeapGraphType::KIND_DEXCACHE:
- return "KIND_DEXCACHE";
- case protos::pbzero::HeapGraphType::KIND_SOFT_REFERENCE:
- return "KIND_SOFT_REFERENCE";
- case protos::pbzero::HeapGraphType::KIND_WEAK_REFERENCE:
- return "KIND_WEAK_REFERENCE";
- case protos::pbzero::HeapGraphType::KIND_FINALIZER_REFERENCE:
- return "KIND_FINALIZER_REFERENCE";
- case protos::pbzero::HeapGraphType::KIND_PHANTOM_REFERENCE:
- return "KIND_PHANTOM_REFERENCE";
- default:
- return "KIND_UNKNOWN";
- }
-}
-
// Iterate over a repeated field of varints, independent of whether it is
// packed or not.
template <int32_t field_no, typename T, typename F>
@@ -237,8 +171,13 @@
entry.kind() == protos::pbzero::HeapGraphType::KIND_ARRAY ||
entry.kind() == protos::pbzero::HeapGraphType::KIND_STRING;
- StringId kind = context_->storage->InternString(
- HeapGraphTypeKindToString(entry.kind()));
+ protos::pbzero::HeapGraphType::Kind kind =
+ protos::pbzero::HeapGraphType::KIND_UNKNOWN;
+ if (protos::pbzero::HeapGraphType_Kind_MIN <= entry.kind() &&
+ entry.kind() <= protos::pbzero::HeapGraphType_Kind_MAX) {
+ kind = protos::pbzero::HeapGraphType::Kind(entry.kind());
+ }
+
std::optional<uint64_t> location_id;
if (entry.has_location_id())
location_id = entry.location_id();
@@ -265,11 +204,15 @@
}
for (auto it = heap_graph.roots(); it; ++it) {
protos::pbzero::HeapGraphRoot::Decoder entry(*it);
- const char* str = HeapGraphRootTypeToString(entry.root_type());
- auto str_view = base::StringView(str);
HeapGraphTracker::SourceRoot src_root;
- src_root.root_type = context_->storage->InternString(str_view);
+ if (protos::pbzero::HeapGraphRoot_Type_MIN <= entry.root_type() &&
+ entry.root_type() <= protos::pbzero::HeapGraphRoot_Type_MAX) {
+ src_root.root_type =
+ protos::pbzero::HeapGraphRoot::Type(entry.root_type());
+ } else {
+ src_root.root_type = protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN;
+ }
// grep-friendly: object_ids
bool parse_error =
ForEachVarInt<protos::pbzero::HeapGraphRoot::kObjectIdsFieldNumber>(
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index e337113..f8971c6 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -21,6 +21,7 @@
#include "perfetto/base/flat_set.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
+#include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
#include "src/trace_processor/importers/proto/profiler_util.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
@@ -57,42 +58,6 @@
}
}
-base::FlatSet<ObjectTable::Id> GetChildren(TraceStorage* storage,
- ObjectTable::RowReference object) {
- auto cls_row_ref =
- *storage->heap_graph_class_table().FindById(object.type_id());
-
- StringId kind = cls_row_ref.kind();
- std::optional<StringId> weakref_kind =
- storage->string_pool().GetId("KIND_WEAK_REFERENCE");
- std::optional<StringId> softref_kind =
- storage->string_pool().GetId("KIND_SOFT_REFERENCE");
- std::optional<StringId> finalizerref_kind =
- storage->string_pool().GetId("KIND_FINALIZER_REFERENCE");
- std::optional<StringId> phantomref_kind =
- storage->string_pool().GetId("KIND_PHANTOM_REFERENCE");
-
- if ((weakref_kind && kind == *weakref_kind) ||
- (softref_kind && kind == *softref_kind) ||
- (finalizerref_kind && kind == *finalizerref_kind) ||
- (phantomref_kind && kind == *phantomref_kind)) {
- // Do not follow weak / soft / finalizer / phantom references.
- return {};
- }
-
- base::FlatSet<ObjectTable::Id> children;
- ForReferenceSet(storage, object,
- [object, &children](ReferenceTable::RowReference ref) {
- PERFETTO_CHECK(ref.owner_id() == object.id());
- auto opt_owned = ref.owned_id();
- if (opt_owned) {
- children.insert(*opt_owned);
- }
- return true;
- });
- return children;
-}
-
struct ClassDescriptor {
StringId name;
std::optional<StringId> location;
@@ -176,56 +141,6 @@
} // namespace
-void MarkRoot(TraceStorage* storage,
- ObjectTable::RowReference row_ref,
- StringId type) {
- row_ref.set_root_type(type);
-
- // DFS to mark reachability for all children
- std::vector<ObjectTable::RowReference> stack({row_ref});
- while (!stack.empty()) {
- ObjectTable::RowReference cur_node = stack.back();
- stack.pop_back();
-
- if (cur_node.reachable())
- continue;
- cur_node.set_reachable(true);
-
- for (ObjectTable::Id child_node : GetChildren(storage, cur_node)) {
- auto child_ref =
- *storage->mutable_heap_graph_object_table()->FindById(child_node);
- stack.push_back(child_ref);
- }
- }
-}
-
-void UpdateShortestPaths(TraceStorage* storage,
- ObjectTable::RowReference row_ref) {
- // Calculate shortest distance to a GC root.
- std::deque<std::pair<int32_t, ObjectTable::RowReference>> reachable_nodes{
- {0, row_ref}};
- while (!reachable_nodes.empty()) {
- auto pair = reachable_nodes.front();
-
- int32_t distance = pair.first;
- ObjectTable::RowReference cur_row_ref = pair.second;
-
- reachable_nodes.pop_front();
- int32_t cur_distance = cur_row_ref.root_distance();
- if (cur_distance == -1 || cur_distance > distance) {
- cur_row_ref.set_root_distance(distance);
-
- for (ObjectTable::Id child_node : GetChildren(storage, cur_row_ref)) {
- auto child_row_ref =
- *storage->mutable_heap_graph_object_table()->FindById(child_node);
- int32_t child_distance = child_row_ref.root_distance();
- if (child_distance == -1 || child_distance > distance + 1)
- reachable_nodes.emplace_back(distance + 1, child_row_ref);
- }
- }
- }
-}
-
std::optional<base::StringView> GetStaticClassTypeName(base::StringView type) {
static const base::StringView kJavaClassTemplate("java.lang.Class<");
if (!type.empty() && type.at(type.size() - 1) == '>' &&
@@ -283,7 +198,21 @@
"libcore.util.NativeAllocationRegistry$CleanerThunk.this$0")),
native_size_str_id_(
storage_->InternString("libcore.util.NativeAllocationRegistry.size")),
- cleaner_next_str_id_(storage_->InternString("sun.misc.Cleaner.next")) {}
+ cleaner_next_str_id_(storage_->InternString("sun.misc.Cleaner.next")) {
+ for (size_t i = 0; i < root_type_string_ids_.size(); i++) {
+ auto val = static_cast<protos::pbzero::HeapGraphRoot::Type>(i);
+ auto str_view =
+ base::StringView(protos::pbzero::HeapGraphRoot_Type_Name(val));
+ root_type_string_ids_[i] = storage_->InternString(str_view);
+ }
+
+ for (size_t i = 0; i < type_kind_string_ids_.size(); i++) {
+ auto val = static_cast<protos::pbzero::HeapGraphType::Kind>(i);
+ auto str_view =
+ base::StringView(protos::pbzero::HeapGraphType_Kind_Name(val));
+ type_kind_string_ids_[i] = storage_->InternString(str_view);
+ }
+}
HeapGraphTracker::SequenceState& HeapGraphTracker::GetOrCreateSequence(
uint32_t seq_id) {
@@ -429,26 +358,27 @@
sequence_state.interned_location_names.emplace(intern_id, strid);
}
-void HeapGraphTracker::AddInternedType(uint32_t seq_id,
- uint64_t intern_id,
- StringId strid,
- std::optional<uint64_t> location_id,
- uint64_t object_size,
- std::vector<uint64_t> field_name_ids,
- uint64_t superclass_id,
- uint64_t classloader_id,
- bool no_fields,
- StringId kind) {
+void HeapGraphTracker::AddInternedType(
+ uint32_t seq_id,
+ uint64_t intern_id,
+ StringId strid,
+ std::optional<uint64_t> location_id,
+ uint64_t object_size,
+ std::vector<uint64_t> field_name_ids,
+ uint64_t superclass_id,
+ uint64_t classloader_id,
+ bool no_fields,
+ protos::pbzero::HeapGraphType::Kind kind) {
SequenceState& sequence_state = GetOrCreateSequence(seq_id);
- sequence_state.interned_types[intern_id].name = strid;
- sequence_state.interned_types[intern_id].location_id = location_id;
- sequence_state.interned_types[intern_id].object_size = object_size;
- sequence_state.interned_types[intern_id].field_name_ids =
- std::move(field_name_ids);
- sequence_state.interned_types[intern_id].superclass_id = superclass_id;
- sequence_state.interned_types[intern_id].classloader_id = classloader_id;
- sequence_state.interned_types[intern_id].no_fields = no_fields;
- sequence_state.interned_types[intern_id].kind = kind;
+ InternedType& type = sequence_state.interned_types[intern_id];
+ type.name = strid;
+ type.location_id = location_id;
+ type.object_size = object_size;
+ type.field_name_ids = std::move(field_name_ids);
+ type.superclass_id = superclass_id;
+ type.classloader_id = classloader_id;
+ type.no_fields = no_fields;
+ type.kind = kind;
}
void HeapGraphTracker::AddInternedFieldName(uint32_t seq_id,
@@ -618,7 +548,7 @@
}
if (location_name)
type_row_ref.set_location(*location_name);
- type_row_ref.set_kind(interned_type.kind);
+ type_row_ref.set_kind(InternTypeKindString(interned_type.kind));
base::StringView normalized_type =
NormalizeTypeName(storage_->GetString(interned_type.name));
@@ -668,8 +598,9 @@
auto it_and_success = roots_[std::make_pair(sequence_state.current_upid,
sequence_state.current_ts)]
.emplace(*ptr);
- if (it_and_success.second)
- MarkRoot(storage_, row_ref, root.root_type);
+ if (it_and_success.second) {
+ MarkRoot(row_ref, InternRootTypeString(root.root_type));
+ }
}
}
@@ -817,9 +748,94 @@
}
}
-void FindPathFromRoot(TraceStorage* storage,
- ObjectTable::RowReference row_ref,
- PathFromRoot* path) {
+base::FlatSet<ObjectTable::Id> HeapGraphTracker::GetChildren(
+ ObjectTable::RowReference object) {
+ auto cls_row_ref =
+ *storage_->heap_graph_class_table().FindById(object.type_id());
+
+ StringId kind = cls_row_ref.kind();
+
+ bool is_ignored_reference =
+ kind == InternTypeKindString(
+ protos::pbzero::HeapGraphType::KIND_WEAK_REFERENCE) ||
+ kind == InternTypeKindString(
+ protos::pbzero::HeapGraphType::KIND_SOFT_REFERENCE) ||
+ kind == InternTypeKindString(
+ protos::pbzero::HeapGraphType::KIND_FINALIZER_REFERENCE) ||
+ kind == InternTypeKindString(
+ protos::pbzero::HeapGraphType::KIND_PHANTOM_REFERENCE);
+
+ base::FlatSet<ObjectTable::Id> children;
+ ForReferenceSet(
+ storage_, object,
+ [object, &children, is_ignored_reference,
+ this](ReferenceTable::RowReference ref) {
+ PERFETTO_CHECK(ref.owner_id() == object.id());
+ auto opt_owned = ref.owned_id();
+ if (!opt_owned) {
+ return true;
+ }
+ if (is_ignored_reference && ref.field_name() == referent_str_id_) {
+ // If `object` is a special reference kind, its
+ // "java.lang.ref.Reference.referent" field should be ignored.
+ return true;
+ }
+ children.insert(*opt_owned);
+ return true;
+ });
+ return children;
+}
+
+void HeapGraphTracker::MarkRoot(ObjectTable::RowReference row_ref,
+ StringId type) {
+ row_ref.set_root_type(type);
+
+ // DFS to mark reachability for all children
+ std::vector<ObjectTable::RowReference> stack({row_ref});
+ while (!stack.empty()) {
+ ObjectTable::RowReference cur_node = stack.back();
+ stack.pop_back();
+
+ if (cur_node.reachable())
+ continue;
+ cur_node.set_reachable(true);
+
+ for (ObjectTable::Id child_node : GetChildren(cur_node)) {
+ auto child_ref =
+ *storage_->mutable_heap_graph_object_table()->FindById(child_node);
+ stack.push_back(child_ref);
+ }
+ }
+}
+
+void HeapGraphTracker::UpdateShortestPaths(ObjectTable::RowReference row_ref) {
+ // Calculate shortest distance to a GC root.
+ std::deque<std::pair<int32_t, ObjectTable::RowReference>> reachable_nodes{
+ {0, row_ref}};
+ while (!reachable_nodes.empty()) {
+ auto pair = reachable_nodes.front();
+
+ int32_t distance = pair.first;
+ ObjectTable::RowReference cur_row_ref = pair.second;
+
+ reachable_nodes.pop_front();
+ int32_t cur_distance = cur_row_ref.root_distance();
+ if (cur_distance == -1 || cur_distance > distance) {
+ cur_row_ref.set_root_distance(distance);
+
+ for (ObjectTable::Id child_node : GetChildren(cur_row_ref)) {
+ auto child_row_ref =
+ *storage_->mutable_heap_graph_object_table()->FindById(child_node);
+ int32_t child_distance = child_row_ref.root_distance();
+ if (child_distance == -1 || child_distance > distance + 1)
+ reachable_nodes.emplace_back(distance + 1, child_row_ref);
+ }
+ }
+ }
+}
+
+void HeapGraphTracker::FindPathFromRoot(ObjectTable::RowReference row_ref,
+ PathFromRoot* path) {
// We have long retention chains (e.g. from LinkedList). If we use the stack
// here, we risk running out of stack space. This is why we use a vector to
// simulate the stack.
@@ -844,7 +860,7 @@
ClassTable::Id type_id = object_row_ref.type_id();
- auto type_row_ref = *storage->heap_graph_class_table().FindById(type_id);
+ auto type_row_ref = *storage_->heap_graph_class_table().FindById(type_id);
std::optional<StringId> opt_class_name_id =
type_row_ref.deobfuscated_name();
if (!opt_class_name_id) {
@@ -854,9 +870,9 @@
StringId class_name_id = *opt_class_name_id;
std::optional<StringId> root_type = object_row_ref.root_type();
if (root_type) {
- class_name_id = storage->InternString(base::StringView(
- storage->GetString(class_name_id).ToStdString() + " [" +
- storage->GetString(*root_type).ToStdString() + "]"));
+ class_name_id = storage_->InternString(base::StringView(
+ storage_->GetString(class_name_id).ToStdString() + " [" +
+ storage_->GetString(*root_type).ToStdString() + "]"));
}
auto it = path->nodes[parent_id].children.find(class_name_id);
if (it == path->nodes[parent_id].children.end()) {
@@ -876,15 +892,14 @@
// size to the relevant node in the resulting tree.
output_tree_node->size += object_row_ref.self_size();
output_tree_node->count++;
- base::FlatSet<ObjectTable::Id> children_set =
- GetChildren(storage, object_row_ref);
+ base::FlatSet<ObjectTable::Id> children_set = GetChildren(object_row_ref);
children.assign(children_set.begin(), children_set.end());
PERFETTO_CHECK(children.size() == children_set.size());
if (object_row_ref.native_size()) {
- StringId native_class_name_id = storage->InternString(
+ StringId native_class_name_id = storage_->InternString(
base::StringView(std::string("[native] ") +
- storage->GetString(class_name_id).ToStdString()));
+ storage_->GetString(class_name_id).ToStdString()));
std::map<StringId, size_t>::iterator native_it;
bool inserted_new_node;
std::tie(native_it, inserted_new_node) =
@@ -910,7 +925,7 @@
PERFETTO_CHECK(i < children.size());
ObjectTable::Id child = children[i];
auto child_row_ref =
- *storage->mutable_heap_graph_object_table()->FindById(child);
+ *storage_->mutable_heap_graph_object_table()->FindById(child);
if (++i == children.size())
stack.pop_back();
@@ -971,11 +986,11 @@
// First pass to calculate shortest paths
for (ObjectTable::RowNumber root : roots) {
- UpdateShortestPaths(storage_, root.ToRowReference(object_table));
+ UpdateShortestPaths(root.ToRowReference(object_table));
}
PathFromRoot init_path;
for (ObjectTable::RowNumber root : roots) {
- FindPathFromRoot(storage_, root.ToRowReference(object_table), &init_path);
+ FindPathFromRoot(root.ToRowReference(object_table), &init_path);
}
std::vector<int64_t> node_to_cumulative_size(init_path.nodes.size());
@@ -1047,6 +1062,26 @@
return false;
}
+StringId HeapGraphTracker::InternRootTypeString(
+ protos::pbzero::HeapGraphRoot::Type root_type) {
+ size_t idx = static_cast<size_t>(root_type);
+ if (idx >= root_type_string_ids_.size()) {
+ idx = static_cast<size_t>(protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN);
+ }
+
+ return root_type_string_ids_[idx];
+}
+
+StringId HeapGraphTracker::InternTypeKindString(
+ protos::pbzero::HeapGraphType::Kind kind) {
+ size_t idx = static_cast<size_t>(kind);
+ if (idx >= type_kind_string_ids_.size()) {
+ idx = static_cast<size_t>(protos::pbzero::HeapGraphType::KIND_UNKNOWN);
+ }
+
+ return type_kind_string_ids_[idx];
+}
+
HeapGraphTracker::~HeapGraphTracker() = default;
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index c2f184f..f5d3a1b 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -23,6 +23,7 @@
#include <utility>
#include <vector>
+#include "perfetto/base/flat_set.h"
#include "perfetto/ext/base/string_view.h"
#include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
@@ -55,15 +56,6 @@
std::set<tables::HeapGraphObjectTable::Id> visited;
};
-void MarkRoot(TraceStorage*,
- tables::HeapGraphObjectTable::RowReference,
- StringId type);
-void UpdateShortestPaths(TraceStorage* s,
- tables::HeapGraphObjectTable::RowReference row_ref);
-void FindPathFromRoot(TraceStorage* storage,
- tables::HeapGraphObjectTable::RowReference,
- PathFromRoot* path);
-
std::optional<base::StringView> GetStaticClassTypeName(base::StringView type);
size_t NumberOfArrays(base::StringView type);
NormalizedType GetNormalizedType(base::StringView type);
@@ -89,7 +81,7 @@
};
struct SourceRoot {
- StringId root_type;
+ protos::pbzero::HeapGraphRoot::Type root_type;
std::vector<uint64_t> object_ids;
};
@@ -114,7 +106,7 @@
uint64_t superclass_id,
uint64_t classloader_id,
bool no_fields,
- StringId kind);
+ protos::pbzero::HeapGraphType::Kind kind);
void AddInternedFieldName(uint32_t seq_id,
uint64_t intern_id,
base::StringView str);
@@ -162,7 +154,7 @@
uint64_t superclass_id;
bool no_fields;
uint64_t classloader_id;
- StringId kind;
+ protos::pbzero::HeapGraphType::Kind kind;
};
struct SequenceState {
UniquePid current_upid = 0;
@@ -218,6 +210,8 @@
InternedType* GetSuperClass(SequenceState* sequence_state,
const InternedType* current_type);
bool IsTruncated(UniquePid upid, int64_t ts);
+ StringId InternRootTypeString(protos::pbzero::HeapGraphRoot::Type);
+ StringId InternTypeKindString(protos::pbzero::HeapGraphType::Kind);
// Returns the object pointed to by `field` in `obj`.
std::optional<tables::HeapGraphObjectTable::Id> GetReferenceByFieldName(
@@ -231,6 +225,13 @@
// all the other tables have been fully populated.
void PopulateNativeSize(const SequenceState& seq);
+ base::FlatSet<tables::HeapGraphObjectTable::Id> GetChildren(
+ tables::HeapGraphObjectTable::RowReference);
+ void MarkRoot(tables::HeapGraphObjectTable::RowReference, StringId type);
+ void UpdateShortestPaths(tables::HeapGraphObjectTable::RowReference row_ref);
+ void FindPathFromRoot(tables::HeapGraphObjectTable::RowReference,
+ PathFromRoot* path);
+
TraceStorage* const storage_;
std::map<uint32_t, SequenceState> sequence_state_;
@@ -253,6 +254,16 @@
StringId cleaner_thunk_this0_str_id_;
StringId native_size_str_id_;
StringId cleaner_next_str_id_;
+
+ std::array<StringId, 15> root_type_string_ids_ = {};
+ static_assert(protos::pbzero::HeapGraphRoot_Type_MIN == 0);
+ static_assert(protos::pbzero::HeapGraphRoot_Type_MAX + 1 ==
+ std::tuple_size<decltype(root_type_string_ids_)>{});
+
+ std::array<StringId, 12> type_kind_string_ids_ = {};
+ static_assert(protos::pbzero::HeapGraphType_Kind_MIN == 0);
+ static_assert(protos::pbzero::HeapGraphType_Kind_MAX + 1 ==
+ std::tuple_size<decltype(type_kind_string_ids_)>{});
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
index 15be4ac..8e9ee32 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
@@ -67,8 +67,6 @@
HeapGraphTracker tracker(context.storage.get());
- StringPool::Id normal_kind = context.storage->InternString("KIND_NORMAL");
-
constexpr uint64_t kLocation = 0;
tracker.AddInternedLocationName(kSeqId, kLocation,
context.storage->InternString("location"));
@@ -94,34 +92,34 @@
kSeqId, kTypeBitmap,
context.storage->InternString("android.graphics.Bitmap"), kLocation,
/*object_size=*/0,
- /*reference_field_name_ids=*/{}, /*superclass_id=*/0,
- /*classloader_id=*/0, /*no_reference_fields=*/false,
- /*kind=*/normal_kind);
+ /*field_name_ids=*/{}, /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
- tracker.AddInternedType(
- kSeqId, kTypeCleaner, context.storage->InternString("sun.misc.Cleaner"),
- kLocation, /*object_size=*/0,
- /*reference_field_name_ids=*/{kReferent, kThunk, kNext},
- /*superclass_id=*/0,
- /*classloader_id=*/0, /*no_reference_fields=*/false,
- /*kind=*/normal_kind);
+ tracker.AddInternedType(kSeqId, kTypeCleaner,
+ context.storage->InternString("sun.misc.Cleaner"),
+ kLocation, /*object_size=*/0,
+ /*field_name_ids=*/{kReferent, kThunk, kNext},
+ /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
tracker.AddInternedType(
kSeqId, kTypeCleanerThunk,
context.storage->InternString(
"libcore.util.NativeAllocationRegistry$CleanerThunk"),
kLocation, /*object_size=*/0,
- /*reference_field_name_ids=*/{kThis0}, /*superclass_id=*/0,
- /*classloader_id=*/0, /*no_reference_fields=*/false,
- /*kind=*/normal_kind);
+ /*field_name_ids=*/{kThis0}, /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
tracker.AddInternedType(
kSeqId, kTypeNativeAllocationRegistry,
context.storage->InternString("libcore.util.NativeAllocationRegistry"),
kLocation, /*object_size=*/0,
- /*reference_field_name_ids=*/{}, /*superclass_id=*/0,
- /*classloader_id=*/0, /*no_reference_fields=*/false,
- /*kind=*/normal_kind);
+ /*field_name_ids=*/{}, /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
enum Objects : uint64_t {
kObjBitmap = 1,
@@ -217,18 +215,13 @@
constexpr uint64_t kY = 2;
constexpr uint64_t kA = 3;
constexpr uint64_t kB = 4;
- constexpr uint64_t kWeakRef = 5;
base::StringView field = base::StringView("foo");
StringPool::Id x = context.storage->InternString("X");
StringPool::Id y = context.storage->InternString("Y");
StringPool::Id a = context.storage->InternString("A");
StringPool::Id b = context.storage->InternString("B");
- StringPool::Id weak_ref = context.storage->InternString("WeakReference");
- StringPool::Id normal_kind = context.storage->InternString("KIND_NORMAL");
- StringPool::Id weak_ref_kind =
- context.storage->InternString("KIND_WEAK_REFERENCE");
tracker.AddInternedFieldName(kSeqId, kField, field);
tracker.AddInternedLocationName(kSeqId, kLocation,
@@ -236,35 +229,19 @@
tracker.AddInternedType(kSeqId, kX, x, kLocation, /*object_size=*/0,
/*field_name_ids=*/{}, /*superclass_id=*/0,
/*classloader_id=*/0, /*no_fields=*/false,
- /*kind=*/normal_kind);
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
tracker.AddInternedType(kSeqId, kY, y, kLocation, /*object_size=*/0,
/*field_name_ids=*/{}, /*superclass_id=*/0,
/*classloader_id=*/0, /*no_fields=*/false,
- /*kind=*/normal_kind);
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
tracker.AddInternedType(kSeqId, kA, a, kLocation, /*object_size=*/0,
/*field_name_ids=*/{}, /*superclass_id=*/0,
/*classloader_id=*/0, /*no_fields=*/false,
- /*kind=*/normal_kind);
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
tracker.AddInternedType(kSeqId, kB, b, kLocation, /*object_size=*/0,
/*field_name_ids=*/{}, /*superclass_id=*/0,
/*classloader_id=*/0, /*no_fields=*/false,
- /*kind=*/normal_kind);
- tracker.AddInternedType(kSeqId, kWeakRef, weak_ref, kLocation,
- /*object_size=*/0,
- /*field_name_ids=*/{}, /*superclass_id=*/0,
- /*classloader_id=*/0, /*no_fields=*/false,
- /*kind=*/weak_ref_kind);
- {
- HeapGraphTracker::SourceObject obj;
- obj.object_id = 999;
- obj.self_size = 999;
- obj.type_id = kWeakRef;
- obj.field_name_ids = {kField};
- obj.referred_objects = {5};
-
- tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
- }
-
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
{
HeapGraphTracker::SourceObject obj;
obj.object_id = 1;
@@ -312,9 +289,8 @@
}
HeapGraphTracker::SourceRoot root;
- root.root_type = context.storage->InternString("ROOT");
+ root.root_type = protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN;
root.object_ids.emplace_back(1);
- root.object_ids.emplace_back(999);
tracker.AddRoot(kSeqId, kPid, kTimestamp, root);
tracker.FinalizeProfile(kSeqId);
@@ -323,16 +299,129 @@
ASSERT_NE(flame, nullptr);
auto cumulative_sizes = flame->cumulative_size().ToVectorForTesting();
- EXPECT_THAT(cumulative_sizes, UnorderedElementsAre(15, 4, 14, 5, 999));
+ EXPECT_THAT(cumulative_sizes, UnorderedElementsAre(15, 4, 14, 5));
auto cumulative_counts = flame->cumulative_count().ToVectorForTesting();
- EXPECT_THAT(cumulative_counts, UnorderedElementsAre(5, 4, 1, 1, 1));
+ EXPECT_THAT(cumulative_counts, UnorderedElementsAre(5, 4, 1, 1));
auto sizes = flame->size().ToVectorForTesting();
- EXPECT_THAT(sizes, UnorderedElementsAre(1, 5, 4, 5, 999));
+ EXPECT_THAT(sizes, UnorderedElementsAre(1, 5, 4, 5));
auto counts = flame->count().ToVectorForTesting();
- EXPECT_THAT(counts, UnorderedElementsAre(1, 2, 1, 1, 1));
+ EXPECT_THAT(counts, UnorderedElementsAre(1, 2, 1, 1));
+}
+
+TEST(HeapGraphTrackerTest, BuildFlamegraphWeakReferences) {
+ // Regression test for http://b.corp.google.com/issues/302662734:
+ // For weak (and other) references, we should not follow the
+ // `java.lang.ref.Reference.referent` field, but we should follow other
+ // fields.
+ //
+ // 2@A 4@B
+ // (java.lang.ref.Reference.referent) \ / (X.other)
+ // 1@X (extends WeakReference)
+
+ constexpr uint64_t kSeqId = 1;
+ constexpr UniquePid kPid = 1;
+ constexpr int64_t kTimestamp = 1;
+
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.process_tracker.reset(new ProcessTracker(&context));
+ context.process_tracker->GetOrCreateProcess(kPid);
+
+ HeapGraphTracker tracker(context.storage.get());
+
+ constexpr uint64_t kLocation = 0;
+
+ base::StringView referent_field =
+ base::StringView("java.lang.ref.Reference.referent");
+ constexpr uint64_t kReferentField = 1;
+ base::StringView other_field = base::StringView("X.other");
+ constexpr uint64_t kOtherField = 2;
+
+ constexpr uint64_t kX = 1;
+ StringPool::Id x = context.storage->InternString("X");
+ constexpr uint64_t kA = 2;
+ StringPool::Id a = context.storage->InternString("A");
+ constexpr uint64_t kB = 4;
+ StringPool::Id b = context.storage->InternString("B");
+ constexpr uint64_t kWeakRef = 5;
+ StringPool::Id weak_ref = context.storage->InternString("WeakReference");
+
+ tracker.AddInternedFieldName(kSeqId, kReferentField, referent_field);
+ tracker.AddInternedFieldName(kSeqId, kOtherField, other_field);
+
+ tracker.AddInternedLocationName(kSeqId, kLocation,
+ context.storage->InternString("location"));
+
+ tracker.AddInternedType(kSeqId, kWeakRef, weak_ref, kLocation,
+ /*object_size=*/0,
+ /*field_name_ids=*/{kReferentField},
+ /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_WEAK_REFERENCE);
+ tracker.AddInternedType(kSeqId, kX, x, kLocation,
+ /*object_size=*/0,
+ /*field_name_ids=*/{kOtherField},
+ /*superclass_id=*/kWeakRef,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_WEAK_REFERENCE);
+ tracker.AddInternedType(kSeqId, kA, a, kLocation, /*object_size=*/0,
+ /*field_name_ids=*/{}, /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
+ tracker.AddInternedType(kSeqId, kB, b, kLocation, /*object_size=*/0,
+ /*field_name_ids=*/{}, /*superclass_id=*/0,
+ /*classloader_id=*/0, /*no_fields=*/false,
+ protos::pbzero::HeapGraphType::KIND_NORMAL);
+ {
+ HeapGraphTracker::SourceObject obj;
+ obj.object_id = 1;
+ obj.self_size = 1;
+ obj.type_id = kX;
+ obj.referred_objects = {/*X.other*/ 4,
+ /*java.lang.ref.Reference.referent*/ 2};
+ tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
+ }
+
+ {
+ HeapGraphTracker::SourceObject obj;
+ obj.object_id = 2;
+ obj.self_size = 2;
+ obj.type_id = kA;
+ tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
+ }
+
+ {
+ HeapGraphTracker::SourceObject obj;
+ obj.object_id = 4;
+ obj.self_size = 4;
+ obj.type_id = kB;
+ tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
+ }
+
+ HeapGraphTracker::SourceRoot root;
+ root.root_type = protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN;
+ root.object_ids.emplace_back(1);
+ tracker.AddRoot(kSeqId, kPid, kTimestamp, root);
+
+ tracker.FinalizeProfile(kSeqId);
+ std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> flame =
+ tracker.BuildFlamegraph(kPid, kTimestamp);
+ ASSERT_NE(flame, nullptr);
+
+ auto cumulative_sizes = flame->cumulative_size().ToVectorForTesting();
+ EXPECT_THAT(cumulative_sizes, UnorderedElementsAre(4, 4 + 1));
+
+ auto cumulative_counts = flame->cumulative_count().ToVectorForTesting();
+ EXPECT_THAT(cumulative_counts, UnorderedElementsAre(1, 1 + 1));
+
+ auto sizes = flame->size().ToVectorForTesting();
+ EXPECT_THAT(sizes, UnorderedElementsAre(1, 4));
+
+ auto counts = flame->count().ToVectorForTesting();
+ EXPECT_THAT(counts, UnorderedElementsAre(1, 1));
}
static const char kArray[] = "X[]";
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index d141d68..ddd9e24 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -729,7 +729,7 @@
auto output_query =
"SELECT * FROM " + sql_metric.output_table_name.value() + ";";
PERFETTO_TP_TRACE(
- metatrace::Category::QUERY, "COMPUTE_METRIC_QUERY",
+ metatrace::Category::QUERY_TIMELINE, "COMPUTE_METRIC_QUERY",
[&](metatrace::Record* r) { r->AddArg("SQL", output_query); });
auto it = engine->ExecuteUntilLastStatement(
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index 99223c0..6c31943 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -23,6 +23,7 @@
"android_batt.sql",
"android_binder.sql",
"android_blocking_calls_cuj_metric.sql",
+ "android_boot.sql",
"android_camera.sql",
"android_camera_unagg.sql",
"android_cpu.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
new file mode 100644
index 0000000..46caea8
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -0,0 +1,51 @@
+--
+-- Copyright 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
+--
+-- 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.
+--
+
+INCLUDE PERFETTO MODULE android.process_metadata;
+
+CREATE PERFETTO FUNCTION get_durations(process_name STRING)
+RETURNS TABLE(uint_sleep_dur LONG, total_dur LONG) AS
+SELECT
+ SUM(CASE WHEN thread_state.state="D" then thread_state.dur ELSE 0 END) AS uint_sleep_dur,
+ SUM(thread_state.dur) as total_dur
+FROM android_process_metadata
+INNER JOIN thread ON thread.upid=android_process_metadata.upid
+INNER JOIN thread_state ON thread.utid=thread_state.utid WHERE android_process_metadata.process_name=$process_name;
+
+DROP VIEW IF EXISTS android_boot_output;
+CREATE VIEW android_boot_output AS
+SELECT AndroidBootMetric(
+ 'system_server_durations', (
+ SELECT NULL_IF_EMPTY(ProcessStateDurations(
+ 'total_dur', total_dur,
+ 'uninterruptible_sleep_dur', uint_sleep_dur))
+ FROM get_durations('system_server')),
+ 'systemui_durations', (
+ SELECT NULL_IF_EMPTY(ProcessStateDurations(
+ 'total_dur', total_dur,
+ 'uninterruptible_sleep_dur', uint_sleep_dur))
+ FROM get_durations('com.android.systemui')),
+ 'launcher_durations', (
+ SELECT NULL_IF_EMPTY(ProcessStateDurations(
+ 'total_dur', total_dur,
+ 'uninterruptible_sleep_dur', uint_sleep_dur))
+ FROM get_durations('com.google.android.apps.nexuslauncher')),
+ 'gms_durations', (
+ SELECT NULL_IF_EMPTY(ProcessStateDurations(
+ 'total_dur', total_dur,
+ 'uninterruptible_sleep_dur', uint_sleep_dur))
+ FROM get_durations('com.google.android.gms.persistent'))
+);
diff --git a/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql b/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql
index e03dca9..ec17f42 100644
--- a/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql
+++ b/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql
@@ -32,6 +32,8 @@
s.name GLOB 'NotificationStackScrollLayout#onMeasure'
OR s.name GLOB 'NotificationToplineView#onMeasure'
OR s.name GLOB 'ExpNotRow#*'
+ OR s.name GLOB 'NotificationShadeWindowView#onMeasure'
+ OR s.name GLOB 'ImageFloatingTextView#onMeasure'
)
GROUP BY s.name;
diff --git a/src/trace_processor/perfetto_sql/engine/BUILD.gn b/src/trace_processor/perfetto_sql/engine/BUILD.gn
index 9374bf6..f3451e0 100644
--- a/src/trace_processor/perfetto_sql/engine/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/engine/BUILD.gn
@@ -26,6 +26,8 @@
"perfetto_sql_engine.h",
"perfetto_sql_parser.cc",
"perfetto_sql_parser.h",
+ "perfetto_sql_preprocessor.cc",
+ "perfetto_sql_preprocessor.h",
"runtime_table_function.cc",
"runtime_table_function.h",
]
@@ -50,6 +52,8 @@
sources = [
"perfetto_sql_engine_unittest.cc",
"perfetto_sql_parser_unittest.cc",
+ "perfetto_sql_preprocessor_unittest.cc",
+ "perfetto_sql_test_utils.h",
]
deps = [
":engine",
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index 66be287..3e88ada 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -320,7 +320,7 @@
}
base::Status Run(Memoizer::MemoizedArgs initial_args) {
- PERFETTO_TP_TRACE(metatrace::Category::FUNCTION,
+ PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL,
"UNROLL_RECURSIVE_FUNCTION_CALL",
[&](metatrace::Record* r) {
r->AddArg("Function", prototype_.function_name);
@@ -336,8 +336,8 @@
state_ = State::kComputingFirstPass;
Memoizer::MemoizedArgs args = first_pass_.front();
- PERFETTO_TP_TRACE(metatrace::Category::FUNCTION, "SQL_FUNCTION_CALL",
- [&](metatrace::Record* r) {
+ PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL,
+ "SQL_FUNCTION_CALL", [&](metatrace::Record* r) {
r->AddArg("Function", prototype_.function_name);
r->AddArg("Type", "UnrollRecursiveCall_FirstPass");
r->AddArg("Arg 0", std::to_string(args));
@@ -352,7 +352,7 @@
state_ = State::kComputingSecondPass;
Memoizer::MemoizedArgs args = second_pass_.top();
- PERFETTO_TP_TRACE(metatrace::Category::FUNCTION, "SQL_FUNCTION_CALL",
+ PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL, "SQL_FUNCTION_CALL",
[&](metatrace::Record* r) {
r->AddArg("Function", prototype_.function_name);
r->AddArg("Type", "UnrollRecursiveCall_SecondPass");
@@ -640,7 +640,7 @@
}
PERFETTO_TP_TRACE(
- metatrace::Category::FUNCTION, "SQL_FUNCTION_CALL",
+ metatrace::Category::FUNCTION_CALL, "SQL_FUNCTION_CALL",
[state, argv](metatrace::Record* r) {
r->AddArg("Function", state->prototype().function_name.c_str());
for (uint32_t i = 0; i < state->prototype().arguments.size(); ++i) {
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index b34f556..1efd12d 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -16,10 +16,12 @@
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include <cctype>
#include <memory>
#include <optional>
#include <string>
#include <variant>
+#include <vector>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
@@ -28,6 +30,7 @@
#include "src/trace_processor/perfetto_sql/engine/created_function.h"
#include "src/trace_processor/perfetto_sql/engine/function_util.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/sqlite/db_sqlite_table.h"
#include "src/trace_processor/sqlite/scoped_db.h"
@@ -36,6 +39,31 @@
#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/util/status_macros.h"
+// Implementation details
+// ----------------------
+//
+// The execution of PerfettoSQL statements is the joint responsibility of
+// several classes which all are linked together in the following way:
+//
+// PerfettoSqlEngine -> PerfettoSqlParser -> PerfettoSqlPreprocessor
+//
+// The responsibility of each of these classes is as follows:
+//
+// * PerfettoSqlEngine: this class is responsible for the end-to-end processing
+// of statements. It calls into PerfettoSqlParser to incrementally receive
+// parsed SQL statements and then executes them. If the statement is a
+// PerfettoSQL-only statement, the execution happens entirely in this class.
+// Otherwise, if the statement is a valid SQLite statement, SQLite is called
+// into to perform the execution.
+// * PerfettoSqlParser: this class is responsible for taking a chunk of SQL and
+// incrementally converting them into parsed SQL statement. The parser calls
+// into the PerfettoSqlPreprocessor to split the SQL chunk into a statement
+// and perform any macro expansion. It then tries to parse any
+// PerfettoSQL-only statements into their component parts and leaves SQLite
+// statements as-is for execution by SQLite.
+// * PerfettoSqlPreprocessor: this class is responsible for taking a chunk of
+// SQL and breaking them into statements, while also expanding any macros
+// which might be present inside.
namespace perfetto {
namespace trace_processor {
namespace {
@@ -91,6 +119,13 @@
return status;
}
+// This function is used when the the PerfettoSQL has been fully executed by the
+// PerfettoSqlEngine and a SqlSoruce is needed for SQLite to execute.
+SqlSource RewriteToDummySql(const SqlSource& source) {
+ return source.RewriteAllIgnoreExisting(
+ SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0"));
+}
+
} // namespace
PerfettoSqlEngine::PerfettoSqlEngine(StringPool* pool)
@@ -187,7 +222,7 @@
// statement for the last valid statement.
std::optional<SqliteEngine::PreparedStatement> res;
ExecutionStats stats;
- PerfettoSqlParser parser(std::move(sql_source));
+ PerfettoSqlParser parser(std::move(sql_source), macros_);
while (parser.Next()) {
std::optional<SqlSource> source;
if (auto* cf = std::get_if<PerfettoSqlParser::CreateFunction>(
@@ -200,17 +235,16 @@
&parser.statement())) {
RETURN_IF_ERROR(AddTracebackIfNeeded(
RegisterRuntimeTable(cst->name, cst->sql), parser.statement_sql()));
- // Since the rest of the code requires a statement, just use a no-value
- // dummy statement.
- source = parser.statement_sql().FullRewrite(
- SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0"));
+ source = RewriteToDummySql(parser.statement_sql());
} else if (auto* include = std::get_if<PerfettoSqlParser::Include>(
&parser.statement())) {
RETURN_IF_ERROR(ExecuteInclude(*include, parser));
- // Since the rest of the code requires a statement, just use a no-value
- // dummy statement.
- source = parser.statement_sql().FullRewrite(
- SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0"));
+ source = RewriteToDummySql(parser.statement_sql());
+ } else if (auto* macro = std::get_if<PerfettoSqlParser::CreateMacro>(
+ &parser.statement())) {
+ auto sql = macro->sql;
+ RETURN_IF_ERROR(ExecuteCreateMacro(*macro));
+ source = RewriteToDummySql(sql);
} else {
// If none of the above matched, this must just be an SQL statement
// directly executable by SQLite.
@@ -223,7 +257,7 @@
// Try to get SQLite to prepare the statement.
std::optional<SqliteEngine::PreparedStatement> cur_stmt;
{
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "QUERY_PREPARE");
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_TIMELINE, "QUERY_PREPARE");
auto stmt = engine_->PrepareStatement(std::move(*source));
RETURN_IF_ERROR(stmt.status());
cur_stmt = std::move(stmt);
@@ -238,9 +272,11 @@
// the previous statement so we don't have two clashing statements (e.g.
// SELECT * FROM v and DROP VIEW v) partially stepped into.
if (res && !res->IsDone()) {
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "STMT_STEP_UNTIL_DONE",
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_TIMELINE,
+ "STMT_STEP_UNTIL_DONE",
[&res](metatrace::Record* record) {
- record->AddArg("SQL", res->expanded_sql());
+ record->AddArg("Original SQL", res->original_sql());
+ record->AddArg("Executed SQL", res->sql());
});
while (res->Step()) {
}
@@ -253,11 +289,14 @@
// Step the newly prepared statement once. This is considered to be
// "executing" the statement.
{
- PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "STMT_FIRST_STEP",
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_TIMELINE, "STMT_FIRST_STEP",
[&res](metatrace::Record* record) {
- record->AddArg("SQL", res->expanded_sql());
+ record->AddArg("Original SQL", res->original_sql());
+ record->AddArg("Executed SQL", res->sql());
});
- PERFETTO_DLOG("Executing statement: %s", res->sql());
+ PERFETTO_DLOG("Executing statement");
+ PERFETTO_DLOG("Original SQL: %s", res->original_sql());
+ PERFETTO_DLOG("Executed SQL: %s", res->sql());
res->Step();
RETURN_IF_ERROR(res->status());
}
@@ -411,8 +450,8 @@
const PerfettoSqlParser::Include& include,
const PerfettoSqlParser& parser) {
std::string key = include.key;
- PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "Import",
- [key](metatrace::Record* r) { r->AddArg("Import", key); });
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_TIMELINE, "Include",
+ [key](metatrace::Record* r) { r->AddArg("Module", key); });
std::string module_name = sql_modules::GetModuleName(key);
auto module = FindModule(module_name);
if (!module)
@@ -447,11 +486,7 @@
if (!cf.is_table) {
RETURN_IF_ERROR(
RegisterSqlFunction(cf.replace, cf.prototype, cf.returns, cf.sql));
-
- // Since the rest of the code requires a statement, just use a no-value
- // dummy statement.
- return parser.statement_sql().FullRewrite(
- SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0"));
+ return RewriteToDummySql(parser.statement_sql());
}
RuntimeTableFunction::State state{cf.prototype, cf.sql, {}, {}, std::nullopt};
@@ -565,10 +600,59 @@
base::StackString<1024> create(
"CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
- return cf.sql.FullRewrite(
+ return cf.sql.RewriteAllIgnoreExisting(
SqlSource::FromTraceProcessorImplementation(create.ToStdString()));
}
+base::Status PerfettoSqlEngine::ExecuteCreateMacro(
+ const PerfettoSqlParser::CreateMacro& create_macro) {
+ // Check that the argument types is one of the allowed types.
+ for (const auto& [name, type] : create_macro.args) {
+ std::string lower_type = base::ToLower(type.sql());
+ if (lower_type != "tableorsubquery" && lower_type != "expr") {
+ // TODO(lalitm): add a link to create macro documentation.
+ return base::ErrStatus(
+ "%sMacro %s argument %s is unkown type %s. Allowed types: "
+ "TableOrSubquery, Expr",
+ type.AsTraceback(0).c_str(), create_macro.name.sql().c_str(),
+ name.sql().c_str(), type.sql().c_str());
+ }
+ }
+ std::string lower_return = base::ToLower(create_macro.returns.sql());
+ if (lower_return != "tableorsubquery" && lower_return != "expr") {
+ // TODO(lalitm): add a link to create macro documentation.
+ return base::ErrStatus(
+ "%sMacro %s return type %s is unknown. Allowed types: "
+ "TableOrSubquery, Expr",
+ create_macro.returns.AsTraceback(0).c_str(),
+ create_macro.name.sql().c_str(), create_macro.returns.sql().c_str());
+ }
+
+ std::vector<std::string> args;
+ for (const auto& arg : create_macro.args) {
+ args.push_back(arg.first.sql());
+ }
+ PerfettoSqlPreprocessor::Macro macro{
+ create_macro.replace,
+ create_macro.name.sql(),
+ std::move(args),
+ create_macro.sql,
+ };
+ if (auto it = macros_.Find(create_macro.name.sql()); it) {
+ if (!create_macro.replace) {
+ // TODO(lalitm): add a link to create macro documentation.
+ return base::ErrStatus("%sMacro already exists",
+ create_macro.name.AsTraceback(0).c_str());
+ }
+ *it = std::move(macro);
+ return base::OkStatus();
+ }
+ std::string name = macro.name;
+ auto it_and_inserted = macros_.Insert(std::move(name), std::move(macro));
+ PERFETTO_CHECK(it_and_inserted.second);
+ return base::OkStatus();
+}
+
RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
const std::string& name) const {
auto it = runtime_table_fn_states_.Find(base::ToLower(name));
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index f4a0abd..29c8206 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -25,6 +25,7 @@
#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/db/runtime_table.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
@@ -151,12 +152,15 @@
// Registers a SQL-defined trace processor C++ table with SQLite.
base::Status RegisterRuntimeTable(std::string name, SqlSource sql);
+ base::Status ExecuteCreateMacro(const PerfettoSqlParser::CreateMacro&);
+
std::unique_ptr<QueryCache> query_cache_;
StringPool* pool_ = nullptr;
base::FlatHashMap<std::string, std::unique_ptr<RuntimeTableFunction::State>>
runtime_table_fn_states_;
base::FlatHashMap<std::string, std::unique_ptr<RuntimeTable>> runtime_tables_;
base::FlatHashMap<std::string, sql_modules::RegisteredModule> modules_;
+ base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro> macros_;
std::unique_ptr<SqliteEngine> engine_;
};
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
index 16a9726..d577ea0 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
@@ -109,6 +109,24 @@
ASSERT_TRUE(res.ok());
}
+TEST_F(PerfettoSqlEngineTest, CreateMacro) {
+ auto res_create = engine_.Execute(SqlSource::FromExecuteQuery(
+ "CREATE PERFETTO MACRO foo() RETURNS TableOrSubquery AS select 42 AS x"));
+ ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
+
+ res_create = engine_.Execute(SqlSource::FromExecuteQuery(
+ "CREATE PERFETTO MACRO bar(x TableOrSubquery) RETURNS TableOrSubquery AS "
+ "select * from $x"));
+ ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
+
+ auto res = engine_.ExecuteUntilLastStatement(
+ SqlSource::FromExecuteQuery("bar!((foo!()))"));
+ ASSERT_TRUE(res.ok()) << res.status().c_message();
+ ASSERT_FALSE(res->stmt.IsDone());
+ ASSERT_EQ(sqlite3_column_int64(res->stmt.sqlite_stmt(), 0), 42);
+ ASSERT_FALSE(res->stmt.Step());
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
index a0b2688..33fc49b 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
@@ -17,13 +17,20 @@
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
#include <algorithm>
+#include <cctype>
#include <functional>
#include <optional>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
#include "src/trace_processor/util/status_macros.h"
@@ -78,13 +85,38 @@
std::not_fn(IsValidModuleWord)) == packages.end();
}
+std::string SerializeArgs(std::vector<std::pair<SqlSource, SqlSource>> args) {
+ bool comma = false;
+ std::string serialized;
+ for (const auto& [name, type] : args) {
+ if (comma) {
+ serialized.append(", ");
+ }
+ comma = true;
+ serialized.append(name.sql().c_str());
+ serialized.push_back(' ');
+ serialized.append(type.sql().c_str());
+ }
+ return serialized;
+}
+
} // namespace
-PerfettoSqlParser::PerfettoSqlParser(SqlSource sql)
- : tokenizer_(std::move(sql)) {}
+PerfettoSqlParser::PerfettoSqlParser(
+ SqlSource source,
+ const base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro>&
+ macros)
+ : preprocessor_(std::move(source), macros),
+ tokenizer_(SqlSource::FromTraceProcessorImplementation("")) {}
bool PerfettoSqlParser::Next() {
- PERFETTO_DCHECK(status_.ok());
+ PERFETTO_CHECK(status_.ok());
+
+ if (!preprocessor_.NextStatement()) {
+ status_ = preprocessor_.status();
+ return false;
+ }
+ tokenizer_.Reset(preprocessor_.statement());
State state = State::kStmtStart;
std::optional<Token> first_non_space_token;
@@ -120,7 +152,9 @@
switch (state) {
case State::kPassthrough:
- break;
+ statement_ = SqliteSql{};
+ statement_sql_ = preprocessor_.statement();
+ return true;
case State::kStmtStart:
if (TokenIsSqliteKeyword("create", token)) {
state = State::kCreate;
@@ -149,9 +183,8 @@
if (TokenIsSqliteKeyword("trigger", token)) {
// TODO(lalitm): add this to the "errors" documentation page
// explaining why this is the case.
- base::StackString<1024> err(
- "Creating triggers are not supported by trace processor.");
- return ErrorAtToken(token, err.c_str());
+ return ErrorAtToken(
+ token, "Creating triggers is not supported in PerfettoSQL.");
}
if (TokenIsCustomKeyword("perfetto", token)) {
state = State::kCreatePerfetto;
@@ -179,9 +212,13 @@
if (TokenIsSqliteKeyword("table", token)) {
return ParseCreatePerfettoTable(*first_non_space_token);
}
+ if (TokenIsCustomKeyword("macro", token)) {
+ return ParseCreatePerfettoMacro(state ==
+ State::kCreateOrReplacePerfetto);
+ }
base::StackString<1024> err(
- "Expected 'FUNCTION' or 'TABLE' after 'CREATE PERFETTO', received "
- "'%*s'.",
+ "Expected 'FUNCTION', 'TABLE' or 'MACRO' after 'CREATE PERFETTO', "
+ "received '%*s'.",
static_cast<int>(token.str.size()), token.str.data());
return ErrorAtToken(token, err.c_str());
}
@@ -254,10 +291,13 @@
return ErrorAtToken(lp, "Malformed function prototype: '(' expected");
}
- prototype.push_back('(');
- if (!ParseArgumentDefinitions(&prototype)) {
+ std::vector<Argument> args;
+ if (!ParseArgumentDefinitions(args)) {
return false;
}
+
+ prototype.push_back('(');
+ prototype.append(SerializeArgs(args));
prototype.push_back(')');
if (Token returns = tokenizer_.NextNonWhitespace();
@@ -276,9 +316,11 @@
return ErrorAtToken(lp, "Malformed table return: '(' expected");
}
// Table function return.
- if (!ParseArgumentDefinitions(&ret)) {
+ std::vector<Argument> ret_args;
+ if (!ParseArgumentDefinitions(ret_args)) {
return false;
}
+ ret = SerializeArgs(ret_args);
} else if (ret_token.token_type != SqliteTokenType::TK_ID) {
// TODO(lalitm): add a link to create function documentation.
return ErrorAtToken(ret_token, "Invalid return type");
@@ -301,28 +343,117 @@
return true;
}
-bool PerfettoSqlParser::ParseArgumentDefinitions(std::string* str) {
- for (Token tok = tokenizer_.Next();; tok = tokenizer_.Next()) {
- if (tok.token_type == SqliteTokenType::TK_RP) {
- return true;
+bool PerfettoSqlParser::ParseCreatePerfettoMacro(bool replace) {
+ Token name = tokenizer_.NextNonWhitespace();
+ if (name.token_type != SqliteTokenType::TK_ID) {
+ // TODO(lalitm): add a link to create macro documentation.
+ base::StackString<1024> err("Invalid macro name %.*s",
+ static_cast<int>(name.str.size()),
+ name.str.data());
+ return ErrorAtToken(name, err.c_str());
+ }
+
+ // TK_LP == '(' (i.e. left parenthesis).
+ if (Token lp = tokenizer_.NextNonWhitespace();
+ lp.token_type != SqliteTokenType::TK_LP) {
+ // TODO(lalitm): add a link to create macro documentation.
+ return ErrorAtToken(lp, "Malformed macro prototype: '(' expected");
+ }
+
+ std::vector<Argument> args;
+ if (!ParseArgumentDefinitions(args)) {
+ return false;
+ }
+
+ if (Token returns = tokenizer_.NextNonWhitespace();
+ !TokenIsCustomKeyword("returns", returns)) {
+ // TODO(lalitm): add a link to create macro documentation.
+ return ErrorAtToken(returns, "Expected keyword 'returns'");
+ }
+
+ Token returns_value = tokenizer_.NextNonWhitespace();
+ if (returns_value.token_type != SqliteTokenType::TK_ID) {
+ // TODO(lalitm): add a link to create function documentation.
+ return ErrorAtToken(returns_value, "Expected return type");
+ }
+
+ if (Token as_token = tokenizer_.NextNonWhitespace();
+ !TokenIsSqliteKeyword("as", as_token)) {
+ // TODO(lalitm): add a link to create macro documentation.
+ return ErrorAtToken(as_token, "Expected keyword 'as'");
+ }
+
+ Token first = tokenizer_.NextNonWhitespace();
+ Token tok = tokenizer_.NextTerminal();
+ statement_ = CreateMacro{
+ replace, tokenizer_.SubstrToken(name), std::move(args),
+ tokenizer_.SubstrToken(returns_value), tokenizer_.Substr(first, tok)};
+ return true;
+}
+
+bool PerfettoSqlParser::ParseArgumentDefinitions(std::vector<Argument>& res) {
+ enum TokenType {
+ kIdOrRp,
+ kId,
+ kType,
+ kCommaOrRp,
+ };
+
+ std::optional<Token> id = std::nullopt;
+ TokenType expected = kIdOrRp;
+ for (Token tok = tokenizer_.NextNonWhitespace();;
+ tok = tokenizer_.NextNonWhitespace()) {
+ // Keywords can be used as names accidentally so have an explicit error
+ // message for those.
+ if (tok.token_type == SqliteTokenType::TK_GENERIC_KEYWORD) {
+ base::StackString<1024> err(
+ "Malformed function/macro prototype: %.*s is a SQL keyword so "
+ "cannot appear in a prototype",
+ static_cast<int>(tok.str.size()), tok.str.data());
+ return ErrorAtToken(tok, err.c_str());
}
- if (tok.token_type == SqliteTokenType::TK_SPACE) {
- str->append(" ");
- continue;
+ if (expected == kCommaOrRp) {
+ PERFETTO_CHECK(expected == kCommaOrRp);
+ if (tok.token_type == SqliteTokenType::TK_RP) {
+ return true;
+ }
+ if (tok.token_type == SqliteTokenType::TK_COMMA) {
+ expected = kId;
+ continue;
+ }
+ return ErrorAtToken(tok, "')' or ',' expected");
}
- if (tok.token_type != SqliteTokenType::TK_ID &&
- tok.token_type != SqliteTokenType::TK_COMMA) {
- if (tok.token_type == SqliteTokenType::TK_GENERIC_KEYWORD) {
- base::StackString<1024> err(
- "Malformed function prototype: %.*s is a SQL keyword so cannot "
- "appear in a function prototype",
- static_cast<int>(tok.str.size()), tok.str.data());
+ if (expected == kType) {
+ if (tok.token_type != SqliteTokenType::TK_ID) {
+ // TODO(lalitm): add a link to documentation.
+ base::StackString<1024> err("%.*s is not a valid argument type",
+ static_cast<int>(tok.str.size()),
+ tok.str.data());
return ErrorAtToken(tok, err.c_str());
}
- // TODO(lalitm): add a link to create function documentation.
- return ErrorAtToken(tok, "')', ',', name or type expected");
+ PERFETTO_CHECK(id);
+ res.push_back(std::make_pair(tokenizer_.SubstrToken(*id),
+ tokenizer_.SubstrToken(tok)));
+ id = std::nullopt;
+ expected = kCommaOrRp;
+ continue;
}
- str->append(tok.str);
+
+ // kIdOrRp only happens on the very first token.
+ if (tok.token_type == SqliteTokenType::TK_RP && expected == kIdOrRp) {
+ return true;
+ }
+
+ if (tok.token_type != SqliteTokenType::TK_ID) {
+ // TODO(lalitm): add a link to documentation.
+ base::StackString<1024> err("%.*s is not a valid argument name",
+ static_cast<int>(tok.str.size()),
+ tok.str.data());
+ return ErrorAtToken(tok, err.c_str());
+ }
+ id = tok;
+ expected = kType;
+ continue;
}
}
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
index b7f67d5..1c38d43 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
@@ -18,10 +18,15 @@
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PARSER_H_
#include <optional>
+#include <string>
#include <string_view>
+#include <utility>
#include <variant>
+#include <vector>
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
@@ -63,12 +68,23 @@
struct Include {
std::string key;
};
- using Statement =
- std::variant<SqliteSql, CreateFunction, CreateTable, Include>;
+ // Indicates that the specified SQL was a CREATE PERFETTO MACRO statement
+ // with the following parameter.
+ struct CreateMacro {
+ bool replace;
+ SqlSource name;
+ std::vector<std::pair<SqlSource, SqlSource>> args;
+ SqlSource returns;
+ SqlSource sql;
+ };
+ using Statement = std::
+ variant<SqliteSql, CreateFunction, CreateTable, Include, CreateMacro>;
// Creates a new SQL parser with the a block of PerfettoSQL statements.
// Concretely, the passed string can contain >1 statement.
- explicit PerfettoSqlParser(SqlSource);
+ explicit PerfettoSqlParser(
+ SqlSource,
+ const base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro>&);
// Attempts to parse to the next statement in the SQL. Returns true if
// a statement was successfully parsed and false if EOF was reached or the
@@ -94,11 +110,15 @@
}
// Returns the error status for the parser. This will be |base::OkStatus()|
- // until
+ // until an unrecoverable error is encountered.
const base::Status& status() const { return status_; }
private:
- // This cannot be moved because we keep pointers into |sql_| in |tokenizer_|.
+ using Argument =
+ std::pair<SqlSource /* name token */, SqlSource /* type token */>;
+
+ // This cannot be moved because we keep pointers into |sql_| in
+ // |preprocessor_|.
PerfettoSqlParser(PerfettoSqlParser&&) = delete;
PerfettoSqlParser& operator=(PerfettoSqlParser&&) = delete;
@@ -110,11 +130,15 @@
bool ParseIncludePerfettoModule(SqliteTokenizer::Token first_non_space_token);
- bool ParseArgumentDefinitions(std::string*);
+ bool ParseCreatePerfettoMacro(bool replace);
+
+ bool ParseArgumentDefinitions(std::vector<Argument>&);
bool ErrorAtToken(const SqliteTokenizer::Token&, const char* error);
+ PerfettoSqlPreprocessor preprocessor_;
SqliteTokenizer tokenizer_;
+
base::Status status_;
std::optional<SqlSource> statement_sql_;
std::optional<Statement> statement_;
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
index b436225..d2391c3 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
@@ -22,6 +22,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "test/gtest_and_gmock.h"
@@ -34,42 +35,15 @@
using CreateFn = PerfettoSqlParser::CreateFunction;
using CreateTable = PerfettoSqlParser::CreateTable;
using Include = PerfettoSqlParser::Include;
-
-inline bool operator==(const SqlSource& a, const SqlSource& b) {
- return a.sql() == b.sql();
-}
-
-inline bool operator==(const SqliteSql&, const SqliteSql&) {
- return true;
-}
-
-inline bool operator==(const CreateFn& a, const CreateFn& b) {
- return std::tie(a.returns, a.is_table, a.prototype, a.replace, a.sql) ==
- std::tie(b.returns, b.is_table, b.prototype, b.replace, b.sql);
-}
-
-inline bool operator==(const CreateTable& a, const CreateTable& b) {
- return std::tie(a.name, a.sql) == std::tie(b.name, b.sql);
-}
-
-inline bool operator==(const Include& a, const Include& b) {
- return std::tie(a.key) == std::tie(b.key);
-}
+using CreateMacro = PerfettoSqlParser::CreateMacro;
namespace {
-SqlSource FindSubstr(const SqlSource& source, const std::string& needle) {
- size_t off = source.sql().find(needle);
- PERFETTO_CHECK(off != std::string::npos);
- return source.Substr(static_cast<uint32_t>(off),
- static_cast<uint32_t>(needle.size()));
-}
-
class PerfettoSqlParserTest : public ::testing::Test {
protected:
base::StatusOr<std::vector<PerfettoSqlParser::Statement>> Parse(
SqlSource sql) {
- PerfettoSqlParser parser(sql);
+ PerfettoSqlParser parser(sql, macros_);
std::vector<PerfettoSqlParser::Statement> results;
while (parser.Next()) {
results.push_back(std::move(parser.statement()));
@@ -79,6 +53,8 @@
}
return results;
}
+
+ base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro> macros_;
};
TEST_F(PerfettoSqlParserTest, Empty) {
@@ -87,7 +63,7 @@
TEST_F(PerfettoSqlParserTest, SemiColonTerminatedStatement) {
SqlSource res = SqlSource::FromExecuteQuery("SELECT * FROM slice;");
- PerfettoSqlParser parser(res);
+ PerfettoSqlParser parser(res, macros_);
ASSERT_TRUE(parser.Next());
ASSERT_EQ(parser.statement(), Statement{SqliteSql{}});
ASSERT_EQ(parser.statement_sql(), FindSubstr(res, "SELECT * FROM slice"));
@@ -96,7 +72,7 @@
TEST_F(PerfettoSqlParserTest, MultipleStmts) {
auto res =
SqlSource::FromExecuteQuery("SELECT * FROM slice; SELECT * FROM s");
- PerfettoSqlParser parser(res);
+ PerfettoSqlParser parser(res, macros_);
ASSERT_TRUE(parser.Next());
ASSERT_EQ(parser.statement(), Statement{SqliteSql{}});
ASSERT_EQ(parser.statement_sql().sql(),
@@ -109,7 +85,7 @@
TEST_F(PerfettoSqlParserTest, IgnoreOnlySpace) {
auto res = SqlSource::FromExecuteQuery(" ; SELECT * FROM s; ; ;");
- PerfettoSqlParser parser(res);
+ PerfettoSqlParser parser(res, macros_);
ASSERT_TRUE(parser.Next());
ASSERT_EQ(parser.statement(), Statement{SqliteSql{}});
ASSERT_EQ(parser.statement_sql().sql(),
@@ -155,7 +131,7 @@
TEST_F(PerfettoSqlParserTest, CreatePerfettoFunctionAndOther) {
auto res = SqlSource::FromExecuteQuery(
"create perfetto function foo() returns INT as select 1; select foo()");
- PerfettoSqlParser parser(res);
+ PerfettoSqlParser parser(res, macros_);
ASSERT_TRUE(parser.Next());
CreateFn fn{false, "foo()", "INT", FindSubstr(res, "select 1"), false};
ASSERT_EQ(parser.statement(), Statement{fn});
@@ -187,6 +163,46 @@
ASSERT_FALSE(Parse(res).status().ok());
}
+TEST_F(PerfettoSqlParserTest, CreatePerfettoMacro) {
+ auto res = SqlSource::FromExecuteQuery(
+ "create perfetto macro foo(a1 Expr, b1 TableOrSubquery,c3_d "
+ "TableOrSubquery2 ) returns TableOrSubquery3 as random sql snippet");
+ PerfettoSqlParser parser(res, macros_);
+ ASSERT_TRUE(parser.Next());
+ ASSERT_EQ(
+ parser.statement(),
+ Statement(CreateMacro{
+ false,
+ FindSubstr(res, "foo"),
+ {
+ {FindSubstr(res, "a1"), FindSubstr(res, "Expr")},
+ {FindSubstr(res, "b1"), FindSubstr(res, "TableOrSubquery")},
+ {FindSubstr(res, "c3_d"), FindSubstr(res, "TableOrSubquery2")},
+ },
+ FindSubstr(res, "TableOrSubquery3"),
+ FindSubstr(res, "random sql snippet")}));
+ ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreatePerfettoMacroAndOther) {
+ auto res = SqlSource::FromExecuteQuery(
+ "create perfetto macro foo() returns sql1 as random sql snippet; "
+ "select 1");
+ PerfettoSqlParser parser(res, macros_);
+ ASSERT_TRUE(parser.Next());
+ ASSERT_EQ(parser.statement(), Statement(CreateMacro{
+ false,
+ FindSubstr(res, "foo"),
+ {},
+ FindSubstr(res, "sql1"),
+ FindSubstr(res, "random sql snippet"),
+ }));
+ ASSERT_TRUE(parser.Next());
+ ASSERT_EQ(parser.statement(), Statement(SqliteSql{}));
+ ASSERT_EQ(parser.statement_sql(), FindSubstr(res, "select 1"));
+ ASSERT_FALSE(parser.Next());
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
new file mode 100644
index 0000000..135f6a8
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/sqlite/sql_source.h"
+#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+PerfettoSqlPreprocessor::PerfettoSqlPreprocessor(
+ SqlSource source,
+ const base::FlatHashMap<std::string, Macro>& macros)
+ : global_tokenizer_(std::move(source)), macros_(¯os) {}
+
+bool PerfettoSqlPreprocessor::NextStatement() {
+ PERFETTO_CHECK(status_.ok());
+
+ // Skip through any number of semi-colons (representing empty statements).
+ SqliteTokenizer::Token tok = global_tokenizer_.NextNonWhitespace();
+ while (tok.token_type == SqliteTokenType::TK_SEMI) {
+ tok = global_tokenizer_.NextNonWhitespace();
+ }
+
+ // If we still see a terminal token at this point, we must have hit EOF.
+ if (tok.IsTerminal()) {
+ PERFETTO_DCHECK(tok.token_type != SqliteTokenType::TK_SEMI);
+ return false;
+ }
+
+ SqlSource stmt =
+ global_tokenizer_.Substr(tok, global_tokenizer_.NextTerminal());
+ auto stmt_or = RewriteInternal(stmt, {});
+ if (stmt_or.ok()) {
+ statement_ = std::move(*stmt_or);
+ return true;
+ }
+ status_ = stmt_or.status();
+ return false;
+}
+
+base::StatusOr<SqlSource> PerfettoSqlPreprocessor::RewriteInternal(
+ const SqlSource& source,
+ const std::unordered_map<std::string, SqlSource>& arg_bindings) {
+ SqlSource::Rewriter rewriter(source);
+ SqliteTokenizer tokenizer(source);
+ for (SqliteTokenizer::Token tok = tokenizer.NextNonWhitespace(), prev;;
+ prev = tok, tok = tokenizer.NextNonWhitespace()) {
+ if (tok.IsTerminal()) {
+ break;
+ }
+ if (tok.token_type == SqliteTokenType::TK_VARIABLE &&
+ !seen_macros_.empty()) {
+ PERFETTO_CHECK(tok.str.size() >= 2);
+ if (tok.str[0] != '$') {
+ return ErrorAtToken(tokenizer, tok, "Variables must start with $");
+ }
+ auto binding_it = arg_bindings.find(std::string(tok.str.substr(1)));
+ if (binding_it == arg_bindings.end()) {
+ return ErrorAtToken(tokenizer, tok, "Variable not found");
+ }
+ tokenizer.RewriteToken(rewriter, tok, binding_it->second);
+ continue;
+ }
+ if (tok.token_type != SqliteTokenType::TK_ILLEGAL || tok.str != "!") {
+ continue;
+ }
+
+ base::StatusOr<MacroInvocation> invocation_or =
+ ParseMacroInvocation(tokenizer, tok, prev, arg_bindings);
+ RETURN_IF_ERROR(invocation_or.status());
+
+ seen_macros_.emplace(invocation_or->macro->name);
+ auto source_or =
+ RewriteInternal(invocation_or->macro->sql, invocation_or->arg_bindings);
+ RETURN_IF_ERROR(source_or.status());
+ seen_macros_.erase(invocation_or->macro->name);
+
+ tokenizer.Rewrite(rewriter, prev, tok, std::move(*source_or),
+ SqliteTokenizer::EndToken::kInclusive);
+ }
+ return std::move(rewriter).Build();
+}
+
+base::StatusOr<PerfettoSqlPreprocessor::MacroInvocation>
+PerfettoSqlPreprocessor::ParseMacroInvocation(
+ SqliteTokenizer& tokenizer,
+ SqliteTokenizer::Token& tok,
+ const SqliteTokenizer::Token& name_token,
+ const std::unordered_map<std::string, SqlSource>& arg_bindings) {
+ if (name_token.token_type == SqliteTokenType::TK_VARIABLE) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, name_token,
+ "Macro name cannot contain a variable");
+ }
+ if (name_token.token_type != SqliteTokenType::TK_ID) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, name_token, "Macro invocation is invalid");
+ }
+
+ // Get the opening left parenthesis.
+ tok = tokenizer.NextNonWhitespace();
+ if (tok.token_type != SqliteTokenType::TK_LP) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok, "( expected to open macro invocation");
+ }
+
+ std::string macro_name(name_token.str);
+ Macro* macro = macros_->Find(macro_name);
+ if (!macro) {
+ // TODO(b/290185551): add a link to macro documentation.
+ base::StackString<1024> err("Macro %s does not exist", macro_name.c_str());
+ return ErrorAtToken(tokenizer, name_token, err.c_str());
+ }
+
+ if (seen_macros_.count(macro_name)) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, name_token,
+ "Macros cannot be recursive or mutually recursive");
+ }
+
+ std::unordered_map<std::string, SqlSource> inner_bindings;
+ for (bool has_more = true; has_more;) {
+ base::StatusOr<InvocationArg> source_or =
+ ParseMacroInvocationArg(tokenizer, tok, !inner_bindings.empty());
+ RETURN_IF_ERROR(source_or.status());
+ if (source_or->arg) {
+ base::StatusOr<SqlSource> res =
+ RewriteInternal(*source_or->arg, arg_bindings);
+ RETURN_IF_ERROR(res.status());
+ if (macro->args.size() <= inner_bindings.size()) {
+ // TODO(lalitm): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, name_token,
+ "Macro invoked with too many args");
+ }
+ inner_bindings.emplace(macro->args[inner_bindings.size()], *res);
+ }
+ has_more = source_or->has_more;
+ }
+
+ if (inner_bindings.size() < macro->args.size()) {
+ // TODO(lalitm): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, name_token,
+ "Macro invoked with too few args");
+ }
+ PERFETTO_CHECK(inner_bindings.size() == macro->args.size());
+ return MacroInvocation{macro, inner_bindings};
+}
+
+base::StatusOr<PerfettoSqlPreprocessor::InvocationArg>
+PerfettoSqlPreprocessor::ParseMacroInvocationArg(SqliteTokenizer& tokenizer,
+ SqliteTokenizer::Token& tok,
+ bool has_prev_args) {
+ uint32_t nested_parens = 0;
+ bool seen_token_in_arg = false;
+ auto start = tokenizer.NextNonWhitespace();
+ for (tok = start;; tok = tokenizer.NextNonWhitespace()) {
+ if (tok.IsTerminal()) {
+ if (tok.token_type == SqliteTokenType::TK_SEMI) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok,
+ "Semi-colon is not allowed in macro invocation");
+ }
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok, "Macro invocation not complete");
+ }
+
+ bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP ||
+ tok.token_type == SqliteTokenType::TK_COMMA;
+ if (nested_parens == 0 && is_arg_terminator) {
+ bool token_required =
+ has_prev_args || tok.token_type != SqliteTokenType::TK_RP;
+ if (!seen_token_in_arg && token_required) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok, "Macro arg is empty");
+ }
+ return InvocationArg{
+ seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok))
+ : std::optional<SqlSource>(std::nullopt),
+ tok.token_type == SqliteTokenType::TK_COMMA,
+ };
+ }
+ seen_token_in_arg = true;
+
+ if (tok.token_type == SqliteTokenType::TK_LP) {
+ nested_parens++;
+ continue;
+ }
+ if (tok.token_type == SqliteTokenType::TK_RP) {
+ nested_parens--;
+ continue;
+ }
+ }
+}
+
+base::Status PerfettoSqlPreprocessor::ErrorAtToken(
+ const SqliteTokenizer& tokenizer,
+ const SqliteTokenizer::Token& token,
+ const char* error) {
+ std::string traceback = tokenizer.AsTraceback(token);
+ return base::ErrStatus("%s%s", traceback.c_str(), error);
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
new file mode 100644
index 0000000..de53786
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PREPROCESSOR_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PREPROCESSOR_H_
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <variant>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/sqlite/sql_source.h"
+#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Preprocessor for PerfettoSQL statements. The main responsiblity of this
+// class is to perform similar functions to the C/C++ preprocessor (e.g.
+// expanding macros). It is also responsible for splitting the given SQL into
+// statements.
+class PerfettoSqlPreprocessor {
+ public:
+ struct Macro {
+ bool replace;
+ std::string name;
+ std::vector<std::string> args;
+ SqlSource sql;
+ };
+
+ // Creates a preprocessor acting on the given SqlSource.
+ explicit PerfettoSqlPreprocessor(
+ SqlSource,
+ const base::FlatHashMap<std::string, Macro>&);
+
+ // Preprocesses the next SQL statement. Returns true if a statement was
+ // successfully preprocessed and false if EOF was reached or the statement was
+ // not preprocessed correctly.
+ //
+ // Note: if this function returns false, callers *must* call |status()|: it
+ // is undefined behaviour to not do so.
+ bool NextStatement();
+
+ // Returns the error status for the parser. This will be |base::OkStatus()|
+ // until an unrecoverable error is encountered.
+ const base::Status& status() const { return status_; }
+
+ // Returns the most-recent preprocessed SQL statement.
+ //
+ // Note: this function must not be called unless |NextStatement()| returned
+ // true.
+ SqlSource& statement() { return *statement_; }
+
+ private:
+ struct MacroInvocation {
+ const Macro* macro;
+ std::unordered_map<std::string, SqlSource> arg_bindings;
+ };
+ struct InvocationArg {
+ std::optional<SqlSource> arg;
+ bool has_more;
+ };
+
+ base::Status ErrorAtToken(const SqliteTokenizer& tokenizer,
+ const SqliteTokenizer::Token& token,
+ const char* error);
+ base::StatusOr<SqlSource> RewriteInternal(
+ const SqlSource&,
+ const std::unordered_map<std::string, SqlSource>& arg_bindings);
+
+ base::StatusOr<MacroInvocation> ParseMacroInvocation(
+ SqliteTokenizer& tokenizer,
+ SqliteTokenizer::Token& token,
+ const SqliteTokenizer::Token& name_token,
+ const std::unordered_map<std::string, SqlSource>& arg_bindings);
+ base::StatusOr<InvocationArg> ParseMacroInvocationArg(
+ SqliteTokenizer& tokenizer,
+ SqliteTokenizer::Token& token,
+ bool has_prev_args);
+
+ SqliteTokenizer global_tokenizer_;
+ const base::FlatHashMap<std::string, Macro>* macros_ = nullptr;
+ std::unordered_set<std::string> seen_macros_;
+ std::optional<SqlSource> statement_;
+ base::Status status_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PREPROCESSOR_H_
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc
new file mode 100644
index 0000000..104ee3d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
+
+#include <optional>
+#include <string>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using Macro = PerfettoSqlPreprocessor::Macro;
+
+class PerfettoSqlPreprocessorUnittest : public ::testing::Test {
+ protected:
+ base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro> macros_;
+};
+
+TEST_F(PerfettoSqlPreprocessorUnittest, Empty) {
+ PerfettoSqlPreprocessor preprocessor(SqlSource::FromExecuteQuery(""),
+ macros_);
+ ASSERT_FALSE(preprocessor.NextStatement());
+ ASSERT_TRUE(preprocessor.status().ok());
+}
+
+TEST_F(PerfettoSqlPreprocessorUnittest, SemiColonTerminatedStatement) {
+ auto source = SqlSource::FromExecuteQuery("SELECT * FROM slice;");
+ PerfettoSqlPreprocessor preprocessor(source, macros_);
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement(),
+ FindSubstr(source, "SELECT * FROM slice"));
+ ASSERT_FALSE(preprocessor.NextStatement());
+ ASSERT_TRUE(preprocessor.status().ok());
+}
+
+TEST_F(PerfettoSqlPreprocessorUnittest, IgnoreOnlySpace) {
+ auto source = SqlSource::FromExecuteQuery(" ; SELECT * FROM s; ; ;");
+ PerfettoSqlPreprocessor preprocessor(source, macros_);
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement(), FindSubstr(source, "SELECT * FROM s"));
+ ASSERT_FALSE(preprocessor.NextStatement());
+ ASSERT_TRUE(preprocessor.status().ok());
+}
+
+TEST_F(PerfettoSqlPreprocessorUnittest, MultipleStmts) {
+ auto source =
+ SqlSource::FromExecuteQuery("SELECT * FROM slice; SELECT * FROM s");
+ PerfettoSqlPreprocessor preprocessor(source, macros_);
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement(),
+ FindSubstr(source, "SELECT * FROM slice"));
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement(), FindSubstr(source, "SELECT * FROM s"));
+ ASSERT_FALSE(preprocessor.NextStatement());
+ ASSERT_TRUE(preprocessor.status().ok());
+}
+
+TEST_F(PerfettoSqlPreprocessorUnittest, CreateMacro) {
+ auto source = SqlSource::FromExecuteQuery(
+ "CREATE PERFETTO MACRO foo(a, b) AS SELECT $a + $b");
+ PerfettoSqlPreprocessor preprocessor(source, macros_);
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(
+ preprocessor.statement(),
+ FindSubstr(source, "CREATE PERFETTO MACRO foo(a, b) AS SELECT $a + $b"));
+ ASSERT_FALSE(preprocessor.NextStatement());
+ ASSERT_TRUE(preprocessor.status().ok());
+}
+
+TEST_F(PerfettoSqlPreprocessorUnittest, SingleMacro) {
+ auto foo = SqlSource::FromExecuteQuery(
+ "CREATE PERFETTO MACRO foo(a Expr, b Expr) Returns Expr AS "
+ "SELECT $a + $b");
+ macros_.Insert(
+ "foo",
+ Macro{false, "foo", {"a", "b"}, FindSubstr(foo, "SELECT $a + $b")});
+
+ auto source = SqlSource::FromExecuteQuery(
+ "foo!((select s.ts + r.dur from s, r), 1234); SELECT 1");
+ PerfettoSqlPreprocessor preprocessor(source, macros_);
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement().AsTraceback(0),
+ "Fully expanded statement\n"
+ " SELECT (select s.ts + r.dur from s, r) + 1234\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 1\n"
+ " foo!((select s.ts + r.dur from s, r), 1234)\n"
+ " ^\n"
+ " File \"stdin\" line 1 col 59\n"
+ " SELECT $a + $b\n"
+ " ^\n");
+ ASSERT_EQ(preprocessor.statement().AsTraceback(7),
+ "Fully expanded statement\n"
+ " SELECT (select s.ts + r.dur from s, r) + 1234\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 1\n"
+ " foo!((select s.ts + r.dur from s, r), 1234)\n"
+ " ^\n"
+ " File \"stdin\" line 1 col 66\n"
+ " SELECT $a + $b\n"
+ " ^\n"
+ " File \"stdin\" line 1 col 6\n"
+ " (select s.ts + r.dur from s, r)\n"
+ " ^\n");
+ ASSERT_EQ(preprocessor.statement().sql(),
+ "SELECT (select s.ts + r.dur from s, r) + 1234");
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement(), FindSubstr(source, "SELECT 1"));
+ ASSERT_FALSE(preprocessor.NextStatement());
+ ASSERT_TRUE(preprocessor.status().ok());
+}
+
+TEST_F(PerfettoSqlPreprocessorUnittest, NestedMacro) {
+ auto foo = SqlSource::FromExecuteQuery(
+ "CREATE PERFETTO MACRO foo(a Expr, b Expr) Returns Expr AS $a + $b");
+ macros_.Insert("foo", Macro{
+ false,
+ "foo",
+ {"a", "b"},
+ FindSubstr(foo, "$a + $b"),
+ });
+
+ auto bar = SqlSource::FromExecuteQuery(
+ "CREATE PERFETTO MACRO bar(a, b) Returns Expr AS "
+ "tfoo!($a, $b) + foo!($b, $a)");
+ macros_.Insert("bar", Macro{
+ false,
+ "bar",
+ {"a", "b"},
+ FindSubstr(bar, "foo!($a, $b) + foo!($b, $a)"),
+ });
+
+ auto source = SqlSource::FromExecuteQuery(
+ "SELECT bar!((select s.ts + r.dur from s, r), 1234); SELECT 1");
+ PerfettoSqlPreprocessor preprocessor(source, macros_);
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement().sql(),
+ "SELECT (select s.ts + r.dur from s, r) + 1234 + 1234 + "
+ "(select s.ts + r.dur from s, r)");
+ ASSERT_TRUE(preprocessor.NextStatement());
+ ASSERT_EQ(preprocessor.statement().sql(), "SELECT 1");
+}
+
+} // namespace
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
new file mode 100644
index 0000000..caca711
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_TEST_UTILS_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_TEST_UTILS_H_
+
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
+#include "src/trace_processor/sqlite/sql_source.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+inline bool operator==(const SqlSource& a, const SqlSource& b) {
+ return a.sql() == b.sql();
+}
+
+inline bool operator==(const PerfettoSqlParser::SqliteSql&,
+ const PerfettoSqlParser::SqliteSql&) {
+ return true;
+}
+
+inline bool operator==(const PerfettoSqlParser::CreateFunction& a,
+ const PerfettoSqlParser::CreateFunction& b) {
+ return std::tie(a.returns, a.is_table, a.prototype, a.replace, a.sql) ==
+ std::tie(b.returns, b.is_table, b.prototype, b.replace, b.sql);
+}
+
+inline bool operator==(const PerfettoSqlParser::CreateTable& a,
+ const PerfettoSqlParser::CreateTable& b) {
+ return std::tie(a.name, a.sql) == std::tie(b.name, b.sql);
+}
+
+inline bool operator==(const PerfettoSqlParser::Include& a,
+ const PerfettoSqlParser::Include& b) {
+ return std::tie(a.key) == std::tie(b.key);
+}
+
+constexpr bool operator==(const PerfettoSqlParser::CreateMacro& a,
+ const PerfettoSqlParser::CreateMacro& b) {
+ return std::tie(a.replace, a.name, a.sql, a.args) ==
+ std::tie(b.replace, b.name, b.sql, b.args);
+}
+
+inline std::ostream& operator<<(std::ostream& stream, const SqlSource& sql) {
+ return stream << "SqlSource(sql=" << testing::PrintToString(sql.sql()) << ")";
+}
+
+inline std::ostream& operator<<(std::ostream& stream,
+ const PerfettoSqlParser::Statement& line) {
+ if (std::get_if<PerfettoSqlParser::SqliteSql>(&line)) {
+ return stream << "SqliteSql()";
+ }
+ if (auto* fn = std::get_if<PerfettoSqlParser::CreateFunction>(&line)) {
+ return stream << "CreateFn(sql=" << testing::PrintToString(fn->sql)
+ << ", prototype=" << testing::PrintToString(fn->prototype)
+ << ", returns=" << testing::PrintToString(fn->returns)
+ << ", is_table=" << testing::PrintToString(fn->is_table)
+ << ", replace=" << testing::PrintToString(fn->replace) << ")";
+ }
+ if (auto* tab = std::get_if<PerfettoSqlParser::CreateTable>(&line)) {
+ return stream << "CreateTable(name=" << testing::PrintToString(tab->name)
+ << ", sql=" << testing::PrintToString(tab->sql) << ")";
+ }
+ if (auto* macro = std::get_if<PerfettoSqlParser::CreateMacro>(&line)) {
+ return stream << "CreateTable(name=" << testing::PrintToString(macro->name)
+ << ", args=" << testing::PrintToString(macro->args)
+ << ", replace=" << testing::PrintToString(macro->replace)
+ << ", sql=" << testing::PrintToString(macro->sql) << ")";
+ }
+ PERFETTO_FATAL("Unknown type");
+}
+
+template <typename T>
+inline bool operator==(const base::StatusOr<T>& a, const base::StatusOr<T>& b) {
+ return a.status().ok() == b.ok() &&
+ a.status().message() == b.status().message() &&
+ (!a.ok() || a.value() == b.value());
+}
+
+inline std::ostream& operator<<(std::ostream& stream, const base::Status& a) {
+ return stream << "base::Status(ok=" << a.ok()
+ << ", message=" << testing::PrintToString(a.message()) << ")";
+}
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& stream,
+ const base::StatusOr<T>& a) {
+ std::string val = a.ok() ? testing::PrintToString(a.value()) : "";
+ return stream << "base::StatusOr(status="
+ << testing::PrintToString(a.status()) << ", value=" << val
+ << ")";
+}
+
+inline SqlSource FindSubstr(const SqlSource& source,
+ const std::string& needle) {
+ size_t off = source.sql().find(needle);
+ PERFETTO_CHECK(off != std::string::npos);
+ return source.Substr(static_cast<uint32_t>(off),
+ static_cast<uint32_t>(needle.size()));
+}
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_TEST_UTILS_H_
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
index 7804e7a..247d79b 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -126,7 +126,7 @@
base::Status RuntimeTableFunction::Cursor::Filter(const QueryConstraints& qc,
sqlite3_value** argv,
FilterHistory) {
- PERFETTO_TP_TRACE(metatrace::Category::FUNCTION, "TABLE_FUNCTION_CALL",
+ PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL, "TABLE_FUNCTION_CALL",
[this](metatrace::Record* r) {
r->AddArg("Function",
state_->prototype.function_name.c_str());
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
index 2462d59..15b1f45 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
@@ -404,7 +404,7 @@
base::Status SpanJoinOperatorTable::Cursor::Filter(const QueryConstraints& qc,
sqlite3_value** argv,
FilterHistory) {
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "SPAN_JOIN_XFILTER");
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, "SPAN_JOIN_XFILTER");
bool t1_partitioned_mixed =
t1_.definition()->IsPartitioned() &&
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
index 9a87d07..73f4748 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
@@ -18,6 +18,7 @@
#include <unordered_set>
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
@@ -342,6 +343,10 @@
context_->storage.get(), values.upid, values.upid_group,
values.time_constraints);
}
+ if (!table) {
+ return base::ErrStatus("Failed to build flamegraph");
+ }
+
if (!values.focus_str.empty()) {
table =
FocusTable(context_->storage.get(), std::move(table), values.focus_str);
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
index 68c8dd1..bf280ea 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
@@ -215,15 +215,16 @@
-- Monitor contention slices that are neither blocking nor blocked by another monitor contention
-- slice. They neither have |parent_id| nor |child_id| fields.
CREATE TABLE internal_isolated AS
-WITH
- x AS (
+WITH parents_and_children AS (
+ SELECT id FROM internal_children
+ UNION ALL
+ SELECT id FROM internal_parents
+), isolated AS (
SELECT id FROM android_monitor_contention
EXCEPT
- SELECT id FROM internal_children
- UNION ALL
- SELECT id FROM internal_parents
+ SELECT id FROM parents_and_children
)
-SELECT * FROM android_monitor_contention JOIN x USING (id);
+SELECT * FROM android_monitor_contention JOIN isolated USING (id);
-- Contains parsed monitor contention slices with the parent-child relationships.
--
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
index ef4921a..b0f9310 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
@@ -440,19 +440,53 @@
SELECT ts, dur, id, slice_id, slice_depth, slice_name
FROM internal_span_graph_slice_sp;
--- |experimental_thread_executing_span_graph| + thread_state view span joined with critical_path information.
-CREATE VIRTUAL TABLE internal_critical_path_thread_state_sp
-USING
- SPAN_JOIN(
- internal_span_graph_thread_state PARTITIONED id,
- internal_critical_path PARTITIONED id);
+-- |experimental_thread_executing_span_graph| + thread_state view joined with critical_path information.
+CREATE PERFETTO TABLE internal_critical_path_thread_state AS
+WITH span AS MATERIALIZED (
+ SELECT * FROM internal_critical_path
+ ),
+ span_starts AS (
+ SELECT
+ span.id,
+ span.utid,
+ span.critical_path_id,
+ span.critical_path_blocked_dur,
+ span.critical_path_blocked_state,
+ span.critical_path_blocked_function,
+ span.critical_path_utid,
+ thread_state_id,
+ MAX(thread_state.ts, span.ts) AS ts,
+ span.ts + span.dur AS span_end_ts,
+ thread_state.ts + thread_state.dur AS thread_state_end_ts,
+ thread_state.state,
+ thread_state.function,
+ thread_state.cpu
+ FROM span
+ JOIN internal_span_graph_thread_state_sp thread_state USING(id)
+ )
+SELECT
+ id,
+ thread_state_id,
+ ts,
+ MIN(span_end_ts, thread_state_end_ts) - ts AS dur,
+ utid,
+ state,
+ function,
+ cpu,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function,
+ critical_path_utid
+FROM span_starts
+WHERE MIN(span_end_ts, thread_state_end_ts) - ts > 0;
-- |experimental_thread_executing_span_graph| + thread_state + critical_path span joined with
-- |experimental_thread_executing_span_graph| + slice view.
CREATE VIRTUAL TABLE internal_critical_path_sp
USING
SPAN_LEFT_JOIN(
- internal_critical_path_thread_state_sp PARTITIONED id,
+ internal_critical_path_thread_state PARTITIONED id,
internal_span_graph_slice PARTITIONED id);
-- Flattened slices span joined with their thread_states. This contains the 'self' information
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 366ebfc..031e3c8 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -132,14 +132,16 @@
TraceProcessor::MetatraceCategories MetatraceCategoriesToPublicEnum(
ProtoEnum categories) {
switch (categories) {
- case ProtoEnum::TOPLEVEL:
- return TraceProcessor::MetatraceCategories::TOPLEVEL;
- case ProtoEnum::QUERY:
- return TraceProcessor::MetatraceCategories::QUERY;
- case ProtoEnum::FUNCTION:
- return TraceProcessor::MetatraceCategories::FUNCTION;
+ case ProtoEnum::QUERY_TIMELINE:
+ return TraceProcessor::MetatraceCategories::QUERY_TIMELINE;
+ case ProtoEnum::QUERY_DETAILED:
+ return TraceProcessor::MetatraceCategories::QUERY_DETAILED;
+ case ProtoEnum::FUNCTION_CALL:
+ return TraceProcessor::MetatraceCategories::FUNCTION_CALL;
case ProtoEnum::DB:
return TraceProcessor::MetatraceCategories::DB;
+ case ProtoEnum::API_TIMELINE:
+ return TraceProcessor::MetatraceCategories::API_TIMELINE;
case ProtoEnum::ALL:
return TraceProcessor::MetatraceCategories::ALL;
case ProtoEnum::NONE:
@@ -288,7 +290,7 @@
util::Status Rpc::Parse(const uint8_t* data, size_t len) {
PERFETTO_TP_TRACE(
- metatrace::Category::TOPLEVEL, "RPC_PARSE",
+ metatrace::Category::API_TIMELINE, "RPC_PARSE",
[&](metatrace::Record* r) { r->AddArg("length", std::to_string(len)); });
if (eof_) {
// Reset the trace processor state if another trace has been previously
@@ -310,7 +312,8 @@
}
void Rpc::NotifyEndOfFile() {
- PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "RPC_NOTIFY_END_OF_FILE");
+ PERFETTO_TP_TRACE(metatrace::Category::API_TIMELINE,
+ "RPC_NOTIFY_END_OF_FILE");
trace_processor_->NotifyEndOfFile();
eof_ = true;
@@ -372,7 +375,7 @@
protos::pbzero::QueryArgs::Decoder query(args, len);
std::string sql = query.sql_query().ToStdString();
PERFETTO_DLOG("[RPC] Query < %s", sql.c_str());
- PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "RPC_QUERY",
+ PERFETTO_TP_TRACE(metatrace::Category::API_TIMELINE, "RPC_QUERY",
[&](metatrace::Record* r) {
r->AddArg("SQL", sql);
if (query.has_tag()) {
@@ -402,7 +405,7 @@
metric_names.emplace_back(it->as_std_string());
}
- PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "RPC_COMPUTE_METRIC",
+ PERFETTO_TP_TRACE(metatrace::Category::API_TIMELINE, "RPC_COMPUTE_METRIC",
[&](metatrace::Record* r) {
for (const auto& metric : metric_names) {
r->AddArg("Metric", metric);
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 027f8ce..04967f9 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -506,9 +506,9 @@
TryCacheCreateSortedTable(qc, history);
break;
case TableComputation::kTableFunction: {
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "DYNAMIC_TABLE_GENERATE",
- [this](metatrace::Record* r) {
- r->AddArg("Table", db_sqlite_table_->name());
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
+ "TABLE_FUNCTION_CALL", [this](metatrace::Record* r) {
+ r->AddArg("Name", db_sqlite_table_->name());
});
// If we have a dynamically created table, regenerate the table based on
// the new constraints.
@@ -530,7 +530,7 @@
}
PERFETTO_TP_TRACE(
- metatrace::Category::QUERY, "DB_TABLE_FILTER_AND_SORT",
+ metatrace::Category::QUERY_DETAILED, "DB_TABLE_FILTER_AND_SORT",
[this](metatrace::Record* r) {
const Table* source = SourceTable();
r->AddArg("Table", db_sqlite_table_->name());
diff --git a/src/trace_processor/sqlite/sql_source.cc b/src/trace_processor/sqlite/sql_source.cc
index 5e12cd1..570e87e 100644
--- a/src/trace_processor/sqlite/sql_source.cc
+++ b/src/trace_processor/sqlite/sql_source.cc
@@ -20,12 +20,15 @@
#include <algorithm>
#include <cstdint>
#include <iterator>
+#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
+#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/sys_types.h"
@@ -84,6 +87,9 @@
} // namespace
+SqlSource::SqlSource() = default;
+SqlSource::SqlSource(Node node) : root_(std::move(node)) {}
+
SqlSource::SqlSource(std::string sql,
std::string name,
bool include_traceback_header) {
@@ -140,9 +146,15 @@
return source;
}
-SqlSource SqlSource::FullRewrite(SqlSource source) const {
- SqlSource::Rewriter rewriter(*this);
- rewriter.Rewrite(0, static_cast<uint32_t>(sql().size()), source);
+SqlSource SqlSource::RewriteAllIgnoreExisting(SqlSource source) const {
+ // Reset any rewrites.
+ SqlSource copy = *this;
+ copy.root_.rewritten_sql = copy.root_.original_sql;
+ copy.root_.rewrites.clear();
+
+ SqlSource::Rewriter rewriter(std::move(copy));
+ rewriter.Rewrite(0, static_cast<uint32_t>(root_.original_sql.size()),
+ std::move(source));
return std::move(rewriter).Build();
}
@@ -162,8 +174,9 @@
}
std::string SqlSource::Node::AsTraceback(uint32_t rewritten_offset) const {
+ PERFETTO_CHECK(rewritten_offset <= rewritten_sql.size());
uint32_t original_offset = RewrittenOffsetToOriginalOffset(rewritten_offset);
- std::string res = SelfTraceback(original_offset);
+ std::string res = SelfTraceback(rewritten_offset, original_offset);
if (auto opt_idx = RewriteForOriginalOffset(original_offset); opt_idx) {
const Rewrite& rewrite = rewrites[*opt_idx];
PERFETTO_CHECK(rewritten_offset >= rewrite.rewritten_sql_start);
@@ -174,12 +187,22 @@
return res;
}
-std::string SqlSource::Node::SelfTraceback(uint32_t original_offset) const {
+std::string SqlSource::Node::SelfTraceback(uint32_t rewritten_offset,
+ uint32_t original_offset) const {
+ PERFETTO_DCHECK(original_offset <= original_sql.size());
auto [o_context, o_caret_pos] =
SqlContextAndCaretPos(original_sql, original_offset);
std::string header;
if (include_traceback_header) {
- header = "Traceback (most recent call last):\n";
+ if (!rewrites.empty()) {
+ auto [r_context, r_caret_pos] =
+ SqlContextAndCaretPos(rewritten_sql, rewritten_offset);
+ std::string caret = std::string(r_caret_pos, ' ') + "^";
+ base::StackString<1024> str("Fully expanded statement\n %s\n %s\n",
+ r_context.c_str(), caret.c_str());
+ header.append(str.c_str());
+ }
+ header += "Traceback (most recent call last):\n";
}
auto line_and_col =
@@ -193,17 +216,54 @@
}
SqlSource::Node SqlSource::Node::Substr(uint32_t offset, uint32_t len) const {
- PERFETTO_CHECK(rewrites.empty());
- auto line_and_col =
- GetLineAndColumnForOffset(rewritten_sql, line, col, offset);
+ uint32_t offset_end = offset + len;
+ PERFETTO_CHECK(offset_end <= rewritten_sql.size());
+
+ uint32_t original_offset_start = RewrittenOffsetToOriginalOffset(offset);
+ uint32_t original_offset_end = RewrittenOffsetToOriginalOffset(offset_end);
+ std::vector<Rewrite> new_rewrites;
+ for (const Rewrite& rewrite : rewrites) {
+ if (offset >= rewrite.rewritten_sql_end) {
+ continue;
+ }
+ if (offset_end < rewrite.rewritten_sql_start) {
+ break;
+ }
+ // Special case: when the end of the substr is in the middle of a rewrite,
+ // we actually want to capture the original SQL up to the end of the
+ // rewrite, not just to the start as |ChildRewrittenOffset| returns.
+ if (offset_end < rewrite.rewritten_sql_end) {
+ original_offset_end = rewrite.original_sql_end;
+ }
+ uint32_t bounded_start = std::max(offset, rewrite.rewritten_sql_start);
+ uint32_t bounded_end = std::min(offset_end, rewrite.rewritten_sql_end);
+
+ uint32_t nested_start = bounded_start - rewrite.rewritten_sql_start;
+ uint32_t nested_len = bounded_end - bounded_start;
+
+ new_rewrites.push_back(Rewrite{
+ rewrite.original_sql_start - original_offset_start,
+ rewrite.original_sql_end - original_offset_start,
+ bounded_start - offset,
+ bounded_end - offset,
+ rewrite.rewrite_node.Substr(nested_start, nested_len),
+ });
+ }
+ std::string new_original = original_sql.substr(
+ original_offset_start, original_offset_end - original_offset_start);
+ std::string new_rewritten = rewritten_sql.substr(offset, len);
+ PERFETTO_DCHECK(ApplyRewrites(new_original, new_rewrites) == new_rewritten);
+
+ auto line_and_col = GetLineAndColumnForOffset(rewritten_sql, line, col,
+ original_offset_start);
return Node{
name,
include_traceback_header,
line_and_col.first,
line_and_col.second,
- original_sql.substr(offset, len),
- {},
- rewritten_sql.substr(offset, len),
+ new_original,
+ std::move(new_rewrites),
+ new_rewritten,
};
}
@@ -239,32 +299,94 @@
return std::nullopt;
}
-SqlSource::Rewriter::Rewriter(SqlSource source) : orig_(std::move(source)) {
- PERFETTO_CHECK(!orig_.IsRewritten());
+SqlSource::Rewriter::Rewriter(SqlSource source)
+ : Rewriter(std::move(source.root_)) {}
+SqlSource::Rewriter::Rewriter(Node source) : orig_(std::move(source)) {
+ // Note: it's important that we *don't* move out of |orig_| here as we want to
+ // be able to access the untouched offsets through
+ // calls to |RewrittenOffsetToOriginalOffset| etc.
+ for (const SqlSource::Rewrite& rewrite : orig_.rewrites) {
+ nested_.push_back(SqlSource::Rewriter(rewrite.rewrite_node));
+ }
}
-void SqlSource::Rewriter::Rewrite(uint32_t start,
- uint32_t end,
+void SqlSource::Rewriter::Rewrite(uint32_t rewritten_start,
+ uint32_t rewritten_end,
SqlSource source) {
- PERFETTO_CHECK(start < end);
- PERFETTO_CHECK(end <= orig_.sql().size());
+ PERFETTO_CHECK(rewritten_start < rewritten_end);
+ PERFETTO_CHECK(rewritten_end <= orig_.rewritten_sql.size());
- uint32_t source_size = static_cast<uint32_t>(source.sql().size());
-
- uint32_t rewritten_start =
- start + rewritten_bytes_in_rewrites - original_bytes_in_rewrites;
- orig_.root_.rewrites.push_back(SqlSource::Rewrite{
- start, end, rewritten_start, rewritten_start + source_size,
- std::move(source.root_)});
-
- original_bytes_in_rewrites += end - start;
- rewritten_bytes_in_rewrites += source_size;
+ uint32_t original_start =
+ orig_.RewrittenOffsetToOriginalOffset(rewritten_start);
+ std::optional<uint32_t> maybe_rewrite =
+ orig_.RewriteForOriginalOffset(original_start);
+ if (maybe_rewrite) {
+ const SqlSource::Rewrite& rewrite = orig_.rewrites[*maybe_rewrite];
+ nested_[*maybe_rewrite].Rewrite(
+ rewritten_start - rewrite.rewritten_sql_start,
+ rewritten_end - rewrite.rewritten_sql_start, std::move(source));
+ } else {
+ uint32_t original_end =
+ orig_.RewrittenOffsetToOriginalOffset(rewritten_end);
+ non_nested_.push_back(SqlSource::Rewrite{
+ original_start,
+ original_end,
+ std::numeric_limits<uint32_t>::max(), // Dummy, corrected in |Build|.
+ std::numeric_limits<uint32_t>::max(), // Dummy, corrected in |Build|.
+ std::move(source.root_),
+ });
+ }
}
SqlSource SqlSource::Rewriter::Build() && {
- orig_.root_.rewritten_sql =
- ApplyRewrites(orig_.root_.original_sql, orig_.root_.rewrites);
- return orig_;
+ // Phase 1: finalize all the nested rewrites and merge both nested and
+ // non-nested into a single vector.
+ std::vector<SqlSource::Rewrite> all_rewrites = std::move(non_nested_);
+ for (uint32_t i = 0; i < nested_.size(); ++i) {
+ const SqlSource::Rewrite orig_rewrite = orig_.rewrites[i];
+ all_rewrites.push_back(SqlSource::Rewrite{
+ orig_rewrite.original_sql_start,
+ orig_rewrite.original_sql_end,
+ std::numeric_limits<uint32_t>::max(), // Dummy, corrected in phase 3.
+ std::numeric_limits<uint32_t>::max(), // Dummy, corrected in phase 3.
+ std::move(nested_[i]).Build().root_,
+ });
+ }
+
+ // Phase 2: sort the new rewrite vector by original offset and verify that the
+ // original offsets are monotonic and non-overlapping.
+ std::sort(all_rewrites.begin(), all_rewrites.end(),
+ [](const SqlSource::Rewrite& a, const SqlSource::Rewrite& b) {
+ return a.original_sql_start < b.original_sql_start;
+ });
+ for (uint32_t i = 1; i < all_rewrites.size(); ++i) {
+ PERFETTO_CHECK(all_rewrites[i - 1].original_sql_end <=
+ all_rewrites[i].original_sql_start);
+ }
+
+ // Phase 3: compute the new rewritten offsets and assign them to the rewrites.
+ // Also unset the traceback flag for all rewrites.
+ uint32_t original_bytes_in_rewrites = 0;
+ uint32_t rewritten_bytes_in_rewrites = 0;
+ for (SqlSource::Rewrite& rewrite : all_rewrites) {
+ uint32_t source_size =
+ static_cast<uint32_t>(rewrite.rewrite_node.rewritten_sql.size());
+
+ rewrite.rewritten_sql_start = rewrite.original_sql_start +
+ rewritten_bytes_in_rewrites -
+ original_bytes_in_rewrites;
+ rewrite.rewritten_sql_end = rewrite.rewritten_sql_start + source_size;
+ rewrite.rewrite_node.include_traceback_header = false;
+
+ original_bytes_in_rewrites +=
+ rewrite.original_sql_end - rewrite.original_sql_start;
+ rewritten_bytes_in_rewrites += source_size;
+ }
+
+ // Phase 4: update the node to reflect the new rewrites.
+ orig_.rewrites = std::move(all_rewrites);
+ orig_.rewritten_sql = ApplyRewrites(orig_.original_sql, orig_.rewrites);
+ return SqlSource(std::move(orig_));
}
} // namespace trace_processor
diff --git a/src/trace_processor/sqlite/sql_source.h b/src/trace_processor/sqlite/sql_source.h
index b4b9870..66e4f31 100644
--- a/src/trace_processor/sqlite/sql_source.h
+++ b/src/trace_processor/sqlite/sql_source.h
@@ -78,19 +78,19 @@
// at |offset| with |len| characters.
SqlSource Substr(uint32_t offset, uint32_t len) const;
- // Creates a SqlSource instance with the execution SQL rewritten to
- // |rewrite_sql| but preserving the context from |this|.
+ // Rewrites the SQL backing |this| to SQL from |source| ignoring any existing
+ // rewrites in |this|.
//
// This is useful when PerfettoSQL statements are transpiled into SQLite
// statements but we want to preserve the context of the original statement.
- //
- // Note: this function should only be called if |this| has not already been
- // rewritten (i.e. it is undefined behaviour if |IsRewritten()| returns true).
- SqlSource FullRewrite(SqlSource) const;
+ SqlSource RewriteAllIgnoreExisting(SqlSource source) const;
// Returns the SQL string backing this SqlSource instance;
const std::string& sql() const { return root_.rewritten_sql; }
+ // Returns the original SQL string backing this SqlSource instance;
+ const std::string& original_sql() const { return root_.original_sql; }
+
// Returns whether this SqlSource has been rewritten.
bool IsRewritten() const { return root_.IsRewritten(); }
@@ -102,7 +102,7 @@
// Suppose that we have the the following situation:
// User: `SELECT foo!(a) FROM bar!(slice) a`
// foo : `$1.x, $1.y`
- // bar : `(SELECT baz!($1) FROM $1 LIMIT 1)`
+ // bar : `(SELECT baz!($1) FROM $1)`
// baz : `$1.x, $1.y, $1.z`
//
// We want to expand this to
@@ -127,8 +127,8 @@
// rewritten_sql: "SELECT a.x, a.y FROM (SELECT slice.x, slice.y, slice.z
// FROM slice) a"
// rewrites: [
- // {start: 7, end: 12, node: foo},
- // {start: 17, end: 26, node: bar}]
+ // {original_sql_start: 7, original_sql_end: 14, node: foo},
+ // {original_sql_start: 20, original_sql_end: 31, node: bar}]
// ]
// }
// foo {
@@ -139,7 +139,7 @@
// bar {
// original_sql: "(SELECT baz!($1) FROM $1 LIMIT 1)"
// rewritten_sql: "(SELECT slice.x, slice.y, slice.z FROM slice)"
- // rewrites: [{start: 8, end: 16, node: baz}]
+ // rewrites: [{original_sql_start: 8, original_sql_end: 16, node: baz}]
// }
// baz {
// original_sql = "$1.x, $1.y, $1.z"
@@ -170,7 +170,8 @@
// Returns the "traceback" for this node only. See |SqlSource::AsTraceback|
// for details.
- std::string SelfTraceback(uint32_t original_offset) const;
+ std::string SelfTraceback(uint32_t rewritten_offset,
+ uint32_t original_offset) const;
Node Substr(uint32_t rewritten_offset, uint32_t rewritten_len) const;
@@ -185,14 +186,22 @@
// IMPORTANT: if |rewritten_offset| is *inside* a rewrite, the original
// offset will point to the *start of the rewrite*. For example, if
// we have:
- // original_sql: "SELECT foo!(a) FROM slice"
- // rewritten_sql: "SELECT a.x, a.y FROM slice"
- // rewrites: [{start: 7, end: 12, node: foo}]
+ // original_sql: "SELECT foo!(a) FROM slice a"
+ // rewritten_sql: "SELECT a.x, a.y FROM slice a"
+ // rewrites: [
+ // {
+ // original_sql_start: 7,
+ // original_sql_end: 14,
+ // rewritten_sql_start: 7,
+ // rewritten_sql_end: 15,
+ // node: foo
+ // }
+ // ]
// then:
// RewrittenOffsetToOriginalOffset(7) == 7 // 7 = start of foo
// RewrittenOffsetToOriginalOffset(14) == 7 // 7 = start of foo
- // RewrittenOffsetToOriginalOffset(15) == 12 // 12 = end of foo
- // RewrittenOffsetToOriginalOffset(16) == 13 // 12 = end of foo
+ // RewrittenOffsetToOriginalOffset(15) == 14 // 14 = end of foo
+ // RewrittenOffsetToOriginalOffset(16) == 15
uint32_t RewrittenOffsetToOriginalOffset(uint32_t rewritten_offset) const;
// Given an |original_offset| for this node, returns the index of a
@@ -218,7 +227,8 @@
Node rewrite_node;
};
- SqlSource() = default;
+ SqlSource();
+ explicit SqlSource(Node);
SqlSource(std::string sql, std::string name, bool include_traceback_header);
static std::string ApplyRewrites(const std::string&,
@@ -233,21 +243,56 @@
// Creates a Rewriter object which can be used to rewrite the SQL backing
// |source|.
//
- // Note: this function should only be called if |source| has not already been
- // rewritten (i.e. it is undefined behaviour if |source.IsRewritten()| returns
- // true).
+ // Note that rewrites of portions of the SQL which have already been rewritten
+ // is supported but *only in limited cases*. Specifically, the new rewrite
+ // must not cross the boundary of any existing rewrite.
+ //
+ // For example, if we have:
+ // SqlSource {
+ // original_sql: "SELECT foo!(a) FROM bar!(slice) a"
+ // rewritten_sql: "SELECT a.x, a.y FROM (SELECT slice.x FROM slice) a"
+ // }
+ // then the following are valid:
+ // # Replaces "SELECT " with "INSERT ". Valid because it does not touch
+ // # any rewrite.
+ // Rewrite(0, 7, "INSERT ")
+ //
+ // # Replaces "a.x, a." with "a.z, ". Valid because it only touches the
+ // # contents of the existing "foo" rewrite.
+ // Rewrite(7, 14, "a.z, ")
+ // while the following are invalid:
+ // # Fails to replace "SELECT a" with "I". Invalid because it affects both
+ // # non-rewritten source and the "foo" rewrite.
+ // Rewrite(0, 8, "I")
+ //
+ // # Fails to replace "a.x, a.y FROM (" with "(". Invalid because it affects
+ // # the "foo" rewrite, non-rewritten source and the "bar" rewrite.
+ // Rewrite(7, 23, "(")
explicit Rewriter(SqlSource source);
- // Replaces the SQL between |start| and |end| with the contents of |rewrite|.
- void Rewrite(uint32_t start, uint32_t end, SqlSource rewrite);
+ // Replaces the SQL in |source.rewritten_sql| between |rewritten_start| and
+ // |rewritten_end| with the contents of |rewrite|.
+ //
+ // Note that calls to Rewrite must be monontonic and non-overlapping. i.e.
+ // if Rewrite(0, 10) is called, the next |rewritten_end| must be greater than
+ // or equal to 10.
+ //
+ // Note also that all offsets passed to this function correspond to offsets
+ // into |source.rewritten_sql|: past calls to rewrite do not affect future
+ // offsets.
+ void Rewrite(uint32_t rewritten_start,
+ uint32_t rewritten_end,
+ SqlSource rewrite);
// Returns the rewritten SqlSource instance.
SqlSource Build() &&;
private:
- SqlSource orig_;
- uint32_t original_bytes_in_rewrites = 0;
- uint32_t rewritten_bytes_in_rewrites = 0;
+ explicit Rewriter(Node);
+
+ Node orig_;
+ std::vector<SqlSource::Rewriter> nested_;
+ std::vector<SqlSource::Rewrite> non_nested_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/sqlite/sql_source_unittest.cc b/src/trace_processor/sqlite/sql_source_unittest.cc
index 945144e..adca9db 100644
--- a/src/trace_processor/sqlite/sql_source_unittest.cc
+++ b/src/trace_processor/sqlite/sql_source_unittest.cc
@@ -53,14 +53,17 @@
" ^\n");
}
-TEST(SqlSourceTest, FullRewrite) {
+TEST(SqlSourceTest, RewriteAllIgnoreExisting) {
SqlSource source =
SqlSource::FromExecuteQuery("macro!()")
- .FullRewrite(SqlSource::FromTraceProcessorImplementation(
+ .RewriteAllIgnoreExisting(SqlSource::FromTraceProcessorImplementation(
"SELECT * FROM slice"));
ASSERT_EQ(source.sql(), "SELECT * FROM slice");
ASSERT_EQ(source.AsTraceback(0),
+ "Fully expanded statement\n"
+ " SELECT * FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 1\n"
" macro!()\n"
@@ -69,6 +72,9 @@
" SELECT * FROM slice\n"
" ^\n");
ASSERT_EQ(source.AsTraceback(7),
+ "Fully expanded statement\n"
+ " SELECT * FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 1\n"
" macro!()\n"
@@ -81,15 +87,18 @@
TEST(SqlSourceTest, NestedFullRewrite) {
SqlSource nested =
SqlSource::FromTraceProcessorImplementation("nested!()")
- .FullRewrite(SqlSource::FromTraceProcessorImplementation(
+ .RewriteAllIgnoreExisting(SqlSource::FromTraceProcessorImplementation(
"SELECT * FROM slice"));
ASSERT_EQ(nested.sql(), "SELECT * FROM slice");
- SqlSource source =
- SqlSource::FromExecuteQuery("macro!()").FullRewrite(std::move(nested));
+ SqlSource source = SqlSource::FromExecuteQuery("macro!()")
+ .RewriteAllIgnoreExisting(std::move(nested));
ASSERT_EQ(source.sql(), "SELECT * FROM slice");
ASSERT_EQ(source.AsTraceback(0),
+ "Fully expanded statement\n"
+ " SELECT * FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 1\n"
" macro!()\n"
@@ -101,6 +110,9 @@
" SELECT * FROM slice\n"
" ^\n");
ASSERT_EQ(source.AsTraceback(7),
+ "Fully expanded statement\n"
+ " SELECT * FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 1\n"
" macro!()\n"
@@ -113,6 +125,38 @@
" ^\n");
}
+TEST(SqlSourceTest, RewriteAllIgnoresExistingCorrectly) {
+ SqlSource foo =
+ SqlSource::FromExecuteQuery("foo!()").RewriteAllIgnoreExisting(
+ SqlSource::FromTraceProcessorImplementation("SELECT * FROM slice"));
+ SqlSource source = foo.RewriteAllIgnoreExisting(
+ SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0"));
+ ASSERT_EQ(source.sql(), "SELECT 0 WHERE 0");
+
+ ASSERT_EQ(source.AsTraceback(0),
+ "Fully expanded statement\n"
+ " SELECT 0 WHERE 0\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 1\n"
+ " foo!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 1\n"
+ " SELECT 0 WHERE 0\n"
+ " ^\n");
+ ASSERT_EQ(source.AsTraceback(4),
+ "Fully expanded statement\n"
+ " SELECT 0 WHERE 0\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 1\n"
+ " foo!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 5\n"
+ " SELECT 0 WHERE 0\n"
+ " ^\n");
+}
+
TEST(SqlSourceTest, Rewriter) {
SqlSource::Rewriter rewriter(
SqlSource::FromExecuteQuery("SELECT cols!() FROM slice"));
@@ -125,11 +169,17 @@
// Offset points at the top level source.
ASSERT_EQ(rewritten.AsTraceback(0),
+ "Fully expanded statement\n"
+ " SELECT ts, dur, ts + dur AS ts_end FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 1\n"
" SELECT cols!() FROM slice\n"
" ^\n");
ASSERT_EQ(rewritten.AsTraceback(40),
+ "Fully expanded statement\n"
+ " SELECT ts, dur, ts + dur AS ts_end FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 21\n"
" SELECT cols!() FROM slice\n"
@@ -137,6 +187,9 @@
// Offset points at the nested source.
ASSERT_EQ(rewritten.AsTraceback(16),
+ "Fully expanded statement\n"
+ " SELECT ts, dur, ts + dur AS ts_end FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 8\n"
" SELECT cols!() FROM slice\n"
@@ -164,11 +217,17 @@
// Offset points at the top level source.
ASSERT_EQ(rewritten.AsTraceback(0),
+ "Fully expanded statement\n"
+ " SELECT id, ts, dur, depth, name FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 1\n"
" SELECT cols!() FROM slice\n"
" ^\n");
ASSERT_EQ(rewritten.AsTraceback(37),
+ "Fully expanded statement\n"
+ " SELECT id, ts, dur, depth, name FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 21\n"
" SELECT cols!() FROM slice\n"
@@ -176,6 +235,9 @@
// Offset points at the first nested source.
ASSERT_EQ(rewritten.AsTraceback(15),
+ "Fully expanded statement\n"
+ " SELECT id, ts, dur, depth, name FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 8\n"
" SELECT cols!() FROM slice\n"
@@ -189,6 +251,9 @@
// Offset points at the second nested source.
ASSERT_EQ(rewritten.AsTraceback(20),
+ "Fully expanded statement\n"
+ " SELECT id, ts, dur, depth, name FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 8\n"
" SELECT cols!() FROM slice\n"
@@ -200,6 +265,9 @@
" depth\n"
" ^\n");
ASSERT_EQ(rewritten.AsTraceback(22),
+ "Fully expanded statement\n"
+ " SELECT id, ts, dur, depth, name FROM slice\n"
+ " ^\n"
"Traceback (most recent call last):\n"
" File \"stdin\" line 1 col 8\n"
" SELECT cols!() FROM slice\n"
@@ -212,6 +280,162 @@
" ^\n");
}
+TEST(SqlSourceTest, NestedRewriteSubstr) {
+ SqlSource::Rewriter nested_rewrite(
+ SqlSource::FromTraceProcessorImplementation(
+ "id, common_cols!(), other_cols!(), name"));
+ nested_rewrite.Rewrite(
+ 4, 18, SqlSource::FromTraceProcessorImplementation("ts, dur"));
+ nested_rewrite.Rewrite(20, 33,
+ SqlSource::FromTraceProcessorImplementation("depth"));
+
+ SqlSource::Rewriter rewriter(
+ SqlSource::FromExecuteQuery("SELECT cols!() FROM slice"));
+ rewriter.Rewrite(7, 14, std::move(nested_rewrite).Build());
+
+ SqlSource rewritten = std::move(rewriter).Build();
+ ASSERT_EQ(rewritten.sql(), "SELECT id, ts, dur, depth, name FROM slice");
+
+ // Full macro cover.
+ SqlSource cols = rewritten.Substr(7, 24);
+ ASSERT_EQ(cols.sql(), "id, ts, dur, depth, name");
+ ASSERT_EQ(cols.AsTraceback(0),
+ "Fully expanded statement\n"
+ " id, ts, dur, depth, name\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 1\n"
+ " id, common_cols!(), other_cols!(), name\n"
+ " ^\n");
+ ASSERT_EQ(cols.AsTraceback(5),
+ "Fully expanded statement\n"
+ " id, ts, dur, depth, name\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 5\n"
+ " id, common_cols!(), other_cols!(), name\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 2\n"
+ " ts, dur\n"
+ " ^\n");
+ ASSERT_EQ(cols.AsTraceback(14),
+ "Fully expanded statement\n"
+ " id, ts, dur, depth, name\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 21\n"
+ " id, common_cols!(), other_cols!(), name\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 2\n"
+ " depth\n"
+ " ^\n");
+
+ // Intersect with nested.
+ SqlSource intersect = rewritten.Substr(8, 13);
+ ASSERT_EQ(intersect.sql(), "d, ts, dur, d");
+ ASSERT_EQ(intersect.AsTraceback(0),
+ "Fully expanded statement\n"
+ " d, ts, dur, d\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 2\n"
+ " d, common_cols!(), other_cols!()\n"
+ " ^\n");
+ ASSERT_EQ(intersect.AsTraceback(4),
+ "Fully expanded statement\n"
+ " d, ts, dur, d\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 5\n"
+ " d, common_cols!(), other_cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 2\n"
+ " ts, dur\n"
+ " ^\n");
+ ASSERT_EQ(intersect.AsTraceback(12),
+ "Fully expanded statement\n"
+ " d, ts, dur, d\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 21\n"
+ " d, common_cols!(), other_cols!()\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 1\n"
+ " d\n"
+ " ^\n");
+}
+
+TEST(SqlSourceTest, Rerewrites) {
+ SqlSource::Rewriter rewriter(
+ SqlSource::FromExecuteQuery("SELECT foo!(a) FROM bar!(slice) a"));
+ rewriter.Rewrite(7, 14,
+ SqlSource::FromTraceProcessorImplementation("a.x, a.y"));
+ rewriter.Rewrite(20, 31,
+ SqlSource::FromTraceProcessorImplementation(
+ "(SELECT slice.x, slice.y, slice.z FROM slice)"));
+
+ SqlSource rewritten = std::move(rewriter).Build();
+ ASSERT_EQ(
+ rewritten.sql(),
+ "SELECT a.x, a.y FROM (SELECT slice.x, slice.y, slice.z FROM slice) a");
+
+ SqlSource::Rewriter rerewriter(std::move(rewritten));
+ rerewriter.Rewrite(0, 7,
+ SqlSource::FromTraceProcessorImplementation("INSERT "));
+ rerewriter.Rewrite(7, 14,
+ SqlSource::FromTraceProcessorImplementation("a.z, "));
+
+ SqlSource rerewritten = std::move(rerewriter).Build();
+ ASSERT_EQ(
+ rerewritten.sql(),
+ "INSERT a.z, y FROM (SELECT slice.x, slice.y, slice.z FROM slice) a");
+ ASSERT_EQ(
+ rerewritten.AsTraceback(0),
+ "Fully expanded statement\n"
+ " INSERT a.z, y FROM (SELECT slice.x, slice.y, slice.z FROM slice) a\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 1\n"
+ " SELECT foo!(a) FROM bar!(slice) a\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 1\n"
+ " INSERT \n"
+ " ^\n");
+ ASSERT_EQ(
+ rerewritten.AsTraceback(8),
+ "Fully expanded statement\n"
+ " INSERT a.z, y FROM (SELECT slice.x, slice.y, slice.z FROM slice) a\n"
+ " ^\n"
+ "Traceback (most recent call last):\n"
+ " File \"stdin\" line 1 col 8\n"
+ " SELECT foo!(a) FROM bar!(slice) a\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 1\n"
+ " a.x, a.y\n"
+ " ^\n"
+ " Trace Processor Internal line 1 col 2\n"
+ " a.z, \n"
+ " ^\n");
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index 77f4003..23b064a 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -131,7 +131,7 @@
}
SqliteEngine::PreparedStatement SqliteEngine::PrepareStatement(SqlSource sql) {
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "QUERY_PREPARE");
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, "QUERY_PREPARE");
sqlite3_stmt* raw_stmt = nullptr;
int err =
sqlite3_prepare_v2(db_.get(), sql.sql().c_str(), -1, &raw_stmt, nullptr);
@@ -222,12 +222,15 @@
SqliteEngine::PreparedStatement::PreparedStatement(ScopedStmt stmt,
SqlSource source)
- : stmt_(std::move(stmt)), sql_source_(std::move(source)) {}
+ : stmt_(std::move(stmt)),
+ expanded_sql_(sqlite3_expanded_sql(stmt_.get())),
+ sql_source_(std::move(source)) {}
bool SqliteEngine::PreparedStatement::Step() {
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "STMT_STEP",
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, "STMT_STEP",
[this](metatrace::Record* record) {
- record->AddArg("SQL", expanded_sql());
+ record->AddArg("Original SQL", original_sql());
+ record->AddArg("Executed SQL", sql());
});
// Now step once into |cur_stmt| so that when we prepare the next statment
@@ -251,14 +254,11 @@
return !sqlite3_stmt_busy(stmt_.get());
}
-const char* SqliteEngine::PreparedStatement::sql() const {
- return sqlite3_sql(stmt_.get());
+const char* SqliteEngine::PreparedStatement::original_sql() const {
+ return sql_source_.original_sql().c_str();
}
-const char* SqliteEngine::PreparedStatement::expanded_sql() {
- if (!expanded_sql_) {
- expanded_sql_.reset(sqlite3_expanded_sql(stmt_.get()));
- }
+const char* SqliteEngine::PreparedStatement::sql() const {
return expanded_sql_.get();
}
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index b93248e..1a1b089 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -58,8 +58,8 @@
bool Step();
bool IsDone() const;
+ const char* original_sql() const;
const char* sql() const;
- const char* expanded_sql();
const base::Status& status() const { return status_; }
sqlite3_stmt* sqlite_stmt() const { return stmt_.get(); }
@@ -70,8 +70,8 @@
explicit PreparedStatement(ScopedStmt, SqlSource);
ScopedStmt stmt_;
- SqlSource sql_source_;
ScopedSqliteString expanded_sql_;
+ SqlSource sql_source_;
base::Status status_ = base::OkStatus();
};
diff --git a/src/trace_processor/sqlite/sqlite_table.cc b/src/trace_processor/sqlite/sqlite_table.cc
index c32a318..c599b2f 100644
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ b/src/trace_processor/sqlite/sqlite_table.cc
@@ -184,8 +184,8 @@
cache_hit = false;
}
- PERFETTO_TP_TRACE(metatrace::Category::QUERY, "SQLITE_TABLE_READ_CONSTRAINTS",
- [&](metatrace::Record* r) {
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
+ "SQLITE_TABLE_READ_CONSTRAINTS", [&](metatrace::Record* r) {
r->AddArg("cache_hit", std::to_string(cache_hit));
r->AddArg("name", name_);
WriteQueryConstraintsToMetatrace(r, qc_cache_, schema_);
@@ -412,7 +412,7 @@
}
PERFETTO_TP_TRACE(
- metatrace::Category::QUERY, "SQLITE_TABLE_BEST_INDEX",
+ metatrace::Category::QUERY_TIMELINE, "SQLITE_TABLE_BEST_INDEX",
[&](metatrace::Record* r) {
r->AddArg("name", table->name());
WriteQueryConstraintsToMetatrace(r, qc, table->schema());
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.cc b/src/trace_processor/sqlite/sqlite_tokenizer.cc
index 6ad1bb6..5a17f30 100644
--- a/src/trace_processor/sqlite/sqlite_tokenizer.cc
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.cc
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <sqlite3.h>
+#include <cstdint>
#include <optional>
#include <string_view>
@@ -454,14 +455,21 @@
return tok;
}
-SqlSource SqliteTokenizer::Substr(Token start, Token end) const {
+SqlSource SqliteTokenizer::Substr(const Token& start, const Token& end) const {
uint32_t offset =
static_cast<uint32_t>(start.str.data() - source_.sql().c_str());
uint32_t len = static_cast<uint32_t>(end.str.data() - start.str.data());
return source_.Substr(offset, len);
}
-std::string SqliteTokenizer::AsTraceback(Token token) const {
+SqlSource SqliteTokenizer::SubstrToken(const Token& token) const {
+ uint32_t offset =
+ static_cast<uint32_t>(token.str.data() - source_.sql().c_str());
+ uint32_t len = static_cast<uint32_t>(token.str.size());
+ return source_.Substr(offset, len);
+}
+
+std::string SqliteTokenizer::AsTraceback(const Token& token) const {
PERFETTO_CHECK(source_.sql().c_str() <= token.str.data());
PERFETTO_CHECK(token.str.data() <=
source_.sql().c_str() + source_.sql().size());
@@ -470,5 +478,30 @@
return source_.AsTraceback(offset);
}
+void SqliteTokenizer::Rewrite(SqlSource::Rewriter& rewriter,
+ const Token& start,
+ const Token& end,
+ SqlSource rewrite,
+ EndToken end_token) const {
+ uint32_t s_off =
+ static_cast<uint32_t>(start.str.data() - source_.sql().c_str());
+ uint32_t e_off =
+ static_cast<uint32_t>(end.str.data() - source_.sql().c_str());
+ uint32_t e_diff = end_token == EndToken::kInclusive
+ ? static_cast<uint32_t>(end.str.size())
+ : 0;
+ rewriter.Rewrite(s_off, e_off + e_diff, std::move(rewrite));
+}
+
+void SqliteTokenizer::RewriteToken(SqlSource::Rewriter& rewriter,
+ const Token& token,
+ SqlSource rewrite) const {
+ uint32_t s_off =
+ static_cast<uint32_t>(token.str.data() - source_.sql().c_str());
+ uint32_t e_off = static_cast<uint32_t>(token.str.data() + token.str.size() -
+ source_.sql().c_str());
+ rewriter.Rewrite(s_off, e_off, std::move(rewrite));
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.h b/src/trace_processor/sqlite/sqlite_tokenizer.h
index d0ef24c..f4e9f79 100644
--- a/src/trace_processor/sqlite/sqlite_tokenizer.h
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.h
@@ -80,7 +80,7 @@
std::string_view str;
// The type of the token.
- SqliteTokenType token_type;
+ SqliteTokenType token_type = SqliteTokenType::TK_ILLEGAL;
bool operator==(const Token& o) const {
return str == o.str && token_type == o.token_type;
@@ -92,6 +92,12 @@
}
};
+ enum class EndToken {
+ kExclusive,
+ kInclusive,
+ };
+
+ // Creates a tokenizer which tokenizes |sql|.
explicit SqliteTokenizer(SqlSource sql);
// Returns the next SQL token.
@@ -107,14 +113,34 @@
//
// Note: |start| and |end| must both have been previously returned by this
// tokenizer.
- SqlSource Substr(Token start, Token end) const;
+ SqlSource Substr(const Token& start, const Token& end) const;
+
+ // Returns an SqlSource containing only the SQL backing |token|.
+ //
+ // Note: |token| must have been previously returned by this tokenizer.
+ SqlSource SubstrToken(const Token& token) const;
// Returns a traceback error message for the SqlSource backing this tokenizer
// pointing to |token|. See SqlSource::AsTraceback for more information about
// this method.
//
// Note: |token| must have been previously returned by this tokenizer.
- std::string AsTraceback(Token) const;
+ std::string AsTraceback(const Token&) const;
+
+ // Replaces the SQL in |rewriter| between |start| and |end| with the contents
+ // of |rewrite|. If |end_token| == kInclusive, the end token is also included
+ // in the rewrite.
+ void Rewrite(SqlSource::Rewriter& rewriter,
+ const Token& start,
+ const Token& end,
+ SqlSource rewrite,
+ EndToken end_token = EndToken::kExclusive) const;
+
+ // Replaces the SQL in |rewriter| backing |token| with the contents of
+ // |rewrite|.
+ void RewriteToken(SqlSource::Rewriter&,
+ const Token&,
+ SqlSource rewrite) const;
// Resets this tokenizer to tokenize |source|. Any previous returned tokens
// are invalidated.
diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py
index c712adf..8361d8c 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -144,10 +144,10 @@
''',
group='Callstack profilers',
columns={
- 'build_id': '''hex-encoded Build ID of the binary / library.''',
- 'start': '''start of the mapping in the process' address space.''',
- 'end': '''end of the mapping in the process' address space.''',
- 'name': '''filename of the binary / library.''',
+ 'build_id': '''Hex-encoded Build ID of the binary / library.''',
+ 'start': '''Start of the mapping in the process' address space.''',
+ 'end': '''End of the mapping in the process' address space.''',
+ 'name': '''Filename of the binary / library.''',
'exact_offset': '''''',
'start_offset': '''''',
'load_bias': ''''''
@@ -172,16 +172,16 @@
group='Callstack profilers',
columns={
'name':
- '''name of the function this location is in.''',
+ '''Name of the function this location is in.''',
'mapping':
- '''the mapping (library / binary) this location is in.''',
+ '''The mapping (library / binary) this location is in.''',
'rel_pc':
- '''the program counter relative to the start of the mapping.''',
+ '''The program counter relative to the start of the mapping.''',
'symbol_set_id':
- '''if the profile was offline symbolized, the offline
+ '''If the profile was offline symbolized, the offline
symbol information of this frame.''',
'deobfuscated_name':
- ''''''
+ '''Deobfuscated name of the function this location is in.'''
}))
STACK_PROFILE_CALLSITE_TABLE = Table(
@@ -201,11 +201,11 @@
group='Callstack profilers',
columns={
'depth':
- '''distance from the bottom-most frame of the callstack.''',
+ '''Distance from the bottom-most frame of the callstack.''',
'parent_id':
- '''parent frame on the callstack. NULL for the bottom-most.''',
+ '''Parent frame on the callstack. NULL for the bottom-most.''',
'frame_id':
- '''frame at this position in the callstack.'''
+ '''Frame at this position in the callstack.'''
}))
STACK_SAMPLE_TABLE = Table(
@@ -265,23 +265,23 @@
group='Callstack profilers',
columns={
'ts':
- '''timestamp of the sample.''',
+ '''Timestamp of the sample.''',
'utid':
- '''sampled thread..''',
+ '''Sampled thread.''',
'cpu':
- '''the core the sampled thread was running on.''',
+ '''Core the sampled thread was running on.''',
'cpu_mode':
- '''execution state (userspace/kernelspace) of the sampled
+ '''Execution state (userspace/kernelspace) of the sampled
thread.''',
'callsite_id':
- '''if set, unwound callstack of the sampled thread.''',
+ '''If set, unwound callstack of the sampled thread.''',
'unwind_error':
- '''if set, indicates that the unwinding for this sample
+ '''If set, indicates that the unwinding for this sample
encountered an error. Such samples still reference the best-effort
-result via the callsite_id (with a synthetic error frame at the point
-where unwinding stopped).''',
+result via the callsite_id, with a synthetic error frame at the point
+where unwinding stopped.''',
'perf_session_id':
- '''distinguishes samples from different profiling
+ '''Distinguishes samples from different profiling
streams (i.e. multiple data sources).'''
}))
diff --git a/src/trace_processor/tp_metatrace.cc b/src/trace_processor/tp_metatrace.cc
index 65f0d07..d9bbfe2 100644
--- a/src/trace_processor/tp_metatrace.cc
+++ b/src/trace_processor/tp_metatrace.cc
@@ -24,15 +24,19 @@
using ProtoEnum = protos::pbzero::MetatraceCategories;
ProtoEnum MetatraceCategoriesToProtoEnum(MetatraceCategories categories) {
+ // Note: these are intentionally chained ifs and not else-ifs as it's possible
+ // for multiple of these if statements to be true.
ProtoEnum result = ProtoEnum::NONE;
- if (categories & MetatraceCategories::TOPLEVEL)
- result = static_cast<ProtoEnum>(result | ProtoEnum::TOPLEVEL);
- if (categories & MetatraceCategories::FUNCTION)
- result = static_cast<ProtoEnum>(result | ProtoEnum::FUNCTION);
- if (categories & MetatraceCategories::QUERY)
- result = static_cast<ProtoEnum>(result | ProtoEnum::QUERY);
+ if (categories & MetatraceCategories::QUERY_TIMELINE)
+ result = static_cast<ProtoEnum>(result | ProtoEnum::QUERY_TIMELINE);
+ if (categories & MetatraceCategories::FUNCTION_CALL)
+ result = static_cast<ProtoEnum>(result | ProtoEnum::FUNCTION_CALL);
+ if (categories & MetatraceCategories::QUERY_DETAILED)
+ result = static_cast<ProtoEnum>(result | ProtoEnum::QUERY_DETAILED);
if (categories & MetatraceCategories::DB)
result = static_cast<ProtoEnum>(result | ProtoEnum::DB);
+ if (categories & MetatraceCategories::API_TIMELINE)
+ result = static_cast<ProtoEnum>(result | ProtoEnum::API_TIMELINE);
return result;
}
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 21f4709..da31fc4 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -726,7 +726,7 @@
}
Iterator TraceProcessorImpl::ExecuteQuery(const std::string& sql) {
- PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "QUERY_EXECUTE");
+ PERFETTO_TP_TRACE(metatrace::Category::API_TIMELINE, "EXECUTE_QUERY");
uint32_t sql_stats_row =
context_.storage->mutable_sql_stats()->RecordQueryBegin(
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 0f0543f..7873af5 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -46,6 +46,7 @@
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/version.h"
+#include "perfetto/trace_processor/metatrace_config.h"
#include "perfetto/trace_processor/read_trace.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h"
@@ -645,14 +646,16 @@
std::string cur = splitter.cur_token();
if (cur == "all" || cur == "*") {
result = Cat::ALL;
- } else if (cur == "toplevel") {
- result = static_cast<Cat>(result | Cat::TOPLEVEL);
- } else if (cur == "function") {
- result = static_cast<Cat>(result | Cat::FUNCTION);
- } else if (cur == "query") {
- result = static_cast<Cat>(result | Cat::QUERY);
+ } else if (cur == "query_toplevel") {
+ result = static_cast<Cat>(result | Cat::QUERY_TIMELINE);
+ } else if (cur == "query_detailed") {
+ result = static_cast<Cat>(result | Cat::QUERY_DETAILED);
+ } else if (cur == "function_call") {
+ result = static_cast<Cat>(result | Cat::FUNCTION_CALL);
} else if (cur == "db") {
result = static_cast<Cat>(result | Cat::DB);
+ } else if (cur == "api") {
+ result = static_cast<Cat>(result | Cat::API_TIMELINE);
} else {
PERFETTO_ELOG("Unknown metatrace category %s", cur.data());
exit(1);
@@ -681,7 +684,9 @@
std::string metatrace_path;
size_t metatrace_buffer_capacity = 0;
metatrace::MetatraceCategories metatrace_categories =
- metatrace::MetatraceCategories::TOPLEVEL;
+ static_cast<metatrace::MetatraceCategories>(
+ metatrace::MetatraceCategories::QUERY_TIMELINE |
+ metatrace::MetatraceCategories::API_TIMELINE);
bool dev = false;
bool no_ftrace_raw = false;
bool analyze_trace_proto_content = false;
diff --git a/src/trace_processor/util/proto_profiler_unittest.cc b/src/trace_processor/util/proto_profiler_unittest.cc
index 181bc97..a2c969a 100644
--- a/src/trace_processor/util/proto_profiler_unittest.cc
+++ b/src/trace_processor/util/proto_profiler_unittest.cc
@@ -57,9 +57,9 @@
got.emplace_back(path, *sample);
}
std::vector<Item> expected{
- {{"NestedA"}, 15},
- {{"NestedA", "#repeated_a", "NestedB"}, 5},
- {{"NestedA", "#repeated_a", "NestedB"}, 5},
+ {{"NestedA"}, 6},
+ {{"NestedA", "#repeated_a", "NestedB"}, 2},
+ {{"NestedA", "#repeated_a", "NestedB"}, 2},
{{"NestedA", "#repeated_a", "NestedB", "#value_b", "NestedC"}, 1},
{{"NestedA", "#repeated_a", "NestedB", "#value_b", "NestedC"}, 1},
{{"NestedA", "#repeated_a", "NestedB", "#value_b", "NestedC", "#value_c",
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index edbc981..a9b6bb7 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -17,6 +17,7 @@
#include <stdio.h>
#include <algorithm>
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/string_utils.h"
@@ -29,16 +30,6 @@
#include "perfetto/ext/tracing/ipc/service_ipc_host.h"
#include "src/traced/service/builtin_producer.h"
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
- PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-#define PERFETTO_SET_SOCKET_PERMISSIONS
-#include <fcntl.h>
-#include <grp.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#endif
-
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <sys/system_properties.h>
#endif
@@ -49,40 +40,6 @@
namespace perfetto {
namespace {
-#if defined(PERFETTO_SET_SOCKET_PERMISSIONS)
-void SetSocketPermissions(const std::string& socket_name,
- const std::string& group_name,
- const std::string& mode_bits) {
- PERFETTO_CHECK(!socket_name.empty());
- PERFETTO_CHECK(!group_name.empty());
- struct group* socket_group = nullptr;
- // Query the group ID of |group|.
- do {
- socket_group = getgrnam(group_name.c_str());
- } while (socket_group == nullptr && errno == EINTR);
- if (socket_group == nullptr) {
- PERFETTO_FATAL("Failed to get group information of %s ",
- group_name.c_str());
- }
-
- if (PERFETTO_EINTR(
- chown(socket_name.c_str(), geteuid(), socket_group->gr_gid))) {
- PERFETTO_FATAL("Failed to chown %s ", socket_name.c_str());
- }
-
- // |mode| accepts values like "0660" as "rw-rw----" mode bits.
- auto mode_value = base::StringToInt32(mode_bits, 8);
- if (!(mode_bits.size() == 4 && mode_value.has_value())) {
- PERFETTO_FATAL(
- "The chmod option must be a 4-digit octal number, e.g. 0660");
- }
- if (PERFETTO_EINTR(chmod(socket_name.c_str(),
- static_cast<mode_t>(mode_value.value())))) {
- PERFETTO_FATAL("Failed to chmod %s", socket_name.c_str());
- }
-}
-#endif // defined(PERFETTO_SET_SOCKET_PERMISSIONS)
-
void PrintUsage(const char* prog_name) {
fprintf(stderr, R"(
Usage: %s [option] ...
@@ -192,18 +149,21 @@
started = svc->Start(producer_sockets, GetConsumerSocket());
if (!producer_socket_group.empty()) {
-#if defined(PERFETTO_SET_SOCKET_PERMISSIONS)
+ auto status = base::OkStatus();
for (const auto& producer_socket : producer_sockets) {
- SetSocketPermissions(producer_socket, producer_socket_group,
- producer_socket_mode);
+ status = base::SetFilePermissions(
+ producer_socket, producer_socket_group, producer_socket_mode);
+ if (!status.ok()) {
+ PERFETTO_ELOG("%s", status.c_message());
+ return 1;
+ }
}
- SetSocketPermissions(GetConsumerSocket(), consumer_socket_group,
- consumer_socket_mode);
-#else
- PERFETTO_ELOG(
- "Setting socket permissions is not supported on this platform");
- return 1;
-#endif
+ status = base::SetFilePermissions(
+ GetConsumerSocket(), consumer_socket_group, consumer_socket_mode);
+ if (!status.ok()) {
+ PERFETTO_ELOG("%s", status.c_message());
+ return 1;
+ }
}
}
diff --git a/src/traced_relay/BUILD.gn b/src/traced_relay/BUILD.gn
new file mode 100644
index 0000000..d4e27cc
--- /dev/null
+++ b/src/traced_relay/BUILD.gn
@@ -0,0 +1,47 @@
+# 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("../../gn/perfetto.gni")
+import("../../gn/perfetto_component.gni")
+
+executable("traced_relay") {
+ deps = [
+ ":lib",
+ "../../gn:default_deps",
+ "../../include/perfetto/ext/traced",
+ "../base",
+ "../base:unix_socket",
+ "../base:version",
+ "../ipc:perfetto_ipc",
+ "../tracing/ipc:default_socket",
+ ]
+ sources = [ "relay_service_main.cc" ]
+}
+
+source_set("lib") {
+ public_deps = [ "../../include/perfetto/ext/tracing/ipc" ]
+ sources = [
+ "relay_service.cc",
+ "relay_service.h",
+ "socket_relay_handler.cc",
+ "socket_relay_handler.h",
+ ]
+ deps = [
+ "../../gn:default_deps",
+ "../../protos/perfetto/ipc",
+ "../../protos/perfetto/ipc:wire_protocol_cpp",
+ "../base",
+ "//src/ipc:perfetto_ipc",
+ ]
+}
diff --git a/src/traced_relay/relay_service.cc b/src/traced_relay/relay_service.cc
new file mode 100644
index 0000000..4b1dc6b
--- /dev/null
+++ b/src/traced_relay/relay_service.cc
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#include "src/traced_relay/relay_service.h"
+#include <memory>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/unix_socket.h"
+#include "perfetto/ext/base/utils.h"
+#include "protos/perfetto/ipc/wire_protocol.gen.h"
+#include "src/ipc/buffered_frame_deserializer.h"
+#include "src/traced_relay/socket_relay_handler.h"
+
+using ::perfetto::protos::gen::IPCFrame;
+
+namespace perfetto {
+
+RelayService::RelayService(base::TaskRunner* task_runner)
+ : task_runner_(task_runner) {}
+
+void RelayService::Start(const char* listening_socket_name,
+ const char* client_socket_name) {
+ auto sock_family = base::GetSockFamily(listening_socket_name);
+ listening_socket_ =
+ base::UnixSocket::Listen(listening_socket_name, this, task_runner_,
+ sock_family, base::SockType::kStream);
+ bool producer_socket_listening =
+ listening_socket_ && listening_socket_->is_listening();
+ if (!producer_socket_listening) {
+ PERFETTO_FATAL("Failed to listen to socket %s", listening_socket_name);
+ }
+
+ // Save |client_socket_name| for opening new client connection to remote
+ // service when a local producer connects.
+ client_socket_name_ = client_socket_name;
+}
+
+void RelayService::OnNewIncomingConnection(
+ base::UnixSocket* listen_socket,
+ std::unique_ptr<base::UnixSocket> server_conn) {
+ PERFETTO_DCHECK(listen_socket == listening_socket_.get());
+
+ // Create a connection to the host to pair with |listen_conn|.
+ auto sock_family = base::GetSockFamily(client_socket_name_.c_str());
+ auto client_conn =
+ base::UnixSocket::Connect(client_socket_name_, this, task_runner_,
+ sock_family, base::SockType::kStream);
+
+ // Pre-queue the SetPeerIdentity request. By enqueueing it into the buffer,
+ // this will be sent out as first frame as soon as we connect to the real
+ // traced.
+ //
+ // This code pretends that we received a SetPeerIdentity frame from the
+ // connecting producer (while instead we are just forging it). The host traced
+ // will only accept only one SetPeerIdentity request pre-queued here.
+ IPCFrame ipc_frame;
+ ipc_frame.set_request_id(0);
+ auto* set_peer_identity = ipc_frame.mutable_set_peer_identity();
+ set_peer_identity->set_pid(server_conn->peer_pid_linux());
+ set_peer_identity->set_uid(
+ static_cast<int32_t>(server_conn->peer_uid_posix()));
+
+ // Buffer the SetPeerIdentity request.
+ auto req = ipc::BufferedFrameDeserializer::Serialize(ipc_frame);
+ SocketWithBuffer server, client;
+ PERFETTO_CHECK(server.available_bytes() >= req.size());
+ memcpy(server.buffer(), req.data(), req.size());
+ server.EnqueueData(req.size());
+
+ // Shut down all callbacks associated with the socket in preparation for the
+ // transfer to |socket_relay_handler_|.
+ server.sock = server_conn->ReleaseSocket();
+ auto new_socket_pair =
+ std::make_unique<SocketPair>(std::move(server), std::move(client));
+ pending_connections_.push_back(
+ {std::move(new_socket_pair), std::move(client_conn)});
+}
+
+void RelayService::OnConnect(base::UnixSocket* self, bool connected) {
+ // This only happens when the client connection is connected or has failed.
+ auto it =
+ std::find_if(pending_connections_.begin(), pending_connections_.end(),
+ [&](const PendingConnection& pending_conn) {
+ return pending_conn.connecting_client_conn.get() == self;
+ });
+ PERFETTO_CHECK(it != pending_connections_.end());
+ // Need to remove the element in |pending_connections_| regardless of
+ // |connected|.
+ auto remover = base::OnScopeExit([&]() { pending_connections_.erase(it); });
+
+ if (!connected)
+ return; // This closes both sockets in PendingConnection.
+
+ // Shut down event handlers and pair with a server connection.
+ it->socket_pair->second.sock = self->ReleaseSocket();
+
+ // Transfer the socket pair to SocketRelayHandler.
+ socket_relay_handler_.AddSocketPair(std::move(it->socket_pair));
+}
+
+void RelayService::OnDisconnect(base::UnixSocket*) {
+ PERFETTO_DFATAL("Should be unreachable.");
+}
+
+void RelayService::OnDataAvailable(base::UnixSocket*) {
+ PERFETTO_DFATAL("Should be unreachable.");
+}
+
+} // namespace perfetto
diff --git a/src/traced_relay/relay_service.h b/src/traced_relay/relay_service.h
new file mode 100644
index 0000000..8e5bf6d
--- /dev/null
+++ b/src/traced_relay/relay_service.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACED_RELAY_RELAY_SERVICE_H_
+#define SRC_TRACED_RELAY_RELAY_SERVICE_H_
+
+#include <memory>
+#include <vector>
+
+#include "perfetto/ext/base/unix_socket.h"
+#include "src/traced_relay/socket_relay_handler.h"
+
+namespace perfetto {
+
+namespace base {
+class TaskRunner;
+} // namespace base.
+
+// A class for relaying the producer data between the local producers and the
+// remote tracing service.
+class RelayService : public base::UnixSocket::EventListener {
+ public:
+ explicit RelayService(base::TaskRunner* task_runner);
+ ~RelayService() override = default;
+
+ // Starts the service relay that forwards messages between the
+ // |server_socket_name| and |client_socket_name| ports.
+ void Start(const char* server_socket_name, const char* client_socket_name);
+
+ private:
+ struct PendingConnection {
+ // This keeps a connected UnixSocketRaw server socket in its first element.
+ std::unique_ptr<SocketPair> socket_pair;
+ // This keeps the connecting client connection.
+ std::unique_ptr<base::UnixSocket> connecting_client_conn;
+ };
+
+ RelayService(const RelayService&) = delete;
+ RelayService& operator=(const RelayService&) = delete;
+
+ // UnixSocket::EventListener implementation.
+ void OnNewIncomingConnection(base::UnixSocket*,
+ std::unique_ptr<base::UnixSocket>) override;
+ void OnConnect(base::UnixSocket* self, bool connected) override;
+ void OnDisconnect(base::UnixSocket* self) override;
+ void OnDataAvailable(base::UnixSocket* self) override;
+
+ base::TaskRunner* const task_runner_ = nullptr;
+
+ std::unique_ptr<base::UnixSocket> listening_socket_;
+ std::string client_socket_name_;
+
+ // Keeps the socket pairs while waiting for relay connections to be
+ // established.
+ std::vector<PendingConnection> pending_connections_;
+
+ SocketRelayHandler socket_relay_handler_;
+};
+
+} // namespace perfetto
+
+#endif // SRC_TRACED_RELAY_RELAY_SERVICE_H_
diff --git a/src/traced_relay/relay_service_main.cc b/src/traced_relay/relay_service_main.cc
new file mode 100644
index 0000000..9562b2d
--- /dev/null
+++ b/src/traced_relay/relay_service_main.cc
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/version.h"
+#include "perfetto/ext/base/watchdog.h"
+#include "perfetto/ext/traced/traced.h"
+#include "perfetto/ext/tracing/ipc/default_socket.h"
+#include "src/traced_relay/relay_service.h"
+
+namespace perfetto {
+namespace {
+void PrintUsage(const char* prog_name) {
+ fprintf(stderr, R"(
+Usage: %s [option] ...
+Options and arguments
+ --background : Exits immediately and continues running in the background
+ --version : print the version number and exit.
+ --set-socket-permissions <permissions> : sets group ownership and permission
+ mode bits of the listening socket.
+ <permissions> format: <prod_group>:<prod_mode>,
+ where <prod_group> is the group name for chgrp the listening socket,
+ <prod_mode> is the mode bits (e.g. 0660) for chmod the producer socket,
+
+Example:
+ %s --set-socket-permissions traced-producer:0660 starts the service and sets
+ the group ownership of the listening socket to "traced-producer". The
+ listening socket is chmod with 0660 (rw-rw----) mode bits. )",
+ prog_name, prog_name);
+}
+
+} // namespace
+
+static int RelayServiceMain(int argc, char** argv) {
+ enum LongOption {
+ OPT_VERSION = 1000,
+ OPT_SET_SOCKET_PERMISSIONS = 1001,
+ OPT_BACKGROUND,
+ };
+
+ bool background = false;
+
+ static const option long_options[] = {
+ {"background", no_argument, nullptr, OPT_BACKGROUND},
+ {"version", no_argument, nullptr, OPT_VERSION},
+ {"set-socket-permissions", required_argument, nullptr,
+ OPT_SET_SOCKET_PERMISSIONS},
+ {nullptr, 0, nullptr, 0}};
+
+ std::string listen_socket_group, consumer_socket_group,
+ listen_socket_mode_bits, consumer_socket_mode;
+
+ for (;;) {
+ int option = getopt_long(argc, argv, "", long_options, nullptr);
+ if (option == -1)
+ break;
+ switch (option) {
+ case OPT_BACKGROUND:
+ background = true;
+ break;
+ case OPT_VERSION:
+ printf("%s\n", base::GetVersionString());
+ return 0;
+ case OPT_SET_SOCKET_PERMISSIONS: {
+ // Check that the socket permission argument is well formed.
+ auto parts = perfetto::base::SplitString(std::string(optarg), ":");
+ PERFETTO_CHECK(parts.size() == 2);
+ PERFETTO_CHECK(
+ std::all_of(parts.cbegin(), parts.cend(),
+ [](const std::string& part) { return !part.empty(); }));
+ listen_socket_group = parts[0];
+ listen_socket_mode_bits = parts[1];
+ break;
+ }
+ default:
+ PrintUsage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (background) {
+ base::Daemonize([] { return 0; });
+ }
+
+ auto listen_socket = GetProducerSocket();
+ remove(listen_socket);
+ if (!listen_socket_group.empty()) {
+ auto status = base::SetFilePermissions(listen_socket, listen_socket_group,
+ listen_socket_mode_bits);
+ if (!status.ok()) {
+ PERFETTO_ELOG("Failed to set socket permissions: %s", status.c_message());
+ return 1;
+ }
+ }
+
+ base::UnixTaskRunner task_runner;
+ auto svc = std::make_unique<RelayService>(&task_runner);
+ svc->Start(listen_socket, GetRelaySocket());
+
+ // Set the CPU limit and start the watchdog running. The memory limit will
+ // be set inside the service code as it relies on the size of buffers.
+ // The CPU limit is the generic one defined in watchdog.h.
+ base::Watchdog* watchdog = base::Watchdog::GetInstance();
+ watchdog->SetCpuLimit(base::kWatchdogDefaultCpuLimit,
+ base::kWatchdogDefaultCpuWindow);
+ watchdog->Start();
+
+ PERFETTO_ILOG("Started traced_relay, listening on %s, forwarding to %s",
+ GetProducerSocket(), GetRelaySocket());
+
+ task_runner.Run();
+ return 0;
+}
+} // namespace perfetto
+
+int main(int argc, char** argv) {
+ return perfetto::RelayServiceMain(argc, argv);
+}
diff --git a/src/traced_relay/socket_relay_handler.cc b/src/traced_relay/socket_relay_handler.cc
new file mode 100644
index 0000000..2930c86
--- /dev/null
+++ b/src/traced_relay/socket_relay_handler.cc
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ */
+
+#include "src/traced_relay/socket_relay_handler.h"
+
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/platform_handle.h"
+#include "perfetto/ext/base/thread_checker.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/ext/base/watchdog.h"
+#include "perfetto/ext/base/watchdog_posix.h"
+
+namespace perfetto {
+namespace {
+// Use the the default watchdog timeout for task runners.
+static constexpr int kWatchdogTimeoutMs = 30000;
+// Timeout of the epoll_wait() call.
+static constexpr int kPollTimeoutMs = 30000;
+} // namespace
+
+FdPoller::Watcher::~Watcher() = default;
+
+FdPoller::FdPoller(Watcher* watcher) : watcher_(watcher) {
+ WatchForRead(notify_fd_.fd());
+
+ // This is done last in the ctor because WatchForRead() asserts using
+ // |thread_checker_|.
+ PERFETTO_DETACH_FROM_THREAD(thread_checker_);
+}
+
+void FdPoller::Poll() {
+ PERFETTO_DCHECK_THREAD(thread_checker_);
+
+ int num_fds =
+ PERFETTO_EINTR(poll(&poll_fds_[0], poll_fds_.size(), kPollTimeoutMs));
+ if (num_fds == -1 && base::IsAgain(errno))
+ return; // Poll again.
+ PERFETTO_DCHECK(num_fds <= static_cast<int>(poll_fds_.size()));
+
+ // Make a copy of |poll_fds_| so it's safe to watch and unwatch while
+ // notifying the watcher.
+ const auto poll_fds(poll_fds_);
+
+ for (const auto& event : poll_fds) {
+ if (!event.revents) // This event isn't active.
+ continue;
+
+ // Check whether the poller needs to break the polling loop for updates.
+ if (event.fd == notify_fd_.fd()) {
+ notify_fd_.Clear();
+ continue;
+ }
+
+ // Notify the callers on fd events.
+ if (event.revents & POLLOUT) {
+ watcher_->OnFdWritable(event.fd);
+ } else if (event.revents & POLLIN) {
+ watcher_->OnFdReadable(event.fd);
+ } else {
+ PERFETTO_DLOG("poll() returns events %d on fd %d", event.events,
+ event.fd);
+ } // Other events like POLLHUP or POLLERR are ignored.
+ }
+}
+
+void FdPoller::Notify() {
+ // Can be called from any thread.
+ notify_fd_.Notify();
+}
+
+std::vector<pollfd>::iterator FdPoller::FindPollEvent(base::PlatformHandle fd) {
+ PERFETTO_DCHECK_THREAD(thread_checker_);
+
+ return std::find_if(poll_fds_.begin(), poll_fds_.end(),
+ [fd](const pollfd& item) { return fd == item.fd; });
+}
+
+void FdPoller::WatchFd(base::PlatformHandle fd, WatchEvents events) {
+ auto it = FindPollEvent(fd);
+ if (it == poll_fds_.end()) {
+ poll_fds_.push_back({fd, events, 0});
+ } else {
+ it->events |= events;
+ }
+}
+
+void FdPoller::UnwatchFd(base::PlatformHandle fd, WatchEvents events) {
+ auto it = FindPollEvent(fd);
+ PERFETTO_CHECK(it != poll_fds_.end());
+ it->events &= ~events;
+}
+
+void FdPoller::RemoveWatch(base::PlatformHandle fd) {
+ auto it = FindPollEvent(fd);
+ PERFETTO_CHECK(it != poll_fds_.end());
+ poll_fds_.erase(it);
+}
+
+SocketRelayHandler::SocketRelayHandler() : fd_poller_(this) {
+ PERFETTO_DETACH_FROM_THREAD(io_thread_checker_);
+
+ io_thread_ = std::thread([this]() { this->Run(); });
+}
+
+SocketRelayHandler::~SocketRelayHandler() {
+ RunOnIOThread([this]() { this->exited_ = true; });
+ io_thread_.join();
+}
+
+void SocketRelayHandler::AddSocketPair(
+ std::unique_ptr<SocketPair> socket_pair) {
+ RunOnIOThread([this, socket_pair = std::move(socket_pair)]() mutable {
+ PERFETTO_DCHECK_THREAD(io_thread_checker_);
+
+ base::PlatformHandle fd1 = socket_pair->first.sock.fd();
+ base::PlatformHandle fd2 = socket_pair->second.sock.fd();
+ auto* ptr = socket_pair.get();
+ socket_pairs_.emplace_back(std::move(socket_pair));
+
+ fd_poller_.WatchForRead(fd1);
+ fd_poller_.WatchForRead(fd2);
+
+ socket_pairs_by_fd_[fd1] = ptr;
+ socket_pairs_by_fd_[fd2] = ptr;
+ });
+}
+
+void SocketRelayHandler::Run() {
+ PERFETTO_DCHECK_THREAD(io_thread_checker_);
+
+ while (!exited_) {
+ fd_poller_.Poll();
+
+ auto handle = base::Watchdog::GetInstance()->CreateFatalTimer(
+ kWatchdogTimeoutMs, base::WatchdogCrashReason::kTaskRunnerHung);
+
+ std::deque<std::packaged_task<void()>> pending_tasks;
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ pending_tasks = std::move(pending_tasks_);
+ }
+ while (!pending_tasks.empty()) {
+ auto task = std::move(pending_tasks.front());
+ pending_tasks.pop_front();
+ task();
+ }
+ }
+}
+
+void SocketRelayHandler::OnFdReadable(base::PlatformHandle fd) {
+ PERFETTO_DCHECK_THREAD(io_thread_checker_);
+
+ auto socket_pair = GetSocketPair(fd);
+ if (!socket_pair)
+ return; // Already removed.
+
+ auto [fd_sock, peer_sock] = *socket_pair;
+ // Buffer some bytes.
+ auto peer_fd = peer_sock.sock.fd();
+ while (fd_sock.available_bytes() > 0) {
+ auto rsize =
+ fd_sock.sock.Receive(fd_sock.buffer(), fd_sock.available_bytes());
+ if (rsize > 0) {
+ fd_sock.EnqueueData(static_cast<size_t>(rsize));
+ continue;
+ }
+
+ if (rsize == 0 || (rsize == -1 && !base::IsAgain(errno))) {
+ // TODO(chinglinyu): flush the remaining data to |peer_sock|.
+ RemoveSocketPair(fd_sock, peer_sock);
+ return;
+ }
+
+ // If there is any buffered data that needs to be sent to |peer_sock|, arm
+ // the write watcher.
+ if (fd_sock.data_size() > 0) {
+ fd_poller_.WatchForWrite(peer_fd);
+ }
+ return;
+ }
+ // We are not bufferable: need to turn off POLLIN to avoid spinning.
+ fd_poller_.UnwatchForRead(fd);
+ PERFETTO_DCHECK(fd_sock.data_size() > 0);
+ // Watching for POLLOUT will cause an OnFdWritable() event of
+ // |peer_sock|.
+ fd_poller_.WatchForWrite(peer_fd);
+}
+
+void SocketRelayHandler::OnFdWritable(base::PlatformHandle fd) {
+ PERFETTO_DCHECK_THREAD(io_thread_checker_);
+
+ auto socket_pair = GetSocketPair(fd);
+ if (!socket_pair)
+ return; // Already removed.
+
+ auto [fd_sock, peer_sock] = *socket_pair;
+ // |fd_sock| can be written to without blocking. Now we can transfer from the
+ // buffer in |peer_sock|.
+ while (peer_sock.data_size() > 0) {
+ auto wsize = fd_sock.sock.Send(peer_sock.data(), peer_sock.data_size());
+ if (wsize > 0) {
+ peer_sock.DequeueData(static_cast<size_t>(wsize));
+ continue;
+ }
+
+ if (wsize == -1 && !base::IsAgain(errno)) {
+ RemoveSocketPair(fd_sock, peer_sock);
+ }
+ // errno == EAGAIN and we still have data to send: continue watching for
+ // read.
+ return;
+ }
+
+ // We don't have buffered data to send. Disable watching for write.
+ fd_poller_.UnwatchForWrite(fd);
+ auto peer_fd = peer_sock.sock.fd();
+ if (peer_sock.available_bytes())
+ fd_poller_.WatchForRead(peer_fd);
+}
+
+std::optional<std::tuple<SocketWithBuffer&, SocketWithBuffer&>>
+SocketRelayHandler::GetSocketPair(base::PlatformHandle fd) {
+ PERFETTO_DCHECK_THREAD(io_thread_checker_);
+
+ auto* socket_pair = socket_pairs_by_fd_.Find(fd);
+ if (!socket_pair)
+ return std::nullopt;
+
+ PERFETTO_DCHECK(fd == (*socket_pair)->first.sock.fd() ||
+ fd == (*socket_pair)->second.sock.fd());
+
+ if (fd == (*socket_pair)->first.sock.fd())
+ return std::tie((*socket_pair)->first, (*socket_pair)->second);
+
+ return std::tie((*socket_pair)->second, (*socket_pair)->first);
+}
+
+void SocketRelayHandler::RemoveSocketPair(SocketWithBuffer& sock1,
+ SocketWithBuffer& sock2) {
+ PERFETTO_DCHECK_THREAD(io_thread_checker_);
+
+ auto fd1 = sock1.sock.fd();
+ auto fd2 = sock2.sock.fd();
+ fd_poller_.RemoveWatch(fd1);
+ fd_poller_.RemoveWatch(fd2);
+
+ auto* ptr1 = socket_pairs_by_fd_.Find(fd1);
+ auto* ptr2 = socket_pairs_by_fd_.Find(fd2);
+ PERFETTO_DCHECK(ptr1 && ptr2);
+ PERFETTO_DCHECK(*ptr1 == *ptr2);
+
+ auto* socket_pair_ptr = *ptr1;
+
+ socket_pairs_by_fd_.Erase(fd1);
+ socket_pairs_by_fd_.Erase(fd2);
+
+ socket_pairs_.erase(
+ std::remove_if(
+ socket_pairs_.begin(), socket_pairs_.end(),
+ [socket_pair_ptr](const std::unique_ptr<SocketPair>& item) {
+ return item.get() == socket_pair_ptr;
+ }),
+ socket_pairs_.end());
+}
+
+} // namespace perfetto
diff --git a/src/traced_relay/socket_relay_handler.h b/src/traced_relay/socket_relay_handler.h
new file mode 100644
index 0000000..39579a9
--- /dev/null
+++ b/src/traced_relay/socket_relay_handler.h
@@ -0,0 +1,190 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACED_RELAY_SOCKET_RELAY_HANDLER_H_
+#define SRC_TRACED_RELAY_SOCKET_RELAY_HANDLER_H_
+
+#include <poll.h>
+
+#include <cstring>
+#include <deque>
+#include <future>
+#include <mutex>
+#include <optional>
+#include <thread>
+#include <tuple>
+
+#include "perfetto/base/platform_handle.h"
+#include "perfetto/ext/base/event_fd.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/thread_checker.h"
+#include "perfetto/ext/base/unix_socket.h"
+#include "perfetto/ext/ipc/basic_types.h"
+
+namespace perfetto {
+
+// FdPoller is a utility for waiting for IO events of a set of watched file
+// descriptors. It's used for multiplexing non-blocking IO operations.
+class FdPoller {
+ public:
+ // The interface class for observing IO events from the FdPoller class.
+ class Watcher {
+ public:
+ virtual ~Watcher();
+ // Called when |fd| can be read from without blocking. For a socket
+ // connection, this indicates the socket read buffer has some data.
+ virtual void OnFdReadable(base::PlatformHandle fd) = 0;
+ // Called when |fd| can be written to without blocking. For a socket
+ // connection, this indicates that the socket write buffer has some capacity
+ // for writting data into.
+ virtual void OnFdWritable(base::PlatformHandle fd) = 0;
+ };
+
+ using WatchEvents = decltype(pollfd::events);
+
+ explicit FdPoller(Watcher* watcher);
+
+ // Watch and unwatch IO event for a given file descriptor.
+ inline void WatchForRead(base::PlatformHandle fd) { WatchFd(fd, POLLIN); }
+ inline void WatchForWrite(base::PlatformHandle fd) { WatchFd(fd, POLLOUT); }
+ inline void UnwatchForRead(base::PlatformHandle fd) { UnwatchFd(fd, POLLIN); }
+ inline void UnwatchForWrite(base::PlatformHandle fd) {
+ UnwatchFd(fd, POLLOUT);
+ }
+
+ // Called when |fd| is no longer of interest (e.g. when |fd| is to be closed).
+ void RemoveWatch(base::PlatformHandle fd);
+
+ // Poll for all watched events previously added with WatchForRead() and
+ // WatchForWrite().
+ //
+ // Must be called on poller thread.
+ void Poll();
+
+ // Notifies the poller for pending updates. Calling Notify() will unblock the
+ // poller and make it return from Poll(). It is caller's responsibility to
+ // call Poll() again once the updates are complete.
+ //
+ // This can be (and typically is) called from any thread.
+ void Notify();
+
+ private:
+ std::vector<pollfd>::iterator FindPollEvent(base::PlatformHandle fd);
+ void WatchFd(base::PlatformHandle fd, WatchEvents events);
+ void UnwatchFd(base::PlatformHandle fd, WatchEvents events);
+
+ base::ThreadChecker thread_checker_;
+ Watcher* const watcher_;
+ base::EventFd notify_fd_;
+ std::vector<pollfd> poll_fds_;
+};
+
+// This class groups a UnixSocketRaw with an associated ring buffer. The ring
+// buffer is used as a temporary storage for data *read* from the socket.
+class SocketWithBuffer {
+ public:
+ constexpr static size_t kBuffSize = ipc::kIPCBufferSize;
+
+ base::UnixSocketRaw sock;
+
+ // Points to the beginning of buffered data.
+ inline uint8_t* data() { return &buf_[0]; }
+ // Size of the buffered data.
+ inline size_t data_size() { return data_size_; }
+
+ // Points to the beginning of the free space for buffering new data.
+ inline uint8_t* buffer() { return &buf_[data_size_]; }
+ // Size of the free space.
+ inline size_t available_bytes() { return buf_.size() - data_size_; }
+
+ // Called when |bytes| of data is enqueued to the buffer.
+ void EnqueueData(size_t bytes) {
+ PERFETTO_CHECK(bytes <= available_bytes());
+ data_size_ += bytes;
+ }
+ // Called when |bytes| of data is dequeued from the buffer.
+ void DequeueData(size_t bytes) {
+ PERFETTO_CHECK(bytes <= data_size());
+ memmove(data(), data() + bytes, data_size() - bytes);
+ data_size_ -= bytes;
+ }
+
+ SocketWithBuffer() : buf_(kBuffSize) {}
+
+ // Movable only.
+ SocketWithBuffer(SocketWithBuffer&& other) = default;
+ SocketWithBuffer& operator=(SocketWithBuffer&& other) = default;
+ SocketWithBuffer(const SocketWithBuffer& other) = delete;
+ SocketWithBuffer& operator=(const SocketWithBuffer& other) = delete;
+
+ private:
+ std::vector<uint8_t> buf_;
+ size_t data_size_ = 0;
+};
+
+using SocketPair = std::pair<SocketWithBuffer, SocketWithBuffer>;
+
+// SocketRelayHandler bidirectionally forwards data between paired sockets.
+// Internally it multiplexes IO operations of the sockets using a FdPoller on a
+// dedicated thread.
+class SocketRelayHandler : public FdPoller::Watcher {
+ public:
+ SocketRelayHandler();
+ SocketRelayHandler(const SocketRelayHandler&) = delete;
+ SocketRelayHandler& operator=(const SocketRelayHandler&) = delete;
+ ~SocketRelayHandler() override;
+
+ // Transfer a pair of sockets to be relayed. Can be called from any thread.
+ void AddSocketPair(std::unique_ptr<SocketPair> socket_pair);
+
+ // The FdPoller::Watcher callbacks.
+ void OnFdReadable(base::PlatformHandle fd) override;
+ void OnFdWritable(base::PlatformHandle fd) override;
+
+ private:
+ void Run();
+ void RemoveSocketPair(SocketWithBuffer&, SocketWithBuffer&);
+
+ // A helper for running a callable object on |io_thread_|.
+ template <typename Callable>
+ void RunOnIOThread(Callable&& c) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ pending_tasks_.emplace_back(std::forward<Callable>(c));
+ fd_poller_.Notify();
+ }
+
+ std::optional<std::tuple<SocketWithBuffer&, SocketWithBuffer&>> GetSocketPair(
+ base::PlatformHandle fd);
+
+ base::FlatHashMap<base::PlatformHandle, SocketPair*> socket_pairs_by_fd_;
+ std::vector<std::unique_ptr<SocketPair>> socket_pairs_;
+
+ FdPoller fd_poller_;
+
+ // The thread that fd_poller_ polls for IO events. Most methods of this class
+ // asserts to be running on this thread.
+ std::thread io_thread_;
+ base::ThreadChecker io_thread_checker_;
+
+ bool exited_ = false;
+
+ //--------------- Member data with multi-thread access ------------------
+ std::mutex mutex_;
+ std::deque<std::packaged_task<void()>> pending_tasks_;
+};
+
+} // namespace perfetto
+#endif // SRC_TRACED_RELAY_SOCKET_RELAY_HANDLER_H_
diff --git a/src/tracing/core/trace_writer_impl_unittest.cc b/src/tracing/core/trace_writer_impl_unittest.cc
index a5b8606..c59d9c5 100644
--- a/src/tracing/core/trace_writer_impl_unittest.cc
+++ b/src/tracing/core/trace_writer_impl_unittest.cc
@@ -299,7 +299,14 @@
const BufferID kBufId = 42;
std::unique_ptr<TraceWriter> writer = arbiter_->CreateTraceWriter(kBufId);
- ScatteredStreamWriter* sw = writer->NewTracePacket().TakeStreamWriter();
+ TraceWriterImpl::TracePacketHandle handle = writer->NewTracePacket();
+
+ // Avoid a secondary DCHECK failure from ~TraceWriterImpl() =>
+ // Message::Finalize() due to the stream writer being modified behind the
+ // Message's back. This turns the Finalize() call into a no-op.
+ handle->set_size_field(nullptr);
+
+ ScatteredStreamWriter* sw = handle.TakeStreamWriter();
std::string raw_proto_bytes = std::string("RAW_PROTO_BYTES");
sw->WriteBytes(reinterpret_cast<const uint8_t*>(raw_proto_bytes.data()),
raw_proto_bytes.size());
diff --git a/src/tracing/ipc/default_socket.cc b/src/tracing/ipc/default_socket.cc
index a2c11d8..c445c6e 100644
--- a/src/tracing/ipc/default_socket.cc
+++ b/src/tracing/ipc/default_socket.cc
@@ -88,6 +88,11 @@
return name;
}
+const char* GetRelaySocket() {
+ // The relay socket is optional and is connected only when the env var is set.
+ return getenv("PERFETTO_RELAY_SOCK_NAME");
+}
+
std::vector<std::string> TokenizeProducerSockets(
const char* producer_socket_names) {
return base::SplitString(producer_socket_names, ",");
diff --git a/test/data/android_boot.pftrace.sha256 b/test/data/android_boot.pftrace.sha256
new file mode 100644
index 0000000..1d9798f
--- /dev/null
+++ b/test/data/android_boot.pftrace.sha256
@@ -0,0 +1 @@
+660231895e7c1816bcbd02771fdf825917ba0540124c3fdd174c1a91470b9448
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index 05dac05..6cee1b2 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-b301d6515efaffb58f8e8227ae5bcf6b78ea592b92f9037c2abe1954c6102b8e
\ No newline at end of file
+ec0a00856b147b2e13d0fe18666a307eb085ac437d67f78787131d4ea4190581
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/android/android_monitor_contention.out b/test/trace_processor/diff_tests/android/android_monitor_contention.out
index b0a9894..43cf561 100644
--- a/test/trace_processor/diff_tests/android/android_monitor_contention.out
+++ b/test/trace_processor/diff_tests/android/android_monitor_contention.out
@@ -9455,41 +9455,6 @@
}
}
node {
- node_id: 1303
- ts: 1737162534263
- dur: 420259
- blocking_method: "void com.android.server.am.AppProfiler.collectPssInBackground()"
- blocked_method: "void com.android.server.am.ProcessProfileRecord.onProcessInactive(com.android.server.am.ProcessStatsService)"
- short_blocking_method: "com.android.server.am.AppProfiler.collectPssInBackground"
- short_blocked_method: "com.android.server.am.ProcessProfileRecord.onProcessInactive"
- blocking_src: "AppProfiler.java:514"
- blocked_src: "ProcessProfileRecord.java:349"
- waiter_count: 0
- blocking_thread_name: "android.bg"
- blocked_thread_name: "binder:642_14"
- blocked_thread_tid: 3485
- blocking_thread_tid: 670
- process_name: "system_server"
- pid: 642
- is_blocked_thread_main: false
- is_blocking_thread_main: false
- thread_states {
- thread_state: "R"
- thread_state_dur: 389378
- thread_state_count: 2
- }
- thread_states {
- thread_state: "R+"
- thread_state_dur: 8868
- thread_state_count: 1
- }
- thread_states {
- thread_state: "Running"
- thread_state_dur: 22013
- thread_state_count: 2
- }
- }
- node {
node_child_id: 1307
node_id: 1303
ts: 1737162534263
@@ -9526,36 +9491,6 @@
}
}
node {
- node_id: 949
- ts: 1737122781871
- dur: 7301144
- blocking_method: "void com.android.server.am.AppProfiler.collectPssInBackground()"
- blocked_method: "void com.android.server.am.ProcessRecord.setPid(int)"
- short_blocking_method: "com.android.server.am.AppProfiler.collectPssInBackground"
- short_blocked_method: "com.android.server.am.ProcessRecord.setPid"
- blocking_src: "AppProfiler.java:514"
- blocked_src: "ProcessRecord.java:596"
- waiter_count: 0
- blocking_thread_name: "android.bg"
- blocked_thread_name: "binder:642_12"
- blocked_thread_tid: 2720
- blocking_thread_tid: 670
- process_name: "system_server"
- pid: 642
- is_blocked_thread_main: false
- is_blocking_thread_main: false
- thread_states {
- thread_state: "R+"
- thread_state_dur: 7127675
- thread_state_count: 3
- }
- thread_states {
- thread_state: "Running"
- thread_state_dur: 173469
- thread_state_count: 2
- }
- }
- node {
node_child_id: 956
node_id: 949
ts: 1737122781871
diff --git a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out
index 5b69129..a30c810 100644
--- a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out
+++ b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out
@@ -14,6 +14,20 @@
min_dur_ns: 10000000
}
blocking_calls {
+ name: "ImageFloatingTextView#onMeasure"
+ cnt: 1
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "NotificationShadeWindowView#onMeasure"
+ cnt: 1
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
name: "NotificationStackScrollLayout#onMeasure"
cnt: 1
total_dur_ns: 10000000
diff --git a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
index c5df8b4..cab8c8f 100644
--- a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
+++ b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
@@ -24,7 +24,8 @@
# List of blocking calls
blocking_call_names = [
'NotificationStackScrollLayout#onMeasure', 'ExpNotRow#onMeasure(MessagingStyle)',
- 'ExpNotRow#onMeasure(BigTextStyle)',
+ 'ExpNotRow#onMeasure(BigTextStyle)', 'NotificationShadeWindowView#onMeasure',
+ 'ImageFloatingTextView#onMeasure',
'Should not be in the metric'
]
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index fcf165c..8bbbeb7 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -1173,3 +1173,16 @@
/system/bin/servicemanager (0x0)
/system/bin/storaged (0x0)
"""))
+
+ def test_android_boot(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_boot.pftrace'),
+ query=Metric('android_boot'),
+ out=TextProto(r"""
+ android_boot {
+ system_server_durations {
+ total_dur: 267193980530
+ uninterruptible_sleep_dur: 3843119529
+ }
+ }
+ """))
diff --git a/test/trace_processor/diff_tests/android_fs/tests.py b/test/trace_processor/diff_tests/android_fs/tests.py
new file mode 100644
index 0000000..358384d
--- /dev/null
+++ b/test/trace_processor/diff_tests/android_fs/tests.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+# 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 a
+#
+# 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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class AndroidFs(TestSuite):
+
+ # android_fs_dataread
+ def test_android_fs_dataread(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 46448185788840
+ pid: 5892
+ android_fs_dataread_start {
+ bytes: 4096
+ pid: 5892
+ ino: 836
+ offset: 0
+ cmdline: "am"
+ i_size: 31772
+ pathbuf: "/system/bin/cmd"
+ }
+ }
+ }
+ }
+ packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 46448185789530
+ pid: 156
+ android_fs_dataread_end {
+ bytes: 4096
+ ino: 836
+ offset: 0
+ }
+ }
+ }
+ }
+ """),
+ query="""
+ SELECT ts, dur, name FROM slice WHERE name = 'android_fs_data_read';
+ """,
+ out=Csv("""
+ "ts","dur","name"
+ 46448185788840,690,"android_fs_data_read"
+ """))
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index b960520..b131e43 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -94,6 +94,7 @@
from diff_tests.translation.tests import Translation
from diff_tests.ufs.tests import Ufs
from diff_tests.webview.tests import WebView
+from diff_tests.android_fs.tests import AndroidFs
sys.path.pop()
@@ -102,6 +103,7 @@
return [
*Android(index_path, 'android', 'Android').fetch(),
*AndroidBugreport(index_path, 'android', 'AndroidBugreport').fetch(),
+ *AndroidFs(index_path, 'android_fs', 'AndroidFs').fetch(),
*AndroidGames(index_path, 'android', 'AndroidGames').fetch(),
*Atrace(index_path, 'atrace', 'Atrace').fetch(),
*AtraceErrorHandling(index_path, 'atrace', 'AtraceErrorHandling').fetch(),
diff --git a/test/trace_processor/diff_tests/perfetto_sql/tests.py b/test/trace_processor/diff_tests/perfetto_sql/tests.py
index f011d3e..d5e37b3 100644
--- a/test/trace_processor/diff_tests/perfetto_sql/tests.py
+++ b/test/trace_processor/diff_tests/perfetto_sql/tests.py
@@ -157,3 +157,31 @@
"TRACE_START()"
1000
"""))
+
+ def test_macro(self):
+ return DiffTestBlueprint(
+ trace=TextProto(''),
+ query='''
+ CREATE PERFETTO MACRO foo(a Expr,b Expr) RETURNS TableOrSubquery AS
+ SELECT $a - $b;
+ SELECT (foo!(123, 100)) as res;
+ ''',
+ out=Csv("""
+ "res"
+ 23
+ """))
+
+ def test_nested_macro(self):
+ return DiffTestBlueprint(
+ trace=TextProto(''),
+ query='''
+ CREATE PERFETTO MACRO foo(a Expr) returns Expr AS $a;
+ CREATE PERFETTO MACRO bar(a Expr) returns Expr AS (SELECT $a);
+ CREATE PERFETTO MACRO baz(a Expr,b Expr) returns TableOrSubquery AS
+ SELECT bar!(foo!(123)) - $b as res;
+ baz!(123, 100);
+ ''',
+ out=Csv("""
+ "res"
+ 23
+ """))
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 8ba1f0a..a5e852e 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -37,18 +37,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 8758200,
+ 8889568,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
'sha256':
- 'b6eb3552efc97b15b1cb7a746b90f77a4ac781566faa4ffa84e1785f5d90b38b',
+ 'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -58,11 +58,11 @@
'file_name':
'traceconv',
'file_size':
- 7332088,
+ 7447928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
'sha256':
- '585ee448560fa89f559566cdf41dcc6677a28c74ea3b5bae3cfe31cde813578c',
+ '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
'platform':
'darwin',
'machine': ['arm64']
@@ -72,11 +72,11 @@
'file_name':
'traceconv',
'file_size':
- 8515568,
+ 8633416,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
'sha256':
- 'bff2c0c525193fc3555725b5767a30bc2b36d7d84b849b7ce41f7abb349f21be',
+ 'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
'platform':
'linux',
'machine': ['x86_64']
@@ -86,11 +86,11 @@
'file_name':
'traceconv',
'file_size':
- 6421928,
+ 6505268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
'sha256':
- '566298db777a31f3eabd0e6b17e1b0638875725be767333308f02a7fb7d4e36c',
+ '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
'file_name':
'traceconv',
'file_size':
- 8001776,
+ 8108432,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
'sha256':
- 'ccf81438df9ed30f88e640f62938b6334698b9158364db43252d7183136e137d',
+ '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
'platform':
'linux',
'machine': ['aarch64']
@@ -114,55 +114,55 @@
'file_name':
'traceconv',
'file_size':
- 6021600,
+ 6096120,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
'sha256':
- 'edae3606563616fa304e3240a8426db3dfd7d12ad28bb879e7d482af357af7f2'
+ 'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 7305880,
+ 7401672,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
'sha256':
- '1a0ef7601a6d2f77f9a04d5bb8c092f5a536764a8e9a99325fff6b81c2879d1e'
+ '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 8118692,
+ 8238268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
'sha256':
- '6f9b53427d367e8582b12763d27e83217c9e744f947f58acc0b404b6ce5103e9'
+ '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 8288976,
+ 8397056,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
'sha256':
- '5c843ddb151f5aa32288814cb190a879f15e6da63d642d3ded0f15954f480b5f'
+ 'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 7756800,
+ 7867904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
'sha256':
- 'b07849cd60872279dda68d4b8ec173af01e0cbb1692eb5473ef230ac89198f2d',
+ '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
'platform':
'win32',
'machine': ['amd64']
diff --git a/tools/gen_bazel b/tools/gen_bazel
index d07cc13..b693c6f 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -76,7 +76,6 @@
# exported publicly.
default_targets = [
'//src/base:perfetto_base_default_platform',
- '//src/bigtrace:bigtrace',
'//src/ipc:perfetto_ipc',
'//src/ipc/protoc_plugin:ipc_plugin',
'//src/protozero:protozero',
@@ -88,12 +87,6 @@
'//test:client_api_example',
] + public_targets
-# Proto targets are required by internal build rules but don't need to be
-# exported publicly.
-proto_default_targets = [
- '//protos/perfetto/bigtrace:lite'
-]
-
# Proto target groups which will be made public.
proto_groups = {
'config': {
@@ -119,6 +112,10 @@
'sources': ['//protos/perfetto/metrics/chrome:source_set',],
'visibility': ALLOWLIST_PUBLIC_VISIBILITY,
},
+ 'trace_processor': {
+ 'sources': ['//protos/perfetto/trace_processor:source_set',],
+ 'visibility': [],
+ },
}
# Path for the protobuf sources in the standalone build.
@@ -517,30 +514,33 @@
':' + get_bazel_proto_sources_label(name)
for name in sorted(list(deps_set))
]
- sources_label.visibility = desc['visibility']
sources_label.comment = f'''[{', '.join(desc['sources'])}]'''
cc_label = BazelLabel(name + '_cc_proto', 'perfetto_cc_proto_library')
cc_label.deps = [':' + sources_label.name]
- cc_label.visibility = desc['visibility']
cc_label.comment = sources_label.comment
java_label = BazelLabel(name + '_java_proto', 'perfetto_java_proto_library')
java_label.deps = [':' + sources_label.name]
- java_label.visibility = desc['visibility']
java_label.comment = sources_label.comment
lite_name = name + '_java_proto_lite'
java_lite_label = BazelLabel(lite_name, 'perfetto_java_lite_proto_library')
java_lite_label.deps = [':' + sources_label.name]
- java_lite_label.visibility = desc['visibility']
java_lite_label.comment = sources_label.comment
py_label = BazelLabel(name + '_py_pb2', 'perfetto_py_proto_library')
py_label.deps = [':' + sources_label.name]
- py_label.visibility = desc['visibility']
py_label.comment = sources_label.comment
+ visibility = desc['visibility']
+ if visibility:
+ sources_label.visibility = visibility
+ cc_label.visibility = visibility
+ java_label.visibility = visibility
+ java_lite_label.visibility = visibility
+ py_label.visibility = visibility
+
return [sources_label, cc_label, java_label, java_lite_label, py_label]
@@ -796,10 +796,6 @@
continue
gn.get_target(re.sub('(lite|zero|cpp|ipc)$', 'source_set', target.name))
- # Discover all the default proto targets so it will be generated next.
- for target in sorted(proto_default_targets):
- gn.get_target(target)
-
# Generate targets for the transitive set of proto targets.
labels = [
l for target in sorted(itervalues(gn.proto_libs))
diff --git a/tools/heap_profile b/tools/heap_profile
index 71fb54a..6c2a399 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -34,18 +34,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 8758200,
+ 8889568,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
'sha256':
- 'b6eb3552efc97b15b1cb7a746b90f77a4ac781566faa4ffa84e1785f5d90b38b',
+ 'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -55,11 +55,11 @@
'file_name':
'traceconv',
'file_size':
- 7332088,
+ 7447928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
'sha256':
- '585ee448560fa89f559566cdf41dcc6677a28c74ea3b5bae3cfe31cde813578c',
+ '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
'platform':
'darwin',
'machine': ['arm64']
@@ -69,11 +69,11 @@
'file_name':
'traceconv',
'file_size':
- 8515568,
+ 8633416,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
'sha256':
- 'bff2c0c525193fc3555725b5767a30bc2b36d7d84b849b7ce41f7abb349f21be',
+ 'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
'platform':
'linux',
'machine': ['x86_64']
@@ -83,11 +83,11 @@
'file_name':
'traceconv',
'file_size':
- 6421928,
+ 6505268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
'sha256':
- '566298db777a31f3eabd0e6b17e1b0638875725be767333308f02a7fb7d4e36c',
+ '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
'file_name':
'traceconv',
'file_size':
- 8001776,
+ 8108432,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
'sha256':
- 'ccf81438df9ed30f88e640f62938b6334698b9158364db43252d7183136e137d',
+ '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
'platform':
'linux',
'machine': ['aarch64']
@@ -111,55 +111,55 @@
'file_name':
'traceconv',
'file_size':
- 6021600,
+ 6096120,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
'sha256':
- 'edae3606563616fa304e3240a8426db3dfd7d12ad28bb879e7d482af357af7f2'
+ 'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 7305880,
+ 7401672,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
'sha256':
- '1a0ef7601a6d2f77f9a04d5bb8c092f5a536764a8e9a99325fff6b81c2879d1e'
+ '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 8118692,
+ 8238268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
'sha256':
- '6f9b53427d367e8582b12763d27e83217c9e744f947f58acc0b404b6ce5103e9'
+ '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 8288976,
+ 8397056,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
'sha256':
- '5c843ddb151f5aa32288814cb190a879f15e6da63d642d3ded0f15954f480b5f'
+ 'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 7756800,
+ 7867904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
'sha256':
- 'b07849cd60872279dda68d4b8ec173af01e0cbb1692eb5473ef230ac89198f2d',
+ '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
'platform':
'win32',
'machine': ['amd64']
diff --git a/tools/record_android_trace b/tools/record_android_trace
index 70f2906..f91496c 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -33,18 +33,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACEBOX_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'tracebox',
'file_size':
- 1481952,
+ 1498560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/tracebox',
'sha256':
- '81787717e819e1412609a7c67e561f643516cabd897d6b91e1f5cf572a40521a',
+ 'b760c7ed682d23f8d268174a939f1b8cb130ffb0d52f42b4cc4499a25423e782',
'platform':
'darwin',
'machine': ['x86_64']
@@ -54,11 +54,11 @@
'file_name':
'tracebox',
'file_size':
- 1359320,
+ 1376008,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/tracebox',
'sha256':
- '5cd27e847e1e88493d136a2a803fd644cffc673d12685442a1df00c928170429',
+ '2835127a5fc42e501e29a685a1cbcd98c26f977ee845bb1bd60a43008fd48161',
'platform':
'darwin',
'machine': ['arm64']
@@ -68,11 +68,11 @@
'file_name':
'tracebox',
'file_size':
- 2200152,
+ 2212088,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/tracebox',
'sha256':
- 'de4f883b1a3509ad9533c3057787a00cf8e68cbb1b5b6bde385caee3b2433574',
+ '0b4a61a3e45f4e1b6111ca0b440f9e4a0b0726df912542373f69b6821a3113bc',
'platform':
'linux',
'machine': ['x86_64']
@@ -82,11 +82,11 @@
'file_name':
'tracebox',
'file_size':
- 1317900,
+ 1327204,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/tracebox',
'sha256':
- 'c404c93a16b3808b9b668f2ef071faecbde1a8c012be670a595716d2a730e5e9',
+ '43c20d80e5b40cebe5b26579319be6d57796b04155ffc90f133b6a13c2de25ab',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -96,11 +96,11 @@
'file_name':
'tracebox',
'file_size':
- 2128352,
+ 2139928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/tracebox',
'sha256':
- '851c82a00e84b492627503ca2c46afcbd3ec1770c06b002134602541e6006b0c',
+ '261cd7912dd69e8d82ce61b66a78f098eba46f6f492f6ec6bf73124d40a9a202',
'platform':
'linux',
'machine': ['aarch64']
@@ -110,44 +110,44 @@
'file_name':
'tracebox',
'file_size':
- 1198036,
+ 1202132,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/tracebox',
'sha256':
- 'fac9f7cbe888139c569068d973be79f4d820a8f323ad04cdb9c5c3be6faba067'
+ '43874c4d187e3da5820ae3988d16608a8bf89d04282b8ab3e1486c6e57cd891f'
}, {
'arch':
'android-arm64',
'file_name':
'tracebox',
'file_size':
- 1813160,
+ 1825448,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/tracebox',
'sha256':
- '5c344c218c440c678fa56c8ce4d2f9c26efc300ebf268afd6e68a5775628cc0c'
+ '9264d8f23ea3988f2952696668d3d572669134e4ca5b3e25a3209d3bfdb7d015'
}, {
'arch':
'android-x86',
'file_name':
'tracebox',
'file_size':
- 1812396,
+ 1820588,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/tracebox',
'sha256':
- 'cd44b4ab3a1579dd0082f6f0fa4d7f710b9d056b44606cb247d41524be6d576e'
+ 'd591e903b1e2b0e4b270f0510c24992aac68167c80e61450ebf6c36cc780cd21'
}, {
'arch':
'android-x64',
'file_name':
'tracebox',
'file_size':
- 2091688,
+ 2108072,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/tracebox',
'sha256':
- '6d614a3d4e77c7fb0712bf13d7a2c86a59b0945a043fefb70ffc70854551fca0'
+ 'd3e4278e17764b605236c568202eb8d94f3f7e7593b0be9eba09f670a987acba'
}]
# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/trace_processor b/tools/trace_processor
index 50e7fe0..f88419c 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -30,18 +30,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACE_PROCESSOR_SHELL_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'trace_processor_shell',
'file_size':
- 9551608,
+ 9682976,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/trace_processor_shell',
'sha256':
- 'f3def324604f551fdf7df088363aebf74de573fdaa02796fd994f1863ad58463',
+ '74b097836f16d788edce11bfda46f52e6499d8ec546d10f8dbab182612407b3b',
'platform':
'darwin',
'machine': ['x86_64']
@@ -51,11 +51,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 8064168,
+ 8180008,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/trace_processor_shell',
'sha256':
- 'f34a2cfb4f93579452bbaf4f47c9b0fe475394d55316c9626949760c206ec801',
+ '9a96a2f9ef81f210fcba4a08b21db6f2f57fb1d325e91924043ae066327c29a8',
'platform':
'darwin',
'machine': ['arm64']
@@ -65,11 +65,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 9415488,
+ 9533320,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/trace_processor_shell',
'sha256':
- '86d133fc2fe0204693d2b22e9287363cb44bf76794b0091d4d0bb3388b7cd8d5',
+ 'ee0ccae766aad09f0135efa83cc3a1cd78bd0e86824a7b1245e013d32e61d820',
'platform':
'linux',
'machine': ['x86_64']
@@ -79,11 +79,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 6880412,
+ 6963584,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/trace_processor_shell',
'sha256':
- '21fd4b2ad68b1f9829cee72d3905b1712cbfa5e169e89b89cdd0a3a4b1682e7d',
+ '2c69d2016dd18b6d42bf6c2a5b4398e29e4fd294950882387942272dd25a045a',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 8843272,
+ 8950112,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/trace_processor_shell',
'sha256':
- '97a6fb150fd6655f03394d8ad166fd2041a1b4ca56a9e7ce5b35902ff64e1a35',
+ 'a10a7a9a6614461beb1f32ee16da3290e2770bb6f3a506269defbdbf7e8803af',
'platform':
'linux',
'machine': ['aarch64']
@@ -107,55 +107,55 @@
'file_name':
'trace_processor_shell',
'file_size':
- 6497440,
+ 6580152,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/trace_processor_shell',
'sha256':
- '6018ce36314a75693c5e5c2a69a3aacc9848385bd3ab9121e23de70df5188e1e'
+ '994a038b932accf5796550207a4e7d80c7612fd25e6eee414324e7e4f3ae14f2'
}, {
'arch':
'android-arm64',
'file_name':
'trace_processor_shell',
'file_size':
- 8019768,
+ 8115560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/trace_processor_shell',
'sha256':
- '4e36255ebcd5e19fe26def6578cc604d636df9c4fa335ec5901e6cd9aba92ac3'
+ '42426b12ad60894aee37e502e4d023cfded53e706d990abf5c70c4556c2b73ff'
}, {
'arch':
'android-x86',
'file_name':
'trace_processor_shell',
'file_size':
- 8897636,
+ 9009020,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/trace_processor_shell',
'sha256':
- 'f21573cc77603150e461402c6f032b7ff8f103debcd9d9b2093f521a69f581f7'
+ 'af982ad7897d8cafb29a9f1303e32df20486a92eb07930db8b1898a75e84a667'
}, {
'arch':
'android-x64',
'file_name':
'trace_processor_shell',
'file_size':
- 9162616,
+ 9270696,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/trace_processor_shell',
'sha256':
- '2cc5a02270a28db52aa17bfafcf215e3d754a936de595186a7152535ab28531b'
+ '081be392ddccdf37da80dd888277ea9215cfa8d05ddf6f90d9bff13a0a72f672'
}, {
'arch':
'windows-amd64',
'file_name':
'trace_processor_shell.exe',
'file_size':
- 8790016,
+ 8898560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/windows-amd64/trace_processor_shell.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/trace_processor_shell.exe',
'sha256':
- 'e3193141bcb8bcc35b47c3ee8126d1f3ebe1b54a9ecde59ade9db4f22c6f77d6',
+ '2e66e5b6ab9c0f7ecad98c3b5b822133c9aa34f137b4007c228c78672fdea5a7',
'platform':
'win32',
'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index f5bbf47..4425edd 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -30,18 +30,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACEBOX_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'tracebox',
'file_size':
- 1481952,
+ 1498560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/tracebox',
'sha256':
- '81787717e819e1412609a7c67e561f643516cabd897d6b91e1f5cf572a40521a',
+ 'b760c7ed682d23f8d268174a939f1b8cb130ffb0d52f42b4cc4499a25423e782',
'platform':
'darwin',
'machine': ['x86_64']
@@ -51,11 +51,11 @@
'file_name':
'tracebox',
'file_size':
- 1359320,
+ 1376008,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/tracebox',
'sha256':
- '5cd27e847e1e88493d136a2a803fd644cffc673d12685442a1df00c928170429',
+ '2835127a5fc42e501e29a685a1cbcd98c26f977ee845bb1bd60a43008fd48161',
'platform':
'darwin',
'machine': ['arm64']
@@ -65,11 +65,11 @@
'file_name':
'tracebox',
'file_size':
- 2200152,
+ 2212088,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/tracebox',
'sha256':
- 'de4f883b1a3509ad9533c3057787a00cf8e68cbb1b5b6bde385caee3b2433574',
+ '0b4a61a3e45f4e1b6111ca0b440f9e4a0b0726df912542373f69b6821a3113bc',
'platform':
'linux',
'machine': ['x86_64']
@@ -79,11 +79,11 @@
'file_name':
'tracebox',
'file_size':
- 1317900,
+ 1327204,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/tracebox',
'sha256':
- 'c404c93a16b3808b9b668f2ef071faecbde1a8c012be670a595716d2a730e5e9',
+ '43c20d80e5b40cebe5b26579319be6d57796b04155ffc90f133b6a13c2de25ab',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
'file_name':
'tracebox',
'file_size':
- 2128352,
+ 2139928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/tracebox',
'sha256':
- '851c82a00e84b492627503ca2c46afcbd3ec1770c06b002134602541e6006b0c',
+ '261cd7912dd69e8d82ce61b66a78f098eba46f6f492f6ec6bf73124d40a9a202',
'platform':
'linux',
'machine': ['aarch64']
@@ -107,44 +107,44 @@
'file_name':
'tracebox',
'file_size':
- 1198036,
+ 1202132,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/tracebox',
'sha256':
- 'fac9f7cbe888139c569068d973be79f4d820a8f323ad04cdb9c5c3be6faba067'
+ '43874c4d187e3da5820ae3988d16608a8bf89d04282b8ab3e1486c6e57cd891f'
}, {
'arch':
'android-arm64',
'file_name':
'tracebox',
'file_size':
- 1813160,
+ 1825448,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/tracebox',
'sha256':
- '5c344c218c440c678fa56c8ce4d2f9c26efc300ebf268afd6e68a5775628cc0c'
+ '9264d8f23ea3988f2952696668d3d572669134e4ca5b3e25a3209d3bfdb7d015'
}, {
'arch':
'android-x86',
'file_name':
'tracebox',
'file_size':
- 1812396,
+ 1820588,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/tracebox',
'sha256':
- 'cd44b4ab3a1579dd0082f6f0fa4d7f710b9d056b44606cb247d41524be6d576e'
+ 'd591e903b1e2b0e4b270f0510c24992aac68167c80e61450ebf6c36cc780cd21'
}, {
'arch':
'android-x64',
'file_name':
'tracebox',
'file_size':
- 2091688,
+ 2108072,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/tracebox',
'sha256':
- '6d614a3d4e77c7fb0712bf13d7a2c86a59b0945a043fefb70ffc70854551fca0'
+ 'd3e4278e17764b605236c568202eb8d94f3f7e7593b0be9eba09f670a987acba'
}]
# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index ef63547..7ba7d3e 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -30,18 +30,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v37.0
+# This file has been generated by: tools/roll-prebuilts v38.0
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 8758200,
+ 8889568,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
'sha256':
- 'b6eb3552efc97b15b1cb7a746b90f77a4ac781566faa4ffa84e1785f5d90b38b',
+ 'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -51,11 +51,11 @@
'file_name':
'traceconv',
'file_size':
- 7332088,
+ 7447928,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
'sha256':
- '585ee448560fa89f559566cdf41dcc6677a28c74ea3b5bae3cfe31cde813578c',
+ '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
'platform':
'darwin',
'machine': ['arm64']
@@ -65,11 +65,11 @@
'file_name':
'traceconv',
'file_size':
- 8515568,
+ 8633416,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
'sha256':
- 'bff2c0c525193fc3555725b5767a30bc2b36d7d84b849b7ce41f7abb349f21be',
+ 'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
'platform':
'linux',
'machine': ['x86_64']
@@ -79,11 +79,11 @@
'file_name':
'traceconv',
'file_size':
- 6421928,
+ 6505268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
'sha256':
- '566298db777a31f3eabd0e6b17e1b0638875725be767333308f02a7fb7d4e36c',
+ '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
'file_name':
'traceconv',
'file_size':
- 8001776,
+ 8108432,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
'sha256':
- 'ccf81438df9ed30f88e640f62938b6334698b9158364db43252d7183136e137d',
+ '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
'platform':
'linux',
'machine': ['aarch64']
@@ -107,55 +107,55 @@
'file_name':
'traceconv',
'file_size':
- 6021600,
+ 6096120,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
'sha256':
- 'edae3606563616fa304e3240a8426db3dfd7d12ad28bb879e7d482af357af7f2'
+ 'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 7305880,
+ 7401672,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
'sha256':
- '1a0ef7601a6d2f77f9a04d5bb8c092f5a536764a8e9a99325fff6b81c2879d1e'
+ '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 8118692,
+ 8238268,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
'sha256':
- '6f9b53427d367e8582b12763d27e83217c9e744f947f58acc0b404b6ce5103e9'
+ '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 8288976,
+ 8397056,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
'sha256':
- '5c843ddb151f5aa32288814cb190a879f15e6da63d642d3ded0f15954f480b5f'
+ 'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 7756800,
+ 7867904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v37.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
'sha256':
- 'b07849cd60872279dda68d4b8ec173af01e0cbb1692eb5473ef230ac89198f2d',
+ '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
'platform':
'win32',
'machine': ['amd64']
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index acb6a47..0999b31 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -16,6 +16,7 @@
import {assertExists, assertTrue, assertUnreachable} from '../base/logging';
import {duration, time} from '../base/time';
+import {exists} from '../base/utils';
import {RecordConfig} from '../controller/record_config_types';
import {
GenericSliceDetailsTabConfig,
@@ -29,7 +30,7 @@
tableColumnEquals,
toggleEnabled,
} from '../frontend/pivot_table_types';
-import {TrackTags} from '../public/index';
+import {PrimaryTrackSortKey, TrackTags} from '../public/index';
import {DebugTrackV2Config} from '../tracks/debug/slice_track';
import {randomColor} from './colorizer';
@@ -47,6 +48,7 @@
traceEventEnd,
TraceEventScope,
} from './metatracing';
+import {pluginManager} from './plugins';
import {
AdbRecordingTarget,
Area,
@@ -61,7 +63,6 @@
Pagination,
PendingDeeplinkState,
PivotTableResult,
- PrimaryTrackSortKey,
ProfileType,
RecordingTarget,
SCROLLING_TRACK_GROUP,
@@ -225,16 +226,25 @@
state.uiTrackIdByTraceTrackId[trackId] = uiTrackId;
};
- const config = trackState.config as {trackId: number};
- if (config.trackId !== undefined) {
- setUiTrackId(config.trackId, uiTrackId);
- return;
- }
-
- const multiple = trackState.config as {trackIds: number[]};
- if (multiple.trackIds !== undefined) {
- for (const trackId of multiple.trackIds) {
+ const {uri, config} = trackState;
+ if (exists(uri)) {
+ // If track is a new "plugin" type track (i.e. it has a uri), resolve the
+ // track ids from through the pluginManager.
+ const trackInfo = pluginManager.resolveTrackInfo(uri);
+ if (trackInfo?.trackIds) {
+ for (const trackId of trackInfo.trackIds) {
+ setUiTrackId(trackId, uiTrackId);
+ }
+ }
+ } else {
+ // Traditional track - resolve track ids through the config.
+ const {trackId, trackIds} = config;
+ if (exists(trackId)) {
setUiTrackId(trackId, uiTrackId);
+ } else if (exists(trackIds)) {
+ for (const trackId of trackIds) {
+ setUiTrackId(trackId, uiTrackId);
+ }
}
}
},
@@ -413,11 +423,6 @@
state.visibleTracks = args.tracks;
},
- updateTrackConfig(state: StateDraft, args: {id: string, config: {}}) {
- if (state.tracks[args.id] === undefined) return;
- state.tracks[args.id].config = args.config;
- },
-
moveTrack(
state: StateDraft,
args: {srcId: string; op: 'before' | 'after', dstId: string}): void {
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 261dd2e..8713c17 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -16,18 +16,18 @@
import {assertExists} from '../base/logging';
import {Time} from '../base/time';
+import {PrimaryTrackSortKey} from '../public';
import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile';
import {
PROCESS_SCHEDULING_TRACK_KIND,
-} from '../tracks/process_scheduling';
+} from '../tracks/process_summary/process_scheduling_track';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
import {StateActions} from './actions';
import {createEmptyState} from './empty_state';
import {
InThreadTrackSortKey,
- PrimaryTrackSortKey,
ProfileType,
SCROLLING_TRACK_GROUP,
State,
diff --git a/ui/src/common/basic_async_track.ts b/ui/src/common/basic_async_track.ts
index 24e3f28..2faf829 100644
--- a/ui/src/common/basic_async_track.ts
+++ b/ui/src/common/basic_async_track.ts
@@ -20,7 +20,7 @@
import {PxSpan, TimeScale} from '../frontend/time_scale';
import {SliceRect} from '../frontend/track';
import {TrackButtonAttrs} from '../frontend/track_panel';
-import {TrackLike} from '../public';
+import {Track} from '../public';
import {TrackData} from './track_data';
@@ -40,7 +40,7 @@
// This provides the logic to perform data reloads at appropriate times as the
// window is panned and zoomed about.
// The extending class need only define renderCanvas() and onBoundsChange().
-export abstract class BasicAsyncTrack<Data> implements TrackLike {
+export abstract class BasicAsyncTrack<Data> implements Track {
private requestingData = false;
private queuedRequest = false;
private currentState?: TrackData;
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 43c5f16..c442e73 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -499,6 +499,13 @@
return this.engine.getCpus();
}
+ async getNumberOfGpus(): Promise<number> {
+ if (!this.isAlive) {
+ return Promise.reject(new Error(`EngineProxy ${this.tag} was disposed.`));
+ }
+ return this.engine.getNumberOfGpus();
+ }
+
get engineId(): string {
return this.engine.id;
}
diff --git a/ui/src/common/metatracing.ts b/ui/src/common/metatracing.ts
index 09d080f..6d070a1 100644
--- a/ui/src/common/metatracing.ts
+++ b/ui/src/common/metatracing.ts
@@ -49,7 +49,7 @@
function getInitialCategories(): MetatraceCategories|undefined {
if (!AOMT_FLAG.get()) return undefined;
if (AOMT_DETAILED_FLAG.get()) return MetatraceCategories.ALL;
- return MetatraceCategories.TOPLEVEL;
+ return MetatraceCategories.QUERY_TIMELINE | MetatraceCategories.API_TIMELINE;
}
let enabledCategories: MetatraceCategories|undefined = getInitialCategories();
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index 9961921..13a4e10 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -30,14 +30,14 @@
Plugin,
PluginClass,
PluginContext,
- PluginInfo,
- PluginTrackInfo,
+ PluginContextTrace,
+ PluginDescriptor,
StatefulPlugin,
Store,
- TracePluginContext,
+ Track,
TrackContext,
- TrackInfo,
- TrackLike,
+ TrackDescriptor,
+ TrackInstanceDescriptor,
Viewer,
} from '../public';
@@ -73,13 +73,13 @@
});
}
- registerTrackController(track: TrackControllerFactory): void {
+ LEGACY_registerTrackController(track: TrackControllerFactory): void {
if (!this.alive) return;
const unregister = trackControllerRegistry.register(track);
this.trash.add(unregister);
}
- registerTrack(track: TrackCreator): void {
+ LEGACY_registerTrack(track: TrackCreator): void {
if (!this.alive) return;
const unregister = trackRegistry.register(track);
this.trash.add(unregister);
@@ -95,29 +95,30 @@
// related resources, such as the engine and the store.
// The TracePluginContext exists for the whole duration a plugin is active AND a
// trace is loaded.
-class TracePluginContextImpl<T> implements TracePluginContext<T>, Disposable {
+class TracePluginContextImpl<T> implements PluginContextTrace<T>, Disposable {
private trash = new Trash();
private alive = true;
constructor(
private ctx: PluginContext, readonly store: Store<T>,
readonly engine: EngineProxy,
- private trackRegistry: Map<string, PluginTrackInfo>,
+ readonly trackRegistry: Map<string, TrackDescriptor>,
+ private suggestedTracks: Set<TrackInstanceDescriptor>,
private commandRegistry: Map<string, Command>) {
this.trash.add(engine);
this.trash.add(store);
}
- registerTrackController(track: TrackControllerFactory): void {
+ LEGACY_registerTrackController(track: TrackControllerFactory): void {
// Silently ignore if context is dead.
if (!this.alive) return;
- this.ctx.registerTrackController(track);
+ this.ctx.LEGACY_registerTrackController(track);
}
- registerTrack(track: TrackCreator): void {
+ LEGACY_registerTrack(track: TrackCreator): void {
// Silently ignore if context is dead.
if (!this.alive) return;
- this.ctx.registerTrack(track);
+ this.ctx.LEGACY_registerTrack(track);
}
addCommand(cmd: Command): void {
@@ -142,16 +143,22 @@
// Register a new track in this context.
// All tracks registered through this method are removed when this context is
// destroyed, i.e. when the trace is unloaded.
- addTrack(trackDetails: PluginTrackInfo): void {
+ addTrack(trackDetails: TrackDescriptor): void {
// Silently ignore if context is dead.
if (!this.alive) return;
const {uri} = trackDetails;
this.trackRegistry.set(uri, trackDetails);
- this.trash.add({
- dispose: () => {
- this.trackRegistry.delete(uri);
- },
- });
+ this.trash.addCallback(() => this.trackRegistry.delete(uri));
+ }
+
+ // Ask Perfetto to add a track to the track list when a fresh trace is loaded.
+ // Ignored when a trace is loaded from a permalink.
+ // This is a direct replacement for findPotentialTracks().
+ // Note: This interface is likely to be deprecated soon, but is required while
+ // both plugin and original type tracks coexist.
+ suggestTrack(trackInfo: TrackInstanceDescriptor): void {
+ this.suggestedTracks.add(trackInfo);
+ this.trash.addCallback(() => this.suggestedTracks.delete(trackInfo));
}
dispose(): void {
@@ -161,7 +168,7 @@
}
// 'Static' registry of all known plugins.
-export class PluginRegistry extends Registry<PluginInfo<unknown>> {
+export class PluginRegistry extends Registry<PluginDescriptor<unknown>> {
constructor() {
super((info) => info.pluginId);
}
@@ -170,26 +177,24 @@
interface PluginDetails<T> {
plugin: Plugin<T>;
context: PluginContext&Disposable;
- traceContext?: TracePluginContext<T>&Disposable;
+ traceContext?: TracePluginContextImpl<unknown>;
}
function isPluginClass<T>(v: unknown): v is PluginClass<T> {
return typeof v === 'function' && !!(v.prototype.onActivate);
}
-function makePlugin<T>(info: PluginInfo<T>): Plugin<T> {
- const {plugin: pluginFactory} = info;
+function makePlugin<T>(info: PluginDescriptor<T>): Plugin<T> {
+ const {plugin} = info;
- if (typeof pluginFactory === 'function') {
- if (isPluginClass(pluginFactory)) {
- const PluginClass = pluginFactory;
+ if (typeof plugin === 'function') {
+ if (isPluginClass(plugin)) {
+ const PluginClass = plugin;
return new PluginClass();
} else {
- return pluginFactory();
+ return plugin();
}
} else {
- // pluginFactory is the plugin!
- const plugin = pluginFactory;
return plugin;
}
}
@@ -198,8 +203,9 @@
private registry: PluginRegistry;
private plugins: Map<string, PluginDetails<unknown>>;
private engine?: Engine;
- readonly trackRegistry = new Map<string, PluginTrackInfo>();
+ readonly trackRegistry = new Map<string, TrackDescriptor>();
readonly commandRegistry = new Map<string, Command>();
+ readonly suggestedTracks = new Set<TrackInstanceDescriptor>();
constructor(registry: PluginRegistry) {
this.registry = registry;
@@ -257,15 +263,8 @@
return this.plugins.get(pluginId);
}
- findPotentialTracks(): Promise<TrackInfo[]>[] {
- const promises: Promise<TrackInfo[]>[] = [];
- for (const {plugin, traceContext} of this.plugins.values()) {
- if (plugin.findPotentialTracks && traceContext) {
- const promise = plugin.findPotentialTracks(traceContext);
- promises.push(promise);
- }
- }
- return promises;
+ findPotentialTracks(): TrackInstanceDescriptor[] {
+ return Array.from(this.suggestedTracks);
}
onTraceLoad(engine: Engine): void {
@@ -299,16 +298,15 @@
// Look up track into for a given track's URI.
// Returns |undefined| if no track can be found.
- resolveTrackInfo(uri: string): PluginTrackInfo|undefined {
+ resolveTrackInfo(uri: string): TrackDescriptor|undefined {
return this.trackRegistry.get(uri);
}
// Create a new plugin track object from its URI.
// Returns undefined if no such track is registered.
- createTrack(uri: string, trackInstanceId: string): TrackLike|undefined {
+ createTrack(uri: string, trackCtx: TrackContext): Track|undefined {
const trackInfo = pluginManager.trackRegistry.get(uri);
- const trackContext: TrackContext = {trackInstanceId};
- return trackInfo && trackInfo.trackFactory(trackContext);
+ return trackInfo && trackInfo.track(trackCtx);
}
private doPluginTraceLoad<T>(
@@ -331,6 +329,7 @@
proxyStore,
engineProxy,
this.trackRegistry,
+ this.suggestedTracks,
this.commandRegistry);
pluginDetails.traceContext = traceCtx;
@@ -347,6 +346,7 @@
proxyStore,
engineProxy,
this.trackRegistry,
+ this.suggestedTracks,
this.commandRegistry);
pluginDetails.traceContext = traceCtx;
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 0d31e01..1a8c68e 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -23,7 +23,7 @@
PivotTree,
TableColumn,
} from '../frontend/pivot_table_types';
-import {TrackTags} from '../public/index';
+import {PrimaryTrackSortKey, TrackTags} from '../public/index';
import {Direction} from './event_set';
@@ -122,7 +122,8 @@
// 39. Ported cpu_slice, ftrace, and android_log tracks to plugin tracks. Track
// state entries now require a URI and old track implementations are no
// longer registered.
-export const STATE_VERSION = 39;
+// 40. Ported counter, process summary/sched, & cpu_freq to plugin tracks.
+export const STATE_VERSION = 40;
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
@@ -130,36 +131,6 @@
export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM';
-// Tracks within track groups (usually corresponding to processes) are sorted.
-// As we want to group all tracks related to a given thread together, we use
-// two keys:
-// - Primary key corresponds to a priority of a track block (all tracks related
-// to a given thread or a single track if it's not thread-associated).
-// - Secondary key corresponds to a priority of a given thread-associated track
-// within its thread track block.
-// Each track will have a sort key, which either a primary sort key
-// (for non-thread tracks) or a tid and secondary sort key (mapping of tid to
-// primary sort key is done independently).
-export enum PrimaryTrackSortKey {
- DEBUG_SLICE_TRACK,
- NULL_TRACK,
- PROCESS_SCHEDULING_TRACK,
- PROCESS_SUMMARY_TRACK,
- EXPECTED_FRAMES_SLICE_TRACK,
- ACTUAL_FRAMES_SLICE_TRACK,
- PERF_SAMPLES_PROFILE_TRACK,
- HEAP_PROFILE_TRACK,
- MAIN_THREAD,
- RENDER_THREAD,
- GPU_COMPLETION_THREAD,
- CHROME_IO_THREAD,
- CHROME_COMPOSITOR_THREAD,
- ORDINARY_THREAD,
- COUNTER_TRACK,
- ASYNC_SLICE_TRACK,
- ORDINARY_TRACK,
-}
-
// Key that is used to sort tracks within a block of tracks associated with a
// given thread.
export enum InThreadTrackSortKey {
@@ -260,6 +231,7 @@
trackIds?: number[];
};
uri?: string;
+ state?: unknown;
}
export interface TrackGroupState {
@@ -268,6 +240,7 @@
name: string;
collapsed: boolean;
tracks: string[]; // Child track ids.
+ state?: unknown;
}
export interface EngineConfig {
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index 3a34afa..34646ba 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {PrimaryTrackSortKey} from '../public';
+
import {createEmptyState} from './empty_state';
-import {getContainingTrackId, PrimaryTrackSortKey, State} from './state';
+import {getContainingTrackId, State} from './state';
import {deserializeStateObject, serializeStateObject} from './upload_utils';
test('createEmptyState', () => {
diff --git a/ui/src/common/track_adapter.ts b/ui/src/common/track_adapter.ts
index 9bb57a4..1c145d3 100644
--- a/ui/src/common/track_adapter.ts
+++ b/ui/src/common/track_adapter.ts
@@ -41,6 +41,7 @@
BasicAsyncTrack<Data> {
private track: TrackAdapter<Config, Data>;
private controller: TrackControllerAdapter<Config, Data>;
+ private isSetup = false;
constructor(
engine: EngineProxy, trackInstanceId: string, config: Config,
@@ -57,11 +58,6 @@
this.controller = new Controller(config, engine);
}
- onCreate(): void {
- this.controller.onSetup();
- super.onCreate();
- }
-
onDestroy(): void {
this.track.onDestroy();
this.controller.onDestroy();
@@ -104,8 +100,13 @@
this.track.onFullRedraw();
}
- onBoundsChange(start: time, end: time, resolution: duration): Promise<Data> {
- return this.controller.onBoundsChange(start, end, resolution);
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<Data> {
+ if (!this.isSetup) {
+ await this.controller.onSetup();
+ this.isSetup = true;
+ }
+ return await this.controller.onBoundsChange(start, end, resolution);
}
renderCanvas(ctx: CanvasRenderingContext2D): void {
@@ -181,6 +182,12 @@
new (args: NewTrackArgs): TrackAdapter<Config, Data>
}
+function hasNamespace(config: unknown): config is {
+ namespace: string
+} {
+ return !!config && typeof config === 'object' && 'namespace' in config;
+}
+
// Extend from this class instead of `TrackController` to use existing track
// controller implementations with `TrackWithControllerAdapter`.
export abstract class TrackControllerAdapter<Config, Data> {
@@ -189,7 +196,7 @@
// don't have access to it.
private uuid = uuidv4();
- constructor(protected config: Config, private engine: EngineProxy) {}
+ constructor(protected config: Config, protected engine: EngineProxy) {}
protected async query(query: string) {
const result = await this.engine.query(query);
@@ -199,8 +206,8 @@
abstract onBoundsChange(start: time, end: time, resolution: duration):
Promise<Data>;
- onSetup(): void {}
- onDestroy(): void {}
+ async onSetup(): Promise<void> {}
+ async onDestroy(): Promise<void> {}
// Returns a valid SQL table name with the given prefix that should be unique
// for each track.
@@ -210,6 +217,14 @@
const idSuffix = this.uuid.split('-').join('_');
return `${prefix}_${idSuffix}`;
}
+
+ namespaceTable(tableName: string): string {
+ if (hasNamespace(this.config)) {
+ return this.config.namespace + '_' + tableName;
+ } else {
+ return tableName;
+ }
+ }
}
type TrackControllerAdapterClass<Config, Data> = {
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index 3c3cf15..ae3e1d7 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -15,9 +15,10 @@
import {Duration} from '../../base/time';
import {ColumnDef} from '../../common/aggregation_data';
import {Engine} from '../../common/engine';
+import {pluginManager} from '../../common/plugins';
import {Area, Sorting} from '../../common/state';
import {globals} from '../../frontend/globals';
-import {Config, COUNTER_TRACK_KIND} from '../../tracks/counter';
+import {COUNTER_TRACK_KIND} from '../../tracks/counter';
import {AggregationController} from './aggregation_controller';
@@ -25,19 +26,17 @@
async createAggregateView(engine: Engine, area: Area) {
await engine.query(`drop view if exists ${this.kind};`);
- const ids = [];
+ const trackIds: (string|number)[] = [];
for (const trackId of area.tracks) {
const track = globals.state.tracks[trackId];
- // Track will be undefined for track groups.
- if (track !== undefined && track.kind === COUNTER_TRACK_KIND) {
- const config = track.config as Config;
- // TODO(hjd): Also aggregate annotation (with namespace) counters.
- if (config.namespace === undefined) {
- ids.push(config.trackId);
+ if (track?.uri) {
+ const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+ if (trackInfo?.kind === COUNTER_TRACK_KIND) {
+ trackInfo.trackIds && trackIds.push(...trackInfo.trackIds);
}
}
}
- if (ids.length === 0) return false;
+ if (trackIds.length === 0) return false;
const duration = area.end - area.start;
const durationSec = Duration.toSeconds(duration);
@@ -61,7 +60,7 @@
(partition by track_id order by ts
range between unbounded preceding and unbounded following) as last
from experimental_counter_dur
- where track_id in (${ids})
+ where track_id in (${trackIds})
and ts + dur >= ${area.start} and
ts <= ${area.end})
join counter_track
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index 46f0f47..db176d9 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -31,9 +31,8 @@
const track = globals.state.tracks[trackId];
if (track?.uri) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- const cpu = trackInfo?.tags?.cpu;
- cpu && selectedCpus.push(cpu);
+ if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
+ trackInfo.cpu && selectedCpus.push(trackInfo.cpu);
}
}
}
diff --git a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
index aebaf94..dbdf07c 100644
--- a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
@@ -30,9 +30,8 @@
const track = globals.state.tracks[trackId];
if (track?.uri) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- const cpu = trackInfo?.tags?.cpu;
- cpu && selectedCpus.push(cpu);
+ if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
+ trackInfo.cpu && selectedCpus.push(trackInfo.cpu);
}
}
}
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 06f9da9..a1c4170 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -15,6 +15,7 @@
import {Time} from '../base/time';
import {Engine} from '../common/engine';
import {featureFlags} from '../common/feature_flags';
+import {pluginManager} from '../common/plugins';
import {LONG, NUM, STR_NULL} from '../common/query_result';
import {Area} from '../common/state';
import {Flow, globals} from '../frontend/globals';
@@ -243,6 +244,17 @@
return null;
}
+ // Perform the same check for "plugin" style tracks.
+ if (track.uri) {
+ const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+ const trackIds = trackInfo?.trackIds;
+ if (trackIds === undefined || trackIds.length <= 1) {
+ uiTrackIdToInfo.set(uiTrackId, null);
+ trackIdToInfo.set(trackId, null);
+ return null;
+ }
+ }
+
const newInfo = {
uiTrackId,
siblingTrackIds: trackIds,
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index b8ee0d3..6055221 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -204,8 +204,8 @@
for (const track of Object.values(globals.state.tracks)) {
if (exists(track?.uri)) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- const cpu = trackInfo?.tags?.cpu;
+ if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
+ const cpu = trackInfo?.cpu;
cpu && cpuToTrackId.set(cpu, track.id);
}
}
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 027398a..31000d6 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -470,8 +470,6 @@
}
}
- pluginManager.onTraceLoad(engine);
-
const emptyOmniboxState = {
omnibox: '',
mode: globals.state.omniboxState.mode || 'SEARCH',
@@ -501,6 +499,8 @@
// Make sure the helper views are available before we start adding tracks.
await this.initialiseHelperViews();
+ pluginManager.onTraceLoad(engine);
+
{
// When we reload from a permalink don't create extra tracks:
const {pinnedTracks, tracks} = globals.state;
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 04bac66..b4a8228 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -25,7 +25,6 @@
import {featureFlags, PERF_SAMPLE_FLAG} from '../common/feature_flags';
import {pluginManager} from '../common/plugins';
import {
- LONG_NULL,
NUM,
NUM_NULL,
STR,
@@ -33,11 +32,12 @@
} from '../common/query_result';
import {
InThreadTrackSortKey,
- PrimaryTrackSortKey,
SCROLLING_TRACK_GROUP,
TrackSortKey,
UtidToTrackSortKey,
} from '../common/state';
+import {PrimaryTrackSortKey} from '../public';
+import {getTrackName} from '../public/utils';
import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices';
import {
@@ -49,8 +49,7 @@
decideTracks as scrollJankDecideTracks,
} from '../tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track';
import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
-import {COUNTER_TRACK_KIND, CounterScaleOptions} from '../tracks/counter';
-import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq';
+import {COUNTER_TRACK_KIND} from '../tracks/counter';
import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile';
import {
EXPECTED_FRAMES_SLICE_TRACK_KIND,
@@ -61,10 +60,6 @@
PERF_SAMPLES_PROFILE_TRACK_KIND,
} from '../tracks/perf_samples_profile';
import {
- PROCESS_SCHEDULING_TRACK_KIND,
-} from '../tracks/process_scheduling';
-import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary';
-import {
decideTracks as screenshotDecideTracks,
} from '../tracks/screenshots';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
@@ -130,29 +125,6 @@
const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
const MISC_GROUP = 'Misc Global Tracks';
-// Sets the default 'scale' for counter tracks. If the regex matches
-// then the paired mode is used. Entries are in priority order so the
-// first match wins.
-const COUNTER_REGEX: [RegExp, CounterScaleOptions][] = [
- // Power counters make more sense in rate mode since you're typically
- // interested in the slope of the graph rather than the absolute
- // value.
- [new RegExp('^power\..*$'), 'RATE'],
- // Same for network counters.
- [NETWORK_TRACK_REGEX, 'RATE'],
- // Entity residency
- [ENTITY_RESIDENCY_REGEX, 'RATE'],
-];
-
-function getCounterScale(name: string): CounterScaleOptions|undefined {
- for (const [re, scale] of COUNTER_REGEX) {
- if (name.match(re)) {
- return scale;
- }
- }
- return undefined;
-}
-
export async function decideTracks(
engineId: string, engine: Engine): Promise<DeferredAction[]> {
return (new TrackDecider(engineId, engine)).decideTracks();
@@ -171,66 +143,6 @@
this.engine = engine;
}
- static getTrackName(args: Partial<{
- name: string | null,
- utid: number,
- processName: string|null,
- pid: number|null,
- threadName: string|null,
- tid: number|null,
- upid: number|null,
- kind: string,
- threadTrack: boolean
- }>) {
- const {
- name,
- upid,
- utid,
- processName,
- threadName,
- pid,
- tid,
- kind,
- threadTrack,
- } = args;
-
- const hasName = name !== undefined && name !== null && name !== '[NULL]';
- const hasUpid = upid !== undefined && upid !== null;
- const hasUtid = utid !== undefined && utid !== null;
- const hasProcessName = processName !== undefined && processName !== null;
- const hasThreadName = threadName !== undefined && threadName !== null;
- const hasTid = tid !== undefined && tid !== null;
- const hasPid = pid !== undefined && pid !== null;
- const hasKind = kind !== undefined;
- const isThreadTrack = threadTrack !== undefined && threadTrack;
-
- // If we don't have any useful information (better than
- // upid/utid) we show the track kind to help with tracking
- // down where this is coming from.
- const kindSuffix = hasKind ? ` (${kind})` : '';
-
- if (isThreadTrack && hasName && hasTid) {
- return `${name} (${tid})`;
- } else if (hasName) {
- return `${name}`;
- } else if (hasUpid && hasPid && hasProcessName) {
- return `${processName} ${pid}`;
- } else if (hasUpid && hasPid) {
- return `Process ${pid}`;
- } else if (hasThreadName && hasTid) {
- return `${threadName} ${tid}`;
- } else if (hasTid) {
- return `Thread ${tid}`;
- } else if (hasUpid) {
- return `upid: ${upid}${kindSuffix}`;
- } else if (hasUtid) {
- return `utid: ${utid}${kindSuffix}`;
- } else if (hasKind) {
- return `Unnamed ${kind}`;
- }
- return 'Unknown';
- }
-
async guessCpuSizes(): Promise<Map<number, string>> {
const cpuToSize = new Map<number, string>();
await this.engine.query(`
@@ -289,54 +201,35 @@
async addCpuFreqTracks(engine: EngineProxy): Promise<void> {
const cpus = await this.engine.getCpus();
- const maxCpuFreqResult = await engine.query(`
- select ifnull(max(value), 0) as freq
- from counter c
- inner join cpu_counter_track t on c.track_id = t.id
- where name = 'cpufreq';
- `);
- const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq;
-
for (const cpu of cpus) {
// Only add a cpu freq track if we have
// cpu freq data.
// TODO(hjd): Find a way to display cpu idle
// events even if there are no cpu freq events.
const cpuFreqIdleResult = await engine.query(`
- select
- id as cpuFreqId,
- (
- select id
- from cpu_counter_track
- where name = 'cpuidle'
- and cpu = ${cpu}
- limit 1
- ) as cpuIdleId
- from cpu_counter_track
- where name = 'cpufreq' and cpu = ${cpu}
- limit 1;
- `);
+ select
+ id as cpuFreqId,
+ (
+ select id
+ from cpu_counter_track
+ where name = 'cpuidle'
+ and cpu = ${cpu}
+ limit 1
+ ) as cpuIdleId
+ from cpu_counter_track
+ where name = 'cpufreq' and cpu = ${cpu}
+ limit 1;
+ `);
if (cpuFreqIdleResult.numRows() > 0) {
- const row = cpuFreqIdleResult.firstRow({
- cpuFreqId: NUM,
- cpuIdleId: NUM_NULL,
- });
- const freqTrackId = row.cpuFreqId;
- const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId;
-
this.tracksToAdd.push({
engineId: this.engineId,
- kind: CPU_FREQ_TRACK_KIND,
+ kind: PLUGIN_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
name: `Cpu ${cpu} Frequency`,
trackGroup: SCROLLING_TRACK_GROUP,
- config: {
- cpu,
- maximumValue: maxCpuFreq,
- freqTrackId,
- idleTrackId,
- },
+ config: {},
+ uri: `perfetto.CpuFreq#${cpu}`,
});
}
}
@@ -394,7 +287,7 @@
const kind = ASYNC_SLICE_TRACK_KIND;
const rawName = it.name === null ? undefined : it.name;
const rawParentName = it.parentName === null ? undefined : it.parentName;
- const name = TrackDecider.getTrackName({name: rawName, kind});
+ const name = getTrackName({name: rawName, kind});
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
const parentTrackId = it.parentId;
@@ -412,8 +305,7 @@
trackGroup = uuidv4();
parentIdToGroupId.set(parentTrackId, trackGroup);
- const parentName =
- TrackDecider.getTrackName({name: rawParentName, kind});
+ const parentName = getTrackName({name: rawParentName, kind});
const summaryTrackId = uuidv4();
this.tracksToAdd.push({
@@ -463,36 +355,24 @@
async addGpuFreqTracks(engine: EngineProxy): Promise<void> {
const numGpus = await this.engine.getNumberOfGpus();
- const maxGpuFreqResult = await engine.query(`
- select ifnull(max(value), 0) as maximumValue
- from counter c
- inner join gpu_counter_track t on c.track_id = t.id
- where name = 'gpufreq';
- `);
- const maximumValue =
- maxGpuFreqResult.firstRow({maximumValue: NUM}).maximumValue;
-
for (let gpu = 0; gpu < numGpus; gpu++) {
// Only add a gpu freq track if we have
// gpu freq data.
const freqExistsResult = await engine.query(`
- select id
+ select *
from gpu_counter_track
where name = 'gpufreq' and gpu_id = ${gpu}
limit 1;
`);
if (freqExistsResult.numRows() > 0) {
- const trackId = freqExistsResult.firstRow({id: NUM}).id;
this.tracksToAdd.push({
engineId: this.engineId,
- kind: COUNTER_TRACK_KIND,
+ kind: PLUGIN_TRACK_KIND,
name: `Gpu ${gpu} Frequency`,
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
- config: {
- trackId,
- maximumValue,
- },
+ config: {},
+ uri: `perfetto.Counter#gpu_freq${gpu}`,
});
}
}
@@ -537,15 +417,12 @@
const trackId = it.id;
this.tracksToAdd.push({
engineId: this.engineId,
- kind: COUNTER_TRACK_KIND,
+ kind: PLUGIN_TRACK_KIND,
name,
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
- config: {
- name,
- trackId,
- scale: getCounterScale(name),
- },
+ config: {},
+ uri: `perfetto.Counter#cpu${trackId}`,
});
}
}
@@ -866,17 +743,6 @@
}
}
- applyDefaultCounterScale(): void {
- for (const track of this.tracksToAdd) {
- if (track.kind === COUNTER_TRACK_KIND) {
- const scaleConfig = {
- scale: getCounterScale(track.name),
- };
- track.config = Object.assign({}, track.config, scaleConfig);
- }
- }
- }
-
async addLogsTrack(engine: EngineProxy): Promise<void> {
const result =
await engine.query(`select count(1) as cnt from android_logs`);
@@ -1017,44 +883,30 @@
}
const counterResult = await engine.query(`
- SELECT
- id,
- name,
- upid,
- min_value as minValue,
- max_value as maxValue
- FROM annotation_counter_track`);
+ SELECT id, name, upid FROM annotation_counter_track
+ `);
const counterIt = counterResult.iter({
id: NUM,
name: STR,
upid: NUM,
- minValue: NUM_NULL,
- maxValue: NUM_NULL,
});
for (; counterIt.valid(); counterIt.next()) {
const id = counterIt.id;
const name = counterIt.name;
const upid = counterIt.upid;
- const minimumValue =
- counterIt.minValue === null ? undefined : counterIt.minValue;
- const maximumValue =
- counterIt.maxValue === null ? undefined : counterIt.maxValue;
this.tracksToAdd.push({
engineId: this.engineId,
- kind: 'CounterTrack',
+ kind: PLUGIN_TRACK_KIND,
name,
trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP :
this.upidToUuid.get(upid),
config: {
- name,
namespace: 'annotation',
- trackId: id,
- minimumValue,
- maximumValue,
},
+ uri: `perfetto.Annotation#counter${id}`,
});
}
}
@@ -1101,7 +953,7 @@
this.tracksToAdd.push({
engineId: this.engineId,
kind: THREAD_STATE_TRACK_KIND,
- name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
+ name: getTrackName({utid, tid, threadName, kind}),
trackGroup: uuid,
trackSortKey: {
utid,
@@ -1116,7 +968,7 @@
this.tracksToAdd.push({
engineId: this.engineId,
kind,
- name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
+ name: getTrackName({utid, tid, threadName, kind}),
trackGroup: uuid,
trackSortKey: {
utid,
@@ -1177,9 +1029,7 @@
upid,
tid,
thread.name as threadName,
- thread_counter_track.id as trackId,
- thread.start_ts as startTs,
- thread.end_ts as endTs
+ thread_counter_track.id as trackId
from thread_counter_track
join thread using(utid)
left join process using(upid)
@@ -1192,9 +1042,7 @@
upid: NUM_NULL,
tid: NUM_NULL,
threadName: STR_NULL,
- startTs: LONG_NULL,
trackId: NUM,
- endTs: LONG_NULL,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
@@ -1204,27 +1052,25 @@
const trackName = it.trackName;
const threadName = it.threadName;
const uuid = this.getUuid(utid, upid);
- const startTs = it.startTs === null ? undefined : it.startTs;
- const endTs = it.endTs === null ? undefined : it.endTs;
- const kind = COUNTER_TRACK_KIND;
- const name = TrackDecider.getTrackName(
- {name: trackName, utid, tid, kind, threadName, threadTrack: true});
+ const name = getTrackName({
+ name: trackName,
+ utid,
+ tid,
+ kind: COUNTER_TRACK_KIND,
+ threadName,
+ threadTrack: true,
+ });
this.tracksToAdd.push({
engineId: this.engineId,
- kind,
+ kind: PLUGIN_TRACK_KIND,
name,
trackSortKey: {
utid,
priority: InThreadTrackSortKey.ORDINARY,
},
trackGroup: uuid,
- config: {
- name,
- trackId,
- startTs,
- endTs,
- tid,
- },
+ config: {},
+ uri: `perfetto.Counter#thread${trackId}`,
});
}
}
@@ -1279,8 +1125,8 @@
const uuid = this.getUuid(0, upid);
const kind = ASYNC_SLICE_TRACK_KIND;
- const name = TrackDecider.getTrackName(
- {name: trackName, upid, pid, processName, kind});
+ const name =
+ getTrackName({name: trackName, upid, pid, processName, kind});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
@@ -1343,8 +1189,8 @@
const uuid = this.getUuid(0, upid);
const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
- const name = TrackDecider.getTrackName(
- {name: trackName, upid, pid, processName, kind});
+ const name =
+ getTrackName({name: trackName, upid, pid, processName, kind});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
@@ -1408,8 +1254,8 @@
const uuid = this.getUuid(0, upid);
const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
- const name = TrackDecider.getTrackName(
- {name: trackName, upid, pid, processName, kind});
+ const name =
+ getTrackName({name: trackName, upid, pid, processName, kind});
this.tracksToAdd.push({
engineId: this.engineId,
kind,
@@ -1467,8 +1313,7 @@
const uuid = this.getUuid(utid, upid);
const kind = SLICE_TRACK_KIND;
- const name = TrackDecider.getTrackName(
- {name: trackName, utid, tid, threadName, kind});
+ const name = getTrackName({name: trackName, utid, tid, threadName, kind});
if (showV1()) {
this.tracksToAdd.push({
engineId: this.engineId,
@@ -1514,9 +1359,7 @@
process_counter_track.name as trackName,
upid,
process.pid,
- process.name as processName,
- process.start_ts as startTs,
- process.end_ts as endTs
+ process.name as processName
from process_counter_track
join process using(upid);
`);
@@ -1526,8 +1369,6 @@
upid: NUM,
pid: NUM_NULL,
processName: STR_NULL,
- startTs: LONG_NULL,
- endTs: LONG_NULL,
});
for (let i = 0; it.valid(); ++i, it.next()) {
const pid = it.pid;
@@ -1536,24 +1377,17 @@
const trackName = it.trackName;
const processName = it.processName;
const uuid = this.getUuid(0, upid);
- const startTs = it.startTs === null ? undefined : it.startTs;
- const endTs = it.endTs === null ? undefined : it.endTs;
- const kind = COUNTER_TRACK_KIND;
- const name = TrackDecider.getTrackName(
- {name: trackName, upid, pid, kind, processName});
+ const name = getTrackName(
+ {name: trackName, upid, pid, kind: COUNTER_TRACK_KIND, processName});
this.tracksToAdd.push({
engineId: this.engineId,
- kind,
+ kind: PLUGIN_TRACK_KIND,
name,
trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack(
upid, trackName || undefined),
trackGroup: uuid,
- config: {
- name,
- trackId,
- startTs,
- endTs,
- },
+ config: {},
+ uri: `perfetto.Counter#process${trackId}`,
});
}
}
@@ -1671,10 +1505,11 @@
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
- kind: PROCESS_SUMMARY_TRACK,
+ kind: PLUGIN_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
name: `Kernel thread summary`,
- config: {pidForColor: 2, upid: it.upid, utid: it.utid},
+ config: {},
+ uri: 'perfetto.ProcessSummary#kernel',
});
const addTrackGroup = Actions.addTrackGroup({
engineId: this.engineId,
@@ -1822,7 +1657,6 @@
processName: STR_NULL,
hasSched: NUM_NULL,
hasHeapProfiles: NUM_NULL,
- isDebuggable: NUM_NULL,
chromeProcessLabels: STR,
});
for (; it.valid(); it.next()) {
@@ -1834,7 +1668,6 @@
const processName = it.processName;
const hasSched = !!it.hasSched;
const hasHeapProfiles = !!it.hasHeapProfiles;
- const isDebuggable = !!it.isDebuggable;
// Group by upid if present else by utid.
let pUuid =
@@ -1843,31 +1676,24 @@
if (pUuid === undefined) {
pUuid = this.getOrCreateUuid(utid, upid);
const summaryTrackId = uuidv4();
-
- const pidForColor = pid || tid || upid || utid || 0;
- const kind =
- hasSched ? PROCESS_SCHEDULING_TRACK_KIND : PROCESS_SUMMARY_TRACK;
+ const type = hasSched ? 'schedule' : 'summary';
+ const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
this.tracksToAdd.push({
id: summaryTrackId,
engineId: this.engineId,
- kind,
+ kind: PLUGIN_TRACK_KIND,
trackSortKey: hasSched ?
PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK :
PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
name: `${upid === null ? tid : pid} summary`,
- config: {
- pidForColor,
- upid,
- utid,
- tid,
- isDebuggable: isDebuggable ?? undefined,
- },
+ config: {},
labels: it.chromeProcessLabels.split(','),
+ uri,
});
- const name = TrackDecider.getTrackName(
- {utid, processName, pid, threadName, tid, upid});
+ const name =
+ getTrackName({utid, processName, pid, threadName, tid, upid});
const addTrackGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
@@ -1934,22 +1760,20 @@
`);
}
- async addPluginTracks(): Promise<void> {
- const promises = pluginManager.findPotentialTracks();
- const groups = await Promise.all(promises);
- for (const infos of groups) {
- for (const info of infos) {
- this.tracksToAdd.push({
- engineId: this.engineId,
- kind: info.trackKind,
- name: info.name,
- // TODO(hjd): Fix how sorting works. Plugins should expose
- // 'sort keys' which the user can use to choose a sort order.
- trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- config: info.config,
- });
- }
+ addPluginTracks(): void {
+ const tracks = pluginManager.findPotentialTracks();
+ for (const info of tracks) {
+ this.tracksToAdd.push({
+ engineId: this.engineId,
+ kind: PLUGIN_TRACK_KIND,
+ name: info.name,
+ uri: info.uri,
+ // TODO(hjd): Fix how sorting works. Plugins should expose
+ // 'sort keys' which the user can use to choose a sort order.
+ trackSortKey: info.sortKey,
+ trackGroup: SCROLLING_TRACK_GROUP,
+ config: {},
+ });
}
}
@@ -1978,7 +1802,7 @@
this.engine.getProxy('TrackDecider::addCpuFreqLimitCounterTracks'));
await this.addCpuPerfCounterTracks(
this.engine.getProxy('TrackDecider::addCpuPerfCounterTracks'));
- await this.addPluginTracks();
+ this.addPluginTracks();
await this.addAnnotationTracks(
this.engine.getProxy('TrackDecider::addAnnotationTracks'));
await this.groupGlobalIonTracks();
@@ -2058,8 +1882,6 @@
this.addTrackGroupActions.push(
Actions.setUtidToTrackSortKey({threadOrderingMetadata}));
- this.applyDefaultCounterScale();
-
return this.addTrackGroupActions;
}
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 23401a0..955df02 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -43,6 +43,7 @@
import {fullscreenModalContainer} from './modal';
import {Omnibox, OmniboxOption} from './omnibox';
import {runQueryInNewTab} from './query_result_tab';
+import {verticalScrollToTrack} from './scroll_helper';
import {executeSearch} from './search_handler';
import {Sidebar} from './sidebar';
import {SqlTableTab} from './sql_table/tab';
@@ -307,8 +308,9 @@
},
},
{
- id: 'perfetto.PrintTrackInfoToConsole',
- name: 'Print track info to console',
+ // Selects & reveals the first track on the timeline with a given URI.
+ id: 'perfetto.FindTrack',
+ name: 'Find track by URI',
callback:
async () => {
const tracks = Array.from(pluginManager.trackRegistry.values());
@@ -326,9 +328,28 @@
});
try {
- const uri = await this.prompt('Choose a track...', sortedOptions);
- const trackDetails = pluginManager.resolveTrackInfo(uri);
- console.log(trackDetails);
+ const selectedUri =
+ await this.prompt('Choose a track...', sortedOptions);
+
+ // Find the first track with this URI
+ const firstTrack = Object.values(globals.state.tracks)
+ .find(({uri}) => uri === selectedUri);
+ if (firstTrack) {
+ console.log(firstTrack);
+ verticalScrollToTrack(firstTrack.id, true);
+ const traceTime = globals.stateTraceTimeTP();
+ globals.makeSelection(
+ Actions.selectArea({
+ area: {
+ start: traceTime.start,
+ end: traceTime.end,
+ tracks: [firstTrack.id],
+ },
+ }),
+ );
+ } else {
+ alert(`No tracks with uri ${selectedUri} on the timeline`);
+ }
} catch {
// Prompt was probably cancelled - do nothing.
}
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index f16057b..e95fb20 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -40,7 +40,7 @@
import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
import {constraintsToQuerySuffix} from './sql_utils';
import {PxSpan, TimeScale} from './time_scale';
-import {NewTrackArgs, SliceRect, Track} from './track';
+import {NewTrackArgs, SliceRect, TrackBase} from './track';
import {BUCKETS_PER_PIXEL, CacheKey, TrackCache} from './track_cache';
// The common class that underpins all tracks drawing slices.
@@ -179,7 +179,7 @@
export abstract class BaseSliceTrack<T extends BaseSliceTrackTypes =
BaseSliceTrackTypes> extends
- Track<T['config']> {
+ TrackBase<T['config']> {
protected sliceLayout: SliceLayout = {...DEFAULT_SLICE_LAYOUT};
// This is the over-skirted cached bounds:
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 4d49c1a..c414454 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {TrackState} from 'src/common/state';
+
import {time} from '../base/time';
+import {pluginManager} from '../common/plugins';
import {TRACK_SHELL_WIDTH} from './css_constants';
import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
@@ -67,6 +70,22 @@
return (obj as {trackGroupId?: string}).trackGroupId !== undefined;
}
+function getTrackIds(track: TrackState): number[] {
+ if (track.uri) {
+ const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+ if (trackInfo?.trackIds) return trackInfo?.trackIds;
+ } else {
+ const config = track.config;
+ if (hasTrackId(config)) {
+ return [config.trackId];
+ }
+ if (hasManyTrackIds(config)) {
+ return config.trackIds;
+ }
+ }
+ return [];
+}
+
export class FlowEventsRendererArgs {
trackIdToTrackPanel: Map<number, TrackPanelInfo>;
groupIdToTrackGroupPanel: Map<string, TrackGroupPanelInfo>;
@@ -78,15 +97,9 @@
registerPanel(panel: PanelVNode, yStart: number, height: number) {
if (panel.state instanceof TrackPanel && hasId(panel.attrs)) {
- const config = globals.state.tracks[panel.attrs.id].config;
- if (hasTrackId(config)) {
- this.trackIdToTrackPanel.set(
- config.trackId, {panel: panel.state, yStart});
- }
- if (hasManyTrackIds(config)) {
- for (const trackId of config.trackIds) {
- this.trackIdToTrackPanel.set(trackId, {panel: panel.state, yStart});
- }
+ const track = globals.state.tracks[panel.attrs.id];
+ for (const trackId of getTrackIds(track)) {
+ this.trackIdToTrackPanel.set(trackId, {panel: panel.state, yStart});
}
} else if (
panel.state instanceof TrackGroupPanel &&
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index bb1b2c7..80f690b 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -146,8 +146,8 @@
for (const track of Object.values(globals.state.tracks)) {
if (exists(track?.uri)) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- if (trackInfo?.tags?.cpu === cpu) {
+ if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
+ if (trackInfo?.cpu === cpu) {
trackId = track.id;
break;
}
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 31e9c79..6ddbc51 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -19,7 +19,7 @@
import {EngineProxy} from '../common/engine';
import {TrackState} from '../common/state';
import {TrackData} from '../common/track_data';
-import {TrackLike} from '../public';
+import {Track} from '../public';
import {checkerboard} from './checkerboard';
import {globals} from './globals';
@@ -42,7 +42,7 @@
// We need the |create| method because the stored value in the registry can be
// an abstract class, and we cannot call 'new' on an abstract class.
- create(args: NewTrackArgs): Track;
+ create(args: NewTrackArgs): TrackBase;
}
export interface SliceRect {
@@ -54,8 +54,8 @@
}
// The abstract class that needs to be implemented by all tracks.
-export abstract class Track<Config = {}, Data extends TrackData = TrackData>
- implements TrackLike {
+export abstract class TrackBase<Config = {}, Data extends TrackData = TrackData>
+ implements Track {
// The UI-generated track ID (not to be confused with the SQL track.id).
protected readonly trackId: string;
protected readonly engine: EngineProxy;
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index e1f2c86..b378189 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -18,17 +18,19 @@
import {assertExists} from '../base/logging';
import {Icons} from '../base/semantic_icons';
import {Actions} from '../common/actions';
+import {pluginManager} from '../common/plugins';
+import {RegistryError} from '../common/registry';
import {
getContainingTrackId,
TrackGroupState,
TrackState,
} from '../common/state';
+import {Migrate, Track, TrackContext} from '../public';
import {globals} from './globals';
import {drawGridLines} from './gridline_helper';
import {Panel, PanelSize} from './panel';
-import {Track} from './track';
-import {TrackChips, TrackContent} from './track_panel';
+import {renderChips, TrackContent} from './track_panel';
import {trackRegistry} from './track_registry';
import {
drawVerticalLineAtTime,
@@ -43,21 +45,33 @@
private readonly trackGroupId: string;
private shellWidth = 0;
private backgroundColor = '#ffffff'; // Updated from CSS later.
- private summaryTrack: Track|undefined;
+ private summaryTrack?: Track;
constructor({attrs}: m.CVnode<Attrs>) {
super();
this.trackGroupId = attrs.trackGroupId;
- const trackCreator = trackRegistry.get(this.summaryTrackState.kind);
- const engineId = this.summaryTrackState.engineId;
- const engine = globals.engines.get(engineId);
- if (engine !== undefined) {
- this.summaryTrack = trackCreator.create({
- trackId: this.summaryTrackState.id,
- engine: engine.getProxy(`Track; kind: ${
- this.summaryTrackState.kind}; id: ${this.summaryTrackState.id}`),
- });
- }
+ }
+
+ private tryLoadTrack() {
+ const trackId = this.trackGroupId;
+ const trackState = this.summaryTrackState;
+
+ const {id, uri} = trackState;
+
+ const ctx: TrackContext = {
+ trackInstanceId: id,
+ mountStore: <T>(migrate: Migrate<T>) => {
+ const {store, state} = globals;
+ const migratedState = migrate(state.trackGroups[trackId].state);
+ store.edit((draft) => {
+ draft.trackGroups[trackId].state = migratedState;
+ });
+ return store.createProxy<T>(['trackGroups', trackId, 'state']);
+ },
+ };
+
+ this.summaryTrack =
+ uri ? pluginManager.createTrack(uri, ctx) : loadTrack(trackState, id);
}
get trackGroupState(): TrackGroupState {
@@ -69,6 +83,10 @@
}
view({attrs}: m.CVnode<Attrs>) {
+ if (!this.summaryTrack) {
+ this.tryLoadTrack();
+ }
+
const collapsed = this.trackGroupState.collapsed;
let name = this.trackGroupState.name;
if (name[0] === '/') {
@@ -132,7 +150,7 @@
'h1.track-title',
{title: name},
name,
- m(TrackChips, {config: this.summaryTrackState.config}),
+ renderChips(this.summaryTrackState),
),
(this.trackGroupState.collapsed && child !== null) ?
m('h2.track-subtitle', child) :
@@ -286,3 +304,25 @@
function StripPathFromExecutable(path: string) {
return path.split('/').slice(-1)[0];
}
+
+function loadTrack(trackState: TrackState, trackId: string): Track|undefined {
+ const engine = globals.engines.get(trackState.engineId);
+ if (engine === undefined) {
+ return undefined;
+ }
+
+ try {
+ const trackCreator = trackRegistry.get(trackState.kind);
+ return trackCreator.create({
+ trackId,
+ engine:
+ engine.getProxy(`Track; kind: ${trackState.kind}; id: ${trackId}`),
+ });
+ } catch (e) {
+ if (e instanceof RegistryError) {
+ return undefined;
+ } else {
+ throw e;
+ }
+ }
+}
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 8dffff1..c280ced 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -18,12 +18,13 @@
import {currentTargetOffset} from '../base/dom_utils';
import {Icons} from '../base/semantic_icons';
import {duration, Span, time} from '../base/time';
+import {exists} from '../base/utils';
import {Actions} from '../common/actions';
import {pluginManager} from '../common/plugins';
import {RegistryError} from '../common/registry';
import {TrackState} from '../common/state';
import {raf} from '../core/raf_scheduler';
-import {TrackLike} from '../public';
+import {Migrate, Track, TrackContext} from '../public';
import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
import {globals} from './globals';
@@ -68,26 +69,39 @@
return selectedArea.tracks.includes(id);
}
-interface TrackChipsAttrs {
- config: {[k: string]: any};
+interface TrackChipAttrs {
+ text: string;
}
-export class TrackChips implements m.ClassComponent<TrackChipsAttrs> {
- view({attrs}: m.CVnode<TrackChipsAttrs>) {
- const {config} = attrs;
-
- const isMetric = 'namespace' in config;
- const isDebuggable = ('isDebuggable' in config) && config.isDebuggable;
-
- return [
- isMetric && m('span.chip', 'metric'),
- isDebuggable && m('span.chip', 'debuggable'),
- ];
+class TrackChip implements m.ClassComponent<TrackChipAttrs> {
+ view({attrs}: m.CVnode<TrackChipAttrs>) {
+ return m('span.chip', attrs.text);
}
}
+export function renderChips({uri, config}: TrackState) {
+ const tagElements: m.Children = [];
+ if (exists(uri)) {
+ const trackInfo = pluginManager.resolveTrackInfo(uri);
+ const tags = trackInfo?.tags;
+ tags?.metric && tagElements.push(m(TrackChip, {text: 'metric'}));
+ tags?.debuggable && tagElements.push(m(TrackChip, {text: 'debuggable'}));
+ } else {
+ if (config && typeof config === 'object') {
+ if ('namespace' in config) {
+ tagElements.push(m(TrackChip, {text: 'metric'}));
+ }
+ if ('isDebuggable' in config && config.isDebuggable) {
+ tagElements.push(m(TrackChip, {text: 'debuggable'}));
+ }
+ }
+ }
+
+ return tagElements;
+}
+
interface TrackShellAttrs {
- track: TrackLike;
+ track: Track;
trackState: TrackState;
}
@@ -134,7 +148,7 @@
},
},
attrs.trackState.name,
- m(TrackChips, {config: attrs.trackState.config}),
+ renderChips(attrs.trackState),
),
m('.track-buttons',
attrs.track.getTrackShellButtons(),
@@ -219,7 +233,7 @@
}
export interface TrackContentAttrs {
- track: TrackLike;
+ track: Track;
}
export class TrackContent implements m.ClassComponent<TrackContentAttrs> {
private mouseDownX?: number;
@@ -278,7 +292,7 @@
interface TrackComponentAttrs {
trackState: TrackState;
- track: TrackLike;
+ track: Track;
}
class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
view({attrs}: m.CVnode<TrackComponentAttrs>) {
@@ -342,7 +356,7 @@
// TODO(hjd): It would be nicer if these could not be undefined here.
// We should implement a NullTrack which can be used if the trackState
// has disappeared.
- private track: TrackLike|undefined;
+ private track: Track|undefined;
private trackState: TrackState|undefined;
private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
@@ -352,8 +366,22 @@
if (!trackState) return;
const {id, uri} = trackState;
- this.track =
- uri ? pluginManager.createTrack(uri, id) : loadTrack(trackState, id);
+
+ const trackCtx: TrackContext = {
+ trackInstanceId: id,
+ mountStore: <T>(migrate: Migrate<T>) => {
+ const {store, state} = globals;
+ const migratedState = migrate(state.tracks[trackId].state);
+ globals.store.edit((draft) => {
+ draft.tracks[trackId].state = migratedState;
+ });
+ return store.createProxy<T>(['tracks', trackId, 'state']);
+ },
+ };
+
+ this.track = uri ? pluginManager.createTrack(uri, trackCtx) :
+ loadTrack(trackState, id);
+
this.track?.onCreate();
this.trackState = trackState;
}
@@ -492,8 +520,7 @@
}
}
-function loadTrack(trackState: TrackState, trackId: string): TrackLike|
- undefined {
+function loadTrack(trackState: TrackState, trackId: string): Track|undefined {
const engine = globals.engines.get(trackState.engineId);
if (engine === undefined) {
return undefined;
diff --git a/ui/src/plugins/com.example.Skeleton/index.ts b/ui/src/plugins/com.example.Skeleton/index.ts
index 0881b5f..354cb7f 100644
--- a/ui/src/plugins/com.example.Skeleton/index.ts
+++ b/ui/src/plugins/com.example.Skeleton/index.ts
@@ -16,9 +16,9 @@
MetricVisualisation,
Plugin,
PluginContext,
- PluginInfo,
- TracePluginContext,
- TrackInfo,
+ PluginContextTrace,
+ PluginDescriptor,
+ TrackInstanceDescriptor,
} from '../../public';
interface State {
@@ -35,11 +35,11 @@
return {foo: 'bar'};
}
- async onTraceLoad(_: TracePluginContext<State>): Promise<void> {
+ async onTraceLoad(_: PluginContextTrace<State>): Promise<void> {
//
}
- async onTraceUnload(_: TracePluginContext<State>): Promise<void> {
+ async onTraceUnload(_: PluginContextTrace<State>): Promise<void> {
//
}
@@ -47,17 +47,17 @@
//
}
- async findPotentialTracks(_: TracePluginContext<State>):
- Promise<TrackInfo[]> {
+ async findPotentialTracks(_: PluginContextTrace<State>):
+ Promise<TrackInstanceDescriptor[]> {
return [];
}
- metricVisualisations(_: TracePluginContext<State>): MetricVisualisation[] {
+ metricVisualisations(_: PluginContextTrace<State>): MetricVisualisation[] {
return [];
}
}
-export const plugin: PluginInfo<State> = {
+export const plugin: PluginDescriptor<State> = {
// SKELETON: Update pluginId to match the directory of the plugin.
pluginId: 'com.example.Skeleton',
plugin: Skeleton,
diff --git a/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts b/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts
index c7e0c8c..a77b14e 100644
--- a/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts
@@ -16,7 +16,7 @@
MetricVisualisation,
Plugin,
PluginContext,
- PluginInfo,
+ PluginDescriptor,
} from '../../public';
const SPEC = `
@@ -50,7 +50,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'dev.perfetto.AndroidBinderVizPlugin',
plugin: AndroidBinderVizPlugin,
};
diff --git a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
index 3870cfe..8947364 100644
--- a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
@@ -15,7 +15,7 @@
import {
Plugin,
PluginContext,
- PluginInfo,
+ PluginDescriptor,
} from '../../public';
class AndroidCujs implements Plugin {
@@ -130,7 +130,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'dev.perfetto.AndroidCujs',
plugin: AndroidCujs,
};
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
index 17aa796..feb7080 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
@@ -15,7 +15,7 @@
import {
Plugin,
PluginContext,
- PluginInfo,
+ PluginDescriptor,
} from '../../public';
class AndroidPerf implements Plugin {
@@ -60,7 +60,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'dev.perfetto.AndroidPerf',
plugin: AndroidPerf,
};
diff --git a/ui/src/plugins/dev.perfetto.CoreCommands/index.ts b/ui/src/plugins/dev.perfetto.CoreCommands/index.ts
index fedc300..5b94d08 100644
--- a/ui/src/plugins/dev.perfetto.CoreCommands/index.ts
+++ b/ui/src/plugins/dev.perfetto.CoreCommands/index.ts
@@ -15,7 +15,7 @@
import {
Plugin,
PluginContext,
- PluginInfo,
+ PluginDescriptor,
} from '../../public';
const SQL_STATS = `
@@ -174,7 +174,7 @@
},
};
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'dev.perfetto.CoreCommands',
plugin: coreCommands,
};
diff --git a/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts b/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts
index 1051784..4bb6e12 100644
--- a/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts
+++ b/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts
@@ -15,7 +15,7 @@
import {
Plugin,
PluginContext,
- PluginInfo,
+ PluginDescriptor,
} from '../../public';
// This is just an example plugin, used to prove that the plugin system works.
@@ -29,7 +29,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'dev.perfetto.ExampleSimpleCommand',
plugin: ExampleSimpleCommand,
};
diff --git a/ui/src/plugins/dev.perfetto.ExampleState/index.ts b/ui/src/plugins/dev.perfetto.ExampleState/index.ts
index a124f71..a18cfe2 100644
--- a/ui/src/plugins/dev.perfetto.ExampleState/index.ts
+++ b/ui/src/plugins/dev.perfetto.ExampleState/index.ts
@@ -15,8 +15,8 @@
import {
Plugin,
PluginContext,
- PluginInfo,
- TracePluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
} from '../../public';
interface State {
@@ -39,7 +39,7 @@
//
}
- async onTraceLoad(ctx: TracePluginContext<State>): Promise<void> {
+ async onTraceLoad(ctx: PluginContextTrace<State>): Promise<void> {
const {viewer, store} = ctx;
ctx.addCommand({
id: 'dev.perfetto.ExampleState#ShowCounter',
@@ -54,7 +54,7 @@
}
}
-export const plugin: PluginInfo<State> = {
+export const plugin: PluginDescriptor<State> = {
pluginId: 'dev.perfetto.ExampleState',
plugin: ExampleState,
};
diff --git a/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts b/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
index 07881da..07872ed 100644
--- a/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
@@ -15,7 +15,7 @@
import {
Plugin,
PluginContext,
- PluginInfo,
+ PluginDescriptor,
} from '../../public';
class LargeScreensPerf implements Plugin {
@@ -36,7 +36,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'dev.perfetto.LargeScreensPerf',
plugin: LargeScreensPerf,
};
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index b939428..60ab2b8 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -154,7 +154,7 @@
// 'TrackController' and a 'Track'. In more recent versions of the UI
// the functionality of |TrackController| has been merged into Track so
// |TrackController|s are not necessary in new code.
- registerTrackController(track: TrackControllerFactory): void;
+ LEGACY_registerTrackController(track: TrackControllerFactory): void;
// Register a track factory. The core UI invokes |TrackCreator| to
// construct tracks discovered by invoking |TrackProvider|s.
@@ -164,25 +164,30 @@
// which returns GPU counter tracks. The counter track factory itself
// could be registered in dev.perfetto.CounterTrack - a whole
// different plugin.
- registerTrack(track: TrackCreator): void;
+ LEGACY_registerTrack(track: TrackCreator): void;
// Add a command.
addCommand(command: Command): void;
}
-export interface TrackContext {
- // A unique ID for the instance of this track.
- trackInstanceId: string;
-}
+export type Migrate<State> = (init: unknown) => State;
export interface TrackContext {
- // A unique ID for the instance of this track.
+ // The ID of this track instance.
trackInstanceId: string;
+
+ // Creates a new store overlaying the track instance's state object.
+ // A migrate function must be passed to convert any existing state to a
+ // compatible format.
+ // When opening a fresh trace, the value of |init| will be undefined, and
+ // state should be updated to an appropriate default value.
+ // When loading a permalink, the value of |init| will be whatever was saved
+ // when the permalink was shared, which might be from an old version of this
+ // track.
+ mountStore<State>(migrate: Migrate<State>): Store<State>;
}
-// TODO(stevegolton): Rename `Track` to `BaseTrack` (or similar) and rename this
-// interface to `Track`.
-export interface TrackLike {
+export interface Track {
onCreate(): void;
render(ctx: CanvasRenderingContext2D): void;
onFullRedraw(): void;
@@ -199,7 +204,7 @@
onDestroy(): void;
}
-export interface PluginTrackInfo {
+export interface TrackDescriptor {
// A unique identifier for the track. This must be unique within all tracks.
uri: string;
@@ -208,34 +213,83 @@
displayName: string;
// A factory function returning the track object.
- trackFactory: (ctx: TrackContext) => TrackLike;
+ track: (ctx: TrackContext) => Track;
- // A list of tags used for sorting and grouping.
+ // The track "kind" Uued by various subsystems e.g. aggregation controllers.
+ // This is where "XXX_TRACK_KIND" values should be placed.
+ // TODO(stevegolton): This will be deprecated once we handle group selections
+ // in a more generic way - i.e. EventSet.
+ kind: string;
+
+ // An optional list of track IDs represented by this trace.
+ // This list is used for participation in track indexing by track ID.
+ // This index is used by various subsystems to find links between tracks based
+ // on the track IDs used by trace processor.
+ trackIds?: number[];
+
+ // Optional: The CPU number associated with this track.
+ cpu?: number;
+
+ // Optional: A list of tags used for sorting, grouping and "chips".
tags?: TrackTags;
}
+// Tracks within track groups (usually corresponding to processes) are sorted.
+// As we want to group all tracks related to a given thread together, we use
+// two keys:
+// - Primary key corresponds to a priority of a track block (all tracks related
+// to a given thread or a single track if it's not thread-associated).
+// - Secondary key corresponds to a priority of a given thread-associated track
+// within its thread track block.
+// Each track will have a sort key, which either a primary sort key
+// (for non-thread tracks) or a tid and secondary sort key (mapping of tid to
+// primary sort key is done independently).
+export enum PrimaryTrackSortKey {
+ DEBUG_SLICE_TRACK,
+ NULL_TRACK,
+ PROCESS_SCHEDULING_TRACK,
+ PROCESS_SUMMARY_TRACK,
+ EXPECTED_FRAMES_SLICE_TRACK,
+ ACTUAL_FRAMES_SLICE_TRACK,
+ PERF_SAMPLES_PROFILE_TRACK,
+ HEAP_PROFILE_TRACK,
+ MAIN_THREAD,
+ RENDER_THREAD,
+ GPU_COMPLETION_THREAD,
+ CHROME_IO_THREAD,
+ CHROME_COMPOSITOR_THREAD,
+ ORDINARY_THREAD,
+ COUNTER_TRACK,
+ ASYNC_SLICE_TRACK,
+ ORDINARY_TRACK,
+}
+
// Similar to PluginContext but with additional properties to operate on the
// currently loaded trace. Passed to trace-relevant hooks instead of
// PluginContext.
-export interface TracePluginContext<T = undefined> extends PluginContext {
+export interface PluginContextTrace<T = undefined> extends PluginContext {
readonly engine: EngineProxy;
readonly store: Store<T>;
// Add a new track from this plugin. The track is just made available here,
// it's not automatically shown until it's added to a workspace.
- addTrack(trackDetails: PluginTrackInfo): void;
+ addTrack(trackDetails: TrackDescriptor): void;
+
+ // Suggest a track be added to the workspace on a fresh trace load.
+ // Supersedes `findPotentialTracks()` which has been removed.
+ // Note: this API will be deprecated soon.
+ suggestTrack(trackInfo: TrackInstanceDescriptor): void;
}
export interface BasePlugin<State> {
// Lifecycle methods.
onActivate(ctx: PluginContext): void;
- onTraceLoad?(ctx: TracePluginContext<State>): Promise<void>;
- onTraceUnload?(ctx: TracePluginContext<State>): Promise<void>;
+ onTraceLoad?(ctx: PluginContextTrace<State>): Promise<void>;
+ onTraceUnload?(ctx: PluginContextTrace<State>): Promise<void>;
onDeactivate?(ctx: PluginContext): void;
// Extension points.
metricVisualisations?(ctx: PluginContext): MetricVisualisation[];
- findPotentialTracks?(ctx: TracePluginContext<State>): Promise<TrackInfo[]>;
}
export interface StatefulPlugin<State> extends BasePlugin<State> {
@@ -264,17 +318,17 @@
new(): Plugin<T>;
}
-export interface TrackInfo {
- // The id of this 'type' of track. This id is used to select the
- // correct |TrackCreator| to construct the track.
- trackKind: string;
-
+export interface TrackInstanceDescriptor {
// A human readable name for this specific track. It will normally be
// displayed on the left-hand-side of the track.
name: string;
- // An opaque config for the track.
- config: {};
+ // Used to define default sort order for new traces.
+ // Note: sortKey will be deprecated soon in favour of tags.
+ sortKey: PrimaryTrackSortKey;
+
+ // URI of the suggested track.
+ uri: string;
}
// A predicate for selecting a groups of tracks.
@@ -284,30 +338,28 @@
// A human readable name for this specific track.
name: string;
- // This is where "XXX_TRACK_KIND" values should be placed.
- kind: string;
+ // Controls whether to show the "metric" chip.
+ metric: boolean;
- // The CPU number associated with this track.
- cpu: number;
+ // Controls whether to show the "debuggable" chip.
+ debuggable: boolean;
}
// An set of key/value pairs describing a given track. These are used for
-// selecting tracks to pin/unpin and (in future) the sorting and grouping of
-// tracks.
-// These are also (ab)used for communicating information about tracks for the
-// purposes of locating tracks by their properties e.g. aggregation & search.
+// selecting tracks to pin/unpin, diplsaying "chips" in the track shell, and
+// (in future) the sorting and grouping of tracks.
// We define a handful of well known fields, and the rest are arbitrary key-
// value pairs.
export type TrackTags = Partial<WellKnownTrackTags>&{
// There may be arbitrary other key/value pairs.
- [key: string]: string|number|undefined;
+ [key: string]: string|number|boolean|undefined;
}
// Plugins can be passed as class refs, factory functions, or concrete plugin
// implementations.
export type PluginFactory<T> = PluginClass<T>|Plugin<T>|(() => Plugin<T>);
-export interface PluginInfo<T = undefined> {
+export interface PluginDescriptor<T = undefined> {
// A unique string for your plugin. To ensure the name is unique you
// may wish to use a URL with reversed components in the manner of
// Java package names.
diff --git a/ui/src/public/utils.ts b/ui/src/public/utils.ts
new file mode 100644
index 0000000..d8db8f0
--- /dev/null
+++ b/ui/src/public/utils.ts
@@ -0,0 +1,73 @@
+// 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.
+
+export function getTrackName(args: Partial<{
+ name: string | null,
+ utid: number,
+ processName: string | null,
+ pid: number | null,
+ threadName: string | null,
+ tid: number | null,
+ upid: number | null,
+ kind: string,
+ threadTrack: boolean
+}>) {
+ const {
+ name,
+ upid,
+ utid,
+ processName,
+ threadName,
+ pid,
+ tid,
+ kind,
+ threadTrack,
+ } = args;
+
+ const hasName = name !== undefined && name !== null && name !== '[NULL]';
+ const hasUpid = upid !== undefined && upid !== null;
+ const hasUtid = utid !== undefined && utid !== null;
+ const hasProcessName = processName !== undefined && processName !== null;
+ const hasThreadName = threadName !== undefined && threadName !== null;
+ const hasTid = tid !== undefined && tid !== null;
+ const hasPid = pid !== undefined && pid !== null;
+ const hasKind = kind !== undefined;
+ const isThreadTrack = threadTrack !== undefined && threadTrack;
+
+ // If we don't have any useful information (better than
+ // upid/utid) we show the track kind to help with tracking
+ // down where this is coming from.
+ const kindSuffix = hasKind ? ` (${kind})` : '';
+
+ if (isThreadTrack && hasName && hasTid) {
+ return `${name} (${tid})`;
+ } else if (hasName) {
+ return `${name}`;
+ } else if (hasUpid && hasPid && hasProcessName) {
+ return `${processName} ${pid}`;
+ } else if (hasUpid && hasPid) {
+ return `Process ${pid}`;
+ } else if (hasThreadName && hasTid) {
+ return `${threadName} ${tid}`;
+ } else if (hasTid) {
+ return `Thread ${tid}`;
+ } else if (hasUpid) {
+ return `upid: ${upid}${kindSuffix}`;
+ } else if (hasUtid) {
+ return `utid: ${utid}${kindSuffix}`;
+ } else if (hasKind) {
+ return `Unnamed ${kind}`;
+ }
+ return 'Unknown';
+}
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/actual_frames/index.ts
index 5e1d631..90de29a 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -17,8 +17,8 @@
import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
import {TrackData} from '../../common/track_data';
import {TrackController} from '../../controller/track_controller';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
import {ChromeSliceTrack} from '../chrome_slices';
export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
@@ -156,19 +156,19 @@
export class ActualFramesSliceTrack extends ChromeSliceTrack {
static readonly kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new ActualFramesSliceTrack(args);
}
}
class ActualFrames implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(ActualFramesSliceTrackController);
- ctx.registerTrack(ActualFramesSliceTrack);
+ ctx.LEGACY_registerTrackController(ActualFramesSliceTrackController);
+ ctx.LEGACY_registerTrack(ActualFramesSliceTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ActualFrames',
plugin: ActualFrames,
};
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index 948f82d..743d665 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -26,10 +26,12 @@
import {
Plugin,
PluginContext,
- PluginInfo,
- TracePluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
} from '../../public';
+export const ANDROID_LOGS_TRACK_KIND = 'AndroidLogTrack';
+
export interface Data extends TrackData {
// Total number of log events within [start, end], before any quantization.
numEvents: number;
@@ -147,7 +149,7 @@
class AndroidLog implements Plugin {
onActivate(_ctx: PluginContext): void {}
- async onTraceLoad(ctx: TracePluginContext): Promise<void> {
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const result =
await ctx.engine.query(`select count(1) as cnt from android_logs`);
const count = result.firstRow({cnt: NUM}).cnt;
@@ -155,7 +157,8 @@
ctx.addTrack({
uri: 'perfetto.AndroidLog',
displayName: 'Android logs',
- trackFactory: ({trackInstanceId}) => {
+ kind: ANDROID_LOGS_TRACK_KIND,
+ track: ({trackInstanceId}) => {
return new TrackWithControllerAdapter<Config, Data>(
ctx.engine,
trackInstanceId,
@@ -168,7 +171,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.AndroidLog',
plugin: AndroidLog,
};
diff --git a/ui/src/tracks/annotation/index.ts b/ui/src/tracks/annotation/index.ts
new file mode 100644
index 0000000..2c8649a
--- /dev/null
+++ b/ui/src/tracks/annotation/index.ts
@@ -0,0 +1,90 @@
+// 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 {
+ NUM,
+ NUM_NULL,
+ STR,
+} from '../../common/query_result';
+import {
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+} from '../../public';
+import {
+ Config as CounterTrackConfig,
+ COUNTER_TRACK_KIND,
+ CounterTrack,
+} from '../counter';
+
+class AnnotationPlugin implements Plugin {
+ onActivate(_ctx: PluginContext): void {}
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ await this.addAnnotationCounterTracks(ctx);
+ }
+
+ private async addAnnotationCounterTracks(ctx: PluginContextTrace) {
+ const {engine} = ctx;
+ const counterResult = await engine.query(`
+ SELECT
+ id,
+ name,
+ min_value as minValue,
+ max_value as maxValue
+ FROM annotation_counter_track`);
+
+ const counterIt = counterResult.iter({
+ id: NUM,
+ name: STR,
+ minValue: NUM_NULL,
+ maxValue: NUM_NULL,
+ });
+
+ for (; counterIt.valid(); counterIt.next()) {
+ const id = counterIt.id;
+ const name = counterIt.name;
+ const minimumValue =
+ counterIt.minValue === null ? undefined : counterIt.minValue;
+ const maximumValue =
+ counterIt.maxValue === null ? undefined : counterIt.maxValue;
+
+ const config: CounterTrackConfig = {
+ name,
+ trackId: id,
+ namespace: 'annotation',
+ minimumValue,
+ maximumValue,
+ };
+
+ ctx.addTrack({
+ uri: `perfetto.Annotation#counter${id}`,
+ displayName: name,
+ kind: COUNTER_TRACK_KIND,
+ tags: {
+ metric: true,
+ },
+ track: (trackCtx) => {
+ return new CounterTrack(trackCtx, config, ctx.engine);
+ },
+ });
+ }
+ }
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'perfetto.Annotation',
+ plugin: AnnotationPlugin,
+};
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index d25717f..09d0dda 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -19,8 +19,8 @@
import {
TrackController,
} from '../../controller/track_controller';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
import {ChromeSliceTrack} from '../chrome_slices';
export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
@@ -134,19 +134,19 @@
export class AsyncSliceTrack extends ChromeSliceTrack {
static readonly kind = ASYNC_SLICE_TRACK_KIND;
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new AsyncSliceTrack(args);
}
}
class AsyncSlicePlugin implements Plugin {
onActivate(ctx: PluginContext) {
- ctx.registerTrackController(AsyncSliceTrackController);
- ctx.registerTrack(AsyncSliceTrack);
+ ctx.LEGACY_registerTrackController(AsyncSliceTrackController);
+ ctx.LEGACY_registerTrack(AsyncSliceTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.AsyncSlices',
plugin: AsyncSlicePlugin,
};
diff --git a/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
index 60db8a0..1026328 100644
--- a/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
@@ -22,10 +22,12 @@
NamedSliceTrackTypes,
} from '../../frontend/named_slice_track';
import {runQueryInNewTab} from '../../frontend/query_result_tab';
-import {NewTrackArgs, Track} from '../../frontend/track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {ScrollJankTracks as DecideTracksResult} from './index';
-import {ENABLE_CHROME_SCROLL_JANK_PLUGIN} from './index';
+import {
+ ENABLE_CHROME_SCROLL_JANK_PLUGIN,
+ ScrollJankTracks as DecideTracksResult,
+} from './index';
interface ChromeTasksScrollJankTrackConfig {}
@@ -36,7 +38,7 @@
export class ChromeTasksScrollJankTrack extends
NamedSliceTrack<ChromeTasksScrollJankTrackTypes> {
static readonly kind = 'org.chromium.ScrollJank.BrowserUIThreadLongTasks';
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new ChromeTasksScrollJankTrack(args);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
index c69e9a2..cfcaada 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -21,12 +21,13 @@
import {
generateSqlWithInternalLayout,
} from '../../common/internal_layout_utils';
-import {PrimaryTrackSortKey, SCROLLING_TRACK_GROUP} from '../../common/state';
+import {SCROLLING_TRACK_GROUP} from '../../common/state';
import {globals} from '../../frontend/globals';
import {
NamedSliceTrackTypes,
} from '../../frontend/named_slice_track';
-import {NewTrackArgs, Track} from '../../frontend/track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {PrimaryTrackSortKey} from '../../public';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
@@ -50,7 +51,7 @@
CustomSqlTableSliceTrack<EventLatencyTrackTypes> {
static readonly kind = 'org.chromium.ScrollJank.event_latencies';
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new EventLatencyTrack(args);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/index.ts b/ui/src/tracks/chrome_scroll_jank/index.ts
index 963a2b1..592d9a5 100644
--- a/ui/src/tracks/chrome_scroll_jank/index.ts
+++ b/ui/src/tracks/chrome_scroll_jank/index.ts
@@ -16,7 +16,7 @@
import {Engine} from '../../common/engine';
import {featureFlags} from '../../common/feature_flags';
import {ObjectById} from '../../common/state';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
import {CustomSqlDetailsPanelConfig} from '../custom_sql_table_slices';
import {ChromeTasksScrollJankTrack} from './chrome_tasks_scroll_jank_track';
@@ -129,14 +129,14 @@
class ChromeScrollJankPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(ChromeTasksScrollJankTrack);
- ctx.registerTrack(EventLatencyTrack);
- ctx.registerTrack(ScrollJankV3Track);
- ctx.registerTrack(TopLevelScrollTrack);
+ ctx.LEGACY_registerTrack(ChromeTasksScrollJankTrack);
+ ctx.LEGACY_registerTrack(EventLatencyTrack);
+ ctx.LEGACY_registerTrack(ScrollJankV3Track);
+ ctx.LEGACY_registerTrack(TopLevelScrollTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ChromeScrollJank',
plugin: ChromeScrollJankPlugin,
};
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index 6e554d4..f1db56e 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -19,12 +19,12 @@
} from '../../common/colorizer';
import {Engine} from '../../common/engine';
import {
- PrimaryTrackSortKey,
SCROLLING_TRACK_GROUP,
} from '../../common/state';
import {globals} from '../../frontend/globals';
import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {NewTrackArgs, Track} from '../../frontend/track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {PrimaryTrackSortKey} from '../../public';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
@@ -48,7 +48,7 @@
CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
static readonly kind = 'org.chromium.ScrollJank.scroll_jank_v3_track';
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new ScrollJankV3Track(args);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
index d82944b..97ccc4a 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
@@ -15,20 +15,20 @@
import {v4 as uuidv4} from 'uuid';
import {Engine} from '../../common/engine';
-import {
- PrimaryTrackSortKey,
- SCROLLING_TRACK_GROUP,
-} from '../../common/state';
+import {SCROLLING_TRACK_GROUP} from '../../common/state';
import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {NewTrackArgs, Track} from '../../frontend/track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {PrimaryTrackSortKey} from '../../public';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../custom_sql_table_slices';
-import {ScrollJankPluginState} from './index';
-import {ScrollJankTracks as DecideTracksResult} from './index';
+import {
+ ScrollJankPluginState,
+ ScrollJankTracks as DecideTracksResult,
+} from './index';
import {ScrollDetailsPanel} from './scroll_details_panel';
export {Data} from '../chrome_slices';
@@ -37,7 +37,7 @@
CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
static readonly kind = 'org.chromium.TopLevelScrolls.scrolls';
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new TopLevelScrollTrack(args);
}
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 875a7bf..28efb24 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -28,8 +28,8 @@
import {globals} from '../../frontend/globals';
import {cachedHsluvToHex} from '../../frontend/hsluv_cache';
import {PxSpan, TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, SliceRect, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, SliceRect, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
const SLICE_HEIGHT = 18;
@@ -159,9 +159,9 @@
}
}
-export class ChromeSliceTrack extends Track<Config, Data> {
+export class ChromeSliceTrack extends TrackBase<Config, Data> {
static readonly kind: string = SLICE_TRACK_KIND;
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new ChromeSliceTrack(args);
}
@@ -439,12 +439,12 @@
class ChromeSlicesPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(ChromeSliceTrackController);
- ctx.registerTrack(ChromeSliceTrack);
+ ctx.LEGACY_registerTrackController(ChromeSliceTrackController);
+ ctx.LEGACY_registerTrack(ChromeSliceTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ChromeSlices',
plugin: ChromeSlicesPlugin,
};
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index cb96f6f..1fbc53f 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -13,28 +13,36 @@
// limitations under the License.
import m from 'mithril';
+import {v4 as uuidv4} from 'uuid';
import {searchSegment} from '../../base/binary_search';
import {assertTrue} from '../../base/logging';
import {duration, time, Time} from '../../base/time';
import {Actions} from '../../common/actions';
+import {
+ BasicAsyncTrack,
+ NUM_NULL,
+ STR_NULL,
+} from '../../common/basic_async_track';
import {drawTrackHoverTooltip} from '../../common/canvas_utils';
import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
import {
+ EngineProxy,
LONG,
LONG_NULL,
NUM,
Plugin,
PluginContext,
- PluginInfo,
+ PluginContextTrace,
+ PluginDescriptor,
+ PrimaryTrackSortKey,
+ Store,
STR,
- TracePluginContext,
- TrackInfo,
+ TrackContext,
} from '../../public';
+import {getTrackName} from '../../public/utils';
import {Button} from '../../widgets/button';
import {MenuItem, PopupMenu2} from '../../widgets/menu';
@@ -66,76 +74,157 @@
minimumValue?: number;
startTs?: time;
endTs?: time;
- namespace: string;
+ namespace?: string;
trackId: number;
- scale?: CounterScaleOptions;
+ defaultScale?: CounterScaleOptions;
}
-class CounterTrackController extends TrackController<Config, Data> {
- static readonly kind = COUNTER_TRACK_KIND;
- private setup = false;
+const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$');
+const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:');
+
+// Sets the default 'scale' for counter tracks. If the regex matches
+// then the paired mode is used. Entries are in priority order so the
+// first match wins.
+const COUNTER_REGEX: [RegExp, CounterScaleOptions][] = [
+ // Power counters make more sense in rate mode since you're typically
+ // interested in the slope of the graph rather than the absolute
+ // value.
+ [new RegExp('^power\..*$'), 'RATE'],
+ // Same for network counters.
+ [NETWORK_TRACK_REGEX, 'RATE'],
+ // Entity residency
+ [ENTITY_RESIDENCY_REGEX, 'RATE'],
+];
+
+function getCounterScale(name: string): CounterScaleOptions|undefined {
+ for (const [re, scale] of COUNTER_REGEX) {
+ if (name.match(re)) {
+ return scale;
+ }
+ }
+ return undefined;
+}
+
+// 0.5 Makes the horizontal lines sharp.
+const MARGIN_TOP = 3.5;
+const RECT_HEIGHT = 24.5;
+
+interface CounterTrackState {
+ scale: CounterScaleOptions;
+}
+
+function isCounterState(x: unknown): x is CounterTrackState {
+ if (x && typeof x === 'object' && 'scale' in x) {
+ if (typeof x.scale === 'string') {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+export class CounterTrack extends BasicAsyncTrack<Data> {
private maximumValueSeen = 0;
private minimumValueSeen = 0;
private maximumDeltaSeen = 0;
private minimumDeltaSeen = 0;
private maxDurNs: duration = 0n;
+ private store: Store<CounterTrackState>;
+ private id: string;
+ private uuid = uuidv4();
+ private isSetup = false;
+
+ constructor(
+ ctx: TrackContext, private config: Config, private engine: EngineProxy) {
+ super();
+ this.id = ctx.trackInstanceId;
+ this.store = ctx.mountStore<CounterTrackState>((init: unknown) => {
+ if (isCounterState(init)) {
+ return init;
+ } else {
+ return {scale: this.config.defaultScale ?? 'ZERO_BASED'};
+ }
+ });
+ }
+
+ // Returns a valid SQL table name with the given prefix that should be unique
+ // for each track.
+ tableName(prefix: string) {
+ // Derive table name from, since that is unique for each track.
+ // Track ID can be UUID but '-' is not valid for sql table name.
+ const idSuffix = this.uuid.split('-').join('_');
+ return `${prefix}_${idSuffix}`;
+ }
+
+ private namespaceTable(tableName: string): string {
+ if (this.config.namespace) {
+ return this.config.namespace + '_' + tableName;
+ } else {
+ return tableName;
+ }
+ }
+
+ private async setup() {
+ if (this.config.namespace === undefined) {
+ await this.engine.query(`
+ create view ${this.tableName('counter_view')} as
+ select
+ id,
+ ts,
+ dur,
+ value,
+ delta
+ from experimental_counter_dur
+ where track_id = ${this.config.trackId};
+ `);
+ } else {
+ await this.engine.query(`
+ create view ${this.tableName('counter_view')} as
+ select
+ id,
+ ts,
+ lead(ts, 1, ts) over (order by ts) - ts as dur,
+ lead(value, 1, value) over (order by ts) - value as delta,
+ value
+ from ${this.namespaceTable('counter')}
+ where track_id = ${this.config.trackId};
+ `);
+ }
+
+ const maxDurResult = await this.engine.query(`
+ select
+ max(
+ iif(dur != -1, dur, (select end_ts from trace_bounds) - ts)
+ ) as maxDur
+ from ${this.tableName('counter_view')}
+ `);
+ this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+
+ const queryRes = await this.engine.query(`
+ select
+ ifnull(max(value), 0) as maxValue,
+ ifnull(min(value), 0) as minValue,
+ ifnull(max(delta), 0) as maxDelta,
+ ifnull(min(delta), 0) as minDelta
+ from ${this.tableName('counter_view')}`);
+ const row = queryRes.firstRow(
+ {maxValue: NUM, minValue: NUM, maxDelta: NUM, minDelta: NUM});
+ this.maximumValueSeen = row.maxValue;
+ this.minimumValueSeen = row.minValue;
+ this.maximumDeltaSeen = row.maxDelta;
+ this.minimumDeltaSeen = row.minDelta;
+ }
async onBoundsChange(start: time, end: time, resolution: duration):
Promise<Data> {
- if (!this.setup) {
- if (this.config.namespace === undefined) {
- await this.query(`
- create view ${this.tableName('counter_view')} as
- select
- id,
- ts,
- dur,
- value,
- delta
- from experimental_counter_dur
- where track_id = ${this.config.trackId};
- `);
- } else {
- await this.query(`
- create view ${this.tableName('counter_view')} as
- select
- id,
- ts,
- lead(ts, 1, ts) over (order by ts) - ts as dur,
- lead(value, 1, value) over (order by ts) - value as delta,
- value
- from ${this.namespaceTable('counter')}
- where track_id = ${this.config.trackId};
- `);
- }
-
- const maxDurResult = await this.query(`
- select
- max(
- iif(dur != -1, dur, (select end_ts from trace_bounds) - ts)
- ) as maxDur
- from ${this.tableName('counter_view')}
- `);
- this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
-
- const queryRes = await this.query(`
- select
- ifnull(max(value), 0) as maxValue,
- ifnull(min(value), 0) as minValue,
- ifnull(max(delta), 0) as maxDelta,
- ifnull(min(delta), 0) as minDelta
- from ${this.tableName('counter_view')}`);
- const row = queryRes.firstRow(
- {maxValue: NUM, minValue: NUM, maxDelta: NUM, minDelta: NUM});
- this.maximumValueSeen = row.maxValue;
- this.minimumValueSeen = row.minValue;
- this.maximumDeltaSeen = row.maxDelta;
- this.minimumDeltaSeen = row.minDelta;
-
- this.setup = true;
+ if (!this.isSetup) {
+ await this.setup();
+ this.isSetup = true;
}
- const queryRes = await this.query(`
+ const queryRes = await this.engine.query(`
select
(ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
min(value) as minValue,
@@ -219,34 +308,18 @@
return this.config.minimumValue;
}
}
-}
-
-
-// 0.5 Makes the horizontal lines sharp.
-const MARGIN_TOP = 3.5;
-const RECT_HEIGHT = 24.5;
-
-class CounterTrack extends Track<Config, Data> {
- static readonly kind = COUNTER_TRACK_KIND;
- static create(args: NewTrackArgs): CounterTrack {
- return new CounterTrack(args);
- }
private mousePos = {x: 0, y: 0};
private hoveredValue: number|undefined = undefined;
private hoveredTs: time|undefined = undefined;
private hoveredTsEnd: time|undefined = undefined;
- constructor(args: NewTrackArgs) {
- super(args);
- }
-
getHeight() {
return MARGIN_TOP + RECT_HEIGHT;
}
getContextMenu(): m.Vnode<any> {
- const currentScale = this.config.scale;
+ const currentScale = this.store.state.scale;
const scales: {name: CounterScaleOptions, humanName: string}[] = [
{name: 'ZERO_BASED', humanName: 'Zero based'},
{name: 'MIN_MAX', humanName: 'Min/Max'},
@@ -258,10 +331,8 @@
label: scale.humanName,
active: currentScale === scale.name,
onclick: () => {
- this.config.scale = scale.name;
- Actions.updateTrackConfig({
- id: this.trackState.id,
- config: this.config,
+ this.store.edit((draft) => {
+ draft.scale = scale.name;
});
},
});
@@ -282,7 +353,7 @@
visibleTimeScale: timeScale,
windowSpan,
} = globals.frontendLocalState;
- const data = this.data();
+ const data = this.data;
// Can't possibly draw anything.
if (data === undefined || data.timestamps.length === 0) {
@@ -295,7 +366,7 @@
assertTrue(data.timestamps.length === data.totalDeltas.length);
assertTrue(data.timestamps.length === data.rate.length);
- const scale: CounterScaleOptions = this.config.scale || 'ZERO_BASED';
+ const scale: CounterScaleOptions = this.store.state.scale;
let minValues = data.minValues;
let maxValues = data.maxValues;
@@ -485,17 +556,17 @@
}
onMouseMove(pos: {x: number, y: number}) {
- const data = this.data();
+ const data = this.data;
if (data === undefined) return;
this.mousePos = pos;
const {visibleTimeScale} = globals.frontendLocalState;
const time = visibleTimeScale.pxToHpTime(pos.x);
let values = data.lastValues;
- if (this.config.scale === 'DELTA_FROM_PREVIOUS') {
+ if (this.store.state.scale === 'DELTA_FROM_PREVIOUS') {
values = data.totalDeltas;
}
- if (this.config.scale === 'RATE') {
+ if (this.store.state.scale === 'RATE') {
values = data.rate;
}
@@ -513,7 +584,7 @@
}
onMouseClick({x}: {x: number}): boolean {
- const data = this.data();
+ const data = this.data;
if (data === undefined) return false;
const {visibleTimeScale} = globals.frontendLocalState;
const time = visibleTimeScale.pxToHpTime(x);
@@ -527,21 +598,59 @@
leftTs: Time.fromRaw(data.timestamps[left]),
rightTs: Time.fromRaw(right !== -1 ? data.timestamps[right] : -1n),
id: counterId,
- trackId: this.trackState.id,
+ trackId: this.id,
}));
return true;
}
}
+
+ async onDestroy(): Promise<void> {
+ await this.engine.query(
+ `DROP VIEW IF EXISTS ${this.tableName('counter_view')}`);
+ }
+}
+
+interface CounterInfo {
+ name: string;
+ trackId: number;
}
class CounterPlugin implements Plugin {
- onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(CounterTrackController);
- ctx.registerTrack(CounterTrack);
+ onActivate(_ctx: PluginContext): void {}
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ await this.addCounterTracks(ctx);
+ await this.addGpuFrequencyTracks(ctx);
+ await this.addCpuFreqLimitCounterTracks(ctx);
+ await this.addCpuPerfCounterTracks(ctx);
+ await this.addThreadCounterTracks(ctx);
+ await this.addProcessCounterTracks(ctx);
}
- async findPotentialTracks({engine}: TracePluginContext):
- Promise<TrackInfo[]> {
+ private async addCounterTracks(ctx: PluginContextTrace) {
+ const counters = await this.getCounterNames(ctx.engine);
+ for (const {trackId, name} of counters) {
+ const config:
+ Config = {name, trackId, defaultScale: getCounterScale(name)};
+ const uri = `perfetto.Counter#${trackId}`;
+ ctx.addTrack({
+ uri,
+ displayName: name,
+ kind: COUNTER_TRACK_KIND,
+ trackIds: [trackId],
+ track: (trackCtx) => {
+ return new CounterTrack(trackCtx, config, ctx.engine);
+ },
+ });
+ ctx.suggestTrack({
+ uri,
+ name,
+ sortKey: PrimaryTrackSortKey.COUNTER_TRACK,
+ });
+ }
+ }
+
+ private async getCounterNames(engine: EngineProxy): Promise<CounterInfo[]> {
const result = await engine.query(`
select name, id
from (
@@ -562,24 +671,237 @@
id: NUM,
});
- const tracks: TrackInfo[] = [];
+ const tracks: CounterInfo[] = [];
for (; it.valid(); it.next()) {
- const name = it.name;
- const trackId = it.id;
tracks.push({
- trackKind: COUNTER_TRACK_KIND,
- name,
- config: {
- name,
- trackId,
- },
+ trackId: it.id,
+ name: it.name,
});
}
return tracks;
}
+
+ private async addGpuFrequencyTracks(ctx: PluginContextTrace) {
+ const engine = ctx.engine;
+ const numGpus = await engine.getNumberOfGpus();
+ const maxGpuFreqResult = await engine.query(`
+ select ifnull(max(value), 0) as maximumValue
+ from counter c
+ inner join gpu_counter_track t on c.track_id = t.id
+ where name = 'gpufreq';
+ `);
+ const maximumValue =
+ maxGpuFreqResult.firstRow({maximumValue: NUM}).maximumValue;
+
+ for (let gpu = 0; gpu < numGpus; gpu++) {
+ // Only add a gpu freq track if we have
+ // gpu freq data.
+ const freqExistsResult = await engine.query(`
+ select id
+ from gpu_counter_track
+ where name = 'gpufreq' and gpu_id = ${gpu}
+ limit 1;
+ `);
+ if (freqExistsResult.numRows() > 0) {
+ const trackId = freqExistsResult.firstRow({id: NUM}).id;
+ const uri = `perfetto.Counter#gpu_freq${gpu}`;
+ const name = `Gpu ${gpu} Frequency`;
+ const config: Config = {
+ name,
+ trackId,
+ maximumValue,
+ defaultScale: getCounterScale(name),
+ };
+ ctx.addTrack({
+ uri,
+ displayName: name,
+ kind: COUNTER_TRACK_KIND,
+ trackIds: [trackId],
+ track: (trackCtx) => {
+ return new CounterTrack(trackCtx, config, ctx.engine);
+ },
+ });
+ }
+ }
+ }
+
+ async addCpuFreqLimitCounterTracks(ctx: PluginContextTrace): Promise<void> {
+ const cpuFreqLimitCounterTracksSql = `
+ select name, id
+ from cpu_counter_track
+ where name glob "Cpu * Freq Limit"
+ order by name asc
+ `;
+
+ this.addCpuCounterTracks(ctx, cpuFreqLimitCounterTracksSql);
+ }
+
+ async addCpuPerfCounterTracks(ctx: PluginContextTrace): Promise<void> {
+ // Perf counter tracks are bound to CPUs, follow the scheduling and
+ // frequency track naming convention ("Cpu N ...").
+ // Note: we might not have a track for a given cpu if no data was seen from
+ // it. This might look surprising in the UI, but placeholder tracks are
+ // wasteful as there's no way of collapsing global counter tracks at the
+ // moment.
+ const addCpuPerfCounterTracksSql = `
+ select printf("Cpu %u %s", cpu, name) as name, id
+ from perf_counter_track as pct
+ order by perf_session_id asc, pct.name asc, cpu asc
+ `;
+ this.addCpuCounterTracks(ctx, addCpuPerfCounterTracksSql);
+ }
+
+ async addCpuCounterTracks(ctx: PluginContextTrace, sql: string):
+ Promise<void> {
+ const result = await ctx.engine.query(sql);
+
+ const it = result.iter({
+ name: STR,
+ id: NUM,
+ });
+
+ for (; it.valid(); it.next()) {
+ const name = it.name;
+ const trackId = it.id;
+ const config: Config = {
+ name,
+ trackId,
+ defaultScale: getCounterScale(name),
+ };
+ ctx.addTrack({
+ uri: `perfetto.Counter#cpu${trackId}`,
+ displayName: name,
+ kind: COUNTER_TRACK_KIND,
+ trackIds: [trackId],
+ track: (trackCtx) => {
+ return new CounterTrack(trackCtx, config, ctx.engine);
+ },
+ });
+ }
+ }
+
+ async addThreadCounterTracks(ctx: PluginContextTrace): Promise<void> {
+ const result = await ctx.engine.query(`
+ select
+ thread_counter_track.name as trackName,
+ utid,
+ upid,
+ tid,
+ thread.name as threadName,
+ thread_counter_track.id as trackId,
+ thread.start_ts as startTs,
+ thread.end_ts as endTs
+ from thread_counter_track
+ join thread using(utid)
+ left join process using(upid)
+ where thread_counter_track.name != 'thread_time'
+ `);
+
+ const it = result.iter({
+ startTs: LONG_NULL,
+ trackId: NUM,
+ endTs: LONG_NULL,
+ trackName: STR_NULL,
+ utid: NUM,
+ upid: NUM_NULL,
+ tid: NUM_NULL,
+ threadName: STR_NULL,
+ });
+ for (; it.valid(); it.next()) {
+ const utid = it.utid;
+ const tid = it.tid;
+ const startTs = it.startTs === null ? undefined : it.startTs;
+ const endTs = it.endTs === null ? undefined : it.endTs;
+ const trackId = it.trackId;
+ const trackName = it.trackName;
+ const threadName = it.threadName;
+ const kind = COUNTER_TRACK_KIND;
+ const name = getTrackName({
+ name: trackName,
+ utid,
+ tid,
+ kind,
+ threadName,
+ threadTrack: true,
+ });
+ const config: Config = {
+ name,
+ trackId,
+ startTs: Time.fromRaw(startTs),
+ endTs: Time.fromRaw(endTs),
+ defaultScale: getCounterScale(name),
+ };
+ ctx.addTrack({
+ uri: `perfetto.Counter#thread${trackId}`,
+ displayName: name,
+ kind,
+ trackIds: [trackId],
+ track: (trackCtx) => {
+ return new CounterTrack(trackCtx, config, ctx.engine);
+ },
+ });
+ }
+ }
+
+ async addProcessCounterTracks(ctx: PluginContextTrace): Promise<void> {
+ const result = await ctx.engine.query(`
+ select
+ process_counter_track.id as trackId,
+ process_counter_track.name as trackName,
+ upid,
+ process.pid,
+ process.name as processName,
+ process.start_ts as startTs,
+ process.end_ts as endTs
+ from process_counter_track
+ join process using(upid);
+ `);
+ const it = result.iter({
+ trackId: NUM,
+ trackName: STR_NULL,
+ upid: NUM,
+ startTs: LONG_NULL,
+ endTs: LONG_NULL,
+ pid: NUM_NULL,
+ processName: STR_NULL,
+ });
+ for (let i = 0; it.valid(); ++i, it.next()) {
+ const trackId = it.trackId;
+ const startTs = it.startTs === null ? undefined : it.startTs;
+ const endTs = it.endTs === null ? undefined : it.endTs;
+ const pid = it.pid;
+ const trackName = it.trackName;
+ const upid = it.upid;
+ const processName = it.processName;
+ const kind = COUNTER_TRACK_KIND;
+ const name = getTrackName({
+ name: trackName,
+ upid,
+ pid,
+ kind,
+ processName,
+ });
+ const config: Config = {
+ name,
+ trackId,
+ startTs: Time.fromRaw(startTs),
+ endTs: Time.fromRaw(endTs),
+ defaultScale: getCounterScale(name),
+ };
+ ctx.addTrack({
+ uri: `perfetto.Counter#process${trackId}`,
+ displayName: name,
+ kind: COUNTER_TRACK_KIND,
+ trackIds: [trackId],
+ track: (trackCtx) => {
+ return new CounterTrack(trackCtx, config, ctx.engine);
+ },
+ });
+ }
+ }
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.Counter',
plugin: CounterPlugin,
};
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index baa24f9..35faefc 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -26,12 +26,21 @@
NUM_NULL,
QueryResult,
} from '../../common/query_result';
+import {
+ TrackAdapter,
+ TrackControllerAdapter,
+ TrackWithControllerAdapter,
+} from '../../common/track_adapter';
import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+} from '../../public';
export const CPU_FREQ_TRACK_KIND = 'CpuFreqTrack';
@@ -55,9 +64,7 @@
minimumValue?: number;
}
-class CpuFreqTrackController extends TrackController<Config, Data> {
- static readonly kind = CPU_FREQ_TRACK_KIND;
-
+class CpuFreqTrackController extends TrackControllerAdapter<Config, Data> {
private maxDur: duration = 0n;
private maxTsEnd: time = Time.ZERO;
private maximumValueSeen = 0;
@@ -266,8 +273,7 @@
const MARGIN_TOP = 4.5;
const RECT_HEIGHT = 20;
-class CpuFreqTrack extends Track<Config, Data> {
- static readonly kind = CPU_FREQ_TRACK_KIND;
+class CpuFreqTrack extends TrackAdapter<Config, Data> {
static create(args: NewTrackArgs): CpuFreqTrack {
return new CpuFreqTrack(args);
}
@@ -484,13 +490,74 @@
}
class CpuFreq implements Plugin {
- onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(CpuFreqTrackController);
- ctx.registerTrack(CpuFreqTrack);
+ onActivate(_ctx: PluginContext): void {}
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ const {engine} = ctx;
+
+ const cpus = await engine.getCpus();
+
+ const maxCpuFreqResult = await engine.query(`
+ select ifnull(max(value), 0) as freq
+ from counter c
+ inner join cpu_counter_track t on c.track_id = t.id
+ where name = 'cpufreq';
+ `);
+ const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq;
+
+ for (const cpu of cpus) {
+ // Only add a cpu freq track if we have
+ // cpu freq data.
+ // TODO(hjd): Find a way to display cpu idle
+ // events even if there are no cpu freq events.
+ const cpuFreqIdleResult = await engine.query(`
+ select
+ id as cpuFreqId,
+ (
+ select id
+ from cpu_counter_track
+ where name = 'cpuidle'
+ and cpu = ${cpu}
+ limit 1
+ ) as cpuIdleId
+ from cpu_counter_track
+ where name = 'cpufreq' and cpu = ${cpu}
+ limit 1;
+ `);
+
+ if (cpuFreqIdleResult.numRows() > 0) {
+ const row = cpuFreqIdleResult.firstRow({
+ cpuFreqId: NUM,
+ cpuIdleId: NUM_NULL,
+ });
+ const freqTrackId = row.cpuFreqId;
+ const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId;
+
+ ctx.addTrack({
+ uri: `perfetto.CpuFreq#${cpu}`,
+ displayName: `Cpu ${cpu} Frequency`,
+ kind: CPU_FREQ_TRACK_KIND,
+ cpu,
+ track: ({trackInstanceId}) => {
+ return new TrackWithControllerAdapter<Config, Data>(
+ engine,
+ trackInstanceId,
+ {
+ cpu,
+ maximumValue: maxCpuFreq,
+ freqTrackId,
+ idleTrackId,
+ },
+ CpuFreqTrack,
+ CpuFreqTrackController);
+ },
+ });
+ }
+ }
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.CpuFreq',
plugin: CpuFreq,
};
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index 7045ac0..616e9a2 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -25,8 +25,8 @@
import {globals} from '../../frontend/globals';
import {cachedHsluvToHex} from '../../frontend/hsluv_cache';
import {TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
const BAR_HEIGHT = 3;
const MARGIN_TOP = 4.5;
@@ -85,7 +85,7 @@
return cachedHsluvToHex(hue, saturation, lightness);
}
-class CpuProfileTrack extends Track<Config, Data> {
+class CpuProfileTrack extends TrackBase<Config, Data> {
static readonly kind = CPU_PROFILE_TRACK_KIND;
static create(args: NewTrackArgs): CpuProfileTrack {
return new CpuProfileTrack(args);
@@ -246,12 +246,12 @@
class CpuProfile implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(CpuProfileTrackController);
- ctx.registerTrack(CpuProfileTrack);
+ ctx.LEGACY_registerTrackController(CpuProfileTrackController);
+ ctx.LEGACY_registerTrack(CpuProfileTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.CpuProfile',
plugin: CpuProfile,
};
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 6694ccd..c2de3f6 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -39,8 +39,8 @@
EngineProxy,
Plugin,
PluginContext,
- PluginInfo,
- TracePluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
} from '../../public';
export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
@@ -472,7 +472,7 @@
// No-op
}
- async onTraceLoad(ctx: TracePluginContext): Promise<void> {
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const cpus = await ctx.engine.getCpus();
const cpuToSize = await this.guessCpuSizes(ctx.engine);
@@ -484,11 +484,9 @@
ctx.addTrack({
uri,
displayName: name,
- tags: {
- cpu,
- kind: CPU_SLICE_TRACK_KIND,
- },
- trackFactory: ({trackInstanceId}) => {
+ kind: CPU_SLICE_TRACK_KIND,
+ cpu,
+ track: ({trackInstanceId}) => {
return new TrackWithControllerAdapter<Config, Data>(
ctx.engine,
trackInstanceId,
@@ -525,7 +523,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.CpuSlices',
plugin: CpuSlices,
};
diff --git a/ui/src/tracks/custom_sql_table_slices/index.ts b/ui/src/tracks/custom_sql_table_slices/index.ts
index e371bff..8ffe388 100644
--- a/ui/src/tracks/custom_sql_table_slices/index.ts
+++ b/ui/src/tracks/custom_sql_table_slices/index.ts
@@ -27,7 +27,7 @@
NamedSliceTrackTypes,
} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export interface CustomSqlTableDefConfig {
// Table name
@@ -109,7 +109,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.CustomSqlTrack',
plugin: CustomSqlTrackPlugin,
};
diff --git a/ui/src/tracks/debug/index.ts b/ui/src/tracks/debug/index.ts
index b3195e1..78c12e7 100644
--- a/ui/src/tracks/debug/index.ts
+++ b/ui/src/tracks/debug/index.ts
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
import {DebugTrackV2} from './slice_track';
class DebugTrackPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(DebugTrackV2);
+ ctx.LEGACY_registerTrack(DebugTrackV2);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.DebugSlices',
plugin: DebugTrackPlugin,
};
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/expected_frames/index.ts
index dce354c..b30d31e 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -16,7 +16,7 @@
export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
-import {NewTrackArgs, Track} from '../../frontend/track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
import {ChromeSliceTrack} from '../chrome_slices';
import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
@@ -24,7 +24,7 @@
import {
TrackController,
} from '../../controller/track_controller';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
import {BigintMath as BIMath} from '../../base/bigint_math';
export interface Config {
@@ -142,19 +142,19 @@
export class ExpectedFramesSliceTrack extends ChromeSliceTrack {
static readonly kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new ExpectedFramesSliceTrack(args);
}
}
class ExpectedFramesPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(ExpectedFramesSliceTrackController);
- ctx.registerTrack(ExpectedFramesSliceTrack);
+ ctx.LEGACY_registerTrackController(ExpectedFramesSliceTrackController);
+ ctx.LEGACY_registerTrack(ExpectedFramesSliceTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ExpectedFrames',
plugin: ExpectedFramesPlugin,
};
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index 124696e..f2ad696 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -23,10 +23,11 @@
EngineProxy,
Plugin,
PluginContext,
- PluginInfo,
- TracePluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
} from '../../public';
+export const FTRACE_RAW_TRACK_KIND = 'FtraceRawTrack';
export interface Data extends TrackData {
timestamps: BigInt64Array;
@@ -138,7 +139,7 @@
class FtraceRawPlugin implements Plugin {
onActivate(_: PluginContext) {}
- async onTraceLoad(ctx: TracePluginContext): Promise<void> {
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const cpus = await this.lookupCpuCores(ctx.engine);
for (const cpuNum of cpus) {
const uri = `perfetto.FtraceRaw#cpu${cpuNum}`;
@@ -146,7 +147,9 @@
ctx.addTrack({
uri,
displayName: `Ftrace Track for CPU ${cpuNum}`,
- trackFactory: () => {
+ kind: FTRACE_RAW_TRACK_KIND,
+ cpu: cpuNum,
+ track: () => {
return new FtraceRawTrack(ctx.engine, cpuNum);
},
});
@@ -169,7 +172,7 @@
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.FtraceRaw',
plugin: FtraceRawPlugin,
};
diff --git a/ui/src/tracks/generic_slice_track/index.ts b/ui/src/tracks/generic_slice_track/index.ts
index c467254..6053621 100644
--- a/ui/src/tracks/generic_slice_track/index.ts
+++ b/ui/src/tracks/generic_slice_track/index.ts
@@ -17,7 +17,7 @@
NamedSliceTrackTypes,
} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export interface GenericSliceTrackConfig {
sqlTrackId: number;
@@ -47,11 +47,11 @@
class GenericSliceTrackPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(GenericSliceTrack);
+ ctx.LEGACY_registerTrack(GenericSliceTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.GenericSliceTrack',
plugin: GenericSliceTrackPlugin,
};
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index 08018ce..38b19e7 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -23,8 +23,8 @@
import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
import {globals} from '../../frontend/globals';
import {TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
@@ -88,7 +88,7 @@
const MARGIN_TOP = 4.5;
const RECT_HEIGHT = 30.5;
-class HeapProfileTrack extends Track<Config, Data> {
+class HeapProfileTrack extends TrackBase<Config, Data> {
static readonly kind = HEAP_PROFILE_TRACK_KIND;
static create(args: NewTrackArgs): HeapProfileTrack {
return new HeapProfileTrack(args);
@@ -217,12 +217,12 @@
class HeapProfilePlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(HeapProfileTrackController);
- ctx.registerTrack(HeapProfileTrack);
+ ctx.LEGACY_registerTrackController(HeapProfileTrackController);
+ ctx.LEGACY_registerTrack(HeapProfileTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.HeapProfile',
plugin: HeapProfilePlugin,
};
diff --git a/ui/src/tracks/null_track/index.ts b/ui/src/tracks/null_track/index.ts
index 1ff53a4..a007185 100644
--- a/ui/src/tracks/null_track/index.ts
+++ b/ui/src/tracks/null_track/index.ts
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export const NULL_TRACK_KIND = 'NullTrack';
-export class NullTrack extends Track {
+export class NullTrack extends TrackBase {
static readonly kind = NULL_TRACK_KIND;
constructor(args: NewTrackArgs) {
super(args);
@@ -37,11 +37,11 @@
class NullTrackPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(NullTrack);
+ ctx.LEGACY_registerTrack(NullTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.NullTrack',
plugin: NullTrackPlugin,
};
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index ff836ad..b6e624a 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -22,8 +22,8 @@
import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
import {globals} from '../../frontend/globals';
import {TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export const PERF_SAMPLES_PROFILE_TRACK_KIND = 'PerfSamplesProfileTrack';
@@ -77,7 +77,7 @@
const MARGIN_TOP = 4.5;
const RECT_HEIGHT = 30.5;
-class PerfSamplesProfileTrack extends Track<Config, Data> {
+class PerfSamplesProfileTrack extends TrackBase<Config, Data> {
static readonly kind = PERF_SAMPLES_PROFILE_TRACK_KIND;
static create(args: NewTrackArgs): PerfSamplesProfileTrack {
return new PerfSamplesProfileTrack(args);
@@ -210,12 +210,12 @@
class PerfSamplesProfilePlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(PerfSamplesProfileTrackController);
- ctx.registerTrack(PerfSamplesProfileTrack);
+ ctx.LEGACY_registerTrackController(PerfSamplesProfileTrackController);
+ ctx.LEGACY_registerTrack(PerfSamplesProfileTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.PerfSamplesProfile',
plugin: PerfSamplesProfilePlugin,
};
diff --git a/ui/src/tracks/process_summary/index.ts b/ui/src/tracks/process_summary/index.ts
index ea795a4..24cae70 100644
--- a/ui/src/tracks/process_summary/index.ts
+++ b/ui/src/tracks/process_summary/index.ts
@@ -12,216 +12,341 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath} from '../../base/bigint_math';
-import {assertFalse} from '../../base/logging';
-import {duration, Time, time} from '../../base/time';
-import {colorForTid} from '../../common/colorizer';
-import {NUM} from '../../common/query_result';
-import {LIMIT, TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
-import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {v4 as uuidv4} from 'uuid';
-export const PROCESS_SUMMARY_TRACK = 'ProcessSummaryTrack';
+import {
+ NUM,
+ NUM_NULL,
+ STR,
+ STR_NULL,
+} from '../../common/query_result';
+import {TrackWithControllerAdapter} from '../../common/track_adapter';
+import {
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+} from '../../public';
-// TODO(dproy): Consider deduping with CPU summary data.
-export interface Data extends TrackData {
- bucketSize: duration;
- utilizations: Float64Array;
-}
+import {
+ Config as ProcessSchedulingTrackConfig,
+ Data as ProcessSchedulingTrackData,
+ PROCESS_SCHEDULING_TRACK_KIND,
+ ProcessSchedulingTrack,
+ ProcessSchedulingTrackController,
+} from './process_scheduling_track';
+import {
+ Config as ProcessSummaryTrackConfig,
+ Data as ProcessSummaryTrackData,
+ PROCESS_SUMMARY_TRACK,
+ ProcessSummaryTrack,
+ ProcessSummaryTrackController,
+} from './process_summary_track';
-export interface Config {
- pidForColor: number;
- upid: number|null;
- utid: number;
-}
+// This plugin now manages both process "scheduling" and "summary" tracks.
+class ProcessSummaryPlugin implements Plugin {
+ private upidToUuid = new Map<number, string>();
+ private utidToUuid = new Map<number, string>();
-// This is the summary displayed when a process only contains chrome slices
-// and no cpu scheduling.
-class ProcessSummaryTrackController extends TrackController<Config, Data> {
- static readonly kind = PROCESS_SUMMARY_TRACK;
- private setup = false;
+ onActivate(_ctx: PluginContext): void {}
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<Data> {
- assertFalse(resolution === 0n, 'Resolution cannot be 0');
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ await this.addProcessTrackGroups(ctx);
+ await this.addKernelThreadSummary(ctx);
+ }
- if (this.setup === false) {
- await this.query(
- `create virtual table ${this.tableName('window')} using window;`);
+ private async addProcessTrackGroups(ctx: PluginContextTrace): Promise<void> {
+ this.upidToUuid.clear();
+ this.utidToUuid.clear();
- let utids = [this.config.utid];
- if (this.config.upid) {
- const threadQuery = await this.query(
- `select utid from thread where upid=${this.config.upid}`);
- utids = [];
- for (const it = threadQuery.iter({utid: NUM}); it.valid(); it.next()) {
- utids.push(it.utid);
+ // We want to create groups of tracks in a specific order.
+ // The tracks should be grouped:
+ // by upid
+ // or (if upid is null) by utid
+ // the groups should be sorted by:
+ // Chrome-based process rank based on process names (e.g. Browser)
+ // has a heap profile or not
+ // total cpu time *for the whole parent process*
+ // process name
+ // upid
+ // thread name
+ // utid
+ const result = await ctx.engine.query(`
+ select
+ the_tracks.upid,
+ the_tracks.utid,
+ total_dur as hasSched,
+ hasHeapProfiles,
+ process.pid as pid,
+ thread.tid as tid,
+ process.name as processName,
+ thread.name as threadName,
+ package_list.debuggable as isDebuggable,
+ ifnull((
+ select group_concat(string_value)
+ from args
+ where
+ process.arg_set_id is not null and
+ arg_set_id = process.arg_set_id and
+ flat_key = 'chrome.process_label'
+ ), '') AS chromeProcessLabels,
+ (case process.name
+ when 'Browser' then 3
+ when 'Gpu' then 2
+ when 'Renderer' then 1
+ else 0
+ end) as chromeProcessRank
+ from (
+ select upid, 0 as utid from process_track
+ union
+ select upid, 0 as utid from process_counter_track
+ union
+ select upid, utid from thread_counter_track join thread using(utid)
+ union
+ select upid, utid from thread_track join thread using(utid)
+ union
+ select upid, utid from sched join thread using(utid) group by utid
+ union
+ select upid, 0 as utid from (
+ select distinct upid
+ from perf_sample join thread using (utid) join process using (upid)
+ where callsite_id is not null)
+ union
+ select upid, utid from (
+ select distinct(utid) from cpu_profile_stack_sample
+ ) join thread using(utid)
+ union
+ select distinct(upid) as upid, 0 as utid from heap_profile_allocation
+ union
+ select distinct(upid) as upid, 0 as utid from heap_graph_object
+ ) the_tracks
+ left join (
+ select upid, sum(thread_total_dur) as total_dur
+ from (
+ select utid, sum(dur) as thread_total_dur
+ from sched where dur != -1 and utid != 0
+ group by utid
+ )
+ join thread using (utid)
+ group by upid
+ ) using(upid)
+ left join (
+ select
+ distinct(upid) as upid,
+ true as hasHeapProfiles
+ from heap_profile_allocation
+ union
+ select
+ distinct(upid) as upid,
+ true as hasHeapProfiles
+ from heap_graph_object
+ ) using (upid)
+ left join (
+ select
+ thread.upid as upid,
+ sum(cnt) as perfSampleCount
+ from (
+ select utid, count(*) as cnt
+ from perf_sample where callsite_id is not null
+ group by utid
+ ) join thread using (utid)
+ group by thread.upid
+ ) using (upid)
+ left join (
+ select
+ process.upid as upid,
+ sum(cnt) as sliceCount
+ from (select track_id, count(*) as cnt from slice group by track_id)
+ left join thread_track on track_id = thread_track.id
+ left join thread on thread_track.utid = thread.utid
+ left join process_track on track_id = process_track.id
+ join process on process.upid = thread.upid
+ or process_track.upid = process.upid
+ where process.upid is not null
+ group by process.upid
+ ) using (upid)
+ left join thread using(utid)
+ left join process using(upid)
+ left join package_list using(uid)
+ order by
+ chromeProcessRank desc,
+ hasHeapProfiles desc,
+ perfSampleCount desc,
+ total_dur desc,
+ sliceCount desc,
+ processName asc nulls last,
+ the_tracks.upid asc nulls last,
+ threadName asc nulls last,
+ the_tracks.utid asc nulls last;
+ `);
+
+ const it = result.iter({
+ utid: NUM,
+ upid: NUM_NULL,
+ tid: NUM_NULL,
+ pid: NUM_NULL,
+ threadName: STR_NULL,
+ processName: STR_NULL,
+ hasSched: NUM_NULL,
+ hasHeapProfiles: NUM_NULL,
+ isDebuggable: NUM_NULL,
+ chromeProcessLabels: STR,
+ });
+ for (; it.valid(); it.next()) {
+ const utid = it.utid;
+ const tid = it.tid;
+ const upid = it.upid;
+ const pid = it.pid;
+ const hasSched = !!it.hasSched;
+ const isDebuggable = !!it.isDebuggable;
+
+ // Group by upid if present else by utid.
+ let pUuid =
+ upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid);
+ // These should only happen once for each track group.
+ if (pUuid === undefined) {
+ pUuid = this.getOrCreateUuid(utid, upid);
+ const pidForColor = pid || tid || upid || utid || 0;
+ const type = hasSched ? 'schedule' : 'summary';
+ const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
+
+ if (hasSched) {
+ const config: ProcessSchedulingTrackConfig = {
+ pidForColor,
+ upid,
+ utid,
+ };
+
+ ctx.addTrack({
+ uri,
+ displayName: `${upid === null ? tid : pid} schedule`,
+ kind: PROCESS_SCHEDULING_TRACK_KIND,
+ tags: {
+ isDebuggable,
+ },
+ track: ({trackInstanceId}) => {
+ return new TrackWithControllerAdapter<
+ ProcessSchedulingTrackConfig,
+ ProcessSchedulingTrackData>(
+ ctx.engine,
+ trackInstanceId,
+ config,
+ ProcessSchedulingTrack,
+ ProcessSchedulingTrackController);
+ },
+ });
+ } else {
+ const config: ProcessSummaryTrackConfig = {
+ pidForColor,
+ upid,
+ utid,
+ };
+
+ ctx.addTrack({
+ uri,
+ displayName: `${upid === null ? tid : pid} summary`,
+ kind: PROCESS_SUMMARY_TRACK,
+ tags: {
+ isDebuggable,
+ },
+ track: ({trackInstanceId}) => {
+ return new TrackWithControllerAdapter<
+ ProcessSummaryTrackConfig,
+ ProcessSummaryTrackData>(
+ ctx.engine,
+ trackInstanceId,
+ config,
+ ProcessSummaryTrack,
+ ProcessSummaryTrackController);
+ },
+ });
}
}
-
- const trackQuery = await this.query(
- `select id from thread_track where utid in (${utids.join(',')})`);
- const tracks = [];
- for (const it = trackQuery.iter({id: NUM}); it.valid(); it.next()) {
- tracks.push(it.id);
- }
-
- const processSliceView = this.tableName('process_slice_view');
- await this.query(
- `create view ${processSliceView} as ` +
- // 0 as cpu is a dummy column to perform span join on.
- `select ts, dur/${utids.length} as dur ` +
- `from slice s ` +
- `where depth = 0 and track_id in ` +
- `(${tracks.join(',')})`);
- await this.query(`create virtual table ${this.tableName('span')}
- using span_join(${processSliceView},
- ${this.tableName('window')});`);
- this.setup = true;
}
-
- // |resolution| is in ns/px we want # ns for 10px window:
- // Max value with 1 so we don't end up with resolution 0.
- const bucketSize = resolution * 10n;
- const windowStart = Time.quant(start, bucketSize);
- const windowDur = BigintMath.max(1n, end - windowStart);
-
- await this.query(`update ${this.tableName('window')} set
- window_start=${windowStart},
- window_dur=${windowDur},
- quantum=${bucketSize}
- where rowid = 0;`);
-
- return this.computeSummary(windowStart, end, resolution, bucketSize);
}
- private async computeSummary(
- start: time, end: time, resolution: duration,
- bucketSize: duration): Promise<Data> {
- const duration = end - start;
- const numBuckets = Math.min(Number(duration / bucketSize), LIMIT);
+ private async addKernelThreadSummary(ctx: PluginContextTrace): Promise<void> {
+ const {engine} = ctx;
- const query = `select
- quantum_ts as bucket,
- sum(dur)/cast(${bucketSize} as float) as utilization
- from ${this.tableName('span')}
- group by quantum_ts
- limit ${LIMIT}`;
+ // Identify kernel threads if this is a linux system trace, and sufficient
+ // process information is available. Kernel threads are identified by being
+ // children of kthreadd (always pid 2).
+ // The query will return the kthreadd process row first, which must exist
+ // for any other kthreads to be returned by the query.
+ // TODO(rsavitski): figure out how to handle the idle process (swapper),
+ // which has pid 0 but appears as a distinct process (with its own comm) on
+ // each cpu. It'd make sense to exclude its thread state track, but still
+ // put process-scoped tracks in this group.
+ const result = await engine.query(`
+ select
+ t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd
+ from
+ thread t
+ join process p using (upid)
+ left join process parent on (p.parent_upid = parent.upid)
+ join
+ (select true from metadata m
+ where (m.name = 'system_name' and m.str_value = 'Linux')
+ union
+ select 1 from (select true from sched limit 1))
+ where
+ p.pid = 2 or parent.pid = 2
+ order by isKthreadd desc
+ `);
- const summary: Data = {
- start,
- end,
- resolution,
- length: numBuckets,
- bucketSize,
- utilizations: new Float64Array(numBuckets),
+ const it = result.iter({
+ utid: NUM,
+ upid: NUM,
+ });
+
+ // Not applying kernel thread grouping.
+ if (!it.valid()) {
+ return;
+ }
+
+ const config: ProcessSummaryTrackConfig = {
+ pidForColor: 2,
+ upid: it.upid,
+ utid: it.utid,
};
- const queryRes = await this.query(query);
- const it = queryRes.iter({bucket: NUM, utilization: NUM});
- for (; it.valid(); it.next()) {
- const bucket = it.bucket;
- if (bucket > numBuckets) {
- continue;
+ ctx.addTrack({
+ uri: 'perfetto.ProcessSummary#kernel',
+ displayName: `Kernel thread summary`,
+ kind: PROCESS_SUMMARY_TRACK,
+ track: ({trackInstanceId}) => {
+ return new TrackWithControllerAdapter<
+ ProcessSummaryTrackConfig,
+ ProcessSummaryTrackData>(
+ ctx.engine,
+ trackInstanceId,
+ config,
+ ProcessSummaryTrack,
+ ProcessSummaryTrackController);
+ },
+ });
+ }
+
+ private getOrCreateUuid(utid: number, upid: number|null) {
+ let uuid = this.getUuidUnchecked(utid, upid);
+ if (uuid === undefined) {
+ uuid = uuidv4();
+ if (upid === null) {
+ this.utidToUuid.set(utid, uuid);
+ } else {
+ this.upidToUuid.set(upid, uuid);
}
- summary.utilizations[bucket] = it.utilization;
}
-
- return summary;
+ return uuid;
}
- onDestroy(): void {
- if (this.setup) {
- this.query(`drop table ${this.tableName('window')}`);
- this.query(`drop table ${this.tableName('span')}`);
- this.setup = false;
- }
+ getUuidUnchecked(utid: number, upid: number|null) {
+ return upid === null ? this.utidToUuid.get(utid) :
+ this.upidToUuid.get(upid);
}
}
-const MARGIN_TOP = 5;
-const RECT_HEIGHT = 30;
-const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
-const SUMMARY_HEIGHT = TRACK_HEIGHT - MARGIN_TOP;
-
-class ProcessSummaryTrack extends Track<Config, Data> {
- static readonly kind = PROCESS_SUMMARY_TRACK;
- static create(args: NewTrackArgs): ProcessSummaryTrack {
- return new ProcessSummaryTrack(args);
- }
-
- constructor(args: NewTrackArgs) {
- super(args);
- }
-
- getHeight(): number {
- return TRACK_HEIGHT;
- }
-
- renderCanvas(ctx: CanvasRenderingContext2D): void {
- const {
- visibleTimeScale,
- windowSpan,
- } = globals.frontendLocalState;
- const data = this.data();
- if (data === undefined) return; // Can't possibly draw anything.
-
- checkerboardExcept(
- ctx,
- this.getHeight(),
- windowSpan.start,
- windowSpan.end,
- visibleTimeScale.timeToPx(data.start),
- visibleTimeScale.timeToPx(data.end));
-
- this.renderSummary(ctx, data);
- }
-
- // TODO(dproy): Dedup with CPU slices.
- renderSummary(ctx: CanvasRenderingContext2D, data: Data): void {
- const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
- const startPx = windowSpan.start;
- const bottomY = TRACK_HEIGHT;
-
- let lastX = startPx;
- let lastY = bottomY;
-
- // TODO(hjd): Dedupe this math.
- const color = colorForTid(this.config.pidForColor);
- color.l = Math.min(color.l + 10, 60);
- color.s -= 20;
-
- ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
- ctx.beginPath();
- ctx.moveTo(lastX, lastY);
- for (let i = 0; i < data.utilizations.length; i++) {
- // TODO(dproy): Investigate why utilization is > 1 sometimes.
- const utilization = Math.min(data.utilizations[i], 1);
- const startTime = Time.fromRaw(BigInt(i) * data.bucketSize + data.start);
-
- lastX = Math.floor(visibleTimeScale.timeToPx(startTime));
-
- ctx.lineTo(lastX, lastY);
- lastY = MARGIN_TOP + Math.round(SUMMARY_HEIGHT * (1 - utilization));
- ctx.lineTo(lastX, lastY);
- }
- ctx.lineTo(lastX, bottomY);
- ctx.closePath();
- ctx.fill();
- }
-}
-
-class ProcessSummaryPlugin implements Plugin {
- onActivate(ctx: PluginContext): void {
- ctx.registerTrack(ProcessSummaryTrack);
- ctx.registerTrackController(ProcessSummaryTrackController);
- }
-}
-
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ProcessSummary',
plugin: ProcessSummaryPlugin,
};
diff --git a/ui/src/tracks/process_scheduling/index.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
similarity index 91%
rename from ui/src/tracks/process_scheduling/index.ts
rename to ui/src/tracks/process_summary/process_scheduling_track.ts
index 631998c..8759260 100644
--- a/ui/src/tracks/process_scheduling/index.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 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.
@@ -20,16 +20,26 @@
import {calcCachedBucketSize} from '../../common/cache_utils';
import {drawTrackHoverTooltip} from '../../common/canvas_utils';
import {colorForThread} from '../../common/colorizer';
-import {LONG, NUM, QueryResult} from '../../common/query_result';
+import {
+ LONG,
+ NUM,
+ QueryResult,
+} from '../../common/query_result';
+import {
+ TrackAdapter,
+ TrackControllerAdapter,
+} from '../../common/track_adapter';
import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
export const PROCESS_SCHEDULING_TRACK_KIND = 'ProcessSchedulingTrack';
+const MARGIN_TOP = 5;
+const RECT_HEIGHT = 30;
+const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
+
export interface Data extends TrackData {
kind: 'slice';
maxCpu: number;
@@ -49,9 +59,8 @@
// This summary is displayed for any processes that have CPU scheduling activity
// associated with them.
-class ProcessSchedulingTrackController extends TrackController<Config, Data> {
- static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
-
+export class ProcessSchedulingTrackController extends
+ TrackControllerAdapter<Config, Data> {
private maxCpu = 0;
private maxDur = 0n;
private cachedBucketSize = BIMath.INT64_MAX;
@@ -178,12 +187,7 @@
}
}
-const MARGIN_TOP = 5;
-const RECT_HEIGHT = 30;
-const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
-
-class ProcessSchedulingTrack extends Track<Config, Data> {
- static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
+export class ProcessSchedulingTrack extends TrackAdapter<Config, Data> {
static create(args: NewTrackArgs): ProcessSchedulingTrack {
return new ProcessSchedulingTrack(args);
}
@@ -312,15 +316,3 @@
this.mousePos = undefined;
}
}
-
-class ProcessSchedulingPlugin implements Plugin {
- onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(ProcessSchedulingTrackController);
- ctx.registerTrack(ProcessSchedulingTrack);
- }
-}
-
-export const plugin: PluginInfo = {
- pluginId: 'perfetto.ProcessScheduling',
- plugin: ProcessSchedulingPlugin,
-};
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
new file mode 100644
index 0000000..4641eb0
--- /dev/null
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -0,0 +1,208 @@
+// 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} from '../../base/bigint_math';
+import {assertFalse} from '../../base/logging';
+import {duration, Time, time} from '../../base/time';
+import {colorForTid} from '../../common/colorizer';
+import {NUM} from '../../common/query_result';
+import {TrackAdapter, TrackControllerAdapter} from '../../common/track_adapter';
+import {LIMIT, TrackData} from '../../common/track_data';
+import {checkerboardExcept} from '../../frontend/checkerboard';
+import {globals} from '../../frontend/globals';
+import {NewTrackArgs} from '../../frontend/track';
+
+export const PROCESS_SUMMARY_TRACK = 'ProcessSummaryTrack';
+
+// TODO(dproy): Consider deduping with CPU summary data.
+export interface Data extends TrackData {
+ bucketSize: duration;
+ utilizations: Float64Array;
+}
+
+export interface Config {
+ pidForColor: number;
+ upid: number|null;
+ utid: number;
+}
+
+// This is the summary displayed when a process only contains chrome slices
+// and no cpu scheduling.
+export class ProcessSummaryTrackController extends
+ TrackControllerAdapter<Config, Data> {
+ async onSetup(): Promise<void> {
+ await this.query(
+ `create virtual table ${this.tableName('window')} using window;`);
+
+ let utids = [this.config.utid];
+ if (this.config.upid) {
+ const threadQuery = await this.query(
+ `select utid from thread where upid=${this.config.upid}`);
+ utids = [];
+ for (const it = threadQuery.iter({utid: NUM}); it.valid(); it.next()) {
+ utids.push(it.utid);
+ }
+ }
+
+ const trackQuery = await this.query(
+ `select id from thread_track where utid in (${utids.join(',')})`);
+ const tracks = [];
+ for (const it = trackQuery.iter({id: NUM}); it.valid(); it.next()) {
+ tracks.push(it.id);
+ }
+
+ const processSliceView = this.tableName('process_slice_view');
+ await this.query(
+ `create view ${processSliceView} as ` +
+ // 0 as cpu is a dummy column to perform span join on.
+ `select ts, dur/${utids.length} as dur ` +
+ `from slice s ` +
+ `where depth = 0 and track_id in ` +
+ `(${tracks.join(',')})`);
+ await this.query(`create virtual table ${this.tableName('span')}
+ using span_join(${processSliceView},
+ ${this.tableName('window')});`);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<Data> {
+ assertFalse(resolution === 0n, 'Resolution cannot be 0');
+
+ // |resolution| is in ns/px we want # ns for 10px window:
+ // Max value with 1 so we don't end up with resolution 0.
+ const bucketSize = resolution * 10n;
+ const windowStart = Time.quant(start, bucketSize);
+ const windowDur = BigintMath.max(1n, end - windowStart);
+
+ await this.query(`update ${this.tableName('window')} set
+ window_start=${windowStart},
+ window_dur=${windowDur},
+ quantum=${bucketSize}
+ where rowid = 0;`);
+
+ return this.computeSummary(windowStart, end, resolution, bucketSize);
+ }
+
+ private async computeSummary(
+ start: time, end: time, resolution: duration,
+ bucketSize: duration): Promise<Data> {
+ const duration = end - start;
+ const numBuckets = Math.min(Number(duration / bucketSize), LIMIT);
+
+ const query = `select
+ quantum_ts as bucket,
+ sum(dur)/cast(${bucketSize} as float) as utilization
+ from ${this.tableName('span')}
+ group by quantum_ts
+ limit ${LIMIT}`;
+
+ const summary: Data = {
+ start,
+ end,
+ resolution,
+ length: numBuckets,
+ bucketSize,
+ utilizations: new Float64Array(numBuckets),
+ };
+
+ const queryRes = await this.query(query);
+ const it = queryRes.iter({bucket: NUM, utilization: NUM});
+ for (; it.valid(); it.next()) {
+ const bucket = it.bucket;
+ if (bucket > numBuckets) {
+ continue;
+ }
+ summary.utilizations[bucket] = it.utilization;
+ }
+
+ return summary;
+ }
+
+ async onDestroy(): Promise<void> {
+ await this.query(`drop table if exists ${
+ this.tableName(
+ 'window')}; drop table if exists ${this.tableName('span')}`);
+ }
+}
+
+const MARGIN_TOP = 5;
+const RECT_HEIGHT = 30;
+const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
+const SUMMARY_HEIGHT = TRACK_HEIGHT - MARGIN_TOP;
+
+export class ProcessSummaryTrack extends TrackAdapter<Config, Data> {
+ static create(args: NewTrackArgs): ProcessSummaryTrack {
+ return new ProcessSummaryTrack(args);
+ }
+
+ constructor(args: NewTrackArgs) {
+ super(args);
+ }
+
+ getHeight(): number {
+ return TRACK_HEIGHT;
+ }
+
+ renderCanvas(ctx: CanvasRenderingContext2D): void {
+ const {
+ visibleTimeScale,
+ windowSpan,
+ } = globals.frontendLocalState;
+ const data = this.data();
+ if (data === undefined) return; // Can't possibly draw anything.
+
+ checkerboardExcept(
+ ctx,
+ this.getHeight(),
+ windowSpan.start,
+ windowSpan.end,
+ visibleTimeScale.timeToPx(data.start),
+ visibleTimeScale.timeToPx(data.end));
+
+ this.renderSummary(ctx, data);
+ }
+
+ // TODO(dproy): Dedup with CPU slices.
+ renderSummary(ctx: CanvasRenderingContext2D, data: Data): void {
+ const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
+ const startPx = windowSpan.start;
+ const bottomY = TRACK_HEIGHT;
+
+ let lastX = startPx;
+ let lastY = bottomY;
+
+ // TODO(hjd): Dedupe this math.
+ const color = colorForTid(this.config.pidForColor);
+ color.l = Math.min(color.l + 10, 60);
+ color.s -= 20;
+
+ ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+ ctx.beginPath();
+ ctx.moveTo(lastX, lastY);
+ for (let i = 0; i < data.utilizations.length; i++) {
+ // TODO(dproy): Investigate why utilization is > 1 sometimes.
+ const utilization = Math.min(data.utilizations[i], 1);
+ const startTime = Time.fromRaw(BigInt(i) * data.bucketSize + data.start);
+
+ lastX = Math.floor(visibleTimeScale.timeToPx(startTime));
+
+ ctx.lineTo(lastX, lastY);
+ lastY = MARGIN_TOP + Math.round(SUMMARY_HEIGHT * (1 - utilization));
+ ctx.lineTo(lastX, lastY);
+ }
+ ctx.lineTo(lastX, bottomY);
+ ctx.closePath();
+ ctx.fill();
+ }
+}
diff --git a/ui/src/tracks/screenshots/index.ts b/ui/src/tracks/screenshots/index.ts
index c1eb5ed..cc85c16 100644
--- a/ui/src/tracks/screenshots/index.ts
+++ b/ui/src/tracks/screenshots/index.ts
@@ -16,12 +16,16 @@
import {AddTrackArgs} from '../../common/actions';
import {Engine} from '../../common/engine';
-import {PrimaryTrackSortKey} from '../../common/state';
import {
NamedSliceTrackTypes,
} from '../../frontend/named_slice_track';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {
+ Plugin,
+ PluginContext,
+ PluginDescriptor,
+ PrimaryTrackSortKey,
+} from '../../public';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
@@ -36,7 +40,7 @@
class ScreenshotsTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
static readonly kind = 'dev.perfetto.ScreenshotsTrack';
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new ScreenshotsTrack(args);
}
@@ -84,11 +88,11 @@
class ScreenshotsPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(ScreenshotsTrack);
+ ctx.LEGACY_registerTrack(ScreenshotsTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.Screenshots',
plugin: ScreenshotsPlugin,
};
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index b684e6c..807f8f9 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -25,8 +25,8 @@
import {TrackController} from '../../controller/track_controller';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export const THREAD_STATE_TRACK_KIND = 'ThreadStateTrack';
@@ -162,7 +162,7 @@
const RECT_HEIGHT = 12;
const EXCESS_WIDTH = 10;
-class ThreadStateTrack extends Track<Config, Data> {
+class ThreadStateTrack extends TrackBase<Config, Data> {
static readonly kind = THREAD_STATE_TRACK_KIND;
static create(args: NewTrackArgs): ThreadStateTrack {
return new ThreadStateTrack(args);
@@ -285,12 +285,12 @@
class ThreadState implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(ThreadStateTrack);
- ctx.registerTrackController(ThreadStateTrackController);
+ ctx.LEGACY_registerTrack(ThreadStateTrack);
+ ctx.LEGACY_registerTrackController(ThreadStateTrackController);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ThreadState',
plugin: ThreadState,
};
diff --git a/ui/src/tracks/thread_state_v2/index.ts b/ui/src/tracks/thread_state_v2/index.ts
index ce78b78..9c465b6 100644
--- a/ui/src/tracks/thread_state_v2/index.ts
+++ b/ui/src/tracks/thread_state_v2/index.ts
@@ -29,7 +29,7 @@
SliceLayout,
} from '../../frontend/slice_layout';
import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
export const THREAD_STATE_ROW = {
...BASE_SLICE_ROW,
@@ -126,11 +126,11 @@
class ThreadStateTrackV2 implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrack(ThreadStateTrack);
+ ctx.LEGACY_registerTrack(ThreadStateTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ThreadStateTrackV2',
plugin: ThreadStateTrackV2,
};
diff --git a/ui/src/tracks/visualised_args/index.ts b/ui/src/tracks/visualised_args/index.ts
index abee625..3385bf9 100644
--- a/ui/src/tracks/visualised_args/index.ts
+++ b/ui/src/tracks/visualised_args/index.ts
@@ -16,9 +16,9 @@
import {Actions} from '../../common/actions';
import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
import {TrackButton, TrackButtonAttrs} from '../../frontend/track_panel';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {Plugin, PluginContext, PluginDescriptor} from '../../public';
import {
ChromeSliceTrack,
ChromeSliceTrackController,
@@ -41,7 +41,7 @@
export class VisualisedArgsTrack extends ChromeSliceTrack {
static readonly kind = VISUALISED_ARGS_SLICE_TRACK_KIND;
- static create(args: NewTrackArgs): Track {
+ static create(args: NewTrackArgs): TrackBase {
return new VisualisedArgsTrack(args);
}
@@ -67,12 +67,12 @@
class VisualisedArgsPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(VisualisedArgsTrackController);
- ctx.registerTrack(VisualisedArgsTrack);
+ ctx.LEGACY_registerTrackController(VisualisedArgsTrackController);
+ ctx.LEGACY_registerTrack(VisualisedArgsTrack);
}
}
-export const plugin: PluginInfo = {
+export const plugin: PluginDescriptor = {
pluginId: 'perfetto.VisualisedArgs',
plugin: VisualisedArgsPlugin,
};