Merge "Add reason, reason_id and launch_dur values to slow_start_reason_with_details." into main
diff --git a/Android.bp b/Android.bp
index c46b914..8b07472 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2346,6 +2346,7 @@
":perfetto_protos_perfetto_trace_translation_lite_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_protos_third_party_pprof_zero_gen",
+ ":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_protos_third_party_statsd_config_zero_gen",
":perfetto_src_android_internal_headers",
":perfetto_src_android_internal_lazy_library_loader",
@@ -2390,6 +2391,7 @@
":perfetto_src_shared_lib_test_utils",
":perfetto_src_trace_processor_containers_containers",
":perfetto_src_trace_processor_db_column_column",
+ ":perfetto_src_trace_processor_db_compare",
":perfetto_src_trace_processor_db_db",
":perfetto_src_trace_processor_db_minimal",
":perfetto_src_trace_processor_export_json",
@@ -2413,6 +2415,7 @@
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -2422,6 +2425,7 @@
":perfetto_src_trace_processor_importers_systrace_full",
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
+ ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -2633,6 +2637,7 @@
"perfetto_protos_perfetto_trace_translation_lite_gen_headers",
"perfetto_protos_perfetto_trace_translation_zero_gen_headers",
"perfetto_protos_third_party_pprof_zero_gen_headers",
+ "perfetto_protos_third_party_simpleperf_zero_gen_headers",
"perfetto_protos_third_party_statsd_config_zero_gen_headers",
"perfetto_src_base_version_gen_h",
"perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
@@ -10387,6 +10392,50 @@
],
}
+// GN: //protos/third_party/simpleperf:zero
+filegroup {
+ name: "perfetto_protos_third_party_simpleperf_zero",
+ srcs: [
+ "protos/third_party/simpleperf/record_file.proto",
+ ],
+}
+
+// GN: //protos/third_party/simpleperf:zero
+genrule {
+ name: "perfetto_protos_third_party_simpleperf_zero_gen",
+ srcs: [
+ ":perfetto_protos_third_party_simpleperf_zero",
+ ],
+ tools: [
+ "aprotoc",
+ "protozero_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_simpleperf_zero)",
+ out: [
+ "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.cc",
+ ],
+}
+
+// GN: //protos/third_party/simpleperf:zero
+genrule {
+ name: "perfetto_protos_third_party_simpleperf_zero_gen_headers",
+ srcs: [
+ ":perfetto_protos_third_party_simpleperf_zero",
+ ],
+ tools: [
+ "aprotoc",
+ "protozero_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_simpleperf_zero)",
+ out: [
+ "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
// GN: //protos/third_party/statsd:config_zero
filegroup {
name: "perfetto_protos_third_party_statsd_config_zero",
@@ -12255,6 +12304,14 @@
],
}
+// GN: //src/trace_processor/importers/perf:tracker
+filegroup {
+ name: "perfetto_src_trace_processor_importers_perf_tracker",
+ srcs: [
+ "src/trace_processor/importers/perf/dso_tracker.cc",
+ ],
+}
+
// GN: //src/trace_processor/importers/perf:unittests
filegroup {
name: "perfetto_src_trace_processor_importers_perf_unittests",
@@ -12506,6 +12563,14 @@
],
}
+// GN: //src/trace_processor/importers/zip:full
+filegroup {
+ name: "perfetto_src_trace_processor_importers_zip_full",
+ srcs: [
+ "src/trace_processor/importers/zip/zip_trace_reader.cc",
+ ],
+}
+
// GN: //src/trace_processor:lib
filegroup {
name: "perfetto_src_trace_processor_lib",
@@ -13050,13 +13115,18 @@
"src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql",
"src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql",
"src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql",
+ "src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql",
"src/trace_processor/perfetto_sql/stdlib/graphs/dominator_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql",
"src/trace_processor/perfetto_sql/stdlib/graphs/search.sql",
"src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql",
"src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql",
+ "src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql",
"src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql",
+ "src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql",
+ "src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql",
+ "src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql",
"src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
@@ -13518,10 +13588,10 @@
"src/trace_redaction/proto_util.cc",
"src/trace_redaction/prune_package_list.cc",
"src/trace_redaction/redact_ftrace_event.cc",
- "src/trace_redaction/redact_process_free.cc",
"src/trace_redaction/redact_sched_switch.cc",
"src/trace_redaction/redact_task_newtask.cc",
"src/trace_redaction/remap_scheduling_events.cc",
+ "src/trace_redaction/remove_process_free_comm.cc",
"src/trace_redaction/scrub_ftrace_events.cc",
"src/trace_redaction/scrub_process_stats.cc",
"src/trace_redaction/scrub_process_trees.cc",
@@ -13529,6 +13599,7 @@
"src/trace_redaction/suspend_resume.cc",
"src/trace_redaction/trace_redaction_framework.cc",
"src/trace_redaction/trace_redactor.cc",
+ "src/trace_redaction/verify_integrity.cc",
],
}
@@ -13547,10 +13618,10 @@
"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_process_free_unittest.cc",
"src/trace_redaction/redact_sched_switch_unittest.cc",
"src/trace_redaction/redact_task_newtask_unittest.cc",
"src/trace_redaction/remap_scheduling_events_unittest.cc",
+ "src/trace_redaction/remove_process_free_comm_unittest.cc",
"src/trace_redaction/suspend_resume_unittest.cc",
],
}
@@ -14899,6 +14970,7 @@
":perfetto_protos_perfetto_trace_translation_lite_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_protos_third_party_pprof_zero_gen",
+ ":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_protos_third_party_statsd_config_zero_gen",
":perfetto_src_android_internal_headers",
":perfetto_src_android_internal_lazy_library_loader",
@@ -15013,6 +15085,7 @@
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_perf_unittests",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
@@ -15026,6 +15099,7 @@
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
":perfetto_src_trace_processor_importers_systrace_unittests",
+ ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -15275,6 +15349,7 @@
"perfetto_protos_perfetto_trace_translation_lite_gen_headers",
"perfetto_protos_perfetto_trace_translation_zero_gen_headers",
"perfetto_protos_third_party_pprof_zero_gen_headers",
+ "perfetto_protos_third_party_simpleperf_zero_gen_headers",
"perfetto_protos_third_party_statsd_config_zero_gen_headers",
"perfetto_src_base_version_gen_h",
"perfetto_src_ipc_test_messages_cpp_gen_headers",
@@ -15975,6 +16050,7 @@
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_protos_third_party_pprof_zero_gen",
+ ":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
":perfetto_src_base_http_http",
":perfetto_src_base_unix_socket",
@@ -15987,6 +16063,7 @@
":perfetto_src_protozero_protozero",
":perfetto_src_trace_processor_containers_containers",
":perfetto_src_trace_processor_db_column_column",
+ ":perfetto_src_trace_processor_db_compare",
":perfetto_src_trace_processor_db_db",
":perfetto_src_trace_processor_db_minimal",
":perfetto_src_trace_processor_export_json",
@@ -16010,6 +16087,7 @@
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -16019,6 +16097,7 @@
":perfetto_src_trace_processor_importers_systrace_full",
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
+ ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -16102,6 +16181,7 @@
"perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
"perfetto_protos_perfetto_trace_translation_zero_gen_headers",
"perfetto_protos_third_party_pprof_zero_gen_headers",
+ "perfetto_protos_third_party_simpleperf_zero_gen_headers",
"perfetto_src_base_version_gen_h",
"perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
"perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor",
@@ -16216,10 +16296,12 @@
":perfetto_protos_perfetto_trace_system_info_zero_gen",
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
+ ":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
":perfetto_src_protozero_protozero",
":perfetto_src_trace_processor_containers_containers",
":perfetto_src_trace_processor_db_column_column",
+ ":perfetto_src_trace_processor_db_compare",
":perfetto_src_trace_processor_db_minimal",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
@@ -16230,6 +16312,7 @@
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_minimal",
":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
":perfetto_src_trace_processor_importers_proto_proto_importer_module",
@@ -16295,6 +16378,7 @@
"perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
"perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
"perfetto_protos_perfetto_trace_translation_zero_gen_headers",
+ "perfetto_protos_third_party_simpleperf_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",
"perfetto_src_trace_processor_tables_tables_python",
@@ -16366,6 +16450,7 @@
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_protos_third_party_pprof_zero_gen",
+ ":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
":perfetto_src_base_version",
":perfetto_src_kernel_utils_syscall_table",
@@ -16376,6 +16461,7 @@
":perfetto_src_protozero_protozero",
":perfetto_src_trace_processor_containers_containers",
":perfetto_src_trace_processor_db_column_column",
+ ":perfetto_src_trace_processor_db_compare",
":perfetto_src_trace_processor_db_db",
":perfetto_src_trace_processor_db_minimal",
":perfetto_src_trace_processor_export_json",
@@ -16399,6 +16485,7 @@
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -16408,6 +16495,7 @@
":perfetto_src_trace_processor_importers_systrace_full",
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
+ ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -16493,6 +16581,7 @@
"perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
"perfetto_protos_perfetto_trace_translation_zero_gen_headers",
"perfetto_protos_third_party_pprof_zero_gen_headers",
+ "perfetto_protos_third_party_simpleperf_zero_gen_headers",
"perfetto_src_base_version_gen_h",
"perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
"perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor",
diff --git a/BUILD b/BUILD
index b3ff934..537ccc4 100644
--- a/BUILD
+++ b/BUILD
@@ -217,6 +217,7 @@
":src_kernel_utils_syscall_table",
":src_protozero_proto_ring_buffer",
":src_trace_processor_db_column_column",
+ ":src_trace_processor_db_compare",
":src_trace_processor_db_db",
":src_trace_processor_db_minimal",
":src_trace_processor_export_json",
@@ -240,6 +241,7 @@
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -249,6 +251,7 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
+ ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -345,6 +348,7 @@
":protos_perfetto_trace_track_event_zero",
":protos_perfetto_trace_translation_zero",
":protos_third_party_pprof_zero",
+ ":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
":src_base_version",
@@ -886,6 +890,7 @@
perfetto_filegroup(
name = "include_perfetto_trace_processor_storage",
srcs = [
+ "include/perfetto/trace_processor/ref_counted.h",
"include/perfetto/trace_processor/trace_blob.h",
"include/perfetto/trace_processor/trace_blob_view.h",
"include/perfetto/trace_processor/trace_processor_storage.h",
@@ -899,7 +904,6 @@
"include/perfetto/trace_processor/iterator.h",
"include/perfetto/trace_processor/metatrace_config.h",
"include/perfetto/trace_processor/read_trace.h",
- "include/perfetto/trace_processor/ref_counted.h",
"include/perfetto/trace_processor/trace_processor.h",
],
)
@@ -1423,6 +1427,14 @@
],
)
+# GN target: //src/trace_processor/db:compare
+perfetto_filegroup(
+ name = "src_trace_processor_db_compare",
+ srcs = [
+ "src/trace_processor/db/compare.h",
+ ],
+)
+
# GN target: //src/trace_processor/db:db
perfetto_filegroup(
name = "src_trace_processor_db_db",
@@ -1738,6 +1750,15 @@
],
)
+# GN target: //src/trace_processor/importers/perf:tracker
+perfetto_filegroup(
+ name = "src_trace_processor_importers_perf_tracker",
+ srcs = [
+ "src/trace_processor/importers/perf/dso_tracker.cc",
+ "src/trace_processor/importers/perf/dso_tracker.h",
+ ],
+)
+
# GN target: //src/trace_processor/importers/proto/winscope:full
perfetto_filegroup(
name = "src_trace_processor_importers_proto_winscope_full",
@@ -1993,6 +2014,15 @@
],
)
+# GN target: //src/trace_processor/importers/zip:full
+perfetto_filegroup(
+ name = "src_trace_processor_importers_zip_full",
+ srcs = [
+ "src/trace_processor/importers/zip/zip_trace_reader.cc",
+ "src/trace_processor/importers/zip/zip_trace_reader.h",
+ ],
+)
+
# GN target: //src/trace_processor/metrics/sql/android:android
perfetto_filegroup(
name = "src_trace_processor_metrics_sql_android_android",
@@ -2578,6 +2608,14 @@
],
)
+# GN target: //src/trace_processor/perfetto_sql/stdlib/gpu:gpu
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_gpu_gpu",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql",
+ ],
+)
+
# GN target: //src/trace_processor/perfetto_sql/stdlib/graphs:graphs
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_graphs_graphs",
@@ -2605,6 +2643,24 @@
],
)
+# GN target: //src/trace_processor/perfetto_sql/stdlib/memory/android:android
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_memory_android_android",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql",
+ ],
+)
+
+# GN target: //src/trace_processor/perfetto_sql/stdlib/memory/linux:linux
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_memory_linux_linux",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql",
+ "src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql",
+ "src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql",
+ ],
+)
+
# GN target: //src/trace_processor/perfetto_sql/stdlib/memory:memory
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_memory_memory",
@@ -2717,9 +2773,12 @@
":src_trace_processor_perfetto_sql_stdlib_cpu_cpu",
":src_trace_processor_perfetto_sql_stdlib_cpu_utilization_utilization",
":src_trace_processor_perfetto_sql_stdlib_deprecated_v42_common_common",
+ ":src_trace_processor_perfetto_sql_stdlib_gpu_gpu",
":src_trace_processor_perfetto_sql_stdlib_graphs_graphs",
":src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
":src_trace_processor_perfetto_sql_stdlib_linux_linux",
+ ":src_trace_processor_perfetto_sql_stdlib_memory_android_android",
+ ":src_trace_processor_perfetto_sql_stdlib_memory_linux_linux",
":src_trace_processor_perfetto_sql_stdlib_memory_memory",
":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
@@ -5557,6 +5616,25 @@
],
)
+# GN target: //protos/third_party/simpleperf:source_set
+perfetto_proto_library(
+ name = "protos_third_party_simpleperf_protos",
+ srcs = [
+ "protos/third_party/simpleperf/record_file.proto",
+ ],
+ visibility = [
+ PERFETTO_CONFIG.proto_library_visibility,
+ ],
+)
+
+# GN target: //protos/third_party/simpleperf:zero
+perfetto_cc_protozero_library(
+ name = "protos_third_party_simpleperf_zero",
+ deps = [
+ ":protos_third_party_simpleperf_protos",
+ ],
+)
+
# GN target: //protos/third_party/statsd:config_source_set
perfetto_proto_library(
name = "protos_third_party_statsd_config_protos",
@@ -5910,6 +5988,7 @@
srcs = [
":src_kernel_utils_syscall_table",
":src_trace_processor_db_column_column",
+ ":src_trace_processor_db_compare",
":src_trace_processor_db_db",
":src_trace_processor_db_minimal",
":src_trace_processor_export_json",
@@ -5933,6 +6012,7 @@
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -5942,6 +6022,7 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
+ ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -6038,6 +6119,7 @@
":protos_perfetto_trace_track_event_zero",
":protos_perfetto_trace_translation_zero",
":protos_third_party_pprof_zero",
+ ":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
":src_trace_processor_containers_containers",
@@ -6086,6 +6168,7 @@
":src_profiling_symbolizer_symbolizer",
":src_protozero_proto_ring_buffer",
":src_trace_processor_db_column_column",
+ ":src_trace_processor_db_compare",
":src_trace_processor_db_db",
":src_trace_processor_db_minimal",
":src_trace_processor_export_json",
@@ -6109,6 +6192,7 @@
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -6118,6 +6202,7 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
+ ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -6203,6 +6288,7 @@
":protos_perfetto_trace_track_event_zero",
":protos_perfetto_trace_translation_zero",
":protos_third_party_pprof_zero",
+ ":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
":src_base_http_http",
@@ -6322,6 +6408,7 @@
":src_profiling_symbolizer_symbolizer",
":src_protozero_proto_ring_buffer",
":src_trace_processor_db_column_column",
+ ":src_trace_processor_db_compare",
":src_trace_processor_db_db",
":src_trace_processor_db_minimal",
":src_trace_processor_export_json",
@@ -6345,6 +6432,7 @@
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -6354,6 +6442,7 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
+ ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -6439,6 +6528,7 @@
":protos_perfetto_trace_track_event_zero",
":protos_perfetto_trace_translation_zero",
":protos_third_party_pprof_zero",
+ ":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
":src_base_version",
diff --git a/CHANGELOG b/CHANGELOG
index 70cb79c..8aae7f5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,10 +1,13 @@
Unreleased:
Tracing service and probes:
- *
+ *
SQL Standard library:
* Added megacycles support to CPU package. Added tables:
`cpu_cycles_per_process`, `cpu_cycles_per_thread` and
`cpu_cycles_per_cpu`.
+ * Improved `memory` package. Added `memory.linux.process`,
+ `memory.linux.high_watermark` and `memory.android.gpu` modules.
+ * Created `gpu` package with `gpu.frequency` module.
* Migrated `sched.utilization` package to `cpu.utilization`.
Trace Processor:
* Added "time to initial display" and "time to full display" metrics to
diff --git a/bazel/proto_gen.bzl b/bazel/proto_gen.bzl
index c5fd127..e237ebc 100644
--- a/bazel/proto_gen.bzl
+++ b/bazel/proto_gen.bzl
@@ -46,13 +46,17 @@
strip_base_path = ctx.label.package + "/"
elif ctx.label.workspace_root:
# This path is hit when proto targets are built as @perfetto//:xxx
- # instead of //:xxx. This happens in embedder builds. In this case,
- # workspace_root == "external/perfetto" and we need to rebase the paths
- # passed to protoc.
+ # instead of //:xxx. This happens in embedder builds.
proto_path = ctx.label.workspace_root
- out_dir += "/" + ctx.label.workspace_root
+
+ # We could be using the sibling repository layout, in which case we do nothing.
+ if not ctx.label.workspace_root.startswith("../"):
+ # workspace_root == "external/perfetto" and we need to rebase the paths
+ # passed to protoc.
+ out_dir += "/" + ctx.label.workspace_root
strip_base_path = ctx.label.workspace_root + "/"
+
out_files = []
suffix = ctx.attr.suffix
for src in proto_src:
diff --git a/docs/reference/perfetto-cli.md b/docs/reference/perfetto-cli.md
index 195ceb1..377502e 100644
--- a/docs/reference/perfetto-cli.md
+++ b/docs/reference/perfetto-cli.md
@@ -124,7 +124,7 @@
: Specifies the path to a configuration file. In normal mode, some
configurations may be encoded in a configuration protocol buffer.
This file must comply with the protocol buffer schema defined in AOSP
- [`trace_config.proto`](/protos/perfetto/config/data_source_config.proto).
+ [`trace_config.proto`](/protos/perfetto/config/trace_config.proto).
You select and configure the data sources using the DataSourceConfig member
of the TraceConfig, as defined in AOSP
[`data_source_config.proto`](/protos/perfetto/config/data_source_config.proto).
diff --git a/gn/standalone/protoc.py b/gn/standalone/protoc.py
index 52e156e..bd8913f 100644
--- a/gn/standalone/protoc.py
+++ b/gn/standalone/protoc.py
@@ -16,45 +16,9 @@
This script exists to work-around the bad depfile generation by protoc when
generating descriptors."""
-from __future__ import print_function
-import argparse
-import os
import sys
import subprocess
-import tempfile
-import uuid
-
-from codecs import open
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--descriptor_set_out', default=None)
- parser.add_argument('--dependency_out', default=None)
- parser.add_argument('protoc')
- args, remaining = parser.parse_known_args()
-
- if args.dependency_out and args.descriptor_set_out:
- tmp_path = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
- custom = [
- '--descriptor_set_out', args.descriptor_set_out, '--dependency_out',
- tmp_path
- ]
- try:
- cmd = [args.protoc] + custom + remaining
- subprocess.check_call(cmd)
- with open(tmp_path, 'rb') as tmp_rd:
- dependency_data = tmp_rd.read().decode('utf-8')
- finally:
- if os.path.exists(tmp_path):
- os.unlink(tmp_path)
-
- with open(args.dependency_out, 'w', encoding='utf-8') as f:
- f.write(args.descriptor_set_out + ":")
- f.write(dependency_data)
- else:
- subprocess.check_call(sys.argv[1:])
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(subprocess.call(sys.argv[1:]))
diff --git a/include/perfetto/ext/base/getopt.h b/include/perfetto/ext/base/getopt.h
index bf993fc..abf8cca 100644
--- a/include/perfetto/ext/base/getopt.h
+++ b/include/perfetto/ext/base/getopt.h
@@ -45,7 +45,7 @@
::perfetto::base::getopt_compat::required_argument;
#else
-#include <getopt.h>
+#include <getopt.h> // IWYU pragma: export
#endif
#endif // INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
diff --git a/include/perfetto/trace_processor/BUILD.gn b/include/perfetto/trace_processor/BUILD.gn
index d0dae7f..8c26054 100644
--- a/include/perfetto/trace_processor/BUILD.gn
+++ b/include/perfetto/trace_processor/BUILD.gn
@@ -17,7 +17,6 @@
"iterator.h",
"metatrace_config.h",
"read_trace.h",
- "ref_counted.h",
"trace_processor.h",
]
public_deps = [
@@ -28,6 +27,7 @@
source_set("storage") {
sources = [
+ "ref_counted.h",
"trace_blob.h",
"trace_blob_view.h",
"trace_processor_storage.h",
diff --git a/perfetto.rc b/perfetto.rc
index 3f1801d..e217db2 100644
--- a/perfetto.rc
+++ b/perfetto.rc
@@ -58,7 +58,7 @@
mkdir /data/misc/perfetto-traces/bugreport 0773 root shell
# Traces in this directory are only accessed by system server
- mkdir /data/misc/perfetto-traces/profiling 0773 root shell
+ mkdir /data/misc/perfetto-traces/profiling 0733 root shell
# This directory allows shell to save configs file in a place where the
# perfetto cmdline client can read then. /data/local/tmp/ isn't safe because
diff --git a/protos/perfetto/common/observable_events.proto b/protos/perfetto/common/observable_events.proto
index 85767a6..b841a0b 100644
--- a/protos/perfetto/common/observable_events.proto
+++ b/protos/perfetto/common/observable_events.proto
@@ -63,6 +63,9 @@
// consumer has no idea of what is the TSID of its own tracing session and
// there is no other good way to plumb it.
optional int64 tracing_session_id = 1;
+
+ // The trigger name of the CLONE_SNAPSHOT trigger which was hit.
+ optional string trigger_name = 2;
}
repeated DataSourceInstanceStateChange instance_state_changes = 1;
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index 900a64b..90234da 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -15,7 +15,10 @@
import("../../../../gn/proto_library.gni")
perfetto_proto_library("@TYPE@") {
- deps = [ "../../common:@TYPE@", ":winscope_regular_@TYPE@" ]
+ deps = [
+ ":winscope_regular_@TYPE@",
+ "../../common:@TYPE@",
+ ]
sources = [
"android_game_intervention_list.proto",
@@ -44,8 +47,8 @@
# Winscope messages added to TracePacket directly
perfetto_proto_library("winscope_regular_@TYPE@") {
deps = [
- "../../common:@TYPE@",
":winscope_common_@TYPE@",
+ "../../common:@TYPE@",
]
sources = [
"protolog.proto",
@@ -64,19 +67,19 @@
]
public_deps = [ ":winscope_common_@TYPE@" ]
sources = [
- "inputmethodeditor.proto",
"graphics/pixelformat.proto",
+ "inputmethodeditor.proto",
"inputmethodservice/inputmethodservice.proto",
"inputmethodservice/softinputwindow.proto",
"server/inputmethod/inputmethodmanagerservice.proto",
"typedef.proto",
- "view/inputmethod/editorinfo.proto",
- "view/inputmethod/inputconnection.proto",
- "view/inputmethod/inputmethodmanager.proto",
"view/display.proto",
"view/displaycutout.proto",
"view/imefocuscontroller.proto",
"view/imeinsetssourceconsumer.proto",
+ "view/inputmethod/editorinfo.proto",
+ "view/inputmethod/inputconnection.proto",
+ "view/inputmethod/inputmethodmanager.proto",
"view/insetsanimationcontrolimpl.proto",
"view/insetscontroller.proto",
"view/insetssource.proto",
@@ -95,8 +98,8 @@
proto_generators = [ "descriptor" ]
generate_descriptor = "winscope.descriptor"
deps = [
- ":winscope_regular_source_set",
":winscope_extensions_source_set",
+ ":winscope_regular_source_set",
]
sources = [ "winscope.proto" ]
import_dirs = [ "${perfetto_protobuf_src_dir}" ]
diff --git a/protos/third_party/simpleperf/BUILD.gn b/protos/third_party/simpleperf/BUILD.gn
new file mode 100644
index 0000000..da934be
--- /dev/null
+++ b/protos/third_party/simpleperf/BUILD.gn
@@ -0,0 +1,19 @@
+# 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("../../../gn/proto_library.gni")
+
+perfetto_proto_library("@TYPE@") {
+ sources = [ "record_file.proto" ]
+}
diff --git a/protos/third_party/simpleperf/record_file.proto b/protos/third_party/simpleperf/record_file.proto
new file mode 100644
index 0000000..ce2962e
--- /dev/null
+++ b/protos/third_party/simpleperf/record_file.proto
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// message types used in perf.data.
+
+syntax = "proto3";
+
+// This is in perfetto.third_party to avoid clashing with potential other
+// copies of this proto.
+package perfetto.third_party.simpleperf.proto;
+
+message DebugUnwindFeature {
+ message File {
+ string path = 1;
+ uint64 size = 2;
+ }
+
+ repeated File file = 1;
+}
+
+message FileFeature {
+ // This enum is not defined in the original simpleperf proto file.
+ // We added it here for convenience. Simpleperf uses a regular c++ enum
+ // instead. See comment in type field.
+ enum DsoType {
+ DSO_KERNEL = 0;
+ DSO_KERNEL_MODULE = 1;
+ DSO_ELF_FILE = 2;
+ DSO_DEX_FILE = 3;
+ DSO_SYMBOL_MAP_FILE = 4;
+ DSO_UNKNOWN_FILE = 5;
+ }
+ string path = 1;
+ // The original simpleperf proto defines this field as a uint32. We use an
+ // enum here instead for convenience. enum fields are encoded as if they were
+ // int32 values, and both uint32 and int32 values have the same proto wire
+ // format, so using the enum here is fine.
+ DsoType type = 2;
+ uint64 min_vaddr = 3;
+
+ message Symbol {
+ uint64 vaddr = 1;
+ uint32 len = 2;
+ string name = 3;
+ }
+ repeated Symbol symbol = 4;
+
+ message DexFile {
+ repeated uint64 dex_file_offset = 1;
+ }
+ message ElfFile {
+ uint64 file_offset_of_min_vaddr = 1;
+ }
+ message KernelModule {
+ uint64 memory_offset_of_min_vaddr = 1;
+ }
+
+ oneof type_specific_msg {
+ DexFile dex_file = 5; // Only when type = DSO_DEX_FILE
+ ElfFile elf_file = 6; // Only when type = DSO_ELF_FILE
+ KernelModule kernel_module = 7; // Only when type = DSO_KERNEL_MODULE
+ }
+}
\ No newline at end of file
diff --git a/python/generators/diff_tests/runner.py b/python/generators/diff_tests/runner.py
index 0fe0982..53a5df4 100644
--- a/python/generators/diff_tests/runner.py
+++ b/python/generators/diff_tests/runner.py
@@ -379,6 +379,7 @@
return res
if result.exit_code != 0 or not result.passed:
+ result.passed = False
str += result.stderr
if result.exit_code == 0:
diff --git a/src/android_internal/statsd_logging.cc b/src/android_internal/statsd_logging.cc
index a164adf..09b55a2 100644
--- a/src/android_internal/statsd_logging.cc
+++ b/src/android_internal/statsd_logging.cc
@@ -16,12 +16,11 @@
#include "src/android_internal/statsd_logging.h"
-#include <string.h>
+#include <cstdint>
#include <statslog_perfetto.h>
-namespace perfetto {
-namespace android_internal {
+namespace perfetto::android_internal {
void StatsdLogUploadEvent(PerfettoStatsdAtom atom,
int64_t uuid_lsb,
@@ -35,5 +34,4 @@
stats_write(PERFETTO_TRIGGER, static_cast<int32_t>(atom), trigger_name);
}
-} // namespace android_internal
-} // namespace perfetto
+} // namespace perfetto::android_internal
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index 1981a77..6352042 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -27,6 +27,8 @@
// Checkpoints inside perfetto_cmd before tracing is finished.
kTraceBegin = 1,
kBackgroundTraceBegin = 2,
+ kCloneTraceBegin = 55,
+ kCloneTriggerTraceBegin = 56,
kOnConnect = 3,
// Guardrails inside perfetto_cmd before tracing is finished.
@@ -105,7 +107,7 @@
// longer supports uploading traces using Dropbox.
// reserved 5, 6, 7;
- // Contained status of guardrail state initalization and upload limit in
+ // Contained status of guardrail state initialization and upload limit in
// perfetto_cmd. Removed as perfetto no longer manages stateful guardrails
// reserved 44, 45, 46;
};
diff --git a/src/android_stats/statsd_logging_helper.cc b/src/android_stats/statsd_logging_helper.cc
index c943027..1f5011a 100644
--- a/src/android_stats/statsd_logging_helper.cc
+++ b/src/android_stats/statsd_logging_helper.cc
@@ -16,10 +16,12 @@
#include "src/android_stats/statsd_logging_helper.h"
+#include <cstdint>
#include <string>
+#include <vector>
#include "perfetto/base/build_config.h"
-#include "perfetto/base/compiler.h"
+#include "src/android_stats/perfetto_atoms.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \
PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
@@ -27,8 +29,7 @@
#include "src/android_internal/statsd_logging.h" // nogncheck
#endif
-namespace perfetto {
-namespace android_stats {
+namespace perfetto::android_stats {
// Make sure we don't accidentally log on non-Android tree build. Note that even
// removing this ifdef still doesn't make uploads work on OS_ANDROID.
@@ -75,5 +76,4 @@
const std::vector<std::string>&) {}
#endif
-} // namespace android_stats
-} // namespace perfetto
+} // namespace perfetto::android_stats
diff --git a/src/android_stats/statsd_logging_helper.h b/src/android_stats/statsd_logging_helper.h
index 5f0911f..0ba2d65 100644
--- a/src/android_stats/statsd_logging_helper.h
+++ b/src/android_stats/statsd_logging_helper.h
@@ -18,6 +18,7 @@
#define SRC_ANDROID_STATS_STATSD_LOGGING_HELPER_H_
#include <stdint.h>
+#include <optional>
#include <string>
#include <vector>
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 2ccd24e..e11a485 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -16,46 +16,47 @@
#include "src/perfetto_cmd/perfetto_cmd.h"
-#include "perfetto/base/build_config.h"
-#include "perfetto/base/proc_utils.h"
-#include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/string_splitter.h"
-
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
-// For dup() (and _setmode() on windows).
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-#include <fcntl.h>
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-
+#include <algorithm>
+#include <array>
#include <atomic>
#include <chrono>
-#include <fstream>
+#include <cinttypes>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
#include <iostream>
#include <iterator>
+#include <memory>
#include <mutex>
+#include <optional>
#include <random>
-#include <sstream>
+#include <string>
#include <thread>
+#include <utility>
+#include <vector>
+#include "perfetto/base/build_config.h"
#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"
-#include "perfetto/base/time.h"
-#include "perfetto/ext/base/android_utils.h"
+#include "perfetto/base/proc_utils.h" // IWYU pragma: keep
+#include "perfetto/ext/base/android_utils.h" // IWYU pragma: keep
#include "perfetto/ext/base/ctrl_c_handler.h"
#include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/getopt.h"
+#include "perfetto/ext/base/getopt.h" // IWYU pragma: keep
#include "perfetto/ext/base/no_destructor.h"
#include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/thread_task_runner.h"
#include "perfetto/ext/base/thread_utils.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/uuid.h"
@@ -64,11 +65,14 @@
#include "perfetto/ext/traced/traced.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
-#include "perfetto/protozero/proto_utils.h"
-#include "perfetto/tracing/core/data_source_descriptor.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
+#include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
+#include "perfetto/tracing/core/flush_flags.h"
+#include "perfetto/tracing/core/forward_decls.h"
#include "perfetto/tracing/core/trace_config.h"
-#include "perfetto/tracing/core/tracing_service_state.h"
#include "perfetto/tracing/default_socket.h"
+#include "protos/perfetto/common/data_source_descriptor.gen.h"
+#include "src/android_stats/perfetto_atoms.h"
#include "src/android_stats/statsd_logging_helper.h"
#include "src/perfetto_cmd/bugreport_path.h"
#include "src/perfetto_cmd/config.h"
@@ -81,6 +85,14 @@
#include "protos/perfetto/common/tracing_service_state.gen.h"
#include "protos/perfetto/common/track_event_descriptor.gen.h"
+// For dup() (and _setmode() on windows).
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <fcntl.h>
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
namespace perfetto {
namespace {
@@ -92,7 +104,7 @@
class LoggingErrorReporter : public ErrorReporter {
public:
LoggingErrorReporter(std::string file_name, const char* config)
- : file_name_(file_name), config_(config) {}
+ : file_name_(std::move(file_name)), config_(config) {}
void AddError(size_t row,
size_t column,
@@ -1014,7 +1026,14 @@
std::this_thread::sleep_for(std::chrono::milliseconds(dist(minstd)));
}
- if (trace_config_->trigger_config().trigger_timeout_ms() == 0) {
+ if (clone_tsid_) {
+ if (snapshot_trigger_name_.empty()) {
+ LogUploadEvent(PerfettoStatsdAtom::kCloneTraceBegin);
+ } else {
+ LogUploadEvent(PerfettoStatsdAtom::kCloneTriggerTraceBegin,
+ snapshot_trigger_name_);
+ }
+ } else if (trace_config_->trigger_config().trigger_timeout_ms() == 0) {
LogUploadEvent(PerfettoStatsdAtom::kTraceBegin);
} else {
LogUploadEvent(PerfettoStatsdAtom::kBackgroundTraceBegin);
@@ -1533,11 +1552,15 @@
}
if (observable_events.has_clone_trigger_hit()) {
int64_t tsid = observable_events.clone_trigger_hit().tracing_session_id();
- OnCloneSnapshotTriggerReceived(static_cast<TracingSessionID>(tsid));
+ std::string trigger_name =
+ observable_events.clone_trigger_hit().trigger_name();
+ OnCloneSnapshotTriggerReceived(static_cast<TracingSessionID>(tsid),
+ std::move(trigger_name));
}
}
-void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid) {
+void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid,
+ std::string trigger_name) {
std::string cmdline;
cmdline.reserve(128);
ArgsAppend(&cmdline, "perfetto");
@@ -1555,13 +1578,15 @@
} else {
PERFETTO_FATAL("Cannot use CLONE_SNAPSHOT with the current cmdline args");
}
- CloneSessionOnThread(tsid, cmdline, kSingleExtraThread, nullptr);
+ CloneSessionOnThread(tsid, cmdline, kSingleExtraThread,
+ std::move(trigger_name), nullptr);
}
void PerfettoCmd::CloneSessionOnThread(
TracingSessionID tsid,
const std::string& cmdline,
CloneThreadMode thread_mode,
+ std::string trigger_name,
std::function<void()> on_clone_callback) {
PERFETTO_DLOG("Creating snapshot for tracing session %" PRIu64, tsid);
@@ -1583,7 +1608,7 @@
std::string trace_config_copy = trace_config_->SerializeAsString();
snapshot_threads_.back().PostTask(
- [tsid, cmdline, trace_config_copy, on_clone_callback] {
+ [tsid, cmdline, trace_config_copy, trigger_name, on_clone_callback] {
int argc = 0;
char* argv[32];
// `splitter` needs to live on the stack for the whole scope as it owns
@@ -1595,6 +1620,7 @@
}
perfetto::PerfettoCmd cmd;
cmd.snapshot_config_ = std::move(trace_config_copy);
+ cmd.snapshot_trigger_name_ = std::move(trigger_name);
cmd.on_session_cloned_ = on_clone_callback;
auto cmdline_res = cmd.ParseCmdlineAndMaybeDaemonize(argc, argv);
PERFETTO_CHECK(!cmdline_res.has_value()); // No daemonization expected.
@@ -1686,7 +1712,7 @@
ArgsAppend(&cmdline, "--clone-for-bugreport");
ArgsAppend(&cmdline, "--out");
ArgsAppend(&cmdline, out_path);
- CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, sync_fn);
+ CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, "", sync_fn);
} // for(sessions)
PERFETTO_DLOG("Issuing %zu CloneSession requests", num_sessions);
@@ -1720,6 +1746,15 @@
android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb());
}
+void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom,
+ const std::string& trigger_name) {
+ if (!statsd_logging_)
+ return;
+ base::Uuid uuid(uuid_);
+ android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb(),
+ trigger_name);
+}
+
void PerfettoCmd::LogTriggerEvents(
PerfettoTriggerAtom atom,
const std::vector<std::string>& trigger_names) {
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 844afaf..160a701 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -17,8 +17,7 @@
#ifndef SRC_PERFETTO_CMD_PERFETTO_CMD_H_
#define SRC_PERFETTO_CMD_PERFETTO_CMD_H_
-#include <time.h>
-
+#include <cstdint>
#include <functional>
#include <list>
#include <memory>
@@ -32,9 +31,12 @@
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/thread_task_runner.h"
#include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/uuid.h"
#include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/consumer.h"
#include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
+#include "perfetto/tracing/core/forward_decls.h"
#include "src/android_stats/perfetto_atoms.h"
#include "src/perfetto_cmd/packet_writer.h"
@@ -86,6 +88,7 @@
void CloneSessionOnThread(TracingSessionID,
const std::string& cmdline, // \0 separated.
CloneThreadMode,
+ std::string clone_trigger_name,
std::function<void()> on_clone_callback);
void OnTimeout();
bool is_detach() const { return !detach_key_.empty(); }
@@ -126,7 +129,8 @@
// will have no effect.
void NotifyBgProcessPipe(BgProcessStatus status);
- void OnCloneSnapshotTriggerReceived(TracingSessionID);
+ void OnCloneSnapshotTriggerReceived(TracingSessionID,
+ std::string trigger_name);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
static base::ScopedFile CreateUnlinkedTmpFile();
@@ -135,6 +139,7 @@
void ReportTraceToAndroidFrameworkOrCrash();
#endif
void LogUploadEvent(PerfettoStatsdAtom atom);
+ void LogUploadEvent(PerfettoStatsdAtom atom, const std::string& trigger_name);
void LogTriggerEvents(PerfettoTriggerAtom atom,
const std::vector<std::string>& trigger_names);
@@ -185,6 +190,7 @@
std::list<base::ThreadTaskRunner> snapshot_threads_;
int snapshot_count_ = 0;
std::string snapshot_config_;
+ std::string snapshot_trigger_name_;
base::WeakPtrFactory<PerfettoCmd> weak_factory_{this};
};
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index b499f0b..802f9bc 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -123,6 +123,7 @@
"importers/ftrace:minimal",
"importers/fuchsia:fuchsia_record",
"importers/memory_tracker:graph_processor",
+ "importers/perf:tracker",
"importers/proto:gen_cc_chrome_track_event_descriptor",
"importers/proto:gen_cc_track_event_descriptor",
"importers/proto:minimal",
@@ -176,6 +177,7 @@
"importers/proto:full",
"importers/proto:minimal",
"importers/systrace:full",
+ "importers/zip:full",
"metrics",
"perfetto_sql/engine",
"perfetto_sql/intrinsics/functions",
diff --git a/src/trace_processor/containers/string_pool.h b/src/trace_processor/containers/string_pool.h
index 75a57a2..2e03592 100644
--- a/src/trace_processor/containers/string_pool.h
+++ b/src/trace_processor/containers/string_pool.h
@@ -17,21 +17,25 @@
#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
#define SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
+#include <cstdint>
+#include <functional>
#include <limits>
+#include <memory>
#include <optional>
+#include <string>
+#include <utility>
#include <vector>
+#include "perfetto/base/logging.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/paged_memory.h"
+#include "perfetto/ext/base/string_view.h"
#include "perfetto/protozero/proto_utils.h"
#include "src/trace_processor/containers/null_term_string_view.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// Interns strings in a string pool and hands out compact StringIds which can
// be used to retrieve the string in O(1).
@@ -40,34 +44,38 @@
struct Id {
Id() = default;
- bool operator==(const Id& other) const { return other.id == id; }
- bool operator!=(const Id& other) const { return !(other == *this); }
- bool operator<(const Id& other) const { return id < other.id; }
+ constexpr bool operator==(const Id& other) const { return other.id == id; }
+ constexpr bool operator!=(const Id& other) const {
+ return !(other == *this);
+ }
+ constexpr bool operator<(const Id& other) const { return id < other.id; }
- bool is_null() const { return id == 0u; }
+ constexpr bool is_null() const { return id == 0u; }
- bool is_large_string() const { return id & kLargeStringFlagBitMask; }
+ constexpr bool is_large_string() const {
+ return id & kLargeStringFlagBitMask;
+ }
- uint32_t block_offset() const { return id & kBlockOffsetBitMask; }
+ constexpr uint32_t block_offset() const { return id & kBlockOffsetBitMask; }
- uint32_t block_index() const {
+ constexpr uint32_t block_index() const {
return (id & kBlockIndexBitMask) >> kNumBlockOffsetBits;
}
- uint32_t large_string_index() const {
+ constexpr uint32_t large_string_index() const {
PERFETTO_DCHECK(is_large_string());
return id & ~kLargeStringFlagBitMask;
}
- uint32_t raw_id() const { return id; }
+ constexpr uint32_t raw_id() const { return id; }
- static Id LargeString(size_t index) {
+ static constexpr Id LargeString(size_t index) {
PERFETTO_DCHECK(index <= static_cast<uint32_t>(index));
PERFETTO_DCHECK(!(index & kLargeStringFlagBitMask));
return Id(kLargeStringFlagBitMask | static_cast<uint32_t>(index));
}
- static Id BlockString(size_t index, uint32_t offset) {
+ static constexpr Id BlockString(size_t index, uint32_t offset) {
PERFETTO_DCHECK(index < (1u << (kNumBlockIndexBits + 1)));
PERFETTO_DCHECK(offset < (1u << (kNumBlockOffsetBits + 1)));
return Id(~kLargeStringFlagBitMask &
@@ -80,7 +88,7 @@
static constexpr Id Null() { return Id(0u); }
private:
- constexpr Id(uint32_t i) : id(i) {}
+ constexpr explicit Id(uint32_t i) : id(i) {}
uint32_t id;
};
@@ -88,7 +96,7 @@
// Iterator over the strings in the pool.
class Iterator {
public:
- Iterator(const StringPool*);
+ explicit Iterator(const StringPool*);
explicit operator bool() const;
Iterator& operator++();
@@ -278,7 +286,7 @@
static NullTermStringView GetFromBlockPtr(const uint8_t* ptr) {
uint32_t size = 0;
const uint8_t* str_ptr = ReadSize(ptr, &size);
- return NullTermStringView(reinterpret_cast<const char*>(str_ptr), size);
+ return {reinterpret_cast<const char*>(str_ptr), size};
}
// Lookup a string in the |large_strings_| vector. |id| should have the MSB
@@ -288,7 +296,7 @@
size_t index = id.large_string_index();
PERFETTO_DCHECK(index < large_strings_.size());
const std::string* str = large_strings_[index].get();
- return NullTermStringView(str->c_str(), str->size());
+ return {str->c_str(), str->size()};
}
// The actual memory storing the strings.
@@ -308,8 +316,7 @@
string_index_{/*initial_capacity=*/4096u};
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
template <>
struct std::hash<::perfetto::trace_processor::StringPool::Id> {
diff --git a/src/trace_processor/db/column/BUILD.gn b/src/trace_processor/db/column/BUILD.gn
index d53e7ae..f77887f 100644
--- a/src/trace_processor/db/column/BUILD.gn
+++ b/src/trace_processor/db/column/BUILD.gn
@@ -43,6 +43,7 @@
"utils.h",
]
deps = [
+ "..:compare",
"../..:metatrace",
"../../../../gn:default_deps",
"../../../../include/perfetto/trace_processor",
diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc
index c31d7c2..e7bc8ff 100644
--- a/src/trace_processor/db/column/arrangement_overlay.cc
+++ b/src/trace_processor/db/column/arrangement_overlay.cc
@@ -68,12 +68,21 @@
if (does_arrangement_order_storage_ && op != FilterOp::kGlob &&
op != FilterOp::kRegex) {
- Range inner_res = inner_->OrderedIndexSearchValidated(
- op, sql_val,
- OrderedIndices{arrangement_->data() + in.start, in.size(),
- arrangement_state_});
+ OrderedIndices indices{arrangement_->data() + in.start, in.size(),
+ arrangement_state_};
+ if (op == FilterOp::kNe) {
+ // Do an equality search and "invert" the range.
+ Range inner_res =
+ inner_->OrderedIndexSearchValidated(FilterOp::kEq, sql_val, indices);
+ BitVector bv(in.start);
+ bv.Resize(in.start + inner_res.start, true);
+ bv.Resize(in.start + inner_res.end, false);
+ bv.Resize(in.end, true);
+ return RangeOrBitVector(std::move(bv));
+ }
+ Range inner_res = inner_->OrderedIndexSearchValidated(op, sql_val, indices);
return RangeOrBitVector(
- Range(inner_res.start + in.start, inner_res.end + in.start));
+ Range(in.start + inner_res.start, in.start + inner_res.end));
}
const auto& arrangement = *arrangement_;
@@ -196,6 +205,11 @@
return inner_->MinElement(indices);
}
+SqlValue ArrangementOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ return inner_->Get_AvoidUsingBecauseSlow((*arrangement_)[index]);
+}
+
void ArrangementOverlay::ChainImpl::Serialize(StorageProto* storage) const {
auto* arrangement_overlay = storage->set_arrangement_overlay();
arrangement_overlay->set_values(
diff --git a/src/trace_processor/db/column/arrangement_overlay.h b/src/trace_processor/db/column/arrangement_overlay.h
index 3656b07..85b9ce4 100644
--- a/src/trace_processor/db/column/arrangement_overlay.h
+++ b/src/trace_processor/db/column/arrangement_overlay.h
@@ -19,10 +19,10 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
-#include "perfetto/base/logging.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/db/column/data_layer.h"
#include "src/trace_processor/db/column/types.h"
@@ -61,13 +61,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override {
- PERFETTO_FATAL(
- "OrderedIndexSearch can't be called on ArrangementOverlay");
- }
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -78,6 +71,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override {
diff --git a/src/trace_processor/db/column/arrangement_overlay_unittest.cc b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
index 55f310b..8820336 100644
--- a/src/trace_processor/db/column/arrangement_overlay_unittest.cc
+++ b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
@@ -108,15 +108,20 @@
}
TEST(ArrangementOverlay, OrderingSearch) {
- std::vector<uint32_t> arrangement{0, 2, 4, 1, 3};
- auto fake = FakeStorageChain::SearchSubset(5, BitVector({0, 1, 0, 1, 0}));
+ std::vector<uint32_t> numeric_data{0, 1, 2, 0, 1, 0};
+ NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
+ std::vector<uint32_t> arrangement{0, 3, 5, 1, 4, 2};
ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
- auto chain =
- storage.MakeChain(std::move(fake), DataLayer::ChainCreationArgs(true));
+ auto chain = storage.MakeChain(numeric.MakeChain(),
+ DataLayer::ChainCreationArgs(true));
RangeOrBitVector res =
- chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 5));
+ chain->Search(FilterOp::kGe, SqlValue::Long(1u), Range(0, 5));
ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4));
+
+ res = chain->Search(FilterOp::kNe, SqlValue::Long(1u), Range(1, 6));
+ ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1, 2, 5));
}
TEST(ArrangementOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/data_layer.cc b/src/trace_processor/db/column/data_layer.cc
index 634cbce..a570415 100644
--- a/src/trace_processor/db/column/data_layer.cc
+++ b/src/trace_processor/db/column/data_layer.cc
@@ -16,12 +16,15 @@
#include "src/trace_processor/db/column/data_layer.h"
+#include <algorithm>
#include <cstdint>
+#include <iterator>
#include <memory>
#include <utility>
#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/db/column/arrangement_overlay.h"
@@ -35,6 +38,7 @@
#include "src/trace_processor/db/column/set_id_storage.h"
#include "src/trace_processor/db/column/string_storage.h"
#include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/compare.h"
namespace perfetto::trace_processor::column {
@@ -113,6 +117,53 @@
PERFETTO_FATAL("For GCC");
}
+Range DataLayerChain::OrderedIndexSearchValidated(
+ FilterOp op,
+ SqlValue value,
+ const OrderedIndices& indices) const {
+ auto lb = [&]() {
+ return static_cast<uint32_t>(std::distance(
+ indices.data,
+ std::lower_bound(indices.data, indices.data + indices.size, value,
+ [this](uint32_t idx, const SqlValue& v) {
+ return compare::SqlValueComparator(
+ Get_AvoidUsingBecauseSlow(idx), v);
+ })));
+ };
+ auto ub = [&]() {
+ return static_cast<uint32_t>(std::distance(
+ indices.data,
+ std::upper_bound(indices.data, indices.data + indices.size, value,
+ [this](const SqlValue& v, uint32_t idx) {
+ return compare::SqlValueComparator(
+ v, Get_AvoidUsingBecauseSlow(idx));
+ })));
+ };
+ switch (op) {
+ case FilterOp::kEq:
+ return {lb(), ub()};
+ case FilterOp::kLe:
+ return {0, ub()};
+ case FilterOp::kLt:
+ return {0, lb()};
+ case FilterOp::kGe:
+ return {lb(), indices.size};
+ case FilterOp::kGt:
+ return {ub(), indices.size};
+ case FilterOp::kIsNull:
+ PERFETTO_CHECK(value.is_null());
+ return {0, ub()};
+ case FilterOp::kIsNotNull:
+ PERFETTO_CHECK(value.is_null());
+ return {ub(), indices.size};
+ case FilterOp::kNe:
+ case FilterOp::kGlob:
+ case FilterOp::kRegex:
+ PERFETTO_FATAL("Wrong filtering operation");
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
ArrangementOverlay::ArrangementOverlay(
const std::vector<uint32_t>* arrangement,
DataLayerChain::Indices::State arrangement_state)
diff --git a/src/trace_processor/db/column/data_layer.h b/src/trace_processor/db/column/data_layer.h
index 5e34539..b6f9382 100644
--- a/src/trace_processor/db/column/data_layer.h
+++ b/src/trace_processor/db/column/data_layer.h
@@ -321,9 +321,26 @@
// Post-validated implementation of |OrderedIndexSearch|. See
// |OrderedIndexSearch|'s documentation.
- virtual Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const = 0;
+ Range OrderedIndexSearchValidated(FilterOp op,
+ SqlValue value,
+ const OrderedIndices& indices) const;
+
+ // Returns the SqlValue representing the value at a given index.
+ //
+ // This function might be very tempting to use as it appears cheap on the
+ // surface but because of how DataLayerChains might be layered on top of each
+ // other, this might require *several* virtual function calls per index.
+ // If you're tempted to use this, please consider instead create a new
+ // "vectorized" function instead and only using this as a last resort.
+ //
+ // The correct "class" of algorithms to use this function are cases where you
+ // have a set of indices you want to lookup and based on the value returned
+ // you will only use a fraction of them. In this case, it might be worth
+ // paying the non-vectorized lookup to vastly reduce how many indices need
+ // to be translated.
+ //
+ // An example of such an algorithm is binary search on indices.
+ virtual SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const = 0;
};
} // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index 79ca3c0..426c6d2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -219,50 +219,6 @@
inner_->IndexSearchValidated(op, sql_val, indices);
}
-Range DenseNullOverlay::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- // For NOT EQUAL the further analysis needs to be done by the caller.
- PERFETTO_CHECK(op != FilterOp::kNe);
-
- PERFETTO_TP_TRACE(metatrace::Category::DB,
- "DenseNullOverlay::ChainImpl::OrderedIndexSearch");
-
- // We assume all NULLs are ordered to be in the front. We are looking for the
- // first index that points to non NULL value.
- const uint32_t* first_non_null =
- std::partition_point(indices.data, indices.data + indices.size,
- [this](uint32_t i) { return !non_null_->IsSet(i); });
-
- auto non_null_offset =
- static_cast<uint32_t>(std::distance(indices.data, first_non_null));
- auto non_null_size = static_cast<uint32_t>(
- std::distance(first_non_null, indices.data + indices.size));
-
- if (op == FilterOp::kIsNull) {
- return {0, non_null_offset};
- }
-
- if (op == FilterOp::kIsNotNull) {
- switch (inner_->ValidateSearchConstraints(op, sql_val)) {
- case SearchValidationResult::kNoData:
- return {};
- case SearchValidationResult::kAllData:
- return {non_null_offset, indices.size};
- case SearchValidationResult::kOk:
- break;
- }
- }
-
- Range inner_range = inner_->OrderedIndexSearchValidated(
- op, sql_val,
- OrderedIndices{first_non_null, non_null_size,
- Indices::State::kNonmonotonic});
- return {inner_range.start + non_null_offset,
- inner_range.end + non_null_offset};
-}
-
void DenseNullOverlay::ChainImpl::StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const {
@@ -314,6 +270,12 @@
: *first_null_it;
}
+SqlValue DenseNullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ return non_null_->IsSet(index) ? inner_->Get_AvoidUsingBecauseSlow(index)
+ : SqlValue();
+}
+
void DenseNullOverlay::ChainImpl::Serialize(StorageProto* storage) const {
auto* null_overlay = storage->set_dense_null_overlay();
non_null_->Serialize(null_overlay->set_bit_vector());
diff --git a/src/trace_processor/db/column/dense_null_overlay.h b/src/trace_processor/db/column/dense_null_overlay.h
index e253400..d0a95d2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.h
+++ b/src/trace_processor/db/column/dense_null_overlay.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include "perfetto/trace_processor/basic_types.h"
@@ -56,10 +57,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -70,6 +67,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override { return non_null_->size(); }
diff --git a/src/trace_processor/db/column/dense_null_overlay_unittest.cc b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
index 24b1eee..7cd74f6 100644
--- a/src/trace_processor/db/column/dense_null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
@@ -129,11 +129,12 @@
}
TEST(DenseNullOverlay, OrderedIndexSearch) {
- auto fake = FakeStorageChain::SearchSubset(6, BitVector({0, 1, 0, 1, 0, 1}));
+ std::vector<uint32_t> numeric_data{0, 1, 0, 1, 0, 1};
+ NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
BitVector bv{0, 1, 0, 1, 0, 1};
DenseNullOverlay storage(&bv);
- auto chain = storage.MakeChain(std::move(fake));
+ auto chain = storage.MakeChain(numeric.MakeChain());
std::vector<uint32_t> indices_vec({0, 2, 4, 1, 3, 5});
OrderedIndices indices{indices_vec.data(), 6, Indices::State::kNonmonotonic};
@@ -146,25 +147,25 @@
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 6u);
- res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices);
+ res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices);
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 6u);
- res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices);
+ res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices);
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 6u);
- res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices);
+ res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices);
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 6u);
- res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices);
- ASSERT_EQ(res.start, 3u);
- ASSERT_EQ(res.end, 6u);
+ res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices);
+ ASSERT_EQ(res.start, 0u);
+ ASSERT_EQ(res.end, 3u);
- res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices);
- ASSERT_EQ(res.start, 3u);
- ASSERT_EQ(res.end, 6u);
+ res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices);
+ ASSERT_EQ(res.start, 0u);
+ ASSERT_EQ(res.end, 3u);
}
TEST(DenseNullOverlay, SingleSearch) {
diff --git a/src/trace_processor/db/column/dummy_storage.cc b/src/trace_processor/db/column/dummy_storage.cc
index 631cbb7..ef9cacf 100644
--- a/src/trace_processor/db/column/dummy_storage.cc
+++ b/src/trace_processor/db/column/dummy_storage.cc
@@ -17,6 +17,7 @@
#include "src/trace_processor/db/column/dummy_storage.h"
#include <cstdint>
+#include <optional>
#include "perfetto/base/logging.h"
#include "perfetto/trace_processor/basic_types.h"
@@ -49,13 +50,6 @@
PERFETTO_FATAL("Shouldn't be called");
}
-Range DummyStorage::ChainImpl::OrderedIndexSearchValidated(
- FilterOp,
- SqlValue,
- const OrderedIndices&) const {
- PERFETTO_FATAL("Shouldn't be called");
-}
-
void DummyStorage::ChainImpl::StableSort(SortToken*,
SortToken*,
SortDirection) const {
@@ -78,6 +72,10 @@
PERFETTO_FATAL("Shouldn't be called");
}
+SqlValue DummyStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t) const {
+ PERFETTO_FATAL("Shouldn't be called");
+}
+
void DummyStorage::ChainImpl::Serialize(StorageProto*) const {
PERFETTO_FATAL("Shouldn't be called");
}
diff --git a/src/trace_processor/db/column/dummy_storage.h b/src/trace_processor/db/column/dummy_storage.h
index ffa0c73..66f2497 100644
--- a/src/trace_processor/db/column/dummy_storage.h
+++ b/src/trace_processor/db/column/dummy_storage.h
@@ -18,6 +18,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include "perfetto/trace_processor/basic_types.h"
@@ -45,10 +46,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -59,6 +56,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override;
diff --git a/src/trace_processor/db/column/fake_storage.cc b/src/trace_processor/db/column/fake_storage.cc
index 5a26961..5015bf3 100644
--- a/src/trace_processor/db/column/fake_storage.cc
+++ b/src/trace_processor/db/column/fake_storage.cc
@@ -19,6 +19,7 @@
#include <algorithm>
#include <cstdint>
#include <iterator>
+#include <optional>
#include <utility>
#include "perfetto/base/logging.h"
@@ -112,43 +113,6 @@
PERFETTO_FATAL("For GCC");
}
-Range FakeStorageChain::OrderedIndexSearchValidated(
- FilterOp,
- SqlValue,
- const OrderedIndices& indices) const {
- switch (strategy_) {
- case kAll:
- return {0, indices.size};
- case kNone:
- return {};
- case kRange: {
- // We are looking at intersection of |range_| and |indices_|.
- const uint32_t* first_in_range = std::partition_point(
- indices.data, indices.data + indices.size,
- [this](uint32_t i) { return !range_.Contains(i); });
- const uint32_t* first_outside_range = std::partition_point(
- first_in_range, indices.data + indices.size,
- [this](uint32_t i) { return range_.Contains(i); });
- return {
- static_cast<uint32_t>(std::distance(indices.data, first_in_range)),
- static_cast<uint32_t>(
- std::distance(indices.data, first_outside_range))};
- }
- case kBitVector:
- // We are looking at intersection of |range_| and |bit_vector_|.
- const uint32_t* first_set = std::partition_point(
- indices.data, indices.data + indices.size,
- [this](uint32_t i) { return !bit_vector_.IsSet(i); });
- const uint32_t* first_non_set = std::partition_point(
- first_set, indices.data + indices.size,
- [this](uint32_t i) { return bit_vector_.IsSet(i); });
- return {
- static_cast<uint32_t>(std::distance(indices.data, first_set)),
- static_cast<uint32_t>(std::distance(indices.data, first_non_set))};
- }
- PERFETTO_FATAL("For GCC");
-}
-
void FakeStorageChain::Distinct(Indices&) const {
// Fake storage shouldn't implement Distinct as it's not a binary (this index
// passes or not) operation on a column.
@@ -166,6 +130,10 @@
PERFETTO_FATAL("Not implemented");
}
+SqlValue FakeStorageChain::Get_AvoidUsingBecauseSlow(uint32_t) const {
+ PERFETTO_FATAL("Not implemented");
+}
+
void FakeStorageChain::Serialize(StorageProto*) const {
// FakeStorage doesn't really make sense to serialize.
PERFETTO_FATAL("Not implemented");
diff --git a/src/trace_processor/db/column/fake_storage.h b/src/trace_processor/db/column/fake_storage.h
index 7adf786..980a951 100644
--- a/src/trace_processor/db/column/fake_storage.h
+++ b/src/trace_processor/db/column/fake_storage.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -84,10 +85,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -95,8 +92,11 @@
void Distinct(Indices&) const override;
std::optional<Token> MaxElement(Indices&) const override;
+
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override { return size_; }
diff --git a/src/trace_processor/db/column/fake_storage_unittest.cc b/src/trace_processor/db/column/fake_storage_unittest.cc
index 908b7e0..28c497f 100644
--- a/src/trace_processor/db/column/fake_storage_unittest.cc
+++ b/src/trace_processor/db/column/fake_storage_unittest.cc
@@ -43,7 +43,6 @@
using testing::IsEmpty;
using Indices = DataLayerChain::Indices;
-using OrderedIndices = DataLayerChain::OrderedIndices;
TEST(FakeStorage, ValidateSearchConstraints) {
{
@@ -197,48 +196,6 @@
}
}
-TEST(FakeStorage, OrderedIndexSearchValidated) {
- std::vector<uint32_t> table_idx{4, 3, 2, 1};
- OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
- Indices::State::kNonmonotonic};
- {
- // All passes
- auto fake = FakeStorageChain::SearchAll(5);
- Range ret =
- fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
- EXPECT_EQ(ret, Range(0, 4));
- }
- {
- // None passes
- auto fake = FakeStorageChain::SearchNone(5);
- Range ret =
- fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
- EXPECT_EQ(ret, Range(0, 0));
- }
- {
- // BitVector
- auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 0, 1, 1, 1});
- Range ret =
- fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
- EXPECT_EQ(ret, Range(0, 3));
- }
- {
- // Index vector
- auto fake =
- FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3});
- Range ret =
- fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
- EXPECT_EQ(ret, Range(1, 4));
- }
- {
- // Range
- auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
- Range ret =
- fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
- EXPECT_EQ(ret, Range(1, 4));
- }
-}
-
} // namespace
} // namespace column
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/column/id_storage.cc b/src/trace_processor/db/column/id_storage.cc
index ea1e909..1ebe459 100644
--- a/src/trace_processor/db/column/id_storage.cc
+++ b/src/trace_processor/db/column/id_storage.cc
@@ -246,47 +246,6 @@
PERFETTO_FATAL("FilterOp not matched");
}
-Range IdStorage::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- PERFETTO_DCHECK(op != FilterOp::kNe);
-
- PERFETTO_TP_TRACE(
- metatrace::Category::DB, "IdStorage::ChainImpl::OrderedIndexSearch",
- [indices, op](metatrace::Record* r) {
- r->AddArg("Count", std::to_string(indices.size));
- r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
- });
-
- // It's a valid filter operation if |sql_val| is a double, although it
- // requires special logic.
- if (sql_val.type == SqlValue::kDouble) {
- switch (utils::CompareIntColumnWithDouble(op, &sql_val)) {
- case SearchValidationResult::kOk:
- break;
- case SearchValidationResult::kAllData:
- return {0, indices.size};
- case SearchValidationResult::kNoData:
- return {};
- }
- }
- auto val = static_cast<uint32_t>(sql_val.AsLong());
-
- // OrderedIndices are monotonic non contiguous values if OrderedIndexSearch
- // was called. Look for the first and last index and find the result of
- // looking for this range in IdStorage.
- Range indices_range(indices.data[0], indices.data[indices.size - 1] + 1);
- Range bin_search_ret = BinarySearchIntrinsic(op, val, indices_range);
-
- const auto* start_ptr = std::lower_bound(
- indices.data, indices.data + indices.size, bin_search_ret.start);
- const auto* end_ptr = std::lower_bound(start_ptr, indices.data + indices.size,
- bin_search_ret.end);
- return {static_cast<uint32_t>(std::distance(indices.data, start_ptr)),
- static_cast<uint32_t>(std::distance(indices.data, end_ptr))};
-}
-
Range IdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op,
Id val,
Range range) {
@@ -363,6 +322,10 @@
return *tok;
}
+SqlValue IdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t index) const {
+ return SqlValue::Long(index);
+}
+
void IdStorage::ChainImpl::Serialize(StorageProto* storage) const {
storage->set_id_storage();
}
diff --git a/src/trace_processor/db/column/id_storage.h b/src/trace_processor/db/column/id_storage.h
index 420568d..c3aeadf 100644
--- a/src/trace_processor/db/column/id_storage.h
+++ b/src/trace_processor/db/column/id_storage.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <limits>
#include <memory>
+#include <optional>
#include <string>
#include "perfetto/trace_processor/basic_types.h"
@@ -55,10 +56,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -69,6 +66,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override {
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index 128d3ff..372d3b0 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -256,56 +256,6 @@
inner_->IndexSearchValidated(op, sql_val, indices);
}
-Range NullOverlay::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- // For NOT EQUAL the translation or results from EQUAL needs to be done by the
- // caller.
- PERFETTO_CHECK(op != FilterOp::kNe);
-
- PERFETTO_TP_TRACE(metatrace::Category::DB,
- "NullOverlay::ChainImpl::OrderedIndexSearch");
-
- // We assume all NULLs are ordered to be in the front. We are looking for the
- // first index that points to non NULL value.
- const uint32_t* first_non_null =
- std::partition_point(indices.data, indices.data + indices.size,
- [this](uint32_t i) { return !non_null_->IsSet(i); });
- auto non_null_offset =
- static_cast<uint32_t>(std::distance(indices.data, first_non_null));
- auto non_null_size = static_cast<uint32_t>(
- std::distance(first_non_null, indices.data + indices.size));
-
- if (op == FilterOp::kIsNull) {
- return {0, non_null_offset};
- }
-
- if (op == FilterOp::kIsNotNull) {
- switch (inner_->ValidateSearchConstraints(op, sql_val)) {
- case SearchValidationResult::kNoData:
- return {};
- case SearchValidationResult::kAllData:
- return {non_null_offset, indices.size};
- case SearchValidationResult::kOk:
- break;
- }
- }
-
- std::vector<uint32_t> storage_iv;
- storage_iv.reserve(non_null_size);
- for (const uint32_t* it = first_non_null;
- it != first_non_null + non_null_size; it++) {
- storage_iv.push_back(non_null_->CountSetBits(*it));
- }
-
- Range inner_range = inner_->OrderedIndexSearchValidated(
- op, sql_val,
- OrderedIndices{storage_iv.data(), non_null_size, indices.state});
- return {inner_range.start + non_null_offset,
- inner_range.end + non_null_offset};
-}
-
void NullOverlay::ChainImpl::StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const {
@@ -369,6 +319,13 @@
return inner_->MinElement(indices);
}
+SqlValue NullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ return non_null_->IsSet(index)
+ ? inner_->Get_AvoidUsingBecauseSlow(non_null_->CountSetBits(index))
+ : SqlValue();
+}
+
void NullOverlay::ChainImpl::Serialize(StorageProto* storage) const {
auto* null_storage = storage->set_null_overlay();
non_null_->Serialize(null_storage->set_bit_vector());
diff --git a/src/trace_processor/db/column/null_overlay.h b/src/trace_processor/db/column/null_overlay.h
index 187c056..a74f211 100644
--- a/src/trace_processor/db/column/null_overlay.h
+++ b/src/trace_processor/db/column/null_overlay.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include "perfetto/trace_processor/basic_types.h"
@@ -55,10 +56,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -69,6 +66,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override { return non_null_->size(); }
diff --git a/src/trace_processor/db/column/null_overlay_unittest.cc b/src/trace_processor/db/column/null_overlay_unittest.cc
index 981c7bd..9cff095 100644
--- a/src/trace_processor/db/column/null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/null_overlay_unittest.cc
@@ -200,15 +200,15 @@
}
TEST(NullOverlay, OrderedIndexSearch) {
+ std::vector<uint32_t> numeric_data{1, 0, 1, 0};
+ NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
BitVector bv{0, 1, 1, 1, 0, 1};
- // Passing values in final storage (on normal operations)
- // 0, 1, 0, 1, 0, 0
- auto fake = FakeStorageChain::SearchSubset(4, BitVector{1, 0, 1, 0});
NullOverlay storage(&bv);
- auto chain = storage.MakeChain(std::move(fake));
+ auto chain = storage.MakeChain(numeric.MakeChain());
// Passing values on final data
- // NULL, NULL, 0, 1, 1
+ // NULL, NULL, 0, 0, 1, 1
std::vector<uint32_t> table_idx{0, 4, 5, 1, 3};
OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
Indices::State::kNonmonotonic};
@@ -218,28 +218,28 @@
ASSERT_EQ(res.end, 2u);
res = chain->OrderedIndexSearch(FilterOp::kIsNotNull, SqlValue(), indices);
+ ASSERT_EQ(res.start, 2u);
+ ASSERT_EQ(res.end, 5u);
+
+ res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices);
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 5u);
- res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices);
+ res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices);
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 5u);
- res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices);
+ res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices);
ASSERT_EQ(res.start, 3u);
ASSERT_EQ(res.end, 5u);
- res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices);
- ASSERT_EQ(res.start, 3u);
- ASSERT_EQ(res.end, 5u);
+ res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices);
+ ASSERT_EQ(res.start, 0u);
+ ASSERT_EQ(res.end, 3u);
- res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices);
- ASSERT_EQ(res.start, 3u);
- ASSERT_EQ(res.end, 5u);
-
- res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices);
- ASSERT_EQ(res.start, 3u);
- ASSERT_EQ(res.end, 5u);
+ res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices);
+ ASSERT_EQ(res.start, 0u);
+ ASSERT_EQ(res.end, 3u);
}
TEST(NullOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index b39b4cb..3612356 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -138,60 +138,6 @@
}
template <typename T>
-uint32_t TypedLowerBoundExtrinsic(T val,
- const T* data,
- OrderedIndices indices) {
- const auto* lower = std::lower_bound(
- indices.data, indices.data + indices.size, val,
- [data](uint32_t index, T value) { return data[index] < value; });
- return static_cast<uint32_t>(std::distance(indices.data, lower));
-}
-
-uint32_t LowerBoundExtrinsic(const void* vector_ptr,
- NumericValue val,
- OrderedIndices indices) {
- if (const auto* u32 = std::get_if<uint32_t>(&val)) {
- const auto* start =
- static_cast<const std::vector<uint32_t>*>(vector_ptr)->data();
- return TypedLowerBoundExtrinsic(*u32, start, indices);
- }
- if (const auto* i64 = std::get_if<int64_t>(&val)) {
- const auto* start =
- static_cast<const std::vector<int64_t>*>(vector_ptr)->data();
- return TypedLowerBoundExtrinsic(*i64, start, indices);
- }
- if (const auto* i32 = std::get_if<int32_t>(&val)) {
- const auto* start =
- static_cast<const std::vector<int32_t>*>(vector_ptr)->data();
- return TypedLowerBoundExtrinsic(*i32, start, indices);
- }
- if (const auto* db = std::get_if<double>(&val)) {
- const auto* start =
- static_cast<const std::vector<double>*>(vector_ptr)->data();
- return TypedLowerBoundExtrinsic(*db, start, indices);
- }
- PERFETTO_FATAL("Type not handled");
-}
-
-uint32_t UpperBoundExtrinsic(const void* vector_ptr,
- NumericValue val,
- OrderedIndices indices) {
- return std::visit(
- [vector_ptr, indices](auto val_data) {
- using T = decltype(val_data);
- const T* typed_start =
- static_cast<const std::vector<T>*>(vector_ptr)->data();
- const auto* upper =
- std::upper_bound(indices.data, indices.data + indices.size,
- val_data, [typed_start](T value, uint32_t index) {
- return value < typed_start[index];
- });
- return static_cast<uint32_t>(std::distance(indices.data, upper));
- },
- val);
-}
-
-template <typename T>
void TypedLinearSearch(T typed_val,
const T* start,
FilterOp op,
@@ -504,79 +450,6 @@
val);
}
-Range NumericStorageBase::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- PERFETTO_TP_TRACE(
- metatrace::Category::DB, "NumericStorage::ChainImpl::OrderedIndexSearch",
- [indices, op](metatrace::Record* r) {
- r->AddArg("Count", std::to_string(indices.size));
- r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
- });
-
- // Mismatched types - value is double and column is int.
- if (sql_val.type == SqlValue::kDouble &&
- storage_type_ != ColumnType::kDouble) {
- if (auto ret = utils::CanReturnEarly(IntColumnWithDouble(op, &sql_val),
- indices.size);
- ret) {
- return *ret;
- }
- }
-
- // Mismatched types - column is double and value is int.
- if (sql_val.type != SqlValue::kDouble &&
- storage_type_ == ColumnType::kDouble) {
- if (auto ret = utils::CanReturnEarly(DoubleColumnWithInt(op, &sql_val),
- indices.size);
- ret) {
- return *ret;
- }
- }
-
- NumericValue val;
- switch (storage_type_) {
- case ColumnType::kDouble:
- val = sql_val.AsDouble();
- break;
- case ColumnType::kInt64:
- val = sql_val.AsLong();
- break;
- case ColumnType::kInt32:
- val = static_cast<int32_t>(sql_val.AsLong());
- break;
- case ColumnType::kUint32:
- val = static_cast<uint32_t>(sql_val.AsLong());
- break;
- case ColumnType::kString:
- case ColumnType::kDummy:
- case ColumnType::kId:
- PERFETTO_FATAL("Invalid type");
- }
-
- switch (op) {
- case FilterOp::kEq:
- return {LowerBoundExtrinsic(vector_ptr_, val, indices),
- UpperBoundExtrinsic(vector_ptr_, val, indices)};
- case FilterOp::kLe:
- return {0, UpperBoundExtrinsic(vector_ptr_, val, indices)};
- case FilterOp::kLt:
- return {0, LowerBoundExtrinsic(vector_ptr_, val, indices)};
- case FilterOp::kGe:
- return {LowerBoundExtrinsic(vector_ptr_, val, indices), indices.size};
- case FilterOp::kGt:
- return {UpperBoundExtrinsic(vector_ptr_, val, indices), indices.size};
- case FilterOp::kNe:
- case FilterOp::kIsNull:
- case FilterOp::kIsNotNull:
- case FilterOp::kGlob:
- case FilterOp::kRegex:
- PERFETTO_FATAL("Wrong filtering operation");
- }
- PERFETTO_FATAL("For GCC");
-}
-
BitVector NumericStorageBase::ChainImpl::LinearSearchInternal(
FilterOp op,
NumericValue val,
diff --git a/src/trace_processor/db/column/numeric_storage.h b/src/trace_processor/db/column/numeric_storage.h
index fd27d26..3c4416b 100644
--- a/src/trace_processor/db/column/numeric_storage.h
+++ b/src/trace_processor/db/column/numeric_storage.h
@@ -23,6 +23,7 @@
#include <optional>
#include <set>
#include <string>
+#include <type_traits>
#include <unordered_set>
#include <variant>
#include <vector>
@@ -49,10 +50,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void Serialize(StorageProto*) const override;
std::string DebugString() const override { return "NumericStorage"; }
@@ -145,6 +142,13 @@
return *tok;
}
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override {
+ if constexpr (std::is_same_v<T, double>) {
+ return SqlValue::Double((*vector_)[index]);
+ }
+ return SqlValue::Long((*vector_)[index]);
+ }
+
void StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const override {
diff --git a/src/trace_processor/db/column/range_overlay.cc b/src/trace_processor/db/column/range_overlay.cc
index 81bb7ae..dee7838 100644
--- a/src/trace_processor/db/column/range_overlay.cc
+++ b/src/trace_processor/db/column/range_overlay.cc
@@ -18,6 +18,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <utility>
#include <vector>
@@ -118,22 +119,6 @@
inner_->IndexSearchValidated(op, sql_val, indices);
}
-Range RangeOverlay::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::IndexSearch");
-
- // Should be SIMD optimized.
- std::vector<uint32_t> storage_iv(indices.size);
- for (uint32_t i = 0; i < indices.size; ++i) {
- storage_iv[i] = indices.data[i] + range_->start;
- }
- return inner_->OrderedIndexSearchValidated(
- op, sql_val,
- OrderedIndices{storage_iv.data(), indices.size, indices.state});
-}
-
void RangeOverlay::ChainImpl::StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const {
@@ -160,6 +145,11 @@
return inner_->MaxElement(indices);
}
+SqlValue RangeOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ return inner_->Get_AvoidUsingBecauseSlow(index + range_->start);
+}
+
std::optional<Token> RangeOverlay::ChainImpl::MinElement(
Indices& indices) const {
PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::MinElement");
diff --git a/src/trace_processor/db/column/range_overlay.h b/src/trace_processor/db/column/range_overlay.h
index f15e8dc..a58df40 100644
--- a/src/trace_processor/db/column/range_overlay.h
+++ b/src/trace_processor/db/column/range_overlay.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include "perfetto/trace_processor/basic_types.h"
@@ -52,10 +53,6 @@
void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -66,6 +63,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override { return range_->size(); }
diff --git a/src/trace_processor/db/column/selector_overlay.cc b/src/trace_processor/db/column/selector_overlay.cc
index b2cbc43..9e957cb 100644
--- a/src/trace_processor/db/column/selector_overlay.cc
+++ b/src/trace_processor/db/column/selector_overlay.cc
@@ -16,9 +16,9 @@
#include "src/trace_processor/db/column/selector_overlay.h"
-#include <algorithm>
#include <cstdint>
#include <memory>
+#include <optional>
#include <utility>
#include <vector>
@@ -35,7 +35,7 @@
namespace perfetto::trace_processor::column {
namespace {
-static constexpr uint32_t kIndexOfNthSetRatio = 32;
+constexpr uint32_t kIndexOfNthSetRatio = 32;
} // namespace
@@ -97,23 +97,6 @@
return inner_->IndexSearchValidated(op, sql_val, indices);
}
-Range SelectorOverlay::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- // To go from TableIndexVector to StorageIndexVector we need to find index in
- // |selector_| by looking only into set bits.
- std::vector<uint32_t> inner_indices(indices.size);
- for (uint32_t i = 0; i < indices.size; ++i) {
- inner_indices[i] = selector_->IndexOfNthSet(indices.data[i]);
- }
- return inner_->OrderedIndexSearchValidated(
- op, sql_val,
- OrderedIndices{inner_indices.data(),
- static_cast<uint32_t>(inner_indices.size()),
- indices.state});
-}
-
void SelectorOverlay::ChainImpl::StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const {
@@ -148,6 +131,11 @@
return inner_->MinElement(indices);
}
+SqlValue SelectorOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ return inner_->Get_AvoidUsingBecauseSlow(selector_->IndexOfNthSet(index));
+}
+
void SelectorOverlay::ChainImpl::Serialize(StorageProto* storage) const {
auto* selector_overlay = storage->set_selector_overlay();
inner_->Serialize(selector_overlay->set_storage());
@@ -164,15 +152,15 @@
for (auto& token : indices.tokens) {
token.index = selector_->IndexOfNthSet(token.index);
}
- } else {
- // TODO(mayzner): once we have a reverse index for IndexOfNthSet in
- // BitVector, this should no longer be necessary.
- std::vector<uint32_t> lookup = selector_->GetSetBitIndices();
- for (auto& token : indices.tokens) {
- token.index = lookup[token.index];
- }
+ return;
}
- return;
+
+ // TODO(mayzner): once we have a reverse index for IndexOfNthSet in
+ // BitVector, this should no longer be necessary.
+ std::vector<uint32_t> lookup = selector_->GetSetBitIndices();
+ for (auto& token : indices.tokens) {
+ token.index = lookup[token.index];
+ }
}
} // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/selector_overlay.h b/src/trace_processor/db/column/selector_overlay.h
index d54bdf5..3726f9f 100644
--- a/src/trace_processor/db/column/selector_overlay.h
+++ b/src/trace_processor/db/column/selector_overlay.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include "perfetto/trace_processor/basic_types.h"
@@ -56,10 +57,6 @@
void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection) const override;
@@ -70,6 +67,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override { return selector_->size(); }
diff --git a/src/trace_processor/db/column/selector_overlay_unittest.cc b/src/trace_processor/db/column/selector_overlay_unittest.cc
index 0abae4c..def2a77 100644
--- a/src/trace_processor/db/column/selector_overlay_unittest.cc
+++ b/src/trace_processor/db/column/selector_overlay_unittest.cc
@@ -17,6 +17,7 @@
#include "src/trace_processor/db/column/selector_overlay.h"
#include <cstdint>
+#include <utility>
#include <vector>
#include "data_layer.h"
@@ -103,35 +104,23 @@
ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(1u));
}
-TEST(SelectorOverlay, OrderedIndexSearchTrivial) {
+TEST(SelectorOverlay, OrderedIndexSearch) {
+ std::vector<uint32_t> numeric_data{1, 0, 0, 1, 1};
+ NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
BitVector selector{1, 0, 1, 0, 1};
- auto fake = FakeStorageChain::SearchAll(5);
SelectorOverlay storage(&selector);
- auto chain = storage.MakeChain(std::move(fake));
+ auto chain = storage.MakeChain(numeric.MakeChain());
std::vector<uint32_t> table_idx{1u, 0u, 2u};
Range res = chain->OrderedIndexSearch(
- FilterOp::kGe, SqlValue::Long(0u),
+ FilterOp::kGe, SqlValue::Long(1u),
OrderedIndices{table_idx.data(), static_cast<uint32_t>(table_idx.size()),
Indices::State::kNonmonotonic});
- ASSERT_EQ(res.start, 0u);
+ ASSERT_EQ(res.start, 1u);
ASSERT_EQ(res.end, 3u);
}
-TEST(SelectorOverlay, OrderedIndexSearchNone) {
- BitVector selector{1, 0, 1, 0, 1};
- auto fake = FakeStorageChain::SearchNone(5);
- SelectorOverlay storage(&selector);
- auto chain = storage.MakeChain(std::move(fake));
-
- std::vector<uint32_t> table_idx{1u, 0u, 2u};
- Range res = chain->OrderedIndexSearch(
- FilterOp::kGe, SqlValue::Long(0u),
- OrderedIndices{table_idx.data(), static_cast<uint32_t>(table_idx.size()),
- Indices::State::kNonmonotonic});
- ASSERT_EQ(res.size(), 0u);
-}
-
TEST(SelectorOverlay, StableSort) {
std::vector<uint32_t> numeric_data{3, 1, 0, 0, 2, 4, 3, 4};
NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index 207b649..42ab4ea 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -19,10 +19,7 @@
#include <algorithm>
#include <cstdint>
#include <functional>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <set>
+#include <optional>
#include <string>
#include <unordered_set>
#include <utility>
@@ -250,38 +247,17 @@
}
}
-Range SetIdStorage::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- PERFETTO_TP_TRACE(metatrace::Category::DB,
- "SetIdStorage::ChainImpl::OrderedIndexSearch");
- // OrderedIndices are monotonic non-contiguous values.
- auto res = SearchValidated(
- op, sql_val, Range(indices.data[0], indices.data[indices.size - 1] + 1));
- PERFETTO_CHECK(res.IsRange());
- Range res_range = std::move(res).TakeIfRange();
-
- const auto* start_ptr = std::lower_bound(
- indices.data, indices.data + indices.size, res_range.start);
- const auto* end_ptr =
- std::lower_bound(start_ptr, indices.data + indices.size, res_range.end);
-
- return {static_cast<uint32_t>(std::distance(indices.data, start_ptr)),
- static_cast<uint32_t>(std::distance(indices.data, end_ptr))};
-}
-
Range SetIdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op,
SetId val,
Range range) const {
switch (op) {
case FilterOp::kEq: {
- if (values_->data()[val] != val) {
- return Range();
+ if ((*values_)[val] != val) {
+ return {};
}
uint32_t start = std::max(val, range.start);
uint32_t end = UpperBoundIntrinsic(values_->data(), val, range);
- return Range(std::min(start, end), end);
+ return {std::min(start, end), end};
}
case FilterOp::kLe: {
return {range.start, UpperBoundIntrinsic(values_->data(), val, range)};
@@ -370,6 +346,11 @@
return *tok;
}
+SqlValue SetIdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ return SqlValue::Long((*values_)[index]);
+}
+
void SetIdStorage::ChainImpl::Serialize(StorageProto* msg) const {
auto* vec_msg = msg->set_set_id_storage();
vec_msg->set_values(reinterpret_cast<const uint8_t*>(values_->data()),
diff --git a/src/trace_processor/db/column/set_id_storage.h b/src/trace_processor/db/column/set_id_storage.h
index 2fa6c81..e459106 100644
--- a/src/trace_processor/db/column/set_id_storage.h
+++ b/src/trace_processor/db/column/set_id_storage.h
@@ -18,6 +18,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -54,10 +55,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const override;
@@ -70,6 +67,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
uint32_t size() const override {
return static_cast<uint32_t>(values_->size());
}
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index adf3eda..1d25dbb 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -163,36 +163,6 @@
return static_cast<uint32_t>(std::distance(data, upper));
}
-uint32_t LowerBoundExtrinsic(StringPool* pool,
- const StringPool::Id* data,
- NullTermStringView val,
- const uint32_t* indices,
- uint32_t indices_count,
- uint32_t offset) {
- Less comp{pool};
- const auto* lower =
- std::lower_bound(indices + offset, indices + indices_count, val,
- [comp, data](uint32_t index, NullTermStringView val) {
- return comp(data[index], val);
- });
- return static_cast<uint32_t>(std::distance(indices, lower));
-}
-
-uint32_t UpperBoundExtrinsic(StringPool* pool,
- const StringPool::Id* data,
- NullTermStringView val,
- const uint32_t* indices,
- uint32_t indices_count,
- uint32_t offset) {
- Greater comp{pool};
- const auto* upper =
- std::upper_bound(indices + offset, indices + indices_count, val,
- [comp, data](NullTermStringView val, uint32_t index) {
- return comp(data[index], val);
- });
- return static_cast<uint32_t>(std::distance(indices, upper));
-}
-
} // namespace
StringStorage::ChainImpl::ChainImpl(StringPool* string_pool,
@@ -518,64 +488,6 @@
return std::move(builder).Build();
}
-Range StringStorage::ChainImpl::OrderedIndexSearchValidated(
- FilterOp op,
- SqlValue sql_val,
- const OrderedIndices& indices) const {
- PERFETTO_TP_TRACE(metatrace::Category::DB,
- "StringStorage::ChainImpl::OrderedIndexSearch");
- StringPool::Id val =
- (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull)
- ? StringPool::Id::Null()
- : string_pool_->InternString(base::StringView(sql_val.AsString()));
- NullTermStringView val_str = string_pool_->Get(val);
-
- auto first_non_null = static_cast<uint32_t>(std::distance(
- indices.data,
- std::partition_point(indices.data, indices.data + indices.size,
- [this](uint32_t i) {
- return (*data_)[i] == StringPool::Id::Null();
- })));
-
- switch (op) {
- case FilterOp::kEq:
- return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
- indices.data, indices.size, first_non_null),
- UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
- indices.data, indices.size, first_non_null)};
- case FilterOp::kLe:
- return {first_non_null,
- UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
- indices.data, indices.size, first_non_null)};
- case FilterOp::kLt:
- return {first_non_null,
- LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
- indices.data, indices.size, first_non_null)};
- case FilterOp::kGe:
- return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
- indices.data, indices.size, first_non_null),
- indices.size};
- case FilterOp::kGt:
- return {UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
- indices.data, indices.size, first_non_null),
- indices.size};
- case FilterOp::kIsNull: {
- // Assuming nulls are at the front.
- return Range(0, first_non_null);
- }
- case FilterOp::kIsNotNull: {
- // Assuming nulls are at the front.
- return Range(first_non_null, indices.size);
- }
-
- case FilterOp::kNe:
- case FilterOp::kGlob:
- case FilterOp::kRegex:
- PERFETTO_FATAL("Not supported for OrderedIndexSearch");
- }
- PERFETTO_FATAL("For GCC");
-}
-
Range StringStorage::ChainImpl::BinarySearchIntrinsic(
FilterOp op,
SqlValue sql_val,
@@ -715,6 +627,14 @@
return *tok;
}
+SqlValue StringStorage::ChainImpl::Get_AvoidUsingBecauseSlow(
+ uint32_t index) const {
+ StringPool::Id id = (*data_)[index];
+ return id == StringPool::Id::Null()
+ ? SqlValue()
+ : SqlValue::String(string_pool_->Get(id).c_str());
+}
+
void StringStorage::ChainImpl::Serialize(StorageProto* msg) const {
auto* string_storage_msg = msg->set_string_storage();
string_storage_msg->set_is_sorted(is_sorted_);
diff --git a/src/trace_processor/db/column/string_storage.h b/src/trace_processor/db/column/string_storage.h
index 470ccd1..1c05dd7 100644
--- a/src/trace_processor/db/column/string_storage.h
+++ b/src/trace_processor/db/column/string_storage.h
@@ -18,6 +18,7 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -57,10 +58,6 @@
void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
- Range OrderedIndexSearchValidated(FilterOp,
- SqlValue,
- const OrderedIndices&) const override;
-
void StableSort(SortToken* start,
SortToken* end,
SortDirection direction) const override;
@@ -71,6 +68,8 @@
std::optional<Token> MinElement(Indices&) const override;
+ SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
void Serialize(StorageProto*) const override;
uint32_t size() const override {
diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc
index 4474ea9..7e7cadb 100644
--- a/src/trace_processor/db/column/string_storage_unittest.cc
+++ b/src/trace_processor/db/column/string_storage_unittest.cc
@@ -383,12 +383,12 @@
op = FilterOp::kLt;
res = chain->OrderedIndexSearch(op, val, indices);
- ASSERT_EQ(res.start, 1u);
+ ASSERT_EQ(res.start, 0u);
ASSERT_EQ(res.end, 4u);
op = FilterOp::kLe;
res = chain->OrderedIndexSearch(op, val, indices);
- ASSERT_EQ(res.start, 1u);
+ ASSERT_EQ(res.start, 0u);
ASSERT_EQ(res.end, 5u);
op = FilterOp::kGt;
@@ -402,6 +402,7 @@
ASSERT_EQ(res.end, 6u);
op = FilterOp::kIsNull;
+ val = SqlValue();
res = chain->OrderedIndexSearch(op, val, indices);
ASSERT_EQ(res.start, 0u);
ASSERT_EQ(res.end, 1u);
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index d6cc57f..dcd75f0 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -513,6 +513,29 @@
}
BENCHMARK(BM_QEFilterOrderedArrangement);
+void BM_QEFilterNullOrderedArrangement(benchmark::State& state) {
+ SliceTableForBenchmark table(state);
+ Order order{table.table_.parent_id().index_in_table(), false};
+ Table slice_sorted_with_parent_id = table.table_.Sort({order});
+
+ Constraint c{table.table_.parent_id().index_in_table(), FilterOp::kGt,
+ SqlValue::Long(26091)};
+ Query q;
+ q.constraints = {c};
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(slice_sorted_with_parent_id.QueryToRowMap(q));
+ }
+ state.counters["s/row"] = benchmark::Counter(
+ static_cast<double>(slice_sorted_with_parent_id.row_count()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
+ state.counters["s/out"] = benchmark::Counter(
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEFilterNullOrderedArrangement);
+
void BM_QESliceFilterIndexSearchOneElement(benchmark::State& state) {
SliceTableForBenchmark table(state);
BenchmarkSliceTableFilter(
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 369917d..a3b6a7f 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -29,6 +29,7 @@
#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
namespace perfetto {
namespace trace_processor {
@@ -53,7 +54,7 @@
case kSystraceTraceType:
case kGzipTraceType:
case kCtraceTraceType:
- case kAndroidBugreportTraceType:
+ case kZipFile:
return std::nullopt;
case kPerfDataTraceType:
@@ -102,26 +103,11 @@
reader_ = std::move(*reader_or);
PERFETTO_DLOG("%s detected", ToString(trace_type));
- std::optional<TraceSorter::SortingMode> minimum_sorting_mode =
- GetMinimumSortingMode(trace_type, *context_);
+ UpdateSorterForTraceType(trace_type);
- if (minimum_sorting_mode.has_value()) {
- if (!context_->sorter) {
- context_->sorter.reset(new TraceSorter(context_, *minimum_sorting_mode));
- }
-
- switch (context_->sorter->sorting_mode()) {
- case TraceSorter::SortingMode::kDefault:
- PERFETTO_CHECK(minimum_sorting_mode ==
- TraceSorter::SortingMode::kDefault);
- break;
- case TraceSorter::SortingMode::kFullSort:
- break;
- }
- }
-
- // TODO(carlscab) Make sure kProtoTraceType and kSystraceTraceType are parsed
- // first so that we do not get issues with SetPidZeroIsUpidZeroIdleProcess()
+ // TODO(b/334978369) Make sure kProtoTraceType and kSystraceTraceType are
+ // parsed first so that we do not get issues with
+ // SetPidZeroIsUpidZeroIdleProcess()
if (trace_type == kProtoTraceType || trace_type == kSystraceTraceType) {
context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
}
@@ -129,6 +115,27 @@
return base::OkStatus();
}
+void ForwardingTraceParser::UpdateSorterForTraceType(TraceType trace_type) {
+ std::optional<TraceSorter::SortingMode> minimum_sorting_mode =
+ GetMinimumSortingMode(trace_type, *context_);
+ if (!minimum_sorting_mode.has_value()) {
+ return;
+ }
+
+ if (!context_->sorter) {
+ context_->sorter.reset(new TraceSorter(context_, *minimum_sorting_mode));
+ }
+
+ switch (context_->sorter->sorting_mode()) {
+ case TraceSorter::SortingMode::kDefault:
+ PERFETTO_CHECK(minimum_sorting_mode ==
+ TraceSorter::SortingMode::kDefault);
+ break;
+ case TraceSorter::SortingMode::kFullSort:
+ break;
+ }
+}
+
base::Status ForwardingTraceParser::Parse(TraceBlobView blob) {
// If this is the first Parse() call, guess the trace type and create the
// appropriate parser.
diff --git a/src/trace_processor/forwarding_trace_parser.h b/src/trace_processor/forwarding_trace_parser.h
index 1f32938..12fe447 100644
--- a/src/trace_processor/forwarding_trace_parser.h
+++ b/src/trace_processor/forwarding_trace_parser.h
@@ -20,6 +20,7 @@
#include "perfetto/base/status.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/util/trace_type.h"
namespace perfetto {
namespace trace_processor {
@@ -37,6 +38,7 @@
private:
base::Status Init(const TraceBlobView&);
+ void UpdateSorterForTraceType(TraceType trace_type);
TraceProcessorContext* const context_;
std::unique_ptr<ChunkedTraceReader> reader_;
};
diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc
index 87aa0f5..1d46efc 100644
--- a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc
+++ b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc
@@ -17,54 +17,90 @@
#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
#include <algorithm>
+#include <cstddef>
#include <optional>
+#include <string>
+#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/trace_processor/trace_blob.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
+#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "src/trace_processor/importers/android_bugreport/android_log_parser.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/zip_reader.h"
-#include "protos/perfetto/common/builtin_clock.pbzero.h"
-
namespace perfetto {
namespace trace_processor {
+namespace {
+const util::ZipFile* FindBugReportFile(
+ const std::vector<util::ZipFile>& zip_file_entries) {
+ for (const auto& zf : zip_file_entries) {
+ if (base::StartsWith(zf.name(), "bugreport-") &&
+ base::EndsWith(zf.name(), ".txt")) {
+ return &zf;
+ }
+ }
+ return nullptr;
+}
-AndroidBugreportParser::AndroidBugreportParser(TraceProcessorContext* ctx)
- : context_(ctx), zip_reader_(new util::ZipReader()) {}
+std::optional<int32_t> ExtractYearFromBugReportFilename(
+ const std::string& filename) {
+ // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt".
+ auto year_str =
+ filename.substr(filename.size() - strlen("2022-12-31-23-59-00.txt"), 4);
+ return base::StringToInt32(year_str);
+}
+
+} // namespace
+
+// static
+bool AndroidBugreportParser::IsAndroidBugReport(
+ const std::vector<util::ZipFile>& zip_file_entries) {
+ if (const util::ZipFile* file = FindBugReportFile(zip_file_entries);
+ file != nullptr) {
+ return ExtractYearFromBugReportFilename(file->name()).has_value();
+ }
+
+ return false;
+}
+
+// static
+util::Status AndroidBugreportParser::Parse(
+ TraceProcessorContext* context,
+ std::vector<util::ZipFile> zip_file_entries) {
+ return AndroidBugreportParser(context, std::move(zip_file_entries))
+ .ParseImpl();
+}
+
+AndroidBugreportParser::AndroidBugreportParser(
+ TraceProcessorContext* context,
+ std::vector<util::ZipFile> zip_file_entries)
+ : context_(context), zip_file_entries_(std::move(zip_file_entries)) {}
AndroidBugreportParser::~AndroidBugreportParser() = default;
-util::Status AndroidBugreportParser::Parse(TraceBlobView tbv) {
- if (!first_chunk_seen_) {
- first_chunk_seen_ = true;
- // All logs in Android bugreports use wall time (which creates problems
- // in case of early boot events before NTP kicks in, which get emitted as
- // 1970), but that is the state of affairs.
- context_->clock_tracker->SetTraceTimeClock(
- protos::pbzero::BUILTIN_CLOCK_REALTIME);
- }
-
- return zip_reader_->Parse(tbv.data(), tbv.size());
-}
-
-void AndroidBugreportParser::NotifyEndOfFile() {
+util::Status AndroidBugreportParser::ParseImpl() {
+ // All logs in Android bugreports use wall time (which creates problems
+ // in case of early boot events before NTP kicks in, which get emitted as
+ // 1970), but that is the state of affairs.
+ context_->clock_tracker->SetTraceTimeClock(
+ protos::pbzero::BUILTIN_CLOCK_REALTIME);
if (!DetectYearAndBrFilename()) {
context_->storage->IncrementStats(stats::android_br_parse_errors);
- return;
+ return base::ErrStatus("Zip file does not contain bugreport file.");
}
ParsePersistentLogcat();
ParseDumpstateTxt();
SortAndStoreLogcat();
+ return base::OkStatus();
}
void AndroidBugreportParser::ParseDumpstateTxt() {
+ PERFETTO_CHECK(dumpstate_file_);
// Dumpstate is organized in a two level hierarchy, beautifully flattened into
// one text file with load bearing ----- markers:
// 1. Various dumpstate sections, examples:
@@ -95,80 +131,81 @@
// Here we put each line in a dedicated table, android_dumpstate, keeping
// track of the dumpstate `section` and dumpsys `service`.
AndroidLogParser log_parser(br_year_, context_->storage.get());
- util::ZipFile* zf = zip_reader_->Find(dumpstate_fname_);
StringId section_id = StringId::Null(); // The current dumpstate section.
StringId service_id = StringId::Null(); // The current dumpsys service.
static constexpr size_t npos = base::StringView::npos;
enum { OTHER = 0, DUMPSYS, LOG } cur_sect = OTHER;
- zf->DecompressLines([&](const std::vector<base::StringView>& lines) {
- // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new vector
- // on every line.
- std::vector<base::StringView> log_line(1);
- for (const base::StringView& line : lines) {
- if (line.StartsWith("------ ") && line.EndsWith(" ------")) {
- // These lines mark the beginning and end of dumpstate sections:
- // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------
- // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------
- base::StringView section = line.substr(7);
- section = section.substr(0, section.size() - 7);
- bool end_marker = section.find("was the duration of") != npos;
- service_id = StringId::Null();
- if (end_marker) {
- section_id = StringId::Null();
- } else {
- section_id = context_->storage->InternString(section);
- cur_sect = OTHER;
- if (section.StartsWith("DUMPSYS")) {
- cur_sect = DUMPSYS;
- } else if (section.StartsWith("SYSTEM LOG") ||
- section.StartsWith("EVENT LOG") ||
- section.StartsWith("RADIO LOG")) {
- // KERNEL LOG is deliberately omitted because SYSTEM LOG is a
- // superset. KERNEL LOG contains all dupes.
- cur_sect = LOG;
- } else if (section.StartsWith("BLOCK STAT")) {
- // Coalesce all the block stats into one section. Otherwise they
- // pollute the table with one section per block device.
- section_id = context_->storage->InternString("BLOCK STAT");
+ dumpstate_file_->DecompressLines(
+ [&](const std::vector<base::StringView>& lines) {
+ // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new
+ // vector on every line.
+ std::vector<base::StringView> log_line(1);
+ for (const base::StringView& line : lines) {
+ if (line.StartsWith("------ ") && line.EndsWith(" ------")) {
+ // These lines mark the beginning and end of dumpstate sections:
+ // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------
+ // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------
+ base::StringView section = line.substr(7);
+ section = section.substr(0, section.size() - 7);
+ bool end_marker = section.find("was the duration of") != npos;
+ service_id = StringId::Null();
+ if (end_marker) {
+ section_id = StringId::Null();
+ } else {
+ section_id = context_->storage->InternString(section);
+ cur_sect = OTHER;
+ if (section.StartsWith("DUMPSYS")) {
+ cur_sect = DUMPSYS;
+ } else if (section.StartsWith("SYSTEM LOG") ||
+ section.StartsWith("EVENT LOG") ||
+ section.StartsWith("RADIO LOG")) {
+ // KERNEL LOG is deliberately omitted because SYSTEM LOG is a
+ // superset. KERNEL LOG contains all dupes.
+ cur_sect = LOG;
+ } else if (section.StartsWith("BLOCK STAT")) {
+ // Coalesce all the block stats into one section. Otherwise they
+ // pollute the table with one section per block device.
+ section_id = context_->storage->InternString("BLOCK STAT");
+ }
+ }
+ continue;
}
+ // Skip end marker lines for dumpsys sections.
+ if (cur_sect == DUMPSYS && line.StartsWith("--------- ") &&
+ line.find("was the duration of dumpsys") != npos) {
+ service_id = StringId::Null();
+ continue;
+ }
+ if (cur_sect == DUMPSYS && service_id.is_null() &&
+ line.StartsWith(
+ "----------------------------------------------")) {
+ continue;
+ }
+ if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) {
+ // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName:
+ base::StringView svc = line.substr(line.rfind(' ') + 1);
+ svc = svc.substr(0, svc.size() - 1);
+ service_id = context_->storage->InternString(svc);
+ } else if (cur_sect == LOG) {
+ // Parse the non-persistent logcat and append to `log_events_`,
+ // together with the persistent one previously parsed by
+ // ParsePersistentLogcat(). Skips entries that are already seen in
+ // the persistent logcat, handling us vs ms truncation.
+ PERFETTO_DCHECK(log_line.size() == 1);
+ log_line[0] = line;
+ log_parser.ParseLogLines(log_line, &log_events_,
+ log_events_last_sorted_idx_);
+ }
+
+ if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) {
+ build_fpr_ = line.substr(20, line.size() - 20).ToStdString();
+ }
+
+ // Append the line to the android_dumpstate table.
+ context_->storage->mutable_android_dumpstate_table()->Insert(
+ {section_id, service_id, context_->storage->InternString(line)});
}
- continue;
- }
- // Skip end marker lines for dumpsys sections.
- if (cur_sect == DUMPSYS && line.StartsWith("--------- ") &&
- line.find("was the duration of dumpsys") != npos) {
- service_id = StringId::Null();
- continue;
- }
- if (cur_sect == DUMPSYS && service_id.is_null() &&
- line.StartsWith("----------------------------------------------")) {
- continue;
- }
- if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) {
- // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName:
- base::StringView svc = line.substr(line.rfind(' ') + 1);
- svc = svc.substr(0, svc.size() - 1);
- service_id = context_->storage->InternString(svc);
- } else if (cur_sect == LOG) {
- // Parse the non-persistent logcat and append to `log_events_`, together
- // with the persistent one previously parsed by ParsePersistentLogcat().
- // Skips entries that are already seen in the persistent logcat,
- // handling us vs ms truncation.
- PERFETTO_DCHECK(log_line.size() == 1);
- log_line[0] = line;
- log_parser.ParseLogLines(log_line, &log_events_,
- log_events_last_sorted_idx_);
- }
-
- if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) {
- build_fpr_ = line.substr(20, line.size() - 20).ToStdString();
- }
-
- // Append the line to the android_dumpstate table.
- context_->storage->mutable_android_dumpstate_table()->Insert(
- {section_id, service_id, context_->storage->InternString(line)});
- }
- });
+ });
}
void AndroidBugreportParser::ParsePersistentLogcat() {
@@ -182,22 +219,23 @@
// Sort files to ease the job of the subsequent line-based sort. Unfortunately
// lines within each file are not 100% timestamp-ordered, due to things like
// kernel messages where log time != event time.
- std::vector<std::pair<uint64_t, std::string>> log_paths;
- for (const util::ZipFile& zf : zip_reader_->files()) {
+ std::vector<std::pair<uint64_t, const util::ZipFile*>> log_files;
+ for (const util::ZipFile& zf : zip_file_entries_) {
if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") &&
!base::EndsWith(zf.name(), "logcat.id")) {
- log_paths.emplace_back(std::make_pair(zf.GetDatetime(), zf.name()));
+ log_files.push_back(std::make_pair(zf.GetDatetime(), &zf));
}
}
- std::sort(log_paths.begin(), log_paths.end());
+
+ std::sort(log_files.begin(), log_files.end());
// Push all events into the AndroidLogParser. It will take care of string
// interning into the pool. Appends entries into `log_events`.
- for (const auto& kv : log_paths) {
- util::ZipFile* zf = zip_reader_->Find(kv.second);
- zf->DecompressLines([&](const std::vector<base::StringView>& lines) {
- log_parser.ParseLogLines(lines, &log_events_);
- });
+ for (const auto& log_file : log_files) {
+ log_file.second->DecompressLines(
+ [&](const std::vector<base::StringView>& lines) {
+ log_parser.ParseLogLines(lines, &log_events_);
+ });
}
// Do an initial sorting pass. This is not the final sorting because we
@@ -230,30 +268,20 @@
// This is obviously bugged for cases of bugreports collected across new year
// but we'll live with that.
bool AndroidBugreportParser::DetectYearAndBrFilename() {
- const util::ZipFile* br_file = nullptr;
- for (const auto& zf : zip_reader_->files()) {
- if (base::StartsWith(zf.name(), "bugreport-") &&
- base::EndsWith(zf.name(), ".txt")) {
- br_file = &zf;
- break;
- }
- }
-
+ const util::ZipFile* br_file = FindBugReportFile(zip_file_entries_);
if (!br_file) {
PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file");
return false;
}
- // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt".
- auto year_str = br_file->name().substr(
- br_file->name().size() - strlen("2022-12-31-23-59-00.txt"), 4);
- std::optional<int32_t> year = base::StringToInt32(year_str);
+ std::optional<int32_t> year =
+ ExtractYearFromBugReportFilename(br_file->name());
if (!year.has_value()) {
PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str());
return false;
}
br_year_ = *year;
- dumpstate_fname_ = br_file->name();
+ dumpstate_file_ = br_file;
return true;
}
diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h
index 9969159..a9ca322 100644
--- a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h
+++ b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h
@@ -17,8 +17,11 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_
-#include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/storage/trace_storage.h"
+#include <cstddef>
+#include <vector>
+
+#include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/util/zip_reader.h"
namespace perfetto {
namespace trace_processor {
@@ -31,16 +34,19 @@
class TraceProcessorContext;
// Trace importer for Android bugreport.zip archives.
-class AndroidBugreportParser : public ChunkedTraceReader {
+class AndroidBugreportParser {
public:
- explicit AndroidBugreportParser(TraceProcessorContext*);
- ~AndroidBugreportParser() override;
-
- // ChunkedTraceReader implementation.
- util::Status Parse(TraceBlobView) override;
- void NotifyEndOfFile() override;
+ static bool IsAndroidBugReport(
+ const std::vector<util::ZipFile>& zip_file_entries);
+ static util::Status Parse(TraceProcessorContext* context,
+ std::vector<util::ZipFile> zip_file_entries);
private:
+ AndroidBugreportParser(TraceProcessorContext* context,
+ std::vector<util::ZipFile> zip_file_entries);
+ ~AndroidBugreportParser();
+ util::Status ParseImpl();
+
bool DetectYearAndBrFilename();
void ParsePersistentLogcat();
void ParseDumpstateTxt();
@@ -48,11 +54,11 @@
void SortLogEvents();
TraceProcessorContext* const context_;
+ std::vector<util::ZipFile> zip_file_entries_;
int br_year_ = 0; // The year when the bugreport has been taken.
- std::string dumpstate_fname_; // The name of bugreport-xxx-2022-08-04....txt
+ const util::ZipFile* dumpstate_file_ =
+ nullptr; // The bugreport-xxx-2022-08-04....txt file
std::string build_fpr_;
- bool first_chunk_seen_ = false;
- std::unique_ptr<util::ZipReader> zip_reader_;
std::vector<AndroidLogEvent> log_events_;
size_t log_events_last_sorted_idx_ = 0;
};
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
index 965c15f..d0a2d88 100644
--- a/src/trace_processor/importers/common/mapping_tracker.cc
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -81,9 +81,6 @@
UserMemoryMapping& MappingTracker::CreateUserMemoryMapping(
UniquePid upid,
CreateMappingParams params) {
- // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf
- // add a mapping file_name ->build_id that we could use here
-
const AddressRange mapping_range = params.memory_range;
std::unique_ptr<UserMemoryMapping> mapping(
new UserMemoryMapping(context_, upid, std::move(params)));
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc
index 3fc95fc..e292801 100644
--- a/src/trace_processor/importers/common/process_tracker.cc
+++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -356,6 +356,17 @@
UniquePid upid = GetOrCreateProcess(pid);
auto* process_table = context_->storage->mutable_process_table();
+ // If we both know the previous and current parent pid and the two are not
+ // matching, we must have died and restarted: create a new process.
+ if (pupid) {
+ std::optional<UniquePid> prev_parent_upid =
+ process_table->parent_upid()[upid];
+ if (prev_parent_upid && prev_parent_upid != pupid) {
+ upid = StartNewProcess(std::nullopt, ppid, pid, kNullStringId,
+ ThreadNamePriority::kOther);
+ }
+ }
+
StringId proc_name_id = context_->storage->InternString(name);
process_table->mutable_name()->Set(upid, proc_name_id);
process_table->mutable_cmdline()->Set(
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index 7a3f2a3..d782352 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -34,9 +34,27 @@
"../../storage",
"../../tables:tables_python",
"../../types",
+ "../../util:build_id",
"../common:parser_types",
]
}
+
+source_set("tracker") {
+ sources = [
+ "dso_tracker.cc",
+ "dso_tracker.h",
+ ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../../include/perfetto/ext/base:base",
+ "../../../../protos/third_party/simpleperf:zero",
+ "../..//storage:storage",
+ "../..//tables:tables",
+ "../..//types:types",
+ "../common:common",
+ ]
+}
+
source_set("perf") {
sources = [
"attrs_section_reader.cc",
@@ -55,9 +73,11 @@
]
public_deps = [ ":record" ]
deps = [
+ ":tracker",
"../../../../gn:default_deps",
"../../../../protos/perfetto/trace:zero",
"../../../../protos/perfetto/trace/profiling:zero",
+ "../../../../protos/third_party/simpleperf:zero",
"../../sorter",
"../../storage",
"../../tables:tables_python",
diff --git a/src/trace_processor/importers/perf/dso_tracker.cc b/src/trace_processor/importers/perf/dso_tracker.cc
new file mode 100644
index 0000000..3ff3cef
--- /dev/null
+++ b/src/trace_processor/importers/perf/dso_tracker.cc
@@ -0,0 +1,134 @@
+/*
+ * 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_processor/importers/perf/dso_tracker.h"
+
+#include <cstdint>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
+#include "protos/third_party/simpleperf/record_file.pbzero.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::perf_importer {
+namespace {
+
+using third_party::simpleperf::proto::pbzero::FileFeature;
+using DexFile = FileFeature::DexFile;
+using ElfFile = FileFeature::ElfFile;
+using KernelModule = FileFeature::KernelModule;
+using DsoType = FileFeature::DsoType;
+using Symbol = FileFeature::Symbol;
+
+void InsertSymbols(const FileFeature::Decoder& file,
+ AddressRangeMap<std::string>& out) {
+ for (auto raw_symbol = file.symbol(); raw_symbol; ++raw_symbol) {
+ Symbol::Decoder symbol(*raw_symbol);
+ out.DeleteOverlapsAndEmplace(
+ AddressRange::FromStartAndSize(symbol.vaddr(), symbol.len()),
+ symbol.name().ToStdString());
+ }
+}
+} // namespace
+
+DsoTracker::DsoTracker(TraceProcessorContext* context)
+ : context_(context),
+ mapping_table_(context_->storage->stack_profile_mapping_table()) {}
+DsoTracker::~DsoTracker() = default;
+
+void DsoTracker::AddSimpleperfFile2(const FileFeature::Decoder& file) {
+ Dso dso;
+ switch (file.type()) {
+ case DsoType::DSO_KERNEL:
+ InsertSymbols(file, kernel_symbols_);
+ return;
+
+ case DsoType::DSO_ELF_FILE: {
+ ElfFile::Decoder elf(file.elf_file());
+ dso.load_bias = file.min_vaddr() - elf.file_offset_of_min_vaddr();
+ break;
+ }
+
+ case DsoType::DSO_KERNEL_MODULE: {
+ KernelModule::Decoder module(file.kernel_module());
+ dso.load_bias = file.min_vaddr() - module.memory_offset_of_min_vaddr();
+ break;
+ }
+
+ case DsoType::DSO_DEX_FILE:
+ case DsoType::DSO_SYMBOL_MAP_FILE:
+ case DsoType::DSO_UNKNOWN_FILE:
+ return;
+ }
+
+ InsertSymbols(file, dso.symbols);
+ files_.Insert(context_->storage->InternString(file.path()), std::move(dso));
+}
+
+void DsoTracker::SymbolizeFrames() {
+ const StringId kEmptyString = context_->storage->InternString("");
+ for (auto frame = context_->storage->mutable_stack_profile_frame_table()
+ ->IterateRows();
+ frame; ++frame) {
+ if (frame.name() != kNullStringId && frame.name() != kEmptyString) {
+ continue;
+ }
+
+ if (!TrySymbolizeFrame(frame.row_reference())) {
+ SymbolizeKernelFrame(frame.row_reference());
+ }
+ }
+}
+
+void DsoTracker::SymbolizeKernelFrame(
+ tables::StackProfileFrameTable::RowReference frame) {
+ const auto mapping = *mapping_table_.FindById(frame.mapping());
+ uint64_t address = static_cast<uint64_t>(frame.rel_pc()) +
+ static_cast<uint64_t>(mapping.start());
+ auto symbol = kernel_symbols_.Find(address);
+ if (symbol == kernel_symbols_.end()) {
+ return;
+ }
+ frame.set_name(
+ context_->storage->InternString(base::StringView(symbol->second)));
+}
+
+bool DsoTracker::TrySymbolizeFrame(
+ tables::StackProfileFrameTable::RowReference frame) {
+ const auto mapping = *mapping_table_.FindById(frame.mapping());
+ auto* file = files_.Find(mapping.name());
+ if (!file) {
+ return false;
+ }
+
+ // Load bias is something we can only determine by looking at the actual elf
+ // file. Thus PERF_RECORD_MMAP{2} events do not record it. So we need to
+ // potentially do an adjustment here if the load_bias tracked in the mapping
+ // table and the one reported by the file are mismatched.
+ uint64_t adj = file->load_bias - static_cast<uint64_t>(mapping.load_bias());
+
+ auto symbol = file->symbols.Find(static_cast<uint64_t>(frame.rel_pc()) + adj);
+ if (symbol == file->symbols.end()) {
+ return false;
+ }
+ frame.set_name(
+ context_->storage->InternString(base::StringView(symbol->second)));
+ return true;
+}
+
+} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/dso_tracker.h b/src/trace_processor/importers/perf/dso_tracker.h
new file mode 100644
index 0000000..314d549
--- /dev/null
+++ b/src/trace_processor/importers/perf/dso_tracker.h
@@ -0,0 +1,78 @@
+/*
+ * 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_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_
+
+#include <cstdint>
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "protos/third_party/simpleperf/record_file.pbzero.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// Keeps track of DSO symbols to symbolize frames at the end of the trace
+// parsing.
+// TODO(b/334978369): We could potentially use this class (or a similar one) to
+// process the ModuleSymbols proto packets and consolidate all symbolization in
+// one place.
+class DsoTracker : public Destructible {
+ public:
+ static DsoTracker& GetOrCreate(TraceProcessorContext* context) {
+ if (!context->perf_dso_tracker) {
+ context->perf_dso_tracker.reset(new DsoTracker(context));
+ }
+ return static_cast<DsoTracker&>(*context->perf_dso_tracker);
+ }
+ ~DsoTracker() override;
+
+ // Add symbol data contained in a `FileFeature` proto.
+ void AddSimpleperfFile2(
+ const third_party::simpleperf::proto::pbzero::FileFeature::Decoder& file);
+
+ // Tries to symbolize any `STACK_PROFILE_FRAME` frame missing the `name`
+ // attribute. This should be called at the end of parsing when all packets
+ // have been processed and all tables updated.
+ void SymbolizeFrames();
+
+ private:
+ struct Dso {
+ uint64_t load_bias;
+ AddressRangeMap<std::string> symbols;
+ };
+
+ explicit DsoTracker(TraceProcessorContext* context);
+
+ void SymbolizeKernelFrame(tables::StackProfileFrameTable::RowReference frame);
+ // Returns true it the frame was symbolized.
+ bool TrySymbolizeFrame(tables::StackProfileFrameTable::RowReference frame);
+
+ TraceProcessorContext* const context_;
+ const tables::StackProfileMappingTable& mapping_table_;
+ base::FlatHashMap<StringId, Dso> files_;
+ AddressRangeMap<std::string> kernel_symbols_;
+};
+
+} // namespace perfetto::trace_processor::perf_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_
diff --git a/src/trace_processor/importers/perf/features.cc b/src/trace_processor/importers/perf/features.cc
index 861a8bf..6ade833 100644
--- a/src/trace_processor/importers/perf/features.cc
+++ b/src/trace_processor/importers/perf/features.cc
@@ -32,6 +32,37 @@
namespace perfetto::trace_processor::perf_importer::feature {
namespace {
+struct BuildIdRecord {
+ static constexpr uint8_t kMaxSize = 20;
+ char data[kMaxSize];
+ uint8_t size;
+ uint8_t reserved[3];
+};
+
+uint8_t CountTrailingZeros(const BuildIdRecord& build_id) {
+ for (uint8_t i = 0; i < BuildIdRecord::kMaxSize; ++i) {
+ if (build_id.data[BuildIdRecord::kMaxSize - i - 1] != 0) {
+ return i;
+ }
+ }
+ return sizeof(build_id.data);
+}
+
+// BuildIds are usually SHA-1 hashes (20 bytes), sometimes MD5 (16 bytes),
+// sometimes 8 bytes long. Simpleperf adds trailing zeros up to 20. Do a best
+// guess based on the number of trailing zeros.
+uint8_t GuessBuildIdSize(const BuildIdRecord& build_id) {
+ static_assert(BuildIdRecord::kMaxSize == 20);
+ uint8_t len = BuildIdRecord::kMaxSize - CountTrailingZeros(build_id);
+ if (len > 16) {
+ return BuildIdRecord::kMaxSize;
+ }
+ if (len > 8) {
+ return 16;
+ }
+ return 8;
+}
+
bool ParseString(Reader& reader, std::string& out) {
uint32_t len;
base::StringView str;
@@ -51,11 +82,8 @@
TraceBlobView blob,
BuildId& out) {
Reader reader(std::move(blob));
- struct {
- char data[20];
- uint8_t size;
- uint8_t reserved[3];
- } build_id;
+
+ BuildIdRecord build_id;
if (!reader.Read(out.pid) || !reader.Read(build_id) ||
!reader.ReadStringUntilEndOrNull(out.filename)) {
@@ -63,7 +91,7 @@
}
if (header.misc & PERF_RECORD_MISC_EXT_RESERVED) {
- if (build_id.size > sizeof(build_id.data)) {
+ if (build_id.size > BuildIdRecord::kMaxSize) {
return false;
}
} else {
@@ -72,15 +100,7 @@
// build_id.size or build_id.reserved to do any checks.
// TODO(b/334978369): We should be able to tell for sure whether this is
// simpleperf or not by checking the existence of SimpleperfMetaInfo.
- build_id.size = 20;
- // BuildIds are usually SHA-1 hashes (20 bytes), sometimes MD5 (16 bites).
- // Simpleperf adds trailing zeros. But zeros could be in the MD5 hash.
- // But it the last 4 bytes are zeros there is a high chance this was an
- // MD5.
- if (build_id.data[16] == 0 && build_id.data[17] == 0 &&
- build_id.data[18] == 0 && build_id.data[19] == 0) {
- build_id.size = 16;
- }
+ build_id.size = GuessBuildIdSize(build_id);
}
out.build_id = std::string(build_id.data, build_id.size);
return true;
@@ -216,9 +236,8 @@
return util::OkStatus();
}
-util::Status ParseSimpleperfFile2(
- TraceBlobView bytes,
- std::function<util::Status(TraceBlobView)> cb) {
+util::Status ParseSimpleperfFile2(TraceBlobView bytes,
+ std::function<void(TraceBlobView)> cb) {
Reader reader(std::move(bytes));
while (reader.size_left() != 0) {
uint32_t len;
@@ -230,7 +249,7 @@
return base::ErrStatus(
"Failed to parse payload in FEATURE_SIMPLEPERF_FILE2");
}
- RETURN_IF_ERROR(cb(std::move(payload)));
+ cb(std::move(payload));
}
return util::OkStatus();
}
diff --git a/src/trace_processor/importers/perf/features.h b/src/trace_processor/importers/perf/features.h
index 2a6c22b..1af9498 100644
--- a/src/trace_processor/importers/perf/features.h
+++ b/src/trace_processor/importers/perf/features.h
@@ -121,9 +121,8 @@
event_type_info;
};
-util::Status ParseSimpleperfFile2(
- TraceBlobView,
- std::function<util::Status(TraceBlobView)> cb);
+util::Status ParseSimpleperfFile2(TraceBlobView,
+ std::function<void(TraceBlobView)> cb);
} // namespace perf_importer::feature
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
index 2dcc3e5..bbecf81 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
@@ -20,6 +20,7 @@
#include <cstdint>
#include <cstring>
#include <optional>
+#include <string>
#include <utility>
#include <vector>
@@ -30,9 +31,11 @@
#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "protos/third_party/simpleperf/record_file.pbzero.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/perf/attrs_section_reader.h"
+#include "src/trace_processor/importers/perf/dso_tracker.h"
#include "src/trace_processor/importers/perf/features.h"
#include "src/trace_processor/importers/perf/perf_event.h"
#include "src/trace_processor/importers/perf/perf_file.h"
@@ -42,6 +45,7 @@
#include "src/trace_processor/importers/proto/perf_sample_tracker.h"
#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/util/build_id.h"
#include "src/trace_processor/util/status_macros.h"
namespace perfetto {
@@ -387,7 +391,12 @@
case feature::ID_BUILD_ID:
return feature::BuildId::Parse(
- std::move(data), [](feature::BuildId) { return base::OkStatus(); });
+ std::move(data), [&](feature::BuildId build_id) {
+ perf_session_->AddBuildId(
+ build_id.pid, std::move(build_id.filename),
+ BuildId::FromRaw(std::move(build_id.build_id)));
+ return base::OkStatus();
+ });
case feature::ID_GROUP_DESC: {
feature::HeaderGroupDesc group_desc;
@@ -408,7 +417,11 @@
}
case feature::ID_SIMPLEPERF_FILE2: {
RETURN_IF_ERROR(feature::ParseSimpleperfFile2(
- std::move(data), [&](TraceBlobView) { return util::OkStatus(); }));
+ std::move(data), [&](TraceBlobView blob) {
+ third_party::simpleperf::proto::pbzero::FileFeature::Decoder file(
+ blob.data(), blob.length());
+ DsoTracker::GetOrCreate(context_).AddSimpleperfFile2(file);
+ }));
break;
}
diff --git a/src/trace_processor/importers/perf/perf_session.cc b/src/trace_processor/importers/perf/perf_session.cc
index db42ae7..4be5d80 100644
--- a/src/trace_processor/importers/perf/perf_session.cc
+++ b/src/trace_processor/importers/perf/perf_session.cc
@@ -20,6 +20,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <limits>
#include <optional>
#include <vector>
@@ -31,6 +32,7 @@
#include "src/trace_processor/importers/perf/perf_event.h"
#include "src/trace_processor/importers/perf/perf_event_attr.h"
#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/util/build_id.h"
namespace perfetto::trace_processor::perf_importer {
namespace {
@@ -153,4 +155,22 @@
}
}
+void PerfSession::AddBuildId(int32_t pid,
+ std::string filename,
+ BuildId build_id) {
+ build_ids_.Insert({pid, std::move(filename)}, std::move(build_id));
+}
+
+std::optional<BuildId> PerfSession::LookupBuildId(
+ uint32_t pid,
+ const std::string& filename) const {
+ // -1 is used in BUILD_ID feature to match any pid.
+ static constexpr int32_t kAnyPid = -1;
+ auto it = build_ids_.Find({static_cast<int32_t>(pid), filename});
+ if (!it) {
+ it = build_ids_.Find({kAnyPid, filename});
+ }
+ return it ? std::make_optional(*it) : std::nullopt;
+}
+
} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/perf_session.h b/src/trace_processor/importers/perf/perf_session.h
index c2a9e66..7153020 100644
--- a/src/trace_processor/importers/perf/perf_session.h
+++ b/src/trace_processor/importers/perf/perf_session.h
@@ -20,13 +20,17 @@
#include <sys/types.h>
#include <cstddef>
#include <cstdint>
+#include <optional>
+#include <string>
#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/perf/perf_event.h"
#include "src/trace_processor/importers/perf/perf_event_attr.h"
+#include "src/trace_processor/util/build_id.h"
namespace perfetto::trace_processor {
@@ -69,7 +73,26 @@
void SetEventName(uint64_t event_id, std::string name);
void SetEventName(uint32_t type, uint64_t config, const std::string& name);
+ void AddBuildId(int32_t pid, std::string filename, BuildId build_id);
+ std::optional<BuildId> LookupBuildId(uint32_t pid,
+ const std::string& filename) const;
+
private:
+ struct BuildIdMapKey {
+ int32_t pid;
+ std::string filename;
+
+ struct Hasher {
+ size_t operator()(const BuildIdMapKey& k) const {
+ return static_cast<size_t>(base::Hasher::Combine(k.pid, k.filename));
+ }
+ };
+
+ bool operator==(const BuildIdMapKey& o) const {
+ return pid == o.pid && filename == o.filename;
+ }
+ };
+
PerfSession(uint32_t perf_session_id,
base::FlatHashMap<uint64_t, RefPtr<PerfEventAttr>> attrs_by_id,
bool has_single_perf_event_attr)
@@ -88,6 +111,8 @@
// associated). This makes the attr lookup given a record trivial and not
// dependant no having any id field in the records.
bool has_single_perf_event_attr_;
+
+ base::FlatHashMap<BuildIdMapKey, BuildId, BuildIdMapKey::Hasher> build_ids_;
};
} // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/reader.h b/src/trace_processor/importers/perf/reader.h
index 91a4253..25d4f4c 100644
--- a/src/trace_processor/importers/perf/reader.h
+++ b/src/trace_processor/importers/perf/reader.h
@@ -74,7 +74,8 @@
if (size_left() < size) {
return false;
}
- blob = TraceBlobView(buffer_, static_cast<size_t>(end_ - current_), size);
+ blob = TraceBlobView(buffer_,
+ static_cast<size_t>(current_ - buffer_->data()), size);
current_ += size;
return true;
}
diff --git a/src/trace_processor/importers/perf/record_parser.cc b/src/trace_processor/importers/perf/record_parser.cc
index 7371adf..c36d3db 100644
--- a/src/trace_processor/importers/perf/record_parser.cc
+++ b/src/trace_processor/importers/perf/record_parser.cc
@@ -51,7 +51,7 @@
CreateMappingParams BuildCreateMappingParams(
const CommonMmapRecordFields& fields,
std::string filename,
- std::optional<BuildId> build_id = std::nullopt) {
+ std::optional<BuildId> build_id) {
return {AddressRange::FromStartAndSize(fields.addr, fields.len), fields.pgoff,
// start_offset: This is the offset into the file where the ELF header
// starts. We assume all file mappings are ELF files an thus this
@@ -233,14 +233,18 @@
base::Status RecordParser::ParseMmap(Record record) {
MmapRecord mmap;
RETURN_IF_ERROR(mmap.Parse(record));
+ std::optional<BuildId> build_id =
+ record.session->LookupBuildId(mmap.pid, mmap.filename);
if (IsInKernel(record.GetCpuMode())) {
context_->mapping_tracker->CreateKernelMemoryMapping(
- BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+ BuildCreateMappingParams(mmap, std::move(mmap.filename),
+ std::move(build_id)));
return base::OkStatus();
}
context_->mapping_tracker->CreateUserMemoryMapping(
- GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+ GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename),
+ std::move(build_id)));
return base::OkStatus();
}
@@ -248,15 +252,20 @@
util::Status RecordParser::ParseMmap2(Record record) {
Mmap2Record mmap2;
RETURN_IF_ERROR(mmap2.Parse(record));
+ std::optional<BuildId> build_id = mmap2.GetBuildId();
+ if (!build_id.has_value()) {
+ build_id = record.session->LookupBuildId(mmap2.pid, mmap2.filename);
+ }
if (IsInKernel(record.GetCpuMode())) {
context_->mapping_tracker->CreateKernelMemoryMapping(
- BuildCreateMappingParams(mmap2, std::move(mmap2.filename)));
+ BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
+ std::move(build_id)));
return base::OkStatus();
}
context_->mapping_tracker->CreateUserMemoryMapping(
GetUpid(mmap2), BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
- mmap2.GetBuildId()));
+ std::move(build_id)));
return base::OkStatus();
}
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc
index 9022cbb..d08d946 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.cc
+++ b/src/trace_processor/importers/proto/gpu_event_parser.cc
@@ -330,6 +330,7 @@
ConstBytes blob) {
protos::pbzero::GpuRenderStageEvent::Decoder event(blob.data, blob.size);
+ int32_t pid = 0;
if (event.has_specifications()) {
protos::pbzero::GpuRenderStageEvent_Specifications::Decoder spec(
event.specifications().data, event.specifications().size);
@@ -349,6 +350,13 @@
context_->storage->InternString(stage.description())));
}
}
+ if (spec.has_context_spec()) {
+ protos::pbzero::GpuRenderStageEvent_Specifications_ContextSpec::Decoder
+ context_spec(spec.context_spec());
+ if (context_spec.has_pid()) {
+ pid = context_spec.pid();
+ }
+ }
}
auto args_callback = [this, &event,
@@ -468,7 +476,8 @@
row.command_buffer_name = command_buffer_name_id;
row.submission_id = event.submission_id();
row.hw_queue_id = static_cast<int64_t>(hw_queue_id);
-
+ row.upid = context_->process_tracker->GetOrCreateProcess(
+ static_cast<uint32_t>(pid));
context_->slice_tracker->ScopedTyped(
context_->storage->mutable_gpu_slice_table(), row, args_callback);
}
diff --git a/src/trace_processor/importers/zip/BUILD.gn b/src/trace_processor/importers/zip/BUILD.gn
new file mode 100644
index 0000000..a938fdd
--- /dev/null
+++ b/src/trace_processor/importers/zip/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright (C) 2022 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.
+
+source_set("full") {
+ sources = [
+ "zip_trace_reader.cc",
+ "zip_trace_reader.h",
+ ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../../gn:zlib",
+ "../../../../include/perfetto/ext/base:base",
+ "../../../trace_processor:storage_minimal",
+ "../../types",
+ "../../util:trace_type",
+ "../../util:util",
+ "../../util:zip_reader",
+ "../android_bugreport",
+ "../common",
+ "../proto:minimal",
+ ]
+}
diff --git a/src/trace_processor/importers/zip/zip_trace_reader.cc b/src/trace_processor/importers/zip/zip_trace_reader.cc
new file mode 100644
index 0000000..20f019f
--- /dev/null
+++ b/src/trace_processor/importers/zip/zip_trace_reader.cc
@@ -0,0 +1,158 @@
+/*
+ * 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_processor/importers/zip/zip_trace_reader.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
+#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+// Proto traces should always parsed first as they might contains clock sync
+// data needed to correctly parse other traces.
+// The rest of the types are sorted by position in the enum but this is not
+// something users should rely on.
+// TODO(carlscab): Proto traces with just ModuleSymbols packets should be an
+// exception. We actually need those are the very end (once whe have all the
+// Frames). Alternatively we could build a map address -> symbol during
+// tokenization and use this during parsing to resolve symbols.
+bool CompareTraceType(TraceType lhs, TraceType rhs) {
+ if (rhs == TraceType::kProtoTraceType) {
+ return false;
+ }
+ if (lhs == TraceType::kProtoTraceType) {
+ return true;
+ }
+ return lhs < rhs;
+}
+
+bool HasSymbols(const TraceBlobView& blob) {
+ bool has_symbols = false;
+ ProtoTraceTokenizer().Tokenize(blob.copy(), [&](TraceBlobView raw) {
+ protos::pbzero::TracePacket::Decoder packet(raw.data(), raw.size());
+ has_symbols = packet.has_module_symbols();
+ return base::ErrStatus("break");
+ });
+ return has_symbols;
+}
+
+} // namespace
+
+ZipTraceReader::ZipTraceReader(TraceProcessorContext* context)
+ : context_(context) {}
+ZipTraceReader::~ZipTraceReader() = default;
+
+bool ZipTraceReader::Entry::operator<(const Entry& rhs) const {
+ // Traces with symbols should be the last ones to be read.
+ if (has_symbols) {
+ return false;
+ }
+ if (rhs.has_symbols) {
+ return true;
+ }
+ if (CompareTraceType(trace_type, rhs.trace_type)) {
+ return true;
+ }
+ if (CompareTraceType(rhs.trace_type, trace_type)) {
+ return false;
+ }
+ return std::tie(name, index) < std::tie(rhs.name, rhs.index);
+}
+
+util::Status ZipTraceReader::Parse(TraceBlobView blob) {
+ zip_reader_.Parse(blob.data(), blob.size());
+ return base::OkStatus();
+}
+
+void ZipTraceReader::NotifyEndOfFile() {
+ base::Status status = NotifyEndOfFileImpl();
+ if (!status.ok()) {
+ PERFETTO_ELOG("ZipTraceReader failed: %s", status.c_message());
+ }
+}
+
+base::Status ZipTraceReader::NotifyEndOfFileImpl() {
+ std::vector<util::ZipFile> files = zip_reader_.TakeFiles();
+
+ // Android bug reports are ZIP files and its files do not get handled
+ // separately.
+ if (AndroidBugreportParser::IsAndroidBugReport(files)) {
+ return AndroidBugreportParser::Parse(context_, std::move(files));
+ }
+
+ base::StatusOr<std::vector<Entry>> entries = ExtractEntries(std::move(files));
+ if (!entries.ok()) {
+ return entries.status();
+ }
+ std::sort(entries->begin(), entries->end());
+
+ for (Entry& e : *entries) {
+ parsers_.push_back(std::make_unique<ForwardingTraceParser>(context_));
+ auto& parser = *parsers_.back();
+ RETURN_IF_ERROR(parser.Parse(std::move(e.uncompressed_data)));
+ parser.NotifyEndOfFile();
+ }
+ return base::OkStatus();
+}
+
+base::StatusOr<std::vector<ZipTraceReader::Entry>>
+ZipTraceReader::ExtractEntries(std::vector<util::ZipFile> files) const {
+ // TODO(carlsacab): There is a lot of unnecessary copying going on here.
+ // ZipTraceReader can directly parse the ZIP file and given that we know the
+ // decompressed size we could directly decompress into TraceBlob chunks and
+ // send them to the tokenizer.
+ std::vector<Entry> entries;
+ std::vector<uint8_t> buffer;
+ for (size_t i = 0; i < files.size(); ++i) {
+ const util::ZipFile& zip_file = files[i];
+ Entry entry;
+ entry.name = zip_file.name();
+ entry.index = i;
+ RETURN_IF_ERROR(files[i].Decompress(&buffer));
+ entry.uncompressed_data =
+ TraceBlobView(TraceBlob::CopyFrom(buffer.data(), buffer.size()));
+ entry.trace_type = GuessTraceType(entry.uncompressed_data.data(),
+ entry.uncompressed_data.size());
+ entry.has_symbols = entry.trace_type == TraceType::kProtoTraceType &&
+ HasSymbols(entry.uncompressed_data);
+ entries.push_back(std::move(entry));
+ }
+ return std::move(entries);
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/zip/zip_trace_reader.h b/src/trace_processor/importers/zip/zip_trace_reader.h
new file mode 100644
index 0000000..d4f3d88
--- /dev/null
+++ b/src/trace_processor/importers/zip/zip_trace_reader.h
@@ -0,0 +1,84 @@
+/*
+ * 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_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/util/trace_type.h"
+#include "src/trace_processor/util/zip_reader.h"
+
+namespace perfetto::trace_processor {
+
+class ForwardingTraceParser;
+class TraceProcessorContext;
+
+// Forwards files contained in a ZIP to the appropiate ChunkedTraceReader. It is
+// guaranteed that proto traces will be parsed first.
+class ZipTraceReader : public ChunkedTraceReader {
+ public:
+ explicit ZipTraceReader(TraceProcessorContext* context);
+ ~ZipTraceReader() override;
+
+ // ChunkedTraceReader implementation
+ util::Status Parse(TraceBlobView) override;
+ void NotifyEndOfFile() override;
+
+ private:
+ // Represents a file in the ZIP file. Used to sort them before sending the
+ // files one by one to a `ForwardingTraceParser` instance.
+ struct Entry {
+ // File name. Used to break ties.
+ std::string name;
+ // Position in the zip file. Used to break ties.
+ size_t index;
+ // Trace type. This is the main attribute traces are ordered by. Proto
+ // traces are always parsed first as they might contains clock sync
+ // data needed to correctly parse other traces.
+ TraceType trace_type;
+ TraceBlobView uncompressed_data;
+ // True for proto trace_types whose fist message is a ModuleSymbols packet
+ bool has_symbols = false;
+ // Comparator used to determine the order in which files in the ZIP will be
+ // read.
+ bool operator<(const Entry& rhs) const;
+ };
+
+ base::Status NotifyEndOfFileImpl();
+ base::StatusOr<std::vector<Entry>> ExtractEntries(
+ std::vector<util::ZipFile> files) const;
+ base::Status ParseEntry(Entry entry);
+
+ TraceProcessorContext* const context_;
+ util::ZipReader zip_reader_;
+ // For every file in the ZIP we will create a `ForwardingTraceParser`instance
+ // and send that file to it for tokenization. The instances are kept around
+ // here as some tokenizers might keep state that is later needed after
+ // sorting.
+ std::vector<std::unique_ptr<ForwardingTraceParser>> parsers_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
index 9140387..5738bff 100644
--- a/src/trace_processor/metrics/sql/android/android_boot.sql
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -19,6 +19,24 @@
INCLUDE PERFETTO MODULE android.garbage_collection;
INCLUDE PERFETTO MODULE android.oom_adjuster;
+DROP VIEW IF EXISTS android_oom_adj_intervals_with_detailed_bucket_name;
+CREATE PERFETTO VIEW android_oom_adj_intervals_with_detailed_bucket_name AS
+SELECT
+ ts,
+ dur,
+ score,
+ android_oom_adj_score_to_detailed_bucket_name(score, android_appid) AS bucket,
+ upid,
+ process_name,
+ oom_adj_id,
+ oom_adj_ts,
+ oom_adj_dur,
+ oom_adj_track_id,
+ oom_adj_thread_name,
+ oom_adj_reason,
+ oom_adj_trigger
+FROM _oom_adjuster_intervals;
+
CREATE OR REPLACE PERFETTO FUNCTION get_durations(process_name STRING)
RETURNS TABLE(uint_sleep_dur LONG, total_dur LONG) AS
SELECT
@@ -41,7 +59,7 @@
bucket,
process_name,
oom_adj_reason
-FROM android_oom_adj_intervals;
+FROM android_oom_adj_intervals_with_detailed_bucket_name;
DROP VIEW IF EXISTS oom_adj_events_by_process_name;
CREATE PERFETTO VIEW oom_adj_events_by_process_name AS
@@ -280,7 +298,7 @@
NULL as name,
bucket,
SUM(dur) as total_dur
- FROM android_oom_adj_intervals
+ FROM android_oom_adj_intervals_with_detailed_bucket_name
WHERE ts > first_user_unlocked()
GROUP BY bucket)
),
@@ -296,7 +314,7 @@
process_name as name,
bucket,
SUM(dur) as total_dur
- FROM android_oom_adj_intervals
+ FROM android_oom_adj_intervals_with_detailed_bucket_name
WHERE ts > first_user_unlocked()
AND process_name IS NOT NULL
GROUP BY process_name, bucket)
@@ -318,7 +336,7 @@
AVG(oom_adj_dur) as avg_oom_adj_dur,
COUNT(DISTINCT(oom_adj_id)) oom_adj_event_count,
oom_adj_reason
- FROM android_oom_adj_intervals
+ FROM android_oom_adj_intervals_with_detailed_bucket_name
WHERE ts > first_user_unlocked()
GROUP BY oom_adj_reason
)
diff --git a/src/trace_processor/metrics/sql/android/android_lmk_reason.sql b/src/trace_processor/metrics/sql/android/android_lmk_reason.sql
index 0ca2258..b7c7ca5 100644
--- a/src/trace_processor/metrics/sql/android/android_lmk_reason.sql
+++ b/src/trace_processor/metrics/sql/android/android_lmk_reason.sql
@@ -64,8 +64,8 @@
FROM lmk_events
JOIN rss_and_swap_span
WHERE lmk_events.ts
- BETWEEN rss_and_swap_span.ts
- AND rss_and_swap_span.ts + MAX(rss_and_swap_span.dur - 1, 0)
+ BETWEEN rss_and_swap_span.ts
+ AND rss_and_swap_span.ts + MAX(rss_and_swap_span.dur - 1, 0)
),
lmk_process_sizes_output AS (
SELECT ts, RepeatedField(AndroidLmkReasonMetric_Process(
diff --git a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
index 7b752a8..1564586 100644
--- a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
+++ b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
@@ -15,6 +15,51 @@
--
INCLUDE PERFETTO MODULE android.oom_adjuster;
+DROP VIEW IF EXISTS android_oom_adj_intervals_with_detailed_bucket_name;
+CREATE PERFETTO VIEW android_oom_adj_intervals_with_detailed_bucket_name (
+ -- Timestamp the oom_adj score of the process changed
+ ts INT,
+ -- Duration until the next oom_adj score change of the process.
+ dur INT,
+ -- oom_adj score of the process.
+ score INT,
+ -- oom_adj bucket of the process.
+ bucket STRING,
+ -- Upid of the process having an oom_adj update.
+ upid INT,
+ -- Name of the process having an oom_adj update.
+ process_name STRING,
+ -- Slice id of the latest oom_adj update in the system_server.
+ oom_adj_id INT,
+ -- Timestamp of the latest oom_adj update in the system_server.
+ oom_adj_ts INT,
+ -- Duration of the latest oom_adj update in the system_server.
+ oom_adj_dur INT,
+ -- Track id of the latest oom_adj update in the system_server
+ oom_adj_track_id INT,
+ -- Thread name of the latest oom_adj update in the system_server.
+ oom_adj_thread_name STRING,
+ -- Reason for the latest oom_adj update in the system_server.
+ oom_adj_reason STRING,
+ -- Trigger for the latest oom_adj update in the system_server.
+ oom_adj_trigger STRING
+ ) AS
+SELECT
+ ts,
+ dur,
+ score,
+ android_oom_adj_score_to_detailed_bucket_name(score, android_appid) AS bucket,
+ upid,
+ process_name,
+ oom_adj_id,
+ oom_adj_ts,
+ oom_adj_dur,
+ oom_adj_track_id,
+ oom_adj_thread_name,
+ oom_adj_reason,
+ oom_adj_trigger
+FROM _oom_adjuster_intervals;
+
DROP TABLE IF EXISTS _oom_adj_events_with_src_bucket;
CREATE PERFETTO TABLE _oom_adj_events_with_src_bucket
AS
@@ -24,7 +69,7 @@
bucket,
process_name,
oom_adj_reason
-FROM android_oom_adj_intervals;
+FROM android_oom_adj_intervals_with_detailed_bucket_name;
DROP VIEW IF EXISTS oom_adj_events_by_process_name;
CREATE PERFETTO VIEW oom_adj_events_by_process_name AS
@@ -100,7 +145,7 @@
)
FROM (
SELECT NULL as name, bucket, SUM(dur) as total_dur
- FROM android_oom_adj_intervals GROUP BY bucket
+ FROM android_oom_adj_intervals_with_detailed_bucket_name GROUP BY bucket
)
),
'oom_adj_bucket_duration_agg_by_process',(SELECT RepeatedField(
@@ -112,7 +157,7 @@
)
FROM (
SELECT process_name as name, bucket, SUM(dur) as total_dur
- FROM android_oom_adj_intervals
+ FROM android_oom_adj_intervals_with_detailed_bucket_name
WHERE process_name IS NOT NULL
GROUP BY process_name, bucket
)
@@ -133,7 +178,7 @@
AVG(oom_adj_dur) as avg_oom_adj_dur,
COUNT(DISTINCT(oom_adj_id)) oom_adj_event_count,
oom_adj_reason
- FROM android_oom_adj_intervals GROUP BY oom_adj_reason
+ FROM android_oom_adj_intervals_with_detailed_bucket_name GROUP BY oom_adj_reason
)
)
);
diff --git a/src/trace_processor/metrics/sql/android/process_mem.sql b/src/trace_processor/metrics/sql/android/process_mem.sql
index f52f63e..f3b1c88 100644
--- a/src/trace_processor/metrics/sql/android/process_mem.sql
+++ b/src/trace_processor/metrics/sql/android/process_mem.sql
@@ -14,75 +14,51 @@
-- limitations under the License.
--
--- Create all the views used to generate the Android Memory metrics proto.
--- Anon RSS
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
- 'table_name', 'anon_rss',
- 'counter_name', 'mem.rss.anon');
+INCLUDE PERFETTO MODULE memory.linux.process;
--- File RSS
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
- 'table_name', 'file_rss',
- 'counter_name', 'mem.rss.file');
-
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
- 'table_name', 'shmem_rss',
- 'counter_name', 'mem.rss.shmem');
-
--- Swap
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
- 'table_name', 'swap',
- 'counter_name', 'mem.swap');
-
--- OOM score
SELECT RUN_METRIC('android/process_oom_score.sql');
--- Anon RSS + Swap
-DROP TABLE IF EXISTS anon_and_swap_join;
-CREATE VIRTUAL TABLE anon_and_swap_join
-USING SPAN_OUTER_JOIN(anon_rss_span PARTITIONED upid, swap_span PARTITIONED upid);
+DROP VIEW IF EXISTS anon_rss_span;
+CREATE PERFETTO VIEW anon_rss_span AS
+SELECT * FROM _anon_rss;
+
+DROP VIEW IF EXISTS file_rss_span;
+CREATE PERFETTO VIEW file_rss_span AS
+SELECT * FROM _file_rss;
+
+DROP VIEW IF EXISTS shmem_rss_span;
+CREATE PERFETTO VIEW shmem_rss_span AS
+SELECT * FROM _shmem_rss;
+
+DROP VIEW IF EXISTS swap_span;
+CREATE PERFETTO VIEW swap_span AS
+SELECT * FROM _swap;
DROP VIEW IF EXISTS anon_and_swap_span;
CREATE PERFETTO VIEW anon_and_swap_span AS
SELECT
ts, dur, upid,
IFNULL(anon_rss_val, 0) + IFNULL(swap_val, 0) AS anon_and_swap_val
-FROM anon_and_swap_join;
-
--- Anon RSS + file RSS + Swap
-DROP TABLE IF EXISTS anon_and_file_and_swap_join;
-CREATE VIRTUAL TABLE anon_and_file_and_swap_join
-USING SPAN_OUTER_JOIN(
- anon_and_swap_join PARTITIONED upid,
- file_rss_span PARTITIONED upid
-);
-
--- RSS + Swap
-DROP TABLE IF EXISTS rss_and_swap_join;
-CREATE VIRTUAL TABLE rss_and_swap_join
-USING SPAN_OUTER_JOIN(
- anon_and_file_and_swap_join PARTITIONED upid,
- shmem_rss_span PARTITIONED upid
-);
+FROM _anon_swap_sj;
DROP VIEW IF EXISTS rss_and_swap_span;
CREATE PERFETTO VIEW rss_and_swap_span AS
SELECT
- ts, dur, upid,
- CAST(IFNULL(file_rss_val, 0) AS INT) AS file_rss_val,
- CAST(IFNULL(anon_rss_val, 0) AS INT) AS anon_rss_val,
- CAST(IFNULL(shmem_rss_val, 0) AS INT) AS shmem_rss_val,
- CAST(IFNULL(swap_val, 0) AS INT) AS swap_val,
- CAST(
- IFNULL(anon_rss_val, 0)
- + IFNULL(file_rss_val, 0)
- + IFNULL(shmem_rss_val, 0) AS int) AS rss_val,
- CAST(
- IFNULL(anon_rss_val, 0)
- + IFNULL(swap_val, 0)
- + IFNULL(file_rss_val, 0)
- + IFNULL(shmem_rss_val, 0) AS int) AS rss_and_swap_val
-FROM rss_and_swap_join;
+ ts,
+ dur,
+ upid,
+ file_rss AS file_rss_val,
+ anon_rss AS anon_rss_val,
+ shmem_rss AS shmem_rss_val,
+ swap AS swap_val,
+ COALESCE(file_rss, 0)
+ + COALESCE(anon_rss, 0)
+ + COALESCE(shmem_rss, 0) AS rss_val,
+ COALESCE(file_rss, 0)
+ + COALESCE(anon_rss, 0)
+ + COALESCE(shmem_rss, 0)
+ + COALESCE(swap, 0) AS rss_and_swap_val
+FROM _memory_rss_and_swap_per_process_table;
-- If we have dalvik events enabled (for ART trace points) we can construct the java heap timeline.
SELECT RUN_METRIC('android/process_counter_span_view.sql',
@@ -94,22 +70,22 @@
SELECT ts, dur, upid, java_heap_kb_val * 1024 AS java_heap_val
FROM java_heap_kb_span;
+DROP TABLE IF EXISTS java_heap_by_oom_span;
+CREATE VIRTUAL TABLE java_heap_by_oom_span
+USING SPAN_JOIN(java_heap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+
DROP TABLE IF EXISTS anon_rss_by_oom_span;
CREATE VIRTUAL TABLE anon_rss_by_oom_span
-USING SPAN_JOIN(anon_rss_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+USING SPAN_JOIN(_anon_rss PARTITIONED upid, oom_score_span PARTITIONED upid);
DROP TABLE IF EXISTS file_rss_by_oom_span;
CREATE VIRTUAL TABLE file_rss_by_oom_span
-USING SPAN_JOIN(file_rss_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+USING SPAN_JOIN(_file_rss PARTITIONED upid, oom_score_span PARTITIONED upid);
DROP TABLE IF EXISTS swap_by_oom_span;
CREATE VIRTUAL TABLE swap_by_oom_span
-USING SPAN_JOIN(swap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+USING SPAN_JOIN(_swap PARTITIONED upid, oom_score_span PARTITIONED upid);
DROP TABLE IF EXISTS anon_and_swap_by_oom_span;
CREATE VIRTUAL TABLE anon_and_swap_by_oom_span
USING SPAN_JOIN(anon_and_swap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
-
-DROP TABLE IF EXISTS java_heap_by_oom_span;
-CREATE VIRTUAL TABLE java_heap_by_oom_span
-USING SPAN_JOIN(java_heap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 8f163a0..3048e68 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -32,6 +32,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
@@ -848,7 +849,18 @@
const std::vector<std::string>& column_names,
const std::vector<sql_argument::ArgumentDefinition>& schema,
const char* tag) {
- // If the user has not provided a schema, we have nothing to validate.
+ std::vector<std::string> duplicate_columns;
+ for (auto it = column_names.begin(); it != column_names.end(); ++it) {
+ if (std::count(it + 1, column_names.end(), *it) > 0) {
+ duplicate_columns.push_back(*it);
+ }
+ }
+ if (!duplicate_columns.empty()) {
+ return base::ErrStatus("%s: multiple columns are named: %s", tag,
+ base::Join(duplicate_columns, ", ").c_str());
+ }
+
+ // If the user has not provided a schema, we have nothing further to validate.
if (schema.empty()) {
return base::OkStatus();
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 349e2f6..e746a1d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -25,6 +25,7 @@
"counters",
"cpu",
"deprecated/v42/common",
+ "gpu",
"graphs",
"intervals",
"linux",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
index 3ce017e..c09414e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
@@ -17,8 +17,30 @@
INCLUDE PERFETTO MODULE slices.with_context;
INCLUDE PERFETTO MODULE counters.intervals;
--- Converts an oom_adj score Integer to String bucket name.
+-- Converts an oom_adj score Integer to String sample name.
+-- One of: cached, background, job, foreground_service, bfgs, foreground and
+-- system.
CREATE PERFETTO FUNCTION android_oom_adj_score_to_bucket_name(
+ -- `oom_score` value
+ oom_score INT
+)
+-- Returns the sample bucket based on the oom score.
+RETURNS STRING
+AS
+SELECT
+ CASE
+ WHEN $oom_score >= 900 THEN 'cached'
+ WHEN $oom_score BETWEEN 250 AND 900 THEN 'background'
+ WHEN $oom_score BETWEEN 201 AND 250 THEN 'job'
+ WHEN $oom_score = 200 THEN 'foreground_service'
+ WHEN $oom_score BETWEEN 100 AND 200 THEN 'bfgs'
+ WHEN $oom_score BETWEEN 0 AND 100 THEN 'foreground'
+ WHEN $oom_score < 0 THEN 'system'
+END;
+
+-- Converts an oom_adj score Integer to String bucket name.
+-- Deprecated: use `android_oom_adj_score_to_bucket_name` instead.
+CREATE PERFETTO FUNCTION android_oom_adj_score_to_detailed_bucket_name(
-- oom_adj score.
value INT,
-- android_app id of the process.
@@ -52,8 +74,58 @@
ELSE 'unknown_app'
END;
+CREATE PERFETTO TABLE _oom_adjuster_intervals AS
+WITH reason AS (
+ SELECT
+ thread_slice.id AS oom_adj_id,
+ thread_slice.ts AS oom_adj_ts,
+ thread_slice.dur AS oom_adj_dur,
+ thread_slice.track_id AS oom_adj_track_id,
+ utid AS oom_adj_utid,
+ thread_name AS oom_adj_thread_name,
+ str_split(thread_slice.name, '_', 1) AS oom_adj_reason,
+ slice.name AS oom_adj_trigger,
+ LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts
+ FROM thread_slice
+ LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1
+ WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server'
+)
+SELECT
+ ts,
+ dur,
+ cast_int!(value) AS score,
+ process.upid,
+ process.name AS process_name,
+ reason.oom_adj_id,
+ reason.oom_adj_ts,
+ reason.oom_adj_dur,
+ reason.oom_adj_track_id,
+ reason.oom_adj_thread_name,
+ reason.oom_adj_utid,
+ reason.oom_adj_reason,
+ reason.oom_adj_trigger,
+ android_appid
+FROM
+ counter_leading_intervals
+ !(
+ (
+ SELECT counter.*
+ FROM counter
+ JOIN counter_track track
+ ON track.id = counter.track_id AND track.name = 'oom_score_adj'
+ ))
+ counter
+JOIN process_counter_track track
+ ON counter.track_id = track.id
+JOIN process
+ USING (upid)
+LEFT JOIN reason
+ ON counter.ts BETWEEN oom_adj_ts AND COALESCE(oom_adj_next_ts, trace_end())
+WHERE track.name = 'oom_score_adj';
+
+
-- All oom adj state intervals across all processes along with the reason for the state update.
-CREATE PERFETTO TABLE android_oom_adj_intervals (
+CREATE PERFETTO VIEW android_oom_adj_intervals (
-- Timestamp the oom_adj score of the process changed
ts INT,
-- Duration until the next oom_adj score change of the process.
@@ -81,49 +153,18 @@
-- Trigger for the latest oom_adj update in the system_server.
oom_adj_trigger STRING
) AS
-WITH
- reason AS (
- SELECT
- thread_slice.id AS oom_adj_id,
- thread_slice.ts AS oom_adj_ts,
- thread_slice.dur AS oom_adj_dur,
- thread_slice.track_id AS oom_adj_track_id,
- thread_name AS oom_adj_thread_name,
- str_split(thread_slice.name, '_', 1) AS oom_adj_reason,
- slice.name AS oom_adj_trigger,
- LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts
- FROM thread_slice
- LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1
- WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server'
- )
SELECT
ts,
dur,
- value AS score,
- android_oom_adj_score_to_bucket_name(value, android_appid) AS bucket,
- process.upid,
- process.name AS process_name,
- reason.oom_adj_id,
- reason.oom_adj_ts,
- reason.oom_adj_dur,
- reason.oom_adj_track_id,
- reason.oom_adj_thread_name,
- reason.oom_adj_reason,
- reason.oom_adj_trigger
-FROM
- counter_leading_intervals
- !(
- (
- SELECT counter.*
- FROM counter
- JOIN counter_track track
- ON track.id = counter.track_id AND track.name = 'oom_score_adj'
- ))
- counter
-JOIN process_counter_track track
- ON counter.track_id = track.id
-JOIN process
- USING (upid)
-LEFT JOIN reason
- ON counter.ts BETWEEN oom_adj_ts AND COALESCE(oom_adj_next_ts, trace_end())
-WHERE track.name = 'oom_score_adj';
+ score,
+ android_oom_adj_score_to_bucket_name(score) AS bucket,
+ upid,
+ process_name,
+ oom_adj_id,
+ oom_adj_ts,
+ oom_adj_dur,
+ oom_adj_track_id,
+ oom_adj_thread_name,
+ oom_adj_reason,
+ oom_adj_trigger
+FROM _oom_adjuster_intervals;
diff --git a/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql b/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql
index 82427f7..7bb0c96 100644
--- a/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql
@@ -62,7 +62,7 @@
ts,
track_id,
LEAD(ts, 1, trace_end()) OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
- CAST(value AS INT) AS value
+ value
FROM base
WHERE value != lag_value OR lag_value IS NULL
);
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
index c0226bf..5dd2a48 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
@@ -37,7 +37,7 @@
count_w_dur.track_id,
count_w_dur.ts,
count_w_dur.dur,
- count_w_dur.value as freq,
+ cast_int!(count_w_dur.value) as freq,
cct.cpu
FROM
counter_leading_intervals!((
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
index 7f837a2..703a913 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
@@ -39,7 +39,7 @@
count_w_dur.track_id,
count_w_dur.ts,
count_w_dur.dur,
- IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value) AS idle,
+ cast_int!(IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value)) AS idle,
cct.cpu
FROM
counter_leading_intervals!((
diff --git a/src/trace_processor/perfetto_sql/stdlib/gpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/gpu/BUILD.gn
new file mode 100644
index 0000000..a5f431d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/gpu/BUILD.gn
@@ -0,0 +1,19 @@
+# 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("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("gpu") {
+ sources = [ "frequency.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql b/src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql
new file mode 100644
index 0000000..2bd87b2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql
@@ -0,0 +1,42 @@
+--
+-- 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 counters.intervals;
+
+-- GPU frequency counter per GPU.
+CREATE PERFETTO TABLE gpu_frequency(
+ -- Timestamp
+ ts INT,
+ -- Duration
+ dur INT,
+ -- GPU id. Joinable with `gpu_counter_track.gpu_id`.
+ gpu_id INT,
+ -- GPU frequency
+ gpu_freq INT
+) AS
+SELECT
+ ts,
+ dur,
+ gpu_id,
+ cast_int!(value) AS gpu_freq
+FROM counter_leading_intervals!((
+ SELECT c.*
+ FROM counter c
+ JOIN gpu_counter_track t
+ ON t.id = c.track_id AND t.name = 'gpufreq'
+ WHERE gpu_id IS NOT NULL
+))
+JOIN gpu_counter_track t ON t.id = track_id;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
index ae3ad17..ddc6419 100644
--- a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
@@ -15,5 +15,9 @@
import("../../../../../gn/perfetto_sql.gni")
perfetto_sql_source_set("memory") {
+ deps = [
+ "android",
+ "linux",
+ ]
sources = [ "heap_graph_dominator_tree.sql" ]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/android/BUILD.gn
new file mode 100644
index 0000000..55123b6
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/android/BUILD.gn
@@ -0,0 +1,19 @@
+# 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("../../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("android") {
+ sources = [ "gpu.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql b/src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql
new file mode 100644
index 0000000..39abaec
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql
@@ -0,0 +1,35 @@
+--
+-- 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 memory.linux.general;
+
+-- Counter for GPU memory per process with duration.
+CREATE PERFETTO TABLE memory_gpu_per_process(
+ -- Timestamp
+ ts INT,
+ -- Duration
+ dur INT,
+ -- Upid of the process
+ upid INT,
+ -- GPU memory
+ gpu_memory INT
+) AS
+SELECT
+ ts,
+ dur,
+ upid,
+ cast_int!(value) AS gpu_memory
+FROM _all_counters_per_process
+WHERE name = 'GPU Memory';
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/linux/BUILD.gn
new file mode 100644
index 0000000..0711197
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/BUILD.gn
@@ -0,0 +1,23 @@
+# 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("../../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("linux") {
+ sources = [
+ "general.sql",
+ "high_watermark.sql",
+ "process.sql",
+ ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql b/src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql
new file mode 100644
index 0000000..372cbf9
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql
@@ -0,0 +1,30 @@
+--
+-- 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.
+
+CREATE PERFETTO VIEW _all_counters_per_process AS
+SELECT
+ ts,
+ LEAD(
+ ts, 1,
+ (SELECT COALESCE(end_ts, trace_end())
+ FROM process p WHERE p.upid = t.upid) + 1)
+ OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur,
+ upid,
+ value,
+ track_id,
+ name
+FROM counter c JOIN process_counter_track t
+ON t.id = c.track_id
+WHERE upid IS NOT NULL;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql b/src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql
new file mode 100644
index 0000000..b01f2df
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql
@@ -0,0 +1,67 @@
+--
+-- 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 memory.linux.process;
+INCLUDE PERFETTO MODULE counters.intervals;
+
+CREATE PERFETTO TABLE _memory_rss_high_watermark_per_process_table AS
+WITH with_rss AS (
+ SELECT
+ ts,
+ dur,
+ upid,
+ COALESCE(file_rss, 0) + COALESCE(anon_rss, 0) + COALESCE(shmem_rss, 0) AS rss
+ FROM _memory_rss_and_swap_per_process_table
+),
+high_watermark_as_counter AS (
+SELECT
+ ts,
+ MAX(rss) OVER (PARTITION BY upid ORDER BY ts) AS value,
+ -- `id` and `track_id` are hacks to use this table in
+ -- `counter_leading_intervals` macro. As `track_id` is using for looking
+ -- for duplicates, we are aliasing `upid` with it. `Id` is ignored by the macro.
+ upid AS track_id,
+ 0 AS id
+FROM with_rss
+)
+SELECT ts, dur, track_id AS upid, cast_int!(value) AS rss_high_watermark
+FROM counter_leading_intervals!(high_watermark_as_counter);
+
+-- For each process fetches the memory high watermark until or during
+-- timestamp.
+CREATE PERFETTO VIEW memory_rss_high_watermark_per_process
+(
+ -- Timestamp
+ ts INT,
+ -- Duration
+ dur INT,
+ -- Upid of the process
+ upid INT,
+ -- Pid of the process
+ pid INT,
+ -- Name of the process
+ process_name STRING,
+ -- Maximum `rss` value until now
+ rss_high_watermark INT
+) AS
+SELECT
+ ts,
+ dur,
+ upid,
+ pid,
+ name AS process_name,
+ rss_high_watermark
+FROM _memory_rss_high_watermark_per_process_table
+JOIN process USING (upid);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql b/src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql
new file mode 100644
index 0000000..4601c21
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql
@@ -0,0 +1,217 @@
+--
+-- 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 android.oom_adjuster;
+INCLUDE PERFETTO MODULE memory.linux.general;
+
+-- All memory counters tables.
+
+CREATE PERFETTO VIEW _anon_rss AS
+SELECT
+ ts,
+ dur,
+ upid,
+ value AS anon_rss_val
+FROM _all_counters_per_process
+WHERE name = 'mem.rss.anon';
+
+CREATE PERFETTO VIEW _file_rss AS
+SELECT
+ ts,
+ dur,
+ upid,
+ value AS file_rss_val
+FROM _all_counters_per_process
+WHERE name = 'mem.rss.file';
+
+CREATE PERFETTO VIEW _shmem_rss AS
+SELECT
+ ts,
+ dur,
+ upid,
+ value AS shmem_rss_val
+FROM _all_counters_per_process
+WHERE name = 'mem.rss.shmem';
+
+CREATE PERFETTO VIEW _swap AS
+SELECT
+ ts,
+ dur,
+ upid,
+ value AS swap_val
+FROM _all_counters_per_process
+WHERE name = 'mem.swap';
+
+-- Span joins
+
+CREATE VIRTUAL TABLE _anon_swap_sj
+USING SPAN_OUTER_JOIN(
+ _anon_rss PARTITIONED upid,
+ _swap PARTITIONED upid);
+
+CREATE VIRTUAL TABLE _anon_swap_file_sj
+USING SPAN_OUTER_JOIN(
+ _anon_swap_sj PARTITIONED upid,
+ _file_rss PARTITIONED upid
+);
+
+CREATE VIRTUAL TABLE _rss_swap_sj
+USING SPAN_OUTER_JOIN(
+ _anon_swap_file_sj PARTITIONED upid,
+ _shmem_rss PARTITIONED upid
+);
+
+CREATE PERFETTO TABLE _memory_rss_and_swap_per_process_table AS
+SELECT
+ ts, dur, upid,
+ cast_int!(anon_rss_val) AS anon_rss,
+ cast_int!(file_rss_val) AS file_rss,
+ cast_int!(shmem_rss_val) AS shmem_rss,
+ cast_int!(swap_val) AS swap
+FROM _rss_swap_sj;
+
+
+-- Memory metrics timeline for each process.
+CREATE PERFETTO VIEW memory_rss_and_swap_per_process(
+ -- Timestamp
+ ts INT,
+ -- Duration
+ dur INT,
+ -- Upid of the process
+ upid INT,
+ -- Pid of the process
+ pid INT,
+ -- Name of the process
+ process_name STRING,
+ -- Anon RSS counter value
+ anon_rss INT,
+ -- File RSS counter value
+ file_rss INT,
+ -- Shared memory RSS counter value
+ shmem_rss INT,
+ -- Total RSS value. Sum of `anon_rss`, `file_rss` and `shmem_rss`. Returns
+ -- value even if one of the values is NULL.
+ rss INT,
+ -- Swap counter value
+ swap INT,
+ -- Sum or `anon_rss` and `swap`. Returns value even if one of the values is
+ -- NULL.
+ anon_rss_and_swap INT,
+ -- Sum or `rss` and `swap`. Returns value even if one of the values is NULL.
+ rss_and_swap INT
+) AS
+SELECT
+ ts,
+ dur,
+ upid,
+ pid,
+ name AS process_name,
+ anon_rss,
+ file_rss,
+ shmem_rss,
+ -- We do COALESCE only on `shmem_rss` and `swap`, as it can be expected all
+ -- process start to emit anon rss and file rss events (you'll need to at
+ -- least read code and have some memory to work with) - so the NULLs are real
+ -- values. But it is possible that you will never swap or never use shmem,
+ -- so those values are expected to often be NULLs, which shouldn't propagate
+ -- into the values like `anon_and_swap` or `rss`.
+ file_rss + anon_rss + COALESCE(shmem_rss, 0) AS rss,
+ swap,
+ anon_rss + COALESCE(swap, 0) AS anon_rss_and_swap,
+ anon_rss + file_rss + COALESCE(shmem_rss, 0) + COALESCE(swap, 0) AS rss_and_swap
+FROM _memory_rss_and_swap_per_process_table
+JOIN process USING (upid);
+
+-- OOM score tables
+
+CREATE VIRTUAL TABLE _mem_ooms_sj
+USING SPAN_OUTER_JOIN(
+ android_oom_adj_intervals PARTITIONED upid,
+ _memory_rss_and_swap_per_process_table PARTITIONED upid);
+
+-- Process memory and it's OOM adjuster scores. Detects transitions, each new
+-- interval means that either the memory or OOM adjuster score of the process changed.
+CREATE PERFETTO TABLE memory_oom_score_with_rss_and_swap_per_process(
+ -- Timestamp the oom_adj score or memory of the process changed
+ ts INT,
+ -- Duration until the next oom_adj score or memory change of the process.
+ dur INT,
+ -- oom adjuster score of the process.
+ score INT,
+ -- oom adjuster bucket of the process.
+ bucket STRING,
+ -- Upid of the process having an oom_adj update.
+ upid INT,
+ -- Name of the process having an oom_adj update.
+ process_name STRING,
+ -- Pid of the process having an oom_adj update.
+ pid INT,
+ -- Slice of the latest oom_adj update in the system_server. Alias of
+ -- `slice.id`.
+ oom_adj_id INT,
+ -- Timestamp of the latest oom_adj update in the system_server.
+ oom_adj_ts INT,
+ -- Duration of the latest oom_adj update in the system_server.
+ oom_adj_dur INT,
+ -- Track of the latest oom_adj update in the system_server. Alias of
+ -- `track.id`.
+ oom_adj_track_id INT,
+ -- Thread name of the latest oom_adj update in the system_server.
+ oom_adj_thread_name STRING,
+ -- Reason for the latest oom_adj update in the system_server.
+ oom_adj_reason STRING,
+ -- Trigger for the latest oom_adj update in the system_server.
+ oom_adj_trigger STRING,
+ -- Anon RSS counter value
+ anon_rss INT,
+ -- File RSS counter value
+ file_rss INT,
+ -- Shared memory RSS counter value
+ shmem_rss INT,
+ -- Total RSS value. Sum of `anon_rss`, `file_rss` and `shmem_rss`. Returns
+ -- value even if one of the values is NULL.
+ rss INT,
+ -- Swap counter value
+ swap INT,
+ -- Sum or `anon_rss` and `swap`. Returns value even if one of the values is
+ -- NULL.
+ anon_rss_and_swap INT,
+ -- Sum or `rss` and `swap`. Returns value even if one of the values is NULL.
+ rss_and_swap INT
+) AS
+SELECT
+ ts,
+ dur,
+ score,
+ bucket,
+ upid,
+ process_name,
+ pid,
+ oom_adj_id,
+ oom_adj_ts,
+ oom_adj_dur,
+ oom_adj_track_id,
+ oom_adj_thread_name,
+ oom_adj_reason,
+ oom_adj_trigger,
+ anon_rss,
+ file_rss,
+ shmem_rss,
+ file_rss + anon_rss + COALESCE(shmem_rss, 0) AS rss,
+ swap,
+ anon_rss + COALESCE(swap, 0) AS anon_rss_and_swap,
+ anon_rss + file_rss + COALESCE(shmem_rss, 0) + COALESCE(swap, 0) AS rss_and_swap
+FROM _mem_ooms_sj
+JOIN process USING (upid);
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
index 89085f3..74f73a5 100644
--- 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
@@ -213,7 +213,7 @@
self_thread_state_id AS id,
ts,
dur,
- utid,
+ critical_path_utid AS utid,
0 AS stack_depth,
'thread_state: ' || self_state AS name,
'thread_state' AS table_name,
@@ -225,7 +225,7 @@
self_thread_state_id AS id,
ts,
dur,
- utid,
+ critical_path_utid AS utid,
1 AS stack_depth,
IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || self_function) AS name,
'thread_state' AS table_name,
@@ -237,7 +237,7 @@
self_thread_state_id AS id,
ts,
dur,
- utid,
+ critical_path_utid AS utid,
2 AS stack_depth,
IIF(self_state GLOB 'R*', NULL, 'io_wait: ' || self_io_wait) AS name,
'thread_state' AS table_name,
@@ -281,7 +281,7 @@
anc.id,
slice.ts,
slice.dur,
- slice.utid,
+ critical_path_utid AS utid,
anc.depth + 5 AS stack_depth,
IIF($enable_self_slice, anc.name, NULL) AS name,
'slice' AS table_name,
@@ -294,7 +294,7 @@
self_slice_id AS id,
ts,
dur,
- utid,
+ critical_path_utid AS utid,
self_slice_depth + 5 AS stack_depth,
IIF($enable_self_slice, self_slice_name, NULL) AS name,
'slice' AS table_name,
diff --git a/src/trace_processor/rpc/wasm_bridge.cc b/src/trace_processor/rpc/wasm_bridge.cc
index ca27abc..2473e4c 100644
--- a/src/trace_processor/rpc/wasm_bridge.cc
+++ b/src/trace_processor/rpc/wasm_bridge.cc
@@ -15,8 +15,13 @@
*/
#include <emscripten/emscripten.h>
-#include <cstdint>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <new>
+
+#include "perfetto/base/compiler.h"
#include "src/trace_processor/rpc/rpc.h"
namespace perfetto::trace_processor {
@@ -29,6 +34,12 @@
// The buffer used to pass the request arguments. The caller (JS) decides how
// big this buffer should be in the Initialize() call.
uint8_t* g_req_buf;
+
+PERFETTO_NO_INLINE void OutOfMemoryHandler() {
+ fprintf(stderr, "\nCannot enlarge memory\n");
+ abort();
+}
+
} // namespace
// +---------------------------------------------------------------------------+
@@ -41,6 +52,13 @@
trace_processor_rpc_init(RpcResponseFn* RpcResponseFn, uint32_t);
uint8_t* trace_processor_rpc_init(RpcResponseFn* resp_function,
uint32_t req_buffer_size) {
+ // Usually OOMs manifest as a failure in dlmalloc() -> sbrk() ->
+ //_emscripten_resize_heap() which aborts itself. However in some rare cases
+ // sbrk() can fail outside of _emscripten_resize_heap and just return null.
+ // When that happens, just abort with the same message that
+ // _emscripten_resize_heap uses, so error_dialog.ts shows a OOM message.
+ std::set_new_handler(&OutOfMemoryHandler);
+
g_trace_processor_rpc = new Rpc();
// |resp_function| is a JS-bound function passed by wasm_bridge.ts. It will
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index 6f0ded8..9ccea77 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -15,21 +15,28 @@
*/
#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
#include <memory>
#include <utility>
#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
#include "src/trace_processor/importers/perf/record.h"
#include "src/trace_processor/sorter/trace_sorter.h"
-#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/sorter/trace_token_buffer.h"
+#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/bump_allocator.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
TraceSorter::TraceSorter(TraceProcessorContext* context,
SortingMode sorting_mode)
@@ -117,10 +124,12 @@
auto& queue = sorter_data.queues[i];
if (queue.events_.empty())
continue;
- all_queues_empty = false;
-
PERFETTO_DCHECK(queue.max_ts_ <= append_max_ts_);
- if (queue.min_ts_ < min_queue_ts[0]) {
+
+ // Checking for |all_queues_empty| is necessary here as in fuzzer cases
+ // we can end up with |int64::max()| as the value here.
+ // See https://crbug.com/oss-fuzz/69164 for an example.
+ if (all_queues_empty || queue.min_ts_ < min_queue_ts[0]) {
min_queue_ts[1] = min_queue_ts[0];
min_queue_ts[0] = queue.min_ts_;
min_queue_idx = i;
@@ -128,6 +137,7 @@
} else if (queue.min_ts_ < min_queue_ts[1]) {
min_queue_ts[1] = queue.min_ts_;
}
+ all_queues_empty = false;
}
}
if (all_queues_empty)
@@ -276,10 +286,9 @@
const TimestampedEvent& event) {
TraceTokenBuffer::Id id = GetTokenBufferId(event);
switch (static_cast<TimestampedEvent::Type>(event.event_type)) {
- case TimestampedEvent::Type::kPerfRecord:
- base::ignore_result(token_buffer_.Extract<perf_importer::Record>(id));
- return;
case TimestampedEvent::Type::kTracePacket:
+ case TimestampedEvent::Type::kFtraceEvent:
+ case TimestampedEvent::Type::kEtwEvent:
base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
return;
case TimestampedEvent::Type::kTrackEvent:
@@ -300,11 +309,8 @@
case TimestampedEvent::Type::kInlineSchedWaking:
base::ignore_result(token_buffer_.Extract<InlineSchedWaking>(id));
return;
- case TimestampedEvent::Type::kFtraceEvent:
- base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
- return;
- case TimestampedEvent::Type::kEtwEvent:
- base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
+ case TimestampedEvent::Type::kPerfRecord:
+ base::ignore_result(token_buffer_.Extract<perf_importer::Record>(id));
return;
}
PERFETTO_FATAL("For GCC");
@@ -343,5 +349,4 @@
}
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index cf747f1..dd81920 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -18,16 +18,23 @@
#define SRC_TRACE_PROCESSOR_SORTER_TRACE_SORTER_H_
#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
#include <memory>
#include <optional>
+#include <string>
+#include <tuple>
+#include <type_traits>
#include <utility>
#include <vector>
+#include "perfetto/base/logging.h"
#include "perfetto/ext/base/circular_queue.h"
-#include "perfetto/ext/base/utils.h"
#include "perfetto/public/compiler.h"
-#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
#include "src/trace_processor/importers/perf/record.h"
@@ -37,8 +44,7 @@
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/bump_allocator.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// This class takes care of sorting events parsed from the trace stream in
// arbitrary order and pushing them to the next pipeline stages (parsing) in
@@ -219,7 +225,7 @@
SortAndExtractEventsUntilAllocId(end_id);
for (auto& sorter_data : sorter_data_by_machine_) {
for (const auto& queue : sorter_data.queues) {
- PERFETTO_DCHECK(queue.events_.empty());
+ PERFETTO_CHECK(queue.events_.empty());
}
sorter_data.queues.clear();
}
@@ -295,13 +301,13 @@
static_assert(sizeof(TimestampedEvent) == 16,
"TimestampedEvent must be equal to 16 bytes");
- static_assert(std::is_trivially_copyable<TimestampedEvent>::value,
+ static_assert(std::is_trivially_copyable_v<TimestampedEvent>,
"TimestampedEvent must be trivially copyable");
- static_assert(std::is_trivially_move_assignable<TimestampedEvent>::value,
+ static_assert(std::is_trivially_move_assignable_v<TimestampedEvent>,
"TimestampedEvent must be trivially move assignable");
- static_assert(std::is_trivially_move_constructible<TimestampedEvent>::value,
+ static_assert(std::is_trivially_move_constructible_v<TimestampedEvent>,
"TimestampedEvent must be trivially move constructible");
- static_assert(std::is_nothrow_swappable<TimestampedEvent>::value,
+ static_assert(std::is_nothrow_swappable_v<TimestampedEvent>,
"TimestampedEvent must be trivially swappable");
struct Queue {
@@ -358,7 +364,7 @@
auto* queues = &sorter_data_by_machine_[0].queues;
// Find the TraceSorterData instance when |machine_id| is not nullopt.
- if (PERFETTO_UNLIKELY(!!machine_id)) {
+ if (PERFETTO_UNLIKELY(machine_id.has_value())) {
auto it = std::find_if(sorter_data_by_machine_.begin() + 1,
sorter_data_by_machine_.end(),
[machine_id](const TraceSorterData& item) {
@@ -401,7 +407,7 @@
const TimestampedEvent&);
void ExtractAndDiscardTokenizedObject(const TimestampedEvent& event);
- TraceTokenBuffer::Id GetTokenBufferId(const TimestampedEvent& event) {
+ static TraceTokenBuffer::Id GetTokenBufferId(const TimestampedEvent& event) {
return TraceTokenBuffer::Id{event.alloc_id()};
}
@@ -448,7 +454,6 @@
int64_t latest_pushed_event_ts_ = std::numeric_limits<int64_t>::min();
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_SORTER_TRACE_SORTER_H_
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 90c8176..1deca2e 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -135,6 +135,7 @@
C('frame_id', CppOptional(CppUint32())),
C('submission_id', CppOptional(CppUint32())),
C('hw_queue_id', CppOptional(CppInt64())),
+ C('upid', CppOptional(CppUint32())),
C('render_subpasses', CppString()),
],
parent=SLICE_TABLE,
@@ -142,17 +143,33 @@
doc='''''',
group='Slice',
columns={
- 'context_id': '''''',
- 'render_target': '''''',
- 'render_target_name': '''''',
- 'render_pass': '''''',
- 'render_pass_name': '''''',
- 'command_buffer': '''''',
- 'command_buffer_name': '''''',
- 'frame_id': '''''',
- 'submission_id': '''''',
- 'hw_queue_id': '''''',
- 'render_subpasses': ''''''
+ 'context_id':
+ '''''',
+ 'render_target':
+ '''''',
+ 'render_target_name':
+ '''''',
+ 'render_pass':
+ '''''',
+ 'render_pass_name':
+ '''''',
+ 'command_buffer':
+ '''''',
+ 'command_buffer_name':
+ '''''',
+ 'frame_id':
+ '''''',
+ 'submission_id':
+ '''''',
+ 'hw_queue_id':
+ '''''',
+ 'upid':
+ '''
+ Unique process id of the app that generates this gpu render
+ stage event.
+ ''',
+ 'render_subpasses':
+ ''''''
}))
GRAPHICS_FRAME_SLICE_TABLE = Table(
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index d09c8a7..731f4e3 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -14,17 +14,23 @@
* limitations under the License.
*/
-#include <algorithm>
+#include <cstdint>
#include <cstdio>
-#include <map>
-#include <optional>
+#include <cstring>
+#include <memory>
#include <random>
#include <string>
+#include <utility>
+#include <vector>
+#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/iterator.h"
+#include "perfetto/trace_processor/status.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "protos/perfetto/common/descriptor.pbzero.h"
#include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
@@ -32,11 +38,12 @@
#include "src/base/test/utils.h"
#include "test/gtest_and_gmock.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
-constexpr size_t kMaxChunkSize = 4 * 1024 * 1024;
+using testing::HasSubstr;
+
+constexpr size_t kMaxChunkSize = 4ul * 1024 * 1024;
TEST(TraceProcessorCustomConfigTest, SkipInternalMetricsMatchingMountPath) {
auto config = Config();
@@ -97,12 +104,13 @@
: processor_(TraceProcessor::CreateInstance(Config())) {}
protected:
- util::Status LoadTrace(const char* name,
+ base::Status LoadTrace(const char* name,
size_t min_chunk_size = 512,
size_t max_chunk_size = kMaxChunkSize) {
EXPECT_LE(min_chunk_size, max_chunk_size);
- base::ScopedFstream f(fopen(
- base::GetTestDataPath(std::string("test/data/") + name).c_str(), "rb"));
+ base::ScopedFstream f(
+ fopen(base::GetTestDataPath(std::string("test/data/") + name).c_str(),
+ "rbe"));
std::minstd_rand0 rnd_engine(0);
std::uniform_int_distribution<size_t> dist(min_chunk_size, max_chunk_size);
while (!feof(*f)) {
@@ -118,7 +126,7 @@
}
Iterator Query(const std::string& query) {
- return processor_->ExecuteQuery(query.c_str());
+ return processor_->ExecuteQuery(query);
}
TraceProcessor* Processor() { return processor_.get(); }
@@ -280,7 +288,7 @@
TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedExtension) {
std::string metric_output;
- util::Status status = Processor()->ComputeMetricText(
+ base::Status status = Processor()->ComputeMetricText(
std::vector<std::string>{"test_chrome_metric"},
TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
ASSERT_TRUE(status.ok());
@@ -293,7 +301,7 @@
TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedNoExtension) {
std::string metric_output;
- util::Status status = Processor()->ComputeMetricText(
+ base::Status status = Processor()->ComputeMetricText(
std::vector<std::string>{"trace_metadata"},
TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
ASSERT_TRUE(status.ok());
@@ -321,21 +329,21 @@
}
TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14762) {
- ASSERT_TRUE(LoadTrace("clusterfuzz_14762", 4096 * 1024).ok());
+ ASSERT_TRUE(LoadTrace("clusterfuzz_14762", 4096ul * 1024).ok());
auto it = Query("select sum(value) from stats where severity = 'error';");
ASSERT_TRUE(it.Next());
ASSERT_GT(it.Get(0).long_value, 0);
}
TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14767) {
- ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096 * 1024).ok());
+ ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096ul * 1024).ok());
auto it = Query("select sum(value) from stats where severity = 'error';");
ASSERT_TRUE(it.Next());
ASSERT_GT(it.Get(0).long_value, 0);
}
TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14799) {
- ASSERT_TRUE(LoadTrace("clusterfuzz_14799", 4096 * 1024).ok());
+ ASSERT_TRUE(LoadTrace("clusterfuzz_14799", 4096ul * 1024).ok());
auto it = Query("select sum(value) from stats where severity = 'error';");
ASSERT_TRUE(it.Next());
ASSERT_GT(it.Get(0).long_value, 0);
@@ -793,6 +801,15 @@
ASSERT_TRUE(it.Status().ok());
}
+TEST_F(TraceProcessorIntegrationTest, CreateTableDuplicateNames) {
+ auto it = Query(
+ "create perfetto table foo select 1 as duplicate_a, 2 as duplicate_a, 3 "
+ "as duplicate_b, 4 as duplicate_b");
+ ASSERT_FALSE(it.Next());
+ ASSERT_FALSE(it.Status().ok());
+ ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_a"));
+ ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_b"));
+}
+
} // namespace
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index fb9b294..117a737 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -59,6 +59,7 @@
#include "src/trace_processor/importers/proto/additional_modules.h"
#include "src/trace_processor/importers/proto/content_analyzer.h"
#include "src/trace_processor/importers/systrace/systrace_trace_parser.h"
+#include "src/trace_processor/importers/zip/zip_trace_reader.h"
#include "src/trace_processor/iterator_impl.h"
#include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h"
#include "src/trace_processor/metrics/all_webview_metrics.descriptor.h"
@@ -121,6 +122,7 @@
#include "src/trace_processor/util/regex.h"
#include "src/trace_processor/util/sql_modules.h"
#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
@@ -308,8 +310,8 @@
return "ctrace";
case kNinjaLogTraceType:
return "ninja_log";
- case kAndroidBugreportTraceType:
- return "android_bugreport";
+ case kZipFile:
+ return "zip";
case kPerfDataTraceType:
return "perf_data";
}
@@ -362,8 +364,7 @@
kGzipTraceType);
context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
kCtraceTraceType);
- context_.reader_registry->RegisterTraceReader<AndroidBugreportParser>(
- kAndroidBugreportTraceType);
+ context_.reader_registry->RegisterTraceReader<ZipTraceReader>(kZipFile);
}
if (json::IsJsonSupported()) {
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 243e3da..09d5386 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -37,6 +37,7 @@
#include "src/trace_processor/importers/common/slice_translation_table.h"
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/perf/dso_tracker.h"
#include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h"
#include "src/trace_processor/importers/proto/default_modules.h"
#include "src/trace_processor/importers/proto/packet_analyzer.h"
@@ -106,6 +107,8 @@
return;
Flush();
context_.chunk_reader->NotifyEndOfFile();
+ // NotifyEndOfFile might have pushed packets to the sorter.
+ Flush();
for (std::unique_ptr<ProtoImporterModule>& module : context_.modules) {
module->NotifyEndOfFile();
}
@@ -116,6 +119,9 @@
context_.slice_tracker->FlushPendingSlices();
context_.args_tracker->Flush();
context_.process_tracker->NotifyEndOfFile();
+ if (context_.perf_dso_tracker) {
+ perf_importer::DsoTracker::GetOrCreate(&context_).SymbolizeFrames();
+ }
}
void TraceProcessorStorageImpl::DestroyContext() {
diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc
index 09c5c7b..065f6f6 100644
--- a/src/trace_processor/trace_reader_registry.cc
+++ b/src/trace_processor/trace_reader_registry.cc
@@ -30,7 +30,7 @@
switch (type) {
case kGzipTraceType:
case kCtraceTraceType:
- case kAndroidBugreportTraceType:
+ case kZipFile:
return true;
case kNinjaLogTraceType:
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index fd72005..cbf8993 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -143,6 +143,7 @@
std::unique_ptr<Destructible> ftrace_sched_tracker; // FtraceSchedEventTracker
std::unique_ptr<Destructible> v8_tracker; // V8Tracker
std::unique_ptr<Destructible> jit_tracker; // JitTracker
+ std::unique_ptr<Destructible> perf_dso_tracker; // DsoTracker
// clang-format on
std::unique_ptr<ProtoTraceParser> proto_trace_parser;
diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc
index d265b90..ee5a56a 100644
--- a/src/trace_processor/util/trace_type.cc
+++ b/src/trace_processor/util/trace_type.cc
@@ -58,8 +58,8 @@
return "gzip trace";
case kCtraceTraceType:
return "ctrace trace";
- case kAndroidBugreportTraceType:
- return "Android Bugreport";
+ case kZipFile:
+ return "ZIP file";
case kPerfDataTraceType:
return "perf data";
case kUnknownTraceType:
@@ -124,12 +124,8 @@
if (base::StartsWith(start, "\x0a"))
return kProtoTraceType;
- // Android bugreport.zip
- // TODO(primiano). For now we assume any .zip file is a bugreport. In future,
- // if we want to support different trace formats based on a .zip arachive we
- // will need an extra layer similar to what we did kGzipTraceType.
if (base::StartsWith(start, "PK\x03\x04"))
- return kAndroidBugreportTraceType;
+ return kZipFile;
return kUnknownTraceType;
}
diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h
index fec7a74..2d40d83 100644
--- a/src/trace_processor/util/trace_type.h
+++ b/src/trace_processor/util/trace_type.h
@@ -31,7 +31,7 @@
kGzipTraceType,
kCtraceTraceType,
kNinjaLogTraceType,
- kAndroidBugreportTraceType,
+ kZipFile,
kPerfDataTraceType,
};
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 45d3bdc..ee876b0 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -59,14 +59,14 @@
"prune_package_list.h",
"redact_ftrace_event.cc",
"redact_ftrace_event.h",
- "redact_process_free.cc",
- "redact_process_free.h",
"redact_sched_switch.cc",
"redact_sched_switch.h",
"redact_task_newtask.cc",
"redact_task_newtask.h",
"remap_scheduling_events.cc",
"remap_scheduling_events.h",
+ "remove_process_free_comm.cc",
+ "remove_process_free_comm.h",
"scrub_ftrace_events.cc",
"scrub_ftrace_events.h",
"scrub_process_stats.cc",
@@ -81,6 +81,8 @@
"trace_redaction_framework.h",
"trace_redactor.cc",
"trace_redactor.h",
+ "verify_integrity.cc",
+ "verify_integrity.h",
]
deps = [
"../../gn:default_deps",
@@ -141,10 +143,10 @@
"process_thread_timeline_unittest.cc",
"proto_util_unittest.cc",
"prune_package_list_unittest.cc",
- "redact_process_free_unittest.cc",
"redact_sched_switch_unittest.cc",
"redact_task_newtask_unittest.cc",
"remap_scheduling_events_unittest.cc",
+ "remove_process_free_comm_unittest.cc",
"suspend_resume_unittest.cc",
]
deps = [
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 7014c0d..798d1a0 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -28,10 +28,10 @@
#include "src/trace_redaction/populate_allow_lists.h"
#include "src/trace_redaction/prune_package_list.h"
#include "src/trace_redaction/redact_ftrace_event.h"
-#include "src/trace_redaction/redact_process_free.h"
#include "src/trace_redaction/redact_sched_switch.h"
#include "src/trace_redaction/redact_task_newtask.h"
#include "src/trace_redaction/remap_scheduling_events.h"
+#include "src/trace_redaction/remove_process_free_comm.h"
#include "src/trace_redaction/scrub_ftrace_events.h"
#include "src/trace_redaction/scrub_process_stats.h"
#include "src/trace_redaction/scrub_process_trees.h"
@@ -39,6 +39,7 @@
#include "src/trace_redaction/suspend_resume.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "src/trace_redaction/trace_redactor.h"
+#include "src/trace_redaction/verify_integrity.h"
namespace perfetto::trace_redaction {
@@ -48,6 +49,12 @@
std::string_view package_name) {
TraceRedactor redactor;
+ // VerifyIntegrity breaks the CollectPrimitive pattern. Instead of writing to
+ // the context, its job is to read trace packets and return errors if any
+ // packet does not look "correct". This primitive is added first in an effort
+ // to detect and react to bad input before other collectors run.
+ redactor.emplace_collect<VerifyIntegrity>();
+
// Add all collectors.
redactor.emplace_collect<FindPackageUid>();
redactor.emplace_collect<CollectTimelineEvents>();
@@ -84,9 +91,17 @@
auto* redact_ftrace_events = redactor.emplace_transform<RedactFtraceEvent>();
redact_ftrace_events
- ->emplace_back<RedactTaskNewTask::kFieldId, RedactTaskNewTask>();
- redact_ftrace_events
- ->emplace_back<RedactProcessFree::kFieldId, RedactProcessFree>();
+ ->emplace_back<RemoveProcessFreeComm::kFieldId, RemoveProcessFreeComm>();
+
+ // By default, the comm value is cleared. However, when thread merging is
+ // enabled (kTaskNewtaskFieldNumber + ThreadMergeDropField), the event is
+ // dropped, meaning that this primitive was effectivly a no-op. This primitive
+ // remains so that removing thread merging won't leak thread names via new
+ // task events.
+ auto* redact_new_task =
+ redact_ftrace_events
+ ->emplace_back<RedactTaskNewTask::kFieldId, RedactTaskNewTask>();
+ redact_new_task->emplace_back<ClearComms>();
// This set of transformations will change pids. This will break the
// connections between pids and the timeline (the synth threads are not in the
diff --git a/src/trace_redaction/redact_ftrace_event.h b/src/trace_redaction/redact_ftrace_event.h
index 9de6cc6..68aff05 100644
--- a/src/trace_redaction/redact_ftrace_event.h
+++ b/src/trace_redaction/redact_ftrace_event.h
@@ -57,8 +57,12 @@
// Add a new redaction. T must extend FtraceEventRedaction. This relies on the
// honor system; no more than one redaction can be mapped to a field.
template <uint32_t field_id, class T>
- void emplace_back() {
- redactions_.Insert(field_id, std::make_unique<T>());
+ T* emplace_back() {
+ auto ptr = std::make_unique<T>();
+ T* raw_ptr = ptr.get();
+
+ redactions_.Insert(field_id, std::move(ptr));
+ return raw_ptr;
}
private:
diff --git a/src/trace_redaction/redact_process_free.cc b/src/trace_redaction/redact_process_free.cc
deleted file mode 100644
index 8f5a83e..0000000
--- a/src/trace_redaction/redact_process_free.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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_process_free.h"
-
-#include "src/trace_redaction/proto_util.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"
-
-namespace perfetto::trace_redaction {
-
-// Redact sched_process_free events.
-//
-// event {
-// timestamp: 6702094703928940
-// pid: 10
-// sched_process_free {
-// comm: "sh"
-// pid: 7973
-// prio: 120
-// }
-// }
-//
-// In the above message, it should be noted that "event.pid" will not be
-// equal to "event.sched_process_free.pid".
-//
-// The timeline treats "start" as inclusive and "end" as exclusive. This means
-// no pid will connect to the target package at a process free event. Because
-// of this, the timeline is not needed.
-base::Status RedactProcessFree::Redact(
- const Context&,
- const protos::pbzero::FtraceEventBundle::Decoder&,
- protozero::ProtoDecoder& event,
- protos::pbzero::FtraceEvent* event_message) const {
- auto sched_process_free = event.FindField(
- protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber);
- if (!sched_process_free.valid()) {
- return base::ErrStatus(
- "RedactProcessFree: was used for unsupported field type");
- }
-
- // SchedProcessFreeFtraceEvent
- protozero::ProtoDecoder process_free_decoder(sched_process_free.as_bytes());
-
- auto* process_free_message = event_message->set_sched_process_free();
-
- // Replace the comm with an empty string instead of dropping the comm field.
- // The perfetto UI doesn't render things correctly if comm values are missing.
- for (auto field = process_free_decoder.ReadField(); field.valid();
- field = process_free_decoder.ReadField()) {
- if (field.id() ==
- protos::pbzero::SchedProcessFreeFtraceEvent::kCommFieldNumber) {
- process_free_message->set_comm("");
- } else {
- proto_util::AppendField(field, process_free_message);
- }
- }
-
- return base::OkStatus();
-}
-
-} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_switch.cc b/src/trace_redaction/redact_sched_switch.cc
index 87412c1..d2ce751 100644
--- a/src/trace_redaction/redact_sched_switch.cc
+++ b/src/trace_redaction/redact_sched_switch.cc
@@ -27,14 +27,62 @@
namespace perfetto::trace_redaction {
namespace {
-// TODO(vaage): While simple, this function saves us from declaring the sample
-// lambda each time we use the has_fields pattern. Once its usage increases, and
-// its value is obvious, remove this comment.
bool IsTrue(bool value) {
return value;
}
+
+// Copy a field from 'decoder' to 'message' if the field can be found. Returns
+// false if the field cannot be found.
+bool Passthrough(protozero::ProtoDecoder& decoder,
+ uint32_t field_id,
+ protozero::Message* message) {
+ auto field = decoder.FindField(field_id);
+
+ if (field.valid()) {
+ proto_util::AppendField(field, message);
+ return true;
+ }
+
+ return false;
+}
} // namespace
+int64_t InternTable::Push(const char* data, size_t size) {
+ std::string_view outer(data, size);
+
+ for (size_t i = 0; i < interned_comms_.size(); ++i) {
+ auto view = interned_comms_[i];
+
+ if (view == outer) {
+ return static_cast<int64_t>(i);
+ }
+ }
+
+ // No room for the new string, reject the request.
+ if (comms_length_ + size > comms_.size()) {
+ return -1;
+ }
+
+ auto* head = comms_.data() + comms_length_;
+
+ // Important note, the null byte is not copied.
+ memcpy(head, data, size);
+ comms_length_ += size;
+
+ size_t id = interned_comms_.size();
+ interned_comms_.emplace_back(head, size);
+
+ return static_cast<int64_t>(id);
+}
+
+std::string_view InternTable::Find(size_t index) const {
+ if (index < interned_comms_.size()) {
+ return interned_comms_[index];
+ }
+
+ return {};
+}
+
// Redact sched switch trace events in an ftrace event bundle:
//
// event {
@@ -58,7 +106,7 @@
// 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.
-SchedSwitchTransform::~SchedSwitchTransform() = default;
+RedactSchedSwitchHarness::Modifier::~Modifier() = default;
base::Status RedactSchedSwitchHarness::Transform(const Context& context,
std::string* packet) const {
@@ -106,9 +154,10 @@
if (field.id() ==
protos::pbzero::FtraceEventBundle::kCompactSchedFieldNumber) {
- // TODO(vaage): Replace this with logic specific to the comp sched data
- // type.
- proto_util::AppendField(field, message);
+ protos::pbzero::FtraceEventBundle::CompactSched::Decoder comp_sched(
+ field.as_bytes());
+ RETURN_IF_ERROR(TransformCompSched(context, cpu.as_int32(), comp_sched,
+ message->set_compact_sched()));
continue;
}
@@ -139,14 +188,29 @@
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
- if (field.id() == protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber) {
- protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
- field.as_bytes());
- RETURN_IF_ERROR(TransformFtraceEventSchedSwitch(
- context, ts.as_uint64(), cpu, sched_switch, &scratch_str,
- message->set_sched_switch()));
- } else {
- proto_util::AppendField(field, message);
+ switch (field.id()) {
+ case protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber: {
+ protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
+ field.as_bytes());
+ RETURN_IF_ERROR(TransformFtraceEventSchedSwitch(
+ context, ts.as_uint64(), cpu, sched_switch, &scratch_str,
+ message->set_sched_switch()));
+ break;
+ }
+
+ case protos::pbzero::FtraceEvent::kSchedWakingFieldNumber: {
+ protos::pbzero::SchedWakingFtraceEvent::Decoder sched_waking(
+ field.as_bytes());
+ RETURN_IF_ERROR(TransformFtraceEventSchedWaking(
+ context, ts.as_uint64(), cpu, sched_waking, &scratch_str,
+ message->set_sched_waking()));
+ break;
+ }
+
+ default: {
+ proto_util::AppendField(field, message);
+ break;
+ }
}
}
@@ -160,6 +224,10 @@
protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
std::string* scratch_str,
protos::pbzero::SchedSwitchFtraceEvent* message) const {
+ PERFETTO_DCHECK(modifier_);
+ PERFETTO_DCHECK(scratch_str);
+ PERFETTO_DCHECK(message);
+
auto has_fields = {
sched_switch.has_prev_comm(), sched_switch.has_prev_pid(),
sched_switch.has_prev_prio(), sched_switch.has_prev_state(),
@@ -184,10 +252,7 @@
scratch_str->assign(prev_comm.data, prev_comm.size);
- for (const auto& transform : transforms_) {
- RETURN_IF_ERROR(
- transform->Transform(context, ts, cpu, &prev_pid, scratch_str));
- }
+ RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &prev_pid, scratch_str));
message->set_prev_comm(*scratch_str); // FieldNumber = 1
message->set_prev_pid(prev_pid); // FieldNumber = 2
@@ -196,10 +261,7 @@
scratch_str->assign(next_comm.data, next_comm.size);
- for (const auto& transform : transforms_) {
- RETURN_IF_ERROR(
- transform->Transform(context, ts, cpu, &next_pid, scratch_str));
- }
+ RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &next_pid, scratch_str));
message->set_next_comm(*scratch_str); // FieldNumber = 5
message->set_next_pid(next_pid); // FieldNumber = 6
@@ -208,13 +270,225 @@
return base::OkStatus();
}
+base::Status RedactSchedSwitchHarness::TransformFtraceEventSchedWaking(
+ const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking,
+ std::string* scratch_str,
+ protos::pbzero::SchedWakingFtraceEvent* message) const {
+ PERFETTO_DCHECK(modifier_);
+ PERFETTO_DCHECK(scratch_str);
+ PERFETTO_DCHECK(message);
+
+ auto has_fields = {sched_waking.has_comm(), sched_waking.has_pid(),
+ sched_waking.has_prio(), sched_waking.has_success(),
+ sched_waking.has_target_cpu()};
+
+ if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: missing required SchedWakingFtraceEvent "
+ "field.");
+ }
+
+ auto pid = sched_waking.pid();
+ auto comm = sched_waking.comm();
+
+ // There are 5 values in a sched switch message. Since 2 of the 5 can be
+ // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
+ // order.
+
+ scratch_str->assign(comm.data, comm.size);
+
+ RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &pid, scratch_str));
+
+ message->set_comm(*scratch_str); // FieldNumber = 1
+ message->set_pid(pid); // FieldNumber = 2
+ message->set_prio(sched_waking.prio()); // FieldNumber = 3
+ message->set_success(sched_waking.success()); // FieldNumber = 4
+ message->set_target_cpu(sched_waking.target_cpu()); // FieldNumber = 5
+
+ return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformCompSched(
+ const Context& context,
+ int32_t cpu,
+ protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+ protos::pbzero::FtraceEventBundle::CompactSched* message) const {
+ auto has_switch_fields = {
+ comp_sched.has_switch_timestamp(),
+ comp_sched.has_switch_prev_state(),
+ comp_sched.has_switch_next_pid(),
+ comp_sched.has_switch_next_prio(),
+ comp_sched.has_switch_next_comm_index(),
+ };
+
+ auto has_waking_fields = {
+ comp_sched.has_waking_timestamp(), comp_sched.has_waking_pid(),
+ comp_sched.has_waking_target_cpu(), comp_sched.has_waking_prio(),
+ comp_sched.has_waking_comm_index(), comp_sched.has_waking_common_flags(),
+ };
+
+ // Populate the intern table once; it will be used by both sched and waking.
+ InternTable intern_table;
+
+ for (auto it = comp_sched.intern_table(); it; ++it) {
+ auto chars = it->as_string();
+ auto index = intern_table.Push(chars.data, chars.size);
+
+ if (index < 0) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: failed to insert string into intern "
+ "table.");
+ }
+ }
+
+ if (std::any_of(has_switch_fields.begin(), has_switch_fields.end(), IsTrue)) {
+ RETURN_IF_ERROR(TransformCompSchedSwitch(context, cpu, comp_sched,
+ &intern_table, message));
+ }
+
+ if (std::any_of(has_waking_fields.begin(), has_waking_fields.end(), IsTrue)) {
+ // TODO(vaage): Create and call TransformCompSchedWaking().
+ }
+
+ // IMPORTANT: The intern table can only be added after switch and waking
+ // because switch and/or waking can/will modify the intern table.
+ for (auto view : intern_table.values()) {
+ message->add_intern_table(view.data(), view.size());
+ }
+
+ return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformCompSchedSwitch(
+ const Context& context,
+ int32_t cpu,
+ protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+ InternTable* intern_table,
+ protos::pbzero::FtraceEventBundle::CompactSched* message) const {
+ PERFETTO_DCHECK(modifier_);
+ PERFETTO_DCHECK(message);
+
+ auto has_fields = {
+ comp_sched.has_intern_table(),
+ comp_sched.has_switch_timestamp(),
+ comp_sched.has_switch_prev_state(),
+ comp_sched.has_switch_next_pid(),
+ comp_sched.has_switch_next_prio(),
+ comp_sched.has_switch_next_comm_index(),
+ };
+
+ if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: missing required "
+ "FtraceEventBundle::CompactSched switch field.");
+ }
+
+ std::array<bool, 3> parse_errors = {false, false, false};
+
+ auto it_ts = comp_sched.switch_timestamp(&parse_errors.at(0));
+ auto it_pid = comp_sched.switch_next_pid(&parse_errors.at(1));
+ auto it_comm = comp_sched.switch_next_comm_index(&parse_errors.at(2));
+
+ if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: failed to parse CompactSched.");
+ }
+
+ std::string scratch_str;
+
+ protozero::PackedVarInt packed_comm;
+ protozero::PackedVarInt packed_pid;
+
+ // The first it_ts value is an absolute value, all other values are delta
+ // values.
+ uint64_t ts = 0;
+
+ while (it_ts && it_pid && it_comm) {
+ ts += *it_ts;
+
+ auto pid = *it_pid;
+
+ auto comm_index = *it_comm;
+ auto comm = intern_table->Find(comm_index);
+
+ scratch_str.assign(comm);
+
+ RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &pid, &scratch_str));
+
+ auto found = intern_table->Push(scratch_str.data(), scratch_str.size());
+
+ if (found < 0) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: failed to insert string into intern "
+ "table.");
+ }
+
+ packed_comm.Append(found);
+ packed_pid.Append(pid);
+
+ ++it_ts;
+ ++it_pid;
+ ++it_comm;
+ }
+
+ if (it_ts || it_pid || it_comm) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: uneven associative arrays in "
+ "FtraceEventBundle::CompactSched (switch).");
+ }
+
+ message->set_switch_next_pid(packed_pid);
+ message->set_switch_next_comm_index(packed_comm);
+
+ // There's a lot of data in a compact sched message. Most of it is packed data
+ // and most of the data is not going to change. To avoid unpacking, doing
+ // nothing, and then packing... cheat. Find the fields and pass them as opaque
+ // blobs.
+ //
+ // kInternTableFieldNumber: The intern table will be modified by both
+ // switch events and waking events. It will
+ // be written elsewhere.
+ //
+ // kSwitchNextPidFieldNumber: The switch pid will change during thread
+ // merging.
+ //
+ // kSwitchNextCommIndexFieldNumber: The switch comm value will change when
+ // clearing thread names and replaced
+ // during thread merging.
+
+ auto passed_through = {
+ Passthrough(comp_sched,
+ protos::pbzero::FtraceEventBundle::CompactSched::
+ kSwitchTimestampFieldNumber,
+ message),
+ Passthrough(comp_sched,
+ protos::pbzero::FtraceEventBundle::CompactSched::
+ kSwitchPrevStateFieldNumber,
+ message),
+ Passthrough(comp_sched,
+ protos::pbzero::FtraceEventBundle::CompactSched::
+ kSwitchNextPrioFieldNumber,
+ message)};
+
+ if (!std::all_of(passed_through.begin(), passed_through.end(), IsTrue)) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: missing required "
+ "FtraceEventBundle::CompactSched switch field.");
+ }
+
+ return base::OkStatus();
+}
+
// Switch event transformation: Clear the comm value if the thread/process is
// not part of the target packet.
-base::Status ClearComms::Transform(const Context& context,
- uint64_t ts,
- int32_t,
- int32_t* pid,
- std::string* comm) const {
+base::Status ClearComms::Modify(const Context& context,
+ uint64_t ts,
+ int32_t,
+ int32_t* pid,
+ std::string* comm) const {
PERFETTO_DCHECK(pid);
PERFETTO_DCHECK(comm);
diff --git a/src/trace_redaction/redact_sched_switch.h b/src/trace_redaction/redact_sched_switch.h
index 55f9a75..906eb11 100644
--- a/src/trace_redaction/redact_sched_switch.h
+++ b/src/trace_redaction/redact_sched_switch.h
@@ -17,31 +17,55 @@
#ifndef SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
#define SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
+#include "src/trace_redaction/trace_redaction_framework.h"
+
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
-#include "src/trace_redaction/trace_redaction_framework.h"
namespace perfetto::trace_redaction {
-class SchedSwitchTransform {
+class InternTable {
public:
- virtual ~SchedSwitchTransform();
- virtual base::Status Transform(const Context& context,
- uint64_t ts,
- int32_t cpu,
- int32_t* pid,
- std::string* comm) const = 0;
+ int64_t Push(const char* data, size_t size);
+
+ std::string_view Find(size_t index) const;
+
+ const std::vector<std::string_view>& values() const {
+ return interned_comms_;
+ }
+
+ private:
+ constexpr static size_t kExpectedCommLength = 16;
+ constexpr static size_t kMaxElements = 4096;
+
+ std::array<char, kMaxElements * kExpectedCommLength> comms_;
+ size_t comms_length_ = 0;
+
+ std::vector<std::string_view> interned_comms_;
};
-// Goes through all sched switch events are modifies them.
+// TODO(vaage): Rename this class. When it was first created, it only handled
+// switch events, so having "switch" in the name sense. Now that it is
+// expanding to include waking events, a more general name is needed (e.g.
+// scheduling covers both switch and waking events).
class RedactSchedSwitchHarness : public TransformPrimitive {
public:
+ class Modifier {
+ public:
+ virtual ~Modifier();
+ virtual base::Status Modify(const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ int32_t* pid,
+ std::string* comm) const = 0;
+ };
+
base::Status Transform(const Context& context,
std::string* packet) const override;
template <class Transform>
void emplace_transform() {
- transforms_.emplace_back(new Transform());
+ modifier_ = std::make_unique<Transform>();
}
private:
@@ -65,15 +89,36 @@
std::string* scratch_str,
protos::pbzero::SchedSwitchFtraceEvent* message) const;
- std::vector<std::unique_ptr<SchedSwitchTransform>> transforms_;
+ base::Status TransformFtraceEventSchedWaking(
+ const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking,
+ std::string* scratch_str,
+ protos::pbzero::SchedWakingFtraceEvent* message) const;
+
+ base::Status TransformCompSched(
+ const Context& context,
+ int32_t cpu,
+ protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+ protos::pbzero::FtraceEventBundle::CompactSched* message) const;
+
+ base::Status TransformCompSchedSwitch(
+ const Context& context,
+ int32_t cpu,
+ protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+ InternTable* intern_table,
+ protos::pbzero::FtraceEventBundle::CompactSched* message) const;
+
+ std::unique_ptr<Modifier> modifier_;
};
-class ClearComms : public SchedSwitchTransform {
- base::Status Transform(const Context& context,
- uint64_t ts,
- int32_t cpu,
- int32_t* pid,
- std::string* comm) const override;
+class ClearComms : public RedactSchedSwitchHarness::Modifier {
+ base::Status Modify(const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ int32_t* pid,
+ std::string* comm) const override;
};
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_switch_unittest.cc b/src/trace_redaction/redact_sched_switch_unittest.cc
index 88280d7..cd05794 100644
--- a/src/trace_redaction/redact_sched_switch_unittest.cc
+++ b/src/trace_redaction/redact_sched_switch_unittest.cc
@@ -35,20 +35,40 @@
constexpr int32_t kNoParent = 10;
constexpr int32_t kPidA = 11;
constexpr int32_t kPidB = 12;
+constexpr int32_t kPidC = 13;
constexpr int32_t kCpuA = 0;
+constexpr int32_t kCpuB = 1;
+constexpr int32_t kCpuC = 2;
constexpr uint64_t kFullStep = 1000;
constexpr uint64_t kTimeA = 0;
constexpr uint64_t kTimeB = kFullStep;
+constexpr uint64_t kTimeC = kFullStep * 2;
constexpr auto kCommA = "comm-a";
constexpr auto kCommB = "comm-b";
+constexpr auto kCommC = "comm-c";
constexpr auto kCommNone = "";
+class ChangePidToMax : public RedactSchedSwitchHarness::Modifier {
+ public:
+ base::Status Modify(const Context& context,
+ uint64_t ts,
+ int32_t,
+ int32_t* pid,
+ std::string*) const override {
+ if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) {
+ *pid = std::numeric_limits<int32_t>::max();
+ }
+
+ return base::OkStatus();
+ }
+};
+
} // namespace
-class RedactSchedSwitchTest : public testing::Test {
+class RedactSchedSwitchFtraceEventTest : public testing::Test {
protected:
void SetUp() override {
// Create a packet where two pids are swapping back-and-forth.
@@ -103,7 +123,7 @@
// In this case, the target uid will be UID A. That means the comm values for
// PID B should be removed, and the comm values for PID A should remain.
-TEST_F(RedactSchedSwitchTest, KeepsTargetCommValues) {
+TEST_F(RedactSchedSwitchFtraceEventTest, KeepsTargetCommValues) {
RedactSchedSwitchHarness redact;
redact.emplace_transform<ClearComms>();
@@ -137,7 +157,7 @@
// This case is very similar to the "some are connected", expect that it
// verifies all comm values will be removed when testing against an unused
// uid.
-TEST_F(RedactSchedSwitchTest, RemovesAllCommsIfPackageDoesntExist) {
+TEST_F(RedactSchedSwitchFtraceEventTest, RemovesAllCommsIfPackageDoesntExist) {
RedactSchedSwitchHarness redact;
redact.emplace_transform<ClearComms>();
@@ -162,4 +182,347 @@
ASSERT_EQ(events[1].sched_switch().next_comm(), kCommNone);
}
+class RedactCompactSchedSwitchTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ // PID A and PID B need to be attached to different packages (UID) so that
+ // its possible to include one but not the other.
+ context_.timeline = std::make_unique<ProcessThreadTimeline>();
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
+ context_.timeline->Sort();
+
+ auto* bundle = packet_.mutable_ftrace_events();
+ bundle->set_cpu(kCpuA); // All switch events occur on this CPU
+
+ compact_sched = bundle->mutable_compact_sched();
+
+ compact_sched->add_intern_table(kCommA);
+ compact_sched->add_intern_table(kCommB);
+ }
+
+ void AddSwitchEvent(uint64_t ts,
+ int32_t next_pid,
+ int32_t prev_state,
+ int32_t prio,
+ uint32_t comm) {
+ compact_sched->add_switch_timestamp(ts);
+ compact_sched->add_switch_next_pid(next_pid);
+ compact_sched->add_switch_prev_state(prev_state);
+ compact_sched->add_switch_next_prio(prio);
+ compact_sched->add_switch_next_comm_index(comm);
+ }
+
+ protos::gen::TracePacket packet_;
+ protos::gen::FtraceEventBundle::CompactSched* compact_sched;
+
+ Context context_;
+};
+
+TEST_F(RedactCompactSchedSwitchTest, KeepsTargetCommValues) {
+ uint32_t kCommIndexA = 0;
+ uint32_t kCommIndexB = 1;
+
+ // The new entry will be appended to the table. Another primitive can be used
+ // to reduce the intern string table.
+ uint32_t kCommIndexNone = 2;
+
+ AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+ AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
+
+ context_.package_uid = kUidA;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ ASSERT_TRUE(bundle.has_compact_sched());
+
+ const auto& compact_sched = bundle.compact_sched();
+
+ // A new entry (empty string) should have been added to the table.
+ ASSERT_EQ(compact_sched.intern_table_size(), 3);
+ ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
+
+ ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2);
+ ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexA);
+ ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone);
+}
+
+// If two pids use the same comm, but one pid changes, the shared comm should
+// still be available.
+TEST_F(RedactCompactSchedSwitchTest, ChangingSharedCommonRetainsComm) {
+ uint32_t kCommIndexA = 0;
+
+ AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+ AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexA);
+
+ context_.package_uid = kUidA;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ ASSERT_TRUE(bundle.has_compact_sched());
+
+ const auto& compact_sched = bundle.compact_sched();
+
+ // A new entry should have been appended, but comm A (previously shared)
+ // should still exist in the table.
+ ASSERT_EQ(compact_sched.intern_table_size(), 3);
+ ASSERT_EQ(compact_sched.intern_table().front(), kCommA);
+ ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
+}
+
+TEST_F(RedactCompactSchedSwitchTest, RemovesAllCommsIfPackageDoesntExist) {
+ uint32_t kCommIndexA = 0;
+ uint32_t kCommIndexB = 1;
+
+ // The new entry will be appended to the table. Another primitive can be used
+ // to reduce the intern string table.
+ uint32_t kCommIndexNone = 2;
+
+ AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+ AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
+
+ context_.package_uid = kUidC;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ ASSERT_TRUE(bundle.has_compact_sched());
+
+ const auto& compact_sched = bundle.compact_sched();
+
+ // A new entry (empty string) should have been added to the table.
+ ASSERT_EQ(compact_sched.intern_table_size(), 3);
+ ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
+
+ ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2);
+ ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexNone);
+ ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone);
+}
+
+TEST_F(RedactCompactSchedSwitchTest, CanChangePid) {
+ uint32_t kCommIndexA = 0;
+ uint32_t kCommIndexB = 1;
+
+ AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+ AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
+
+ // Because the target is package A, PidA should be remain. PidB should change.
+ context_.package_uid = kUidA;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ChangePidToMax>();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ ASSERT_TRUE(bundle.has_compact_sched());
+
+ const auto& compact_sched = bundle.compact_sched();
+
+ // The intern table should not change.
+ ASSERT_EQ(compact_sched.intern_table_size(), 2);
+
+ ASSERT_EQ(compact_sched.switch_next_pid_size(), 2);
+ ASSERT_EQ(compact_sched.switch_next_pid().at(0), kPidA);
+ ASSERT_NE(compact_sched.switch_next_pid().at(1), kPidB);
+}
+
+class RedactSchedWakingFtraceEventTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ // Create a packet where two pids are swapping back-and-forth.
+ auto* bundle = packet_.mutable_ftrace_events();
+ bundle->set_cpu(kCpuA);
+
+ // Pid A wakes up Pid B at time Time B
+ {
+ auto* event = bundle->add_event();
+
+ event->set_timestamp(kTimeB);
+ event->set_pid(kPidA);
+
+ auto* sched_waking = event->mutable_sched_waking();
+ sched_waking->set_comm(kCommB);
+ sched_waking->set_pid(kPidB);
+ sched_waking->set_prio(0);
+ sched_waking->set_success(true);
+ sched_waking->set_target_cpu(kCpuB);
+ }
+
+ // Pid A wakes up Pid C at time Time C.
+ {
+ auto* event = bundle->add_event();
+
+ event->set_timestamp(kTimeC);
+ event->set_pid(kPidA);
+
+ auto* sched_waking = event->mutable_sched_waking();
+ sched_waking->set_comm(kCommC);
+ sched_waking->set_pid(kPidC);
+ sched_waking->set_prio(0);
+ sched_waking->set_success(true);
+ sched_waking->set_target_cpu(kCpuC);
+ }
+
+ // PID A and PID B need to be attached to different packages (UID) so that
+ // its possible to include one but not the other.
+ context_.timeline = std::make_unique<ProcessThreadTimeline>();
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
+ context_.timeline->Append(
+ ProcessThreadTimeline::Event::Open(kTimeA, kPidC, kNoParent, kUidC));
+ context_.timeline->Sort();
+ }
+
+ protos::gen::TracePacket packet_;
+ Context context_;
+};
+
+TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsCommWhenConnectedToPackage) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
+
+ context_.package_uid = kUidB;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ const auto& events = bundle.event();
+
+ ASSERT_EQ(events.size(), 2u);
+
+ ASSERT_EQ(events.front().sched_waking().comm(), kCommB);
+ ASSERT_EQ(events.back().sched_waking().comm(), kCommNone);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest,
+ WakeeLosesCommWhenNotConnectedToPackage) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
+
+ context_.package_uid = kUidA;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ const auto& events = bundle.event();
+
+ ASSERT_EQ(events.size(), 2u);
+
+ ASSERT_EQ(events.front().sched_waking().comm(), kCommNone);
+ ASSERT_EQ(events.back().sched_waking().comm(), kCommNone);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsPidWhenConnectedToPackage) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ChangePidToMax>();
+
+ context_.package_uid = kUidB;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ const auto& events = bundle.event();
+
+ ASSERT_EQ(events.size(), 2u);
+
+ ASSERT_EQ(events.front().sched_waking().pid(), kPidB);
+ ASSERT_NE(events.back().sched_waking().pid(), kPidC);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest,
+ WakeeLosesPidWhenNotConnectedToPackage) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ChangePidToMax>();
+
+ context_.package_uid = kUidA;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ const auto& events = bundle.event();
+
+ ASSERT_EQ(events.size(), 2u);
+
+ ASSERT_NE(events.front().sched_waking().pid(), kPidB);
+ ASSERT_NE(events.back().sched_waking().pid(), kPidC);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest, WakerPidIsLeftUnaffected) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ChangePidToMax>();
+
+ context_.package_uid = kUidB;
+
+ auto packet_buffer = packet_.SerializeAsString();
+
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+ const auto& bundle = packet.ftrace_events();
+ const auto& events = bundle.event();
+
+ ASSERT_EQ(events.size(), 2u);
+
+ ASSERT_EQ(events.front().pid(), static_cast<uint32_t>(kPidA));
+ ASSERT_EQ(events.back().pid(), static_cast<uint32_t>(kPidA));
+}
+
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_task_newtask.cc b/src/trace_redaction/redact_task_newtask.cc
index fb0cfe8..0286df2 100644
--- a/src/trace_redaction/redact_task_newtask.cc
+++ b/src/trace_redaction/redact_task_newtask.cc
@@ -16,28 +16,15 @@
#include "src/trace_redaction/redact_task_newtask.h"
-#include "src/trace_redaction/proto_util.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/task.pbzero.h"
+#include "src/trace_processor/util/status_macros.h"
namespace perfetto::trace_redaction {
namespace {
-// TODO(vaage): Merge with RedactComm in redact_sched_switch.cc.
-protozero::ConstChars RedactComm(const Context& context,
- uint64_t ts,
- int32_t pid,
- protozero::ConstChars comm) {
- if (context.timeline->PidConnectsToUid(ts, pid, *context.package_uid)) {
- return comm;
- }
-
- return {};
-}
-
} // namespace
// Redact sched switch trace events in an ftrace event bundle:
//
@@ -56,9 +43,11 @@
// equal to "event.task_newtask.pid" (a thread cannot start itself).
base::Status RedactTaskNewTask::Redact(
const Context& context,
- const protos::pbzero::FtraceEventBundle::Decoder&,
+ const protos::pbzero::FtraceEventBundle::Decoder& bundle,
protozero::ProtoDecoder& event,
protos::pbzero::FtraceEvent* event_message) const {
+ PERFETTO_DCHECK(modifier_);
+
if (!context.package_uid.has_value()) {
return base::ErrStatus("RedactTaskNewTask: missing package uid");
}
@@ -68,45 +57,41 @@
}
// The timestamp is needed to do the timeline look-up. If the packet has no
- // timestamp, don't add the sched switch event. This is the safest option.
- auto timestamp =
+ // timestamp, don't add the sched switch event.
+ auto timestamp_field =
event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
- if (!timestamp.valid()) {
- return base::OkStatus();
- }
-
- auto new_task =
+ auto new_task_field =
event.FindField(protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber);
- if (!new_task.valid()) {
+
+ if (!timestamp_field.valid() || !new_task_field.valid()) {
return base::ErrStatus(
- "RedactTaskNewTask: was used for unsupported field type");
+ "RedactTaskNewTask: missing required FtraceEvent field.");
}
- protozero::ProtoDecoder new_task_decoder(new_task.as_bytes());
+ protos::pbzero::TaskNewtaskFtraceEvent::Decoder new_task(
+ new_task_field.as_bytes());
- auto pid = new_task_decoder.FindField(
- protos::pbzero::TaskNewtaskFtraceEvent::kPidFieldNumber);
-
- if (!pid.valid()) {
- return base::OkStatus();
+ // There are only four fields in a new task event. Since two of them can
+ // change, it is easier to work with them directly.
+ if (!new_task.has_pid() || !new_task.has_comm() ||
+ !new_task.has_clone_flags() || !new_task.has_oom_score_adj()) {
+ return base::ErrStatus(
+ "RedactTaskNewTask: missing required TaskNewtaskFtraceEvent field.");
}
- // Avoid making the message until we know that we have prev and next pids.
+ auto pid = new_task.pid();
+ auto comm = new_task.comm().ToStdString();
+
+ auto cpu = static_cast<int32_t>(bundle.cpu());
+
+ RETURN_IF_ERROR(modifier_->Modify(context, timestamp_field.as_uint64(), cpu,
+ &pid, &comm));
+
auto* new_task_message = event_message->set_task_newtask();
-
- for (auto field = new_task_decoder.ReadField(); field.valid();
- field = new_task_decoder.ReadField()) {
- // Perfetto view (ui.perfetto.dev) crashes if the comm value is missing.
- // To work around this, the comm value is replaced with an empty string.
- // This appears to work.
- if (field.id() ==
- protos::pbzero::TaskNewtaskFtraceEvent::kCommFieldNumber) {
- new_task_message->set_comm(RedactComm(context, timestamp.as_uint64(),
- pid.as_int32(), field.as_string()));
- } else {
- proto_util::AppendField(field, new_task_message);
- }
- }
+ new_task_message->set_pid(pid);
+ new_task_message->set_comm(comm);
+ new_task_message->set_clone_flags(new_task.clone_flags());
+ new_task_message->set_oom_score_adj(new_task.oom_score_adj());
return base::OkStatus();
}
diff --git a/src/trace_redaction/redact_task_newtask.h b/src/trace_redaction/redact_task_newtask.h
index 51c1124..61c8b52 100644
--- a/src/trace_redaction/redact_task_newtask.h
+++ b/src/trace_redaction/redact_task_newtask.h
@@ -18,6 +18,7 @@
#define SRC_TRACE_REDACTION_REDACT_TASK_NEWTASK_H_
#include "src/trace_redaction/redact_ftrace_event.h"
+#include "src/trace_redaction/redact_sched_switch.h"
#include "src/trace_redaction/trace_redaction_framework.h"
namespace perfetto::trace_redaction {
@@ -34,6 +35,14 @@
const protos::pbzero::FtraceEventBundle::Decoder& bundle,
protozero::ProtoDecoder& event,
protos::pbzero::FtraceEvent* event_message) const override;
+
+ template <class Modifier>
+ void emplace_back() {
+ modifier_ = std::make_unique<Modifier>();
+ }
+
+ public:
+ std::unique_ptr<RedactSchedSwitchHarness::Modifier> modifier_;
};
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_task_newtask_unittest.cc b/src/trace_redaction/redact_task_newtask_unittest.cc
index 33886f6..11b1405 100644
--- a/src/trace_redaction/redact_task_newtask_unittest.cc
+++ b/src/trace_redaction/redact_task_newtask_unittest.cc
@@ -16,19 +16,21 @@
#include "src/trace_redaction/redact_task_newtask.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
-#include "protos/perfetto/trace/ftrace/task.gen.h"
+#include "src/base/test/status_matchers.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
-#include "protos/perfetto/trace/ftrace/sched.gen.h"
+#include "protos/perfetto/trace/ftrace/task.gen.h"
#include "protos/perfetto/trace/trace.gen.h"
#include "protos/perfetto/trace/trace_packet.gen.h"
namespace perfetto::trace_redaction {
namespace {
+constexpr uint64_t kCpu = 1;
+
constexpr uint64_t kUidA = 1;
constexpr uint64_t kUidB = 2;
@@ -44,19 +46,23 @@
class RedactTaskNewTaskTest : public testing::Test {
protected:
void SetUp() override {
- auto* event = bundle_.add_event();
+ bundle_.set_cpu(kCpu);
+ auto* event = bundle_.add_event();
event->set_timestamp(123456789);
event->set_pid(kPidA);
auto* new_task = event->mutable_task_newtask();
+ new_task->set_clone_flags(0);
new_task->set_comm(std::string(kCommA));
+ new_task->set_oom_score_adj(0);
new_task->set_pid(kPidA);
}
base::Status Redact(const Context& context,
protos::pbzero::FtraceEvent* event_message) {
RedactTaskNewTask redact;
+ redact.emplace_back<ClearComms>();
auto bundle_str = bundle_.SerializeAsString();
protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str);
@@ -93,8 +99,6 @@
};
TEST_F(RedactTaskNewTaskTest, RejectMissingPackageUid) {
- RedactTaskNewTask redact;
-
Context context;
context.timeline = std::make_unique<ProcessThreadTimeline>();
@@ -106,8 +110,6 @@
}
TEST_F(RedactTaskNewTaskTest, RejectMissingTimeline) {
- RedactTaskNewTask redact;
-
Context context;
context.package_uid = kUidA;
@@ -119,8 +121,6 @@
}
TEST_F(RedactTaskNewTaskTest, PidInPackageKeepsComm) {
- RedactTaskNewTask redact;
-
// Because Uid A is the target, when Pid A starts (new task event), it should
// keep its comm value.
Context context;
@@ -130,8 +130,7 @@
protos::pbzero::FtraceEvent::Decoder event_decoder(event_string());
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
- ASSERT_TRUE(result.ok());
+ ASSERT_OK(Redact(context, event_message.get()));
protos::gen::FtraceEvent redacted_event;
redacted_event.ParseFromString(event_message.SerializeAsString());
@@ -142,8 +141,6 @@
}
TEST_F(RedactTaskNewTaskTest, PidOutsidePackageLosesComm) {
- RedactTaskNewTask redact;
-
// Because Uid B is the target, when Pid A starts (new task event), it should
// lose its comm value.
Context context;
@@ -153,8 +150,7 @@
protos::pbzero::FtraceEvent::Decoder event_decoder(event_string());
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
- ASSERT_TRUE(result.ok());
+ ASSERT_OK(Redact(context, event_message.get()));
protos::gen::FtraceEvent redacted_event;
redacted_event.ParseFromString(event_message.SerializeAsString());
diff --git a/src/trace_redaction/remove_process_free_comm.cc b/src/trace_redaction/remove_process_free_comm.cc
new file mode 100644
index 0000000..265df79
--- /dev/null
+++ b/src/trace_redaction/remove_process_free_comm.cc
@@ -0,0 +1,59 @@
+/*
+ * 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/remove_process_free_comm.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"
+
+namespace perfetto::trace_redaction {
+
+base::Status RemoveProcessFreeComm::Redact(
+ const Context&,
+ const protos::pbzero::FtraceEventBundle::Decoder&,
+ protozero::ProtoDecoder& event,
+ protos::pbzero::FtraceEvent* event_message) const {
+ auto sched_process_free = event.FindField(
+ protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber);
+ if (!sched_process_free.valid()) {
+ return base::ErrStatus("RemoveProcessFreeComm: missing required field.");
+ }
+
+ // SchedProcessFreeFtraceEvent
+ protozero::ProtoDecoder decoder(sched_process_free.as_bytes());
+
+ auto pid_field = decoder.FindField(
+ protos::pbzero::SchedProcessFreeFtraceEvent::kPidFieldNumber);
+ auto prio_field = decoder.FindField(
+ protos::pbzero::SchedProcessFreeFtraceEvent::kPrioFieldNumber);
+
+ if (!pid_field.valid() || !prio_field.valid()) {
+ return base::ErrStatus("RemoveProcessFreeComm: missing required field.");
+ }
+
+ auto* message = event_message->set_sched_process_free();
+
+ // Replace the comm with an empty string instead of dropping the comm field.
+ // The perfetto UI doesn't render things correctly if comm values are missing.
+ message->set_comm("");
+ message->set_pid(pid_field.as_int32());
+ message->set_prio(prio_field.as_int32());
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_process_free.h b/src/trace_redaction/remove_process_free_comm.h
similarity index 65%
rename from src/trace_redaction/redact_process_free.h
rename to src/trace_redaction/remove_process_free_comm.h
index 19e954c..f3dc0d9 100644
--- a/src/trace_redaction/redact_process_free.h
+++ b/src/trace_redaction/remove_process_free_comm.h
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-#ifndef SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_
-#define SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_
+#ifndef SRC_TRACE_REDACTION_REMOVE_PROCESS_FREE_COMM_H_
+#define SRC_TRACE_REDACTION_REMOVE_PROCESS_FREE_COMM_H_
#include "src/trace_redaction/redact_ftrace_event.h"
#include "src/trace_redaction/trace_redaction_framework.h"
namespace perfetto::trace_redaction {
-// Goes through ftrace events and conditonally removes the comm values from
-// process free events.
-class RedactProcessFree : public FtraceEventRedaction {
+// Process free events will always end up testing positive for "remove comm
+// value", so instead of running the test, always remove the comm value. We know
+// it would fail the test because process free events create close events on the
+// timeline. Because the timeline uses exclusive ends, the event's time will
+// never call within the range it belongs to.
+class RemoveProcessFreeComm : public FtraceEventRedaction {
public:
static constexpr auto kFieldId =
protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber;
@@ -38,4 +41,4 @@
} // namespace perfetto::trace_redaction
-#endif // SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_
+#endif // SRC_TRACE_REDACTION_REMOVE_PROCESS_FREE_COMM_H_
diff --git a/src/trace_redaction/redact_process_free_unittest.cc b/src/trace_redaction/remove_process_free_comm_unittest.cc
similarity index 66%
rename from src/trace_redaction/redact_process_free_unittest.cc
rename to src/trace_redaction/remove_process_free_comm_unittest.cc
index 04064ae..5999a4a 100644
--- a/src/trace_redaction/redact_process_free_unittest.cc
+++ b/src/trace_redaction/remove_process_free_comm_unittest.cc
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-#include "src/trace_redaction/redact_process_free.h"
+#include "src/trace_redaction/remove_process_free_comm.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
#include "src/base/test/status_matchers.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "test/gtest_and_gmock.h"
@@ -29,16 +30,18 @@
namespace perfetto::trace_redaction {
-class RedactProcessFreeTest : public testing::Test {
+class RemoveProcessFreeCommTest : public testing::Test {
protected:
void SetUp() override {
auto* source_event = bundle.add_event();
source_event->set_timestamp(123456789);
source_event->set_pid(10);
+
+ process_free = source_event->mutable_sched_process_free();
}
base::Status Redact(protos::pbzero::FtraceEvent* event_message) {
- RedactProcessFree redact;
+ RemoveProcessFreeComm redact;
Context context;
auto bundle_str = bundle.SerializeAsString();
@@ -51,42 +54,19 @@
}
protos::gen::FtraceEventBundle bundle;
+ protos::gen::SchedProcessFreeFtraceEvent* process_free;
};
// A free event will always test as "not active". So the comm value should
// always be replaced with an empty string.
-TEST_F(RedactProcessFreeTest, ClearsCommValue) {
- auto* process_free =
- bundle.mutable_event()->back().mutable_sched_process_free();
+TEST_F(RemoveProcessFreeCommTest, ClearsCommValue) {
process_free->set_comm("comm-a");
process_free->set_pid(11);
+ process_free->set_prio(0);
protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(event_message.get());
- ASSERT_OK(result) << result.c_message();
-
- protos::gen::FtraceEvent redacted_event;
- redacted_event.ParseFromString(event_message.SerializeAsString());
-
- // No process free event should have been added to the ftrace event.
- ASSERT_TRUE(redacted_event.has_sched_process_free());
- ASSERT_TRUE(redacted_event.sched_process_free().has_comm());
- ASSERT_TRUE(redacted_event.sched_process_free().comm().empty());
-}
-
-// Even if there is no pid in the process free event, the comm value should be
-// replaced with an empty string.
-TEST_F(RedactProcessFreeTest, NoPidClearsEvent) {
- // Don't add a pid. This should have no change in behaviour.
- auto* process_free =
- bundle.mutable_event()->back().mutable_sched_process_free();
- process_free->set_comm("comm-a");
-
- protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
-
- auto result = Redact(event_message.get());
- ASSERT_OK(result) << result.c_message();
+ ASSERT_OK(Redact(event_message.get()));
protos::gen::FtraceEvent redacted_event;
redacted_event.ParseFromString(event_message.SerializeAsString());
diff --git a/src/trace_redaction/verify_integrity.cc b/src/trace_redaction/verify_integrity.cc
new file mode 100644
index 0000000..a2d58ce
--- /dev/null
+++ b/src/trace_redaction/verify_integrity.cc
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Projectf
+ *
+ * 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/verify_integrity.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+// All constants are from
+// "system/core/libcutils/include/private/android_filesystem_config.h"
+//
+// AID 1000 == system (those are probably frame_timeline packets you will see
+// those on) AID 9999 is nobody == traced/traced_probes
+constexpr int32_t kAidSystem = 1000;
+constexpr int32_t kAidNobody = 9999;
+} // namespace
+
+base::Status VerifyIntegrity::Collect(
+ const protos::pbzero::TracePacket::Decoder& packet,
+ Context*) const {
+ if (!packet.has_trusted_uid()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field (TracePacket.trusted_uid).");
+ }
+
+ if (packet.trusted_uid() != kAidSystem &&
+ packet.trusted_uid() != kAidNobody) {
+ return base::ErrStatus(
+ "VerifyIntegrity: invalid field value (TracePacket.trusted_uid).");
+ }
+
+ if (packet.has_ftrace_events()) {
+ protos::pbzero::FtraceEventBundle::Decoder ftrace_events(
+ packet.ftrace_events());
+
+ // The other clocks in ftrace are only used on very old kernel versions. No
+ // device with V should have such an old version. As a failsafe though,
+ // check that the ftrace_clock field is unset to ensure no invalid
+ // timestamps get by.
+ if (ftrace_events.has_ftrace_clock()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: unexpected field "
+ "(FtraceEventBundle::kFtraceClockFieldNumber).");
+ }
+
+ // Every ftrace event bundle should have a CPU field. This is necessary for
+ // switch/waking redaction to work.
+ if (!ftrace_events.has_cpu()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field "
+ "(FtraceEventBundle::kCpuFieldNumber).");
+ }
+
+ RETURN_IF_ERROR(VerifyFtraceEventsTime(ftrace_events));
+ }
+
+ return base::OkStatus();
+}
+
+base::Status VerifyIntegrity::VerifyFtraceEventsTime(
+ const protos::pbzero::FtraceEventBundle::Decoder& bundle) const {
+ // If a bundle has ftrace events, the events will contain the time stamps.
+ // However, there are no ftrace events, the timestamp will be in the bundle.
+ if (!bundle.has_event() && !bundle.has_ftrace_timestamp()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field "
+ "(FtraceEventBundle::kFtraceTimestampFieldNumber).");
+ }
+
+ for (auto event_buffer = bundle.event(); event_buffer; ++event_buffer) {
+ protos::pbzero::FtraceEvent::Decoder event(*event_buffer);
+
+ if (!protozero::ProtoDecoder(*event_buffer)
+ .FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber)
+ .valid()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field "
+ "(FtraceEvent::kTimestampFieldNumber)");
+ }
+ }
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/verify_integrity.h b/src/trace_redaction/verify_integrity.h
new file mode 100644
index 0000000..6548ce5
--- /dev/null
+++ b/src/trace_redaction/verify_integrity.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+#ifndef SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
+#define SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
+
+namespace perfetto::trace_redaction {
+
+// This breaks the normal collect primitive pattern. Rather than collecting
+// information, it looks at packets and returns an error if the packet violates
+// any requirements.
+class VerifyIntegrity : public CollectPrimitive {
+ public:
+ base::Status Collect(const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const override;
+
+ private:
+ base::Status VerifyFtraceEventsTime(
+ const protos::pbzero::FtraceEventBundle::Decoder& bundle) const;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
diff --git a/src/traceconv/symbolize_profile.cc b/src/traceconv/symbolize_profile.cc
index 00cc7b9..4efaa10 100644
--- a/src/traceconv/symbolize_profile.cc
+++ b/src/traceconv/symbolize_profile.cc
@@ -55,12 +55,12 @@
PERFETTO_FATAL("Failed to read trace.");
tp->Flush();
+ tp->NotifyEndOfFile();
SymbolizeDatabase(
tp.get(), symbolizer.get(),
[output](const std::string& trace_proto) { *output << trace_proto; });
- tp->NotifyEndOfFile();
return 0;
}
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 66b9b1b..4045978 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -869,6 +869,9 @@
case kDevId64ToUint64:
ReadDevId<uint64_t>(field_start, field_id, message, metadata);
return true;
+ case kFtraceSymAddr32ToUint64:
+ ReadSymbolAddr<uint32_t>(field_start, field_id, message, metadata);
+ return true;
case kFtraceSymAddr64ToUint64:
ReadSymbolAddr<uint64_t>(field_start, field_id, message, metadata);
return true;
diff --git a/src/traced/probes/ftrace/event_info_constants.cc b/src/traced/probes/ftrace/event_info_constants.cc
index 37f9c77..4a0d08f 100644
--- a/src/traced/probes/ftrace/event_info_constants.cc
+++ b/src/traced/probes/ftrace/event_info_constants.cc
@@ -106,6 +106,8 @@
*out = kBoolToUint64;
} else if (ftrace == kFtraceDataLoc && proto == ProtoSchemaType::kString) {
*out = kDataLocToString;
+ } else if (ftrace == kFtraceSymAddr32 && proto == ProtoSchemaType::kUint64) {
+ *out = kFtraceSymAddr32ToUint64;
} else if (ftrace == kFtraceSymAddr64 && proto == ProtoSchemaType::kUint64) {
*out = kFtraceSymAddr64ToUint64;
} else {
diff --git a/src/traced/probes/ftrace/event_info_constants.h b/src/traced/probes/ftrace/event_info_constants.h
index 0283f12..cce144f 100644
--- a/src/traced/probes/ftrace/event_info_constants.h
+++ b/src/traced/probes/ftrace/event_info_constants.h
@@ -48,6 +48,7 @@
kFtraceDevId32,
kFtraceDevId64,
kFtraceDataLoc,
+ kFtraceSymAddr32,
kFtraceSymAddr64,
};
@@ -84,6 +85,7 @@
kDevId32ToUint64,
kDevId64ToUint64,
kDataLocToString,
+ kFtraceSymAddr32ToUint64,
kFtraceSymAddr64ToUint64,
};
@@ -127,6 +129,7 @@
return "devid64";
case kFtraceDataLoc:
return "__data_loc";
+ case kFtraceSymAddr32:
case kFtraceSymAddr64:
return "void*";
case kInvalidFtraceFieldType:
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 9dcde14..7e9092e 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -239,6 +239,7 @@
case kFtraceUint64:
case kFtraceInode32:
case kFtraceInode64:
+ case kFtraceSymAddr32:
case kFtraceSymAddr64:
*proto_type = ProtoSchemaType::kUint64;
*proto_field_id = GenericFtraceEvent::Field::kUintValueFieldNumber;
@@ -310,13 +311,16 @@
return true;
}
- // Kernel addresses that need symbolization via kallsyms. Only 64-bit kernels
- // are supported for now. 32-bit kernels seems to be going away.
- if ((base::StartsWith(type_and_name, "void*") ||
- base::StartsWith(type_and_name, "void *")) &&
- size == 8) {
- *out = kFtraceSymAddr64;
- return true;
+ // Kernel addresses that need symbolization via kallsyms.
+ if (base::StartsWith(type_and_name, "void*") ||
+ base::StartsWith(type_and_name, "void *")) {
+ if (size == 4) {
+ *out = kFtraceSymAddr32;
+ return true;
+ } else if (size == 8) {
+ *out = kFtraceSymAddr64;
+ return true;
+ }
}
// Variable length strings: "char foo" + size: 0 (as in 'print').
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index d667767..6631d68 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -24,6 +24,7 @@
#include <limits>
#include <optional>
#include <regex>
+#include <string>
#include <unordered_set>
#include "perfetto/base/time.h"
#include "perfetto/ext/tracing/core/client_identity.h"
@@ -1689,13 +1690,14 @@
tracing_session.config, tracing_session.trace_uuid,
PerfettoStatsdAtom::kTracedTriggerCloneSnapshot, iter->name());
task_runner_->PostDelayedTask(
- [weak_this, tsid] {
+ [weak_this, tsid, trigger_name = iter->name()] {
if (!weak_this)
return;
auto* tsess = weak_this->GetTracingSession(tsid);
if (!tsess || !tsess->consumer_maybe_null)
return;
- tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger();
+ tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger(
+ trigger_name);
},
iter->stop_delay_ms());
break;
@@ -4265,13 +4267,15 @@
observable_events->set_all_data_sources_started(true);
}
-void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger() {
+void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger(
+ const std::string& trigger_name) {
if (!(observable_events_mask_ & ObservableEvents::TYPE_CLONE_TRIGGER_HIT)) {
return;
}
auto* observable_events = AddObservableEvents();
auto* clone_trig = observable_events->mutable_clone_trigger_hit();
clone_trig->set_tracing_session_id(static_cast<int64_t>(tracing_session_id_));
+ clone_trig->set_trigger_name(trigger_name);
}
ObservableEvents*
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 9fa84fe..1dbcbe6 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -32,6 +32,7 @@
#include "perfetto/base/time.h"
#include "perfetto/ext/base/circular_queue.h"
#include "perfetto/ext/base/periodic_task.h"
+#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/uuid.h"
#include "perfetto/ext/base/weak_ptr.h"
#include "perfetto/ext/tracing/core/basic_types.h"
@@ -211,7 +212,7 @@
~ConsumerEndpointImpl() override;
void NotifyOnTracingDisabled(const std::string& error);
- void NotifyCloneSnapshotTrigger();
+ void NotifyCloneSnapshotTrigger(const std::string& trigger_name);
// TracingService::ConsumerEndpoint implementation.
void EnableTracing(const TraceConfig&, base::ScopedFile) override;
diff --git a/test/.gitignore b/test/.gitignore
index 8280df0..47a77bd 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -18,3 +18,11 @@
/data/parser/*
!/data/parser/*.sha256
!/data/parser/README.md
+
+!/data/simpleperf
+/data/simpleperf/*
+!/data/simpleperf/*.sha256
+
+!/data/zip
+/data/zip/*
+!/data/zip/*.sha256
\ No newline at end of file
diff --git a/test/data/simpleperf/perf.data.sha256 b/test/data/simpleperf/perf.data.sha256
new file mode 100644
index 0000000..8e90588
--- /dev/null
+++ b/test/data/simpleperf/perf.data.sha256
@@ -0,0 +1 @@
+cb3066f4050d84d3e204a37ca4c479113b7623b663c17a3ee8cae5a85b8238bf
\ No newline at end of file
diff --git a/test/data/simpleperf/perf_with_add_counter.data.sha256 b/test/data/simpleperf/perf_with_add_counter.data.sha256
new file mode 100644
index 0000000..06c8a91
--- /dev/null
+++ b/test/data/simpleperf/perf_with_add_counter.data.sha256
@@ -0,0 +1 @@
+f3f44439a4add389b13510d9bb9ed2a151dc6d0a1ee8a7f5c52a369d96c9f0eb
\ No newline at end of file
diff --git a/test/data/zip/perf_track_sym.zip.sha256 b/test/data/zip/perf_track_sym.zip.sha256
new file mode 100644
index 0000000..53dfcb3
--- /dev/null
+++ b/test/data/zip/perf_track_sym.zip.sha256
@@ -0,0 +1 @@
+146b1d8f48743323e91fd63d6ab5a1f6d8fcca8c5ea8455ebe8f087667a23c3f
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 45c972f..e70dfa3 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -88,6 +88,7 @@
from diff_tests.parser.profiling.tests_heap_profiling import ProfilingHeapProfiling
from diff_tests.parser.profiling.tests_llvm_symbolizer import ProfilingLlvmSymbolizer
from diff_tests.parser.sched.tests import SchedParser
+from diff_tests.parser.simpleperf.tests import Simpleperf
from diff_tests.parser.smoke.tests import Smoke
from diff_tests.parser.smoke.tests_compute_metrics import SmokeComputeMetrics
from diff_tests.parser.smoke.tests_json import SmokeJson
@@ -95,6 +96,7 @@
from diff_tests.parser.track_event.tests import TrackEvent
from diff_tests.parser.translated_args.tests import TranslatedArgs
from diff_tests.parser.ufs.tests import Ufs
+from diff_tests.parser.zip.tests import Zip
from diff_tests.stdlib.android.frames_tests import Frames
from diff_tests.stdlib.android.startups_tests import Startups
from diff_tests.stdlib.android.tests import AndroidStdlib
@@ -104,6 +106,7 @@
from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
from diff_tests.stdlib.cpu.tests import Cpu
from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
+from diff_tests.stdlib.gpu.tests import Gpu
from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
from diff_tests.stdlib.graphs.partition_tests import GraphPartitionTests
from diff_tests.stdlib.graphs.search_tests import GraphSearchTests
@@ -111,6 +114,7 @@
from diff_tests.stdlib.intervals.tests import StdlibIntervals
from diff_tests.stdlib.linux.tests import LinuxStdlib
from diff_tests.stdlib.memory.heap_graph_dominator_tree_tests import HeapGraphDominatorTree
+from diff_tests.stdlib.memory.tests import Memory
from diff_tests.stdlib.pkvm.tests import Pkvm
from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions
from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions
@@ -182,6 +186,7 @@
*ProfilingLlvmSymbolizer(index_path, 'parser/profiling',
'ProfilingLlvmSymbolizer').fetch(),
*SchedParser(index_path, 'parser/sched', 'SchedParser').fetch(),
+ *Simpleperf(index_path, 'parser/simpleperf', 'Simpleperf').fetch(),
*StdlibSched(index_path, 'stdlib/sched', 'StdlibSched').fetch(),
*Smoke(index_path, 'parser/smoke', 'Smoke').fetch(),
*SmokeComputeMetrics(index_path, 'parser/smoke',
@@ -216,6 +221,7 @@
*FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(),
*ParsingTracedStats(index_path, 'parser/parsing',
'ParsingTracedStats').fetch(),
+ *Zip(index_path, 'parser/zip', 'Zip').fetch(),
]
metrics_tests = [
@@ -262,6 +268,7 @@
*Cpu(index_path, 'stdlib/cpu', 'Cpu').fetch(),
*DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
*Frames(index_path, 'stdlib/android', 'Frames').fetch(),
+ *Gpu(index_path, 'stdlib/gpu', 'Gpu').fetch(),
*GraphSearchTests(index_path, 'stdlib/graphs',
'GraphSearchTests').fetch(),
*GraphPartitionTests(index_path, 'stdlib/graphs',
@@ -271,6 +278,7 @@
*DynamicTables(index_path, 'stdlib/dynamic_tables',
'DynamicTables').fetch(),
*LinuxStdlib(index_path, 'stdlib/linux', 'LinuxStdlib').fetch(),
+ *Memory(index_path, 'stdlib/memory', 'Memory').fetch(),
*PreludeMathFunctions(index_path, 'stdlib/prelude',
'PreludeMathFunctions').fetch(),
*HeapGraphDominatorTree(index_path, 'stdlib/memory',
diff --git a/test/trace_processor/diff_tests/metrics/memory/tests.py b/test/trace_processor/diff_tests/metrics/memory/tests.py
index 7ea5fda..a5a5cec 100644
--- a/test/trace_processor/diff_tests/metrics/memory/tests.py
+++ b/test/trace_processor/diff_tests/metrics/memory/tests.py
@@ -248,6 +248,17 @@
}
"""))
+ def test_android_lmk_reason(self):
+ return DiffTestBlueprint(
+ trace=DataPath('lmk_userspace.pb'),
+ query=Metric('android_lmk_reason'),
+ # TODO(mayzner): Find a trace that returns results. This is still
+ # beneficial though, as at least this metric is run.
+ out=TextProto(r"""
+ android_lmk_reason {
+ }
+ """))
+
def test_android_mem_delta(self):
return DiffTestBlueprint(
trace=Path('android_mem_delta.py'),
diff --git a/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
index a706d7e..83e1034 100644
--- a/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
+++ b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
@@ -22,24 +22,25 @@
trace = synth_common.create_trace()
-# Create a parent process which will be forked below.
+# Create a parent process which will be forked below.
trace.add_packet(ts=1)
trace.add_process(10, 0, "parent")
-# Fork off the new process and then kill it 5ns later.
+# Fork the process into a child.
trace.add_ftrace_packet(0)
trace.add_newtask(ts=15, tid=10, new_tid=11, new_comm='child', flags=0)
trace.add_sched(ts=16, prev_pid=10, next_pid=11, next_comm='child')
-# Create a parent process which will be forked below.
+# Scrape event for the forked process.
trace.add_packet(ts=20)
-trace.add_process(11, 0, "child_process")
+trace.add_process(11, 10, "child_process")
+# Rename of the main thread of forked process.
trace.add_ftrace_packet(0)
trace.add_rename(
ts=25, tid=11, old_comm='child', new_comm='true_name', oom_score_adj=1000)
-# Create a parent process which will be forked below.
+# Scrape of the forked process.
trace.add_packet(ts=30)
trace.add_process(11, 10, "true_process_name")
diff --git a/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
index 68641ad..19bf834 100644
--- a/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
+++ b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
@@ -90,7 +90,7 @@
ts=28, prev_pid=32, next_pid=40, prev_comm='p3-t2', next_comm='p4-t0')
trace.add_packet(ts=29)
-trace.add_process(40, 0, "process_4")
+trace.add_process(40, 30, "process_4")
# And now, this new process starts a new thread that recycles TID=31 (previously
# used as p3-t1, now becomes p4-t1).
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql
new file mode 100644
index 0000000..f5a65ee
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql
@@ -0,0 +1,99 @@
+--
+-- 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.
+--
+
+CREATE PERFETTO VIEW perf_sample_in(ts INT, dur INT)
+AS
+SELECT ts, 0 AS dur FROM perf_sample;
+
+CREATE VIRTUAL TABLE span
+USING
+ SPAN_JOIN(perf_sample_in, slice PARTITIONED depth);
+
+CREATE PERFETTO TABLE slice_stack
+AS
+WITH
+ tmp AS (
+ SELECT
+ ts,
+ parent_stack_id,
+ string_AGG(IIF(name = 'Main loop', 'main', name), ',')
+ OVER (
+ PARTITION BY ts
+ ORDER BY depth ASC
+ RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ ) AS stack
+ FROM span
+ )
+SELECT ts, stack FROM tmp WHERE parent_stack_id = 0 ORDER BY TS ASC;
+
+CREATE PERFETTO TABLE perf_stack
+AS
+WITH
+ symbol AS (
+ SELECT
+ id,
+ symbol_set_id,
+ replace(replace(name, '(anonymous namespace)::', ''), '()', '') AS name
+ FROM stack_profile_symbol
+ ),
+ symbol_agg AS (
+ SELECT
+ id,
+ symbol_set_id,
+ string_agg(name, ',')
+ OVER (
+ PARTITION BY symbol_set_id
+ ORDER BY id DESC
+ RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ ) AS name
+ FROM symbol
+ WHERE name IN ('main', 'A', 'B', 'C', 'D', 'E')
+ ),
+ inline AS (
+ SELECT symbol_set_id, name FROM symbol_agg WHERE id = symbol_set_id
+ ),
+ frame AS (
+ SELECT f.id AS frame_id, i.name
+ FROM STACK_PROFILE_FRAME f, inline i
+ USING (symbol_set_id)
+ ),
+ child AS (
+ SELECT
+ s.ts,
+ spc.id,
+ spc.parent_id,
+ name
+ FROM perf_sample s, stack_profile_callsite spc
+ ON (s.callsite_id = spc.id),
+ frame USING (frame_id)
+ UNION ALL
+ SELECT
+ child.ts,
+ parent.id,
+ parent.parent_id,
+ COALESCE(f.name || ',', '') || child.name AS name
+ FROM child, stack_profile_callsite parent
+ ON (child.parent_id = parent.id)
+ LEFT JOIN frame f
+ USING (frame_id)
+ )
+SELECT ts, name AS stack FROM child WHERE parent_id IS NULL ORDER BY ts ASC;
+
+SELECT COUNT(*) AS misaligned_count
+FROM slice_stack s
+FULL JOIN perf_stack p
+ USING (ts)
+WHERE s.stack <> p.stack;
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql
new file mode 100644
index 0000000..9f93369
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql
@@ -0,0 +1,46 @@
+WITH
+ counter_delta_base AS (
+ SELECT
+ *,
+ LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value
+ FROM counter
+ ),
+ counter_delta AS (
+ SELECT
+ id,
+ type,
+ ts,
+ track_id,
+ IIF(lag_value IS NULL, value, value - lag_value) AS delta,
+ arg_set_id,
+ machine_id
+ FROM counter_delta_base
+ )
+SELECT
+ CAST(SUM(c.delta) AS INTEGER) AS event_count,
+ thread.name AS command,
+ pid,
+ tid,
+ spm.name AS shared_object,
+ IIF(
+ spf.name IS NOT NULL AND spf.name <> '',
+ spf.name,
+ format(
+ '%s[+%x]',
+ -- substring after last /
+ replace(spm.name, rtrim(spm.name, replace(spm.name, '/', '')), ''),
+ spf.rel_pc)) AS symbol
+FROM counter_delta AS c, perf_counter_track AS t
+ON c.track_id = t.id,
+perf_sample AS s
+ON c.ts = s.ts AND t.perf_session_id = s.perf_session_id AND t.cpu = s.cpu,
+thread USING (utid),
+process USING (upid),
+stack_profile_callsite AS spc ON (s.callsite_id = spc.id),
+stack_profile_frame AS spf ON (spc.frame_id = spf.id),
+stack_profile_mapping AS spm
+ON (spf.mapping = spm.id)
+WHERE
+ s.cpu IN (2, 6, 7)
+GROUP BY command, pid, tid, shared_object, symbol
+ORDER BY event_count DESC;
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql
new file mode 100644
index 0000000..b81beac
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql
@@ -0,0 +1,68 @@
+WITH
+ counter_delta_base AS (
+ SELECT
+ *,
+ LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value
+ FROM counter
+ ),
+ counter_delta AS (
+ SELECT
+ id,
+ type,
+ ts,
+ track_id,
+ IIF(lag_value IS NULL, value, value - lag_value) AS delta,
+ arg_set_id,
+ machine_id
+ FROM counter_delta_base
+ ),
+ named_counter AS (
+ SELECT
+ perf_session_id,
+ ts,
+ cpu,
+ SUM(cast_int !(IIF(t.name = 'cpu-cycles', c.delta, 0)))
+ AS cpu_cycles,
+ SUM(cast_int !(IIF(t.name = 'instructions', c.delta, 0)))
+ AS instructions,
+ SUM(
+ cast_int
+ !(IIF(t.name NOT IN ('cpu-cycles', 'instructions'), c.delta, 0)))
+ AS others
+ FROM counter_delta AS c, perf_counter_track AS t
+ ON (c.track_id = t.id)
+ GROUP BY
+ perf_session_id,
+ ts,
+ cpu
+ )
+SELECT
+ SUM(c.cpu_cycles) AS cpu_cycles,
+ SUM(c.instructions) AS instructions,
+ -- Additional column (not present in simpleperf output) to validate that there
+ -- are no other counters.
+ SUM(c.others) AS others,
+ thread.name AS command,
+ pid,
+ tid,
+ spm.name AS shared_object,
+ IIF(
+ spf.name IS NOT NULL AND spf.name <> '',
+ spf.name,
+ format(
+ '%s[+%x]',
+ -- substring after last /
+ replace(spm.name, rtrim(spm.name, replace(spm.name, '/', '')), ''),
+ spf.rel_pc)) AS symbol
+FROM
+ named_counter AS c,
+ perf_sample AS s
+USING (perf_session_id, ts, cpu),
+thread USING (utid),
+process USING (upid),
+stack_profile_callsite AS spc ON (s.callsite_id = spc.id),
+stack_profile_frame AS spf ON (spc.frame_id = spf.id),
+stack_profile_mapping AS spm
+ON (spf.mapping = spm.id)
+GROUP BY command, pid, tid, shared_object, symbol
+ORDER BY cpu_cycles DESC;
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/tests.py b/test/trace_processor/diff_tests/parser/simpleperf/tests.py
new file mode 100644
index 0000000..4f5a22b
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/tests.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+# 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 a
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+# These diff tests are based on the same test data simpleperf uses for its
+# testing
+# (https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/testdata).
+# Basically we load these perf files and make sure we can get the same data we
+# would get via `simpleperf report`
+class Simpleperf(TestSuite):
+ # simpleperf report -i perf.data --print-event-count --csv --cpu 2,6,7
+ def test_perf(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf/perf.data'),
+ query=Path('perf_test.sql'),
+ out=Csv('''
+ "event_count","command","pid","tid","shared_object","symbol"
+ 130707953,"t2",26130,26130,"/t2","t2[+51c]"
+ 126249237,"elf",26083,26083,"/elf","elf[+51c]"
+ 109687208,"t1",26124,26124,"/t1","t1[+523]"
+ 107027760,"t1",26124,26124,"/t1","t1[+51c]"
+ 101887409,"t2",26130,26130,"/t2","t2[+523]"
+ 92421568,"elf",26083,26083,"/elf","elf[+523]"
+ 61539363,"t1",26124,26124,"/t1","t1[+518]"
+ 60355129,"elf",26083,26083,"/elf","elf[+513]"
+ 54840659,"t1",26124,26124,"/t1","t1[+4ed]"
+ 52233968,"elf",26083,26083,"/elf","elf[+4ed]"
+ 50833094,"t1",26124,26124,"/t1","t1[+4f7]"
+ 50746374,"t2",26130,26130,"/t2","t2[+4ed]"
+ 49185691,"elf",26083,26083,"/elf","elf[+4f7]"
+ 47520901,"t2",26130,26130,"/t2","t2[+513]"
+ 45979652,"elf",26083,26083,"/elf","elf[+518]"
+ 44834371,"t2",26130,26130,"/t2","t2[+4f7]"
+ 42928068,"t2",26130,26130,"/t2","t2[+518]"
+ 39608138,"t1",26124,26124,"/t1","t1[+513]"
+ 1390415,"t1",26124,26124,"/t1","t1[+4fa]"
+ 1390305,"t2",26130,26130,"/t2","t2[+4fa]"
+ 1390173,"elf",26083,26083,"/elf","elf[+500]"
+ 1389030,"t2",26130,26130,"/t2","t2[+500]"
+ 693786,"t2",26130,26130,"/lib/modules/3.13.0-76-generic/kernel/drivers/ata/pata_acpi.ko","pata_acpi.ko[+ffffffffa05c4da4]"
+ '''))
+
+ def test_perf_tracks(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf/perf.data'),
+ query='''
+ SELECT
+ name,
+ unit,
+ description,
+ perf_session_id,
+ cpu,
+ is_timebase
+ FROM perf_counter_track
+ ORDER BY perf_session_id, name, cpu;
+ ''',
+ out=Csv('''
+ "name","unit","description","perf_session_id","cpu","is_timebase"
+ "","","",0,2,1
+ "","","",0,6,1
+ "","","",0,7,1
+ "","","",0,16,1
+ '''))
+
+ def test_perf_with_add_counter_tracks(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf/perf_with_add_counter.data'),
+ query='''
+ SELECT
+ name,
+ parent_id,
+ source_arg_set_id,
+ machine_id,
+ unit,
+ description,
+ perf_session_id,
+ cpu,
+ is_timebase
+ FROM perf_counter_track
+ ORDER BY perf_session_id, name, cpu;
+ ''',
+ out=Csv('''
+ "name","parent_id","source_arg_set_id","machine_id","unit","description","perf_session_id","cpu","is_timebase"
+ "cpu-cycles","[NULL]","[NULL]","[NULL]","","",0,40,1
+ "instructions","[NULL]","[NULL]","[NULL]","","",0,40,0
+ '''))
+
+ # simpleperf report -i perf.data --print-event-count --csv
+ # The thread name in this trace changes over time. simpleperf shows samples
+ # with the old and new name. Perfetto does not support threads changing names,
+ # it only keeps the last name, thus there is a slight mismatch in the outputs.
+ def test_perf_with_add_counter(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf/perf_with_add_counter.data'),
+ query=Path('perf_with_add_counter_test.sql'),
+ out=Csv('''
+ "cpu_cycles","instructions","others","command","pid","tid","shared_object","symbol"
+ 1011567,1188389,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc9d30]"
+ 219490,233619,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e498c6]"
+ 191017,157031,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa94d0901]"
+ 175099,140443,0,"sleep",689664,689664,"/lib/x86_64-linux-gnu/libc-2.32.so","_dl_addr"
+ 152310,130151,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e30c70]"
+ 122439,87058,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa960015d]"
+ 89368,68332,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e03757]"
+ 40272,30457,0,"sleep",689664,689664,"/lib/x86_64-linux-gnu/ld-2.32.so","ld-2.32.so[+1767b]"
+ 14742,7858,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8ce7a78]"
+ 7551,1953,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc90c5]"
+ 7080,2940,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc8119]"
+ 3520,295,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8c6b3e6]"
+ '''))
+
+ def test_build_id_feature(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf/perf.data'),
+ query='''
+ SELECT build_id, name
+ FROM stack_profile_mapping
+ WHERE build_id <> ""
+ ORDER BY name
+ ''',
+ out=Csv('''
+ "build_id","name"
+ "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/elf"
+ "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/elf"
+ "47111a47babdcd27ca2f9ff450dc1897ded761ed","/lib/modules/3.13.0-76-generic/kernel/drivers/ata/pata_acpi.ko"
+ "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/t1"
+ "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/t2"
+ '''))
+
+ def test_clocks_align(self):
+ return DiffTestBlueprint(
+ trace=DataPath('zip/perf_track_sym.zip'),
+ query=Path('clocks_align_test.sql'),
+ out=Csv('''
+ "misaligned_count"
+ 0
+ '''))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/zip/stacks_test.sql b/test/trace_processor/diff_tests/parser/zip/stacks_test.sql
new file mode 100644
index 0000000..68543f1
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/zip/stacks_test.sql
@@ -0,0 +1,48 @@
+WITH
+ symbol AS (
+ SELECT
+ id,
+ symbol_set_id,
+ replace(replace(name, '(anonymous namespace)::', ''), '()', '') AS name
+ FROM stack_profile_symbol
+ ),
+ symbol_agg AS (
+ SELECT
+ id,
+ symbol_set_id,
+ string_agg(name, ',')
+ OVER (
+ PARTITION BY symbol_set_id
+ ORDER BY id DESC
+ RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ ) AS name
+ FROM symbol
+ WHERE name IN ('main', 'A', 'B', 'C', 'D', 'E')
+ ),
+ inline AS (
+ SELECT symbol_set_id, name FROM symbol_agg WHERE id = symbol_set_id
+ ),
+ frame AS (
+ SELECT f.id AS frame_id, i.name
+ FROM STACK_PROFILE_FRAME f, inline i
+ USING (symbol_set_id)
+ ),
+ child AS (
+ SELECT
+ spc.id,
+ spc.parent_id,
+ name
+ FROM perf_sample s, stack_profile_callsite spc
+ ON (s.callsite_id = spc.id),
+ frame USING (frame_id)
+ UNION ALL
+ SELECT
+ parent.id,
+ parent.parent_id,
+ COALESCE(f.name || ',', '') || child.name AS name
+ FROM child, stack_profile_callsite parent
+ ON (child.parent_id = parent.id)
+ LEFT JOIN frame f
+ USING (frame_id)
+ )
+SELECT DISTINCT name FROM child WHERE parent_id IS NULL ORDER BY name
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/zip/tests.py b/test/trace_processor/diff_tests/parser/zip/tests.py
new file mode 100644
index 0000000..9827072
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/zip/tests.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Zip(TestSuite):
+
+ def test_perf_proto_sym(self):
+ return DiffTestBlueprint(
+ trace=DataPath('zip/perf_track_sym.zip'),
+ query=Path('stacks_test.sql'),
+ out=Csv('''
+ "name"
+ "main,A"
+ "main,A,B"
+ "main,A,B,C"
+ "main,A,B,C,D"
+ "main,A,B,C,D,E"
+ "main,A,B,C,E"
+ "main,A,B,D"
+ "main,A,B,D,E"
+ "main,A,B,E"
+ "main,A,C"
+ "main,A,C,D"
+ "main,A,C,D,E"
+ "main,A,C,E"
+ "main,A,D"
+ "main,A,D,E"
+ "main,A,E"
+ "main,B"
+ "main,B,C"
+ "main,B,C,D"
+ "main,B,C,D,E"
+ "main,B,C,E"
+ "main,B,D"
+ "main,B,D,E"
+ "main,B,E"
+ "main,C"
+ "main,C,D"
+ "main,C,D,E"
+ "main,C,E"
+ "main,D"
+ "main,D,E"
+ "main,E"
+ '''))
diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
index 59a8877..6781e3e 100644
--- a/test/trace_processor/diff_tests/stdlib/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -1244,16 +1244,16 @@
""",
out=Csv("""
"ts","dur","score","bucket","process_name","oom_adj_ts","oom_adj_dur","oom_adj_thread_name","oom_adj_reason","oom_adj_trigger"
- 1737065264829,701108081,925,"cached_app","com.android.providers.calendar",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737066678827,3470211742,935,"cached_app","com.android.imsserviceentitlement",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737066873002,3470017567,945,"cached_app","com.android.carrierconfig",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737067058812,3469831757,955,"cached_app_lmk_first","com.android.messaging",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737067246975,699224817,955,"cached_app_lmk_first","android.process.acore",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737068421919,3468468650,965,"cached_app_lmk_first","com.android.shell",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737068599673,697908135,965,"cached_app_lmk_first","android.process.media",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737068933602,3467956967,975,"cached_app_lmk_first","com.android.gallery3d",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737069091010,3467799559,975,"cached_app_lmk_first","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
- 1737069240534,3467650035,985,"cached_app_lmk_first","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737065264829,701108081,925,"cached","com.android.providers.calendar",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737066678827,3470211742,935,"cached","com.android.imsserviceentitlement",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737066873002,3470017567,945,"cached","com.android.carrierconfig",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737067058812,3469831757,955,"cached","com.android.messaging",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737067246975,699224817,955,"cached","android.process.acore",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737068421919,3468468650,965,"cached","com.android.shell",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737068599673,697908135,965,"cached","android.process.media",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737068933602,3467956967,975,"cached","com.android.gallery3d",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737069091010,3467799559,975,"cached","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737069240534,3467650035,985,"cached","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
"""))
def test_broadcast_minsdk_u(self):
diff --git a/test/trace_processor/diff_tests/stdlib/gpu/tests.py b/test/trace_processor/diff_tests/stdlib/gpu/tests.py
new file mode 100644
index 0000000..2c23e80
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/gpu/tests.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
+
+
+class Gpu(TestSuite):
+
+ def test_gpu_frequency(self):
+ return DiffTestBlueprint(
+ trace=Path('../../metrics/graphics/gpu_frequency_metric.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE gpu.frequency;
+ SELECT *
+ FROM gpu_frequency;
+ """,
+ out=Csv("""
+ "ts","dur","gpu_id","gpu_freq"
+ 200001000000,2000000,0,585000
+ 200003000000,1000000,0,0
+ 200004000000,2000000,0,603000
+ 200002000000,3000000,1,400000
+ 200005000000,1000000,1,758000
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/memory/tests.py b/test/trace_processor/diff_tests/stdlib/memory/tests.py
new file mode 100644
index 0000000..d781b54
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/memory/tests.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# 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 a
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
+
+
+class Memory(TestSuite):
+
+ def test_memory_rss_and_swap_per_process(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_postboot_unlock.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE memory.linux.process;
+
+ SELECT *
+ FROM memory_rss_and_swap_per_process
+ WHERE upid = 1
+ LIMIT 5
+ """,
+ out=Csv("""
+ "ts","dur","upid","pid","process_name","anon_rss","file_rss","shmem_rss","rss","swap","anon_rss_and_swap","rss_and_swap"
+ 37592474220,12993896,1,1982,"com.android.systemui",125865984,"[NULL]","[NULL]","[NULL]","[NULL]",125865984,"[NULL]"
+ 37605468116,1628,1,1982,"com.android.systemui",126050304,"[NULL]","[NULL]","[NULL]","[NULL]",126050304,"[NULL]"
+ 37605469744,1302,1,1982,"com.android.systemui",126050304,"[NULL]",2990080,"[NULL]","[NULL]",126050304,"[NULL]"
+ 37605471046,685791,1,1982,"com.android.systemui",126046208,"[NULL]",2990080,"[NULL]","[NULL]",126046208,"[NULL]"
+ 37606156837,6510,1,1982,"com.android.systemui",126042112,"[NULL]",2990080,"[NULL]","[NULL]",126042112,"[NULL]"
+ """))
+
+ def test_memory_rss_high_watermark_per_process(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_postboot_unlock.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE memory.linux.high_watermark;
+
+ SELECT *
+ FROM memory_rss_high_watermark_per_process
+ WHERE upid = 1
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "ts","dur","upid","pid","process_name","rss_high_watermark"
+ 37592474220,12993896,1,1982,"com.android.systemui",125865984
+ 37605468116,1628,1,1982,"com.android.systemui",126050304
+ 37605469744,333774129,1,1982,"com.android.systemui",129040384
+ 37939243873,120479574,1,1982,"com.android.systemui",372977664
+ 38059723447,936,1,1982,"com.android.systemui",373043200
+ 38059724383,6749186,1,1982,"com.android.systemui",373174272
+ 38066473569,7869426,1,1982,"com.android.systemui",373309440
+ 38074342995,11596761,1,1982,"com.android.systemui",373444608
+ 38085939756,4877848,1,1982,"com.android.systemui",373579776
+ 38090817604,11930827,1,1982,"com.android.systemui",373714944
+ """))
+
+ def test_memory_oom_score_with_rss_and_swap_per_process(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sched_wakeup_trace.atr'),
+ query="""
+ INCLUDE PERFETTO MODULE memory.linux.process;
+ SELECT *
+ FROM memory_oom_score_with_rss_and_swap_per_process
+ WHERE oom_adj_reason IS NOT NULL
+ ORDER BY ts
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "ts","dur","score","bucket","upid","process_name","pid","oom_adj_id","oom_adj_ts","oom_adj_dur","oom_adj_track_id","oom_adj_thread_name","oom_adj_reason","oom_adj_trigger","anon_rss","file_rss","shmem_rss","rss","swap","anon_rss_and_swap","rss_and_swap"
+ 1737065264829,701108081,925,"cached",269,"com.android.providers.calendar",1937,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49229824,57495552,835584,107560960,0,49229824,107560960
+ 1737066678827,2934486383,935,"cached",287,"com.android.imsserviceentitlement",2397,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48881664,57081856,831488,106795008,0,48881664,106795008
+ 1737066873002,2934292208,945,"cached",292,"com.android.carrierconfig",2593,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48586752,49872896,823296,99282944,0,48586752,99282944
+ 1737067058812,2934106398,955,"cached",288,"com.android.messaging",2416,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",54956032,71417856,843776,127217664,0,54956032,127217664
+ 1737067246975,699224817,955,"cached",267,"android.process.acore",1866,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",52498432,72048640,856064,125403136,0,52498432,125403136
+ 1737068421919,2932743291,965,"cached",273,"com.android.shell",2079,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48738304,52056064,823296,101617664,0,48738304,101617664
+ 1737068599673,970398,965,"cached",271,"android.process.media",2003,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49917952,60444672,839680,111202304,0,49917952,111202304
+ 1737068933602,2932231608,975,"cached",286,"com.android.gallery3d",2371,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49561600,54521856,831488,104914944,0,49561600,104914944
+ 1737069091010,682459310,975,"cached",289,"com.android.packageinstaller",2480,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49364992,52539392,827392,102731776,0,49364992,102731776
+ 1737069240534,489635,985,"cached",268,"com.android.managedprovisioning",1868,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",50683904,53985280,815104,105484288,0,50683904,105484288
+ """))
+
+ def test_memory_gpu_per_process(self):
+ return DiffTestBlueprint(
+ trace=Path('../../metrics/graphics/gpu_metric.py'),
+ query="""
+ INCLUDE PERFETTO MODULE memory.android.gpu;
+ SELECT *
+ FROM memory_gpu_per_process;
+ """,
+ out=Csv("""
+ "ts","dur","upid","gpu_memory"
+ 2,2,2,6
+ 4,6,2,8
+ 4,5,1,2
+ 9,1,1,8
+ 6,1,3,7
+ 7,3,3,10
+ """))
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index ad23a5d..f13d42c 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -487,22 +487,24 @@
table_name,
critical_path_utid
FROM _thread_executing_span_critical_path_stack((select utid from thread where tid = 3487), start_ts, end_ts), trace_bounds
- ORDER BY ts
- LIMIT 11
+ WHERE ts = 1737500355691
+ ORDER BY utid, id
""",
out=Csv("""
"id","ts","dur","utid","stack_depth","name","table_name","critical_path_utid"
- 11889,1737349401439,57188,1477,0,"thread_state: R","thread_state",1477
- 11889,1737349401439,57188,1477,1,"[NULL]","thread_state",1477
- 11889,1737349401439,57188,1477,2,"[NULL]","thread_state",1477
- 11889,1737349401439,57188,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
- 11889,1737349401439,57188,1477,4,"thread_name: rs.media.module","thread_state",1477
- 11891,1737349458627,1884896,1477,0,"thread_state: Running","thread_state",1477
- 11891,1737349458627,1884896,1477,1,"[NULL]","thread_state",1477
- 11891,1737349458627,1884896,1477,2,"[NULL]","thread_state",1477
- 11891,1737349458627,1884896,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
- 11891,1737349458627,1884896,1477,4,"thread_name: rs.media.module","thread_state",1477
- 11891,1737349458627,1884896,1477,5,"cpu: 0","thread_state",1477
+ 4271,1737500355691,1456753,1477,5,"bindApplication","slice",1477
+ 13120,1737500355691,1456753,1477,0,"thread_state: S","thread_state",1477
+ 13120,1737500355691,1456753,1477,1,"[NULL]","thread_state",1477
+ 13120,1737500355691,1456753,1477,2,"[NULL]","thread_state",1477
+ 13120,1737500355691,1456753,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
+ 13120,1737500355691,1456753,1477,4,"thread_name: rs.media.module","thread_state",1477
+ 4800,1737500355691,1456753,1498,11,"HIDL::IComponentStore::getStructDescriptors::client","slice",1477
+ 4801,1737500355691,1456753,1498,12,"binder transaction","slice",1477
+ 13648,1737500355691,1456753,1498,6,"blocking thread_state: R+","thread_state",1477
+ 13648,1737500355691,1456753,1498,7,"blocking process_name: com.android.providers.media.module","thread_state",1477
+ 13648,1737500355691,1456753,1498,8,"blocking thread_name: CodecLooper","thread_state",1477
+ 13648,1737500355691,1456753,1498,9,"[NULL]","thread_state",1477
+ 13648,1737500355691,1456753,1498,10,"[NULL]","thread_state",1477
"""))
def test_thread_executing_span_critical_path_graph(self):
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 31972d4..62d1ac6 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -185,6 +185,11 @@
# Keep in sync with Chromium's //third_party/protobuf.
Dependency(
'buildtools/protobuf',
+ # If you revert the below version back to an earlier version of
+ # protobuf, make sure to revert the changes to
+ # //gn/standalone/protoc.py as well.
+ #
+ # This comment can be removed with protobuf is next upreved.
'https://chromium.googlesource.com/external/github.com/protocolbuffers/protobuf.git',
'f0dc78d7e6e331b8c6bb2d5283e06aa26883ca7c', # refs/tags/v21.12
'all',
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index ab29fc1..f729597 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -45,6 +45,7 @@
import {raf} from '../core/raf_scheduler';
import {defaultPlugins} from '../core/default_plugins';
import {HighPrecisionTimeSpan} from './high_precision_time';
+import {PromptOption} from '../frontend/omnibox_manager';
// Every plugin gets its own PluginContext. This is how we keep track
// what each plugin is doing and how we can blame issues on particular
@@ -345,6 +346,13 @@
get trace(): TraceContext {
return globals.traceContext;
}
+
+ async prompt(
+ text: string,
+ options?: PromptOption[] | undefined,
+ ): Promise<string> {
+ return globals.omnibox.prompt(text, options);
+ }
}
function isPinned(trackId: string): boolean {
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index ae40e79..44b4e21 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -57,4 +57,6 @@
'perfetto.ThreadState',
'perfetto.VisualisedArgs',
'org.kernel.LinuxKernelDevices',
+ 'perfetto.TrackUtils',
+ 'com.google.PixelMemory',
];
diff --git a/ui/src/core_plugins/track_utils/index.ts b/ui/src/core_plugins/track_utils/index.ts
new file mode 100644
index 0000000..937ba70
--- /dev/null
+++ b/ui/src/core_plugins/track_utils/index.ts
@@ -0,0 +1,100 @@
+// 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 {Actions} from '../../common/actions';
+import {
+ getTimeSpanOfSelectionOrVisibleWindow,
+ globals,
+} from '../../frontend/globals';
+import {OmniboxMode} from '../../frontend/omnibox_manager';
+import {verticalScrollToTrack} from '../../frontend/scroll_helper';
+import {
+ Plugin,
+ PluginContextTrace,
+ PluginDescriptor,
+ PromptOption,
+} from '../../public';
+
+class TrackUtilsPlugin implements Plugin {
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ ctx.registerCommand({
+ id: 'perfetto.RunQueryInSelectedTimeWindow',
+ name: `Run query in selected time window`,
+ callback: () => {
+ const window = getTimeSpanOfSelectionOrVisibleWindow();
+ globals.omnibox.setMode(OmniboxMode.Query);
+ globals.omnibox.setText(
+ `select where ts >= ${window.start} and ts < ${window.end}`,
+ );
+ globals.omnibox.focusOmnibox(7);
+ },
+ });
+
+ ctx.registerCommand({
+ // Selects & reveals the first track on the timeline with a given URI.
+ id: 'perfetto.FindTrack',
+ name: 'Find track by URI',
+ callback: async () => {
+ const tracks = globals.trackManager.getAllTracks();
+ const options = tracks.map(({uri}): PromptOption => {
+ return {key: uri, displayName: uri};
+ });
+
+ // Sort tracks in a natural sort order
+ const collator = new Intl.Collator('en', {
+ numeric: true,
+ sensitivity: 'base',
+ });
+ const sortedOptions = options.sort((a, b) => {
+ return collator.compare(a.displayName, b.displayName);
+ });
+
+ try {
+ const selectedUri = await ctx.prompt(
+ 'Choose a track...',
+ sortedOptions,
+ );
+
+ // Find the first track with this URI
+ const firstTrack = Object.values(globals.state.tracks).find(
+ ({uri}) => uri === selectedUri,
+ );
+ if (firstTrack) {
+ console.log(firstTrack);
+ verticalScrollToTrack(firstTrack.key, true);
+ const traceTime = globals.stateTraceTimeTP();
+ globals.makeSelection(
+ Actions.selectArea({
+ area: {
+ start: traceTime.start,
+ end: traceTime.end,
+ tracks: [firstTrack.key],
+ },
+ }),
+ );
+ } else {
+ alert(`No tracks with uri ${selectedUri} on the timeline`);
+ }
+ } catch {
+ // Prompt was probably cancelled - do nothing.
+ }
+ },
+ });
+ }
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'perfetto.TrackUtils',
+ plugin: TrackUtilsPlugin,
+};
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 989a331..6c90216 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -18,9 +18,8 @@
import {Trash} from '../base/disposable';
import {findRef} from '../base/dom_utils';
import {FuzzyFinder} from '../base/fuzzy';
-import {assertExists} from '../base/logging';
+import {assertExists, assertUnreachable} from '../base/logging';
import {undoCommonChatAppReplacements} from '../base/string_utils';
-import {duration, Span, time, TimeSpan} from '../base/time';
import {Actions} from '../common/actions';
import {getLegacySelection} from '../common/state';
import {
@@ -30,28 +29,22 @@
TimestampFormat,
} from '../core/timestamp_format';
import {raf} from '../core/raf_scheduler';
-import {Command} from '../public';
-import {Engine} from '../trace_processor/engine';
-import {THREAD_STATE_TRACK_KIND} from '../core_plugins/thread_state';
+import {Command, Engine, addDebugSliceTrack} from '../public';
import {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
import {maybeRenderFullscreenModalDialog} from '../widgets/modal';
import {onClickCopy} from './clipboard';
import {CookieConsent} from './cookie_consent';
-import {globals} from './globals';
+import {getTimeSpanOfSelectionOrVisibleWindow, globals} from './globals';
import {toggleHelp} from './help_modal';
import {Notes} from './notes';
import {Omnibox, OmniboxOption} from './omnibox';
import {addQueryResultsTab} from './query_result_tab';
-import {verticalScrollToTrack} from './scroll_helper';
import {executeSearch} from './search_handler';
import {Sidebar} from './sidebar';
-import {Utid} from './sql_types';
-import {getThreadInfo} from './thread_and_process_info';
import {Topbar} from './topbar';
import {shareTrace} from './trace_attrs';
-import {addDebugSliceTrack} from './debug_tracks';
import {AggregationsTabs} from './aggregation_tab';
import {addSqlTableTab} from './sql_table/tab';
import {SqlTables} from './sql_table/well_known_tables';
@@ -61,8 +54,11 @@
lockSliceSpan,
moveByFocusedFlow,
} from './keyboard_event_handler';
-import {exists} from '../base/utils';
import {publishPermalinkHash} from './publish';
+import {OmniboxMode, PromptOption} from './omnibox_manager';
+import {Utid} from './sql_types';
+import {getThreadInfo} from './thread_and_process_info';
+import {THREAD_STATE_TRACK_KIND} from '../core_plugins/thread_state';
function renderPermalink(): m.Children {
const hash = globals.permalinkHash;
@@ -88,25 +84,6 @@
}
}
-interface PromptOption {
- key: string;
- displayName: string;
-}
-
-interface Prompt {
- text: string;
- options?: PromptOption[];
- resolve(result: string): void;
- reject(): void;
-}
-
-enum OmniboxMode {
- Search,
- Query,
- Command,
- Prompt,
-}
-
const criticalPathSliceColumns = {
ts: 'ts',
dur: 'dur',
@@ -138,14 +115,6 @@
export class App implements m.ClassComponent {
private trash = new Trash();
-
- private omniboxMode: OmniboxMode = OmniboxMode.Search;
- private omniboxText = '';
- private queryText = '';
- private omniboxSelectionIndex = 0;
- private focusOmniboxNextRender = false;
- private pendingCursorPlacement = -1;
- private pendingPrompt?: Prompt;
static readonly OMNIBOX_INPUT_REF = 'omnibox';
private omniboxInputEl?: HTMLInputElement;
private recentCommands: string[] = [];
@@ -164,80 +133,6 @@
return engine;
}
- private enterCommandMode(): void {
- this.omniboxMode = OmniboxMode.Command;
- this.resetOmnibox();
- this.rejectPendingPrompt();
- this.focusOmniboxNextRender = true;
-
- raf.scheduleFullRedraw();
- }
-
- private enterQueryMode(): void {
- this.omniboxMode = OmniboxMode.Query;
- this.resetOmnibox();
- this.rejectPendingPrompt();
- this.focusOmniboxNextRender = true;
-
- raf.scheduleFullRedraw();
- }
-
- private enterSearchMode(focusOmnibox: boolean): void {
- this.omniboxMode = OmniboxMode.Search;
- this.resetOmnibox();
- this.rejectPendingPrompt();
-
- if (focusOmnibox) {
- this.focusOmniboxNextRender = true;
- }
-
- globals.dispatch(Actions.setOmniboxMode({mode: 'SEARCH'}));
-
- raf.scheduleFullRedraw();
- }
-
- // Start a prompt. If options are supplied, the user must pick one from the
- // list, otherwise the input is free-form text.
- private prompt(text: string, options?: PromptOption[]): Promise<string> {
- this.omniboxMode = OmniboxMode.Prompt;
- this.resetOmnibox();
- this.rejectPendingPrompt();
-
- const promise = new Promise<string>((resolve, reject) => {
- this.pendingPrompt = {
- text,
- options,
- resolve,
- reject,
- };
- });
-
- this.focusOmniboxNextRender = true;
- raf.scheduleFullRedraw();
-
- return promise;
- }
-
- // Resolve the pending prompt with a value to return to the prompter.
- private resolvePrompt(value: string): void {
- if (this.pendingPrompt) {
- this.pendingPrompt.resolve(value);
- this.pendingPrompt = undefined;
- }
- this.enterSearchMode(false);
- }
-
- // Reject the prompt outright. Doing this will force the owner of the prompt
- // promise to catch, so only do this when things go seriously wrong.
- // Use |resolvePrompt(null)| to indicate cancellation.
- private rejectPrompt(): void {
- if (this.pendingPrompt) {
- this.pendingPrompt.reject();
- this.pendingPrompt = undefined;
- }
- this.enterSearchMode(false);
- }
-
private getFirstUtidOfSelectionOrVisibleWindow(): number {
const selection = getLegacySelection(globals.state);
if (selection && selection.kind === 'AREA') {
@@ -283,7 +178,7 @@
const promptText = 'Select format...';
try {
- const result = await this.prompt(promptText, options);
+ const result = await globals.omnibox.prompt(promptText, options);
setTimestampFormat(result as TimestampFormat);
raf.scheduleFullRedraw();
} catch {
@@ -305,7 +200,7 @@
const promptText = 'Select duration precision mode...';
try {
- const result = await this.prompt(promptText, options);
+ const result = await globals.omnibox.prompt(promptText, options);
setDurationPrecision(result as DurationPrecision);
raf.scheduleFullRedraw();
} catch {
@@ -452,19 +347,19 @@
{
id: 'perfetto.OpenCommandPalette',
name: 'Open command palette',
- callback: () => this.enterCommandMode(),
+ callback: () => globals.omnibox.setMode(OmniboxMode.Command),
defaultHotkey: '!Mod+Shift+P',
},
{
id: 'perfetto.RunQuery',
name: 'Run query',
- callback: () => this.enterQueryMode(),
+ callback: () => globals.omnibox.setMode(OmniboxMode.Query),
defaultHotkey: '!Mod+O',
},
{
id: 'perfetto.Search',
name: 'Search',
- callback: () => this.enterSearchMode(true),
+ callback: () => globals.omnibox.setMode(OmniboxMode.Search),
defaultHotkey: '/',
},
{
@@ -474,16 +369,6 @@
defaultHotkey: '?',
},
{
- id: 'perfetto.RunQueryInSelectedTimeWindow',
- name: `Run query in selected time window`,
- callback: () => {
- const window = getTimeSpanOfSelectionOrVisibleWindow();
- this.enterQueryMode();
- this.queryText = `select where ts >= ${window.start} and ts < ${window.end}`;
- this.pendingCursorPlacement = 7;
- },
- },
- {
id: 'perfetto.CopyTimeWindow',
name: `Copy selected time window to clipboard`,
callback: () => {
@@ -493,56 +378,6 @@
},
},
{
- // Selects & reveals the first track on the timeline with a given URI.
- id: 'perfetto.FindTrack',
- name: 'Find track by URI',
- callback: async () => {
- const tracks = globals.trackManager.getAllTracks();
- const options = tracks.map(({uri}): PromptOption => {
- return {key: uri, displayName: uri};
- });
-
- // Sort tracks in a natural sort order
- const collator = new Intl.Collator('en', {
- numeric: true,
- sensitivity: 'base',
- });
- const sortedOptions = options.sort((a, b) => {
- return collator.compare(a.displayName, b.displayName);
- });
-
- try {
- const selectedUri = await this.prompt(
- 'Choose a track...',
- sortedOptions,
- );
-
- // Find the first track with this URI
- const firstTrack = Object.values(globals.state.tracks).find(
- ({uri}) => uri === selectedUri,
- );
- if (firstTrack) {
- console.log(firstTrack);
- verticalScrollToTrack(firstTrack.key, true);
- const traceTime = globals.stateTraceTimeTP();
- globals.makeSelection(
- Actions.selectArea({
- area: {
- start: traceTime.start,
- end: traceTime.end,
- tracks: [firstTrack.key],
- },
- }),
- );
- } else {
- alert(`No tracks with uri ${selectedUri} on the timeline`);
- }
- } catch {
- // Prompt was probably cancelled - do nothing.
- }
- },
- },
- {
id: 'perfetto.FocusSelection',
name: 'Focus current selection',
callback: () => findCurrentSelection(),
@@ -653,18 +488,6 @@
return this.cmds;
}
- private rejectPendingPrompt() {
- if (this.pendingPrompt) {
- this.pendingPrompt.reject();
- this.pendingPrompt = undefined;
- }
- }
-
- private resetOmnibox() {
- this.omniboxText = '';
- this.omniboxSelectionIndex = 0;
- }
-
private renderOmnibox(): m.Children {
const msgTTL = globals.state.status.timestamp + 1 - Date.now() / 1e3;
const engineIsBusy =
@@ -681,22 +504,23 @@
);
}
- if (this.omniboxMode === OmniboxMode.Command) {
+ const omniboxMode = globals.omnibox.omniboxMode;
+
+ if (omniboxMode === OmniboxMode.Command) {
return this.renderCommandOmnibox();
- } else if (this.omniboxMode === OmniboxMode.Prompt) {
+ } else if (omniboxMode === OmniboxMode.Prompt) {
return this.renderPromptOmnibox();
- } else if (this.omniboxMode === OmniboxMode.Query) {
+ } else if (omniboxMode === OmniboxMode.Query) {
return this.renderQueryOmnibox();
- } else if (this.omniboxMode === OmniboxMode.Search) {
+ } else if (omniboxMode === OmniboxMode.Search) {
return this.renderSearchOmnibox();
} else {
- const x: never = this.omniboxMode;
- throw new Error(`Unhandled omnibox mode ${x}`);
+ assertUnreachable(omniboxMode);
}
}
renderPromptOmnibox(): m.Children {
- const prompt = assertExists(this.pendingPrompt);
+ const prompt = assertExists(globals.omnibox.pendingPrompt);
let options: OmniboxOption[] | undefined = undefined;
@@ -705,7 +529,7 @@
prompt.options,
({displayName}) => displayName,
);
- const result = fuzzy.find(this.omniboxText);
+ const result = fuzzy.find(globals.omnibox.text);
options = result.map((result) => {
return {
key: result.item.key,
@@ -715,27 +539,27 @@
}
return m(Omnibox, {
- value: this.omniboxText,
+ value: globals.omnibox.text,
placeholder: prompt.text,
inputRef: App.OMNIBOX_INPUT_REF,
extraClasses: 'prompt-mode',
closeOnOutsideClick: true,
options,
- selectedOptionIndex: this.omniboxSelectionIndex,
+ selectedOptionIndex: globals.omnibox.omniboxSelectionIndex,
onSelectedOptionChanged: (index) => {
- this.omniboxSelectionIndex = index;
+ globals.omnibox.setOmniboxSelectionIndex(index);
raf.scheduleFullRedraw();
},
onInput: (value) => {
- this.omniboxText = value;
- this.omniboxSelectionIndex = 0;
+ globals.omnibox.setText(value);
+ globals.omnibox.setOmniboxSelectionIndex(0);
raf.scheduleFullRedraw();
},
onSubmit: (value, _alt) => {
- this.resolvePrompt(value);
+ globals.omnibox.resolvePrompt(value);
},
onClose: () => {
- this.rejectPrompt();
+ globals.omnibox.rejectPrompt();
},
});
}
@@ -744,7 +568,7 @@
const cmdMgr = globals.commandManager;
// Fuzzy-filter commands by the filter string.
- const filteredCmds = cmdMgr.fuzzyFilterCommands(this.omniboxText);
+ const filteredCmds = cmdMgr.fuzzyFilterCommands(globals.omnibox.text);
// Create an array of commands with attached heuristics from the recent
// command register.
@@ -775,36 +599,35 @@
});
return m(Omnibox, {
- value: this.omniboxText,
+ value: globals.omnibox.text,
placeholder: 'Filter commands...',
inputRef: App.OMNIBOX_INPUT_REF,
extraClasses: 'command-mode',
options,
closeOnSubmit: true,
closeOnOutsideClick: true,
- selectedOptionIndex: this.omniboxSelectionIndex,
+ selectedOptionIndex: globals.omnibox.omniboxSelectionIndex,
onSelectedOptionChanged: (index) => {
- this.omniboxSelectionIndex = index;
+ globals.omnibox.setOmniboxSelectionIndex(index);
raf.scheduleFullRedraw();
},
onInput: (value) => {
- this.omniboxText = value;
- this.omniboxSelectionIndex = 0;
+ globals.omnibox.setText(value);
+ globals.omnibox.setOmniboxSelectionIndex(0);
raf.scheduleFullRedraw();
},
onClose: () => {
if (this.omniboxInputEl) {
this.omniboxInputEl.blur();
}
- this.enterSearchMode(false);
- raf.scheduleFullRedraw();
+ globals.omnibox.reset();
},
onSubmit: (key: string) => {
this.addRecentCommand(key);
cmdMgr.runCommand(key);
},
onGoBack: () => {
- this.enterSearchMode(false);
+ globals.omnibox.reset();
},
});
}
@@ -820,13 +643,13 @@
renderQueryOmnibox(): m.Children {
const ph = 'e.g. select * from sched left join thread using(utid) limit 10';
return m(Omnibox, {
- value: this.queryText,
+ value: globals.omnibox.text,
placeholder: ph,
inputRef: App.OMNIBOX_INPUT_REF,
extraClasses: 'query-mode',
onInput: (value) => {
- this.queryText = value;
+ globals.omnibox.setText(value);
raf.scheduleFullRedraw();
},
onSubmit: (query, alt) => {
@@ -838,15 +661,15 @@
addQueryResultsTab(config, tag);
},
onClose: () => {
- this.queryText = '';
+ globals.omnibox.setText('');
if (this.omniboxInputEl) {
this.omniboxInputEl.blur();
}
- this.enterSearchMode(false);
+ globals.omnibox.reset();
raf.scheduleFullRedraw();
},
onGoBack: () => {
- this.enterSearchMode(false);
+ globals.omnibox.reset();
},
});
}
@@ -863,10 +686,10 @@
onInput: (value, prev) => {
if (prev === '') {
if (value === '>') {
- this.enterCommandMode();
+ globals.omnibox.setMode(OmniboxMode.Command);
return;
} else if (value === ':') {
- this.enterQueryMode();
+ globals.omnibox.setMode(OmniboxMode.Query);
return;
}
}
@@ -985,32 +808,20 @@
}
private maybeFocusOmnibar() {
- if (this.focusOmniboxNextRender) {
+ if (globals.omnibox.focusOmniboxNextRender) {
const omniboxEl = this.omniboxInputEl;
if (omniboxEl) {
omniboxEl.focus();
- if (this.pendingCursorPlacement === -1) {
+ if (globals.omnibox.pendingCursorPlacement === undefined) {
omniboxEl.select();
} else {
omniboxEl.setSelectionRange(
- this.pendingCursorPlacement,
- this.pendingCursorPlacement,
+ globals.omnibox.pendingCursorPlacement,
+ globals.omnibox.pendingCursorPlacement,
);
- this.pendingCursorPlacement = -1;
}
}
- this.focusOmniboxNextRender = false;
+ globals.omnibox.clearOmniboxFocusFlag();
}
}
}
-
-// Returns the time span of the current selection, or the visible window if
-// there is no current selection.
-function getTimeSpanOfSelectionOrVisibleWindow(): Span<time, duration> {
- const range = globals.findTimeRangeOfSelection();
- if (exists(range)) {
- return new TimeSpan(range.start, range.end);
- } else {
- return globals.stateVisibleTime();
- }
-}
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index f2cea37..835292e 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -36,6 +36,7 @@
// Here we rely on the exception message from onCannotGrowMemory function
if (
err.message.includes('Cannot enlarge memory') ||
+ err.stack.some((entry) => entry.name.includes('OutOfMemoryHandler')) ||
err.stack.some((entry) => entry.name.includes('_emscripten_resize_heap')) ||
err.stack.some((entry) => entry.name.includes('sbrk')) ||
/^out of memory$/m.exec(err.message)
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 504b46f..46d6cbe 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -56,6 +56,7 @@
import {PxSpan, TimeScale} from './time_scale';
import {SelectionManager, LegacySelection} from '../core/selection_manager';
import {exists} from '../base/utils';
+import {OmniboxManager} from './omnibox_manager';
const INSTANT_FOCUS_DURATION = 1n;
const INCOMPLETE_SLICE_DURATION = 30_000n;
@@ -299,6 +300,8 @@
private _selectionManager = new SelectionManager(this._store);
private _hasFtrace: boolean = false;
+ omnibox = new OmniboxManager();
+
scrollToTrackKey?: string | number;
httpRpcState: HttpRpcState = {connected: false};
newVersionAvailable = false;
@@ -868,4 +871,15 @@
}
}
+// Returns the time span of the current selection, or the visible window if
+// there is no current selection.
+export function getTimeSpanOfSelectionOrVisibleWindow(): Span<time, duration> {
+ const range = globals.findTimeRangeOfSelection();
+ if (exists(range)) {
+ return new TimeSpan(range.start, range.end);
+ } else {
+ return globals.stateVisibleTime();
+ }
+}
+
export const globals = new Globals();
diff --git a/ui/src/frontend/omnibox_manager.ts b/ui/src/frontend/omnibox_manager.ts
new file mode 100644
index 0000000..41d795b
--- /dev/null
+++ b/ui/src/frontend/omnibox_manager.ts
@@ -0,0 +1,156 @@
+// 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 {raf} from '../core/raf_scheduler';
+
+export enum OmniboxMode {
+ Search,
+ Query,
+ Command,
+ Prompt,
+}
+
+export interface PromptOption {
+ key: string;
+ displayName: string;
+}
+
+interface Prompt {
+ text: string;
+ options?: PromptOption[];
+ resolve(result: string): void;
+ reject(): void;
+}
+
+const defaultMode = OmniboxMode.Search;
+
+export class OmniboxManager {
+ private _omniboxMode = defaultMode;
+ private _focusOmniboxNextRender = false;
+ private _pendingCursorPlacement?: number;
+ private _pendingPrompt?: Prompt;
+ private _text = '';
+ private _omniboxSelectionIndex = 0;
+
+ get omniboxMode(): OmniboxMode {
+ return this._omniboxMode;
+ }
+
+ get pendingPrompt(): Prompt | undefined {
+ return this._pendingPrompt;
+ }
+
+ get text(): string {
+ return this._text;
+ }
+
+ get omniboxSelectionIndex(): number {
+ return this._omniboxSelectionIndex;
+ }
+
+ get focusOmniboxNextRender(): boolean {
+ return this._focusOmniboxNextRender;
+ }
+
+ get pendingCursorPlacement(): number | undefined {
+ return this._pendingCursorPlacement;
+ }
+
+ setText(value: string): void {
+ this._text = value;
+ }
+
+ setOmniboxSelectionIndex(index: number): void {
+ this._omniboxSelectionIndex = index;
+ }
+
+ focusOmnibox(cursorPlacement?: number): void {
+ this._focusOmniboxNextRender = true;
+ this._pendingCursorPlacement = cursorPlacement;
+ raf.scheduleFullRedraw();
+ }
+
+ clearOmniboxFocusFlag(): void {
+ this._focusOmniboxNextRender = false;
+ this._pendingCursorPlacement = undefined;
+ }
+
+ setMode(mode: OmniboxMode): void {
+ this._omniboxMode = mode;
+ this._focusOmniboxNextRender = true;
+ this.resetOmniboxText();
+ this.rejectPendingPrompt();
+ raf.scheduleFullRedraw();
+ }
+
+ // Start a prompt. If options are supplied, the user must pick one from the
+ // list, otherwise the input is free-form text.
+ prompt(text: string, options?: PromptOption[]): Promise<string> {
+ this._omniboxMode = OmniboxMode.Prompt;
+ this.resetOmniboxText();
+ this.rejectPendingPrompt();
+
+ const promise = new Promise<string>((resolve, reject) => {
+ this._pendingPrompt = {
+ text,
+ options,
+ resolve,
+ reject,
+ };
+ });
+
+ this._focusOmniboxNextRender = true;
+ raf.scheduleFullRedraw();
+
+ return promise;
+ }
+
+ // Resolve the pending prompt with a value to return to the prompter.
+ resolvePrompt(value: string): void {
+ if (this._pendingPrompt) {
+ this._pendingPrompt.resolve(value);
+ this._pendingPrompt = undefined;
+ }
+ this.setMode(OmniboxMode.Search);
+ }
+
+ // Reject the prompt outright. Doing this will force the owner of the prompt
+ // promise to catch, so only do this when things go seriously wrong.
+ // Use |resolvePrompt(null)| to indicate cancellation.
+ rejectPrompt(): void {
+ if (this._pendingPrompt) {
+ this._pendingPrompt.reject();
+ this._pendingPrompt = undefined;
+ }
+ this.setMode(OmniboxMode.Search);
+ }
+
+ reset(): void {
+ this.setMode(defaultMode);
+ this.resetOmniboxText();
+ raf.scheduleFullRedraw();
+ }
+
+ private rejectPendingPrompt() {
+ if (this._pendingPrompt) {
+ this._pendingPrompt.reject();
+ this._pendingPrompt = undefined;
+ }
+ }
+
+ private resetOmniboxText() {
+ this._text = '';
+ this._omniboxSelectionIndex = 0;
+ }
+}
diff --git a/ui/src/plugins/com.google.PixelMemory/OWNERS b/ui/src/plugins/com.google.PixelMemory/OWNERS
new file mode 100644
index 0000000..249152a
--- /dev/null
+++ b/ui/src/plugins/com.google.PixelMemory/OWNERS
@@ -0,0 +1 @@
+liumartin@google.com
diff --git a/ui/src/plugins/com.google.PixelMemory/index.ts b/ui/src/plugins/com.google.PixelMemory/index.ts
new file mode 100644
index 0000000..c79faf2
--- /dev/null
+++ b/ui/src/plugins/com.google.PixelMemory/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 {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
+
+import {addDebugCounterTrack} from '../../frontend/debug_tracks';
+
+class PixelMemory implements Plugin {
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ ctx.registerCommand({
+ id: 'dev.perfetto.PixelMemory#ShowTotalMemory',
+ name: 'Add tracks: show a process total memory',
+ callback: async (pid) => {
+ if (pid === undefined) {
+ pid = prompt('Enter a process pid', '');
+ if (pid === null) return;
+ }
+ const RSS_ALL = `
+ INCLUDE PERFETTO MODULE memory.linux.process;
+ INCLUDE PERFETTO MODULE memory.android.gpu;
+
+ DROP TABLE IF EXISTS process_mem_rss_anon_file_shmem_swap_gpu;
+
+ CREATE VIRTUAL TABLE process_mem_rss_anon_file_shmem_swap_gpu
+ USING
+ SPAN_OUTER_JOIN(
+ memory_gpu_per_process PARTITIONED upid, memory_rss_and_swap_per_process PARTITIONED upid);
+ `;
+ await ctx.engine.query(RSS_ALL);
+ await addDebugCounterTrack(
+ {
+ sqlSource: `
+ SELECT
+ ts,
+ COALESCE(rss_and_swap, 0) + COALESCE(gpu_memory, 0) AS value
+ FROM process_mem_rss_anon_file_shmem_swap_gpu
+ WHERE pid = ${pid}
+ `,
+ columns: ['ts', 'value'],
+ },
+ pid + '_rss_anon_file_swap_shmem_gpu',
+ {ts: 'ts', value: 'value'},
+ );
+ },
+ });
+ }
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'com.google.PixelMemory',
+ plugin: PixelMemory,
+};
diff --git a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
index 74f2554..2ebd860 100644
--- a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
+++ b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import m from 'mithril';
+
import {
Plugin,
PluginContext,
@@ -19,17 +21,23 @@
PluginDescriptor,
} from '../../public';
import {duration, Span, Time, time, TimeSpan} from '../../base/time';
+import {redrawModal, showModal} from '../../widgets/modal';
const PLUGIN_ID = 'dev.perfetto.TimelineSync';
const DEFAULT_BROADCAST_CHANNEL = `${PLUGIN_ID}#broadcastChannel`;
const VIEWPORT_UPDATE_THROTTLE_TIME_FOR_SENDING_AFTER_RECEIVING_MS = 1_000;
const BIGINT_PRECISION_MULTIPLIER = 1_000_000_000n;
+const ADVERTISE_PERIOD_MS = 15_000;
+const DEFAULT_SESSION_ID = 1;
type ClientId = number;
+type SessionId = number;
/**
* Synchronizes the timeline of 2 or more perfetto traces.
*
- * To trigger the sync, the command needs to be enabled.
+ * To trigger the sync, the command needs to be executed on one tab. It will
+ * prompt a list of other tabs to keep in sync. Each tab advertise itself
+ * on a BroadcastChannel upon trace load.
*
* This is able to sync between traces recorded at different times, even if
* their durations don't match. The initial viewport bound for each trace is
@@ -38,11 +46,17 @@
class TimelineSync implements Plugin {
private _chan?: BroadcastChannel;
private _ctx?: PluginContextTrace;
+ private _traceLoadTime = 0;
// Attached to broadcast messages to allow other windows to remap viewports.
- private _clientId: ClientId = 0;
+ private readonly _clientId: ClientId = Math.floor(Math.random() * 1_000_000);
// Used to throttle sending updates after one has been received.
private _lastReceivedUpdateMillis: number = 0;
private _lastViewportBounds?: ViewportBounds;
+ private _advertisedClients = new Map<ClientId, ClientInfo>();
+ private _sessionId: SessionId = 0;
+ // Used when the url passes ?dev.perfetto.TimelineSync:enable to auto-enable
+ // timeline sync on trace load.
+ private _sessionidFromUrl: SessionId = 0;
// Contains the Viewport bounds of this window when it received the first sync
// message from another one. This is used to re-scale timestamps, so that we
@@ -53,42 +67,169 @@
ViewportBoundsSnapshot
>();
- private _active: boolean = false;
-
onActivate(ctx: PluginContext): void {
ctx.registerCommand({
id: `dev.perfetto.SplitScreen#enableTimelineSync`,
- name: 'Enable timeline sync with open windows',
- callback: this.enableTimelineSync.bind(this),
+ name: 'Enable timeline sync with other Perfetto UI tabs',
+ callback: () => this.showTimelineSyncDialog(),
});
ctx.registerCommand({
id: `dev.perfetto.SplitScreen#disableTimelineSync`,
name: 'Disable timeline sync',
- callback: this.disableTimelineSync.bind(this),
+ callback: () => this.disableTimelineSync(this._sessionId),
});
+
+ // Start advertising this tab. This allows the command run in other
+ // instances to discover us.
+ this._chan = new BroadcastChannel(DEFAULT_BROADCAST_CHANNEL);
+ this._chan.onmessage = this.onmessage.bind(this);
+ document.addEventListener('visibilitychange', () => this.advertise());
+ setInterval(() => this.advertise(), ADVERTISE_PERIOD_MS);
+
+ // Allow auto-enabling of timeline sync from the URI. The user can
+ // optionally specify a session id, otherwise we just use a default one.
+ const m = /dev.perfetto.TimelineSync:enable(=\d+)?/.exec(location.hash);
+ if (m !== null) {
+ this._sessionidFromUrl = m[1]
+ ? parseInt(m[1].substring(1))
+ : DEFAULT_SESSION_ID;
+ }
}
onDeactivate(_: PluginContext) {
- this.disableTimelineSync();
+ this.disableTimelineSync(this._sessionId);
}
async onTraceLoad(ctx: PluginContextTrace) {
this._ctx = ctx;
+ this._traceLoadTime = Date.now();
+ this.advertise();
+ if (this._sessionidFromUrl !== 0) {
+ this.enableTimelineSync(this._sessionidFromUrl);
+ }
}
- private enableTimelineSync() {
- this._active = true;
+ async onTraceUnload(_: PluginContextTrace) {
+ this.disableTimelineSync(this._sessionId);
+ this._ctx = undefined;
+ }
+
+ private advertise() {
+ if (this._ctx === undefined) return; // Don't advertise if no trace loaded.
+ this._chan?.postMessage({
+ perfettoSync: {
+ cmd: 'MSG_ADVERTISE',
+ title: document.title,
+ traceLoadTime: this._traceLoadTime,
+ },
+ clientId: this._clientId,
+ } as SyncMessage);
+ }
+
+ private showTimelineSyncDialog() {
+ const selectedClients = new Array<ClientId>();
+
+ // This nested function is invoked when the modal dialog buton is pressed.
+ const doStartSession = () => {
+ // Disable any prior session.
+ this.disableTimelineSync(this._sessionId);
+
+ const clients = selectedClients.concat(this._clientId);
+ this._sessionId = Math.floor(Math.random() * 1_000_000);
+ this._chan?.postMessage({
+ perfettoSync: {
+ cmd: 'MSG_SESSION_START',
+ sessionId: this._sessionId,
+ clients: clients,
+ },
+ clientId: this._clientId,
+ } as SyncMessage);
+ this._initialBoundsForSibling.clear();
+ this.scheduleViewportUpdateMessage();
+ };
+
+ // The function below is called on every mithril render pass. It's important
+ // that this function re-computes the list of other clients on every pass.
+ // The user will go to other tabs (which causes an advertise due to the
+ // visibilitychange listener) and come back on here while the modal dialog
+ // is still being displayed.
+ const renderModalContents = (): m.Children => {
+ const children: m.Children = [];
+ this.purgeInactiveClients();
+ const clients = Array.from(this._advertisedClients.entries());
+ clients.sort((a, b) => b[1].traceLoadTime - a[1].traceLoadTime);
+ for (const [clientId, info] of clients) {
+ const opened = new Date(info.traceLoadTime).toLocaleTimeString();
+ const attrs: {value: number; selected?: boolean} = {value: clientId};
+ if (this._advertisedClients.size === 1) {
+ attrs.selected = true;
+ }
+ children.push(m('option', attrs, `${info.title} (${opened})`));
+ }
+ return m(
+ 'div',
+ {style: 'display: flex; flex-direction: column;'},
+ m(
+ 'div',
+ 'Select the perfetto UI tab(s) you want to keep in sync ' +
+ '(Ctrl+Click to select many).',
+ ),
+ m(
+ 'div',
+ "If you don't see the trace listed here, temporarily focus the " +
+ 'corresponding browser tab and then come back here.',
+ ),
+ m(
+ 'select[multiple=multiple][size=8]',
+ {
+ onchange: (e: Event) => {
+ selectedClients.splice(0);
+ const sel = (e.target as HTMLSelectElement).selectedOptions;
+ for (let i = 0; i < sel.length; i++) {
+ const clientId = parseInt(sel[i].value);
+ if (!isNaN(clientId)) selectedClients.push(clientId);
+ }
+ },
+ },
+ children,
+ ),
+ );
+ };
+
+ showModal({
+ title: 'Synchronize timeline across several tabs',
+ content: renderModalContents,
+ buttons: [
+ {
+ primary: true,
+ text: `Synchronize timelines`,
+ action: doStartSession,
+ },
+ ],
+ });
+ }
+
+ private enableTimelineSync(sessionId: SessionId) {
+ if (sessionId === this._sessionId) return; // Already in this session id.
+ this._sessionId = sessionId;
this._initialBoundsForSibling.clear();
- this._clientId = this.generateClientId();
- this._chan = new BroadcastChannel(DEFAULT_BROADCAST_CHANNEL);
- this._chan.onmessage = this.onmessage.bind(this);
this.scheduleViewportUpdateMessage();
}
- private disableTimelineSync() {
- this._active = false;
+ private disableTimelineSync(sessionId: SessionId, skipMsg = false) {
+ if (sessionId !== this._sessionId || this._sessionId === 0) return;
+
+ if (!skipMsg) {
+ this._chan?.postMessage({
+ perfettoSync: {
+ cmd: 'MSG_SESSION_STOP',
+ sessionId: this._sessionId,
+ },
+ clientId: this._clientId,
+ } as SyncMessage);
+ }
+ this._sessionId = 0;
this._initialBoundsForSibling.clear();
- this._chan?.close();
}
private shouldThrottleViewportUpdates() {
@@ -99,7 +240,7 @@
}
private scheduleViewportUpdateMessage() {
- if (!this._active) return;
+ if (!this.active) return;
const currentViewport = this.getCurrentViewportBounds();
if (
(!this._lastViewportBounds ||
@@ -116,6 +257,7 @@
this._chan?.postMessage({
perfettoSync: {
cmd: 'MSG_SET_VIEWPORT',
+ sessionId: this._sessionId,
viewportBounds,
},
clientId: this._clientId,
@@ -123,12 +265,35 @@
}
private onmessage(msg: MessageEvent) {
+ if (this._ctx === undefined) return; // Trace unloaded
if (!('perfettoSync' in msg.data)) return;
const msgData = msg.data as SyncMessage;
const sync = msgData.perfettoSync;
switch (sync.cmd) {
+ case 'MSG_ADVERTISE':
+ if (msgData.clientId !== this._clientId) {
+ this._advertisedClients.set(msgData.clientId, {
+ title: sync.title,
+ traceLoadTime: sync.traceLoadTime,
+ lastHeartbeat: Date.now(),
+ });
+ this.purgeInactiveClients();
+ redrawModal();
+ }
+ break;
+ case 'MSG_SESSION_START':
+ if (sync.clients.includes(this._clientId)) {
+ this.enableTimelineSync(sync.sessionId);
+ }
+ break;
+ case 'MSG_SESSION_STOP':
+ this.disableTimelineSync(sync.sessionId, /* skipMsg= */ true);
+ break;
case 'MSG_SET_VIEWPORT':
- this.onViewportSyncReceived(sync.viewportBounds, msgData.clientId);
+ if (sync.sessionId === this._sessionId) {
+ this.onViewportSyncReceived(sync.viewportBounds, msgData.clientId);
+ }
+ break;
}
}
@@ -136,6 +301,7 @@
requestViewBounds: ViewportBounds,
source: ClientId,
) {
+ if (!this.active) return;
this.cacheSiblingInitialBoundIfNeeded(requestViewBounds, source);
const remappedViewport = this.remapViewportBounds(
requestViewBounds,
@@ -222,8 +388,17 @@
return this._ctx!.timeline.viewport;
}
- private generateClientId(): ClientId {
- return Math.floor(Math.random() * 1_000_000);
+ private purgeInactiveClients() {
+ const now = Date.now();
+ const TIMEOUT_MS = 30_000;
+ for (const [clientId, info] of this._advertisedClients.entries()) {
+ if (now - info.lastHeartbeat < TIMEOUT_MS) continue;
+ this._advertisedClients.delete(clientId);
+ }
+ }
+
+ private get active() {
+ return this._sessionId !== 0;
}
}
@@ -236,17 +411,45 @@
interface MsgSetViewport {
cmd: 'MSG_SET_VIEWPORT';
+ sessionId: SessionId;
viewportBounds: ViewportBounds;
}
+interface MsgAdvertise {
+ cmd: 'MSG_ADVERTISE';
+ title: string;
+ traceLoadTime: number;
+}
+
+interface MsgSessionStart {
+ cmd: 'MSG_SESSION_START';
+ sessionId: SessionId;
+ clients: ClientId[];
+}
+
+interface MsgSessionStop {
+ cmd: 'MSG_SESSION_STOP';
+ sessionId: SessionId;
+}
+
// In case of new messages, they should be "or-ed" here.
-type SyncMessages = MsgSetViewport;
+type SyncMessages =
+ | MsgSetViewport
+ | MsgAdvertise
+ | MsgSessionStart
+ | MsgSessionStop;
interface SyncMessage {
perfettoSync: SyncMessages;
clientId: ClientId;
}
+interface ClientInfo {
+ title: string;
+ lastHeartbeat: number; // Datetime.now() of the last MSG_ADVERTISE.
+ traceLoadTime: number; // Datetime.now() of the onTraceLoad().
+}
+
export const plugin: PluginDescriptor = {
pluginId: PLUGIN_ID,
plugin: TimelineSync,
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 91f2b36..9e540fd 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -23,6 +23,7 @@
import {Engine} from '../trace_processor/engine';
import {UntypedEventSet} from '../core/event_set';
import {TraceContext} from '../frontend/globals';
+import {PromptOption} from '../frontend/omnibox_manager';
export {Engine} from '../trace_processor/engine';
export {
@@ -35,6 +36,7 @@
} from '../trace_processor/query_result';
export {BottomTabToSCSAdapter} from './utils';
export {createStore, Migrate, Store} from '../base/store';
+export {PromptOption} from '../frontend/omnibox_manager';
// This is a temporary fix until this is available in the plugin API.
export {
@@ -435,6 +437,8 @@
mountStore<T>(migrate: Migrate<T>): Store<T>;
trace: TraceContext;
+
+ prompt(text: string, options?: PromptOption[]): Promise<string>;
}
export interface Plugin {
diff --git a/ui/src/widgets/modal.ts b/ui/src/widgets/modal.ts
index 778f07e..3c85652 100644
--- a/ui/src/widgets/modal.ts
+++ b/ui/src/widgets/modal.ts
@@ -228,6 +228,15 @@
return returnedClosePromise;
}
+// Technically we don't need to redraw the whole app, but it's the more
+// pragmatic option. This is exposed to keep the plugin code more clear, so it's
+// evident why a redraw is requested.
+export function redrawModal() {
+ if (currentModal !== undefined) {
+ scheduleFullRedraw();
+ }
+}
+
// Closes the full-screen modal dialog (if any).
// `key` is optional: if provided it will close the modal dialog only if the key
// matches. This is to avoid accidentally closing another dialog that popped