Merge "tp: Stub of frames stdlib support" into main
diff --git a/Android.bp b/Android.bp
index 7f9dcb1..bd2825a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5151,6 +5151,7 @@
"protos/perfetto/metrics/android/ad_services_metric.proto",
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto",
"protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
@@ -5239,6 +5240,7 @@
"protos/perfetto/metrics/android/ad_services_metric.proto",
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto",
"protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
@@ -5310,6 +5312,7 @@
"protos/perfetto/metrics/android/ad_services_metric.proto",
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto",
"protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
@@ -6041,6 +6044,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -9177,6 +9181,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -9219,6 +9224,7 @@
"external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.gen.cc",
"external/perfetto/protos/perfetto/trace/track_event/debug_annotation.gen.cc",
"external/perfetto/protos/perfetto/trace/track_event/log_message.gen.cc",
+ "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.gen.cc",
"external/perfetto/protos/perfetto/trace/track_event/process_descriptor.gen.cc",
"external/perfetto/protos/perfetto/trace/track_event/range_of_interest.gen.cc",
"external/perfetto/protos/perfetto/trace/track_event/screenshot.gen.cc",
@@ -9261,6 +9267,7 @@
"external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.gen.h",
"external/perfetto/protos/perfetto/trace/track_event/debug_annotation.gen.h",
"external/perfetto/protos/perfetto/trace/track_event/log_message.gen.h",
+ "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.gen.h",
"external/perfetto/protos/perfetto/trace/track_event/process_descriptor.gen.h",
"external/perfetto/protos/perfetto/trace/track_event/range_of_interest.gen.h",
"external/perfetto/protos/perfetto/trace/track_event/screenshot.gen.h",
@@ -9299,6 +9306,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -9340,6 +9348,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -9381,6 +9390,7 @@
"external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.cc",
"external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pb.cc",
"external/perfetto/protos/perfetto/trace/track_event/log_message.pb.cc",
+ "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pb.cc",
"external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pb.cc",
"external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pb.cc",
"external/perfetto/protos/perfetto/trace/track_event/screenshot.pb.cc",
@@ -9422,6 +9432,7 @@
"external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.h",
"external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pb.h",
"external/perfetto/protos/perfetto/trace/track_event/log_message.pb.h",
+ "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pb.h",
"external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pb.h",
"external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pb.h",
"external/perfetto/protos/perfetto/trace/track_event/screenshot.pb.h",
@@ -9460,6 +9471,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -9502,6 +9514,7 @@
"external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.cc",
"external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.cc",
"external/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.cc",
+ "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pbzero.cc",
"external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.cc",
"external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pbzero.cc",
"external/perfetto/protos/perfetto/trace/track_event/screenshot.pbzero.cc",
@@ -9544,6 +9557,7 @@
"external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.h",
"external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h",
"external/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.h",
+ "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pbzero.h",
"external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h",
"external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pbzero.h",
"external/perfetto/protos/perfetto/trace/track_event/screenshot.pbzero.h",
@@ -9712,6 +9726,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -11947,6 +11962,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_blocking_calls_unagg.sql",
"src/trace_processor/metrics/sql/android/android_boot.sql",
"src/trace_processor/metrics/sql/android/android_boot_unagg.sql",
"src/trace_processor/metrics/sql/android/android_camera.sql",
@@ -12371,8 +12387,10 @@
"src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql",
+ "src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/states.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
+ "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql",
@@ -12806,6 +12824,8 @@
"src/trace_redaction/process_thread_timeline.cc",
"src/trace_redaction/proto_util.cc",
"src/trace_redaction/prune_package_list.cc",
+ "src/trace_redaction/redact_sched_switch.cc",
+ "src/trace_redaction/redact_sched_waking.cc",
"src/trace_redaction/scrub_ftrace_events.cc",
"src/trace_redaction/scrub_process_trees.cc",
"src/trace_redaction/scrub_task_rename.cc",
@@ -12824,6 +12844,8 @@
"src/trace_redaction/process_thread_timeline_unittest.cc",
"src/trace_redaction/proto_util_unittest.cc",
"src/trace_redaction/prune_package_list_unittest.cc",
+ "src/trace_redaction/redact_sched_switch_unittest.cc",
+ "src/trace_redaction/redact_sched_waking_unittest.cc",
"src/trace_redaction/scrub_ftrace_events_unittest.cc",
"src/trace_redaction/scrub_task_rename_unittest.cc",
"src/trace_redaction/scrub_trace_packet_unittest.cc",
@@ -13872,6 +13894,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
@@ -15153,70 +15176,38 @@
":perfetto_include_perfetto_trace_processor_basic_types",
":perfetto_include_perfetto_trace_processor_storage",
":perfetto_include_perfetto_trace_processor_trace_processor",
- ":perfetto_protos_perfetto_common_cpp_gen",
":perfetto_protos_perfetto_common_zero_gen",
- ":perfetto_protos_perfetto_config_android_cpp_gen",
":perfetto_protos_perfetto_config_android_zero_gen",
- ":perfetto_protos_perfetto_config_cpp_gen",
- ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
":perfetto_protos_perfetto_config_ftrace_zero_gen",
- ":perfetto_protos_perfetto_config_gpu_cpp_gen",
":perfetto_protos_perfetto_config_gpu_zero_gen",
- ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
":perfetto_protos_perfetto_config_inode_file_zero_gen",
- ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
":perfetto_protos_perfetto_config_interceptors_zero_gen",
- ":perfetto_protos_perfetto_config_power_cpp_gen",
":perfetto_protos_perfetto_config_power_zero_gen",
- ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
":perfetto_protos_perfetto_config_process_stats_zero_gen",
- ":perfetto_protos_perfetto_config_profiling_cpp_gen",
":perfetto_protos_perfetto_config_profiling_zero_gen",
- ":perfetto_protos_perfetto_config_statsd_cpp_gen",
":perfetto_protos_perfetto_config_statsd_zero_gen",
- ":perfetto_protos_perfetto_config_sys_stats_cpp_gen",
":perfetto_protos_perfetto_config_sys_stats_zero_gen",
- ":perfetto_protos_perfetto_config_system_info_cpp_gen",
":perfetto_protos_perfetto_config_system_info_zero_gen",
- ":perfetto_protos_perfetto_config_track_event_cpp_gen",
":perfetto_protos_perfetto_config_track_event_zero_gen",
":perfetto_protos_perfetto_config_zero_gen",
- ":perfetto_protos_perfetto_trace_android_cpp_gen",
":perfetto_protos_perfetto_trace_android_zero_gen",
- ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
":perfetto_protos_perfetto_trace_chrome_zero_gen",
- ":perfetto_protos_perfetto_trace_etw_cpp_gen",
":perfetto_protos_perfetto_trace_etw_zero_gen",
- ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
":perfetto_protos_perfetto_trace_filesystem_zero_gen",
- ":perfetto_protos_perfetto_trace_ftrace_cpp_gen",
":perfetto_protos_perfetto_trace_ftrace_zero_gen",
- ":perfetto_protos_perfetto_trace_gpu_cpp_gen",
":perfetto_protos_perfetto_trace_gpu_zero_gen",
- ":perfetto_protos_perfetto_trace_interned_data_cpp_gen",
":perfetto_protos_perfetto_trace_interned_data_zero_gen",
- ":perfetto_protos_perfetto_trace_minimal_cpp_gen",
":perfetto_protos_perfetto_trace_minimal_zero_gen",
- ":perfetto_protos_perfetto_trace_non_minimal_cpp_gen",
":perfetto_protos_perfetto_trace_non_minimal_zero_gen",
- ":perfetto_protos_perfetto_trace_perfetto_cpp_gen",
":perfetto_protos_perfetto_trace_perfetto_zero_gen",
- ":perfetto_protos_perfetto_trace_power_cpp_gen",
":perfetto_protos_perfetto_trace_power_zero_gen",
":perfetto_protos_perfetto_trace_processor_zero_gen",
- ":perfetto_protos_perfetto_trace_profiling_cpp_gen",
":perfetto_protos_perfetto_trace_profiling_zero_gen",
- ":perfetto_protos_perfetto_trace_ps_cpp_gen",
":perfetto_protos_perfetto_trace_ps_zero_gen",
- ":perfetto_protos_perfetto_trace_statsd_cpp_gen",
":perfetto_protos_perfetto_trace_statsd_zero_gen",
- ":perfetto_protos_perfetto_trace_sys_stats_cpp_gen",
":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
- ":perfetto_protos_perfetto_trace_system_info_cpp_gen",
":perfetto_protos_perfetto_trace_system_info_zero_gen",
- ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
":perfetto_protos_perfetto_trace_track_event_zero_gen",
- ":perfetto_protos_perfetto_trace_translation_cpp_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_src_base_base",
":perfetto_src_protozero_protozero",
@@ -15259,70 +15250,38 @@
"libz",
],
generated_headers: [
- "perfetto_protos_perfetto_common_cpp_gen_headers",
"perfetto_protos_perfetto_common_zero_gen_headers",
- "perfetto_protos_perfetto_config_android_cpp_gen_headers",
"perfetto_protos_perfetto_config_android_zero_gen_headers",
- "perfetto_protos_perfetto_config_cpp_gen_headers",
- "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
"perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
- "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
"perfetto_protos_perfetto_config_gpu_zero_gen_headers",
- "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
"perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
- "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
"perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
- "perfetto_protos_perfetto_config_power_cpp_gen_headers",
"perfetto_protos_perfetto_config_power_zero_gen_headers",
- "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
"perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
- "perfetto_protos_perfetto_config_profiling_cpp_gen_headers",
"perfetto_protos_perfetto_config_profiling_zero_gen_headers",
- "perfetto_protos_perfetto_config_statsd_cpp_gen_headers",
"perfetto_protos_perfetto_config_statsd_zero_gen_headers",
- "perfetto_protos_perfetto_config_sys_stats_cpp_gen_headers",
"perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
- "perfetto_protos_perfetto_config_system_info_cpp_gen_headers",
"perfetto_protos_perfetto_config_system_info_zero_gen_headers",
- "perfetto_protos_perfetto_config_track_event_cpp_gen_headers",
"perfetto_protos_perfetto_config_track_event_zero_gen_headers",
"perfetto_protos_perfetto_config_zero_gen_headers",
- "perfetto_protos_perfetto_trace_android_cpp_gen_headers",
"perfetto_protos_perfetto_trace_android_zero_gen_headers",
- "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
"perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
- "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
"perfetto_protos_perfetto_trace_etw_zero_gen_headers",
- "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
"perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
- "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
"perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
- "perfetto_protos_perfetto_trace_gpu_cpp_gen_headers",
"perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
- "perfetto_protos_perfetto_trace_interned_data_cpp_gen_headers",
"perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
- "perfetto_protos_perfetto_trace_minimal_cpp_gen_headers",
"perfetto_protos_perfetto_trace_minimal_zero_gen_headers",
- "perfetto_protos_perfetto_trace_non_minimal_cpp_gen_headers",
"perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
- "perfetto_protos_perfetto_trace_perfetto_cpp_gen_headers",
"perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
- "perfetto_protos_perfetto_trace_power_cpp_gen_headers",
"perfetto_protos_perfetto_trace_power_zero_gen_headers",
"perfetto_protos_perfetto_trace_processor_zero_gen_headers",
- "perfetto_protos_perfetto_trace_profiling_cpp_gen_headers",
"perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
- "perfetto_protos_perfetto_trace_ps_cpp_gen_headers",
"perfetto_protos_perfetto_trace_ps_zero_gen_headers",
- "perfetto_protos_perfetto_trace_statsd_cpp_gen_headers",
"perfetto_protos_perfetto_trace_statsd_zero_gen_headers",
- "perfetto_protos_perfetto_trace_sys_stats_cpp_gen_headers",
"perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
- "perfetto_protos_perfetto_trace_system_info_cpp_gen_headers",
"perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
- "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
"perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
- "perfetto_protos_perfetto_trace_translation_cpp_gen_headers",
"perfetto_protos_perfetto_trace_translation_zero_gen_headers",
"perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
"perfetto_src_trace_processor_importers_proto_gen_cc_track_event_descriptor",
diff --git a/BUILD b/BUILD
index 3b56b52..efa579a 100644
--- a/BUILD
+++ b/BUILD
@@ -1950,6 +1950,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_blocking_calls_unagg.sql",
"src/trace_processor/metrics/sql/android/android_boot.sql",
"src/trace_processor/metrics/sql/android/android_boot_unagg.sql",
"src/trace_processor/metrics/sql/android/android_camera.sql",
@@ -2537,8 +2538,10 @@
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_sched_sched",
srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/states.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
+ "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql",
@@ -4380,6 +4383,7 @@
"protos/perfetto/metrics/android/ad_services_metric.proto",
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
+ "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto",
"protos/perfetto/metrics/android/android_boot.proto",
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
@@ -5232,6 +5236,7 @@
"protos/perfetto/trace/track_event/counter_descriptor.proto",
"protos/perfetto/trace/track_event/debug_annotation.proto",
"protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
"protos/perfetto/trace/track_event/process_descriptor.proto",
"protos/perfetto/trace/track_event/range_of_interest.proto",
"protos/perfetto/trace/track_event/screenshot.proto",
diff --git a/gn/standalone/wasm.gni b/gn/standalone/wasm.gni
index 3b09531..eb6d4d4 100644
--- a/gn/standalone/wasm.gni
+++ b/gn/standalone/wasm.gni
@@ -48,6 +48,8 @@
"-s",
"INITIAL_MEMORY=33554432",
"-s",
+ "MAXIMUM_MEMORY=4GB",
+ "-s",
"ALLOW_MEMORY_GROWTH=1",
"-s",
"ALLOW_TABLE_GROWTH=1",
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 33f4195..5b8fab6 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -23,6 +23,7 @@
"ad_services_metric.proto",
"android_blocking_call.proto",
"android_blocking_calls_cuj_metric.proto",
+ "android_blocking_calls_unagg.proto",
"android_boot.proto",
"android_boot_unagg.proto",
"android_frame_timeline_metric.proto",
diff --git a/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto b/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto
new file mode 100644
index 0000000..96b73ba
--- /dev/null
+++ b/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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;
+
+import "protos/perfetto/metrics/android/android_blocking_call.proto";
+import "protos/perfetto/metrics/android/process_metadata.proto";
+
+// All blocking calls for a trace. Shows count and total duration for each.
+message AndroidBlockingCallsUnagg {
+
+ repeated ProcessWithBlockingCalls process_with_blocking_calls = 1;
+
+ message ProcessWithBlockingCalls {
+ // Details about the process (uid, version, etc)
+ optional AndroidProcessMetadata process = 1;
+
+ // List of blocking calls on the process main thread.
+ repeated AndroidBlockingCall blocking_calls = 2;
+ }
+}
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index f06f085..83395ec 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -29,6 +29,7 @@
import "protos/perfetto/metrics/android/batt_metric.proto";
import "protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto";
import "protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto";
+import "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto";
import "protos/perfetto/metrics/android/codec_metrics.proto";
import "protos/perfetto/metrics/android/cpu_metric.proto";
import "protos/perfetto/metrics/android/camera_metric.proto";
@@ -116,7 +117,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 64
+// Next id: 66
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -294,6 +295,9 @@
// Specific for Android Auto
optional AndroidMultiuserMetric android_auto_multiuser = 64;
+ // All blocking calls (e.g. binder calls) for a trace.
+ optional AndroidBlockingCallsUnagg android_blocking_calls_unagg = 65;
+
// 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 fbd42fd..c2e2263 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -145,6 +145,24 @@
// End of protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto
+// Begin of protos/perfetto/metrics/android/android_blocking_calls_unagg.proto
+
+// All blocking calls for a trace. Shows count and total duration for each.
+message AndroidBlockingCallsUnagg {
+
+ repeated ProcessWithBlockingCalls process_with_blocking_calls = 1;
+
+ message ProcessWithBlockingCalls {
+ // Details about the process (uid, version, etc)
+ optional AndroidProcessMetadata process = 1;
+
+ // List of blocking calls on the process main thread.
+ repeated AndroidBlockingCall blocking_calls = 2;
+ }
+}
+
+// End of protos/perfetto/metrics/android/android_blocking_calls_unagg.proto
+
// Begin of protos/perfetto/metrics/android/android_boot.proto
// This metric computes how much time processes spend in UNINTERRUPTIBLE_SLEEP
@@ -2520,7 +2538,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 64
+// Next id: 66
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -2698,6 +2716,9 @@
// Specific for Android Auto
optional AndroidMultiuserMetric android_auto_multiuser = 64;
+ // All blocking calls (e.g. binder calls) for a trace.
+ optional AndroidBlockingCallsUnagg android_blocking_calls_unagg = 65;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 0921dc0..74e49b6 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -12130,6 +12130,16 @@
// End of protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
+// Begin of protos/perfetto/trace/track_event/pixel_modem.proto
+
+// Event insights emitted by the Pixel modem.
+message PixelModemEventInsight {
+ // Opaque string containing arguments from the modem.
+ optional string detokenized_message = 1;
+}
+
+// End of protos/perfetto/trace/track_event/pixel_modem.proto
+
// Begin of protos/perfetto/trace/track_event/screenshot.proto
message Screenshot {
@@ -12215,7 +12225,7 @@
// their default track association) can be emitted as part of a
// TrackEventDefaults message.
//
-// Next reserved id: 13 (up to 15). Next id: 51.
+// Next reserved id: 13 (up to 15). Next id: 52.
message TrackEvent {
// Names of categories of the event. In the client library, categories are a
// way to turn groups of individual events on or off.
@@ -12369,6 +12379,7 @@
43;
optional ChromeActiveProcesses chrome_active_processes = 49;
optional Screenshot screenshot = 50;
+ optional PixelModemEventInsight pixel_modem_event_insight = 51;
// This field is used only if the source location represents the function that
// executes during this event.
diff --git a/protos/perfetto/trace/track_event/BUILD.gn b/protos/perfetto/trace/track_event/BUILD.gn
index c101c46..00a5ff5 100644
--- a/protos/perfetto/trace/track_event/BUILD.gn
+++ b/protos/perfetto/trace/track_event/BUILD.gn
@@ -35,6 +35,7 @@
"counter_descriptor.proto",
"debug_annotation.proto",
"log_message.proto",
+ "pixel_modem.proto",
"process_descriptor.proto",
"range_of_interest.proto",
"screenshot.proto",
diff --git a/protos/perfetto/trace/track_event/pixel_modem.proto b/protos/perfetto/trace/track_event/pixel_modem.proto
new file mode 100644
index 0000000..ffe9062
--- /dev/null
+++ b/protos/perfetto/trace/track_event/pixel_modem.proto
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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;
+
+// Event insights emitted by the Pixel modem.
+message PixelModemEventInsight {
+ // Opaque string containing arguments from the modem.
+ optional string detokenized_message = 1;
+}
diff --git a/protos/perfetto/trace/track_event/track_event.proto b/protos/perfetto/trace/track_event/track_event.proto
index 325465d..2b9d35c 100644
--- a/protos/perfetto/trace/track_event/track_event.proto
+++ b/protos/perfetto/trace/track_event/track_event.proto
@@ -33,6 +33,7 @@
import "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto";
import "protos/perfetto/trace/track_event/chrome_user_event.proto";
import "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto";
+import "protos/perfetto/trace/track_event/pixel_modem.proto";
import "protos/perfetto/trace/track_event/screenshot.proto";
import "protos/perfetto/trace/track_event/source_location.proto";
@@ -103,7 +104,7 @@
// their default track association) can be emitted as part of a
// TrackEventDefaults message.
//
-// Next reserved id: 13 (up to 15). Next id: 51.
+// Next reserved id: 13 (up to 15). Next id: 52.
message TrackEvent {
// Names of categories of the event. In the client library, categories are a
// way to turn groups of individual events on or off.
@@ -257,6 +258,7 @@
43;
optional ChromeActiveProcesses chrome_active_processes = 49;
optional Screenshot screenshot = 50;
+ optional PixelModemEventInsight pixel_modem_event_insight = 51;
// This field is used only if the source location represents the function that
// executes during this event.
diff --git a/python/BUILD b/python/BUILD
index 3b65af7..52a511d 100644
--- a/python/BUILD
+++ b/python/BUILD
@@ -126,6 +126,9 @@
] + PERFETTO_CONFIG.deps.pandas_py +
PERFETTO_CONFIG.deps.protobuf_py +
PERFETTO_CONFIG.deps.tp_vendor_py,
+ tags = [
+ "avoid_dep",
+ ],
)
# GN target: //python:trace_uri_resolver
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 6b4b352..a5fe326 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 6e1e1bb..bc832bf 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -68,6 +68,7 @@
#include "protos/perfetto/trace/ftrace/mm_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/net.pbzero.h"
#include "protos/perfetto/trace/ftrace/oom.pbzero.h"
+#include "protos/perfetto/trace/ftrace/panel.pbzero.h"
#include "protos/perfetto/trace/ftrace/power.pbzero.h"
#include "protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h"
#include "protos/perfetto/trace/ftrace/rpm.pbzero.h"
@@ -1143,6 +1144,10 @@
ParseRpmStatus(ts, fld_bytes);
break;
}
+ case FtraceEvent::kPanelWriteGenericFieldNumber: {
+ ParsePanelWriteGeneric(ts, pid, fld_bytes);
+ break;
+ }
default:
break;
}
@@ -3329,6 +3334,22 @@
devices_with_active_rpm_slice_.insert(device_name);
}
+void FtraceParser::ParsePanelWriteGeneric(int64_t timestamp,
+ uint32_t pid,
+ ConstBytes blob) {
+ protos::pbzero::PanelWriteGenericFtraceEvent::Decoder evt(blob.data,
+ blob.size);
+ if (!evt.type()) {
+ context_->storage->IncrementStats(stats::systrace_parse_failure);
+ return;
+ }
+
+ uint32_t tgid = static_cast<uint32_t>(evt.pid());
+ SystraceParser::GetOrCreate(context_)->ParseKernelTracingMarkWrite(
+ timestamp, pid, static_cast<char>(evt.type()), false /*trace_begin*/,
+ evt.name(), tgid, evt.value());
+}
+
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 a3f8fc6..402ad73 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -296,6 +296,9 @@
protozero::ConstBytes);
StringId GetRpmStatusStringId(int32_t rpm_status_val);
void ParseRpmStatus(int64_t ts, protozero::ConstBytes);
+ void ParsePanelWriteGeneric(int64_t timestamp,
+ uint32_t pid,
+ protozero::ConstBytes);
TraceProcessorContext* context_;
RssStatTracker rss_stat_tracker_;
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index b06bb71..e00322b 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -98,11 +98,12 @@
point.int_value = value;
point.tgid = tgid;
- // Some versions of this trace point fill trace_type with one of (B/E/C),
+ // Some versions of this trace point fill trace_type with one of (B/E/C/I),
// others use the trace_begin boolean and only support begin/end events:
if (trace_type == 0) {
point.phase = trace_begin ? 'B' : 'E';
- } else if (trace_type == 'B' || trace_type == 'E' || trace_type == 'C') {
+ } else if (trace_type == 'B' || trace_type == 'E' || trace_type == 'C' ||
+ trace_type == 'I') {
point.phase = trace_type;
} else {
context_->storage->IncrementStats(stats::systrace_parse_failure);
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index f87b560..bd77d0a 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -25,6 +25,7 @@
"android_batt.sql",
"android_binder.sql",
"android_blocking_calls_cuj_metric.sql",
+ "android_blocking_calls_unagg.sql",
"android_boot.sql",
"android_boot_unagg.sql",
"android_camera.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
index eaa2104..5838125 100644
--- a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
+++ b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
@@ -21,6 +21,7 @@
INCLUDE PERFETTO MODULE android.slices;
INCLUDE PERFETTO MODULE android.binder;
+INCLUDE PERFETTO MODULE android.critical_blocking_calls;
-- Jank "J<*>" and latency "L<*>" cujs are put together in android_cujs table.
-- They are computed separately as latency ones are slightly different, don't
@@ -75,99 +76,22 @@
SELECT ROW_NUMBER() OVER (ORDER BY ts) AS cuj_id, *
FROM all_cujs;
-DROP TABLE IF EXISTS relevant_binder_calls_with_names;
-CREATE TABLE relevant_binder_calls_with_names AS
-SELECT DISTINCT
- tx.aidl_name AS name,
- tx.client_ts AS ts,
- s.track_id,
- tx.client_dur AS dur,
- s.id,
- tx.client_process as process_name,
- tx.client_utid as utid,
- tx.client_upid as upid
-FROM android_binder_txns AS tx
- JOIN slice AS s ON s.id = tx.binder_txn_id
-WHERE is_main_thread AND aidl_name IS NOT NULL AND is_sync = 1;
-
-DROP TABLE IF EXISTS android_blocking_calls_cuj_calls;
-CREATE TABLE android_blocking_calls_cuj_calls AS
-WITH all_main_thread_relevant_slices AS (
- SELECT DISTINCT
- android_standardize_slice_name(s.name) AS name,
- s.ts,
- s.track_id,
- s.dur,
- s.id,
- process.name AS process_name,
- thread.utid,
- process.upid
- FROM slice s
- JOIN thread_track ON s.track_id = thread_track.id
- JOIN thread USING (utid)
- JOIN process USING (upid)
- WHERE
- thread.is_main_thread AND (
- s.name = 'measure'
- OR s.name = 'layout'
- OR s.name = 'configChanged'
- OR s.name = 'animation'
- OR s.name = 'input'
- OR s.name = 'traversal'
- OR s.name = 'Contending for pthread mutex'
- OR s.name = 'postAndWait'
- OR s.name GLOB 'monitor contention with*'
- OR s.name GLOB 'SuspendThreadByThreadId*'
- OR s.name GLOB 'LoadApkAssetsFd*'
- OR s.name GLOB '*binder transaction*'
- OR s.name GLOB 'inflate*'
- OR s.name GLOB 'Lock contention on*'
- OR s.name GLOB 'android.os.Handler: kotlinx.coroutines*'
- OR s.name GLOB 'relayoutWindow*'
- OR s.name GLOB 'ImageDecoder#decode*'
- OR s.name GLOB 'NotificationStackScrollLayout#onMeasure'
- OR s.name GLOB 'ExpNotRow#*'
- OR s.name GLOB 'GC: Wait For*'
- OR (
- -- Some top level handler slices
- s.depth = 0
- AND s.name NOT GLOB '*Choreographer*'
- AND s.name NOT GLOB '*Input*'
- AND s.name NOT GLOB '*input*'
- AND s.name NOT GLOB 'android.os.Handler: #*'
- AND (
- -- Handler pattern heuristics
- s.name GLOB '*Handler: *$*'
- OR s.name GLOB '*.*.*: *$*'
- OR s.name GLOB '*.*$*: #*'
- )
- )
- )
- UNION ALL
- SELECT
- name,
- ts,
- track_id,
- dur,
- id,
- process_name,
- utid,
- upid
- FROM relevant_binder_calls_with_names
-),
--- Now we have:
--- (1) a list of slices from the main thread of each process
+-- We have:
+-- (1) a list of slices from the main thread of each process from the
+-- all_main_thread_relevant_slices table.
-- (2) a list of android cuj with beginning, end, and process
-- It's needed to:
-- (1) assign a cuj to each slice. If there are multiple cujs going on during a
-- slice, there needs to be 2 entries for that slice, one for each cuj id.
-- (2) each slice needs to be trimmed to be fully inside the cuj associated
-- (as we don't care about what's outside cujs)
+DROP TABLE IF EXISTS android_blocking_calls_cuj_calls;
+CREATE TABLE android_blocking_calls_cuj_calls AS
+WITH
main_thread_slices_scoped_to_cujs AS (
SELECT
s.id,
s.id AS slice_id,
- s.track_id,
s.name,
max(s.ts, cuj.ts) AS ts,
min(s.ts + s.dur, cuj.ts_end) as ts_end,
@@ -177,7 +101,7 @@
s.process_name,
s.upid,
s.utid
-FROM all_main_thread_relevant_slices s
+FROM _android_critical_blocking_calls s
JOIN android_cujs cuj
-- only when there is an overlap
ON s.ts + s.dur > cuj.ts AND s.ts < cuj.ts_end
diff --git a/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql b/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql
new file mode 100644
index 0000000..2008c0b
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql
@@ -0,0 +1,96 @@
+--
+-- Copyright 2024 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.
+
+SELECT RUN_METRIC('android/process_metadata.sql');
+
+INCLUDE PERFETTO MODULE android.slices;
+INCLUDE PERFETTO MODULE android.binder;
+INCLUDE PERFETTO MODULE android.critical_blocking_calls;
+
+DROP TABLE IF EXISTS process_info;
+CREATE TABLE process_info AS
+SELECT
+ process.upid AS upid,
+ process.name AS process_name,
+ process_metadata.metadata AS process_metadata
+FROM process
+JOIN process_metadata USING (upid);
+
+DROP TABLE IF EXISTS android_blocking_calls_unagg_calls;
+CREATE TABLE android_blocking_calls_unagg_calls AS
+SELECT
+ name,
+ COUNT(*) AS occurrences,
+ MAX(dur) AS max_dur_ns,
+ MIN(dur) AS min_dur_ns,
+ SUM(dur) AS total_dur_ns,
+ upid,
+ process_name
+FROM
+ _android_critical_blocking_calls
+GROUP BY name, upid, process_name;
+
+DROP TABLE IF EXISTS filtered_processes_with_non_zero_blocking_calls;
+CREATE TABLE filtered_processes_with_non_zero_blocking_calls AS
+SELECT pi.upid,
+ pi.process_name,
+ pi.process_metadata
+FROM process_info pi WHERE pi.upid IN
+ (SELECT DISTINCT upid FROM _android_critical_blocking_calls);
+
+
+DROP TABLE IF EXISTS filtered_processes_with_non_zero_blocking_calls;
+CREATE TABLE filtered_processes_with_non_zero_blocking_calls AS
+SELECT pi.upid,
+ pi.process_name,
+ pi.process_metadata
+FROM process_info pi WHERE pi.upid IN
+ (SELECT DISTINCT upid FROM _android_critical_blocking_calls);
+
+DROP VIEW IF EXISTS android_blocking_calls_unagg_output;
+CREATE PERFETTO VIEW android_blocking_calls_unagg_output AS
+SELECT AndroidBlockingCallsUnagg(
+ 'process_with_blocking_calls', (
+ SELECT RepeatedField(
+ AndroidBlockingCallsUnagg_ProcessWithBlockingCalls(
+ 'process', e.process_metadata,
+ 'blocking_calls', (
+ SELECT RepeatedField(
+ AndroidBlockingCall(
+ 'name', d.name,
+ 'cnt', d.occurrences,
+ 'total_dur_ms', CAST(total_dur_ns / 1e6 AS INT),
+ 'max_dur_ms', CAST(max_dur_ns / 1e6 AS INT),
+ 'min_dur_ms', CAST(min_dur_ns / 1e6 AS INT),
+ 'total_dur_ns', d.total_dur_ns,
+ 'max_dur_ns', d.max_dur_ns,
+ 'min_dur_ns', d.min_dur_ns
+ )
+ ) FROM (
+ SELECT b.name,
+ b.occurrences,
+ b.total_dur_ns,
+ b.max_dur_ns,
+ b.min_dur_ns
+ FROM android_blocking_calls_unagg_calls b INNER JOIN filtered_processes_with_non_zero_blocking_calls c
+ ON b.upid = c.upid WHERE b.upid = e.upid
+ ORDER BY total_dur_ns DESC
+ ) d
+ )
+ )
+ )
+ FROM filtered_processes_with_non_zero_blocking_calls e
+ )
+);
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index 18f9fbf..4564f64 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -504,16 +504,6 @@
) AS startup
FROM android_startups launches;
-DROP VIEW IF EXISTS android_startup_event;
-CREATE PERFETTO VIEW android_startup_event AS
-SELECT
- 'slice' AS track_type,
- 'Android App Startups' AS track_name,
- l.ts AS ts,
- l.dur AS dur,
- l.package AS slice_name
-FROM android_startups l;
-
DROP VIEW IF EXISTS android_startup_output;
CREATE PERFETTO VIEW android_startup_output AS
SELECT
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
index 383ed17..59e571f 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
@@ -38,6 +38,7 @@
#include "protos/perfetto/trace/ftrace/g2d.pbzero.h"
#include "protos/perfetto/trace/ftrace/irq.pbzero.h"
#include "protos/perfetto/trace/ftrace/mdss.pbzero.h"
+#include "protos/perfetto/trace/ftrace/panel.pbzero.h"
#include "protos/perfetto/trace/ftrace/power.pbzero.h"
#include "protos/perfetto/trace/ftrace/samsung.pbzero.h"
#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
@@ -459,6 +460,19 @@
writer_->AppendString("|");
WriteValueForField(TMW::kValueFieldNumber, DVW());
return;
+ } else if (event_name_ == "panel_write_generic") {
+ using TMW = protos::pbzero::PanelWriteGenericFtraceEvent;
+ WriteValueForField(TMW::kTypeFieldNumber, [this](const Variadic& value) {
+ PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
+ writer_->AppendChar(static_cast<char>(value.uint_value));
+ });
+ writer_->AppendString("|");
+ WriteValueForField(TMW::kPidFieldNumber, DVW());
+ writer_->AppendString("|");
+ WriteValueForField(TMW::kNameFieldNumber, DVW());
+ writer_->AppendString("|");
+ WriteValueForField(TMW::kValueFieldNumber, DVW());
+ return;
} else if (event_name_ == "g2d_tracing_mark_write") {
using TMW = protos::pbzero::G2dTracingMarkWriteFtraceEvent;
WriteValueForField(TMW::kTypeFieldNumber, [this](const Variadic& value) {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
index 4ff33cc..dda42ab 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
@@ -86,20 +86,20 @@
std::vector<Edge> roots;
bool parse_error = false;
auto root_node_ids = start.int_values(&parse_error);
- auto max_weights = end.int_values(&parse_error);
+ auto target_weights = end.int_values(&parse_error);
- for (; root_node_ids && max_weights; ++root_node_ids, ++max_weights) {
+ for (; root_node_ids && target_weights; ++root_node_ids, ++target_weights) {
roots.push_back(Edge{static_cast<uint32_t>(*root_node_ids),
- static_cast<uint32_t>(*max_weights)});
+ static_cast<uint32_t>(*target_weights)});
}
if (parse_error) {
return base::ErrStatus(
- "Failed while parsing root_node_ids or root_max_weights");
+ "Failed while parsing root_node_ids or root_target_weights");
}
- if (static_cast<bool>(root_node_ids) != static_cast<bool>(max_weights)) {
+ if (static_cast<bool>(root_node_ids) != static_cast<bool>(target_weights)) {
return base::ErrStatus(
- "dfs_weight_bounded: length of root_node_ids and root_max_weights "
+ "dfs_weight_bounded: length of root_node_ids and root_target_weights "
"columns is not the same");
}
return roots;
@@ -108,7 +108,8 @@
void DfsWeightBoundedImpl(
tables::DfsWeightBoundedTable* table,
const std::vector<Destinations>& source_to_destinations_map,
- const std::vector<Edge>& roots) {
+ const std::vector<Edge>& roots,
+ const bool is_target_weight_floor) {
struct StackState {
uint32_t id;
uint32_t weight;
@@ -131,22 +132,26 @@
continue;
}
seen_node_ids[stack_state.id] = true;
-
- // We want to greedily return all possible edges that are reachable within
- // the target weight. If an edge already fails the requirement, skip it
- // and don't include it's weight but continue the search, some other edges
- // might fit.
- if (total_weight + stack_state.weight > root.weight) {
- continue;
- }
total_weight += stack_state.weight;
+ if (!is_target_weight_floor && total_weight > root.weight) {
+ // If target weight is a ceiling weight then we don't want to include
+ // the last node that crosses the threshold.
+ break;
+ }
+
tables::DfsWeightBoundedTable::Row row;
row.root_node_id = root.id;
row.node_id = stack_state.id;
row.parent_node_id = stack_state.parent_id;
table->Insert(row);
+ if (total_weight > root.weight) {
+ // If the target weight is a floor weight, we add the last node that
+ // crossed the threshold before exiting the search.
+ break;
+ }
+
PERFETTO_DCHECK(stack_state.id < source_to_destinations_map.size());
const auto& children = source_to_destinations_map[stack_state.id];
@@ -176,24 +181,29 @@
base::StatusOr<std::unique_ptr<Table>> DfsWeightBounded::ComputeTable(
const std::vector<SqlValue>& arguments) {
- PERFETTO_CHECK(arguments.size() == 5);
+ PERFETTO_CHECK(arguments.size() == 6);
const SqlValue& raw_source_ids = arguments[0];
const SqlValue& raw_dest_ids = arguments[1];
const SqlValue& raw_edge_weights = arguments[2];
const SqlValue& raw_root_ids = arguments[3];
- const SqlValue& raw_root_max_weights = arguments[4];
+ const SqlValue& raw_root_target_weights = arguments[4];
+ const SqlValue& raw_is_target_weight_floor = arguments[5];
if (raw_source_ids.is_null() && raw_dest_ids.is_null() &&
- raw_edge_weights.is_null() && raw_root_ids.is_null() &&
- raw_root_max_weights.is_null()) {
+ raw_edge_weights.is_null()) {
+ return std::unique_ptr<Table>(
+ std::make_unique<tables::DfsWeightBoundedTable>(pool_));
+ }
+
+ if (raw_root_ids.is_null() && raw_root_target_weights.is_null()) {
return std::unique_ptr<Table>(
std::make_unique<tables::DfsWeightBoundedTable>(pool_));
}
if (raw_source_ids.is_null() || raw_dest_ids.is_null() ||
raw_edge_weights.is_null() || raw_root_ids.is_null() ||
- raw_root_max_weights.is_null()) {
+ raw_root_target_weights.is_null()) {
return base::ErrStatus(
"dfs_weight_bounded: either all arguments should be null or none "
"should be");
@@ -214,9 +224,9 @@
return base::ErrStatus(
"dfs_weight_bounded: root_ids should be a repeated field");
}
- if (raw_root_max_weights.type != SqlValue::kBytes) {
+ if (raw_root_target_weights.type != SqlValue::kBytes) {
return base::ErrStatus(
- "dfs_weight_bounded: root_max_weights should be a repeated field");
+ "dfs_weight_bounded: root_target_weights should be a repeated field");
}
protos::pbzero::ProtoBuilderResult::Decoder proto_source_ids(
@@ -263,25 +273,27 @@
protos::pbzero::RepeatedBuilderResult::Decoder root_ids(
proto_root_ids.repeated());
- protos::pbzero::ProtoBuilderResult::Decoder proto_root_max_weights(
- static_cast<const uint8_t*>(raw_root_max_weights.AsBytes()),
- raw_root_max_weights.bytes_count);
- if (!proto_root_max_weights.is_repeated()) {
+ protos::pbzero::ProtoBuilderResult::Decoder proto_root_target_weights(
+ static_cast<const uint8_t*>(raw_root_target_weights.AsBytes()),
+ raw_root_target_weights.bytes_count);
+ if (!proto_root_target_weights.is_repeated()) {
return base::ErrStatus(
- "dfs_weight_bounded: root_max_weights is not generated by "
+ "dfs_weight_bounded: root_target_weights is not generated by "
"RepeatedField function");
}
- protos::pbzero::RepeatedBuilderResult::Decoder root_max_weights(
- proto_root_max_weights.repeated());
+ protos::pbzero::RepeatedBuilderResult::Decoder root_target_weights(
+ proto_root_target_weights.repeated());
+ bool is_target_weight_floor =
+ static_cast<bool>(raw_is_target_weight_floor.AsLong());
ASSIGN_OR_RETURN(auto map, ParseSourceToDestionationsMap(source_ids, dest_ids,
edge_weights));
ASSIGN_OR_RETURN(auto roots,
- ParseRootToMaxWeightMap(root_ids, root_max_weights));
+ ParseRootToMaxWeightMap(root_ids, root_target_weights));
auto table = std::make_unique<tables::DfsWeightBoundedTable>(pool_);
- DfsWeightBoundedImpl(table.get(), map, roots);
+ DfsWeightBoundedImpl(table.get(), map, roots, is_target_weight_floor);
return std::unique_ptr<Table>(std::move(table));
}
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
index f1af772..92caf25 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
@@ -54,6 +54,13 @@
// uint32 values corresponding to the max sum of edge weights inclusive,
// at which point the DFS from the |root_node_ids| stops. This number of
// values should be the same as |root_node_ids|.
+// 6) |is_target_weight_floor|: Whether the target_weight is a floor weight or
+// ceiling weight.
+// If it's floor, the search stops right after we exceed the target weight,
+// and we include the node that pushed just passed the target. If ceiling,
+// the search stops right before the target weight and the node that would
+// have pushed us passed the target is not included.
+
//
// Returns:
// A table with the nodes reachable from the start node, their "parent" in
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
index 0570082..e129565 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
@@ -206,6 +206,9 @@
C("in_root_max_weights",
CppOptional(CppUint32()),
flags=ColumnFlag.HIDDEN),
+ C("in_is_target_weight_floor",
+ CppOptional(CppUint32()),
+ flags=ColumnFlag.HIDDEN),
])
# Keep this list sorted.
diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
index 6214716..34fc1de 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
@@ -131,7 +131,7 @@
-- (
-- SELECT
-- id AS root_node_id,
--- id - COALESCE(prev_id, id) AS root_max_weight
+-- id - COALESCE(prev_id, id) AS root_target_weight
-- FROM _wakeup_chain
-- ));
-- ```
@@ -150,7 +150,7 @@
graph_table TableOrSubquery,
-- A table/view/subquery corresponding to start nodes to |graph_table| which will be the
-- roots of the reachability trees. This table must have the columns
- -- "root_node_id" and "root_max_weight" corresponding to the starting node id and the max
+ -- "root_node_id" and "root_target_weight" corresponding to the starting node id and the max
-- weight allowed on the tree.
--
-- Note: the columns must contain uint32 similar to ids in trace processor
@@ -158,7 +158,14 @@
-- implementation makes assumptions on this for performance reasons and, if
-- this criteria is not, can lead to enormous amounts of memory being
-- allocated.
- root_table TableOrSubquery
+ root_table TableOrSubquery,
+ -- Whether the target_weight is a floor weight or ceiling weight.
+ -- If it's floor, the search stops right after we exceed the target weight, and we
+ -- include the node that pushed just passed the target. If ceiling, the search stops
+ -- right before the target weight and the node that would have pushed us passed the
+ -- target is not included.
+ is_target_weight_floor Expr
+
)
-- The returned table has the schema (root_node_id, node_id UINT32, parent_node_id UINT32).
-- |root_node_id| is the id of the starting node under which this edge was encountered.
@@ -175,6 +182,7 @@
(SELECT RepeatedField(dest_node_id) FROM __temp_graph_table),
(SELECT RepeatedField(edge_weight) FROM __temp_graph_table),
(SELECT RepeatedField(root_node_id) FROM __temp_root_table),
- (SELECT RepeatedField(root_max_weight) FROM __temp_root_table)
+ (SELECT RepeatedField(root_target_weight) FROM __temp_root_table),
+ $is_target_weight_floor
) dt
);
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
index 2326aab..28edf7f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
@@ -160,4 +160,5 @@
_subtree_native_size_bytes(t.id) AS dominated_native_size_bytes,
d.depth
FROM _heap_graph_dominator_tree t
-JOIN _heap_graph_dominator_tree_depth d USING(id);
+JOIN _heap_graph_dominator_tree_depth d USING(id)
+ORDER BY id;
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
index bd2dc94..9168926 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
@@ -17,8 +17,10 @@
perfetto_sql_source_set("sched") {
deps = [ "utilization" ]
sources = [
+ "runnable.sql",
"states.sql",
"thread_executing_span.sql",
+ "thread_executing_span_with_slice.sql",
"thread_level_parallelism.sql",
"thread_state_flattened.sql",
"time_in_state.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql b/src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql
new file mode 100644
index 0000000..38956b1
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql
@@ -0,0 +1,53 @@
+--
+-- Copyright 2024 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.
+--
+
+-- Previous runnable slice on the same thread.
+-- For each "Running" thread state finds:
+-- - previous "Runnable" (or runnable preempted) state.
+-- - previous uninterrupted "Runnable" state with a valid waker thread.
+CREATE PERFETTO TABLE sched_previous_runnable_on_thread(
+ -- `thread_state.id` id.
+ id INT,
+ -- Previous runnable `thread_state.id`.
+ prev_runnable_id INT,
+ -- Previous runnable `thread_state.id` with valid waker
+ -- thread.
+ prev_wakeup_runnable_id INT
+) AS
+WITH running_and_runnable AS (
+ SELECT
+ id,
+ state,
+ MAX(id)
+ FILTER (WHERE state != 'Running')
+ OVER utid_part AS prev_runnable_id,
+ MAX(id)
+ FILTER (WHERE
+ waker_utid IS NOT NULL
+ AND (irq_context IS NULL OR irq_context != 1))
+ OVER utid_part AS prev_wakeup_runnable_id
+ FROM thread_state
+ -- Optimal operation for state IN (R, R+, Running)
+ WHERE state GLOB 'R*' AND dur != -1
+ WINDOW utid_part AS (PARTITION BY utid ORDER BY id)
+)
+SELECT
+ id,
+ prev_runnable_id,
+ prev_wakeup_runnable_id
+FROM running_and_runnable
+WHERE state = 'Running'
+ORDER BY id;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql
index 348d261..e3a92e2 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql
@@ -14,7 +14,7 @@
-- limitations under the License.
--
-INCLUDE PERFETTO MODULE slices.flat_slices;
+INCLUDE PERFETTO MODULE graphs.search;
-- A 'thread_executing_span' is thread_state span starting with a runnable slice
-- until the next runnable slice that's woken up by a process (as opposed
@@ -33,7 +33,7 @@
-- so this table might contain wakeups from interrupt context, consequently, the
-- wakeup graph generated might not be accurate.
--
-CREATE PERFETTO VIEW _runnable_state
+CREATE PERFETTO TABLE _runnable_state
AS
SELECT
thread_state.id,
@@ -50,7 +50,7 @@
AND (thread_state.irq_context = 0 OR thread_state.irq_context IS NULL);
-- Similar to |_runnable_state| but finds the first runnable state at thread.
-CREATE PERFETTO VIEW _first_runnable_state
+CREATE PERFETTO TABLE _first_runnable_state
AS
WITH
first_state AS (
@@ -77,7 +77,7 @@
--
-- Finds all sleep states including interruptible (S) and uninterruptible (D).
-CREATE PERFETTO VIEW _sleep_state
+CREATE PERFETTO TABLE _sleep_state
AS
SELECT
thread_state.id,
@@ -92,7 +92,7 @@
--
-- Finds the last execution for every thread to end executing_spans without a Sleep.
--
-CREATE PERFETTO VIEW _thread_end_ts
+CREATE PERFETTO TABLE _thread_end_ts
AS
SELECT
MAX(ts) + dur AS end_ts,
@@ -102,7 +102,7 @@
GROUP BY utid;
-- Similar to |_sleep_state| but finds the first sleep state in a thread.
-CREATE PERFETTO VIEW _first_sleep_state
+CREATE PERFETTO TABLE _first_sleep_state
AS
SELECT
MIN(s.id) AS id,
@@ -145,42 +145,45 @@
-- end_ts = S1_ts.
CREATE PERFETTO TABLE _wakeup
AS
+WITH
+ all_wakeups AS (
+ SELECT
+ s.state,
+ s.blocked_function,
+ r.id,
+ r.ts AS ts,
+ r.utid AS utid,
+ r.waker_id,
+ r.waker_utid,
+ s.ts AS prev_end_ts
+ FROM _runnable_state r
+ JOIN _sleep_state s
+ ON s.utid = r.utid AND (s.ts + s.dur = r.ts)
+ UNION ALL
+ SELECT
+ NULL AS state,
+ NULL AS blocked_function,
+ r.id,
+ r.ts,
+ r.utid AS utid,
+ r.waker_id,
+ r.waker_utid,
+ NULL AS prev_end_ts
+ FROM _first_runnable_state r
+ LEFT JOIN _first_sleep_state s
+ ON s.utid = r.utid
+ )
SELECT
- s.state,
- s.blocked_function,
- r.id,
- r.ts AS ts,
- r.utid AS utid,
- r.waker_id,
- r.waker_utid,
- IFNULL(LEAD(s.ts) OVER (PARTITION BY r.utid ORDER BY r.ts), thread_end.end_ts) AS end_ts,
- s.ts AS prev_end_ts,
- LAG(r.id) OVER (PARTITION BY r.utid ORDER BY r.ts) AS prev_id
-FROM _runnable_state r
-JOIN _sleep_state s
- ON s.utid = r.utid AND (s.ts + s.dur = r.ts)
-LEFT JOIN _thread_end_ts thread_end
- USING (utid)
-UNION ALL
-SELECT
- NULL AS state,
- NULL AS blocked_function,
- r.id,
- r.ts,
- r.utid AS utid,
- r.waker_id,
- r.waker_utid,
- IFNULL(s.ts, thread_end.end_ts) AS end_ts,
- NULL AS prev_end_ts,
- NULL AS prev_id
-FROM _first_runnable_state r
-LEFT JOIN _first_sleep_state s
- ON s.utid = r.utid
+ all_wakeups.*,
+ LAG(id) OVER (PARTITION BY utid ORDER BY ts) AS prev_id,
+ IFNULL(LEAD(prev_end_ts) OVER (PARTITION BY utid ORDER BY ts), thread_end.end_ts) AS end_ts
+FROM all_wakeups
LEFT JOIN _thread_end_ts thread_end
USING (utid);
-- Mapping from running thread state to runnable
-CREATE PERFETTO TABLE _waker_map
+-- TODO(zezeozue): Switch to use `sched_previous_runnable_on_thread`.
+CREATE PERFETTO TABLE _wakeup_map
AS
WITH x AS (
SELECT id, waker_id, utid, state FROM thread_state WHERE state = 'Running' AND dur != -1
@@ -192,12 +195,12 @@
SELECT
id AS waker_id,
state,
- max(id)
+ MAX(id)
filter(WHERE state = 'R')
OVER (PARTITION BY utid ORDER BY id) AS id
FROM x
)
-SELECT id, waker_id FROM y WHERE state = 'Running' AND id IS NOT NULL ORDER BY waker_id;
+SELECT id, waker_id FROM y WHERE state = 'Running' ORDER BY waker_id;
--
-- Builds the parent-child chain from all thread_executing_spans. The parent is the waker and
@@ -206,10 +209,10 @@
-- Note that this doesn't include the roots. We'll compute the roots below.
-- This two step process improves performance because it's more efficient to scan
-- parent and find a child between than to scan child and find the parent it lies between.
-CREATE PERFETTO TABLE _wakeup_chain
+CREATE PERFETTO TABLE _wakeup_graph
AS
SELECT
- _waker_map.id AS parent_id,
+ _wakeup_map.id AS waker_id,
prev_id,
prev_end_ts,
_wakeup.id AS id,
@@ -220,178 +223,259 @@
_wakeup.state,
_wakeup.blocked_function
FROM _wakeup
-JOIN _waker_map USING(waker_id);
+JOIN _wakeup_map USING(waker_id)
+ORDER BY id;
-- The inverse of thread_executing_spans. All the sleeping periods between thread_executing_spans.
-CREATE PERFETTO TABLE _sleeping_span
+CREATE PERFETTO TABLE _sleep
AS
WITH
x AS (
SELECT
id,
ts,
- lag(end_ts) OVER (PARTITION BY utid ORDER BY id) AS prev_end_ts,
- utid
- FROM _wakeup_chain
+ prev_end_ts,
+ utid,
+ state,
+ blocked_function
+ FROM _wakeup_graph
)
SELECT
- ts - prev_end_ts AS dur, prev_end_ts AS ts, id AS root_id, utid AS root_utid
+ ts - prev_end_ts AS dur,
+ prev_end_ts AS ts,
+ id AS root_node_id,
+ utid AS critical_path_utid,
+ id AS critical_path_id,
+ ts - prev_end_ts AS critical_path_blocked_dur,
+ state AS critical_path_blocked_state,
+ blocked_function AS critical_path_blocked_function
FROM x
WHERE ts IS NOT NULL;
---
--- Finds the roots of the |_wakeup_chain|.
-CREATE PERFETTO TABLE _wakeup_root
-AS
-WITH
- _wakeup_root_id AS (
- SELECT DISTINCT parent_id AS id FROM _wakeup_chain
- EXCEPT
- SELECT DISTINCT id FROM _wakeup_chain
+-- Given a set of critical paths identified by their |root_node_ids|, flattens
+-- the critical path tasks such that there are no overlapping intervals. The end of a
+-- task in the critical path is the start of the following task in the critical path.
+CREATE PERFETTO MACRO _flatten_critical_path_tasks(_critical_path_table TableOrSubquery)
+RETURNS TableOrSubquery
+AS (
+ WITH
+ x AS (
+ SELECT
+ LEAD(ts) OVER (PARTITION BY root_node_id ORDER BY node_id) AS ts,
+ node_id,
+ ts AS node_ts,
+ root_node_id,
+ utid AS node_utid,
+ _wakeup_graph.prev_end_ts
+ FROM $_critical_path_table
+ JOIN _wakeup_graph
+ ON node_id = id
+ )
+ SELECT node_ts AS ts, root_node_id, node_id, ts - node_ts AS dur, node_utid, prev_end_ts FROM x
+);
+
+-- Converts a table with <ts, dur, utid> columns to a unique set of wakeup roots <id> that
+-- completely cover the time intervals.
+CREATE PERFETTO MACRO _intervals_to_roots(source_table TableOrSubQuery)
+RETURNS TableOrSubQuery
+AS (
+ WITH source AS (
+ SELECT * FROM $source_table
+ ), thread_bounds AS (
+ SELECT utid, MIN(ts) AS min_start, MAX(ts) AS max_start FROM _wakeup_graph GROUP BY utid
+ ), start AS (
+ SELECT
+ _wakeup_graph.utid, max(_wakeup_graph.id) AS start_id, source.ts, source.dur
+ FROM _wakeup_graph
+ JOIN thread_bounds
+ USING (utid)
+ JOIN source
+ ON source.utid = _wakeup_graph.utid AND MAX(source.ts, min_start) >= _wakeup_graph.ts
+ GROUP BY source.ts, source.utid
+ ), end AS (
+ SELECT
+ _wakeup_graph.utid, min(_wakeup_graph.id) AS end_id, source.ts, source.dur
+ FROM _wakeup_graph
+ JOIN thread_bounds
+ USING (utid)
+ JOIN source ON source.utid = _wakeup_graph.utid
+ AND MIN((source.ts + source.dur), max_start) <= _wakeup_graph.ts
+ GROUP BY source.ts, source.utid
+ ), bound AS (
+ SELECT start.utid, start.ts, start.dur, start_id, end_id
+ FROM start
+ JOIN end ON start.ts = end.ts AND start.dur = end.dur AND start.utid = end.utid
)
-SELECT NULL AS parent_id, _wakeup.*
-FROM _wakeup
-JOIN _wakeup_root_id USING(id);
+ SELECT DISTINCT _wakeup_graph.id FROM bound
+ JOIN _wakeup_graph ON _wakeup_graph.id BETWEEN start_id AND end_id
+);
---
--- Finds the leafs of the |_wakeup_chain|.
-CREATE PERFETTO TABLE _wakeup_leaf AS
-WITH
- _wakeup_leaf_id AS (
- SELECT DISTINCT id AS id FROM _wakeup_chain
- EXCEPT
- SELECT DISTINCT parent_id AS id FROM _wakeup_chain
- )
-SELECT _wakeup_chain.*
-FROM _wakeup_chain
-JOIN _wakeup_leaf_id USING(id);
-
---
--- Merges the roots, leafs and the rest of the chain.
-CREATE TABLE _wakeup_graph
-AS
-SELECT
- _wakeup_chain.parent_id,
- _wakeup_chain.id,
- _wakeup_chain.ts,
- _wakeup_chain.end_ts - _wakeup_chain.ts AS dur,
- _wakeup_chain.utid,
- _wakeup_chain.prev_end_ts,
- _wakeup_chain.state,
- _wakeup_chain.blocked_function,
- 0 AS is_root,
- (_wakeup_leaf.id IS NOT NULL) AS is_leaf
-FROM _wakeup_chain
-LEFT JOIN _wakeup_leaf
- USING (id)
-UNION ALL
-SELECT
- _wakeup_root.parent_id,
- _wakeup_root.id,
- _wakeup_root.ts,
- _wakeup_root.end_ts - _wakeup_root.ts AS dur,
- _wakeup_root.utid,
- _wakeup_root.prev_end_ts,
- _wakeup_root.state,
- _wakeup_root.blocked_function,
- 1 AS is_root,
- 0 AS is_leaf
-FROM _wakeup_root;
-
--- Thread_executing_span graph of all wakeups across all processes.
---
--- @column root_id Id of thread_executing_span that initiated the wakeup of |id|.
--- @column parent_id Id of thread_executing_span that directly woke |id|.
--- @column id Id of the first (runnable) thread state in thread_executing_span.
--- @column ts Timestamp of first thread_state in thread_executing_span.
--- @column dur Duration of thread_executing_span.
--- @column utid Utid of thread with thread_state.
--- @column blocked_dur Duration of blocking thread state before waking up.
--- @column blocked_state Thread state ('D' or 'S') of blocked thread_state before waking up.
--- @column blocked_function Kernel blocked_function of thread state before waking up.
--- @column is_root Whether the thread_executing_span is a root.
--- @column depth Tree depth of thread executing span from the root.
-CREATE TABLE _thread_executing_span_graph AS
-WITH roots AS (
-SELECT
- id AS root_id,
- parent_id,
- id,
- ts,
- end_ts - ts AS dur,
- utid,
- ts - prev_end_ts AS blocked_dur,
- state AS blocked_state,
- blocked_function AS blocked_function,
- 1 AS is_root,
- 0 AS depth
-FROM _wakeup_root
-), chain AS (
- SELECT * FROM roots
- UNION ALL
+-- Flattens overlapping tasks within a critical path and flattens overlapping critical paths.
+CREATE PERFETTO MACRO _flatten_critical_paths(critical_path_table TableOrSubquery, sleeping_table TableOrSubquery)
+RETURNS TableOrSubquery
+AS (
+ WITH
+ span_starts AS (
+ SELECT
+ cr.node_utid AS utid,
+ MAX(cr.ts, sleep.ts) AS ts,
+ sleep.ts + sleep.dur AS sleep_end_ts,
+ cr.ts + cr.dur AS cr_end_ts,
+ cr.node_id AS id,
+ cr.root_node_id AS root_id,
+ cr.prev_end_ts AS prev_end_ts,
+ critical_path_utid,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function
+ FROM
+ _flatten_critical_path_tasks!($critical_path_table) cr
+ JOIN $sleeping_table sleep
+ USING (root_node_id)
+ )
SELECT
- chain.root_id,
- graph.parent_id,
- graph.id,
- graph.ts,
- graph.dur,
- graph.utid,
- graph.ts - graph.prev_end_ts AS blocked_dur,
- graph.state AS blocked_state,
- graph.blocked_function AS blocked_function,
- 0 AS is_root,
- chain.depth + 1 AS depth
- FROM _wakeup_graph graph
- JOIN chain ON chain.id = graph.parent_id
-) SELECT chain.*, thread.upid FROM chain LEFT JOIN thread USING(utid);
-
--- It finds the MAX between the start of the critical span and the start
--- of the blocked region. This ensures that the critical path doesn't overlap
--- the preceding thread_executing_span before the blocked region.
-CREATE PERFETTO FUNCTION _critical_path_start_ts(ts LONG, leaf_blocked_ts LONG)
-RETURNS LONG AS SELECT MAX($ts, IFNULL($leaf_blocked_ts, $ts));
-
--- See |_thread_executing_span_critical_path|
-CREATE PERFETTO TABLE _critical_path
-AS
-WITH chain AS (
- SELECT
- parent_id,
+ ts,
+ MIN(cr_end_ts, sleep_end_ts) - ts AS dur,
+ utid,
id,
+ root_id,
+ prev_end_ts,
+ critical_path_utid,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function
+ FROM span_starts
+ WHERE MIN(sleep_end_ts, cr_end_ts) - ts > 0
+);
+
+-- Generates a critical path.
+CREATE PERFETTO MACRO _critical_path(
+ graph_table TableOrSubquery, root_table TableOrSubquery, sleeping_table TableOrSubquery)
+RETURNS TableOrSubquery
+AS (
+ WITH
+ critical_path AS (
+ SELECT * FROM graph_reachable_weight_bounded_dfs !($graph_table, $root_table, 1)
+ )
+ SELECT
ts,
dur,
+ root_id,
+ id,
utid,
- id AS critical_path_id,
- ts - blocked_dur AS critical_path_blocked_ts,
- blocked_dur AS critical_path_blocked_dur,
- blocked_state AS critical_path_blocked_state,
- blocked_function AS critical_path_blocked_function,
- utid AS critical_path_utid,
- upid AS critical_path_upid
- FROM _thread_executing_span_graph graph
+ critical_path_utid,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function
+ FROM _flatten_critical_paths!(critical_path, $sleeping_table)
UNION ALL
+ -- Add roots
SELECT
- graph.parent_id,
- graph.id,
- _critical_path_start_ts(graph.ts, chain.critical_path_blocked_ts) AS ts,
- MIN(_critical_path_start_ts(graph.ts, chain.critical_path_blocked_ts) + graph.dur, chain.ts)
- - _critical_path_start_ts(graph.ts, chain.critical_path_blocked_ts) AS dur,
- graph.utid,
- chain.critical_path_id,
- chain.critical_path_blocked_ts,
- chain.critical_path_blocked_dur,
- chain.critical_path_blocked_state,
- chain.critical_path_blocked_function,
- chain.critical_path_utid,
- chain.critical_path_upid
- FROM _thread_executing_span_graph graph
- JOIN chain ON (chain.parent_id = graph.id AND (chain.ts > chain.critical_path_blocked_ts))
-) SELECT * FROM chain;
+ ts,
+ end_ts - ts AS dur,
+ id AS root_id,
+ id,
+ utid,
+ utid AS critical_path_utid,
+ NULL AS critical_path_id,
+ NULL AS critical_path_blocked_dur,
+ NULL AS critical_path_blocked_state,
+ NULL AS critical_path_blocked_function
+ FROM $root_table
+ ORDER BY root_id
+);
--- Thread executing span critical paths for all threads. For each thread, the critical path of
--- every sleeping thread state is computed and unioned with the thread executing spans on that thread.
--- The duration of a thread executing span in the critical path is the range between the start of the
--- thread_executing_span and the start of the next span in the critical path.
+-- Generates the critical path for only the set of roots <id> passed in.
+-- _intervals_to_roots can be used to generate root ids from a given time interval.
+-- This can be used to genrate the critical path over sparse regions of a trace, e.g
+-- binder transactions. It might be more efficient to generate the _critical_path
+-- for the entire trace, see _thread_executing_span_critical_path_all, but for a
+-- per-process susbset of binder txns for instance, this is likely faster.
+CREATE PERFETTO MACRO _critical_path_by_roots(roots_table TableOrSubQuery)
+RETURNS TableOrSubQuery
+AS (
+ WITH roots AS (
+ SELECT * FROM $roots_table
+ ), root_bounds AS (
+ SELECT MIN(id) AS min_root_id, MAX(id) AS max_root_id FROM roots
+ ), wakeup_bounds AS (
+ SELECT COALESCE(_wakeup_graph.prev_id, min_root_id) AS min_wakeup, max_root_id AS max_wakeup
+ FROM root_bounds
+ JOIN _wakeup_graph ON id = min_root_id
+ ) SELECT
+ id,
+ ts,
+ dur,
+ utid,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function,
+ critical_path_utid
+ FROM
+ _critical_path
+ !(
+ (
+ SELECT
+ id AS source_node_id,
+ COALESCE(waker_id, id) AS dest_node_id,
+ id - COALESCE(waker_id, id) AS edge_weight
+ FROM _wakeup_graph
+ JOIN wakeup_bounds WHERE id BETWEEN min_wakeup AND max_wakeup
+ ),
+ (
+ SELECT
+ _wakeup_graph.id AS root_node_id,
+ _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight,
+ id,
+ ts,
+ end_ts,
+ utid
+ FROM _wakeup_graph
+ JOIN (SELECT * FROM roots) USING (id)
+ ),
+ _sleep));
+
+-- Generates the critical path for only the time intervals for the utids given.
+-- Currently expensive because of naive interval_intersect implementation.
+-- Prefer _critical_paths_by_roots for performance. This is useful for a small
+-- set of intervals, e.g app startups in a trace.
+CREATE PERFETTO MACRO _critical_path_by_intervals(intervals_table TableOrSubQuery)
+RETURNS TableOrSubQuery AS (
+WITH span_starts AS (
+ SELECT
+ id,
+ MAX(span.ts, intervals.ts) AS ts,
+ MIN(span.ts + span.dur, intervals.ts + intervals.dur) AS end_ts,
+ span.utid,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function,
+ critical_path_utid
+ FROM _critical_path_by_roots!(_intervals_to_roots!($intervals_table)) span
+ -- TODO(zezeozue): Replace with interval_intersect when partitions are supported
+ JOIN (SELECT * FROM $intervals_table) intervals ON span.critical_path_utid = intervals.utid
+ AND ((span.ts BETWEEN intervals.ts AND intervals.ts + intervals.dur)
+ OR (intervals.ts BETWEEN span.ts AND span.ts + span.dur))
+) SELECT
+ id,
+ ts,
+ end_ts - ts AS dur,
+ utid,
+ critical_path_id,
+ critical_path_blocked_dur,
+ critical_path_blocked_state,
+ critical_path_blocked_function,
+ critical_path_utid
+ FROM span_starts);
+
+-- Generates the critical path for a given utid over the <ts, dur> interval.
+-- The duration of a thread executing span in the critical path is the range between the
+-- start of the thread_executing_span and the start of the next span in the critical path.
CREATE PERFETTO FUNCTION _thread_executing_span_critical_path(
-- Utid of the thread to compute the critical path for.
critical_path_utid INT,
@@ -419,598 +503,41 @@
-- Thread Utid the critical path was filtered to.
critical_path_utid INT
) AS
-WITH span_starts AS (
- SELECT
- id,
- MAX(ts, $ts) AS ts,
- MIN(ts + dur, $ts + $dur) AS end_ts,
- utid,
- critical_path_id,
- critical_path_blocked_dur,
- critical_path_blocked_state,
- critical_path_blocked_function,
- critical_path_utid
- FROM _critical_path span
- WHERE (($critical_path_utid IS NOT NULL AND span.critical_path_utid = $critical_path_utid) OR ($critical_path_utid IS NULL))
- AND ((ts BETWEEN $ts AND $ts + $dur) OR ($ts BETWEEN ts AND ts + dur))
-) SELECT
- id,
- ts,
- end_ts - ts AS dur,
- utid,
- critical_path_id,
- critical_path_blocked_dur,
- critical_path_blocked_state,
- critical_path_blocked_function,
- critical_path_utid
- FROM span_starts;
+SELECT * FROM _critical_path_by_intervals!((SELECT $critical_path_utid AS utid, $ts as ts, $dur AS dur));
--- Limited thread_state view that will later be span joined with the |_thread_executing_span_graph|.
-CREATE PERFETTO VIEW _span_thread_state_view
-AS SELECT id AS thread_state_id, ts, dur, utid, state, blocked_function as function, io_wait, cpu FROM thread_state;
-
--- |_thread_executing_span_graph| span joined with thread_state information.
-CREATE VIRTUAL TABLE _span_graph_thread_state_sp
-USING
- SPAN_JOIN(
- _thread_executing_span_graph PARTITIONED utid,
- _span_thread_state_view PARTITIONED utid);
-
--- Limited slice_view that will later be span joined with the |_thread_executing_span_graph|.
-CREATE PERFETTO VIEW _span_slice_view
+-- Generates the critical path for all threads for the entire trace duration.
+-- The duration of a thread executing span in the critical path is the range between the
+-- start of the thread_executing_span and the start of the next span in the critical path.
+CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_all()
+RETURNS
+ TABLE(
+ -- Id of the first (runnable) thread state in thread_executing_span.
+ id INT,
+ -- Timestamp of first thread_state in thread_executing_span.
+ ts LONG,
+ -- Duration of thread_executing_span.
+ dur LONG,
+ -- Utid of thread with thread_state.
+ utid INT,
+ -- Id of thread executing span following the sleeping thread state for which the critical path is computed.
+ critical_path_id INT,
+ -- Critical path duration.
+ critical_path_blocked_dur LONG,
+ -- Sleeping thread state in critical path.
+ critical_path_blocked_state STRING,
+ -- Kernel blocked_function of the critical path.
+ critical_path_blocked_function STRING,
+ -- Thread Utid the critical path was filtered to.
+ critical_path_utid INT)
AS
SELECT
- slice_id,
- depth AS slice_depth,
- name AS slice_name,
- CAST(ts AS INT) AS ts,
- CAST(dur AS INT) AS dur,
- utid
-FROM _slice_flattened;
-
--- |_thread_executing_span_graph| span joined with slice information.
-CREATE VIRTUAL TABLE _span_graph_slice_sp
-USING
- SPAN_JOIN(
- _thread_executing_span_graph PARTITIONED utid,
- _span_slice_view PARTITIONED utid);
-
--- Limited |_thread_executing_span_graph| + thread_state view.
-CREATE PERFETTO VIEW _span_graph_thread_state
-AS
-SELECT ts, dur, id, thread_state_id, state, function, io_wait, cpu
-FROM _span_graph_thread_state_sp;
-
--- Limited |_thread_executing_span_graph| + slice view.
-CREATE PERFETTO VIEW _span_graph_slice
-AS
-SELECT ts, dur, id, slice_id, slice_depth, slice_name
-FROM _span_graph_slice_sp;
-
--- |_thread_executing_span_graph| + thread_state view joined with critical_path information.
-CREATE PERFETTO TABLE _critical_path_thread_state AS
-WITH span AS MATERIALIZED (
- SELECT * FROM _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,
- thread_state.io_wait
- FROM span
- JOIN _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,
+ dur,
utid,
- state,
- function,
- cpu,
- io_wait,
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;
-
--- |_thread_executing_span_graph| + thread_state + critical_path span joined with
--- |_thread_executing_span_graph| + slice view.
-CREATE VIRTUAL TABLE _critical_path_sp
-USING
- SPAN_LEFT_JOIN(
- _critical_path_thread_state PARTITIONED id,
- _span_graph_slice PARTITIONED id);
-
--- Flattened slices span joined with their thread_states. This contains the 'self' information
--- without 'critical_path' (blocking) information.
-CREATE VIRTUAL TABLE _self_sp USING
- SPAN_LEFT_JOIN(thread_state PARTITIONED utid, _slice_flattened PARTITIONED utid);
-
--- Limited view of |_self_sp|.
-CREATE PERFETTO VIEW _self_view
- AS
- SELECT
- id AS self_thread_state_id,
- slice_id AS self_slice_id,
- ts,
- dur,
- utid AS critical_path_utid,
- state AS self_state,
- blocked_function AS self_function,
- cpu AS self_cpu,
- io_wait AS self_io_wait,
- name AS self_slice_name,
- depth AS self_slice_depth
- FROM _self_sp;
-
--- Self and critical path span join. This contains the union of the time intervals from the following:
--- a. Self slice stack + thread_state.
--- b. Critical path stack + thread_state.
-CREATE VIRTUAL TABLE _self_and_critical_path_sp
-USING
- SPAN_JOIN(
- _self_view PARTITIONED critical_path_utid,
- _critical_path_sp PARTITIONED critical_path_utid);
-
--- Returns a view of |_self_and_critical_path_sp| unpivoted over the following columns:
--- self thread_state.
--- self blocked_function (if one exists).
--- self process_name (enabled with |enable_process_name|).
--- self thread_name (enabled with |enable_thread_name|).
--- self slice_stack (enabled with |enable_self_slice|).
--- critical_path thread_state.
--- critical_path process_name.
--- critical_path thread_name.
--- critical_path slice_stack (enabled with |enable_critical_path_slice|).
--- running cpu (if one exists).
--- A 'stack' is the group of resulting unpivoted rows sharing the same timestamp.
-CREATE PERFETTO FUNCTION _critical_path_stack(critical_path_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT)
-RETURNS
- TABLE(
- id INT,
- ts LONG,
- dur LONG,
- utid INT,
- stack_depth INT,
- name STRING,
- table_name STRING,
- critical_path_utid INT) AS
- -- Spans filtered to the query time window and critical_path_utid.
- -- This is a preliminary step that gets the start and end ts of all the rows
- -- so that we can chop the ends of each interval correctly if it overlaps with the query time interval.
- WITH relevant_spans_starts AS (
- SELECT
- self_thread_state_id,
- self_state,
- self_slice_id,
- self_slice_name,
- self_slice_depth,
- self_function,
- self_io_wait,
- thread_state_id,
- state,
- function,
- io_wait,
- slice_id,
- slice_name,
- slice_depth,
- cpu,
- utid,
- MAX(ts, $ts) AS ts,
- MIN(ts + dur, $ts + $dur) AS end_ts,
- critical_path_utid
- FROM _self_and_critical_path_sp
- WHERE dur > 0 AND critical_path_utid = $critical_path_utid
- ),
- -- This is the final step that gets the |dur| of each span from the start and
- -- and end ts of the previous step.
- -- Now we manually unpivot the result with 3 key steps: 1) Self 2) Critical path 3) CPU
- -- This CTE is heavily used throughout the entire function so materializing it is
- -- very important.
- relevant_spans AS MATERIALIZED (
- SELECT
- self_thread_state_id,
- self_state,
- self_slice_id,
- self_slice_name,
- self_slice_depth,
- self_function,
- self_io_wait,
- thread_state_id,
- state,
- function,
- io_wait,
- slice_id,
- slice_name,
- slice_depth,
- cpu,
- utid,
- ts,
- end_ts - ts AS dur,
- critical_path_utid,
- utid
- FROM relevant_spans_starts
- WHERE dur > 0
- ),
- -- 1. Builds the 'self' stack of items as an ordered UNION ALL
- self_stack AS MATERIALIZED (
- -- Builds the self thread_state
- SELECT
- self_thread_state_id AS id,
- ts,
- dur,
- utid,
- 0 AS stack_depth,
- 'thread_state: ' || self_state AS name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM relevant_spans
- UNION ALL
- -- Builds the self kernel blocked_function
- SELECT
- self_thread_state_id AS id,
- ts,
- dur,
- utid,
- 1 AS stack_depth,
- IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || self_function) AS name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM relevant_spans
- UNION ALL
- -- Builds the self kernel io_wait
- SELECT
- self_thread_state_id AS id,
- ts,
- dur,
- utid,
- 2 AS stack_depth,
- IIF(self_state GLOB 'R*', NULL, 'io_wait: ' || self_io_wait) AS name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM relevant_spans
- UNION ALL
- -- Builds the self process_name
- SELECT
- self_thread_state_id AS id,
- ts,
- dur,
- thread.utid,
- 3 AS stack_depth,
- IIF($enable_process_name, 'process_name: ' || process.name, NULL) AS name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM relevant_spans
- LEFT JOIN thread
- ON thread.utid = critical_path_utid
- LEFT JOIN process
- USING (upid)
- -- Builds the self thread_name
- UNION ALL
- SELECT
- self_thread_state_id AS id,
- ts,
- dur,
- thread.utid,
- 4 AS stack_depth,
- IIF($enable_thread_name, 'thread_name: ' || thread.name, NULL) AS name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM relevant_spans
- LEFT JOIN thread
- ON thread.utid = critical_path_utid
- JOIN process
- USING (upid)
- UNION ALL
- -- Builds the self 'ancestor' slice stack
- SELECT
- anc.id,
- slice.ts,
- slice.dur,
- slice.utid,
- anc.depth + 5 AS stack_depth,
- IIF($enable_self_slice, anc.name, NULL) AS name,
- 'slice' AS table_name,
- critical_path_utid
- FROM relevant_spans slice
- JOIN ancestor_slice(self_slice_id) anc WHERE anc.dur != -1
- UNION ALL
- -- Builds the self 'deepest' ancestor slice stack
- SELECT
- self_slice_id AS id,
- ts,
- dur,
- utid,
- self_slice_depth + 5 AS stack_depth,
- IIF($enable_self_slice, self_slice_name, NULL) AS name,
- 'slice' AS table_name,
- critical_path_utid
- FROM relevant_spans slice
- -- Ordering by stack depth is important to ensure the items can
- -- be renedered in the UI as a debug track in the order in which
- -- the sub-queries were 'unioned'.
- ORDER BY stack_depth
- ),
- -- Prepares for stage 2 in building the entire stack.
- -- Computes the starting depth for each stack. This is necessary because
- -- each self slice stack has variable depth and the depth in each stack
- -- most be contiguous in order to efficiently generate a pprof in the future.
- critical_path_start_depth AS MATERIALIZED (
- SELECT critical_path_utid, ts, MAX(stack_depth) + 1 AS start_depth
- FROM self_stack
- GROUP BY critical_path_utid, ts
- ),
- critical_path_span AS MATERIALIZED (
- SELECT
- thread_state_id,
- state,
- function,
- io_wait,
- slice_id,
- slice_name,
- slice_depth,
- spans.ts,
- spans.dur,
- spans.critical_path_utid,
- utid,
- start_depth
- FROM relevant_spans spans
- JOIN critical_path_start_depth
- ON
- critical_path_start_depth.critical_path_utid = spans.critical_path_utid
- AND critical_path_start_depth.ts = spans.ts
- WHERE critical_path_start_depth.critical_path_utid = $critical_path_utid AND spans.critical_path_utid != spans.utid
- ),
- -- 2. Builds the 'critical_path' stack of items as an ordered UNION ALL
- critical_path_stack AS MATERIALIZED (
- -- Builds the critical_path thread_state
- SELECT
- thread_state_id AS id,
- ts,
- dur,
- utid,
- start_depth AS stack_depth,
- 'blocking thread_state: ' || state AS name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM critical_path_span
- UNION ALL
- -- Builds the critical_path process_name
- SELECT
- thread_state_id AS id,
- ts,
- dur,
- thread.utid,
- start_depth + 1 AS stack_depth,
- 'blocking process_name: ' || process.name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM critical_path_span
- JOIN thread USING (utid)
- LEFT JOIN process USING (upid)
- UNION ALL
- -- Builds the critical_path thread_name
- SELECT
- thread_state_id AS id,
- ts,
- dur,
- thread.utid,
- start_depth + 2 AS stack_depth,
- 'blocking thread_name: ' || thread.name,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM critical_path_span
- JOIN thread USING (utid)
- UNION ALL
- -- Builds the critical_path kernel blocked_function
- SELECT
- thread_state_id AS id,
- ts,
- dur,
- thread.utid,
- start_depth + 3 AS stack_depth,
- 'blocking kernel_function: ' || function,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM critical_path_span
- JOIN thread USING (utid)
- UNION ALL
- -- Builds the critical_path kernel io_wait
- SELECT
- thread_state_id AS id,
- ts,
- dur,
- thread.utid,
- start_depth + 4 AS stack_depth,
- 'blocking io_wait: ' || io_wait,
- 'thread_state' AS table_name,
- critical_path_utid
- FROM critical_path_span
- JOIN thread USING (utid)
- UNION ALL
- -- Builds the critical_path 'ancestor' slice stack
- SELECT
- anc.id,
- slice.ts,
- slice.dur,
- slice.utid,
- anc.depth + start_depth + 5 AS stack_depth,
- IIF($enable_critical_path_slice, anc.name, NULL) AS name,
- 'slice' AS table_name,
- critical_path_utid
- FROM critical_path_span slice
- JOIN ancestor_slice(slice_id) anc WHERE anc.dur != -1
- UNION ALL
- -- Builds the critical_path 'deepest' slice
- SELECT
- slice_id AS id,
- ts,
- dur,
- utid,
- slice_depth + start_depth + 5 AS stack_depth,
- IIF($enable_critical_path_slice, slice_name, NULL) AS name,
- 'slice' AS table_name,
- critical_path_utid
- FROM critical_path_span slice
- -- Ordering is also important as in the 'self' step above.
- ORDER BY stack_depth
- ),
- -- Prepares for stage 3 in building the entire stack.
- -- Computes the starting depth for each stack using the deepest stack_depth between
- -- the critical_path stack and self stack. The self stack depth is
- -- already computed and materialized in |critical_path_start_depth|.
- cpu_start_depth_raw AS (
- SELECT critical_path_utid, ts, MAX(stack_depth) + 1 AS start_depth
- FROM critical_path_stack
- GROUP BY critical_path_utid, ts
- UNION ALL
- SELECT * FROM critical_path_start_depth
- ),
- cpu_start_depth AS (
- SELECT critical_path_utid, ts, MAX(start_depth) AS start_depth
- FROM cpu_start_depth_raw
- GROUP BY critical_path_utid, ts
- ),
- -- 3. Builds the 'CPU' stack for 'Running' states in either the self or critical path stack.
- cpu_stack AS (
- SELECT
- thread_state_id AS id,
- spans.ts,
- spans.dur,
- utid,
- start_depth AS stack_depth,
- 'cpu: ' || cpu AS name,
- 'thread_state' AS table_name,
- spans.critical_path_utid
- FROM relevant_spans spans
- JOIN cpu_start_depth
- ON
- cpu_start_depth.critical_path_utid = spans.critical_path_utid
- AND cpu_start_depth.ts = spans.ts
- WHERE cpu_start_depth.critical_path_utid = $critical_path_utid AND state = 'Running' OR self_state = 'Running'
- ),
- merged AS (
- SELECT * FROM self_stack
- UNION ALL
- SELECT * FROM critical_path_stack
- UNION ALL
- SELECT * FROM cpu_stack
- )
-SELECT * FROM merged WHERE id IS NOT NULL;
-
--- Critical path stack of thread_executing_spans with the following entities in the critical path
--- stacked from top to bottom: self thread_state, self blocked_function, self process_name,
--- self thread_name, slice stack, critical_path thread_state, critical_path process_name,
--- critical_path thread_name, critical_path slice_stack, running_cpu.
-CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_stack(
- -- Thread utid to filter critical paths to.
- critical_path_utid INT,
- -- Timestamp of start of time range to filter critical paths to.
- ts LONG,
- -- Duration of time range to filter critical paths to.
- dur LONG)
-RETURNS
- TABLE(
- -- Id of the thread_state or slice in the thread_executing_span.
- id INT,
- -- Timestamp of slice in the critical path.
- ts LONG,
- -- Duration of slice in the critical path.
- dur LONG,
- -- Utid of thread that emitted the slice.
- utid INT,
- -- Stack depth of the entitity in the debug track.
- stack_depth INT,
- -- Name of entity in the critical path (could be a thread_state, kernel blocked_function, process_name, thread_name, slice name or cpu).
- name STRING,
- -- Table name of entity in the critical path (could be either slice or thread_state).
- table_name STRING,
- -- Utid of the thread the critical path was filtered to.
- critical_path_utid INT
-) AS
-SELECT * FROM _critical_path_stack($critical_path_utid, $ts, $dur, 1, 1, 1, 1);
-
--- Returns a pprof aggregation of the stacks in |_critical_path_stack|.
-CREATE PERFETTO FUNCTION _critical_path_graph(graph_title STRING, critical_path_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT)
-RETURNS TABLE(pprof BYTES)
-AS
-WITH
- stack AS MATERIALIZED (
- SELECT
- ts,
- dur - IFNULL(LEAD(dur) OVER (PARTITION BY critical_path_utid, ts ORDER BY stack_depth), 0) AS dur,
- name,
- utid,
- critical_path_utid,
- stack_depth
- FROM
- _critical_path_stack($critical_path_utid, $ts, $dur, $enable_process_name, $enable_thread_name, $enable_self_slice, $enable_critical_path_slice)
- ),
- graph AS (
- SELECT CAT_STACKS($graph_title) AS stack
- ),
- parent AS (
- SELECT
- cr.ts,
- cr.dur,
- cr.name,
- cr.utid,
- cr.stack_depth,
- CAT_STACKS(graph.stack, cr.name) AS stack,
- cr.critical_path_utid
- FROM stack cr, graph
- WHERE stack_depth = 0
- UNION ALL
- SELECT
- child.ts,
- child.dur,
- child.name,
- child.utid,
- child.stack_depth,
- CAT_STACKS(stack, child.name) AS stack,
- child.critical_path_utid
- FROM stack child
- JOIN parent
- ON
- parent.critical_path_utid = child.critical_path_utid
- AND parent.ts = child.ts
- AND child.stack_depth = parent.stack_depth + 1
- ),
- stacks AS (
- SELECT dur, stack FROM parent
- )
-SELECT EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', dur) AS pprof FROM stacks;
-
--- Returns a pprof aggreagation of the stacks in |_thread_executing_span_critical_path_stack|
-CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_graph(
- -- Descriptive name for the graph.
- graph_title STRING,
- -- Thread utid to filter critical paths to.
- critical_path_utid INT,
- -- Timestamp of start of time range to filter critical paths to.
- ts INT,
- -- Duration of time range to filter critical paths to.
- dur INT)
-RETURNS TABLE(
- -- Pprof of critical path stacks.
- pprof BYTES
-)
-AS
-SELECT * FROM _critical_path_graph($graph_title, $critical_path_utid, $ts, $dur, 1, 1, 1, 1);
+FROM _critical_path_by_roots!((SELECT id FROM _wakeup_graph));
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql
new file mode 100644
index 0000000..89085f3
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql
@@ -0,0 +1,573 @@
+--
+-- Copyright 2024 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 slices.flat_slices;
+INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+CREATE PERFETTO TABLE _critical_path_all AS
+SELECT * FROM _thread_executing_span_critical_path_all();
+
+-- Limited thread_state view that will later be span joined with the |_thread_executing_span_graph|.
+CREATE PERFETTO VIEW _span_thread_state_view
+AS SELECT id AS thread_state_id, ts, dur, utid, state, blocked_function as function, io_wait, cpu FROM thread_state;
+
+-- Limited slice_view that will later be span joined with the |_thread_executing_span_graph|.
+CREATE PERFETTO VIEW _span_slice_view
+AS
+SELECT
+ slice_id,
+ depth AS slice_depth,
+ name AS slice_name,
+ CAST(ts AS INT) AS ts,
+ CAST(dur AS INT) AS dur,
+ utid
+FROM _slice_flattened;
+
+CREATE VIRTUAL TABLE _span_thread_state_slice_view
+USING
+ SPAN_LEFT_JOIN(
+ _span_thread_state_view PARTITIONED utid,
+ _span_slice_view PARTITIONED utid);
+
+-- |_thread_executing_span_graph| span joined with thread_state information.
+CREATE VIRTUAL TABLE _span_critical_path_thread_state_slice_sp
+USING
+ SPAN_JOIN(
+ _critical_path_all PARTITIONED utid,
+ _span_thread_state_slice_view PARTITIONED utid);
+
+-- |_thread_executing_span_graph| + thread_state view joined with critical_path information.
+CREATE PERFETTO TABLE _critical_path_thread_state_slice AS
+WITH 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,
+ thread_state.io_wait,
+ thread_state.slice_id,
+ thread_state.slice_name,
+ thread_state.slice_depth
+ FROM _critical_path_all span
+ JOIN _span_critical_path_thread_state_slice_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,
+ io_wait,
+ slice_id,
+ slice_name,
+ slice_depth,
+ 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;
+
+-- Flattened slices span joined with their thread_states. This contains the 'self' information
+-- without 'critical_path' (blocking) information.
+CREATE VIRTUAL TABLE _self_sp USING
+ SPAN_LEFT_JOIN(thread_state PARTITIONED utid, _slice_flattened PARTITIONED utid);
+
+-- Limited view of |_self_sp|.
+CREATE PERFETTO VIEW _self_view
+ AS
+ SELECT
+ id AS self_thread_state_id,
+ slice_id AS self_slice_id,
+ ts,
+ dur,
+ utid AS critical_path_utid,
+ state AS self_state,
+ blocked_function AS self_function,
+ cpu AS self_cpu,
+ io_wait AS self_io_wait,
+ name AS self_slice_name,
+ depth AS self_slice_depth
+ FROM _self_sp;
+
+-- Self and critical path span join. This contains the union of the time intervals from the following:
+-- a. Self slice stack + thread_state.
+-- b. Critical path stack + thread_state.
+CREATE VIRTUAL TABLE _self_and_critical_path_sp
+USING
+ SPAN_JOIN(
+ _self_view PARTITIONED critical_path_utid,
+ _critical_path_thread_state_slice PARTITIONED critical_path_utid);
+
+-- Returns a view of |_self_and_critical_path_sp| unpivoted over the following columns:
+-- self thread_state.
+-- self blocked_function (if one exists).
+-- self process_name (enabled with |enable_process_name|).
+-- self thread_name (enabled with |enable_thread_name|).
+-- self slice_stack (enabled with |enable_self_slice|).
+-- critical_path thread_state.
+-- critical_path process_name.
+-- critical_path thread_name.
+-- critical_path slice_stack (enabled with |enable_critical_path_slice|).
+-- running cpu (if one exists).
+-- A 'stack' is the group of resulting unpivoted rows sharing the same timestamp.
+CREATE PERFETTO FUNCTION _critical_path_stack(critical_path_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT)
+RETURNS
+ TABLE(
+ id INT,
+ ts LONG,
+ dur LONG,
+ utid INT,
+ stack_depth INT,
+ name STRING,
+ table_name STRING,
+ critical_path_utid INT) AS
+ -- Spans filtered to the query time window and critical_path_utid.
+ -- This is a preliminary step that gets the start and end ts of all the rows
+ -- so that we can chop the ends of each interval correctly if it overlaps with the query time interval.
+ WITH relevant_spans_starts AS (
+ SELECT
+ self_thread_state_id,
+ self_state,
+ self_slice_id,
+ self_slice_name,
+ self_slice_depth,
+ self_function,
+ self_io_wait,
+ thread_state_id,
+ state,
+ function,
+ io_wait,
+ slice_id,
+ slice_name,
+ slice_depth,
+ cpu,
+ utid,
+ MAX(ts, $ts) AS ts,
+ MIN(ts + dur, $ts + $dur) AS end_ts,
+ critical_path_utid
+ FROM _self_and_critical_path_sp
+ WHERE dur > 0 AND critical_path_utid = $critical_path_utid
+ ),
+ -- This is the final step that gets the |dur| of each span from the start and
+ -- and end ts of the previous step.
+ -- Now we manually unpivot the result with 3 key steps: 1) Self 2) Critical path 3) CPU
+ -- This CTE is heavily used throughout the entire function so materializing it is
+ -- very important.
+ relevant_spans AS MATERIALIZED (
+ SELECT
+ self_thread_state_id,
+ self_state,
+ self_slice_id,
+ self_slice_name,
+ self_slice_depth,
+ self_function,
+ self_io_wait,
+ thread_state_id,
+ state,
+ function,
+ io_wait,
+ slice_id,
+ slice_name,
+ slice_depth,
+ cpu,
+ utid,
+ ts,
+ end_ts - ts AS dur,
+ critical_path_utid,
+ utid
+ FROM relevant_spans_starts
+ WHERE dur > 0
+ ),
+ -- 1. Builds the 'self' stack of items as an ordered UNION ALL
+ self_stack AS MATERIALIZED (
+ -- Builds the self thread_state
+ SELECT
+ self_thread_state_id AS id,
+ ts,
+ dur,
+ utid,
+ 0 AS stack_depth,
+ 'thread_state: ' || self_state AS name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM relevant_spans
+ UNION ALL
+ -- Builds the self kernel blocked_function
+ SELECT
+ self_thread_state_id AS id,
+ ts,
+ dur,
+ utid,
+ 1 AS stack_depth,
+ IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || self_function) AS name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM relevant_spans
+ UNION ALL
+ -- Builds the self kernel io_wait
+ SELECT
+ self_thread_state_id AS id,
+ ts,
+ dur,
+ utid,
+ 2 AS stack_depth,
+ IIF(self_state GLOB 'R*', NULL, 'io_wait: ' || self_io_wait) AS name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM relevant_spans
+ UNION ALL
+ -- Builds the self process_name
+ SELECT
+ self_thread_state_id AS id,
+ ts,
+ dur,
+ thread.utid,
+ 3 AS stack_depth,
+ IIF($enable_process_name, 'process_name: ' || process.name, NULL) AS name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM relevant_spans
+ LEFT JOIN thread
+ ON thread.utid = critical_path_utid
+ LEFT JOIN process
+ USING (upid)
+ -- Builds the self thread_name
+ UNION ALL
+ SELECT
+ self_thread_state_id AS id,
+ ts,
+ dur,
+ thread.utid,
+ 4 AS stack_depth,
+ IIF($enable_thread_name, 'thread_name: ' || thread.name, NULL) AS name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM relevant_spans
+ LEFT JOIN thread
+ ON thread.utid = critical_path_utid
+ JOIN process
+ USING (upid)
+ UNION ALL
+ -- Builds the self 'ancestor' slice stack
+ SELECT
+ anc.id,
+ slice.ts,
+ slice.dur,
+ slice.utid,
+ anc.depth + 5 AS stack_depth,
+ IIF($enable_self_slice, anc.name, NULL) AS name,
+ 'slice' AS table_name,
+ critical_path_utid
+ FROM relevant_spans slice
+ JOIN ancestor_slice(self_slice_id) anc WHERE anc.dur != -1
+ UNION ALL
+ -- Builds the self 'deepest' ancestor slice stack
+ SELECT
+ self_slice_id AS id,
+ ts,
+ dur,
+ utid,
+ self_slice_depth + 5 AS stack_depth,
+ IIF($enable_self_slice, self_slice_name, NULL) AS name,
+ 'slice' AS table_name,
+ critical_path_utid
+ FROM relevant_spans slice
+ -- Ordering by stack depth is important to ensure the items can
+ -- be renedered in the UI as a debug track in the order in which
+ -- the sub-queries were 'unioned'.
+ ORDER BY stack_depth
+ ),
+ -- Prepares for stage 2 in building the entire stack.
+ -- Computes the starting depth for each stack. This is necessary because
+ -- each self slice stack has variable depth and the depth in each stack
+ -- most be contiguous in order to efficiently generate a pprof in the future.
+ critical_path_start_depth AS MATERIALIZED (
+ SELECT critical_path_utid, ts, MAX(stack_depth) + 1 AS start_depth
+ FROM self_stack
+ GROUP BY critical_path_utid, ts
+ ),
+ critical_path_span AS MATERIALIZED (
+ SELECT
+ thread_state_id,
+ state,
+ function,
+ io_wait,
+ slice_id,
+ slice_name,
+ slice_depth,
+ spans.ts,
+ spans.dur,
+ spans.critical_path_utid,
+ utid,
+ start_depth
+ FROM relevant_spans spans
+ JOIN critical_path_start_depth
+ ON
+ critical_path_start_depth.critical_path_utid = spans.critical_path_utid
+ AND critical_path_start_depth.ts = spans.ts
+ WHERE critical_path_start_depth.critical_path_utid = $critical_path_utid AND spans.critical_path_utid != spans.utid
+ ),
+ -- 2. Builds the 'critical_path' stack of items as an ordered UNION ALL
+ critical_path_stack AS MATERIALIZED (
+ -- Builds the critical_path thread_state
+ SELECT
+ thread_state_id AS id,
+ ts,
+ dur,
+ utid,
+ start_depth AS stack_depth,
+ 'blocking thread_state: ' || state AS name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM critical_path_span
+ UNION ALL
+ -- Builds the critical_path process_name
+ SELECT
+ thread_state_id AS id,
+ ts,
+ dur,
+ thread.utid,
+ start_depth + 1 AS stack_depth,
+ 'blocking process_name: ' || process.name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM critical_path_span
+ JOIN thread USING (utid)
+ LEFT JOIN process USING (upid)
+ UNION ALL
+ -- Builds the critical_path thread_name
+ SELECT
+ thread_state_id AS id,
+ ts,
+ dur,
+ thread.utid,
+ start_depth + 2 AS stack_depth,
+ 'blocking thread_name: ' || thread.name,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM critical_path_span
+ JOIN thread USING (utid)
+ UNION ALL
+ -- Builds the critical_path kernel blocked_function
+ SELECT
+ thread_state_id AS id,
+ ts,
+ dur,
+ thread.utid,
+ start_depth + 3 AS stack_depth,
+ 'blocking kernel_function: ' || function,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM critical_path_span
+ JOIN thread USING (utid)
+ UNION ALL
+ -- Builds the critical_path kernel io_wait
+ SELECT
+ thread_state_id AS id,
+ ts,
+ dur,
+ thread.utid,
+ start_depth + 4 AS stack_depth,
+ 'blocking io_wait: ' || io_wait,
+ 'thread_state' AS table_name,
+ critical_path_utid
+ FROM critical_path_span
+ JOIN thread USING (utid)
+ UNION ALL
+ -- Builds the critical_path 'ancestor' slice stack
+ SELECT
+ anc.id,
+ slice.ts,
+ slice.dur,
+ slice.utid,
+ anc.depth + start_depth + 5 AS stack_depth,
+ IIF($enable_critical_path_slice, anc.name, NULL) AS name,
+ 'slice' AS table_name,
+ critical_path_utid
+ FROM critical_path_span slice
+ JOIN ancestor_slice(slice_id) anc WHERE anc.dur != -1
+ UNION ALL
+ -- Builds the critical_path 'deepest' slice
+ SELECT
+ slice_id AS id,
+ ts,
+ dur,
+ utid,
+ slice_depth + start_depth + 5 AS stack_depth,
+ IIF($enable_critical_path_slice, slice_name, NULL) AS name,
+ 'slice' AS table_name,
+ critical_path_utid
+ FROM critical_path_span slice
+ -- Ordering is also important as in the 'self' step above.
+ ORDER BY stack_depth
+ ),
+ -- Prepares for stage 3 in building the entire stack.
+ -- Computes the starting depth for each stack using the deepest stack_depth between
+ -- the critical_path stack and self stack. The self stack depth is
+ -- already computed and materialized in |critical_path_start_depth|.
+ cpu_start_depth_raw AS (
+ SELECT critical_path_utid, ts, MAX(stack_depth) + 1 AS start_depth
+ FROM critical_path_stack
+ GROUP BY critical_path_utid, ts
+ UNION ALL
+ SELECT * FROM critical_path_start_depth
+ ),
+ cpu_start_depth AS (
+ SELECT critical_path_utid, ts, MAX(start_depth) AS start_depth
+ FROM cpu_start_depth_raw
+ GROUP BY critical_path_utid, ts
+ ),
+ -- 3. Builds the 'CPU' stack for 'Running' states in either the self or critical path stack.
+ cpu_stack AS (
+ SELECT
+ thread_state_id AS id,
+ spans.ts,
+ spans.dur,
+ utid,
+ start_depth AS stack_depth,
+ 'cpu: ' || cpu AS name,
+ 'thread_state' AS table_name,
+ spans.critical_path_utid
+ FROM relevant_spans spans
+ JOIN cpu_start_depth
+ ON
+ cpu_start_depth.critical_path_utid = spans.critical_path_utid
+ AND cpu_start_depth.ts = spans.ts
+ WHERE cpu_start_depth.critical_path_utid = $critical_path_utid AND state = 'Running' OR self_state = 'Running'
+ ),
+ merged AS (
+ SELECT * FROM self_stack
+ UNION ALL
+ SELECT * FROM critical_path_stack
+ UNION ALL
+ SELECT * FROM cpu_stack
+ )
+SELECT * FROM merged WHERE id IS NOT NULL;
+
+-- Critical path stack of thread_executing_spans with the following entities in the critical path
+-- stacked from top to bottom: self thread_state, self blocked_function, self process_name,
+-- self thread_name, slice stack, critical_path thread_state, critical_path process_name,
+-- critical_path thread_name, critical_path slice_stack, running_cpu.
+CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_stack(
+ -- Thread utid to filter critical paths to.
+ critical_path_utid INT,
+ -- Timestamp of start of time range to filter critical paths to.
+ ts LONG,
+ -- Duration of time range to filter critical paths to.
+ dur LONG)
+RETURNS
+ TABLE(
+ -- Id of the thread_state or slice in the thread_executing_span.
+ id INT,
+ -- Timestamp of slice in the critical path.
+ ts LONG,
+ -- Duration of slice in the critical path.
+ dur LONG,
+ -- Utid of thread that emitted the slice.
+ utid INT,
+ -- Stack depth of the entitity in the debug track.
+ stack_depth INT,
+ -- Name of entity in the critical path (could be a thread_state, kernel blocked_function, process_name, thread_name, slice name or cpu).
+ name STRING,
+ -- Table name of entity in the critical path (could be either slice or thread_state).
+ table_name STRING,
+ -- Utid of the thread the critical path was filtered to.
+ critical_path_utid INT
+) AS
+SELECT * FROM _critical_path_stack($critical_path_utid, $ts, $dur, 1, 1, 1, 1);
+
+-- Returns a pprof aggregation of the stacks in |_critical_path_stack|.
+CREATE PERFETTO FUNCTION _critical_path_graph(graph_title STRING, critical_path_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT)
+RETURNS TABLE(pprof BYTES)
+AS
+WITH
+ stack AS MATERIALIZED (
+ SELECT
+ ts,
+ dur - IFNULL(LEAD(dur) OVER (PARTITION BY critical_path_utid, ts ORDER BY stack_depth), 0) AS dur,
+ name,
+ utid,
+ critical_path_utid,
+ stack_depth
+ FROM
+ _critical_path_stack($critical_path_utid, $ts, $dur, $enable_process_name, $enable_thread_name, $enable_self_slice, $enable_critical_path_slice)
+ ),
+ graph AS (
+ SELECT CAT_STACKS($graph_title) AS stack
+ ),
+ parent AS (
+ SELECT
+ cr.ts,
+ cr.dur,
+ cr.name,
+ cr.utid,
+ cr.stack_depth,
+ CAT_STACKS(graph.stack, cr.name) AS stack,
+ cr.critical_path_utid
+ FROM stack cr, graph
+ WHERE stack_depth = 0
+ UNION ALL
+ SELECT
+ child.ts,
+ child.dur,
+ child.name,
+ child.utid,
+ child.stack_depth,
+ CAT_STACKS(stack, child.name) AS stack,
+ child.critical_path_utid
+ FROM stack child
+ JOIN parent
+ ON
+ parent.critical_path_utid = child.critical_path_utid
+ AND parent.ts = child.ts
+ AND child.stack_depth = parent.stack_depth + 1
+ ),
+ stacks AS (
+ SELECT dur, stack FROM parent
+ )
+SELECT EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', dur) AS pprof FROM stacks;
+
+-- Returns a pprof aggreagation of the stacks in |_thread_executing_span_critical_path_stack|
+CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_graph(
+ -- Descriptive name for the graph.
+ graph_title STRING,
+ -- Thread utid to filter critical paths to.
+ critical_path_utid INT,
+ -- Timestamp of start of time range to filter critical paths to.
+ ts INT,
+ -- Duration of time range to filter critical paths to.
+ dur INT)
+RETURNS TABLE(
+ -- Pprof of critical path stacks.
+ pprof BYTES
+)
+AS
+SELECT * FROM _critical_path_graph($graph_title, $critical_path_utid, $ts, $dur, 1, 1, 1, 1);
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index fe67ac8..9f67428 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -42,6 +42,10 @@
"proto_util.h",
"prune_package_list.cc",
"prune_package_list.h",
+ "redact_sched_switch.cc",
+ "redact_sched_switch.h",
+ "redact_sched_waking.cc",
+ "redact_sched_waking.h",
"scrub_ftrace_events.cc",
"scrub_ftrace_events.h",
"scrub_process_trees.cc",
@@ -61,9 +65,7 @@
"../../include/perfetto/ext/base",
"../../include/perfetto/protozero:protozero",
"../../include/perfetto/trace_processor:storage",
- "../../protos/perfetto/trace:non_minimal_cpp",
"../../protos/perfetto/trace:non_minimal_zero",
- "../../protos/perfetto/trace/android:cpp",
"../../protos/perfetto/trace/android:zero",
"../../protos/perfetto/trace/ftrace:zero",
"../../protos/perfetto/trace/ps:zero",
@@ -74,8 +76,11 @@
source_set("integrationtests") {
testonly = true
sources = [
+ "redact_sched_switch_integrationtest.cc",
+ "redact_sched_waking_integrationtest.cc",
"scrub_ftrace_events_integrationtest.cc",
"scrub_process_trees_integrationtest.cc",
+ "scrub_task_rename_integrationtest.cc",
"trace_redactor_integrationtest.cc",
]
deps = [
@@ -99,6 +104,8 @@
"process_thread_timeline_unittest.cc",
"proto_util_unittest.cc",
"prune_package_list_unittest.cc",
+ "redact_sched_switch_unittest.cc",
+ "redact_sched_waking_unittest.cc",
"scrub_ftrace_events_unittest.cc",
"scrub_task_rename_unittest.cc",
"scrub_trace_packet_unittest.cc",
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index e8cea13..767e90e 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -21,6 +21,8 @@
#include "src/trace_redaction/optimize_timeline.h"
#include "src/trace_redaction/populate_allow_lists.h"
#include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/redact_sched_switch.h"
+#include "src/trace_redaction/redact_sched_waking.h"
#include "src/trace_redaction/scrub_ftrace_events.h"
#include "src/trace_redaction/scrub_process_trees.h"
#include "src/trace_redaction/scrub_task_rename.h"
@@ -50,6 +52,8 @@
redactor.transformers()->emplace_back(new ScrubFtraceEvents());
redactor.transformers()->emplace_back(new ScrubProcessTrees());
redactor.transformers()->emplace_back(new ScrubTaskRename());
+ redactor.transformers()->emplace_back(new RedactSchedSwitch());
+ redactor.transformers()->emplace_back(new RedactSchedWaking());
Context context;
context.package_name = package_name;
diff --git a/src/trace_redaction/prune_package_list.cc b/src/trace_redaction/prune_package_list.cc
index a85a18f..de6331b 100644
--- a/src/trace_redaction/prune_package_list.cc
+++ b/src/trace_redaction/prune_package_list.cc
@@ -18,15 +18,29 @@
#include <string>
+#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
-#include "protos/perfetto/trace/android/packages_list.gen.h"
-#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
namespace perfetto::trace_redaction {
+namespace {
-PrunePackageList::PrunePackageList() = default;
-PrunePackageList::~PrunePackageList() = default;
+bool ShouldKeepPackageInfo(protozero::Field package_info, uint64_t uid) {
+ PERFETTO_DCHECK(package_info.id() ==
+ protos::pbzero::PackagesList::kPackagesFieldNumber);
+
+ protozero::ProtoDecoder decoder(package_info.as_bytes());
+ auto uid_field = decoder.FindField(
+ protos::pbzero::PackagesList::PackageInfo::kUidFieldNumber);
+
+ return uid_field.valid() &&
+ NormalizeUid(uid_field.as_uint64()) == NormalizeUid(uid);
+}
+
+} // namespace
base::Status PrunePackageList::Transform(const Context& context,
std::string* packet) const {
@@ -34,30 +48,47 @@
return base::ErrStatus("PrunePackageList: missing package uid.");
}
+ protozero::ProtoDecoder packet_decoder(*packet);
+
protos::pbzero::TracePacket::Decoder trace_packet_decoder(*packet);
- if (!trace_packet_decoder.has_packages_list()) {
+ auto package_list = packet_decoder.FindField(
+ protos::pbzero::TracePacket::kPackagesListFieldNumber);
+
+ if (!package_list.valid()) {
return base::OkStatus();
}
- auto normalized_uid = NormalizeUid(context.package_uid.value());
+ auto uid = context.package_uid.value();
- protos::gen::TracePacket mutable_packet;
- mutable_packet.ParseFromString(*packet);
+ protozero::HeapBuffered<protos::pbzero::TracePacket> packet_message;
- auto* packages = mutable_packet.mutable_packages_list()->mutable_packages();
+ for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+ packet_field = packet_decoder.ReadField()) {
+ if (packet_field.id() !=
+ protos::pbzero::TracePacket::kPackagesListFieldNumber) {
+ proto_util::AppendField(packet_field, packet_message.get());
+ continue;
+ }
- // Remove all entries that don't match the uid. After this, one or more
- // packages will be left in the list (multiple packages can share a uid).
- packages->erase(
- std::remove_if(
- packages->begin(), packages->end(),
- [normalized_uid](const protos::gen::PackagesList::PackageInfo& info) {
- return NormalizeUid(info.uid()) != normalized_uid;
- }),
- packages->end());
+ auto* package_list_message = packet_message->set_packages_list();
- packet->assign(mutable_packet.SerializeAsString());
+ protozero::ProtoDecoder package_list_decoder(packet_field.as_bytes());
+
+ for (auto package_field = package_list_decoder.ReadField();
+ package_field.valid();
+ package_field = package_list_decoder.ReadField()) {
+ // If not packages, keep.
+ // If packages and uid matches, keep.
+ if (package_field.id() !=
+ protos::pbzero::PackagesList::kPackagesFieldNumber ||
+ ShouldKeepPackageInfo(package_field, uid)) {
+ proto_util::AppendField(package_field, package_list_message);
+ }
+ }
+ }
+
+ packet->assign(packet_message.SerializeAsString());
return base::OkStatus();
}
diff --git a/src/trace_redaction/prune_package_list.h b/src/trace_redaction/prune_package_list.h
index 24d9ec2..ff5f060 100644
--- a/src/trace_redaction/prune_package_list.h
+++ b/src/trace_redaction/prune_package_list.h
@@ -28,9 +28,6 @@
// Returns `base::ErrStatus()` if `Context.package_uid` was not set.
class PrunePackageList final : public TransformPrimitive {
public:
- PrunePackageList();
- ~PrunePackageList() override;
-
base::Status Transform(const Context& context,
std::string* packet) const override;
};
diff --git a/src/trace_redaction/redact_sched_switch.cc b/src/trace_redaction/redact_sched_switch.cc
new file mode 100644
index 0000000..b70ef7a
--- /dev/null
+++ b/src/trace_redaction/redact_sched_switch.cc
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 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_redaction/redact_sched_switch.h"
+
+#include <string>
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+// Redact sched switch trace events in an ftrace event bundle:
+//
+// event {
+// timestamp: 6702093744772646
+// pid: 0
+// sched_switch {
+// prev_comm: "swapper/0"
+// prev_pid: 0
+// prev_prio: 120
+// prev_state: 0
+// next_comm: "writer"
+// next_pid: 23020
+// next_prio: 96
+// }
+// }
+//
+// In the above message, it should be noted that "event.pid" will always be
+// equal to "event.sched_switch.prev_pid".
+//
+// "ftrace_event_bundle_message" is the ftrace event bundle (contains a
+// collection of ftrace event messages) because data in a sched_switch message
+// is needed in order to know if the event should be added to the bundle.
+void RedactSwitchEvent(
+ const Context& context,
+ protos::pbzero::FtraceEvent::Decoder event,
+ protos::pbzero::FtraceEventBundle* ftrace_event_bundle_message) {
+ PERFETTO_DCHECK(context.timeline);
+ PERFETTO_DCHECK(context.package_uid.has_value());
+ PERFETTO_DCHECK(event.has_sched_switch());
+ PERFETTO_DCHECK(ftrace_event_bundle_message);
+
+ // If there is no timestamp in the event, it is not possible to query the
+ // timeline. This is too risky to keep.
+ if (!event.has_timestamp()) {
+ return;
+ }
+
+ protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
+ event.sched_switch());
+
+ // There must be a prev pid and a next pid. Otherwise, the event is invalid.
+ // Dropping the event is the safest option.
+ if (!sched_switch.has_prev_pid() || !sched_switch.has_next_pid()) {
+ return;
+ }
+
+ auto uid = context.package_uid.value();
+
+ auto prev_slice =
+ context.timeline->Search(event.timestamp(), sched_switch.prev_pid());
+ auto next_slice =
+ context.timeline->Search(event.timestamp(), sched_switch.next_pid());
+
+ // Build a new event, clearing the comm values when needed.
+ auto* event_message = ftrace_event_bundle_message->add_event();
+
+ // Reset to scan event fields.
+ event.Reset();
+
+ for (auto event_field = event.ReadField(); event_field.valid();
+ event_field = event.ReadField()) {
+ // This primitive only needs to affect sched switch events.
+ if (event_field.id() !=
+ protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber) {
+ proto_util::AppendField(event_field, event_message);
+ continue;
+ }
+
+ // Reset to scan sched_switch fields.
+ sched_switch.Reset();
+
+ auto switch_message = event_message->set_sched_switch();
+
+ for (auto switch_field = sched_switch.ReadField(); switch_field.valid();
+ switch_field = sched_switch.ReadField()) {
+ switch (switch_field.id()) {
+ case protos::pbzero::SchedSwitchFtraceEvent::kPrevCommFieldNumber:
+ if (prev_slice.uid == uid) {
+ proto_util::AppendField(switch_field, switch_message);
+ }
+ break;
+
+ case protos::pbzero::SchedSwitchFtraceEvent::kNextCommFieldNumber: {
+ if (next_slice.uid == uid) {
+ proto_util::AppendField(switch_field, switch_message);
+ }
+ break;
+ }
+
+ default:
+ proto_util::AppendField(switch_field, switch_message);
+ break;
+ }
+ }
+ }
+}
+
+} // namespace
+
+base::Status RedactSchedSwitch::Transform(const Context& context,
+ std::string* packet) const {
+ if (packet == nullptr || packet->empty()) {
+ return base::ErrStatus("RedactSchedSwitch: null or empty packet.");
+ }
+
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("RedactSchedSwitch: missing packet uid.");
+ }
+
+ if (!context.timeline) {
+ return base::ErrStatus("RedactSchedSwitch: missing timeline.");
+ }
+
+ protozero::ProtoDecoder packet_decoder(*packet);
+
+ auto trace_event_bundle = packet_decoder.FindField(
+ protos::pbzero::TracePacket::kFtraceEventsFieldNumber);
+
+ if (!trace_event_bundle.valid()) {
+ return base::OkStatus();
+ }
+
+ protozero::HeapBuffered<protos::pbzero::TracePacket> packet_message;
+
+ for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+ packet_field = packet_decoder.ReadField()) {
+ if (packet_field.id() !=
+ protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
+ proto_util::AppendField(packet_field, packet_message.get());
+ continue;
+ }
+
+ protozero::ProtoDecoder bundle(packet_field.as_bytes());
+
+ auto* bundle_message = packet_message->set_ftrace_events();
+
+ for (auto field = bundle.ReadField(); field.valid();
+ field = bundle.ReadField()) {
+ if (field.id() != protos::pbzero::FtraceEventBundle::kEventFieldNumber) {
+ proto_util::AppendField(field, bundle_message);
+ continue;
+ }
+
+ protos::pbzero::FtraceEvent::Decoder ftrace_event(field.as_bytes());
+
+ if (ftrace_event.has_sched_switch()) {
+ RedactSwitchEvent(context, std::move(ftrace_event), bundle_message);
+ } else {
+ proto_util::AppendField(field, bundle_message);
+ }
+ }
+ }
+
+ *packet = packet_message.SerializeAsString();
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_switch.h b/src/trace_redaction/redact_sched_switch.h
new file mode 100644
index 0000000..e33527d
--- /dev/null
+++ b/src/trace_redaction/redact_sched_switch.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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_REDACTION_REDACT_SCHED_SWITCH_H_
+#define SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
+
+#include <string>
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Assumptions:
+// 1. This is a hot path (a lot of ftrace packets)
+// 2. Allocations are slower than CPU cycles.
+//
+// Redact sched switch is called "redact" and not "prune" or "scrub" because it
+// is not removing sched switch events, but rather removing information from
+// within the event.
+class RedactSchedSwitch final : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
diff --git a/src/trace_redaction/redact_sched_switch_integrationtest.cc b/src/trace_redaction/redact_sched_switch_integrationtest.cc
new file mode 100644
index 0000000..7cb516b
--- /dev/null
+++ b/src/trace_redaction/redact_sched_switch_integrationtest.cc
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 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 <cstdint>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/base/test/status_matchers.h"
+#include "src/base/test/tmp_dir_tree.h"
+#include "src/base/test/utils.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/redact_sched_switch.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr std::string_view kTracePath =
+ "test/data/trace-redaction-general.pftrace";
+constexpr std::string_view kPackageName =
+ "com.Unity.com.unity.multiplayer.samples.coop";
+
+class RedactSchedSwitchIntegrationTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ redactor_.collectors()->emplace_back(new FindPackageUid());
+ redactor_.collectors()->emplace_back(new BuildTimeline());
+ redactor_.builders()->emplace_back(new OptimizeTimeline());
+ redactor_.transformers()->emplace_back(new RedactSchedSwitch());
+
+ context_.package_name = kPackageName;
+
+ src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+
+ dest_trace_ = tmp_dir_.AbsolutePath("dst.pftrace");
+ tmp_dir_.TrackFile("dst.pftrace");
+ }
+
+ base::Status Redact() {
+ return redactor_.Redact(src_trace_, dest_trace_, &context_);
+ }
+
+ base::StatusOr<std::string> LoadOriginal() const {
+ return ReadRawTrace(src_trace_);
+ }
+
+ base::StatusOr<std::string> LoadRedacted() const {
+ return ReadRawTrace(dest_trace_);
+ }
+
+ private:
+ base::StatusOr<std::string> ReadRawTrace(const std::string& path) const {
+ std::string redacted_buffer;
+
+ if (base::ReadFile(path, &redacted_buffer)) {
+ return redacted_buffer;
+ }
+
+ return base::ErrStatus("Failed to read %s", path.c_str());
+ }
+
+ Context context_;
+ TraceRedactor redactor_;
+
+ base::TmpDirTree tmp_dir_;
+
+ std::string src_trace_;
+ std::string dest_trace_;
+};
+
+// >>> SELECT uid
+// >>> FROM package_list
+// >>> WHERE package_name='com.Unity.com.unity.multiplayer.samples.coop'
+//
+// +-------+
+// | uid |
+// +-------+
+// | 10252 |
+// +-------+
+//
+// >>> SELECT uid, upid, name
+// >>> FROM process
+// >>> WHERE uid=10252
+//
+// +-------+------+----------------------------------------------+
+// | uid | upid | name |
+// +-------+------+----------------------------------------------+
+// | 10252 | 843 | com.Unity.com.unity.multiplayer.samples.coop |
+// +-------+------+----------------------------------------------+
+//
+// >>> SELECT tid, name
+// >>> FROM thread
+// >>> WHERE upid=843 AND name IS NOT NULL
+//
+// +------+-----------------+
+// | tid | name |
+// +------+-----------------+
+// | 7120 | Binder:7105_2 |
+// | 7127 | UnityMain |
+// | 7142 | Job.worker 0 |
+// | 7143 | Job.worker 1 |
+// | 7144 | Job.worker 2 |
+// | 7145 | Job.worker 3 |
+// | 7146 | Job.worker 4 |
+// | 7147 | Job.worker 5 |
+// | 7148 | Job.worker 6 |
+// | 7150 | Background Job. |
+// | 7151 | Background Job. |
+// | 7167 | UnityGfxDeviceW |
+// | 7172 | AudioTrack |
+// | 7174 | FMOD stream thr |
+// | 7180 | Binder:7105_3 |
+// | 7184 | UnityChoreograp |
+// | 7945 | Filter0 |
+// | 7946 | Filter1 |
+// | 7947 | Thread-7 |
+// | 7948 | FMOD mixer thre |
+// | 7950 | UnityGfxDeviceW |
+// | 7969 | UnityGfxDeviceW |
+// +------+-----------------+
+
+TEST_F(RedactSchedSwitchIntegrationTest, ClearsNonTargetSwitchComms) {
+ auto result = Redact();
+ ASSERT_OK(result) << result.c_message();
+
+ auto original = LoadOriginal();
+ ASSERT_OK(original) << original.status().c_message();
+
+ auto redacted = LoadRedacted();
+ ASSERT_OK(redacted) << redacted.status().c_message();
+
+ base::FlatHashMap<int32_t, std::string> expected_names;
+ expected_names.Insert(7120, "Binder:7105_2");
+ expected_names.Insert(7127, "UnityMain");
+ expected_names.Insert(7142, "Job.worker 0");
+ expected_names.Insert(7143, "Job.worker 1");
+ expected_names.Insert(7144, "Job.worker 2");
+ expected_names.Insert(7145, "Job.worker 3");
+ expected_names.Insert(7146, "Job.worker 4");
+ expected_names.Insert(7147, "Job.worker 5");
+ expected_names.Insert(7148, "Job.worker 6");
+ expected_names.Insert(7150, "Background Job.");
+ expected_names.Insert(7151, "Background Job.");
+ expected_names.Insert(7167, "UnityGfxDeviceW");
+ expected_names.Insert(7172, "AudioTrack");
+ expected_names.Insert(7174, "FMOD stream thr");
+ expected_names.Insert(7180, "Binder:7105_3");
+ expected_names.Insert(7184, "UnityChoreograp");
+ expected_names.Insert(7945, "Filter0");
+ expected_names.Insert(7946, "Filter1");
+ expected_names.Insert(7947, "Thread-7");
+ expected_names.Insert(7948, "FMOD mixer thre");
+ expected_names.Insert(7950, "UnityGfxDeviceW");
+ expected_names.Insert(7969, "UnityGfxDeviceW");
+
+ auto redacted_trace_data = LoadRedacted();
+ ASSERT_OK(redacted_trace_data) << redacted.status().c_message();
+
+ protos::pbzero::Trace::Decoder decoder(redacted_trace_data.value());
+
+ for (auto packet = decoder.packet(); packet; ++packet) {
+ protos::pbzero::TracePacket::Decoder packet_decoder(*packet);
+
+ if (!packet_decoder.has_ftrace_events()) {
+ continue;
+ }
+
+ protos::pbzero::FtraceEventBundle::Decoder ftrace_events_decoder(
+ packet_decoder.ftrace_events());
+
+ for (auto event = ftrace_events_decoder.event(); event; ++event) {
+ protos::pbzero::FtraceEvent::Decoder event_decoder(*event);
+
+ if (!event_decoder.has_sched_switch()) {
+ continue;
+ }
+
+ protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_decoder(
+ event_decoder.sched_switch());
+
+ ASSERT_TRUE(sched_decoder.has_next_pid());
+ ASSERT_TRUE(sched_decoder.has_prev_pid());
+
+ auto next_pid = sched_decoder.next_pid();
+ auto prev_pid = sched_decoder.prev_pid();
+
+ // If the pid is expected, make sure it has the right now. If it is not
+ // expected, it should be missing.
+ const auto* next_comm = expected_names.Find(next_pid);
+ const auto* prev_comm = expected_names.Find(prev_pid);
+
+ if (next_comm) {
+ EXPECT_TRUE(sched_decoder.has_next_comm());
+ EXPECT_EQ(sched_decoder.next_comm().ToStdString(), *next_comm);
+ } else {
+ EXPECT_FALSE(sched_decoder.has_next_comm());
+ }
+
+ if (prev_comm) {
+ EXPECT_TRUE(sched_decoder.has_prev_comm());
+ EXPECT_EQ(sched_decoder.prev_comm().ToStdString(), *prev_comm);
+ } else {
+ EXPECT_FALSE(sched_decoder.has_prev_comm());
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_switch_unittest.cc b/src/trace_redaction/redact_sched_switch_unittest.cc
new file mode 100644
index 0000000..5f74078
--- /dev/null
+++ b/src/trace_redaction/redact_sched_switch_unittest.cc
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2024 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_redaction/redact_sched_switch.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
+#include "protos/perfetto/trace/ftrace/sched.gen.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+constexpr uint64_t kUidA = 1;
+constexpr uint64_t kUidB = 2;
+constexpr uint64_t kUidC = 3;
+
+constexpr int32_t kNoParent = 10;
+constexpr int32_t kPidA = 11;
+constexpr int32_t kPidB = 12;
+constexpr int32_t kPidC = 13;
+
+constexpr std::string_view kCommA = "comm-a";
+constexpr std::string_view kCommB = "comm-b";
+constexpr std::string_view kCommC = "comm-c";
+
+constexpr uint64_t kTimeA = 100;
+constexpr uint64_t kTimeB = 200;
+constexpr uint64_t kTimeC = 300;
+
+} // namespace
+
+// Tests which nested messages and fields are removed.
+class RedactSchedSwitchTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ context_.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ // Three concurrent processes. No parent. All in different packages.
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(0, kPidA, kNoParent, kUidA));
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(0, kPidB, kNoParent, kUidB));
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(0, kPidC, kNoParent, kUidC));
+
+ context_.timeline->Sort();
+ }
+
+ void BeginBundle() { ftrace_bundle_ = trace_packet_.mutable_ftrace_events(); }
+
+ void AddSwitch(uint64_t ts,
+ int32_t prev_pid,
+ std::string_view prev_comm,
+ int32_t next_pid,
+ std::string_view next_comm) {
+ ASSERT_NE(ftrace_bundle_, nullptr);
+
+ auto* event = ftrace_bundle_->add_event();
+ event->set_timestamp(ts);
+
+ auto* sched_switch = event->mutable_sched_switch();
+ sched_switch->set_prev_pid(prev_pid);
+ sched_switch->set_prev_comm(std::string(prev_comm));
+ sched_switch->set_next_pid(next_pid);
+ sched_switch->set_next_comm(std::string(next_comm));
+ }
+
+ base::StatusOr<protos::gen::TracePacket> Transform() {
+ auto packet = trace_packet_.SerializeAsString();
+ auto result = transform_.Transform(context_, &packet);
+
+ if (!result.ok()) {
+ return result;
+ }
+
+ protos::gen::TracePacket redacted_packet;
+ redacted_packet.ParseFromString(packet);
+
+ return redacted_packet;
+ }
+
+ Context context_;
+
+ const RedactSchedSwitch& transform() const { return transform_; }
+
+ private:
+ protos::gen::TracePacket trace_packet_;
+ protos::gen::FtraceEventBundle* ftrace_bundle_;
+
+ RedactSchedSwitch transform_;
+};
+
+TEST_F(RedactSchedSwitchTest, ReturnsErrorForNullPacket) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = kUidA;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ ASSERT_FALSE(transform().Transform(context, nullptr).ok());
+}
+
+TEST_F(RedactSchedSwitchTest, ReturnsErrorForEmptyPacket) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = kUidA;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ std::string packet_str = "";
+
+ ASSERT_FALSE(transform().Transform(context, &packet_str).ok());
+}
+
+TEST_F(RedactSchedSwitchTest, ReturnsErrorForNoTimeline) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = kUidA;
+
+ protos::gen::TracePacket packet;
+ std::string packet_str = packet.SerializeAsString();
+
+ ASSERT_FALSE(transform().Transform(context, &packet_str).ok());
+}
+
+TEST_F(RedactSchedSwitchTest, ReturnsErrorForNoPackage) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ protos::gen::TracePacket packet;
+ std::string packet_str = packet.SerializeAsString();
+
+ ASSERT_FALSE(transform().Transform(context, &packet_str).ok());
+}
+
+TEST_F(RedactSchedSwitchTest, BundleWithNonEventChild) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context.package_uid = 0;
+
+ // packet {
+ // ftrace_events {
+ // cpu: 0
+ // event {
+ // timestamp: 6702093744772646
+ // pid: 0
+ // sched_switch {
+ // prev_comm: "swapper/0"
+ // prev_pid: 0
+ // prev_prio: 120
+ // prev_state: 0
+ // next_comm: "writer"
+ // next_pid: 23020
+ // next_prio: 96
+ // }
+ // }
+ // }
+ // }
+
+ protos::gen::TracePacket packet;
+ auto* events = packet.mutable_ftrace_events();
+ events->set_cpu(0);
+
+ auto* event = events->add_event();
+ event->set_timestamp(kPidA);
+ event->set_pid(kPidA);
+
+ auto* sched_switch = event->mutable_sched_switch();
+ sched_switch->set_prev_comm("swapper/0");
+ sched_switch->set_prev_pid(kPidA);
+ sched_switch->set_prev_prio(120);
+ sched_switch->set_prev_state(0);
+
+ sched_switch->set_next_comm("writer");
+ sched_switch->set_next_pid(kPidB);
+ sched_switch->set_next_prio(96);
+
+ std::string packet_str = packet.SerializeAsString();
+
+ ASSERT_TRUE(transform().Transform(context, &packet_str).ok());
+
+ protos::gen::TracePacket redacted;
+ redacted.ParseFromString(packet_str);
+
+ // Make sure values alongside the "event" value (e.g. "cpu") are retained.
+ ASSERT_TRUE(redacted.has_ftrace_events());
+ ASSERT_TRUE(redacted.ftrace_events().has_cpu());
+}
+
+// There are more than sched_switch events in the ftrace_events message.
+// Beyond supporting simple fields along side the event (e.g. cpu), not all
+// events will contain sched_switch events. Make sure that all every message is
+// retained while redacting the sched_switch.
+TEST_F(RedactSchedSwitchTest, KeepsNonSwitchEvents) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = 2;
+
+ // Keep the previous PID and remove the next PID.
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(0, 0, 1, 2));
+ context.timeline->Sort();
+
+ // packet {
+ // ftrace_events {
+ // cpu: 0
+ // event {
+ // timestamp: 6702093744766292
+ // pid: 0
+ // cpu_idle {
+ // state: 4294967295
+ // cpu_id: 0
+ // }
+ // }
+ // event {
+ // timestamp: 6702093744772646
+ // pid: 0
+ // sched_switch {
+ // prev_comm: "swapper/0"
+ // prev_pid: 0
+ // prev_prio: 120
+ // prev_state: 0
+ // next_comm: "writer"
+ // next_pid: 23020
+ // next_prio: 96
+ // }
+ // }
+ // event {
+ // timestamp: 6702093744803376
+ // pid: 23020
+ // sched_waking {
+ // comm: "FastMixer"
+ // pid: 1619
+ // prio: 96
+ // success: 1
+ // target_cpu: 1
+ // }
+ // }
+ // }
+ // }
+
+ protos::gen::TracePacket source_packet;
+ source_packet.mutable_ftrace_events()->set_cpu(0);
+
+ // cpu_idle
+ do {
+ auto* event = source_packet.mutable_ftrace_events()->add_event();
+ event->set_timestamp(6702093744766292);
+ event->set_pid(0);
+
+ auto* cpu_idle = event->mutable_cpu_idle();
+ cpu_idle->set_state(4294967295);
+ cpu_idle->set_cpu_id(0);
+ } while (false);
+
+ // sched_switch
+ do {
+ auto* event = source_packet.mutable_ftrace_events()->add_event();
+ event->set_timestamp(6702093744772646);
+ event->set_pid(0);
+
+ auto* sched_switch = event->mutable_sched_switch();
+ sched_switch->set_prev_comm("swapper/0");
+ sched_switch->set_prev_pid(0);
+ sched_switch->set_prev_prio(120);
+ sched_switch->set_prev_state(0);
+ sched_switch->set_next_comm("writer");
+ sched_switch->set_next_pid(23020);
+ sched_switch->set_next_prio(96);
+ } while (false);
+
+ // sched_waking
+ do {
+ auto* event = source_packet.mutable_ftrace_events()->add_event();
+ event->set_timestamp(6702093744803376);
+ event->set_pid(23020);
+
+ auto* sched_waking = event->mutable_sched_waking();
+ sched_waking->set_comm("FastMixer");
+ sched_waking->set_pid(1619);
+ sched_waking->set_prio(96);
+ sched_waking->set_success(1);
+ sched_waking->set_target_cpu(1);
+ } while (false);
+
+ auto packet_str = source_packet.SerializeAsString();
+
+ ASSERT_TRUE(transform().Transform(context, &packet_str).ok());
+
+ protos::gen::TracePacket packet;
+ source_packet.ParseFromString(packet_str);
+
+ // Make sure values alongside the "event" value (e.g. "cpu") are retained.
+ ASSERT_TRUE(source_packet.has_ftrace_events());
+
+ auto& ftrace_packets = source_packet.ftrace_events();
+
+ ASSERT_TRUE(ftrace_packets.has_cpu());
+ ASSERT_EQ(ftrace_packets.cpu(), 0u);
+
+ // Assumes order is retained.
+ ASSERT_EQ(ftrace_packets.event_size(), 3);
+ ASSERT_TRUE(ftrace_packets.event().at(0).has_cpu_idle());
+ ASSERT_TRUE(ftrace_packets.event().at(1).has_sched_switch());
+ ASSERT_TRUE(ftrace_packets.event().at(2).has_sched_waking());
+
+ // The sched switch event's next comm should be cleared.
+ const auto& sched_switch = ftrace_packets.event().at(1).sched_switch();
+
+ ASSERT_TRUE(sched_switch.has_prev_comm());
+ ASSERT_EQ(sched_switch.prev_comm(), "swapper/0");
+
+ ASSERT_FALSE(sched_switch.has_next_comm());
+}
+
+class CommTestParams {
+ public:
+ CommTestParams(size_t event_index,
+ int32_t prev_pid,
+ std::optional<std::string_view> prev_comm,
+ int32_t next_pid,
+ std::optional<std::string_view> next_comm)
+ : event_index_(event_index),
+ prev_pid_(prev_pid),
+ prev_comm_(prev_comm),
+ next_pid_(next_pid),
+ next_comm_(next_comm) {}
+
+ size_t event_index() const { return event_index_; }
+
+ int32_t prev_pid() const { return prev_pid_; }
+
+ std::optional<std::string> prev_comm() const { return prev_comm_; }
+
+ int32_t next_pid() const { return next_pid_; }
+
+ std::optional<std::string> next_comm() const { return next_comm_; }
+
+ private:
+ size_t event_index_;
+
+ int32_t prev_pid_;
+ std::optional<std::string> prev_comm_;
+
+ int32_t next_pid_;
+ std::optional<std::string> next_comm_;
+};
+
+class RedactSchedSwitchTestRemoveComm
+ : public RedactSchedSwitchTest,
+ public testing::WithParamInterface<CommTestParams> {};
+
+TEST_P(RedactSchedSwitchTestRemoveComm, AllEvents) {
+ auto params = GetParam();
+
+ context_.package_uid = kUidA;
+
+ BeginBundle();
+
+ // Cycle through all the processes: Pid A -> Pid B -> Pid C -> Pid A
+ AddSwitch(kTimeA, kPidA, kCommA, kPidB, kCommB);
+ AddSwitch(kTimeB, kPidB, kCommB, kPidC, kCommC);
+ AddSwitch(kTimeC, kPidC, kCommC, kPidA, kCommA);
+
+ auto packet = Transform();
+
+ ASSERT_TRUE(packet->has_ftrace_events());
+
+ auto& ftrace_events = packet->ftrace_events().event();
+
+ ASSERT_EQ(ftrace_events.size(), 3u);
+
+ auto event_index = params.event_index();
+
+ ASSERT_TRUE(ftrace_events[event_index].has_sched_switch());
+
+ auto& sched_switch = ftrace_events[event_index].sched_switch();
+
+ ASSERT_EQ(sched_switch.prev_pid(), params.prev_pid());
+ ASSERT_EQ(sched_switch.next_pid(), params.next_pid());
+
+ ASSERT_EQ(sched_switch.has_prev_comm(), params.prev_comm().has_value());
+ ASSERT_EQ(sched_switch.has_next_comm(), params.next_comm().has_value());
+
+ if (sched_switch.has_prev_comm()) {
+ ASSERT_EQ(sched_switch.prev_comm(), params.prev_comm());
+ }
+
+ if (sched_switch.has_next_comm()) {
+ ASSERT_EQ(sched_switch.next_comm(), params.next_comm());
+ }
+}
+
+// Cycle through all the processes: Pid A -> Pid B -> Pid C -> Pid A
+//
+// Only kPidA is attached to kUidA, so it should be the only one with a comm
+// value.
+INSTANTIATE_TEST_SUITE_P(
+ EveryPid,
+ RedactSchedSwitchTestRemoveComm,
+ testing::Values(CommTestParams(0, kPidA, kCommA, kPidB, std::nullopt),
+ CommTestParams(1, kPidB, std::nullopt, kPidC, std::nullopt),
+ CommTestParams(2, kPidC, std::nullopt, kPidA, kCommA)));
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_waking.cc b/src/trace_redaction/redact_sched_waking.cc
new file mode 100644
index 0000000..ce5c26d
--- /dev/null
+++ b/src/trace_redaction/redact_sched_waking.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 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_redaction/redact_sched_waking.h"
+
+#include <string>
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+// Redact sched waking trace events in a ftrace event bundle:
+//
+// event {
+// timestamp: 6702093787823849
+// pid: 814
+// sched_waking {
+// comm: "surfaceflinger"
+// pid: 756
+// prio: 97
+// success: 1
+// target_cpu: 2
+// }
+// }
+//
+// The three values needed are:
+//
+// 1. event.pid
+// 2. event.timestamp
+// 3. event.sched_waking.pid
+//
+// The two checks that are executed are:
+//
+// 1. package(event.pid).at(event.timestamp).is(target)
+// 2. package(event.sched_waking.pid).at(event.timestamp).is(target)
+//
+// Both must be true in order to keep an event.
+bool KeepEvent(const Context& context, protozero::Field bundle_field) {
+ PERFETTO_DCHECK(context.timeline);
+ PERFETTO_DCHECK(context.package_uid.has_value());
+
+ PERFETTO_DCHECK(bundle_field.valid());
+ PERFETTO_DCHECK(bundle_field.id() ==
+ protos::pbzero::FtraceEventBundle::kEventFieldNumber);
+
+ protozero::ProtoDecoder event_decoder(bundle_field.as_bytes());
+
+ auto sched_waking = event_decoder.FindField(
+ protos::pbzero::FtraceEvent::kSchedWakingFieldNumber);
+
+ if (!sched_waking.valid()) {
+ return true;
+ }
+
+ auto timestamp = event_decoder.FindField(
+ protos::pbzero::FtraceEvent::kTimestampFieldNumber);
+
+ if (!timestamp.valid()) {
+ return false;
+ }
+
+ auto outer_pid =
+ event_decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber);
+
+ if (!outer_pid.valid()) {
+ return false;
+ }
+
+ auto outer_slice = context.timeline->Search(
+ timestamp.as_uint64(), static_cast<int32_t>(outer_pid.as_uint32()));
+
+ if (outer_slice.uid != context.package_uid.value()) {
+ return false;
+ }
+
+ protozero::ProtoDecoder waking_decoder(sched_waking.as_bytes());
+
+ auto inner_pid = waking_decoder.FindField(
+ protos::pbzero::SchedWakingFtraceEvent::kPidFieldNumber);
+
+ if (!inner_pid.valid()) {
+ return false;
+ }
+
+ auto inner_slice =
+ context.timeline->Search(timestamp.as_uint64(), inner_pid.as_int32());
+ return inner_slice.uid == context.package_uid.value();
+}
+
+} // namespace
+
+base::Status RedactSchedWaking::Transform(const Context& context,
+ std::string* packet) const {
+ if (packet == nullptr || packet->empty()) {
+ return base::ErrStatus("RedactSchedWaking: null or empty packet.");
+ }
+
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("RedactSchedWaking: missing packet uid.");
+ }
+
+ if (!context.timeline) {
+ return base::ErrStatus("RedactSchedWaking: missing timeline.");
+ }
+
+ protozero::ProtoDecoder packet_decoder(*packet);
+
+ auto trace_event_bundle = packet_decoder.FindField(
+ protos::pbzero::TracePacket::kFtraceEventsFieldNumber);
+
+ if (!trace_event_bundle.valid()) {
+ return base::OkStatus();
+ }
+
+ protozero::HeapBuffered<protos::pbzero::TracePacket> packet_message;
+ packet_message.Reset();
+
+ for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+ packet_field = packet_decoder.ReadField()) {
+ if (packet_field.id() !=
+ protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
+ proto_util::AppendField(packet_field, packet_message.get());
+ continue;
+ }
+
+ protozero::ProtoDecoder bundle_decoder(packet_field.as_bytes());
+
+ auto* bundle_message = packet_message->set_ftrace_events();
+
+ for (auto field = bundle_decoder.ReadField(); field.valid();
+ field = bundle_decoder.ReadField()) {
+ if (field.id() != protos::pbzero::FtraceEventBundle::kEventFieldNumber ||
+ KeepEvent(context, field)) {
+ proto_util::AppendField(field, bundle_message);
+ }
+ }
+ }
+
+ *packet = packet_message.SerializeAsString();
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_waking.h b/src/trace_redaction/redact_sched_waking.h
new file mode 100644
index 0000000..adb1ec8
--- /dev/null
+++ b/src/trace_redaction/redact_sched_waking.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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_REDACTION_REDACT_SCHED_WAKING_H_
+#define SRC_TRACE_REDACTION_REDACT_SCHED_WAKING_H_
+
+#include <string>
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+class RedactSchedWaking final : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_REDACT_SCHED_WAKING_H_
diff --git a/src/trace_redaction/redact_sched_waking_integrationtest.cc b/src/trace_redaction/redact_sched_waking_integrationtest.cc
new file mode 100644
index 0000000..6b6a990
--- /dev/null
+++ b/src/trace_redaction/redact_sched_waking_integrationtest.cc
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 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 <cstdint>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/base/test/status_matchers.h"
+#include "src/base/test/tmp_dir_tree.h"
+#include "src/base/test/utils.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/redact_sched_waking.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr std::string_view kTracePath =
+ "test/data/trace-redaction-general.pftrace";
+constexpr std::string_view kPackageName =
+ "com.Unity.com.unity.multiplayer.samples.coop";
+
+class RedactSchedWakingIntegrationTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ redactor_.collectors()->emplace_back(new FindPackageUid());
+ redactor_.collectors()->emplace_back(new BuildTimeline());
+ redactor_.builders()->emplace_back(new OptimizeTimeline());
+ redactor_.transformers()->emplace_back(new RedactSchedWaking());
+
+ context_.package_name = kPackageName;
+
+ src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+
+ dest_trace_ = tmp_dir_.AbsolutePath("dst.pftrace");
+ tmp_dir_.TrackFile("dst.pftrace");
+ }
+
+ base::Status Redact() {
+ return redactor_.Redact(src_trace_, dest_trace_, &context_);
+ }
+
+ base::StatusOr<std::string> LoadOriginal() const {
+ return ReadRawTrace(src_trace_);
+ }
+
+ base::StatusOr<std::string> LoadRedacted() const {
+ return ReadRawTrace(dest_trace_);
+ }
+
+ private:
+ base::StatusOr<std::string> ReadRawTrace(const std::string& path) const {
+ std::string redacted_buffer;
+
+ if (base::ReadFile(path, &redacted_buffer)) {
+ return redacted_buffer;
+ }
+
+ return base::ErrStatus("Failed to read %s", path.c_str());
+ }
+
+ Context context_;
+ TraceRedactor redactor_;
+
+ base::TmpDirTree tmp_dir_;
+
+ std::string src_trace_;
+ std::string dest_trace_;
+};
+
+// >>> SELECT uid
+// >>> FROM package_list
+// >>> WHERE package_name='com.Unity.com.unity.multiplayer.samples.coop'
+//
+// +-------+
+// | uid |
+// +-------+
+// | 10252 |
+// +-------+
+//
+// >>> SELECT uid, upid, name
+// >>> FROM process
+// >>> WHERE uid=10252
+//
+// +-------+------+----------------------------------------------+
+// | uid | upid | name |
+// +-------+------+----------------------------------------------+
+// | 10252 | 843 | com.Unity.com.unity.multiplayer.samples.coop |
+// +-------+------+----------------------------------------------+
+//
+// >>> SELECT tid, name
+// >>> FROM thread
+// >>> WHERE upid=843 AND name IS NOT NULL
+//
+// +------+-----------------+
+// | tid | name |
+// +------+-----------------+
+// | 7120 | Binder:7105_2 |
+// | 7127 | UnityMain |
+// | 7142 | Job.worker 0 |
+// | 7143 | Job.worker 1 |
+// | 7144 | Job.worker 2 |
+// | 7145 | Job.worker 3 |
+// | 7146 | Job.worker 4 |
+// | 7147 | Job.worker 5 |
+// | 7148 | Job.worker 6 |
+// | 7150 | Background Job. |
+// | 7151 | Background Job. |
+// | 7167 | UnityGfxDeviceW |
+// | 7172 | AudioTrack |
+// | 7174 | FMOD stream thr |
+// | 7180 | Binder:7105_3 |
+// | 7184 | UnityChoreograp |
+// | 7945 | Filter0 |
+// | 7946 | Filter1 |
+// | 7947 | Thread-7 |
+// | 7948 | FMOD mixer thre |
+// | 7950 | UnityGfxDeviceW |
+// | 7969 | UnityGfxDeviceW |
+// +------+-----------------+
+
+TEST_F(RedactSchedWakingIntegrationTest, OnlyKeepsPackageEvents) {
+ auto result = Redact();
+ ASSERT_OK(result) << result.c_message();
+
+ auto original = LoadOriginal();
+ ASSERT_OK(original) << original.status().c_message();
+
+ auto redacted = LoadRedacted();
+ ASSERT_OK(redacted) << redacted.status().c_message();
+
+ base::FlatHashMap<int32_t, std::string> expected_names;
+ expected_names.Insert(7120, "Binder:7105_2");
+ expected_names.Insert(7127, "UnityMain");
+ expected_names.Insert(7142, "Job.worker 0");
+ expected_names.Insert(7143, "Job.worker 1");
+ expected_names.Insert(7144, "Job.worker 2");
+ expected_names.Insert(7145, "Job.worker 3");
+ expected_names.Insert(7146, "Job.worker 4");
+ expected_names.Insert(7147, "Job.worker 5");
+ expected_names.Insert(7148, "Job.worker 6");
+ expected_names.Insert(7150, "Background Job.");
+ expected_names.Insert(7151, "Background Job.");
+ expected_names.Insert(7167, "UnityGfxDeviceW");
+ expected_names.Insert(7172, "AudioTrack");
+ expected_names.Insert(7174, "FMOD stream thr");
+ expected_names.Insert(7180, "Binder:7105_3");
+ expected_names.Insert(7184, "UnityChoreograp");
+ expected_names.Insert(7945, "Filter0");
+ expected_names.Insert(7946, "Filter1");
+ expected_names.Insert(7947, "Thread-7");
+ expected_names.Insert(7948, "FMOD mixer thre");
+ expected_names.Insert(7950, "UnityGfxDeviceW");
+ expected_names.Insert(7969, "UnityGfxDeviceW");
+
+ auto redacted_trace_data = LoadRedacted();
+ ASSERT_OK(redacted_trace_data) << redacted.status().c_message();
+
+ protos::pbzero::Trace::Decoder decoder(redacted_trace_data.value());
+
+ for (auto packet = decoder.packet(); packet; ++packet) {
+ protos::pbzero::TracePacket::Decoder packet_decoder(*packet);
+
+ if (!packet_decoder.has_ftrace_events()) {
+ continue;
+ }
+
+ protos::pbzero::FtraceEventBundle::Decoder ftrace_events_decoder(
+ packet_decoder.ftrace_events());
+
+ for (auto event = ftrace_events_decoder.event(); event; ++event) {
+ protos::pbzero::FtraceEvent::Decoder event_decoder(*event);
+
+ if (!event_decoder.has_sched_waking()) {
+ continue;
+ }
+
+ ASSERT_TRUE(event_decoder.has_pid());
+ ASSERT_TRUE(
+ expected_names.Find(static_cast<int32_t>(event_decoder.pid())));
+
+ protos::pbzero::SchedWakingFtraceEvent::Decoder waking_decoder(
+ event_decoder.sched_waking());
+
+ ASSERT_TRUE(waking_decoder.has_pid());
+ ASSERT_TRUE(expected_names.Find(waking_decoder.pid()));
+ }
+ }
+}
+
+} // namespace
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_waking_unittest.cc b/src/trace_redaction/redact_sched_waking_unittest.cc
new file mode 100644
index 0000000..a196b09
--- /dev/null
+++ b/src/trace_redaction/redact_sched_waking_unittest.cc
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2024 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_redaction/redact_sched_waking.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
+#include "protos/perfetto/trace/ftrace/sched.gen.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+constexpr int32_t kPackageUid = 1;
+} // namespace
+
+class RedactSchedWakingTest : public testing::Test {
+ protected:
+ void BeginBundle() { ftrace_bundle_ = trace_packet_.mutable_ftrace_events(); }
+
+ void AddWaking(uint64_t ts, int32_t pid, std::string_view comm) {
+ ASSERT_NE(ftrace_bundle_, nullptr);
+
+ auto* event = ftrace_bundle_->add_event();
+ event->set_timestamp(ts);
+
+ auto* sched_waking = event->mutable_sched_waking();
+ sched_waking->set_pid(pid);
+ sched_waking->set_comm(std::string(comm));
+ }
+
+ const RedactSchedWaking& transform() const { return transform_; }
+
+ // event {
+ // timestamp: 6702093757720043
+ // pid: 0
+ // sched_switch {
+ // prev_comm: "swapper/0"
+ // prev_pid: 0
+ // prev_prio: 120
+ // prev_state: 0
+ // next_comm: "Job.worker 5"
+ // next_pid: 7147
+ // next_prio: 120
+ // }
+ // }
+ protos::gen::FtraceEvent* CreateSchedSwitchEvent(
+ protos::gen::FtraceEvent* event) {
+ event->set_timestamp(6702093757720043);
+ event->set_pid(0);
+
+ auto* sched_switch = event->mutable_sched_switch();
+ sched_switch->set_prev_comm("swapper/0");
+ sched_switch->set_prev_pid(0);
+ sched_switch->set_prev_prio(120);
+ sched_switch->set_prev_state(0);
+ sched_switch->set_next_comm("Job.worker 6");
+ sched_switch->set_next_pid(7147);
+ sched_switch->set_next_prio(120);
+
+ return event;
+ }
+
+ // event {
+ // timestamp: 6702093757727075
+ // pid: 7147 <- This pid woke up...
+ // sched_waking {
+ // comm: "Job.worker 6"
+ // pid: 7148 <- ... this pid
+ // prio: 120
+ // success: 1
+ // target_cpu: 6
+ // }
+ // }
+ protos::gen::FtraceEvent* CreateSchedWakingEvent(
+ protos::gen::FtraceEvent* event) {
+ event->set_timestamp(6702093757727075);
+ event->set_pid(7147);
+
+ auto* sched_waking = event->mutable_sched_waking();
+ sched_waking->set_comm("Job.worker 6");
+ sched_waking->set_pid(7148);
+ sched_waking->set_prio(120);
+ sched_waking->set_success(1);
+ sched_waking->set_target_cpu(6);
+
+ return event;
+ }
+
+ private:
+ protos::gen::TracePacket trace_packet_;
+ protos::gen::FtraceEventBundle* ftrace_bundle_;
+
+ RedactSchedWaking transform_;
+};
+
+TEST_F(RedactSchedWakingTest, ReturnsErrorForNullPacket) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = kPackageUid;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ ASSERT_FALSE(transform().Transform(context, nullptr).ok());
+}
+
+TEST_F(RedactSchedWakingTest, ReturnsErrorForEmptyPacket) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = kPackageUid;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ std::string packet_str = "";
+
+ ASSERT_FALSE(transform().Transform(context, &packet_str).ok());
+}
+
+TEST_F(RedactSchedWakingTest, ReturnsErrorForNoTimeline) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.package_uid = kPackageUid;
+
+ protos::gen::TracePacket packet;
+ std::string packet_str = packet.SerializeAsString();
+
+ ASSERT_FALSE(transform().Transform(context, &packet_str).ok());
+}
+
+TEST_F(RedactSchedWakingTest, ReturnsErrorForMissingPackage) {
+ // Don't use context_. These tests will use invalid contexts.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+
+ protos::gen::TracePacket packet;
+ std::string packet_str = packet.SerializeAsString();
+
+ ASSERT_FALSE(transform().Transform(context, &packet_str).ok());
+}
+
+// Assume that the traces has a series of events like the events below. All
+// constants will come from these packets:
+//
+// event {
+// timestamp: 6702093757720043
+// pid: 0
+// sched_switch {
+// prev_comm: "swapper/0"
+// prev_pid: 0
+// prev_prio: 120
+// prev_state: 0
+// next_comm: "Job.worker 5"
+// next_pid: 7147
+// next_prio: 120
+// }
+// }
+// event {
+// timestamp: 6702093757727075
+// pid: 7147 <- This pid woke up...
+// sched_waking {
+// comm: "Job.worker 6"
+// pid: 7148 <- ... this pid
+// prio: 120
+// success: 1
+// target_cpu: 6
+// }
+// }
+//
+// The waking event is configured to be retained (see
+// KeepsWakingWhenBothPidsConnectToPackage for more information on how). Because
+// this transform only affects waking events, the sched switch event should be
+// retain.
+TEST_F(RedactSchedWakingTest, RetainsNonWakingEvents) {
+ std::string packet_str;
+
+ {
+ protos::gen::TracePacket packet;
+ auto* events = packet.mutable_ftrace_events();
+ events->set_cpu(0);
+
+ CreateSchedSwitchEvent(events->add_event());
+ CreateSchedWakingEvent(events->add_event());
+
+ packet_str = packet.SerializeAsString();
+ }
+
+ // Create a timeline where the wake-target (7147 & 7148) is connected to the
+ // target package.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context.package_uid = kPackageUid;
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(
+ 6702093757720043, 7147, 0, kPackageUid));
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(
+ 6702093757720043, 7148, 0, kPackageUid));
+ context.timeline->Sort();
+
+ ASSERT_TRUE(transform().Transform(context, &packet_str).ok());
+
+ {
+ protos::gen::TracePacket packet;
+ packet.ParseFromString(packet_str);
+
+ ASSERT_TRUE(packet.has_ftrace_events());
+
+ const protos::gen::FtraceEvent* switch_it = nullptr;
+ const protos::gen::FtraceEvent* waking_it = nullptr;
+
+ for (const auto& event : packet.ftrace_events().event()) {
+ if (event.has_sched_switch()) {
+ switch_it = &event;
+ }
+
+ if (event.has_sched_waking()) {
+ waking_it = &event;
+ }
+ }
+
+ // The sched switch event should be here because this primitive should not
+ // affect it.
+ //
+ // The sched waking event should be here because the waker and target
+ // connect to the target package.
+ ASSERT_TRUE(switch_it);
+ ASSERT_TRUE(waking_it);
+ }
+}
+
+// Assume that the traces has a series of events like the events below. All
+// constants will come from these packets:
+//
+// event {
+// timestamp: 6702093757727075
+// pid: 7147 <- This pid woke up...
+// sched_waking {
+// comm: "Job.worker 6"
+// pid: 7148 <- ... this pid
+// prio: 120
+// success: 1
+// target_cpu: 6
+// }
+// }
+//
+// Because the sched waking event pid's appears in the timeline and is connected
+// to the target package (kPackageUid), the waking even should remain.
+TEST_F(RedactSchedWakingTest, KeepsWakingWhenBothPidsConnectToPackage) {
+ std::string packet_str;
+
+ {
+ protos::gen::TracePacket packet;
+ auto* events = packet.mutable_ftrace_events();
+ events->set_cpu(0);
+
+ CreateSchedWakingEvent(events->add_event());
+
+ packet_str = packet.SerializeAsString();
+ }
+
+ // Create a timeline where the wake-target (7147 & 7148) is connected to the
+ // target package.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context.package_uid = kPackageUid;
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(
+ 6702093757720043, 7147, 0, kPackageUid));
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(
+ 6702093757720043, 7148, 0, kPackageUid));
+ context.timeline->Sort();
+
+ ASSERT_TRUE(transform().Transform(context, &packet_str).ok());
+
+ {
+ protos::gen::TracePacket packet;
+ packet.ParseFromString(packet_str);
+
+ ASSERT_TRUE(packet.has_ftrace_events());
+
+ const protos::gen::FtraceEvent* waking_it = nullptr;
+
+ for (const auto& event : packet.ftrace_events().event()) {
+ if (event.has_sched_waking()) {
+ waking_it = &event;
+ }
+ }
+
+ ASSERT_TRUE(waking_it);
+
+ const auto& waking = waking_it->sched_waking();
+
+ ASSERT_EQ(waking.comm(), "Job.worker 6");
+ ASSERT_EQ(waking.pid(), 7148);
+ ASSERT_EQ(waking.prio(), 120);
+ ASSERT_EQ(waking.success(), 1);
+ ASSERT_EQ(waking.target_cpu(), 6);
+ }
+}
+
+// Assume that the traces has a series of events like the events below. All
+// constants will come from these packets:
+//
+// event {
+// timestamp: 6702093757727075
+// pid: 7147 <- This pid woke up...
+// sched_waking {
+// comm: "Job.worker 6"
+// pid: 7148 <- ... this pid
+// prio: 120
+// success: 1
+// target_cpu: 6
+// }
+// }
+//
+// Because the only one of the sched waking events pid's appears in the
+// timeline and is connected to the target package (kPackageUid), the waking
+// even should remain.
+TEST_F(RedactSchedWakingTest, DropsWakingWhenOnlyWakerPidsConnectToPackage) {
+ std::string packet_str;
+
+ {
+ protos::gen::TracePacket packet;
+ auto* events = packet.mutable_ftrace_events();
+ events->set_cpu(0);
+
+ CreateSchedWakingEvent(events->add_event());
+
+ packet_str = packet.SerializeAsString();
+ }
+
+ // Because 7147 is not added to the timeline, the waking event should not be
+ // retained.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context.package_uid = kPackageUid;
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(
+ 6702093757720043, 7148, 0, kPackageUid));
+ context.timeline->Sort();
+
+ ASSERT_TRUE(transform().Transform(context, &packet_str).ok());
+
+ {
+ protos::gen::TracePacket packet;
+ packet.ParseFromString(packet_str);
+
+ ASSERT_TRUE(packet.has_ftrace_events());
+
+ const protos::gen::FtraceEvent* waking_it = nullptr;
+
+ for (const auto& event : packet.ftrace_events().event()) {
+ if (event.has_sched_waking()) {
+ waking_it = &event;
+ }
+ }
+
+ ASSERT_FALSE(waking_it);
+ }
+}
+
+// Assume that the traces has a series of events like the events below. All
+// constants will come from these packets:
+//
+// event {
+// timestamp: 6702093757727075
+// pid: 7147 <- This pid woke up...
+// sched_waking {
+// comm: "Job.worker 6"
+// pid: 7148 <- ... this pid
+// prio: 120
+// success: 1
+// target_cpu: 6
+// }
+// }
+//
+// Because the only one of the sched waking events pid's appears in the
+// timeline and is connected to the target package (kPackageUid), the waking
+// even should remain.
+TEST_F(RedactSchedWakingTest, DropsWakingWhenOnlyTargetPidsConnectToPackage) {
+ std::string packet_str;
+
+ {
+ protos::gen::TracePacket packet;
+ auto* events = packet.mutable_ftrace_events();
+ events->set_cpu(0);
+
+ CreateSchedWakingEvent(events->add_event());
+
+ packet_str = packet.SerializeAsString();
+ }
+
+ // Because 7147 is not added to the timeline, the waking event should not be
+ // retained.
+ Context context;
+ context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context.package_uid = kPackageUid;
+ context.timeline->Append(ProcessThreadTimeline::Event::Open(
+ 6702093757720043, 7147, 0, kPackageUid));
+ context.timeline->Sort();
+
+ ASSERT_TRUE(transform().Transform(context, &packet_str).ok());
+
+ {
+ protos::gen::TracePacket packet;
+ packet.ParseFromString(packet_str);
+
+ ASSERT_TRUE(packet.has_ftrace_events());
+
+ const protos::gen::FtraceEvent* waking_it = nullptr;
+
+ for (const auto& event : packet.ftrace_events().event()) {
+ if (event.has_sched_waking()) {
+ waking_it = &event;
+ }
+ }
+
+ ASSERT_FALSE(waking_it);
+ }
+}
+} // namespace perfetto::trace_redaction
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index 221817f..e21997c 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-0d8e9f71b51633ce3927ff1ec94e1ce527c836df8f9fd748924ca46711185331
\ No newline at end of file
+8e2f7043d233187a8e2229e3bfd64c3a21522e52036bf4135641c4b22ff2a40d
\ 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 b7941b6..22796d2 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 @@
-bf670ed387e44d0b4209f4da443f15e82be24da5b9573290017775cb5d68446d
\ No newline at end of file
+804eb26f54e147bc536d14354cc1694f748e598b7baaaa50cfd41e137781cbac
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out
new file mode 100644
index 0000000..4f288a7
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out
@@ -0,0 +1,362 @@
+android_blocking_calls_unagg {
+ process_with_blocking_calls {
+ process {
+ name: "com.android.systemui"
+ uid: 10001
+ pid: 1000
+ }
+ blocking_calls {
+ name: "binder transaction"
+ cnt: 7
+ total_dur_ms: 20
+ max_dur_ms: 10
+ min_dur_ms: 1
+ total_dur_ns: 20000000
+ max_dur_ns: 10000000
+ min_dur_ns: 1000000
+ }
+ blocking_calls {
+ name: "monitor contention with <...>"
+ cnt: 1
+ total_dur_ms: 12
+ max_dur_ms: 12
+ min_dur_ms: 12
+ total_dur_ns: 12000000
+ max_dur_ns: 12000000
+ min_dur_ns: 12000000
+ }
+ blocking_calls {
+ name: "AIDL::java::IWindowManager::hasNavigationBar::server"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ }
+ process_with_blocking_calls {
+ process {
+ name: "com.google.android.apps.nexuslauncher"
+ uid: 10002
+ pid: 2000
+ }
+ blocking_calls {
+ name: "binder transaction"
+ cnt: 6
+ total_dur_ms: 10
+ max_dur_ms: 3
+ min_dur_ms: 1
+ total_dur_ns: 10000000
+ max_dur_ns: 3000000
+ min_dur_ns: 1000000
+ }
+ }
+ process_with_blocking_calls {
+ process {
+ name: "com.google.android.third.process"
+ uid: 10003
+ pid: 3000
+ }
+
+ blocking_calls {
+ name: "CoroutineContinuation"
+ cnt: 2
+ total_dur_ms: 20
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 20000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Contending for pthread mutex"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "ExpNotRow#onMeasure(BigTextStyle)"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "ExpNotRow#onMeasure(MessagingStyle)"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Garbage Collector"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "ImageDecoder#decodeBitmap"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "ImageDecoder#decodeDrawable"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "LoadApkAssetsFd <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Lock contention on a monitor lock <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Lock contention on thread list lock <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Lock contention on thread suspend count lock <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "NotificationStackScrollLayout#onMeasure"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "SuspendThreadByThreadId <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "animation"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "binder transaction"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "configChanged"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "inflate"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "input"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "layout"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "measure"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "monitor contention with <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "postAndWait"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "relayoutWindow <...>"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "traversal"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+}
+process_with_blocking_calls {
+ process {
+ name: "com.google.android.top.level.slices"
+ uid: 10004
+ pid: 4000
+ }
+
+ blocking_calls {
+ name: "Handler: android.os.AsyncTask"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Handler: android.view.View"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Handler: com.android.keyguard.KeyguardUpdateMonitor"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Handler: com.android.systemui.broadcast.ActionReceiver"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "Handler: com.android.systemui.qs.external.TileServiceManager"
+ cnt: 1
+ total_dur_ms: 10
+ max_dur_ms: 10
+ min_dur_ms: 10
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ }
+}
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index 5038aec..06cbe59 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -121,6 +121,12 @@
query=Metric('android_blocking_calls_cuj_metric'),
out=Path('android_blocking_calls_cuj_metric.out'))
+ def test_android_blocking_calls_unagg(self):
+ return DiffTestBlueprint(
+ trace=Path('android_blocking_calls_cuj_metric.py'),
+ query=Metric('android_blocking_calls_unagg'),
+ out=Path('android_blocking_calls_unagg.out'))
+
def test_android_blocking_calls_on_jank_cujs(self):
return DiffTestBlueprint(
trace=Path('../graphics/android_jank_cuj.py'),
@@ -296,4 +302,4 @@
duration_ms: 3878
}
}
- """))
\ No newline at end of file
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
index 48cc56f..37f5d52 100644
--- a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
@@ -154,7 +154,7 @@
2,"[NULL]"
"""))
- def test_weight_bounded_dfs(self):
+ def test_weight_bounded_dfs_floor(self):
return DiffTestBlueprint(
trace=DataPath('counters.json'),
query="""
@@ -174,7 +174,7 @@
VALUES (5, 6, 0);
CREATE PERFETTO TABLE roots AS
- SELECT 0 AS root_node_id, 0 AS root_max_weight
+ SELECT 0 AS root_node_id, 0 AS root_target_weight
UNION ALL
VALUES (1, 2)
UNION ALL
@@ -182,7 +182,7 @@
UNION ALL
VALUES (2, 0);
- SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots);
+ SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots, 1);
""",
out=Csv("""
"root_node_id","node_id","parent_node_id"
@@ -190,8 +190,50 @@
1,1,"[NULL]"
1,2,1
1,3,1
- 1,5,3
- 1,6,5
+ 1,4,3
+ 3,3,"[NULL]"
+ 3,4,3
+ 3,5,3
+ 3,6,5
+ 2,2,"[NULL]"
+ """))
+
+ def test_weight_bounded_dfs_ceiling(self):
+ return DiffTestBlueprint(
+ trace=DataPath('counters.json'),
+ query="""
+ INCLUDE PERFETTO MODULE graphs.search;
+
+ CREATE PERFETTO TABLE foo AS
+ SELECT 0 AS source_node_id, 0 AS dest_node_id, 0 AS edge_weight
+ UNION ALL
+ VALUES (1, 2, 1)
+ UNION ALL
+ VALUES (1, 3, 1)
+ UNION ALL
+ VALUES (3, 4, 1)
+ UNION ALL
+ VALUES (3, 5, 0)
+ UNION ALL
+ VALUES (5, 6, 0);
+
+ CREATE PERFETTO TABLE roots AS
+ SELECT 0 AS root_node_id, 0 AS root_target_weight
+ UNION ALL
+ VALUES (1, 2)
+ UNION ALL
+ VALUES (3, 1)
+ UNION ALL
+ VALUES (2, 0);
+
+ SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots, 0);
+ """,
+ out=Csv("""
+ "root_node_id","node_id","parent_node_id"
+ 0,0,"[NULL]"
+ 1,1,"[NULL]"
+ 1,2,1
+ 1,3,1
3,3,"[NULL]"
3,4,3
3,5,3
diff --git a/test/trace_processor/diff_tests/stdlib/sched/tests.py b/test/trace_processor/diff_tests/stdlib/sched/tests.py
index bb24f1b..3527185 100644
--- a/test/trace_processor/diff_tests/stdlib/sched/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/sched/tests.py
@@ -13,8 +13,8 @@
# 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 Path, DataPath
+from python.generators.diff_tests.testing import Csv
from python.generators.diff_tests.testing import DiffTestBlueprint
from python.generators.diff_tests.testing import TestSuite
@@ -263,3 +263,29 @@
"S",3868233011
"x",35240577
"""))
+
+ def test_sched_previous_runnable_on_thread(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_boot.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.runnable;
+
+ SELECT *
+ FROM sched_previous_runnable_on_thread
+ WHERE prev_wakeup_runnable_id IS NOT NULL
+ ORDER BY id DESC
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "id","prev_runnable_id","prev_wakeup_runnable_id"
+ 538199,538191,538191
+ 538197,538191,538191
+ 538195,538191,538191
+ 538190,538136,538136
+ 538188,538088,533235
+ 538184,538176,524613
+ 538181,538178,537492
+ 538179,524619,524619
+ 538177,537492,537492
+ 538175,538174,524613
+ """))
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 71ca6ab..c2ad002 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -146,34 +146,32 @@
query="""
INCLUDE PERFETTO MODULE sched.thread_executing_span;
SELECT
- root_id,
- parent_id,
+ waker_id,
+ prev_id,
+ prev_end_ts,
id,
ts,
- dur,
+ end_ts,
+ is_kernel,
utid,
- blocked_dur,
- blocked_state,
- blocked_function,
- is_root,
- depth
- FROM _thread_executing_span_graph
- WHERE blocked_function IS NOT NULL
+ state,
+ blocked_function
+ FROM _wakeup_graph
ORDER BY ts
LIMIT 10
""",
out=Csv("""
- "root_id","parent_id","id","ts","dur","utid","blocked_dur","blocked_state","blocked_function","is_root","depth"
- 357,377,380,1735842234188,283571,46,351402620,"I","worker_thread",0,5
- 394,402,405,1735843726296,8545303,46,1208537,"I","worker_thread",0,3
- 357,419,432,1735850643698,16245,95,154087,"I","worker_thread",0,4
- 357,443,446,1735851953029,554638012,95,1103252,"I","worker_thread",0,6
- 357,500,503,1735886367018,191863,46,34095419,"I","worker_thread",0,10
- 357,446,667,1736125372478,52493,46,238813597,"I","worker_thread",0,7
- 357,835,838,1736405409972,278036,46,279985001,"I","worker_thread",0,12
- 357,862,865,1736406817672,7959441,46,1129664,"I","worker_thread",0,10
- 357,882,889,1736413734042,25870,95,7143001,"I","worker_thread",0,11
- 357,882,894,1736413763072,31692550,11,4413060,"I","rcu_gp_fqs_loop",0,11
+ "waker_id","prev_id","prev_end_ts","id","ts","end_ts","is_kernel","utid","state","blocked_function"
+ "[NULL]","[NULL]","[NULL]",5,1735489812571,1735489896509,0,304,"[NULL]","[NULL]"
+ 6,"[NULL]","[NULL]",11,1735489876788,1735489953773,0,428,"[NULL]","[NULL]"
+ 5,"[NULL]","[NULL]",12,1735489879097,1735490217277,0,243,"[NULL]","[NULL]"
+ 11,"[NULL]","[NULL]",17,1735489933912,1735490587658,0,230,"[NULL]","[NULL]"
+ "[NULL]","[NULL]","[NULL]",20,1735489972385,1735489995809,0,298,"[NULL]","[NULL]"
+ "[NULL]",20,1735489995809,25,1735489999987,1735490055966,0,298,"S","[NULL]"
+ 25,"[NULL]","[NULL]",28,1735490039439,1735490610238,0,421,"[NULL]","[NULL]"
+ 25,"[NULL]","[NULL]",29,1735490042084,1735490068213,0,420,"[NULL]","[NULL]"
+ 25,"[NULL]","[NULL]",30,1735490045825,1735491418790,0,1,"[NULL]","[NULL]"
+ 17,"[NULL]","[NULL]",41,1735490544063,1735490598211,0,427,"[NULL]","[NULL]"
"""))
def test_thread_executing_span_graph_contains_forked_states(self):
@@ -182,23 +180,15 @@
query="""
INCLUDE PERFETTO MODULE sched.thread_executing_span;
SELECT
- root_id,
- parent_id,
id,
- ts,
- dur,
- utid,
- blocked_dur,
- blocked_state,
- blocked_function,
- is_root,
- depth
- FROM _thread_executing_span_graph
- WHERE ts = 1735842081507 AND dur = 293868
+ waker_id,
+ prev_id
+ FROM _wakeup_graph
+ WHERE ts = 1735842081507 AND end_ts = 1735842081507 + 293868
""",
out=Csv("""
- "root_id","parent_id","id","ts","dur","utid","blocked_dur","blocked_state","blocked_function","is_root","depth"
- 357,369,376,1735842081507,293868,1465,"[NULL]","[NULL]","[NULL]",0,4
+ "id","waker_id","prev_id"
+ 376,369,"[NULL]"
"""))
def test_thread_executing_span_runnable_state_has_no_running(self):
@@ -218,11 +208,11 @@
trace=DataPath('sched_wakeup_trace.atr'),
query="""
INCLUDE PERFETTO MODULE sched.thread_executing_span;
- SELECT ts,dur FROM _thread_executing_span_graph
- WHERE dur IS NULL OR ts IS NULL
+ SELECT ts,end_ts FROM _wakeup_graph
+ WHERE end_ts IS NULL OR ts IS NULL
""",
out=Csv("""
- "ts","dur"
+ "ts","end_ts"
"""))
def test_thread_executing_span_graph_accepts_null_irq_context(self):
@@ -230,44 +220,222 @@
trace=DataPath('sched_switch_original.pb'),
query="""
INCLUDE PERFETTO MODULE sched.thread_executing_span;
- SELECT COUNT(*) AS count FROM _thread_executing_span_graph
+ SELECT COUNT(*) AS count FROM _wakeup_graph
""",
out=Csv("""
"count"
- 25
+ 17
"""))
- def test_thread_executing_span_critical_path_all(self):
+ def test_thread_executing_span_flatten_critical_path_tasks(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_switch_original.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+ CREATE PERFETTO TABLE graph AS
+ SELECT
+ id AS source_node_id,
+ COALESCE(waker_id, id) AS dest_node_id,
+ id - COALESCE(waker_id, id) AS edge_weight
+ FROM _wakeup_graph;
+
+ CREATE PERFETTO TABLE roots AS
+ SELECT
+ _wakeup_graph.id AS root_node_id,
+ _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight,
+ id,
+ ts,
+ end_ts,
+ utid
+ FROM _wakeup_graph LIMIT 10;
+
+ CREATE PERFETTO TABLE critical_path AS
+ SELECT * FROM graph_reachable_weight_bounded_dfs!(graph, roots, 1);
+
+ SELECT * FROM _flatten_critical_path_tasks!(critical_path);
+ """,
+ out=Csv("""
+ "ts","root_node_id","node_id","dur","node_utid","prev_end_ts"
+ 807082868359903,29,29,"[NULL]",8,"[NULL]"
+ 807082871734539,35,35,"[NULL]",9,"[NULL]"
+ 807082871734539,38,35,45052,9,"[NULL]"
+ 807082871779591,38,38,"[NULL]",5,807082871764903
+ 807082878623081,45,45,"[NULL]",9,807082871805424
+ 807082947156994,57,57,"[NULL]",9,807082878865945
+ 807082947246838,62,62,"[NULL]",6,807082879179539
+ 807082947261525,63,63,"[NULL]",12,"[NULL]"
+ 807082947267463,64,64,"[NULL]",13,"[NULL]"
+ 807082947278140,65,65,"[NULL]",14,"[NULL]"
+ 807082947288765,66,66,"[NULL]",15,"[NULL]"
+ """))
+
+ def test_thread_executing_span_intervals_to_roots_edge_case(self):
return DiffTestBlueprint(
trace=DataPath('sched_wakeup_trace.atr'),
query="""
INCLUDE PERFETTO MODULE sched.thread_executing_span;
- SELECT
- id,
- ts,
- dur,
- utid,
- critical_path_id,
- critical_path_blocked_dur,
- critical_path_blocked_state,
- critical_path_blocked_function,
- critical_path_utid INT
- FROM _thread_executing_span_critical_path(NULL, start_ts, end_ts), trace_bounds
- ORDER BY ts
- LIMIT 10
+
+ SELECT * FROM
+ _intervals_to_roots!((SELECT 1477 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur))
+ LIMIT 10;
""",
out=Csv("""
- "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","INT"
- 5,1735489812571,83938,304,5,"[NULL]","[NULL]","[NULL]",304
- 6,1735489833977,52463,297,6,"[NULL]","[NULL]","[NULL]",297
- 11,1735489876788,76985,428,11,"[NULL]","[NULL]","[NULL]",428
- 12,1735489879097,338180,243,12,"[NULL]","[NULL]","[NULL]",243
- 17,1735489933912,653746,230,17,"[NULL]","[NULL]","[NULL]",230
- 25,1735489999987,55979,298,25,4178,"S","[NULL]",298
- 28,1735490039439,570799,421,28,"[NULL]","[NULL]","[NULL]",421
- 29,1735490042084,26129,420,29,"[NULL]","[NULL]","[NULL]",420
- 30,1735490045825,1372965,1,30,"[NULL]","[NULL]","[NULL]",1
- 41,1735490544063,54148,427,41,"[NULL]","[NULL]","[NULL]",427
+ "id"
+ 11889
+ 11892
+ 11893
+ 11896
+ 11897
+ 11900
+ 11911
+ 11916
+ 11917
+ 11921
+ """))
+
+ def test_thread_executing_span_intervals_to_roots(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_wakeup_trace.atr'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+ SELECT * FROM
+ _intervals_to_roots!((SELECT 1477 AS utid, 1737362149192 AS ts, CAST(2e7 AS INT) AS dur))
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "id"
+ 11980
+ 11983
+ 11984
+ 11989
+ 11990
+ 11991
+ 11992
+ 11993
+ 12001
+ 12006
+ """))
+
+ def test_thread_executing_span_flatten_critical_paths(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_switch_original.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+ CREATE PERFETTO TABLE graph AS
+ SELECT
+ id AS source_node_id,
+ COALESCE(waker_id, id) AS dest_node_id,
+ id - COALESCE(waker_id, id) AS edge_weight
+ FROM _wakeup_graph;
+
+ CREATE PERFETTO TABLE roots AS
+ SELECT
+ _wakeup_graph.id AS root_node_id,
+ _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight,
+ id,
+ ts,
+ end_ts,
+ utid
+ FROM _wakeup_graph;
+
+ CREATE PERFETTO TABLE critical_path AS
+ SELECT * FROM graph_reachable_weight_bounded_dfs!(graph, roots, 1);
+
+ SELECT * FROM _flatten_critical_paths!(critical_path, _sleep);
+ """,
+ out=Csv("""
+ "ts","dur","utid","id","root_id","prev_end_ts","critical_path_utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function"
+ 807082871764903,14688,9,35,38,"[NULL]",5,38,14688,"S","[NULL]"
+ 807082947156994,351302,9,57,76,807082878865945,5,76,68858913,"S","[NULL]"
+ 807083031589763,324114,21,127,130,"[NULL]",5,130,80026987,"S","[NULL]"
+ """))
+
+ def test_thread_executing_span_critical_path(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_switch_original.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+ CREATE PERFETTO TABLE graph AS
+ SELECT
+ id AS source_node_id,
+ COALESCE(waker_id, id) AS dest_node_id,
+ id - COALESCE(waker_id, id) AS edge_weight
+ FROM _wakeup_graph;
+
+ CREATE PERFETTO TABLE roots AS
+ SELECT
+ _wakeup_graph.id AS root_node_id,
+ _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight,
+ id,
+ ts,
+ end_ts,
+ utid
+ FROM _wakeup_graph;
+
+ SELECT * FROM _critical_path!(graph, roots, _sleep);
+ """,
+ out=Csv("""
+ "ts","dur","root_id","id","utid","critical_path_utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function"
+ 807082868359903,81302,29,29,8,8,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082871734539,70885,35,35,9,9,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082871764903,14688,38,35,9,5,38,14688,"S","[NULL]"
+ 807082871779591,55729,38,38,5,5,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082878623081,242864,45,45,9,9,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947156994,436354,57,57,9,9,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947246838,1038854,62,62,6,6,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947261525,293594,63,63,12,12,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947267463,228958,64,64,13,13,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947278140,54114,65,65,14,14,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947288765,338802,66,66,15,15,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947294182,296875,67,67,16,16,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082947156994,351302,76,57,9,5,76,68858913,"S","[NULL]"
+ 807082947508296,122083,76,76,5,5,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082951822463,104427,96,96,9,9,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807082959173506,215104,107,107,6,6,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807083031589763,436198,127,127,21,21,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807083031589763,324114,130,127,21,5,130,80026987,"S","[NULL]"
+ 807083031913877,166302,130,130,5,5,"[NULL]","[NULL]","[NULL]","[NULL]"
+ 807083032278825,208490,135,135,2,2,"[NULL]","[NULL]","[NULL]","[NULL]"
+ """))
+
+ def test_thread_executing_span_critical_path_by_roots(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_switch_original.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+ SELECT * FROM _critical_path_by_roots!(_intervals_to_roots!((SELECT 6 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur)));
+ """,
+ out=Csv("""
+ "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","critical_path_utid"
+ 62,807082947246838,1038854,6,"[NULL]","[NULL]","[NULL]","[NULL]",6
+ 63,807082947261525,293594,12,"[NULL]","[NULL]","[NULL]","[NULL]",12
+ 64,807082947267463,228958,13,"[NULL]","[NULL]","[NULL]","[NULL]",13
+ 65,807082947278140,54114,14,"[NULL]","[NULL]","[NULL]","[NULL]",14
+ 66,807082947288765,338802,15,"[NULL]","[NULL]","[NULL]","[NULL]",15
+ 67,807082947294182,296875,16,"[NULL]","[NULL]","[NULL]","[NULL]",16
+ 57,807082947156994,351302,9,76,68858913,"S","[NULL]",5
+ 76,807082947508296,122083,5,"[NULL]","[NULL]","[NULL]","[NULL]",5
+ 96,807082951822463,104427,9,"[NULL]","[NULL]","[NULL]","[NULL]",9
+ 107,807082959173506,215104,6,"[NULL]","[NULL]","[NULL]","[NULL]",6
+ """))
+
+ def test_thread_executing_span_critical_path_by_intervals(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_switch_original.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE sched.thread_executing_span;
+
+ SELECT * FROM _critical_path_by_intervals!((SELECT 6 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur));
+ """,
+ out=Csv("""
+ "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","critical_path_utid"
+ 62,807082947246838,1038854,6,"[NULL]","[NULL]","[NULL]","[NULL]",6
+ 107,807082959173506,215104,6,"[NULL]","[NULL]","[NULL]","[NULL]",6
"""))
def test_thread_executing_span_critical_path_utid(self):
@@ -284,22 +452,22 @@
critical_path_blocked_dur,
critical_path_blocked_state,
critical_path_blocked_function,
- critical_path_utid INT
+ critical_path_utid
FROM _thread_executing_span_critical_path((select utid from thread where tid = 3487), start_ts, end_ts), trace_bounds
ORDER BY ts
LIMIT 10
""",
out=Csv("""
- "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","INT"
- 11889,1737349401439,7705561,1477,11889,"[NULL]","[NULL]","[NULL]",1477
+ "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","critical_path_utid"
+ 11889,1737349401439,7705561,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477
11952,1737357107000,547583,1480,11980,547583,"S","[NULL]",1477
- 11980,1737357654583,8430762,1477,11980,547583,"S","[NULL]",1477
+ 11980,1737357654583,8430762,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477
12052,1737366085345,50400,91,12057,50400,"S","[NULL]",1477
- 12057,1737366135745,6635927,1477,12057,50400,"S","[NULL]",1477
+ 12057,1737366135745,6635927,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477
12081,1737372771672,12798314,1488,12254,12798314,"S","[NULL]",1477
- 12254,1737385569986,21830622,1477,12254,12798314,"S","[NULL]",1477
+ 12254,1737385569986,21830622,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477
12517,1737407400608,241267,91,12521,241267,"S","[NULL]",1477
- 12521,1737407641875,1830015,1477,12521,241267,"S","[NULL]",1477
+ 12521,1737407641875,1830015,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477
12669,1737409471890,68590,91,12672,68590,"S","[NULL]",1477
"""))
@@ -307,7 +475,7 @@
return DiffTestBlueprint(
trace=DataPath('sched_wakeup_trace.atr'),
query="""
- INCLUDE PERFETTO MODULE sched.thread_executing_span;
+ INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
SELECT
id,
ts,
@@ -340,7 +508,7 @@
return DiffTestBlueprint(
trace=DataPath('sched_wakeup_trace.atr'),
query="""
- INCLUDE PERFETTO MODULE sched.thread_executing_span;
+ INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
SELECT HEX(pprof) FROM _thread_executing_span_critical_path_graph("critical path", (select utid from thread where tid = 3487), 1737488133487, 16000), trace_bounds
""",
out=BinaryProto(
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index 3819c27..f025f47 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -41,6 +41,11 @@
'android_cujs', 'relevant_binder_calls_with_names',
'android_blocking_calls_cuj_calls'
],
+ ('/android'
+ '/android_blocking_calls_unagg.sql'): [
+ 'filtered_processes_with_non_zero_blocking_calls',
+ 'process_info', 'android_blocking_calls_unagg_calls'
+ ],
'/android/jank/cujs.sql': ['android_jank_cuj'],
'/chrome/gesture_flow_event.sql': [
'{{prefix}}_latency_info_flow_step_filtered'
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 14775fc..049cf40 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -181,6 +181,11 @@
"PERFETTO_SHLIB_SDK_IMPLEMENTATION"
]
+# Targets with the "avoid_dep" tag;
+avoid_dep_targets = set([
+ '//python:trace_processor_py_no_resolvers',
+])
+
# Filter defines that appear in the bazel build file to only those that bazel requires.
def filter_defines(defines):
return [d for d in defines if d in bazel_required_defines]
@@ -240,6 +245,8 @@
self.populate_python_deps(target, label)
if target.name in public_python_targets:
label.visibility = ['//visibility:public']
+ if target.name in avoid_dep_targets:
+ label.tags += ['avoid_dep']
return [label]
def gen_python_binary(self, target: GnParser.Target):
@@ -346,6 +353,7 @@
self.python_version: Optional[str] = None
self.root_dir: Optional[str] = None
self.namespace: Optional[str] = None
+ self.tags: List[str] = []
def __lt__(self, other):
if isinstance(other, self.__class__):
diff --git a/ui/src/base/async_limiter.ts b/ui/src/base/async_limiter.ts
new file mode 100644
index 0000000..3130d6d
--- /dev/null
+++ b/ui/src/base/async_limiter.ts
@@ -0,0 +1,73 @@
+// Copyright (C) 2024 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 {Deferred, defer} from './deferred';
+
+type Callback = () => Promise<void>;
+
+interface Task {
+ deferred: Deferred<void>;
+ work: Callback;
+}
+
+/**
+ * A tiny task queue management utility that ensures async tasks are not
+ * executed concurrently.
+ *
+ * If a task is run while a previous one is still running, it is enqueued and
+ * run after the first task completes.
+ *
+ * If multiple tasks are enqueued, only the latest task is run.
+ */
+export class AsyncLimiter {
+ private readonly taskQueue: Task[] = [];
+ private isRunning: boolean = false;
+
+ /**
+ * Schedule a task to be run.
+ *
+ * @param work An async function to schedule.
+ * @returns A promise that resolves when either the task has finished
+ * executing, or after the task has silently been discarded because a newer
+ * task was scheduled.
+ */
+ schedule(work: Callback): Promise<void> {
+ const deferred = defer<void>();
+ this.taskQueue.push({work, deferred});
+
+ if (!this.isRunning) {
+ this.isRunning = true;
+ this.runTaskQueue().finally(() => (this.isRunning = false));
+ }
+
+ return deferred;
+ }
+
+ private async runTaskQueue(): Promise<void> {
+ let task: Task | undefined;
+
+ while ((task = this.taskQueue.shift())) {
+ if (this.taskQueue.length > 0) {
+ task.deferred.resolve();
+ } else {
+ try {
+ await task.work();
+ task.deferred.resolve();
+ } catch (e) {
+ task.deferred.reject(e);
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/base/async_limiter_unittest.ts b/ui/src/base/async_limiter_unittest.ts
new file mode 100644
index 0000000..6b080e1
--- /dev/null
+++ b/ui/src/base/async_limiter_unittest.ts
@@ -0,0 +1,80 @@
+// Copyright (C) 2024 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 {AsyncLimiter} from './async_limiter';
+
+test('no concurrent callbacks', async () => {
+ const limiter = new AsyncLimiter();
+
+ const mock1 = jest.fn();
+ limiter.schedule(async () => mock1());
+ expect(mock1).toHaveBeenCalled();
+
+ const mock2 = jest.fn();
+ limiter.schedule(async () => mock2());
+ expect(mock2).not.toHaveBeenCalled();
+});
+
+test('queueing', async () => {
+ const limiter = new AsyncLimiter();
+
+ const mock1 = jest.fn();
+ limiter.schedule(async () => mock1());
+
+ const mock2 = jest.fn();
+ await limiter.schedule(async () => mock2());
+
+ expect(mock1).toHaveBeenCalled();
+ expect(mock2).toHaveBeenCalled();
+});
+
+test('multiple queuing', async () => {
+ const limiter = new AsyncLimiter();
+
+ const mock1 = jest.fn();
+ limiter.schedule(async () => mock1());
+
+ const mock2 = jest.fn();
+ limiter.schedule(async () => mock2());
+
+ const mock3 = jest.fn();
+ await limiter.schedule(async () => mock3());
+
+ expect(mock1).toHaveBeenCalled();
+ expect(mock2).not.toHaveBeenCalled();
+ expect(mock3).toHaveBeenCalled();
+});
+
+test('error in callback bubbles up to caller', async () => {
+ const limiter = new AsyncLimiter();
+ const failingCallback = async () => {
+ throw Error();
+ };
+
+ expect(async () => await limiter.schedule(failingCallback)).rejects.toThrow();
+});
+
+test('chain continues even when one callback fails', async () => {
+ const limiter = new AsyncLimiter();
+
+ const failingCallback = async () => {
+ throw Error();
+ };
+ limiter.schedule(failingCallback).catch(() => {});
+
+ const mock = jest.fn();
+ await limiter.schedule(async () => mock());
+
+ expect(mock).toHaveBeenCalled();
+});
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 10fbbff..74cbe44 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -60,7 +60,7 @@
if (err instanceof ErrorEvent) {
errType = 'ERROR';
- errMsg = err.message;
+ errMsg = `${err.error}`;
errorObj = err.error;
} else if (err instanceof PromiseRejectionEvent) {
errType = 'PROMISE_REJ';
diff --git a/ui/src/base/monitor.ts b/ui/src/base/monitor.ts
new file mode 100644
index 0000000..d4e0f87
--- /dev/null
+++ b/ui/src/base/monitor.ts
@@ -0,0 +1,36 @@
+// Copyright (C) 2024 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.
+
+type Reducer = () => unknown;
+type Callback = () => void;
+
+/**
+ * A little helper that monitors a list of immutable objects and calls a
+ * callback only when at least one them changes.
+ */
+export class Monitor {
+ private cached: unknown[];
+
+ constructor(private reducers: Reducer[]) {
+ this.cached = reducers.map(() => undefined);
+ }
+
+ ifStateChanged(callback: Callback): void {
+ const state = this.reducers.map((f) => f());
+ if (state.some((x, i) => x !== this.cached[i])) {
+ callback();
+ }
+ this.cached = state;
+ }
+}
diff --git a/ui/src/base/monitor_unittest.ts b/ui/src/base/monitor_unittest.ts
new file mode 100644
index 0000000..51b22da
--- /dev/null
+++ b/ui/src/base/monitor_unittest.ts
@@ -0,0 +1,38 @@
+// Copyright (C) 2024 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 {Monitor} from './monitor';
+
+test('callback is called when state changes', () => {
+ const reducer = jest.fn().mockReturnValue('foo');
+ const monitor = new Monitor([reducer]);
+ const mockCallback = jest.fn();
+
+ monitor.ifStateChanged(mockCallback);
+ expect(mockCallback).toHaveBeenCalledTimes(1);
+
+ mockCallback.mockReset();
+ monitor.ifStateChanged(mockCallback);
+ monitor.ifStateChanged(mockCallback);
+ expect(mockCallback).not.toHaveBeenCalled();
+
+ mockCallback.mockReset();
+ reducer.mockReturnValue('bar');
+ monitor.ifStateChanged(mockCallback);
+ expect(mockCallback).toHaveBeenCalledTimes(1);
+
+ mockCallback.mockReset();
+ monitor.ifStateChanged(mockCallback);
+ expect(mockCallback).not.toHaveBeenCalled();
+});
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index baba17e..4f16d80 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -80,6 +80,7 @@
trackSortKey: TrackSortKey;
trackGroup?: string;
params?: unknown;
+ closeable?: boolean;
}
export interface PostedTrace {
@@ -211,6 +212,7 @@
labels: track.labels,
uri: track.uri,
params: track.params,
+ closeable: track.closeable,
};
if (track.trackGroup === SCROLLING_TRACK_GROUP) {
state.scrollingTracks.push(trackKey);
@@ -726,6 +728,7 @@
type: args.type,
viewingOption: args.viewingOption,
focusRegex: '',
+ expandedCallsiteByViewingOption: {},
};
},
@@ -746,10 +749,15 @@
expandFlamegraphState(
state: StateDraft,
- args: {expandedCallsite?: CallsiteInfo},
+ args: {
+ expandedCallsite?: CallsiteInfo;
+ viewingOption: FlamegraphStateViewingOption;
+ },
): void {
if (state.currentFlamegraphState === null) return;
- state.currentFlamegraphState.expandedCallsite = args.expandedCallsite;
+ state.currentFlamegraphState.expandedCallsiteByViewingOption[
+ args.viewingOption
+ ] = args.expandedCallsite;
},
changeViewFlamegraphState(
diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts
index 63689ca..0817ebf 100644
--- a/ui/src/common/flamegraph_util.ts
+++ b/ui/src/common/flamegraph_util.ts
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {featureFlags} from '../core/feature_flags';
import {CallsiteInfo, FlamegraphStateViewingOption, ProfileType} from './state';
interface ViewingOption {
@@ -19,6 +20,13 @@
name: string;
}
+const SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG = featureFlags.register({
+ id: 'showHeapGraphDominatorTree',
+ name: 'Show heap graph dominator tree',
+ description: 'Show dominated size and objects tabs in Java heap graph view.',
+ defaultValue: false,
+});
+
export function viewingOptions(profileType: ProfileType): Array<ViewingOption> {
switch (profileType) {
case ProfileType.PERF_SAMPLE:
@@ -39,7 +47,22 @@
option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
name: 'Objects',
},
- ];
+ ].concat(
+ SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG.get()
+ ? [
+ {
+ option:
+ FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
+ name: 'Dominated size',
+ },
+ {
+ option:
+ FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
+ name: 'Dominated objects',
+ },
+ ]
+ : [],
+ );
case ProfileType.HEAP_PROFILE:
return [
{
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 19b156c..92efee5 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -22,7 +22,7 @@
} from '../frontend/pivot_table_types';
import {PrimaryTrackSortKey} from '../public/index';
-import {Direction} from './event_set';
+import {Direction} from '../core/event_set';
import {
selectionToLegacySelection,
@@ -147,7 +147,8 @@
// 48. Rename legacySelection -> selection and introduce new Selection type.
// 49. Remove currentTab, which is only relevant to TabsV1.
// 50. Remove ftrace filter state.
-export const STATE_VERSION = 50;
+// 51. Changed structure of FlamegraphState.expandedCallsiteByViewingOption.
+export const STATE_VERSION = 51;
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
@@ -189,6 +190,24 @@
OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS',
OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS',
PERF_SAMPLES_KEY = 'PERF_SAMPLES',
+ DOMINATOR_TREE_OBJ_SIZE_KEY = 'DOMINATED_OBJ_SIZE',
+ DOMINATOR_TREE_OBJ_COUNT_KEY = 'DOMINATED_OBJ_COUNT',
+}
+
+const HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS = [
+ FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
+ FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
+] as const;
+
+export type HeapGraphDominatorTreeViewingOption =
+ (typeof HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS)[number];
+
+export function isHeapGraphDominatorTreeViewingOption(
+ option: FlamegraphStateViewingOption,
+): option is HeapGraphDominatorTreeViewingOption {
+ return (
+ HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS as readonly FlamegraphStateViewingOption[]
+ ).includes(option);
}
export interface FlamegraphState {
@@ -199,7 +218,7 @@
type: ProfileType;
viewingOption: FlamegraphStateViewingOption;
focusRegex: string;
- expandedCallsite?: CallsiteInfo;
+ expandedCallsiteByViewingOption: {[key: string]: CallsiteInfo | undefined};
}
export interface CallsiteInfo {
@@ -262,6 +281,7 @@
trackGroup?: string;
params?: unknown;
state?: unknown;
+ closeable?: boolean;
}
export interface TrackGroupState {
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index aa123f7..a10252c 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Duration, time} from '../base/time';
+import {Duration, Time, time} from '../base/time';
import {exists} from '../base/utils';
import {Actions} from '../common/actions';
import {
@@ -26,6 +26,7 @@
FlamegraphState,
FlamegraphStateViewingOption,
ProfileType,
+ isHeapGraphDominatorTreeViewingOption,
} from '../common/state';
import {FlamegraphDetails, globals} from '../frontend/globals';
import {publishFlamegraphDetails} from '../frontend/publish';
@@ -115,6 +116,10 @@
private flamegraphDetails: FlamegraphDetails = {};
private areaSelectionHandler: AreaSelectionHandler;
private cache: TablesCache;
+ private heapGraphSelected: {upid: number; timestamp: time} = {
+ upid: -1,
+ timestamp: Time.INVALID,
+ };
constructor(private args: FlamegraphControllerArgs) {
super('main');
@@ -197,13 +202,12 @@
this.lastSelectedFlamegraphState = {...selection};
- const expandedId = selectedFlamegraphState.expandedCallsite
- ? selectedFlamegraphState.expandedCallsite.id
- : -1;
- const rootSize =
- selectedFlamegraphState.expandedCallsite === undefined
- ? undefined
- : selectedFlamegraphState.expandedCallsite.totalSize;
+ const expandedCallsite =
+ selectedFlamegraphState.expandedCallsiteByViewingOption[
+ selectedFlamegraphState.viewingOption
+ ];
+ const expandedId = expandedCallsite ? expandedCallsite.id : -1;
+ const rootSize = expandedCallsite?.totalSize;
const key = `${selectedFlamegraphState.upids};${selectedFlamegraphState.start};${selectedFlamegraphState.end}`;
@@ -238,7 +242,7 @@
this.lastSelectedFlamegraphState.viewingOption,
isInAreaSelection,
rootSize,
- this.lastSelectedFlamegraphState.expandedCallsite,
+ expandedCallsite,
);
}
} finally {
@@ -264,8 +268,10 @@
this.lastSelectedFlamegraphState.viewingOption !==
selection.viewingOption ||
this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex ||
- this.lastSelectedFlamegraphState.expandedCallsite !==
- selection.expandedCallsite)
+ this.lastSelectedFlamegraphState.expandedCallsiteByViewingOption[
+ selection.viewingOption
+ ] !==
+ selection.expandedCallsiteByViewingOption[selection.viewingOption])
);
}
@@ -311,7 +317,7 @@
if (this.flamegraphDatasets.has(key)) {
currentData = this.flamegraphDatasets.get(key)!;
} else {
- // TODO(hjd): Show loading state.
+ // TODO(b/330703412): Show loading state.
// Collecting data for drawing flamegraph for selected profile.
// Data needs to be in following format:
@@ -322,6 +328,7 @@
upids,
type,
focusRegex,
+ viewingOption,
);
currentData = await this.getFlamegraphDataFromTables(
tableName,
@@ -374,6 +381,18 @@
totalColumnName = 'cumulativeSize';
selfColumnName = 'size';
break;
+ case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY:
+ orderBy = `where depth < ${maxDepth} order by depth,
+ cumulativeCount desc, name`;
+ totalColumnName = 'cumulativeCount';
+ selfColumnName = 'count';
+ break;
+ case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY:
+ orderBy = `where depth < ${maxDepth} order by depth,
+ cumulativeSize desc, name`;
+ totalColumnName = 'cumulativeSize';
+ selfColumnName = 'size';
+ break;
default:
const exhaustiveCheck: never = viewingOption;
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
@@ -381,21 +400,21 @@
}
const callsites = await this.args.engine.query(`
- SELECT
- id as hash,
- IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name,
- IFNULL(parent_id, -1) as parentHash,
- depth,
- cumulative_size as cumulativeSize,
- cumulative_alloc_size as cumulativeAllocSize,
- cumulative_count as cumulativeCount,
- cumulative_alloc_count as cumulativeAllocCount,
- map_name as mapping,
- size,
- count,
- IFNULL(source_file, '') as sourceFile,
- IFNULL(line_number, -1) as lineNumber
- from ${tableName} ${orderBy}`);
+ SELECT
+ id as hash,
+ IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name,
+ IFNULL(parent_id, -1) as parentHash,
+ depth,
+ cumulative_size as cumulativeSize,
+ cumulative_alloc_size as cumulativeAllocSize,
+ cumulative_count as cumulativeCount,
+ cumulative_alloc_count as cumulativeAllocCount,
+ map_name as mapping,
+ size,
+ count,
+ IFNULL(source_file, '') as sourceFile,
+ IFNULL(line_number, -1) as lineNumber
+ from ${tableName} ${orderBy}`);
const flamegraphData: CallsiteInfo[] = [];
const hashToindex: Map<number, number> = new Map();
@@ -467,6 +486,7 @@
upids: number[],
type: ProfileType,
focusRegex: string,
+ viewingOption: FlamegraphStateViewingOption,
): Promise<string> {
const flamegraphType = getFlamegraphType(type);
if (type === ProfileType.PERF_SAMPLE) {
@@ -493,6 +513,14 @@
)`,
);
}
+ if (
+ type === ProfileType.JAVA_HEAP_GRAPH &&
+ isHeapGraphDominatorTreeViewingOption(viewingOption)
+ ) {
+ return this.cache.getTableName(
+ await this.loadHeapGraphDominatorTreeQuery(upids[0], end),
+ );
+ }
return this.cache.getTableName(
`select id, name, map_name, parent_id, depth, cumulative_size,
cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
@@ -508,6 +536,91 @@
);
}
+ private async loadHeapGraphDominatorTreeQuery(upid: number, timestamp: time) {
+ const selectTreeQuery = `
+ -- cache invalidate: upid ${upid}, ts ${timestamp}
+ SELECT * FROM heap_graph_type_dominated`;
+ if (
+ this.heapGraphSelected.upid === upid &&
+ this.heapGraphSelected.timestamp === timestamp
+ ) {
+ return selectTreeQuery;
+ }
+ this.heapGraphSelected = {upid, timestamp};
+ this.args.engine.query(`
+ INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+
+ -- heap graph dominator tree with objects as nodes and all relavant
+ -- object self stats and dominated stats
+ CREATE PERFETTO TABLE heap_graph_object_dominated AS
+ SELECT
+ node.id,
+ node.idom_id,
+ node.dominated_obj_count,
+ node.dominated_size_bytes + node.dominated_native_size_bytes AS dominated_size,
+ node.depth,
+ obj.type_id,
+ obj.root_type,
+ obj.self_size + obj.native_size AS self_size
+ FROM memory_heap_graph_dominator_tree node
+ JOIN heap_graph_object obj USING(id)
+ WHERE obj.upid = ${upid} AND obj.graph_sample_ts = ${timestamp}
+ -- required to accelerate the recursive cte below
+ ORDER BY idom_id;
+
+ -- calculate for each object node in the dominator tree the
+ -- HASH(path of type_id's from the super root to the object)
+ CREATE PERFETTO TABLE _dominator_tree_path_hash AS
+ WITH RECURSIVE _tree_visitor(id, path, path_hash) AS (
+ SELECT
+ id,
+ CAST(type_id AS text) || '-' || IFNULL(root_type, '') AS path,
+ HASH(
+ CAST(type_id AS text) || '-' || IFNULL(root_type, '')
+ ) AS path_hash
+ FROM heap_graph_object_dominated
+ WHERE depth = 1
+ UNION ALL
+ SELECT
+ child.id,
+ parent.path || '/' || CAST(type_id AS text) AS path,
+ HASH(parent.path || '/' || CAST(type_id AS text)) AS path_hash
+ FROM heap_graph_object_dominated child
+ JOIN _tree_visitor parent ON child.idom_id = parent.id
+ )
+ SELECT * from _tree_visitor
+ ORDER BY id;
+
+ -- merge object nodes with the same path into one "class type node", so the
+ -- end result is a tree where nodes are identified by their types and the
+ -- dominator relationships are preserved.
+ CREATE PERFETTO TABLE heap_graph_type_dominated AS
+ SELECT
+ map.path_hash as id,
+ COALESCE(cls.deobfuscated_name, cls.name, '[NULL]') || IIF(
+ node.root_type IS NOT NULL,
+ ' [' || node.root_type || ']', ''
+ ) AS name,
+ IFNULL(parent_map.path_hash, -1) AS parent_id,
+ node.depth - 1 AS depth,
+ sum(dominated_size) AS cumulative_size,
+ -1 AS cumulative_alloc_size,
+ sum(dominated_obj_count) AS cumulative_count,
+ -1 AS cumulative_alloc_count,
+ '' as map_name,
+ '' as source_file,
+ -1 as line_number,
+ sum(self_size) AS size,
+ count(*) AS count
+ FROM heap_graph_object_dominated node
+ JOIN _dominator_tree_path_hash map USING(id)
+ LEFT JOIN _dominator_tree_path_hash parent_map ON node.idom_id = parent_map.id
+ JOIN heap_graph_class cls ON node.type_id = cls.id
+ GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number;`);
+
+ return selectTreeQuery;
+ }
+
getMinSizeDisplayed(
flamegraphData: CallsiteInfo[],
rootSize?: number,
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 9e39eed..161fba7 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -104,7 +104,6 @@
type States = 'init' | 'loading_trace' | 'ready';
const METRICS = [
- 'android_startup',
'android_ion',
'android_lmk',
'android_dma_heap',
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index 8a03d47..583e6bc 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -28,6 +28,7 @@
'dev.perfetto.AndroidNetwork',
'dev.perfetto.AndroidPerf',
'dev.perfetto.AndroidPerfTraceCounters',
+ 'dev.perfetto.AndroidStartup',
'dev.perfetto.BookmarkletApi',
'dev.perfetto.CoreCommands',
'dev.perfetto.LargeScreensPerf',
diff --git a/ui/src/common/event_set.ts b/ui/src/core/event_set.ts
similarity index 100%
rename from ui/src/common/event_set.ts
rename to ui/src/core/event_set.ts
diff --git a/ui/src/common/event_set_nocompile_test.ts b/ui/src/core/event_set_nocompile_test.ts
similarity index 100%
rename from ui/src/common/event_set_nocompile_test.ts
rename to ui/src/core/event_set_nocompile_test.ts
diff --git a/ui/src/common/event_set_unittest.ts b/ui/src/core/event_set_unittest.ts
similarity index 100%
rename from ui/src/common/event_set_unittest.ts
rename to ui/src/core/event_set_unittest.ts
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 940ff31..4bed3fc 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -322,7 +322,7 @@
if (engine !== undefined && trackUtid != 0) {
await runQuery(
- `SELECT IMPORT('sched.thread_executing_span');`,
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
engine,
);
await addDebugSliceTrack(
@@ -365,7 +365,7 @@
if (engine !== undefined && trackUtid != 0) {
await runQuery(
- `SELECT IMPORT('sched.thread_executing_span');`,
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
engine,
);
await addDebugSliceTrack(
@@ -399,7 +399,7 @@
if (engine !== undefined && trackUtid != 0) {
addQueryResultsTab({
- query: `SELECT IMPORT('sched.thread_executing_span');
+ query: `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
SELECT *
FROM
_thread_executing_span_critical_path_graph(
diff --git a/ui/src/frontend/debug_tracks.ts b/ui/src/frontend/debug_tracks.ts
index 7e2ce7e..6ff6297 100644
--- a/ui/src/frontend/debug_tracks.ts
+++ b/ui/src/frontend/debug_tracks.ts
@@ -67,7 +67,6 @@
const trackConfig: DebugTrackV2Config = {
data,
columns: sliceColumns,
- closeable,
argColumns,
};
@@ -79,6 +78,7 @@
trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
params: trackConfig,
+ closeable,
}),
];
if (config?.pinned ?? true) {
@@ -117,7 +117,6 @@
export interface CounterDebugTrackConfig {
data: SqlDataSource;
columns: CounterColumns;
- closeable: boolean;
}
export interface CounterDebugTrackCreateConfig {
@@ -143,7 +142,6 @@
const params: CounterDebugTrackConfig = {
data,
columns,
- closeable,
};
const trackKey = uuidv4();
@@ -155,6 +153,7 @@
trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
trackGroup: SCROLLING_TRACK_GROUP,
params,
+ closeable,
}),
];
if (config?.pinned ?? true) {
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index 7bf69c9..fea7fb2 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -230,7 +230,9 @@
case ProfileType.JAVA_HEAP_GRAPH:
if (
viewingOption ===
- FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY
+ FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY ||
+ viewingOption ===
+ FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY
) {
return RENDER_OBJ_COUNT;
} else {
@@ -347,7 +349,9 @@
current.viewingOption ===
FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY ||
current.viewingOption ===
- FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY
+ FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY ||
+ current.viewingOption ===
+ FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY
? 'B'
: '';
this.flamegraph.draw(ctx, width, height, 0, 0, unit);
@@ -355,7 +359,13 @@
private onMouseClick({x, y}: {x: number; y: number}): boolean {
const expandedCallsite = this.flamegraph.onMouseClick({x, y});
- globals.dispatch(Actions.expandFlamegraphState({expandedCallsite}));
+ globals.state.currentFlamegraphState &&
+ globals.dispatch(
+ Actions.expandFlamegraphState({
+ expandedCallsite,
+ viewingOption: globals.state.currentFlamegraphState.viewingOption,
+ }),
+ );
return true;
}
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index 30209ab..26dcb57 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -357,7 +357,7 @@
label: 'Critical path',
onclick: () =>
runQuery(
- `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
this.engine,
).then(() =>
addDebugSliceTrack(
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 2e15d67..4240ef4 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -37,6 +37,7 @@
import {canvasClip} from '../common/canvas_utils';
import {TimeScale} from './time_scale';
import {getLegacySelection} from '../common/state';
+import {CloseTrackButton} from './close_track_button';
function getTitleSize(title: string): string | undefined {
const length = title.length;
@@ -330,6 +331,7 @@
tags?: TrackTags;
track?: Track;
error?: Error | undefined;
+ closeable: boolean;
// Issues a scrollTo() on this DOM element at creation time. Default: false.
revealOnCreate?: boolean;
@@ -359,6 +361,7 @@
m(TrackShell, {
buttons: [
attrs.error && m(CrashButton, {error: attrs.error}),
+ attrs.closeable && m(CloseTrackButton, {trackKey: attrs.trackKey}),
attrs.buttons,
],
title: attrs.title,
@@ -426,6 +429,7 @@
tags?: TrackTags;
trackFSM?: TrackCacheEntry;
revealOnCreate?: boolean;
+ closeable: boolean;
}
export class TrackPanel implements Panel {
@@ -452,6 +456,7 @@
trackKey: attrs.trackKey,
error: attrs.trackFSM.getError(),
track: attrs.trackFSM.track,
+ closeable: attrs.closeable,
});
}
return m(TrackComponent, {
@@ -463,12 +468,14 @@
track: attrs.trackFSM.track,
error: attrs.trackFSM.getError(),
revealOnCreate: attrs.revealOnCreate,
+ closeable: attrs.closeable,
});
} else {
return m(TrackComponent, {
trackKey: attrs.trackKey,
title: attrs.title,
revealOnCreate: attrs.revealOnCreate,
+ closeable: attrs.closeable,
});
}
}
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 3f12a7d..719705e 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -241,6 +241,7 @@
title: trackBundle.title,
tags: trackBundle.tags,
trackFSM: trackBundle.trackFSM,
+ closeable: trackBundle.closeable,
});
},
);
@@ -270,6 +271,7 @@
title: trackBundle.title,
tags: trackBundle.tags,
trackFSM: trackBundle.trackFSM,
+ closeable: trackBundle.closeable,
});
childTracks.push(panel);
}
@@ -327,6 +329,7 @@
tags: trackBundle.tags,
trackFSM: trackBundle.trackFSM,
revealOnCreate: true,
+ closeable: trackBundle.closeable,
});
}),
kind: 'TRACKS',
@@ -348,7 +351,7 @@
// Resolve a track and its metadata through the track cache
private resolveTrack(key: string): TrackBundle {
const trackState = globals.state.tracks[key];
- const {uri, params, name, labels} = trackState;
+ const {uri, params, name, labels, closeable} = trackState;
const trackDesc = globals.trackManager.resolveTrackInfo(uri);
const trackCacheEntry =
trackDesc && globals.trackManager.resolveTrack(key, trackDesc, params);
@@ -361,6 +364,7 @@
trackFSM,
labels,
trackIds,
+ closeable: closeable ?? false,
};
}
@@ -371,6 +375,7 @@
interface TrackBundle {
title: string;
+ closeable: boolean;
trackFSM?: TrackCacheEntry;
tags?: TrackTags;
labels?: string[];
diff --git a/ui/src/plugins/dev.perfetto.AndroidStartup/OWNERS b/ui/src/plugins/dev.perfetto.AndroidStartup/OWNERS
new file mode 100644
index 0000000..52dc56a
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidStartup/OWNERS
@@ -0,0 +1,2 @@
+lalitm@google.com
+ilkos@google.com
diff --git a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
new file mode 100644
index 0000000..ebb10b8
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
@@ -0,0 +1,63 @@
+// Copyright (C) 2024 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 {
+ LONG,
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+} from '../../public';
+import {
+ SimpleSliceTrack,
+ SimpleSliceTrackConfig,
+} from '../../frontend/simple_slice_track';
+
+class AndroidStartup implements Plugin {
+ onActivate(_ctx: PluginContext): void {}
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ const e = ctx.engine;
+ await e.query(`include perfetto module android.startup.startups;`);
+
+ const cnt = await e.query('select count() cnt from android_startups');
+ if (cnt.firstRow({cnt: LONG}).cnt === 0n) {
+ return;
+ }
+
+ const config: SimpleSliceTrackConfig = {
+ data: {
+ sqlSource: `
+ SELECT l.ts AS ts, l.dur AS dur, l.package AS name
+ FROM android_startups l
+ `,
+ columns: ['ts', 'dur', 'name'],
+ },
+ columns: {ts: 'ts', dur: 'dur', name: 'name'},
+ argColumns: [],
+ };
+ ctx.registerStaticTrack({
+ uri: `dev.perfetto.AndroidStartup#startups`,
+ displayName: 'Android App Startups',
+ trackFactory: (trackCtx) => {
+ return new SimpleSliceTrack(ctx.engine, trackCtx, config);
+ },
+ });
+ }
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'dev.perfetto.AndroidStartup',
+ plugin: AndroidStartup,
+};
diff --git a/ui/src/plugins/dev.perfetto.Chaos/OWNERS b/ui/src/plugins/dev.perfetto.Chaos/OWNERS
new file mode 100644
index 0000000..9ee9fce
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Chaos/OWNERS
@@ -0,0 +1 @@
+hjd@google.com
diff --git a/ui/src/plugins/dev.perfetto.Chaos/index.ts b/ui/src/plugins/dev.perfetto.Chaos/index.ts
new file mode 100644
index 0000000..c18ff7f
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Chaos/index.ts
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 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 {
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+ addDebugSliceTrack,
+} from '../../public';
+
+class Chaos implements Plugin {
+ onActivate(ctx: PluginContext): void {
+ ctx.registerCommand({
+ id: 'dev.perfetto.Chaos#CrashNow',
+ name: 'Chaos: crash now',
+ callback: () => {
+ throw new Error('Manual crash from dev.perfetto.Chaos#CrashNow');
+ },
+ });
+ }
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ ctx.registerCommand({
+ id: 'dev.perfetto.Chaos#CrashNowQuery',
+ name: 'Chaos: run crashing query',
+ callback: () => {
+ ctx.engine.query(`this is a
+ syntactically
+ invalid
+ query
+ over
+ many
+ lines
+ `);
+ },
+ });
+
+ ctx.registerCommand({
+ id: 'dev.perfetto.Chaos#AddCrashingDebugTrack',
+ name: 'Chaos: add crashing debug track',
+ callback: () => {
+ addDebugSliceTrack(
+ ctx.engine,
+ {
+ sqlSource: `
+ syntactically
+ invalid
+ query
+ over
+ many
+ `,
+ },
+ `Chaos track`,
+ {ts: 'ts', dur: 'dur', name: 'name'},
+ [],
+ );
+ },
+ });
+ }
+
+ async onTraceUnload(_: PluginContextTrace): Promise<void> {}
+
+ onDeactivate(_: PluginContext): void {}
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'dev.perfetto.Chaos',
+ plugin: Chaos,
+};
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 19c1867..97cee3c 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -21,7 +21,7 @@
import {LegacySelection} from '../common/state';
import {PanelSize} from '../frontend/panel';
import {EngineProxy} from '../trace_processor/engine';
-import {UntypedEventSet} from '../common/event_set';
+import {UntypedEventSet} from '../core/event_set';
export {EngineProxy} from '../trace_processor/engine';
export {
diff --git a/ui/src/tracks/debug/counter_track.ts b/ui/src/tracks/debug/counter_track.ts
index d61d573..5b37e3d 100644
--- a/ui/src/tracks/debug/counter_track.ts
+++ b/ui/src/tracks/debug/counter_track.ts
@@ -14,10 +14,7 @@
import m from 'mithril';
-import {Actions} from '../../common/actions';
import {BaseCounterTrack} from '../../frontend/base_counter_track';
-import {globals} from '../../frontend/globals';
-import {TrackButton} from '../../frontend/track_panel';
import {TrackContext} from '../../public';
import {EngineProxy} from '../../trace_processor/engine';
import {CounterDebugTrackConfig} from '../../frontend/debug_tracks';
@@ -49,20 +46,7 @@
}
getTrackShellButtons(): m.Children {
- return [
- this.getCounterContextMenu(),
- this.config.closeable &&
- m(TrackButton, {
- action: () => {
- globals.dispatch(
- Actions.removeTracks({trackKeys: [this.trackKey]}),
- );
- },
- i: 'close',
- tooltip: 'Close',
- showButton: true,
- }),
- ];
+ return this.getCounterContextMenu();
}
getSqlSource(): string {
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index dbab2be..4ea16dc 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -12,12 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import m from 'mithril';
-
-import {Actions} from '../../common/actions';
-import {globals} from '../../frontend/globals';
import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {TrackButton} from '../../frontend/track_panel';
import {TrackContext} from '../../public';
import {EngineProxy} from '../../trace_processor/engine';
import {
@@ -38,7 +33,6 @@
export interface DebugTrackV2Config {
data: SqlDataSource;
columns: SliceColumns;
- closeable: boolean;
argColumns: string[];
}
@@ -81,21 +75,6 @@
};
}
- getTrackShellButtons(): m.Children {
- return this.config.closeable
- ? m(TrackButton, {
- action: () => {
- globals.dispatch(
- Actions.removeTracks({trackKeys: [this.trackKey]}),
- );
- },
- i: 'close',
- tooltip: 'Close',
- showButton: true,
- })
- : [];
- }
-
private async createTrackTable(
data: SqlDataSource,
sliceColumns: SliceColumns,
diff --git a/ui/src/tracks/ftrace/ftrace_explorer.ts b/ui/src/tracks/ftrace/ftrace_explorer.ts
index 7015d5c..99a2ce2 100644
--- a/ui/src/tracks/ftrace/ftrace_explorer.ts
+++ b/ui/src/tracks/ftrace/ftrace_explorer.ts
@@ -39,14 +39,15 @@
STR_NULL,
} from '../../public';
import {raf} from '../../core/raf_scheduler';
-import {VisibleState} from '../../common/state';
+import {AsyncLimiter} from '../../base/async_limiter';
+import {Monitor} from '../../base/monitor';
const ROW_H = 20;
const PAGE_SIZE = 250;
interface FtraceExplorerAttrs {
counters: FtraceStat[];
- store: Store<FtraceFilter>;
+ filterStore: Store<FtraceFilter>;
engine: EngineProxy;
}
@@ -72,32 +73,37 @@
}
export class FtraceExplorer implements m.ClassComponent<FtraceExplorerAttrs> {
- private paginationStore = createStore<Pagination>({
+ private readonly paginationStore = createStore<Pagination>({
page: 0,
pageCount: 0,
});
- private oldPagination?: Pagination;
- private oldFilterState?: FtraceFilter;
- private oldVisibleState?: VisibleState;
+ private readonly monitor: Monitor;
+ private readonly queryLimiter = new AsyncLimiter();
// A cache of the data we have most recently loaded from our store
private data?: FtracePanelData;
+ constructor({attrs}: m.CVnode<FtraceExplorerAttrs>) {
+ this.monitor = new Monitor([
+ () => globals.state.frontendLocalState.visibleState.start,
+ () => globals.state.frontendLocalState.visibleState.end,
+ () => attrs.filterStore.state,
+ () => this.paginationStore.state,
+ ]);
+ }
+
view({attrs}: m.CVnode<FtraceExplorerAttrs>) {
- if (this.shouldUpdate(attrs.store)) {
- this.oldVisibleState = globals.state.frontendLocalState.visibleState;
- this.oldFilterState = attrs.store.state;
- this.oldPagination = this.paginationStore.state;
- lookupFtraceEvents(
- attrs.engine,
- this.paginationStore.state.page * PAGE_SIZE,
- this.paginationStore.state.pageCount * PAGE_SIZE,
- attrs.store.state,
- ).then((data) => {
- this.data = data;
+ this.monitor.ifStateChanged(() =>
+ this.queryLimiter.schedule(async () => {
+ this.data = await lookupFtraceEvents(
+ attrs.engine,
+ this.paginationStore.state.page * PAGE_SIZE,
+ this.paginationStore.state.pageCount * PAGE_SIZE,
+ attrs.filterStore.state,
+ );
raf.scheduleFullRedraw();
- });
- }
+ }),
+ );
return m(
DetailsShell,
@@ -124,7 +130,7 @@
const visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H);
// Work out which "page" we're on
- const page = Math.floor(visibleRowOffset / PAGE_SIZE) - 1;
+ const page = Math.max(0, Math.floor(visibleRowOffset / PAGE_SIZE) - 1);
const pageCount = Math.ceil(visibleRowCount / PAGE_SIZE) + 2;
if (page !== prevPage || pageCount !== prevPageCount) {
@@ -136,24 +142,6 @@
}
}
- private shouldUpdate(filterStore: Store<FtraceFilter>): boolean {
- if (filterStore.state !== this.oldFilterState) {
- return true;
- }
-
- if (
- globals.state.frontendLocalState.visibleState !== this.oldVisibleState
- ) {
- return true;
- }
-
- if (this.paginationStore.state !== this.oldPagination) {
- return true;
- }
-
- return false;
- }
-
onRowOver(ts: time) {
globals.dispatch(Actions.setHoverCursorTimestamp({ts}));
}
@@ -172,7 +160,7 @@
}
private renderFilterPanel(attrs: FtraceExplorerAttrs) {
- const excludeList = attrs.store.state.excludeList;
+ const excludeList = attrs.filterStore.state.excludeList;
const options: MultiSelectOption[] = attrs.counters.map(({name, count}) => {
return {
id: name,
@@ -196,7 +184,7 @@
newList.add(id);
}
});
- attrs.store.edit((draft) => {
+ attrs.filterStore.edit((draft) => {
draft.excludeList = Array.from(newList);
});
},
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index c5860b9..a58c27a 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -85,7 +85,11 @@
isEphemeral: false,
content: {
render: () =>
- m(FtraceExplorer, {counters, store: filterStore, engine: ctx.engine}),
+ m(FtraceExplorer, {
+ counters,
+ filterStore,
+ engine: ctx.engine,
+ }),
getTitle: () => 'Ftrace Events',
},
});